pH/sommelier/scripts/buffer_stats.py

245 lines
7.5 KiB
Python
Raw Permalink Normal View History

# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utility class for analyzing sommelier buffer statistics."""
import argparse
from collections import defaultdict
import sys
class Action(object):
"""Hold data about sommelier buffer operations."""
__slots__ = ("type", "bid", "time")
def __init__(self, action_type, bid, time):
self.type = action_type
self.bid = bid
self.time = time
class Stats:
"""For processing text files of sommelier data and printing their stats."""
def __init__(self):
self.surfaces = defaultdict(list)
self.bid_sid = {}
self.sid_bid = {}
def read(self, filename):
"""Read in a sommelier-timing output file for later processing.
Sample file:
Event, Type, Surface_ID, Buffer_ID, Time # header line 1
0 surface_attach 12 23 1612345678.987654321 # line 2
1 surface_commit 12 -1 1612345678.987654325 # lines 3, 4, ...
Run sommelier with the timing-filename option to generate an event log:
sommelier -X --glamor --trace-system \
--timing-filename=out.txt glxgears &
Args:
filename (string): The path to the buffer time data.
"""
with open(filename, "r") as f:
# Skip the header line.
f.readline()
for line in f:
try:
[_, action_type, sid, bid, timestamp] = line.split()
except ValueError:
continue
sid = int(sid)
bid = int(bid)
# use dictionary history to fill missing ids
if sid != -1 and bid != -1:
self.sid_bid[sid] = bid
self.bid_sid[bid] = sid
elif sid in self.sid_bid:
bid = self.sid_bid[sid]
elif bid in self.bid_sid:
sid = self.bid_sid[bid]
# convert string time into floating point time
time = float(timestamp)
self.surfaces[sid].append(Action(action_type, bid, time))
def add(self, durr, tot, err):
"""Kahan sum function to reduce floating point error.
Args:
durr (float): The elapsed time between two actions.
tot (float): The summed total of elapsed times.
err (float): The accumulated error.
Returns:
tot (int): the new summed total of elapsed times.
err (int): the new accumulated error.
"""
y = durr - err
t = tot + y
err = (t - tot) - y
tot = t
return tot, err
def add_acr(self, sid, time, acr, idx):
"""Update the sum/total, error, and count of each action type.
Args:
sid (int): the id of the surface to display stats for.
time (float): the time the current action to place.
acr (dict): A dicitionary containing numerical info on accumulated stats.
idx (dict): A dictionary containing the last index of an action
"""
for action, pos in idx.items():
if pos is not None:
prev_time = self.surfaces[sid][pos].time
x = action[0] # use first letter of action type in key names
tot, err = self.add(time - prev_time, acr[x], acr[x + "err"])
acr[x] = tot
acr[x + "err"] = err
acr[x + "_count"] += 1
def print_stats(self, sid):
"""Print the average times between calls to a surface.
Args:
sid (int): the id of the surface to display stats for.
"""
# to_attach - a dict used in calculating summed difference from any other
# call (a, c, r) to an "attach" call.
# a - attach, c - commit, r - release
# aerr - accumulated numerical error from summing differences.
# a_count - count of attach-attach differences so far.
to_attach = {
"a": 0,
"aerr": 0,
"a_count": 0,
"c": 0,
"cerr": 0,
"c_count": 0,
"r": 0,
"rerr": 0,
"r_count": 0,
}
to_commit = {
"a": 0,
"aerr": 0,
"a_count": 0,
"c": 0,
"cerr": 0,
"c_count": 0,
"r": 0,
"rerr": 0,
"r_count": 0,
}
to_release = {
"a": 0,
"aerr": 0,
"a_count": 0,
"c": 0,
"cerr": 0,
"c_count": 0,
"r": 0,
"rerr": 0,
"r_count": 0,
}
# idx stores the last index of an attach, commit, and release call.
idx = {"attach": None, "commit": None, "release": None}
# For every action, calculate summed difference based on its type.
# e.g. for a commit action, calculate time between it and the previous
# attach, commit, and release call using to_commit.
for i, action in enumerate(self.surfaces[sid]):
time = action.time
if action.type == "attach":
self.add_acr(sid, time, to_attach, idx)
idx["attach"] = i
elif action.type == "commit":
self.add_acr(sid, time, to_commit, idx)
idx["commit"] = i
elif action.type == "release":
self.add_acr(sid, time, to_release, idx)
idx["release"] = i
print(
"attach-attach avg: ",
to_attach["a"] * 1000 / to_attach["a_count"],
" ms",
)
print(
"commit-attach avg: ",
to_attach["c"] * 1000 / to_attach["c_count"],
" ms",
)
print(
"release-attach avg: ",
to_attach["r"] * 1000 / to_attach["r_count"],
" ms",
)
print()
print(
"commit-commit avg: ",
to_commit["c"] * 1000 / to_commit["c_count"],
" ms",
)
print(
"attach-commit avg: ",
to_commit["a"] * 1000 / to_commit["a_count"],
" ms",
)
print(
"release-commit avg: ",
to_commit["r"] * 1000 / to_commit["r_count"],
" ms",
)
print()
print(
"release-release avg: ",
to_release["r"] * 1000 / to_release["r_count"],
" ms",
)
print(
"attach-release avg: ",
to_release["a"] * 1000 / to_release["a_count"],
" ms",
)
print(
"commit-release avg: ",
to_release["c"] * 1000 / to_release["c_count"],
" ms",
)
def main(args):
parser = argparse.ArgumentParser(
description="Compute average of interprocess times"
)
parser.add_argument("file", type=str, help="the timing log file to process")
parser.add_argument(
"--surface_id",
type=int,
help="the id of the surface to compute averages on",
)
args = parser.parse_args()
log = args.file
sid = args.surface_id
stats = Stats()
stats.read(log)
# Print buffer stats for the sid with the most number of actions.
if not sid:
sid = next(iter(stats.surfaces.keys()))
for key in stats.surfaces:
if len(stats.surfaces[key]) > len(stats.surfaces[sid]):
sid = key
stats.print_stats(sid)
if __name__ == "__main__":
main(sys.argv[1:])