1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
# 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.com/40193968): 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
|