| # Copyright 2025 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ Retrieves the system metrics and logs them into the monitors system. """ |
| |
| import json |
| import os |
| import subprocess |
| |
| from numbers import Number |
| from typing import Optional |
| |
| import monitors |
| |
| from common import run_ffx_command, get_host_tool_path |
| |
| # Copy to avoid cycle dependency. |
| TEMP_DIR = os.environ.get('TMPDIR', '/tmp') |
| |
| FXT_FILE = os.path.join(TEMP_DIR, 'perf_trace.fxt') |
| JSON_FILE = os.path.join(TEMP_DIR, 'perf_trace.json') |
| |
| METRIC_FILTERS = [ |
| 'gfx/ContiguousPooledMemoryAllocator::Allocate/size_bytes', |
| 'gfx/SysmemProtectedPool/size', |
| 'gfx/WindowedFramePredictor::GetPrediction/Predicted frame duration(ms)', |
| 'gfx/WindowedFramePredictor::GetPrediction/Render time(ms)', |
| 'gfx/WindowedFramePredictor::GetPrediction/Update time(ms)', |
| 'memory_monitor/bandwidth_free/value', |
| 'memory_monitor/free/free$', |
| 'system_metrics/cpu_usage/average_cpu_percentage', |
| ] |
| |
| |
| def start() -> None: |
| """ Starts the system tracing. """ |
| # TODO(crbug.com/40935291): May include kernel:meta, kernel:sched, magma, |
| # oemcrypto, media, kernel:syscall |
| run_ffx_command(cmd=('trace', 'start', '--background', '--categories', |
| 'gfx,memory_monitor,system_metrics', '--output', |
| FXT_FILE)) |
| |
| |
| def stop(prefix: Optional[str] = None) -> None: |
| """ Stops the system tracing and logs the metrics into the monitors system |
| with an optional prefix as part of the metric names. """ |
| run_ffx_command(cmd=('trace', 'stop')) |
| _parse_trace(prefix) |
| |
| |
| # pylint: disable=too-many-nested-blocks |
| def _parse_trace(prefix: Optional[str] = None) -> None: |
| subprocess.run([ |
| get_host_tool_path('trace2json'), f'--input-file={FXT_FILE}', |
| f'--output-file={JSON_FILE}' |
| ], |
| check=True) |
| with open(JSON_FILE, 'r') as file: |
| recorders = {} |
| for event in json.load(file)['traceEvents']: |
| if not 'args' in event: |
| # Support only the events with args now. |
| continue |
| cat_name = [event['cat'], event['name']] |
| if prefix: |
| cat_name.insert(0, prefix) |
| args = event['args'] |
| # Support only the events with str or numeric args now. |
| for arg in args: |
| if isinstance(args[arg], str): |
| cat_name.append(arg) |
| cat_name.append(args[arg]) |
| for arg in args: |
| # Allows all number types. |
| if isinstance(args[arg], Number): |
| name = cat_name.copy() |
| name.append(arg) |
| for f in METRIC_FILTERS: |
| if f in '/'.join(name) + '$': |
| if tuple(name) not in recorders: |
| recorders[tuple(name)] = monitors.average( |
| *name) |
| recorders[tuple(name)].record(args[arg]) |
| |
| |
| # For tests only. To run this test, create a perf_trace.fxt in the /tmp/, run |
| # this script and inspect /tmp/test_script_metrics.jsonpb. |
| # |
| # If nothing needs to be customized, the commands look like, |
| # $ ffx trace start --background \ |
| # --categories 'gfx,memory_monitor,system_metrics' \ |
| # --output /tmp/perf_trace.fxt |
| # -- do something on the fuchsia device -- |
| # $ ffx trace stop |
| # $ vpython3 build/fuchsia/test/perf_trace.py |
| # |
| # Note, reuse the perf_trace.fxt is OK, i.e. running perf_trace.py multiple |
| # times works. |
| if __name__ == '__main__': |
| _parse_trace() |
| monitors.dump(TEMP_DIR) |