• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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