blob: 02908a52bd4c06445d27c7d623f9c28070ce94ce [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2024 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.
from typing import Any, TypedDict
from reporting import metrics
from trace_processing.metrics import cpu
# Default cut-off for the percentage CPU. Any process that has CPU below this
# won't be listed in the results. User can pass in a cutoff.
DEFAULT_PERCENT_CUTOFF = 0.0
class Record(TypedDict):
tid: int
process_name: str
thread_name: str
cpu: int
duration: float
percent: float
class AggregateRecord(TypedDict):
process_name: str
thread_name: str
duration: float
percent: float
def record_from_dict(t: dict[str, metrics.JSON]) -> Record:
record: dict[str, Any] = {}
for key, key_type in Record.__annotations__.items():
assert key in t and isinstance(
t[key], key_type
), f"{t} must contain {key} of type {key_type}"
record[key] = t[key]
# Types are manually verified above, and mypy can't figure out that it's
# safe to unpack `record` and build a `Record` typed-dict with it.
return Record(**record) # type: ignore
class AggCpuBreakdownMetricsProcessor:
"""
Aggregates a given breakdown over the cores for each available frequency,
and outputs in a free-form metrics format.
"""
def __init__(
self,
# A map from cpu numbers to their frequency.
# e.g. { 0: 1.8, 1: 1.8, 2: 2.2, 3: 2.2, 4: : 2.2, 5: 2.2 }
cpu_to_freq: dict[int, float],
total_time: float,
percent_cutoff: float = DEFAULT_PERCENT_CUTOFF,
) -> None:
# Transforms the frequency config to a map from cpu to frequency. Used
# to determine which frequency each record should contribute its duration to.
self._cpu_to_freq = cpu_to_freq
self._percent_cutoff = percent_cutoff
self._total_time = total_time
def aggregate_metrics(
self, breakdown: cpu.Breakdown
) -> dict[float, list[AggregateRecord]]:
"""
Given the breakdown of duration per thread, iterates through all the threads' durations for each
CPU and aggregates them over each CPU frequency.
Args:
breakdown: The json output from the cpu_breakdown script.
"""
# Map from frequency to tid to aggregated duration.
freq_to_tid_durs: dict[float, dict[int, float]] = {}
# Tracks the final output. Contains a map from frequency to list of
# threads with their durations and percentages.
agg_breakdown: dict[float, list[AggregateRecord]] = {}
tid_to_thread_name: dict[int, str] = {}
tid_to_process_name: dict[int, str] = {}
for r in breakdown:
# Save process and thread name for tid
t = record_from_dict(r)
tid = t["tid"]
tid_to_process_name[tid] = t["process_name"]
tid_to_thread_name[tid] = t["thread_name"]
# Get frequency for the cpu
freq = self._cpu_to_freq[t["cpu"]]
# Set the duration sum for the tid to 0 if nonexistent
freq_to_tid_durs.setdefault(freq, {})
freq_to_tid_durs[freq].setdefault(tid, 0)
# Add the duration to the tid
duration = t["duration"]
freq_to_tid_durs[freq][tid] += duration
for freq, tid_durs in freq_to_tid_durs.items():
dur_list: list[AggregateRecord] = []
for tid, dur in tid_durs.items():
percent = (dur / self._total_time) * 100
if percent >= self._percent_cutoff:
dur_list.append(
AggregateRecord(
{
"process_name": tid_to_process_name[tid],
"thread_name": tid_to_thread_name[tid],
"duration": round(dur, 3),
"percent": round(percent, 3),
}
)
)
agg_breakdown[freq] = sorted(
dur_list,
key=lambda m: (m["duration"]),
reverse=True,
)
return agg_breakdown