| Index: tools/binary_size/java/src/org/chromium/tools/binary_size/ParallelAddress2Line.java
|
| diff --git a/tools/binary_size/java/src/org/chromium/tools/binary_size/ParallelAddress2Line.java b/tools/binary_size/java/src/org/chromium/tools/binary_size/ParallelAddress2Line.java
|
| deleted file mode 100644
|
| index 8e49c43cfaf5890df67923e752a6018641372a8e..0000000000000000000000000000000000000000
|
| --- a/tools/binary_size/java/src/org/chromium/tools/binary_size/ParallelAddress2Line.java
|
| +++ /dev/null
|
| @@ -1,526 +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.FileNotFoundException;
|
| -import java.io.FileOutputStream;
|
| -import java.io.FileReader;
|
| -import java.io.IOException;
|
| -import java.io.InputStream;
|
| -import java.io.OutputStream;
|
| -import java.util.Timer;
|
| -import java.util.TimerTask;
|
| -import java.util.concurrent.CountDownLatch;
|
| -import java.util.concurrent.TimeUnit;
|
| -import java.util.concurrent.atomic.AtomicBoolean;
|
| -import java.util.concurrent.atomic.AtomicInteger;
|
| -import java.util.regex.Matcher;
|
| -import java.util.regex.Pattern;
|
| -
|
| -/**
|
| - * A tool for parallelizing "addr2line" against a given binary.
|
| - * The tool runs "nm" to dump the symbols from a library, then spawns a pool
|
| - * of addr2name workers that resolve addresses to name in parallel.
|
| - * <p>
|
| - * This tool is intentionally written to be standalone so that it can be
|
| - * compiled without reliance upon any other libraries. All that is required
|
| - * is a vanilla installation of the Java Runtime Environment, 1.5 or later.
|
| - */
|
| -// TODO(andrewhayden): Rewrite entire tool in Python
|
| -public class ParallelAddress2Line {
|
| - private final AtomicBoolean mStillEnqueuing = new AtomicBoolean(true);
|
| - private final AtomicInteger mEnqueuedCount =
|
| - new AtomicInteger(Integer.MAX_VALUE);
|
| - private final AtomicInteger mDoneCount = new AtomicInteger(0);
|
| - private final AtomicInteger mSuccessCount = new AtomicInteger(0);
|
| - private final AtomicInteger mAddressSkipCount = new AtomicInteger(0);
|
| - private final String mLibraryPath;
|
| - private final String mNmPath;
|
| - private final String mNmInPath;
|
| - private final String mAddr2linePath;
|
| - private final boolean mVerbose;
|
| - private final boolean mNoProgress;
|
| - private Addr2LineWorkerPool mPool;
|
| - private final boolean mNoDedupe;
|
| - private final boolean mDisambiguate;
|
| - private final NmDumper mNmDumper;
|
| -
|
| - private static final String USAGE =
|
| - "--addr2line [ARG] instead of the 'addr2line' in $PATH, use this (e.g.,\n" +
|
| - " arch-specific binary) (optional)\n" +
|
| - "--disambiguate create a listing of all source files which can be used\n" +
|
| - " to disambiguate some percentage of ambiguous source\n" +
|
| - " references; only useful on some architectures and adds\n" +
|
| - " significant startup cost (optional)\n" +
|
| - "--failfile [ARG] output symbols from failed lookups to the specified\n" +
|
| - " file (optional)\n" +
|
| - "--library [ARG] path to the library to process, e.g.\n" +
|
| - " out/Release/lib/libchromeview.so (required)\n" +
|
| - "--nm [ARG] instead of the 'nm' in $PATH, use this (e.g.,\n" +
|
| - " arch-specific binary) (optional)\n" +
|
| - "--nm-infile [ARG] instead of running nm on the specified library,\n" +
|
| - " ingest the specified nm file. (optional)\n" +
|
| - "--no-dedupe don't de-dupe symbols that live at the same address;\n" +
|
| - " deduping more accurately describes the use of space\n" +
|
| - " within the binary; if spatial analysis is your goal,\n" +
|
| - " leave deduplication on. (optional)\n" +
|
| - "--no-progress don't output periodic progress reports (optional)\n" +
|
| - "--outfile [ARG] output results into the specified file (required)\n" +
|
| - "--skipfile [ARG] output skipped symbols to the specified file (optional)\n" +
|
| - "--threads [ARG] number of parallel worker threads to create. Start low\n" +
|
| - " and watch your memory, defaults to 1 (optional)\n" +
|
| - "--verbose be verbose (optional)\n";
|
| -
|
| - // Regex for parsing "nm" output. A sample line looks like this:
|
| - // 0167b39c 00000018 t ACCESS_DESCRIPTION_free /path/file.c:95
|
| - //
|
| - // The fields are: address, size, type, name, source location
|
| - // Regular expression explained ( see also: https://xkcd.com/208 ):
|
| - // ([0-9a-f]{8}+) The address
|
| - // [\\s]+ Whitespace separator
|
| - // ([0-9a-f]{8}+) The size. From here on out it's all optional.
|
| - // [\\s]+ Whitespace separator
|
| - // (\\S?) The symbol type, which is any non-whitespace char
|
| - // [\\s*] Whitespace separator
|
| - // ([^\\t]*) Symbol name, any non-tab character (spaces ok!)
|
| - // [\\t]? Tab separator
|
| - // (.*) The location (filename[:linennum|?][ (discriminator n)]
|
| - private static final Pattern sNmPattern = Pattern.compile(
|
| - "([0-9a-f]{8}+)[\\s]+([0-9a-f]{8}+)[\\s]*(\\S?)[\\s*]([^\\t]*)[\\t]?(.*)");
|
| -
|
| - private ParallelAddress2Line(
|
| - final String libraryPath,
|
| - final String nmPath,
|
| - final String nmInPath,
|
| - final String addr2linePath,
|
| - final String outPath,
|
| - final String skipPath,
|
| - final String failPath,
|
| - final boolean verbose,
|
| - final boolean noProgress,
|
| - final boolean noDedupe,
|
| - final boolean disambiguate) {
|
| - this.mLibraryPath = libraryPath;
|
| - this.mNmPath = nmPath;
|
| - this.mNmInPath = nmInPath;
|
| - this.mAddr2linePath = addr2linePath;
|
| - this.mVerbose = verbose;
|
| - this.mNoProgress = noProgress;
|
| - this.mNoDedupe = noDedupe;
|
| - this.mDisambiguate = disambiguate;
|
| - this.mNmDumper = new NmDumper(outPath, failPath, skipPath);
|
| -
|
| - final File libraryFile = new File(libraryPath);
|
| - if (!(libraryFile.exists() && libraryFile.canRead())) {
|
| - throw new IllegalStateException("Can't read library file: " + libraryPath);
|
| - }
|
| - }
|
| -
|
| - private static final File findFile(File directory, String target) {
|
| - for (File file : directory.listFiles()) {
|
| - if (file.isDirectory() && file.canRead()) {
|
| - File result = findFile(file, target);
|
| - if (result != null) return result;
|
| - } else {
|
| - if (target.equals(file.getName())) return file;
|
| - }
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - private void run(final int addr2linePoolSize) throws InterruptedException {
|
| - try {
|
| - runInternal(addr2linePoolSize);
|
| - } finally {
|
| - mNmDumper.close();
|
| - }
|
| - }
|
| -
|
| - private void runInternal(final int addr2linePoolSize) throws InterruptedException {
|
| - // Step 1: Dump symbols with nm
|
| - final String nmOutputPath;
|
| - if (mNmInPath == null) {
|
| - // Generate nm output with nm binary
|
| - logVerbose("Running nm to dump symbols from " + mLibraryPath);
|
| - try {
|
| - nmOutputPath = dumpSymbols();
|
| - } catch (Exception e) {
|
| - throw new RuntimeException("nm failed", e);
|
| - }
|
| - } else {
|
| - // Use user-supplied nm output
|
| - logVerbose("Using user-supplied nm file: " + mNmInPath);
|
| - nmOutputPath = mNmInPath;
|
| - }
|
| -
|
| - // Step 2: Prepare addr2line worker pool to process nm output
|
| - try {
|
| - logVerbose("Creating " + addr2linePoolSize + " workers for " + mAddr2linePath);
|
| - mPool = new Addr2LineWorkerPool(addr2linePoolSize,
|
| - mAddr2linePath, mLibraryPath, mDisambiguate, !mNoDedupe);
|
| - } catch (IOException e) {
|
| - throw new RuntimeException("Couldn't initialize name2address pool!", e);
|
| - }
|
| -
|
| - // Step 3: Spool symbol-processing tasks to workers
|
| - final long startTime = System.currentTimeMillis();
|
| - Timer timer = null;
|
| - if (!mNoProgress) {
|
| - timer = startTaskMonitor(startTime);
|
| - }
|
| - final int queued = spoolTasks(nmOutputPath);
|
| -
|
| - // All tasks have been enqueued.
|
| - mEnqueuedCount.set(queued);
|
| - mStillEnqueuing.set(false);
|
| - mPool.allRecordsSubmitted();
|
| - float percentAddressesSkipped = 100f * (mAddressSkipCount.floatValue()
|
| - / (queued + mAddressSkipCount.get()));
|
| - float percentAddressesQueued = 100f - percentAddressesSkipped;
|
| - int totalAddresses = mAddressSkipCount.get() + queued;
|
| - logVerbose("All addresses have been enqueued (total " + queued + ").");
|
| - // Remember that the queue to which the addresses was enqueued is of a
|
| - // small fixed size; by the time this code executes, there is very
|
| - // little work left to do. Await the termination of the pool with a
|
| - // reasonable timeout for safety purposes.
|
| - boolean timedOut = !mPool.await(5, TimeUnit.MINUTES);
|
| - if (timedOut) {
|
| - throw new RuntimeException("Worker pool did not terminate!");
|
| - }
|
| - if (!mNoProgress) timer.cancel();
|
| - log(totalAddresses + " addresses discovered; " +
|
| - queued + " queued for processing (" +
|
| - String.format("%.2f", percentAddressesQueued) + "%), " +
|
| - mAddressSkipCount.get() + " skipped (" +
|
| - String.format("%.2f", percentAddressesSkipped) + "%)");
|
| - dumpStats(startTime);
|
| - log("Done.");
|
| - }
|
| -
|
| - /**
|
| - * Monitors the pool periodically printing status updates to stdout.
|
| - * @param addressProcessingStartTime the time address processing began
|
| - * @return the daemon timer that is generating the status updates
|
| - */
|
| - private final Timer startTaskMonitor(
|
| - final long addressProcessingStartTime) {
|
| - Runnable monitorTask = new OutputSpooler();
|
| - Thread monitor = new Thread(monitorTask, "progress monitor");
|
| - monitor.setDaemon(true);
|
| - monitor.start();
|
| -
|
| - TimerTask task = new TimerTask() {
|
| - @Override
|
| - public void run() {
|
| - dumpStats(addressProcessingStartTime);
|
| - }
|
| - };
|
| - Timer timer = new Timer(true);
|
| - timer.schedule(task, 1000L, 1000L);
|
| - return timer;
|
| - }
|
| -
|
| - /**
|
| - * Spools address-lookup tasks to the addr2line workers.
|
| - * This method will block until most of (or possibly all of) the tasks
|
| - * have been spooled.
|
| - * If a skip path is set, any line in the input file that doesn't have
|
| - * an address will be copied into the skip file.
|
| - *
|
| - * @param inputPath the path to the dump produced by nm
|
| - * @return the number of tasks spooled
|
| - */
|
| - private final int spoolTasks(final String inputPath) {
|
| - FileReader inputReader = null;
|
| - try {
|
| - inputReader = new FileReader(inputPath);
|
| - } catch (IOException e) {
|
| - throw new RuntimeException("Can't open input file: " + inputPath, e);
|
| - }
|
| - final BufferedReader bufferedReader = new BufferedReader(inputReader);
|
| -
|
| - String currentLine = null;
|
| - int numSpooled = 0;
|
| - try {
|
| - while ((currentLine = bufferedReader.readLine()) != null) {
|
| - try {
|
| - final Matcher matcher = sNmPattern.matcher(currentLine);
|
| - if (!matcher.matches()) {
|
| - // HACK: Special case for ICU data.
|
| - // This thing is HUGE (5+ megabytes) and is currently
|
| - // missed because there is no size information.
|
| - // torne@ has volunteered to patch the generation code
|
| - // so that the generated ASM includes a size attribute
|
| - // so that this hard-coding can go away in the future.
|
| - if (currentLine.endsWith("icudt46_dat")) {
|
| - Record record = getIcuRecord(currentLine);
|
| - if (record != null) {
|
| - numSpooled++;
|
| - mPool.submit(record);
|
| - continue;
|
| - }
|
| - }
|
| - mNmDumper.skipped(currentLine);
|
| - mAddressSkipCount.incrementAndGet();
|
| - continue;
|
| - }
|
| - final Record record = new Record();
|
| - record.address = matcher.group(1);
|
| - record.size = matcher.group(2);
|
| - if (matcher.groupCount() >= 3) {
|
| - record.symbolType = matcher.group(3).charAt(0);
|
| - }
|
| - if (matcher.groupCount() >= 4) {
|
| - // May or may not be present
|
| - record.symbolName = matcher.group(4);
|
| - }
|
| - numSpooled++;
|
| - mPool.submit(record);
|
| - } catch (Exception e) {
|
| - throw new RuntimeException("Error processing line: '" + currentLine + "'", e);
|
| - }
|
| - }
|
| - } catch (Exception e) {
|
| - throw new RuntimeException("Input processing failed", e);
|
| - } finally {
|
| - try {
|
| - bufferedReader.close();
|
| - } catch (Exception ignored) {
|
| - // Nothing to be done
|
| - }
|
| - try {
|
| - inputReader.close();
|
| - } catch (Exception ignored) {
|
| - // Nothing to be done
|
| - }
|
| - }
|
| - return numSpooled;
|
| - }
|
| -
|
| - private Record getIcuRecord(String line) throws IOException {
|
| - // Line looks like this:
|
| - // 01c9ee00 r icudt46_dat
|
| - String[] parts = line.split("\\s");
|
| - if (parts.length != 3) return null;
|
| -
|
| - // Convert /src/out/Release/lib/[libraryfile] -> /src/out/Release
|
| - final File libraryOutputDirectory = new File(mLibraryPath)
|
| - .getParentFile().getParentFile().getCanonicalFile();
|
| - final File icuDir = new File(
|
| - libraryOutputDirectory.getAbsolutePath() +
|
| - "/obj/third_party/icu");
|
| - final File icuFile = findFile(icuDir, "icudata.icudt46l_dat.o");
|
| - if (!icuFile.exists()) return null;
|
| - final Record record = new Record();
|
| - record.address = parts[0];
|
| - record.symbolType = parts[1].charAt(0);
|
| - record.symbolName = parts[2];
|
| - record.size = Integer.toHexString((int) icuFile.length());
|
| - record.location = icuFile.getCanonicalPath() + ":0";
|
| - record.resolvedSuccessfully = true;
|
| - while (record.size.length() < 8) {
|
| - record.size = "0" + record.size;
|
| - }
|
| - return record;
|
| - }
|
| -
|
| - /**
|
| - * @return the path to the file that nm wrote
|
| - * @throws Exception
|
| - * @throws FileNotFoundException
|
| - * @throws InterruptedException
|
| - */
|
| - private String dumpSymbols() throws Exception, FileNotFoundException, InterruptedException {
|
| - final Process process = createNmProcess();
|
| - final File tempFile = File.createTempFile("ParallelAddress2Line", "nm");
|
| - tempFile.deleteOnExit();
|
| - final CountDownLatch completionLatch = sink(
|
| - process.getInputStream(), new FileOutputStream(tempFile), true);
|
| - sink(process.getErrorStream(), System.err, false);
|
| - logVerbose("Dumping symbols to: " + tempFile.getAbsolutePath());
|
| - final int nmRc = process.waitFor();
|
| - if (nmRc != 0) {
|
| - throw new RuntimeException("nm process returned " + nmRc);
|
| - }
|
| - completionLatch.await(); // wait for output to be done
|
| - return tempFile.getAbsolutePath();
|
| - }
|
| -
|
| - private void dumpStats(final long startTime) {
|
| - long successful = mSuccessCount.get();
|
| - long doneNow = mDoneCount.get();
|
| - long unsuccessful = doneNow - successful;
|
| - float successPercent = doneNow == 0 ? 100f : 100f * ((float)successful / (float)doneNow);
|
| - long elapsedMillis = System.currentTimeMillis() - startTime;
|
| - float elapsedSeconds = elapsedMillis / 1000f;
|
| - long throughput = doneNow / (elapsedMillis / 1000);
|
| - final int mapLookupSuccess = mPool.getDisambiguationSuccessCount();
|
| - final int mapLookupFailure = mPool.getDisambiguationFailureCount();
|
| - final int mapLookupTotal = mapLookupSuccess + mapLookupFailure;
|
| - float mapLookupSuccessPercent = 0f;
|
| - if (mapLookupTotal != 0 && mapLookupSuccess != 0) {
|
| - mapLookupSuccessPercent = 100f *
|
| - ((float) mapLookupSuccess / (float) mapLookupTotal);
|
| - }
|
| -
|
| - log(doneNow + " addresses processed (" +
|
| - mSuccessCount.get() + " ok, " + unsuccessful + " failed)" +
|
| - ", avg " + throughput + " addresses/sec, " +
|
| - String.format("%.2f", successPercent) + "% success" +
|
| - ", " + mapLookupTotal + " ambiguous path" +
|
| - (!mDisambiguate ? "" :
|
| - ", (" + String.format("%.2f", mapLookupSuccessPercent) + "% disambiguated)") +
|
| - (mNoDedupe ? "" : ", " + mPool.getDedupeCount() + " deduped") +
|
| - ", elapsed time " + String.format("%.3f", elapsedSeconds) + " seconds");
|
| - }
|
| -
|
| - private Process createNmProcess() throws Exception {
|
| - ProcessBuilder builder = new ProcessBuilder(
|
| - mNmPath,
|
| - "-C", // demangle (for the humans)
|
| - "-S", // print size
|
| - mLibraryPath);
|
| - logVerbose("Creating process: " + builder.command());
|
| - return builder.start();
|
| - }
|
| -
|
| - /**
|
| - * Make a pipe to drain the specified input stream into the specified
|
| - * output stream asynchronously.
|
| - * @param in read from here
|
| - * @param out and write to here
|
| - * @param closeWhenDone whether or not to close the target output stream
|
| - * when the input stream terminates
|
| - * @return a latch that can be used to await the final write to the
|
| - * output stream, which occurs when either of the streams closes
|
| - */
|
| - private static final CountDownLatch sink(final InputStream in,
|
| - final OutputStream out, final boolean closeWhenDone) {
|
| - final CountDownLatch latch = new CountDownLatch(1);
|
| - final Runnable task = new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - byte[] buffer = new byte[4096];
|
| - try {
|
| - int numRead = 0;
|
| - do {
|
| - numRead = in.read(buffer);
|
| - if (numRead > 0) {
|
| - out.write(buffer, 0, numRead);
|
| - out.flush();
|
| - }
|
| - } while (numRead >= 0);
|
| - } catch (Exception e) {
|
| - e.printStackTrace();
|
| - } finally {
|
| - try { out.flush(); } catch (Exception ignored) {
|
| - // Nothing to be done
|
| - }
|
| - if (closeWhenDone) {
|
| - try { out.close(); } catch (Exception ignored) {
|
| - // Nothing to be done
|
| - }
|
| - }
|
| - latch.countDown();
|
| - }
|
| - }
|
| - };
|
| - final Thread worker = new Thread(task, "pipe " + in + "->" + out);
|
| - worker.setDaemon(true);
|
| - worker.start();
|
| - return latch;
|
| - }
|
| -
|
| - private final class OutputSpooler implements Runnable {
|
| - @Override
|
| - public void run() {
|
| - do {
|
| - readRecord();
|
| - } while (mStillEnqueuing.get() || (mDoneCount.get() < mEnqueuedCount.get()));
|
| - }
|
| -
|
| - /**
|
| - * Read a record and process it.
|
| - */
|
| - private void readRecord() {
|
| - Record record = mPool.poll();
|
| - if (record != null) {
|
| - mDoneCount.incrementAndGet();
|
| - if (record.resolvedSuccessfully) {
|
| - mSuccessCount.incrementAndGet();
|
| - mNmDumper.succeeded(record);
|
| - } else {
|
| - mNmDumper.failed(record);
|
| - }
|
| - } else {
|
| - try {
|
| - // wait to keep going
|
| - Thread.sleep(100);
|
| - } catch (InterruptedException e) {
|
| - e.printStackTrace();
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Log a message to the console.
|
| - * @param message the message to log
|
| - */
|
| - private final void log(String message) {
|
| - System.out.println(message);
|
| - }
|
| -
|
| - /**
|
| - * Log a message to the console iff verbose logging is enabled.
|
| - * @param message the message to log
|
| - */
|
| - private final void logVerbose(String message) {
|
| - if (mVerbose) log(message);
|
| - }
|
| -
|
| - /**
|
| - * Runs the tool. Run with --help for limited help.
|
| - * @param args
|
| - * @throws Exception if anything explodes
|
| - */
|
| - public static void main(String[] args) throws Exception {
|
| - ParallelAddress2Line tool = new ParallelAddress2Line(
|
| - getArg(args, "--library"),
|
| - getArg(args, "--nm", "nm"),
|
| - getArg(args, "--nm-infile", null),
|
| - getArg(args, "--addr2line", "addr2line"),
|
| - getArg(args, "--outfile"),
|
| - getArg(args, "--skipfile", null),
|
| - getArg(args, "--failfile", null),
|
| - hasFlag(args, "--verbose"),
|
| - hasFlag(args, "--no-progress"),
|
| - hasFlag(args, "--no-dedupe"),
|
| - hasFlag(args, "--disambiguate"));
|
| - tool.run(Integer.parseInt(getArg(args, "--threads", "1")));
|
| - }
|
| -
|
| - private static boolean hasFlag(String[] args, String name) {
|
| - for (int x = 0; x < args.length; x++) if (name.equals(args[x])) return true;
|
| - return false;
|
| - }
|
| -
|
| - private static String getArg(String[] args, String name, String defaultValue) {
|
| - for (int x = 0; x < args.length; x++) {
|
| - if (name.equals(args[x])) {
|
| - if (x < args.length - 1) return args[x + 1];
|
| - throw new RuntimeException(name + " is missing a value\n" + USAGE);
|
| - }
|
| - }
|
| - return defaultValue;
|
| - }
|
| -
|
| - private static String getArg(String[] args, String name) {
|
| - String result = getArg(args, name, null);
|
| - if (result == null) throw new RuntimeException(name + " is required\n" + USAGE);
|
| - return result;
|
| - }
|
| -}
|
|
|