blob: 345d4b26cf3f27a5e355943c179d085c205bd152 [file] [log] [blame]
// Copyright 2019 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 'dart:math';
import '../time_delta.dart';
import '../time_point.dart';
import '../trace_model.dart';
// This file contains common utility functions that are shared across metrics.
T _add<T extends num>(T a, T b) => a + b;
T _min<T extends Comparable<T>>(T a, T b) => a.compareTo(b) < 0 ? a : b;
T _max<T extends Comparable<T>>(T a, T b) => a.compareTo(b) > 0 ? a : b;
/// Get all events contained in [model], without regard for what [Process] or
/// [Thread] they belong to.
Iterable<Event> getAllEvents(Model model) => model.processes
.expand((process) => process.threads.expand((thread) => thread.events));
/// Get the trace duration by subtracing the max event end time by the min event start time.
TimeDelta getTotalTraceDuration(Model model) {
final allEvents = getAllEvents(model);
if (allEvents.isEmpty) {
return TimeDelta.zero();
}
final TimePoint traceStartTime = allEvents.map((e) => e.start).reduce(_min);
final TimePoint traceEndTime = allEvents
.map((e) => e is DurationEvent
? e.start + e.duration
: (e is AsyncEvent ? e.start + e.duration : e.start))
.reduce(_max);
return traceEndTime - traceStartTime;
}
/// Filter [events] for events that have a matching [category] and [name]
/// fields.
Iterable<Event> filterEvents(Iterable<Event> events,
{String category, String name}) =>
events.where((event) =>
(category == null || event.category == category) &&
(name == null || event.name == name));
/// Filter [events] for events that have a matching [category] and [name]
/// fields, that are also of type [T].
Iterable<T> filterEventsTyped<T extends Event>(Iterable<Event> events,
{String category, String name}) =>
Iterable.castFrom<Event, T>(events.where((event) =>
(event is T) &&
(category == null || event.category == category) &&
(name == null || event.name == name)));
Iterable<T> getArgValuesFromEvents<T extends Object>(
Iterable<Event> events, String argKey) {
return events.map((e) {
if (!(e.args.containsKey(argKey) && e.args[argKey] is T)) {
throw ArgumentError(
'Error, expected events to include arg key "$argKey" of type $T');
}
final T v = e.args[argKey];
return v;
});
}
/// Find all [Event]s that follow [event].
///
/// A following event is defined to be all events that are reachable by walking
/// forward in the flow/duration graph. "Walking forward" means recursively
/// visiting all sub-duration-events and outgoing flow events for duration
/// events, and the enclosing duration and next flow event for flow events.
List<Event> getFollowingEvents(Event event) {
final List<Event> frontier = [event];
final Set<Event> visited = {};
while (frontier.isNotEmpty) {
final current = frontier.removeLast();
if (current == null) {
continue;
}
final added = visited.add(current);
if (!added) {
continue;
}
if (current is DurationEvent) {
frontier..addAll(current.childDurations)..addAll(current.childFlows);
} else if (current is FlowEvent) {
frontier..add(current.enclosingDuration)..add(current.nextFlow);
} else {
assert(false);
}
}
final result = List<Event>.from(visited)
..sort((a, b) => a.start.compareTo(b.start));
return result;
}
/// Find the first "VSYNC" event that [durationEvent] is connected to.
///
/// Returns [null] if no "VSYNC" is found.
DurationEvent findFollowingVsync(DurationEvent durationEvent) {
final followingVsyncs = filterEventsTyped<DurationEvent>(
getFollowingEvents(durationEvent),
category: 'gfx',
name: 'Display::Controller::OnDisplayVsync');
if (followingVsyncs.isEmpty) {
return null;
}
return followingVsyncs.first;
}
/// Compute the mean (https://en.wikipedia.org/wiki/Arithmetic_mean#Definition)
/// of [values].
double computeMean<T extends num>(Iterable<T> values) {
if (values.isEmpty) {
throw ArgumentError(
'[values] must not be empty in order to compute its average');
}
return values.reduce(_add) / values.length;
}
/// Compute the population variance (https://en.wikipedia.org/wiki/Variance#Population_variance)
/// of [values].
double computeVariance<T extends num>(Iterable<T> values) {
final mean = computeMean(values);
return values.map((value) => pow(value - mean, 2.0)).reduce(_add) /
values.length;
}
/// Compute the population standard deviation (https://en.wikipedia.org/wiki/Standard_deviation#Uncorrected_sample_standard_deviation)
/// of [values].
double computeStandardDeviation<T extends num>(Iterable<T> values) =>
sqrt(computeVariance(values));
/// Compute the linear interpolated [percentile]th percentile
/// (https://en.wikipedia.org/wiki/Percentile) of [values].
double computePercentile<T extends num>(Iterable<T> values, int percentile) {
if (values.isEmpty) {
throw ArgumentError(
'[values] must not be empty in order to compute percentile');
}
final valuesAsList = values.toList()..sort();
if (percentile == 100) {
return valuesAsList.last.toDouble();
}
final indexAsFloat = (valuesAsList.length - 1.0) * (0.01 * percentile);
final index = indexAsFloat.floor();
final fractional = indexAsFloat % 1.0;
if (index + 1 == valuesAsList.length) {
return valuesAsList.last.toDouble();
}
return valuesAsList[index] * (1.0 - fractional) +
valuesAsList[index + 1] * fractional;
}
/// Compute the maximum (https://en.wikipedia.org/wiki/Sample_maximum_and_minimum)
/// of [values].
T computeMax<T extends num>(Iterable<T> values) => values.reduce(max);
/// Compute the minimum (# https://en.wikipedia.org/wiki/Sample_maximum_and_minimum)
/// of [values].
T computeMin<T extends num>(Iterable<T> values) => values.reduce(min);
/// Compute the 1st degree difference of [values].
///
/// I.e., [x0, x1, x2, ...] -> [(x1 - x0), (x2 - x1), ...]
List<T> differenceValues<T extends num>(Iterable<T> values) {
final List<T> result = [];
T previous;
for (final value in values) {
if (previous != null) {
final difference = value - previous;
result.add(difference);
}
previous = value;
}
return result;
}
/// Generate a string summary of [values].
///
/// Example output:
/// count: 1033
/// mean: 1.5378480048402718
/// std: 1.5095374570697047
/// min: 0.626416
/// 25%: 1.105875
/// 50%: 1.173375
/// 75%: 1.3925
/// max: 26.257833
String describeValues<T extends num>(List<T> values,
{int indent = 0, List<int> percentiles = const [25, 50, 75]}) =>
[
'count: ${values.length}',
'mean: ${values.isNotEmpty ? computeMean(values) : double.nan}',
'std: ${values.length > 1 ? computeStandardDeviation(values) : double.nan}',
'min: ${values.isNotEmpty ? computeMin(values) : double.nan}',
...percentiles.map((percentile) {
final statName = '$percentile%';
final lineStart = '$statName:${' ' * (6 - statName.length)}';
return '$lineStart${values.isNotEmpty ? computePercentile(values, percentile) : double.nan}';
}),
'max: ${values.isNotEmpty ? computeMax(values) : double.nan}',
'',
].map((line) => '${' ' * indent}$line\n').join('');
/// Iterate over a sequence of [T1]s and [T2]s by applying [_f] to pairs of
/// elements in the sequences.
///
/// In other words, iterate over [x1, x2, ...] and [y1, y2, ...] like
/// [_f(x1, y1), _f(x2, y2), ...]. Iteration stops when any [Iterable] is
/// exhausted.
class Zip2Iterable<T1, T2, R> extends Iterable<R> {
final Iterable<T1> _t1s;
final Iterable<T2> _t2s;
final R Function(T1, T2) _f;
Zip2Iterable(this._t1s, this._t2s, this._f);
@override
Iterator<R> get iterator =>
Zip2Iterator<T1, T2, R>(_t1s.iterator, _t2s.iterator, _f);
}
class Zip2Iterator<T1, T2, R> extends Iterator<R> {
final Iterator<T1> _t1s;
final Iterator<T2> _t2s;
final R Function(T1, T2) _f;
R _current;
Zip2Iterator(this._t1s, this._t2s, this._f);
@override
bool moveNext() {
if (!(_t1s.moveNext() && _t2s.moveNext())) {
return false;
}
_current = _f(_t1s.current, _t2s.current);
return true;
}
@override
R get current => _current;
}