1# Copyright 2025 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4""" Retrieves the system metrics and logs them into the monitors system. """ 5 6import json 7import os 8import subprocess 9 10from numbers import Number 11from typing import Optional 12 13import monitors 14 15from common import run_ffx_command, get_host_tool_path 16 17# Copy to avoid cycle dependency. 18TEMP_DIR = os.environ.get('TMPDIR', '/tmp') 19 20FXT_FILE = os.path.join(TEMP_DIR, 'perf_trace.fxt') 21JSON_FILE = os.path.join(TEMP_DIR, 'perf_trace.json') 22 23METRIC_FILTERS = [ 24 'gfx/ContiguousPooledMemoryAllocator::Allocate/size_bytes', 25 'gfx/SysmemProtectedPool/size', 26 'gfx/WindowedFramePredictor::GetPrediction/Predicted frame duration(ms)', 27 'gfx/WindowedFramePredictor::GetPrediction/Render time(ms)', 28 'gfx/WindowedFramePredictor::GetPrediction/Update time(ms)', 29 'memory_monitor/bandwidth_free/value', 30 'memory_monitor/free/free$', 31 'system_metrics/cpu_usage/average_cpu_percentage', 32] 33 34 35def start() -> None: 36 """ Starts the system tracing. """ 37 # TODO(crbug.com/40935291): May include kernel:meta, kernel:sched, magma, 38 # oemcrypto, media, kernel:syscall 39 run_ffx_command(cmd=('trace', 'start', '--background', '--categories', 40 'gfx,memory_monitor,system_metrics', '--output', 41 FXT_FILE)) 42 43 44def stop(prefix: Optional[str] = None) -> None: 45 """ Stops the system tracing and logs the metrics into the monitors system 46 with an optional prefix as part of the metric names. """ 47 run_ffx_command(cmd=('trace', 'stop')) 48 _parse_trace(prefix) 49 50 51# pylint: disable=too-many-nested-blocks 52def _parse_trace(prefix: Optional[str] = None) -> None: 53 subprocess.run([ 54 get_host_tool_path('trace2json'), f'--input-file={FXT_FILE}', 55 f'--output-file={JSON_FILE}' 56 ], 57 check=True) 58 with open(JSON_FILE, 'r') as file: 59 recorders = {} 60 for event in json.load(file)['traceEvents']: 61 if not 'args' in event: 62 # Support only the events with args now. 63 continue 64 cat_name = [event['cat'], event['name']] 65 if prefix: 66 cat_name.insert(0, prefix) 67 args = event['args'] 68 # Support only the events with str or numeric args now. 69 for arg in args: 70 if isinstance(args[arg], str): 71 cat_name.append(arg) 72 cat_name.append(args[arg]) 73 for arg in args: 74 # Allows all number types. 75 if isinstance(args[arg], Number): 76 name = cat_name.copy() 77 name.append(arg) 78 for f in METRIC_FILTERS: 79 if f in '/'.join(name) + '$': 80 if tuple(name) not in recorders: 81 recorders[tuple(name)] = monitors.average( 82 *name) 83 recorders[tuple(name)].record(args[arg]) 84 85 86# For tests only. To run this test, create a perf_trace.fxt in the /tmp/, run 87# this script and inspect /tmp/test_script_metrics.jsonpb. 88# 89# If nothing needs to be customized, the commands look like, 90# $ ffx trace start --background \ 91# --categories 'gfx,memory_monitor,system_metrics' \ 92# --output /tmp/perf_trace.fxt 93# -- do something on the fuchsia device -- 94# $ ffx trace stop 95# $ vpython3 build/fuchsia/test/perf_trace.py 96# 97# Note, reuse the perf_trace.fxt is OK, i.e. running perf_trace.py multiple 98# times works. 99if __name__ == '__main__': 100 _parse_trace() 101 monitors.dump(TEMP_DIR) 102