Commit 17a62197 authored by bratell@opera.com's avatar bratell@opera.com

Graphical version of the run_binary_size_analysis tool.

The binary_size tool suit includes tools that are useful when trying
to reduce binary size of a program, and chromium related programs
in particular.

This commit (mostly written by andrewhayden@chromium.org for 
Android but ported to generic Linux by bratell@opera.com) adds 
a graphical HTML based output for run_binary_size_analysis.py.
In the generated web page it is possible to dynamically and 
graphically browse the binary and each part of the source tree 
is given a size that reflects its contribution to the binary size.

The run_binary_size_analysis tool is run on compiled binaries 
with symbols and uses nm and addr2line to map parts of the
binary to source code. Since addr2line is slow the operation to map 
binary symbols to source files takes a while but the output is 
well worth it when shrinking programs. See its usage information
for details about how to run it.

This commit also includes the tool explain_binary_size_delta.py 
(textual output) which can be used to understand why a binary 
changed size and in what way. See its usage information for 
details about how to run it.

There are many further improvements possible to to do on these tools.
Search the bug database for Label:Tools-BinarySize for suggestions.

BUG=339059
R=primiano@chromium.org,andrewhayden@chromium.org

Review URL: https://codereview.chromium.org/258633003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@272255 0039d316-1c4b-4281-b951-d872f2087c98
parent 56281735
......@@ -817,14 +817,6 @@
}],
],
}, # target_name: android_builder_chromium_webrtc
{
# Build the java portions of the binary size analysis tool.
'target_name': 'binary_size_tool',
'type': 'none',
'dependencies': [
'../tools/binary_size/binary_size.gyp:binary_size_java',
],
},
], # targets
}], # OS="android"
['OS=="mac"', {
......
......@@ -23,4 +23,3 @@ M V EI: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.favicon()
M V EI: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.thumbnail() may expose internal representation by returning ChromeBrowserProvider$BookmarkNode.mThumbnail At ChromeBrowserProvider.java
M V EI: org.chromium.content.browser.LoadUrlParams.getPostData() may expose internal representation by returning LoadUrlParams.mPostData At LoadUrlParams.java
M M LI: Incorrect lazy initialization of static field org.chromium.chrome.browser.sync.ProfileSyncService.sSyncSetupManager in org.chromium.chrome.browser.sync.ProfileSyncService.get(Context) At ProfileSyncService.java
M B OS: org.chromium.tools.binary_size.Addr2LineWorkerPool$Addr2LineWorker$Addr2LineTask.run() may fail to close stream At Addr2LineWorkerPool.java
......@@ -286,7 +286,8 @@ class ELFSymbolizer(object):
if m:
if not m.group(1).startswith('?'):
source_path = m.group(1)
source_line = int(m.group(2))
if not m.group(2).startswith('?'):
source_line = int(m.group(2))
else:
logging.warning('Got invalid symbol path from addr2line: %s' % line2)
......
......@@ -30,7 +30,7 @@ def print_landmines(target):
builder() == 'ninja'):
print 'Need to clobber winja goma due to backend cwd cache fix.'
if platform() == 'android':
print 'Clobber: Autogen java file needs to be removed (issue 159173002)'
print 'Clobber: build_size.jar needs to be deleted (issue 258633003)'
if platform() == 'win' and builder() == 'ninja':
print 'Compile on cc_unittests fails due to symbols removed in r185063.'
if platform() == 'linux' and builder() == 'ninja':
......
# 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.
{
'targets': [
{
'target_name': 'binary_size_java',
'type': 'none',
'variables': {
'java_in_dir': '../binary_size/java',
},
'includes': [ '../../build/java.gypi' ],
},
],
}
# 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.
"""Common utilities for tools that deal with binary size information.
"""
import logging
import re
def ParseNm(nm_lines):
"""Parse nm output, returning data for all relevant (to binary size)
symbols and ignoring the rest.
Args:
nm_lines: an iterable over lines of nm output.
Yields:
(symbol name, symbol type, symbol size, source file path).
Path may be None if nm couldn't figure out the source file.
"""
# Match lines with size, symbol, optional location, optional discriminator
sym_re = re.compile(r'^[0-9a-f]{8,} ' # address (8+ hex digits)
'([0-9a-f]{8,}) ' # size (8+ hex digits)
'(.) ' # symbol type, one character
'([^\t]+)' # symbol name, separated from next by tab
'(?:\t(.*):[\d\?]+)?.*$') # location
# Match lines with addr but no size.
addr_re = re.compile(r'^[0-9a-f]{8,} (.) ([^\t]+)(?:\t.*)?$')
# Match lines that don't have an address at all -- typically external symbols.
noaddr_re = re.compile(r'^ {8,} (.) (.*)$')
# Match lines with no symbol name, only addr and type
addr_only_re = re.compile(r'^[0-9a-f]{8,} (.)$')
for line in nm_lines:
line = line.rstrip()
match = sym_re.match(line)
if match:
size, sym_type, sym = match.groups()[0:3]
size = int(size, 16)
if sym_type in ('B', 'b'):
continue # skip all BSS for now.
path = match.group(4)
yield sym, sym_type, size, path
continue
match = addr_re.match(line)
if match:
# sym_type, sym = match.groups()[0:2]
continue # No size == we don't care.
match = noaddr_re.match(line)
if match:
sym_type, sym = match.groups()
if sym_type in ('U', 'w'):
continue # external or weak symbol
match = addr_only_re.match(line)
if match:
continue # Nothing to do.
# If we reach this part of the loop, there was something in the
# line that we didn't expect or recognize.
logging.warning('nm output parser failed to parse: %s', repr(line))
This diff is collapsed.
// 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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Converts records to a format that simulates output from 'nm'.
*/
class NmDumper {
private final String mOutPath;
private final String mSkipPath;
private final String mFailPath;
private final Output mOutput;
/**
* Create a new dumper that will output to the specified paths.
* @param outPath where to write all records and lines, including lines
* that are skipped or records that failed to resolve
* @param failPath if not null, a path to which <em>only</em> records that
* failed to resolve will be written
* @param skipPath if not null, a path to which <em>only</em> lines that
* were skipped will be written
*/
NmDumper(final String outPath, final String failPath, final String skipPath) {
mOutPath = outPath;
mFailPath = failPath;
mSkipPath = skipPath;
mOutput = new Output();
}
/**
* Close all output files.
*/
void close() {
mOutput.closeAll();
}
/**
* Output a line that was skipped.
* @param line the line
*/
void skipped(String line) {
mOutput.printSkip(line);
}
/**
* Output a record that failed to resolve.
* @param record the record
*/
void failed(Record record) {
mOutput.printFail(simulateNmOutput(record));
}
/**
* Output a record that successfully resolved.
* @param record the record
*/
void succeeded(Record record) {
mOutput.print(simulateNmOutput(record));
}
/**
* Generate a string that looks like output from nm for a given record.
* @param record the record
* @return nm-like output
*/
private static final String simulateNmOutput(final Record record) {
StringBuilder builder = new StringBuilder(record.address);
builder.append(' ');
builder.append(record.size);
builder.append(' ');
builder.append(record.symbolType);
builder.append(' ');
builder.append(record.symbolName != null ? record.symbolName : "unknown");
if (record.location != null) {
builder.append('\t');
builder.append(record.location);
}
return builder.toString();
}
private class Output {
private final PrintWriter skipWriter;
private final PrintWriter failWriter;
private final PrintWriter outWriter;
private Output() {
try {
File parentDir = new File(mOutPath).getParentFile();
assert (parentDir.mkdirs() || parentDir.isDirectory());
outWriter = new PrintWriter(mOutPath);
} catch (FileNotFoundException e) {
throw new RuntimeException("Can't open output file: " + mOutPath, e);
}
if (mFailPath != null) {
try {
File parentDir = new File(mFailPath).getParentFile();
assert (parentDir.mkdirs() || parentDir.isDirectory());
failWriter = new PrintWriter(mFailPath);
} catch (FileNotFoundException e) {
throw new RuntimeException("Can't open fail file: " + mFailPath, e);
}
} else {
failWriter = null;
}
if (mSkipPath != null) {
try {
File parentDir = new File(mSkipPath).getParentFile();
assert (parentDir.mkdirs() || parentDir.isDirectory());
skipWriter = new PrintWriter(mSkipPath);
} catch (IOException e) {
throw new RuntimeException("Can't open skip file: " + mSkipPath, e);
}
} else {
skipWriter = null;
}
}
private void println(PrintWriter writer, String string) {
if (writer != null) {
synchronized (writer) {
writer.println(string);
writer.flush();
}
}
}
void print(String string) {
println(outWriter, string);
}
void printSkip(String string) {
println(skipWriter, string);
println(outWriter, string);
}
void printFail(String string) {
println(failWriter, string);
println(outWriter, string);
}
private void closeAll() {
for (PrintWriter writer : new PrintWriter[]{outWriter, failWriter, skipWriter}) {
if (writer != null) {
try {
writer.flush(); }
catch (Exception ignored) {
// Nothing to be done.
}
try {
writer.close(); }
catch (Exception ignored) {
// Nothing to be done.
}
}
}
}
}
}
// 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;
/**
* A record that is filled in partially by nm and partially by addr2line,
* along with tracking information about whether or not the lookup in
* addr2line was successful.
*/
class Record {
/**
* The base-16 address, as a string.
*/
String address;
/**
* The symbol type.
*/
char symbolType;
/**
* The name of the symbol. Note that this may include whitespace, but
* not tabs.
*/
String symbolName;
/**
* The base-10 size in bytes, as a String.
*/
String size;
/**
* The location, if available; may include a file name and, optionally,
* a colon separator character followed by a line number or a
* question mark.
*/
String location;
/**
* Whether or not the record was successfully resolved. Records that are
* successfully resolved should have a non-null location.
*/
boolean resolvedSuccessfully;
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment