Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(24)

Unified Diff: tools/binary_size/java/src/org/chromium/tools/binary_size/Addr2LineWorkerPool.java

Issue 258633003: Graphical version of the run_binary_size_analysis tool. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Made the code fully pylint clean. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/binary_size/java/src/org/chromium/tools/binary_size/Addr2LineWorkerPool.java
diff --git a/tools/binary_size/java/src/org/chromium/tools/binary_size/Addr2LineWorkerPool.java b/tools/binary_size/java/src/org/chromium/tools/binary_size/Addr2LineWorkerPool.java
deleted file mode 100644
index 626e7e42dc70255c25c2ef3c0489d1dc602fae6b..0000000000000000000000000000000000000000
--- a/tools/binary_size/java/src/org/chromium/tools/binary_size/Addr2LineWorkerPool.java
+++ /dev/null
@@ -1,469 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.tools.binary_size;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * A pool of workers that run 'addr2line', looking up source locations for
- * addresses that are fed into the pool via a queue. As address lookups
- * complete, records are placed onto an output queue.
- */
-class Addr2LineWorkerPool {
- private static final Charset sAscii = Charset.forName("US-ASCII");
- private final Addr2LineWorker[] mWorkers;
- private final ArrayBlockingQueue<Record> mRecordsIn = new ArrayBlockingQueue<Record>(1000);
- private final Queue<Record> mRecordsOut = new ConcurrentLinkedQueue<Record>();
- private final CountDownLatch mCompletionLatch;
- private final String mAddr2linePath;
- private final String mLibraryPath;
- private final boolean mDisambiguate;
- private final boolean mDedupe;
- private final String stripLocation;
- private final ConcurrentMap<Long, Record> mAddressesSeen =
- new ConcurrentHashMap<Long, Record>(100000, 0.75f, 32);
- private volatile Map<String,String> mFileLookupTable = null;
- private final AtomicInteger mDisambiguationSuccessCount = new AtomicInteger(0);
- private final AtomicInteger mDisambiguationFailureCount = new AtomicInteger(0);
- private final AtomicInteger mDedupeCount = new AtomicInteger(0);
-
- /**
- * These are the suffixes of files that we are potentially interested in
- * searching during symbol lookups when disambiguation is enabled. Anything
- * that could theoretically end up being linked into the library file
- * should be listed here.
- * <p>
- * IMPORTANT: All of these should be lowercase. When doing comparisons,
- * always lowercase the file name before attempting the match.
- */
- private static final String[] INTERESTING_FILE_ENDINGS = new String[]{
- ".c", ".cc", ".h", ".cp", ".cpp", ".cxx", ".c++", ".asm", ".inc", ".s", ".hxx"
- };
-
-
- Addr2LineWorkerPool(final int size,
- final String addr2linePath, final String libraryPath,
- final boolean disambiguate, final boolean dedupe)
- throws IOException {
- this.mAddr2linePath = addr2linePath;
- this.mLibraryPath = libraryPath;
- this.mDisambiguate = disambiguate;
- this.mDedupe = dedupe;
-
- // Prepare disambiguation table if necessary. This must be done
- // before launching the threads in the processing pool for visibility.
- if (disambiguate) {
- try {
- createDisambiguationTable();
- } catch (IOException e) {
- throw new RuntimeException("Can't create lookup table", e);
- }
- }
-
- // The library is in, e.g.: src/out/Release
- // Convert all output paths to be relative to src.
- // Strip everything up to and including "src/".
- String canonical = new File(libraryPath).getCanonicalPath();
- int end = canonical.lastIndexOf("/src/");
- if (end < 0) {
- // Shouldn't happen if the library exists.
- throw new RuntimeException("Bad library path: " + libraryPath +
- ". Library is expected to be within a build directory.");
- }
- stripLocation = canonical.substring(0, end + "/src/".length());
-
- mWorkers = new Addr2LineWorker[size];
- mCompletionLatch = new CountDownLatch(size);
- for (int x = 0; x < mWorkers.length; x++) {
- mWorkers[x] = new Addr2LineWorker();
- }
- }
-
- void submit(Record record) throws InterruptedException {
- mRecordsIn.put(record);
- }
-
- void allRecordsSubmitted() {
- for (Addr2LineWorker worker : mWorkers) {
- worker.stopIfQueueIsEmpty = true;
- }
- }
-
- boolean await(int amount, TimeUnit unit) throws InterruptedException {
- return mCompletionLatch.await(amount, unit);
- }
-
- /**
- * @param value the base value
- * @param percent absolute percentage to jitter by (in the range [0,100])
- * @return a value that is on average uniformly distributed within
- * plus or minus <em>percent</em> of the base value.
- */
- private static int jitter(final int value, final int percent) {
- Random r = new Random();
- int delta = (r.nextBoolean() ? 1 : -1) * r.nextInt((percent * value) / 100);
- return value + delta;
- }
-
-
- /**
- * A class that encapsulates an addr2line process and the work that it
- * needs to do.
- */
- private class Addr2LineWorker {
- // Our work queues
- private final AtomicReference<Process> processRef = new AtomicReference<Process>();
- private final Thread workerThread;
- private volatile boolean stopIfQueueIsEmpty = false;
-
- /**
- * After this many addresses have been processed, the addr2line process
- * itself will be recycled. This prevents the addr2line process from
- * getting too huge, which in turn allows more parallel addr2line
- * processes to run. There is a balance to be achieved here, as
- * creating a new addr2line process is very costly. A value of
- * approximately 2000 appears, empirically, to be a good tradeoff
- * on a modern machine; memory usage stays tolerable, and good
- * throughput can be achieved. The value is jittered by +/- 10% so that
- * the processes don't all restart at once.
- */
- private final int processRecycleThreshold = jitter(2000, 10);
-
- private Addr2LineWorker() throws IOException {
- this.processRef.set(createAddr2LineProcess());
- workerThread = new Thread(new Addr2LineTask(), "Addr2Line Worker");
- workerThread.setDaemon(true);
- workerThread.start();
- }
-
- /**
- * Builds a new addr2line process for use in this worker.
- * @return the process
- * @throws IOException if unable to create the process for any reason
- */
- private Process createAddr2LineProcess()
- throws IOException {
- ProcessBuilder builder = new ProcessBuilder(mAddr2linePath, "-e", mLibraryPath, "-f");
- Process process = builder.start();
- return process;
- }
-
- /**
- * Reads records from the input queue and pipes addresses into
- * addr2line, using the output to complete the record and pushing
- * the record into the output queue.
- */
- private class Addr2LineTask implements Runnable {
- @Override
- public void run() {
- int processTaskCounter = 0;
- InputStream inStream = processRef.get().getInputStream();
- Reader isr = new InputStreamReader(inStream);
- BufferedReader reader = new BufferedReader(isr);
- try {
- while (true) {
- // Check for a task.
- final Record record = mRecordsIn.poll(1, TimeUnit.SECONDS);
- if (record == null) {
- if (stopIfQueueIsEmpty) {
- // All tasks have been submitted, so if the
- // queue is empty then there is nothing left
- // to do and it's ready to shut down
- return;
- }
- continue; // else, queue starvation. Try again.
- }
-
- // Avoid reprocessing previously-seen symbols if
- // deduping is enabled
- if (tryDedupe(record)) continue;
-
- // Create a new reader if the addr2line process is new
- // or has been recycled. A single reader will be used
- // for the entirety of the addr2line process' lifetime.
- final Process process = processRef.get();
- if (inStream == null) {
- inStream = process.getInputStream();
- isr = new InputStreamReader(inStream);
- reader = new BufferedReader(isr);
- }
-
- // Write the address to stdin of addr2line
- process.getOutputStream().write(record.address.getBytes(sAscii));
- process.getOutputStream().write('\n');
- process.getOutputStream().flush();
-
- // Read the answer from addr2line. Example:
- // ABGRToYRow_C
- // /src/out/Release/../../third_party/libyuv/source/row_common.cc:293
- final String name = reader.readLine();
- if (name == null || name.isEmpty()) {
- stopIfQueueIsEmpty = true;
- continue;
- }
-
- String location = reader.readLine();
- if (location == null || location.isEmpty()) {
- stopIfQueueIsEmpty = true;
- continue;
- }
-
- record.resolvedSuccessfully = !(
- name.equals("??") && location.equals("??:0"));
-
- if (record.resolvedSuccessfully) {
- // Keep the name from the initial NM dump.
- // Some addr2line impls, such as the one for Android
- // on ARM, seem to lose data here.
- // Note that the location may also include a line
- // discriminator, which maps to a part of a line.
- // Not currently used.
- record.location = processLocation(location);;
- }
-
- // Check if there is more input on the stream.
- // If there is then it is a serious processing
- // error, and reading anything else would de-sync
- // the input queue from the results being read.
- if (inStream.available() > 0) {
- throw new IllegalStateException(
- "Alignment mismatch in output from address " + record.address);
- }
-
- // Track stats and move record to output queue
- processTaskCounter++;
- mRecordsOut.add(record);
-
- // If the addr2line process has done too much work,
- // kill it and start a new one to reduce memory
- // pressure created by the pool.
- if (processTaskCounter >= processRecycleThreshold) {
- // Out with the old...
- try {
- processRef.get().destroy();
- } catch (Exception e) {
- System.err.println("WARNING: zombie process");
- e.printStackTrace();
- }
- // ... and in with the new!
- try {
- processRef.set(createAddr2LineProcess());
- } catch (IOException e) {
- e.printStackTrace();
- }
- processTaskCounter = 0;
- inStream = null; // New readers need to be created next iteration
- }
- }
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- // Make an attempt to clean up. If we fail, there is
- // nothing we can do beyond this.
- processRef.get().destroy();
- } catch (Exception e) {
- // There's nothing more we can do here.
- }
- mCompletionLatch.countDown();
- }
- }
- }
-
- /**
- * Examines the location from a record and attempts to canonicalize
- * it and strip off the common source root.
- * @param location the location to process
- * @return the canonicalized, stripped location or the original input
- * string if the location cannot be canonicalized
- */
- private String processLocation(String location) {
- if (location.startsWith("/")) {
- try {
- location = new File(location).getCanonicalPath();
- } catch (IOException e) {
- System.err.println("Unable to canonicalize path: " + location);
- }
- } else if (mDisambiguate) {
- // Ambiguous path (only has the file name)
- // Try dictionary lookup if that's enabled
- final int indexOfColon = location.lastIndexOf(':');
- final String key;
- final String line;
- if (indexOfColon != -1) {
- key = location.substring(0, indexOfColon);
- line = location.substring(indexOfColon + 1);
- } else {
- key = location;
- line = null;
- }
- final String found = mFileLookupTable.get(key);
- if (found != null) {
- mDisambiguationSuccessCount.incrementAndGet();
- location = found;
- if (line != null) location = location + ":" + line;
- } else {
- mDisambiguationFailureCount.incrementAndGet();
- }
- }
- if (location.startsWith(stripLocation)) {
- location = location.substring(stripLocation.length());
- }
- return location;
- }
-
- /**
- * Attempts to dedupe a record using a cache of previously-seen
- * addresses if and only if deduping is enabled.
- * @param record the record to attempt deduping
- * @return true if deduplication is enabled and the record references
- * an address that has already been seen; otherwise false
- */
- private boolean tryDedupe(Record record) {
- if (mDedupe) {
- long addressLong = Long.parseLong(record.address, 16);
- Record existing = mAddressesSeen.get(addressLong);
- if (existing != null) {
- if (!existing.size.equals(record.size)) {
- System.err.println("WARNING: Deduped address " +
- record.address + " has a size mismatch, " +
- existing.size + " != " + record.size);
- }
- mDedupeCount.incrementAndGet();
- return true;
- }
- if (mAddressesSeen.putIfAbsent(addressLong, record) != null) {
- // putIfAbsent is used to ensure that we have
- // an accurate dedupeCount; otherwise, two
- // workers could insert the same record in
- // parallel without realizing that one of them
- // was actually a duplicate.
- mDedupeCount.incrementAndGet();
- return true;
- }
- }
- return false;
- }
- }
-
- // TODO(andrewhayden): Make this less Android-specific
- private void createDisambiguationTable() throws IOException {
- // Convert /src/out/Release/lib/*.so -> /src/out/Release
- final File libraryOutputDirectory = new File(mLibraryPath)
- .getParentFile().getParentFile().getCanonicalFile();
-
- // Convert /src/out/Release -> /src
- final File root = libraryOutputDirectory
- .getParentFile().getParentFile().getCanonicalFile();
-
- // There is no code at the root of Chromium.
- // Ignore all the 'out' directories.
- mFileLookupTable = new HashMap<String, String>();
- Set<String> dupes = new HashSet<String>();
- for (File file : root.listFiles()) {
- if (file.isDirectory()) {
- String name = file.getName();
- if (name.startsWith("out")) {
- if (new File(file, "Release").exists() || new File(file, "Debug").exists()) {
- // It's an output directory, skip it - except for the
- // 'obj' and 'gen' subdirectories that are siblings
- // to the library file's parent dir, which is needed.
- // Include those at the very end, since they are known.
- continue;
- }
- } else if (name.startsWith(".")) {
- // Skip dot directories: .git, .svn, etcetera.
- continue;
- }
- findInterestingFiles(file, dupes);
- }
- }
-
- // Include directories that contain generated resources we are likely
- // to encounter in the symbol table.
- findInterestingFiles(new File(libraryOutputDirectory, "gen"), dupes);
- findInterestingFiles(new File(libraryOutputDirectory, "obj"), dupes);
-
- // Any duplicates in the filesystem can't be used for disambiguation
- // because it is not obvious which of the duplicates is the true source.
- // Therefore, discard all files that have duplicate names.
- for (String dupe : dupes) {
- mFileLookupTable.remove(dupe);
- }
- }
-
- // TODO(andrewhayden): Could integrate with build system to know EXACTLY
- // what is out there. This would avoid the need for the dupes set, which
- // would make it possible to do much better deduping.
- private void findInterestingFiles(File directory, Set<String> dupes) {
- for (File file : directory.listFiles()) {
- if (file.isDirectory() && file.canRead()) {
- if (!file.getName().startsWith(".")) {
- findInterestingFiles(file, dupes);
- }
- } else {
- String name = file.getName();
- String normalized = name.toLowerCase();
- for (String ending : INTERESTING_FILE_ENDINGS) {
- if (normalized.endsWith(ending)) {
- String other = mFileLookupTable.put(
- name, file.getAbsolutePath());
- if (other != null) dupes.add(name);
- }
- }
- }
- }
- }
-
- /**
- * Polls the output queue for the next record.
- * @return the next record
- */
- Record poll() {
- return mRecordsOut.poll();
- }
-
- /**
- * @return the number of ambiguous paths successfully disambiguated
- */
- int getDisambiguationSuccessCount() {
- return mDisambiguationSuccessCount.get();
- }
-
- /**
- * @return the number of ambiguous paths that couldn't be disambiguated
- */
- int getDisambiguationFailureCount() {
- return mDisambiguationFailureCount.get();
- }
-
- /**
- * @return the number of symbols deduped
- */
- int getDedupeCount() {
- return mDedupeCount.get();
- }
-}

Powered by Google App Engine
This is Rietveld 408576698