# 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, '--buffer-size', '32', '--buffering-mode',
                         'streaming'))


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)
