| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ |
| Extracts metadata information from proto traces. |
| """ |
| |
| import os |
| import sys |
| |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'perf')) |
| |
| from core.tbmv3 import trace_processor |
| |
| VERSION_NUM_QUERY = ( |
| 'select str_value from metadata where name="cr-product-version"') |
| OS_NAME_QUERY = 'select str_value from metadata where name="cr-os-name"' |
| ARCH_QUERY = 'select str_value from metadata where name="cr-os-arch"' |
| BITNESS_QUERY = ( |
| 'select int_value from metadata where name="cr-chrome-bitness"') |
| VERSION_CODE_QUERY = ( |
| 'select int_value from metadata where name="cr-playstore_version_code"') |
| MODULES_QUERY = 'select name, build_id from stack_profile_mapping' |
| |
| |
| class OSName(): |
| ANDROID = 'Android' |
| LINUX = 'Linux' |
| MAC = 'Mac OS X' |
| WINDOWS = 'Windows NT' |
| CROS = 'CrOS' |
| FUSCHIA = 'Fuschia' |
| |
| |
| class MetadataExtractor: |
| """Extracts and stores metadata from a perfetto trace. |
| |
| Attributes: |
| _initialized: boolean of whether the class has been |
| initialized or not by calling the Initialize function. |
| _trace_processor_path: path to the trace_processor executable. |
| _trace_file: path to a perfetto system trace file. |
| version_number: chrome version number (eg: 93.0.4537.0). |
| os_name: platform of the trace writer of type OSName |
| (eg. OSName.Android). |
| architecture: OS arch of the trace writer, as returned by |
| base::SysInfo::OperatingSystemArchitecture() (eg: 'x86_64). |
| bitness: integer of architecture bitness (eg. 32, 64). |
| version_code: version code of chrome used by Android play store. |
| modules: map from module name to module debug ID, for all |
| modules that need symbolization. |
| """ |
| |
| def __init__(self, trace_processor_path, trace_file): |
| self._initialized = False |
| self._trace_processor_path = trace_processor_path |
| self._trace_file = trace_file |
| self.version_number = None |
| self.os_name = None |
| self.architecture = None |
| self.bitness = None |
| self.version_code = None |
| self.modules = None |
| |
| def __str__(self): |
| return ('Initialized: {initialized}\n' |
| 'Trace Processor Path: {trace_processor_path}\n' |
| 'Trace File: {trace_file}\n' |
| 'Version Number: {version_number}\n' |
| 'OS Name: {os_name}\n' |
| 'Architecture: {architecture}\n' |
| 'Bitness: {bitness}\n' |
| 'Version Code: {version_code}\n' |
| 'Modules: {modules}\n'.format( |
| initialized=self._initialized, |
| trace_processor_path=self._trace_processor_path, |
| trace_file=self._trace_file, |
| version_number=self.version_number, |
| os_name=self.os_name, |
| architecture=self.architecture, |
| bitness=self.bitness, |
| version_code=self.version_code, |
| modules=self.modules)) |
| |
| @property |
| def trace_file(self): |
| return self._trace_file |
| |
| def GetModuleIds(self): |
| """Returns set of all module IDs in |modules| field. |
| """ |
| self.Initialize() |
| if self.modules is None: |
| return None |
| return set(self.modules.values()) |
| |
| def Initialize(self): |
| """Extracts metadata from perfetto system trace. |
| """ |
| # TODO(crbug/1239694): Implement Trace Processor method to run multiple |
| # SQL queries without processing trace for every query. |
| |
| if self._initialized: |
| return |
| self._initialized = True |
| |
| # Version Number query returns the name and number (Chrome/93.0.4537.0). |
| # Parse the result to only get the version number. |
| version_number = self._GetStringValueFromQuery(VERSION_NUM_QUERY) |
| if version_number is None: |
| self.version_number = None |
| elif version_number.count('/') == 1: |
| self.version_number = version_number.split('/')[1] |
| else: |
| self.version_number = version_number |
| # Mac 64 traces add '-64' after the version number. |
| if self.version_number is not None and self.version_number.endswith('-64'): |
| self.version_number = self.version_number[:-3] |
| |
| raw_os_name = self._GetStringValueFromQuery(OS_NAME_QUERY) |
| self.os_name = self._ParseOSName(raw_os_name) |
| |
| self.architecture = self._GetStringValueFromQuery(ARCH_QUERY) |
| self.bitness = self._GetIntValueFromQuery(BITNESS_QUERY) |
| self.version_code = self._GetIntValueFromQuery(VERSION_CODE_QUERY) |
| |
| # Parse module to be a mapping between module name and debug id |
| self.modules = self._ExtractValidModuleMap() |
| |
| def _ParseOSName(self, raw_os_name): |
| """Parsed OS name string into an enum. |
| |
| Args: |
| raw_os_name: An OS name string returned from |
| base::SysInfo::OperatingSystemName(). |
| |
| Returns: |
| An enum of type OSName. |
| |
| Raises: |
| Exception: If OS name string is not recognized. |
| """ |
| if raw_os_name is None: |
| return None |
| |
| if raw_os_name == 'Android': |
| return OSName.ANDROID |
| if raw_os_name == 'Linux': |
| return OSName.LINUX |
| if raw_os_name == 'Mac OS X': |
| return OSName.MAC |
| if raw_os_name == 'Windows NT': |
| return OSName.WINDOWS |
| if raw_os_name == 'CrOS': |
| return OSName.CROS |
| if raw_os_name == 'Fuschia': |
| return OSName.FUSCHIA |
| raise Exception('OS name "%s" not recognized: %s' % |
| (raw_os_name, self._trace_file)) |
| |
| def InitializeForTesting(self, |
| version_number=None, |
| os_name=None, |
| architecture=None, |
| bitness=None, |
| version_code=None, |
| modules=None): |
| """Sets class parameter values for test cases. |
| |
| The |trace_processor_path| and |trace_file| parameters should |
| be specified in the constructor. |
| """ |
| self._initialized = True |
| self.version_number = version_number |
| self.os_name = os_name |
| self.architecture = architecture |
| self.bitness = bitness |
| self.version_code = version_code |
| self.modules = modules |
| |
| def _GetStringValueFromQuery(self, sql): |
| """Runs SQL query on trace processor and returns 'str_value' result. |
| """ |
| try: |
| return trace_processor.RunQuery(self._trace_processor_path, |
| self._trace_file, sql)[0]['str_value'] |
| except Exception: |
| return None |
| |
| def _GetIntValueFromQuery(self, sql): |
| """Runs SQL query on trace processor and returns 'int_value' result. |
| """ |
| try: |
| return trace_processor.RunQuery(self._trace_processor_path, |
| self._trace_file, sql)[0]['int_value'] |
| except Exception: |
| return None |
| |
| def _ExtractValidModuleMap(self): |
| """Extracts valid module name to module debug ID map/dict from trace. |
| """ |
| try: |
| query_result = trace_processor.RunQuery(self._trace_processor_path, |
| self._trace_file, MODULES_QUERY) |
| module_map = {} |
| for row in query_result: |
| row_name = row['name'] |
| row_debug_id = row['build_id'] |
| # Discard invalid key, value pairs |
| if ((row_name is None or row_name == '/missing') |
| or (row_debug_id is None or row_debug_id == '/missing')): |
| continue |
| module_map[row_name] = row_debug_id.upper() |
| |
| if not module_map: |
| return None |
| return module_map |
| |
| except Exception: |
| return None |