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

Unified Diff: infra/bots/recipe_modules/skia/resources/run_binary_size_analysis.py

Issue 2198173002: Re-organize Skia recipes (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Fix missing dependency Created 4 years, 4 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: infra/bots/recipe_modules/skia/resources/run_binary_size_analysis.py
diff --git a/infra/bots/recipe_modules/skia/resources/run_binary_size_analysis.py b/infra/bots/recipe_modules/skia/resources/run_binary_size_analysis.py
deleted file mode 100755
index 5cb24d967a78ca6a1d73f712f90de3bc923a48af..0000000000000000000000000000000000000000
--- a/infra/bots/recipe_modules/skia/resources/run_binary_size_analysis.py
+++ /dev/null
@@ -1,817 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Generate a spatial analysis against an arbitrary library.
-
-Adapted for Skia's use case from
-chromium/src/tools/binary_size/run_binary_size_analysis.py. Main changes:
-
--- Cleans up some deprecated codes.
--- Always use relative code path so the tree root is Skia repo's root.
--- Instead of outputting the standalone HTML/CSS/JS filesets, writes the
- TreeMap JSON data into a Google Storage bucket.
--- Adds githash and total_size to the JSON data.
--- Outputs another summary data in JSON Bench format for skiaperf ingestion.
-
-The output JSON data for visualization is in the following format:
-
-{
- "githash": 123abc,
- "commit_ts": 1234567890,
- "total_size": 1234567,
- "key": {"source_type": "binary_size"},
- "tree_data": {
- "maxDepth": 9,
- "k": "p", "children":[
- {"k":"p","children":[
- {"k":"p","children":[
- {"k":"p","lastPathElement":true,"children":[
- {"k":"b","t":"t","children":[
- {"k":"s", "t":"t", "value":4029,
- "n":"etc_encode_subblock_helper(unsigned char const*, ...)"
- },
- ......
- }
-}
-
-Another JSON file is generated for size summaries to be used in skiaperf. The
-JSON format details can be found at:
- https://github.com/google/skia/blob/master/bench/ResultsWriter.h#L54
-and:
- https://skia.googlesource.com/buildbot/+/master/perf/go/ingester/nanobench.go
-
-In the binary size case, outputs look like:
-
-{
- "gitHash": "123abc",
- "key": {
- "source_type": "binarysize"
- }
- "results: {
- "src_lazy_global_weak_symbol": {
- "memory": {
- "bytes": 41,
- "options": {
- "path": "src_lazy",
- "symbol": "global_weak_symbol"
- }
- }
- },
- "src_lazy_global_read_only_data": {
- "memory": {
- "bytes": 13476,
- "options": {
- "path": "src_lazy",
- "symbol": "global_read_only_data"
- }
- }
- },
- ...
- }
-}
-
-"""
-
-import collections
-import datetime
-import json
-import logging
-import multiprocessing
-import optparse
-import os
-import re
-import shutil
-import struct
-import subprocess
-import sys
-import tempfile
-import time
-import urllib2
-
-import binary_size_utils
-import elf_symbolizer
-
-from recipe_engine.types import freeze
-
-# Node dictionary keys. These are output in json read by the webapp so
-# keep them short to save file size.
-# Note: If these change, the webapp must also change.
-NODE_TYPE_KEY = 'k'
-NODE_NAME_KEY = 'n'
-NODE_CHILDREN_KEY = 'children'
-NODE_SYMBOL_TYPE_KEY = 't'
-NODE_SYMBOL_SIZE_KEY = 'value'
-NODE_MAX_DEPTH_KEY = 'maxDepth'
-NODE_LAST_PATH_ELEMENT_KEY = 'lastPathElement'
-
-# The display name of the bucket where we put symbols without path.
-NAME_NO_PATH_BUCKET = '(No Path)'
-
-# Try to keep data buckets smaller than this to avoid killing the
-# graphing lib.
-BIG_BUCKET_LIMIT = 3000
-
-# Skia addition: relative dir for libskia.so from code base.
-LIBSKIA_RELATIVE_PATH = os.path.join('out', 'Release', 'lib')
-
-# Skia addition: dictionary mapping symbol type code to symbol name.
-# See
-# https://code.google.com/p/chromium/codesearch#chromium/src/tools/binary_size/template/D3SymbolTreeMap.js&l=74
-SYMBOL_MAP = freeze({
- 'A': 'global_absolute',
- 'B': 'global_uninitialized_data',
- 'b': 'local_uninitialized_data',
- 'C': 'global_uninitialized_common',
- 'D': 'global_initialized_data',
- 'd': 'local_initialized_data',
- 'G': 'global_small initialized_data',
- 'g': 'local_small_initialized_data',
- 'i': 'indirect_function',
- 'N': 'debugging',
- 'p': 'stack_unwind',
- 'R': 'global_read_only_data',
- 'r': 'local_read_only_data',
- 'S': 'global_small_uninitialized_data',
- 's': 'local_small_uninitialized_data',
- 'T': 'global_code',
- 't': 'local_code',
- 'U': 'undefined',
- 'u': 'unique',
- 'V': 'global_weak_object',
- 'v': 'local_weak_object',
- 'W': 'global_weak_symbol',
- 'w': 'local_weak_symbol',
- '@': 'vtable_entry',
- '-': 'stabs_debugging',
- '?': 'unrecognized',
-})
-
-
-def _MkChild(node, name):
- child = node[NODE_CHILDREN_KEY].get(name)
- if child is None:
- child = {NODE_NAME_KEY: name,
- NODE_CHILDREN_KEY: {}}
- node[NODE_CHILDREN_KEY][name] = child
- return child
-
-
-def SplitNoPathBucket(node):
- """NAME_NO_PATH_BUCKET can be too large for the graphing lib to
- handle. Split it into sub-buckets in that case."""
- root_children = node[NODE_CHILDREN_KEY]
- if NAME_NO_PATH_BUCKET in root_children:
- no_path_bucket = root_children[NAME_NO_PATH_BUCKET]
- old_children = no_path_bucket[NODE_CHILDREN_KEY]
- count = 0
- for symbol_type, symbol_bucket in old_children.iteritems():
- count += len(symbol_bucket[NODE_CHILDREN_KEY])
- if count > BIG_BUCKET_LIMIT:
- new_children = {}
- no_path_bucket[NODE_CHILDREN_KEY] = new_children
- current_bucket = None
- index = 0
- for symbol_type, symbol_bucket in old_children.iteritems():
- for symbol_name, value in symbol_bucket[NODE_CHILDREN_KEY].iteritems():
- if index % BIG_BUCKET_LIMIT == 0:
- group_no = (index / BIG_BUCKET_LIMIT) + 1
- current_bucket = _MkChild(no_path_bucket,
- '%s subgroup %d' % (NAME_NO_PATH_BUCKET,
- group_no))
- assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p'
- node[NODE_TYPE_KEY] = 'p' # p for path
- index += 1
- symbol_size = value[NODE_SYMBOL_SIZE_KEY]
- AddSymbolIntoFileNode(current_bucket, symbol_type,
- symbol_name, symbol_size)
-
-
-def MakeChildrenDictsIntoLists(node):
- largest_list_len = 0
- if NODE_CHILDREN_KEY in node:
- largest_list_len = len(node[NODE_CHILDREN_KEY])
- child_list = []
- for child in node[NODE_CHILDREN_KEY].itervalues():
- child_largest_list_len = MakeChildrenDictsIntoLists(child)
- if child_largest_list_len > largest_list_len:
- largest_list_len = child_largest_list_len
- child_list.append(child)
- node[NODE_CHILDREN_KEY] = child_list
-
- return largest_list_len
-
-
-def AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size):
- """Puts symbol into the file path node |node|.
- Returns the number of added levels in tree. I.e. returns 2."""
-
- # 'node' is the file node and first step is to find its symbol-type bucket.
- node[NODE_LAST_PATH_ELEMENT_KEY] = True
- node = _MkChild(node, symbol_type)
- assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'b'
- node[NODE_SYMBOL_TYPE_KEY] = symbol_type
- node[NODE_TYPE_KEY] = 'b' # b for bucket
-
- # 'node' is now the symbol-type bucket. Make the child entry.
- node = _MkChild(node, symbol_name)
- if NODE_CHILDREN_KEY in node:
- if node[NODE_CHILDREN_KEY]:
- logging.warning('A container node used as symbol for %s.' % symbol_name)
- # This is going to be used as a leaf so no use for child list.
- del node[NODE_CHILDREN_KEY]
- node[NODE_SYMBOL_SIZE_KEY] = symbol_size
- node[NODE_SYMBOL_TYPE_KEY] = symbol_type
- node[NODE_TYPE_KEY] = 's' # s for symbol
-
- return 2 # Depth of the added subtree.
-
-
-def MakeCompactTree(symbols, symbol_path_origin_dir):
- result = {NODE_NAME_KEY: '/',
- NODE_CHILDREN_KEY: {},
- NODE_TYPE_KEY: 'p',
- NODE_MAX_DEPTH_KEY: 0}
- seen_symbol_with_path = False
- for symbol_name, symbol_type, symbol_size, file_path in symbols:
-
- if 'vtable for ' in symbol_name:
- symbol_type = '@' # hack to categorize these separately
- if file_path and file_path != "??":
- seen_symbol_with_path = True
- else:
- file_path = NAME_NO_PATH_BUCKET
-
- path_parts = file_path.split('/')
-
- # Find pre-existing node in tree, or update if it already exists
- node = result
- depth = 0
- while len(path_parts) > 0:
- path_part = path_parts.pop(0)
- if len(path_part) == 0:
- continue
- depth += 1
- node = _MkChild(node, path_part)
- assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p'
- node[NODE_TYPE_KEY] = 'p' # p for path
-
- depth += AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size)
- result[NODE_MAX_DEPTH_KEY] = max(result[NODE_MAX_DEPTH_KEY], depth)
-
- if not seen_symbol_with_path:
- logging.warning('Symbols lack paths. Data will not be structured.')
-
- # The (no path) bucket can be extremely large if we failed to get
- # path information. Split it into subgroups if needed.
- SplitNoPathBucket(result)
-
- largest_list_len = MakeChildrenDictsIntoLists(result)
-
- if largest_list_len > BIG_BUCKET_LIMIT:
- logging.warning('There are sections with %d nodes. '
- 'Results might be unusable.' % largest_list_len)
- return result
-
-
-# Skia added: summarizes tree size by symbol type for the given root node.
-# Returns a dict keyed by symbol type, and value the type's overall size.
-# e.g., {"t": 12345, "W": 543}.
-def GetTreeSizes(node):
- if 'children' not in node or not node['children']:
- return {node['t']: node['value']}
- dic = {}
- for i in node['children']:
- for k, v in GetTreeSizes(i).items():
- dic.setdefault(k, 0)
- dic[k] += v
-
- return dic
-
-
-# Skia added: creates dict to be converted to JSON in bench format.
-# See top of file for the structure description.
-def GetBenchDict(githash, tree_root):
- dic = {'gitHash': githash,
- 'key': {'source_type': 'binarysize'},
- 'results': {},}
- for i in tree_root['children']:
- if '(No Path)' == i['n']: # Already at symbol summary level.
- for k, v in GetTreeSizes(i).items():
- dic['results']['no_path_' + SYMBOL_MAP[k]] = {
- 'memory': {
- 'bytes': v,
- 'options': {'path': 'no_path',
- 'symbol': SYMBOL_MAP[k],},}}
- else: # We need to go deeper.
- for c in i['children']:
- path = i['n'] + '_' + c['n']
- for k, v in GetTreeSizes(c).items():
- dic['results'][path + '_' + SYMBOL_MAP[k]] = {
- 'memory': {
- 'bytes': v,
- 'options': {'path': path,
- 'symbol': SYMBOL_MAP[k],}}}
-
- return dic
-
-
-# Skia added: constructs 'gsutil cp' subprocess command list.
-def GetGsCopyCommandList(gsutil, src, dst):
- return [gsutil, '-h', 'Content-Type:application/json', 'cp', '-a',
- 'public-read', src, dst]
-
-
-def DumpCompactTree(symbols, symbol_path_origin_dir, ha, ts, issue, gsutil):
- tree_root = MakeCompactTree(symbols, symbol_path_origin_dir)
- json_data = {'tree_data': tree_root,
- 'githash': ha,
- 'commit_ts': ts,
- 'key': {'source_type': 'binary_size'},
- 'total_size': sum(GetTreeSizes(tree_root).values()),}
- tmpfile = tempfile.NamedTemporaryFile(delete=False).name
- with open(tmpfile, 'w') as out:
- # Use separators without whitespace to get a smaller file.
- json.dump(json_data, out, separators=(',', ':'))
-
- GS_PREFIX = 'gs://chromium-skia-gm/'
- # Writes to Google Storage for visualization.
- subprocess.check_call(GetGsCopyCommandList(
- gsutil, tmpfile, GS_PREFIX + 'size/' + ha + '.json'))
- # Updates the latest data.
- if not issue:
- subprocess.check_call(GetGsCopyCommandList(gsutil, tmpfile,
- GS_PREFIX + 'size/latest.json'))
- # Writes an extra copy using year/month/day/hour path for easy ingestion.
- with open(tmpfile, 'w') as out:
- json.dump(GetBenchDict(ha, tree_root), out, separators=(',', ':'))
- now = datetime.datetime.utcnow()
- ingest_path = '/'.join(('nano-json-v1', str(now.year).zfill(4),
- str(now.month).zfill(2), str(now.day).zfill(2),
- str(now.hour).zfill(2)))
- if issue:
- ingest_path = '/'.join('trybot', ingest_path, issue)
- subprocess.check_call(GetGsCopyCommandList(gsutil, tmpfile,
- GS_PREFIX + ingest_path + '/binarysize_' + ha + '.json'))
-
-
-def MakeSourceMap(symbols):
- sources = {}
- for _sym, _symbol_type, size, path in symbols:
- key = None
- if path:
- key = os.path.normpath(path)
- else:
- key = '[no path]'
- if key not in sources:
- sources[key] = {'path': path, 'symbol_count': 0, 'size': 0}
- record = sources[key]
- record['size'] += size
- record['symbol_count'] += 1
- return sources
-
-
-# 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)]
-sNmPattern = re.compile(
- r'([0-9a-f]{8,})[\s]+([0-9a-f]{8,})[\s]*(\S?)[\s*]([^\t]*)[\t]?(.*)')
-
-class Progress():
- def __init__(self):
- self.count = 0
- self.skip_count = 0
- self.collisions = 0
- self.time_last_output = time.time()
- self.count_last_output = 0
- self.disambiguations = 0
- self.was_ambiguous = 0
-
-
-def RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs,
- disambiguate, src_path):
- nm_output = RunNm(library, nm_binary)
- nm_output_lines = nm_output.splitlines()
- nm_output_lines_len = len(nm_output_lines)
- address_symbol = {}
- progress = Progress()
- def map_address_symbol(symbol, addr):
- progress.count += 1
- if addr in address_symbol:
- # 'Collision between %s and %s.' % (str(symbol.name),
- # str(address_symbol[addr].name))
- progress.collisions += 1
- else:
- if symbol.disambiguated:
- progress.disambiguations += 1
- if symbol.was_ambiguous:
- progress.was_ambiguous += 1
-
- address_symbol[addr] = symbol
-
- progress_output()
-
- def progress_output():
- progress_chunk = 100
- if progress.count % progress_chunk == 0:
- time_now = time.time()
- time_spent = time_now - progress.time_last_output
- if time_spent > 1.0:
- # Only output at most once per second.
- progress.time_last_output = time_now
- chunk_size = progress.count - progress.count_last_output
- progress.count_last_output = progress.count
- if time_spent > 0:
- speed = chunk_size / time_spent
- else:
- speed = 0
- progress_percent = (100.0 * (progress.count + progress.skip_count) /
- nm_output_lines_len)
- disambiguation_percent = 0
- if progress.disambiguations != 0:
- disambiguation_percent = (100.0 * progress.disambiguations /
- progress.was_ambiguous)
-
- sys.stdout.write('\r%.1f%%: Looked up %d symbols (%d collisions, '
- '%d disambiguations where %.1f%% succeeded)'
- ' - %.1f lookups/s.' %
- (progress_percent, progress.count, progress.collisions,
- progress.disambiguations, disambiguation_percent, speed))
-
- # In case disambiguation was disabled, we remove the source path (which upon
- # being set signals the symbolizer to enable disambiguation)
- if not disambiguate:
- src_path = None
- symbol_path_origin_dir = os.path.dirname(library)
- # Skia specific.
- symbol_path_prefix = symbol_path_origin_dir.replace(LIBSKIA_RELATIVE_PATH, '')
- symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary,
- map_address_symbol,
- max_concurrent_jobs=jobs,
- source_root_path=src_path,
- prefix_to_remove=symbol_path_prefix)
- user_interrupted = False
- try:
- for line in nm_output_lines:
- match = sNmPattern.match(line)
- if match:
- location = match.group(5)
- if not location:
- addr = int(match.group(1), 16)
- size = int(match.group(2), 16)
- if addr in address_symbol: # Already looked up, shortcut
- # ELFSymbolizer.
- map_address_symbol(address_symbol[addr], addr)
- continue
- elif size == 0:
- # Save time by not looking up empty symbols (do they even exist?)
- print('Empty symbol: ' + line)
- else:
- symbolizer.SymbolizeAsync(addr, addr)
- continue
-
- progress.skip_count += 1
- except KeyboardInterrupt:
- user_interrupted = True
- print('Interrupting - killing subprocesses. Please wait.')
-
- try:
- symbolizer.Join()
- except KeyboardInterrupt:
- # Don't want to abort here since we will be finished in a few seconds.
- user_interrupted = True
- print('Patience you must have my young padawan.')
-
- print ''
-
- if user_interrupted:
- print('Skipping the rest of the file mapping. '
- 'Output will not be fully classified.')
-
- symbol_path_origin_dir = os.path.dirname(library)
- # Skia specific: path prefix to strip.
- symbol_path_prefix = symbol_path_origin_dir.replace(LIBSKIA_RELATIVE_PATH, '')
-
- with open(outfile, 'w') as out:
- for line in nm_output_lines:
- match = sNmPattern.match(line)
- if match:
- location = match.group(5)
- if not location:
- addr = int(match.group(1), 16)
- symbol = address_symbol.get(addr)
- if symbol is not None:
- path = '??'
- if symbol.source_path is not None:
- path = symbol.source_path.replace(symbol_path_prefix, '')
- line_number = 0
- if symbol.source_line is not None:
- line_number = symbol.source_line
- out.write('%s\t%s:%d\n' % (line, path, line_number))
- continue
-
- out.write('%s\n' % line)
-
- print('%d symbols in the results.' % len(address_symbol))
-
-
-def RunNm(binary, nm_binary):
- cmd = [nm_binary, '-C', '--print-size', '--size-sort', '--reverse-sort',
- binary]
- nm_process = subprocess.Popen(cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- (process_output, err_output) = nm_process.communicate()
-
- if nm_process.returncode != 0:
- if err_output:
- raise Exception, err_output
- else:
- raise Exception, process_output
-
- return process_output
-
-
-def GetNmSymbols(nm_infile, outfile, library, jobs, verbose,
- addr2line_binary, nm_binary, disambiguate, src_path):
- if nm_infile is None:
- if outfile is None:
- outfile = tempfile.NamedTemporaryFile(delete=False).name
-
- if verbose:
- print 'Running parallel addr2line, dumping symbols to ' + outfile
- RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs,
- disambiguate, src_path)
-
- nm_infile = outfile
-
- elif verbose:
- print 'Using nm input from ' + nm_infile
- with file(nm_infile, 'r') as infile:
- return list(binary_size_utils.ParseNm(infile))
-
-
-PAK_RESOURCE_ID_TO_STRING = { "inited": False }
-
-def LoadPakIdsFromResourceFile(filename):
- """Given a file name, it loads everything that looks like a resource id
- into PAK_RESOURCE_ID_TO_STRING."""
- with open(filename) as resource_header:
- for line in resource_header:
- if line.startswith("#define "):
- line_data = line.split()
- if len(line_data) == 3:
- try:
- resource_number = int(line_data[2])
- resource_name = line_data[1]
- PAK_RESOURCE_ID_TO_STRING[resource_number] = resource_name
- except ValueError:
- pass
-
-def GetReadablePakResourceName(pak_file, resource_id):
- """Pak resources have a numeric identifier. It is not helpful when
- trying to locate where footprint is generated. This does its best to
- map the number to a usable string."""
- if not PAK_RESOURCE_ID_TO_STRING['inited']:
- # Try to find resource header files generated by grit when
- # building the pak file. We'll look for files named *resources.h"
- # and lines of the type:
- # #define MY_RESOURCE_JS 1234
- PAK_RESOURCE_ID_TO_STRING['inited'] = True
- gen_dir = os.path.join(os.path.dirname(pak_file), 'gen')
- if os.path.isdir(gen_dir):
- for dirname, _dirs, files in os.walk(gen_dir):
- for filename in files:
- if filename.endswith('resources.h'):
- LoadPakIdsFromResourceFile(os.path.join(dirname, filename))
- return PAK_RESOURCE_ID_TO_STRING.get(resource_id,
- 'Pak Resource %d' % resource_id)
-
-def AddPakData(symbols, pak_file):
- """Adds pseudo-symbols from a pak file."""
- pak_file = os.path.abspath(pak_file)
- with open(pak_file, 'rb') as pak:
- data = pak.read()
-
- PAK_FILE_VERSION = 4
- HEADER_LENGTH = 2 * 4 + 1 # Two uint32s. (file version, number of entries)
- # and one uint8 (encoding of text resources)
- INDEX_ENTRY_SIZE = 2 + 4 # Each entry is a uint16 and a uint32.
- version, num_entries, _encoding = struct.unpack('<IIB', data[:HEADER_LENGTH])
- assert version == PAK_FILE_VERSION, ('Unsupported pak file '
- 'version (%d) in %s. Only '
- 'support version %d' %
- (version, pak_file, PAK_FILE_VERSION))
- if num_entries > 0:
- # Read the index and data.
- data = data[HEADER_LENGTH:]
- for _ in range(num_entries):
- resource_id, offset = struct.unpack('<HI', data[:INDEX_ENTRY_SIZE])
- data = data[INDEX_ENTRY_SIZE:]
- _next_id, next_offset = struct.unpack('<HI', data[:INDEX_ENTRY_SIZE])
- resource_size = next_offset - offset
-
- symbol_name = GetReadablePakResourceName(pak_file, resource_id)
- symbol_path = pak_file
- symbol_type = 'd' # Data. Approximation.
- symbol_size = resource_size
- symbols.append((symbol_name, symbol_type, symbol_size, symbol_path))
-
-def _find_in_system_path(binary):
- """Locate the full path to binary in the system path or return None
- if not found."""
- system_path = os.environ["PATH"].split(os.pathsep)
- for path in system_path:
- binary_path = os.path.join(path, binary)
- if os.path.isfile(binary_path):
- return binary_path
- return None
-
-def CheckDebugFormatSupport(library, addr2line_binary):
- """Kills the program if debug data is in an unsupported format.
-
- There are two common versions of the DWARF debug formats and
- since we are right now transitioning from DWARF2 to newer formats,
- it's possible to have a mix of tools that are not compatible. Detect
- that and abort rather than produce meaningless output."""
- tool_output = subprocess.check_output([addr2line_binary, '--version'])
- version_re = re.compile(r'^GNU [^ ]+ .* (\d+).(\d+).*?$', re.M)
- parsed_output = version_re.match(tool_output)
- major = int(parsed_output.group(1))
- minor = int(parsed_output.group(2))
- supports_dwarf4 = major > 2 or major == 2 and minor > 22
-
- if supports_dwarf4:
- return
-
- print('Checking version of debug information in %s.' % library)
- debug_info = subprocess.check_output(['readelf', '--debug-dump=info',
- '--dwarf-depth=1', library])
- dwarf_version_re = re.compile(r'^\s+Version:\s+(\d+)$', re.M)
- parsed_dwarf_format_output = dwarf_version_re.search(debug_info)
- version = int(parsed_dwarf_format_output.group(1))
- if version > 2:
- print('The supplied tools only support DWARF2 debug data but the binary\n' +
- 'uses DWARF%d. Update the tools or compile the binary\n' % version +
- 'with -gdwarf-2.')
- sys.exit(1)
-
-
-def main():
- usage = """%prog [options]
-
- Runs a spatial analysis on a given library, looking up the source locations
- of its symbols and calculating how much space each directory, source file,
- and so on is taking. The result is a report that can be used to pinpoint
- sources of large portions of the binary, etceteras.
-
- Under normal circumstances, you only need to pass two arguments, thusly:
-
- %prog --library /path/to/library --destdir /path/to/output
-
- In this mode, the program will dump the symbols from the specified library
- and map those symbols back to source locations, producing a web-based
- report in the specified output directory.
-
- Other options are available via '--help'.
- """
- parser = optparse.OptionParser(usage=usage)
- parser.add_option('--nm-in', metavar='PATH',
- help='if specified, use nm input from <path> instead of '
- 'generating it. Note that source locations should be '
- 'present in the file; i.e., no addr2line symbol lookups '
- 'will be performed when this option is specified. '
- 'Mutually exclusive with --library.')
- parser.add_option('--destdir', metavar='PATH',
- help='write output to the specified directory. An HTML '
- 'report is generated here along with supporting files; '
- 'any existing report will be overwritten. Not used in '
- 'Skia.')
- parser.add_option('--library', metavar='PATH',
- help='if specified, process symbols in the library at '
- 'the specified path. Mutually exclusive with --nm-in.')
- parser.add_option('--pak', metavar='PATH',
- help='if specified, includes the contents of the '
- 'specified *.pak file in the output.')
- parser.add_option('--nm-binary',
- help='use the specified nm binary to analyze library. '
- 'This is to be used when the nm in the path is not for '
- 'the right architecture or of the right version.')
- parser.add_option('--addr2line-binary',
- help='use the specified addr2line binary to analyze '
- 'library. This is to be used when the addr2line in '
- 'the path is not for the right architecture or '
- 'of the right version.')
- parser.add_option('--jobs', type='int',
- help='number of jobs to use for the parallel '
- 'addr2line processing pool; defaults to 1. More '
- 'jobs greatly improve throughput but eat RAM like '
- 'popcorn, and take several gigabytes each. Start low '
- 'and ramp this number up until your machine begins to '
- 'struggle with RAM. '
- 'This argument is only valid when using --library.')
- parser.add_option('-v', dest='verbose', action='store_true',
- help='be verbose, printing lots of status information.')
- parser.add_option('--nm-out', metavar='PATH',
- help='keep the nm output file, and store it at the '
- 'specified path. This is useful if you want to see the '
- 'fully processed nm output after the symbols have been '
- 'mapped to source locations. By default, a tempfile is '
- 'used and is deleted when the program terminates.'
- 'This argument is only valid when using --library.')
- parser.add_option('--legacy', action='store_true',
- help='emit legacy binary size report instead of modern')
- parser.add_option('--disable-disambiguation', action='store_true',
- help='disables the disambiguation process altogether,'
- ' NOTE: this may, depending on your toolchain, produce'
- ' output with some symbols at the top layer if addr2line'
- ' could not get the entire source path.')
- parser.add_option('--source-path', default='./',
- help='the path to the source code of the output binary, '
- 'default set to current directory. Used in the'
- ' disambiguation process.')
- parser.add_option('--githash', default='latest',
- help='Git hash for the binary version. Added by Skia.')
- parser.add_option('--commit_ts', type='int', default=-1,
- help='Timestamp for the commit. Added by Skia.')
- parser.add_option('--issue_number', default='',
- help='The trybot issue number in string. Added by Skia.')
- parser.add_option('--gsutil_path', default='gsutil',
- help='Path to gsutil binary. Added by Skia.')
- opts, _args = parser.parse_args()
-
- if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in):
- parser.error('exactly one of --library or --nm-in is required')
- if (opts.nm_in):
- if opts.jobs:
- print >> sys.stderr, ('WARNING: --jobs has no effect '
- 'when used with --nm-in')
- if not opts.jobs:
- # Use the number of processors but cap between 2 and 4 since raw
- # CPU power isn't the limiting factor. It's I/O limited, memory
- # bus limited and available-memory-limited. Too many processes and
- # the computer will run out of memory and it will be slow.
- opts.jobs = max(2, min(4, str(multiprocessing.cpu_count())))
-
- if opts.addr2line_binary:
- assert os.path.isfile(opts.addr2line_binary)
- addr2line_binary = opts.addr2line_binary
- else:
- addr2line_binary = _find_in_system_path('addr2line')
- assert addr2line_binary, 'Unable to find addr2line in the path. '\
- 'Use --addr2line-binary to specify location.'
-
- if opts.nm_binary:
- assert os.path.isfile(opts.nm_binary)
- nm_binary = opts.nm_binary
- else:
- nm_binary = _find_in_system_path('nm')
- assert nm_binary, 'Unable to find nm in the path. Use --nm-binary '\
- 'to specify location.'
-
- if opts.pak:
- assert os.path.isfile(opts.pak), 'Could not find ' % opts.pak
-
- print('addr2line: %s' % addr2line_binary)
- print('nm: %s' % nm_binary)
-
- if opts.library:
- CheckDebugFormatSupport(opts.library, addr2line_binary)
-
- symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library,
- opts.jobs, opts.verbose is True,
- addr2line_binary, nm_binary,
- opts.disable_disambiguation is None,
- opts.source_path)
-
- if opts.pak:
- AddPakData(symbols, opts.pak)
-
- if opts.legacy: # legacy report
- print 'Do Not set legacy flag.'
-
- else: # modern report
- if opts.library:
- symbol_path_origin_dir = os.path.dirname(os.path.abspath(opts.library))
- else:
- # Just a guess. Hopefully all paths in the input file are absolute.
- symbol_path_origin_dir = os.path.abspath(os.getcwd())
- DumpCompactTree(symbols, symbol_path_origin_dir, opts.githash,
- opts.commit_ts, opts.issue_number, opts.gsutil_path)
- print 'Report data uploaded to GS.'
-
-
-if __name__ == '__main__':
- sys.exit(main())

Powered by Google App Engine
This is Rietveld 408576698