| #!/usr/bin/env fuchsia-vendored-python |
| # Copyright 2025 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import itertools |
| import logging |
| from typing import Iterable, Iterator, MutableSequence |
| |
| from reporting import metrics |
| from trace_processing import trace_metrics, trace_model, trace_utils |
| |
| _LOGGER: logging.Logger = logging.getLogger(__name__) |
| _GPU_USAGE_EVENT_NAME = "GPU Utilization" |
| |
| |
| class GpuMetricsProcessor(trace_metrics.MetricsProcessor): |
| """Computes GPU utilization metrics. |
| |
| GPU utilization metrics mark the percentage of time the GPU is busy performing |
| work. It is a value from 0 to 1. |
| """ |
| |
| def __init__( |
| self, |
| aggregates_only: bool = True, |
| ): |
| """Constructor. |
| |
| Args: |
| aggregates_only: When True, generates GpuMin, GpuMax, GpuAverage and GpuP* (%iles). |
| Otherwise generates GpuLoad metric with all gpu values. |
| """ |
| self.aggregates_only: bool = aggregates_only |
| |
| @property |
| def event_patterns(self) -> set[str]: |
| return {_GPU_USAGE_EVENT_NAME} |
| |
| def process_metrics( |
| self, model: trace_model.Model |
| ) -> MutableSequence[metrics.TestCaseResult]: |
| all_events: Iterator[trace_model.Event] = model.all_events() |
| gpu_usage_events: Iterable[ |
| trace_model.CounterEvent |
| ] = trace_utils.filter_events( |
| all_events, |
| category="magma", |
| name=_GPU_USAGE_EVENT_NAME, |
| type=trace_model.CounterEvent, |
| ) |
| |
| gpu_starts: list[float] = [] |
| gpu_percentages: list[float] = [] |
| |
| # Parse the start time and percentage for each `Event`. |
| for event in gpu_usage_events: |
| gpu_starts.append(event.start.to_epoch_delta().to_nanoseconds()) |
| gpu_percentages.append(event.args.get("utilization") or 0) |
| |
| # The duration for each utilization is the time between it and the next one. |
| # We don't know the duration for the last event so add one '0'. |
| gpu_durations: list[float] = [ |
| curr - prev for prev, curr in itertools.pairwise(gpu_starts) |
| ] + [0] |
| |
| if len(gpu_percentages) == 0: |
| _LOGGER.info( |
| "No gpu usage measurements are present. Perhaps the trace " |
| "duration is too short to provide gpu usage information" |
| ) |
| return [] |
| |
| if len(gpu_durations) != len(gpu_percentages): |
| _LOGGER.warning( |
| "The number of GPU duration segments don't match the number " |
| f"of percentages. ({len(gpu_durations)} != {len(gpu_percentages)}) Data may be truncated." |
| ) |
| |
| if self.aggregates_only: |
| return trace_utils.standard_metrics_set( |
| values=gpu_percentages, |
| label_prefix="Gpu", |
| unit=metrics.Unit.percent, |
| durations=gpu_durations, |
| ) |
| return [ |
| metrics.TestCaseResult( |
| "GpuUtilization", metrics.Unit.percent, gpu_percentages |
| ) |
| ] |