Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Unified Diff: tracing/tracing/extras/chrome/cpu_time.html

Issue 2804043003: [WIP] [DEFINITELY NOT READY TO LAND] Cpu time metric implementation (Closed)
Patch Set: Diff from base Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tracing/tracing/extras/chrome/cpu_time.html
diff --git a/tracing/tracing/extras/chrome/cpu_time.html b/tracing/tracing/extras/chrome/cpu_time.html
new file mode 100644
index 0000000000000000000000000000000000000000..8cc6b00a814a5fe585f12402e86c7ed8e6dfc38e
--- /dev/null
+++ b/tracing/tracing/extras/chrome/cpu_time.html
@@ -0,0 +1,308 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/multi_dimensional_view.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/model/helpers/chrome_renderer_helper.html">
+<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.chrome.cpuTime', function() {
+ /**
+ * Returns the total cpu time consumed within |range| by |thread|.
+ */
+ function getCpuTimeForThread(thread, range) {
+ let totalCpuTime = 0;
+ tr.b.math.iterateOverIntersectingIntervals(
benjhayden 2017/04/07 18:05:17 High-level design question: associatedEvents? Way
dproy 2017/04/07 18:35:10 Thank you so much for providing the context here!
+ thread.sliceGroup.topLevelSlices,
+ slice => slice.start, slice => slice.end,
+ range.min, range.max,
+ slice => {
+ if (slice.duration === 0) return;
+ if (!slice.cpuDuration) return;
+
+ const intersection = range.findIntersection(slice.range);
+ const fractionOfSliceInsideRangeOfInterest =
+ intersection.duration / slice.duration;
+
+ // We assume that if a slice doesn't lie entirely inside the range of
+ // interest, then the CPU time is evenly distributed inside of the
+ // slice.
+ totalCpuTime +=
+ slice.cpuDuration * fractionOfSliceInsideRangeOfInterest;
+ });
+
+ return totalCpuTime;
+ }
+
+ /**
+ * Returns two level map of rail stage to initiator type to set of associated
+ * segments that intersects with |rangeOfInterest|.
+ *
+ * For each rail stage, we additionally have a value for the key
+ * 'all_initiators' returns all the segments associated with that rail stage
+ * across all initiator types. For completeness, there is an additional rail
+ * stage 'all_stages' that has all the segments across all rail stages.
+ *
+ * The returned value is a
+ * Map<string, Map<string, Set<tr.model.um.Segment>> object.
+ * Example content of the map:
+ *
+ * {
+ * 'Animation': {
+ * 'CSS': {Segments for CSS Animation},
+ * 'Video': {Segments for Video Animation},
+ * ...
+ * 'all_initiators': {All Animation segments}
+ * },
+ * 'Response': {
+ * 'Click': {Segments for Click Response},
+ * 'Scroll': {Segments for Scroll Response},
+ * ...
+ * 'all_initiators': {All Response segments}
+ * },
+ * ...
+ * 'all_stages': {
+ * 'all_initiators': {All segments intersecting with |rangeOfInterest|}
+ * }
+ * }
+ *
+ * @param {!Array<tr.model.um.Segment>} segments
+ * @param {!Array<tr.b.math.Range>} rangeOfInterest
+ */
+ function getStageToInitiatorToSegments(segments, rangeOfInterest) {
+ const stageToInitiatorToSegments = new Map();
+ stageToInitiatorToSegments.set('all_stages',
+ new Map([['all_initiators', new Set()]]));
+
+ for (const segment of segments) {
+ if (!rangeOfInterest.intersectsRangeInclusive(segment.range)) continue;
+ stageToInitiatorToSegments.get(
+ 'all_stages').get('all_initiators').add(segment);
+
+ for (const exp of segment.expectations) {
+ const stageTitle = exp.stageTitle;
+ if (!stageToInitiatorToSegments.has(stageTitle)) {
+ stageToInitiatorToSegments.set(stageTitle,
+ new Map([['all_initiators', new Set()]]));
+ }
+
+ const initiatorToSegments = stageToInitiatorToSegments.get(stageTitle);
+ initiatorToSegments.get('all_initiators').add(segment);
+
+ const initiatorType = exp.initiatorType;
+ if (initiatorType) {
benjhayden 2017/04/07 18:05:17 I think all UEs should have initiatorType, right?
dproy 2017/04/07 18:35:10 Idle Expectation doesn't.
+ if (!initiatorToSegments.has(initiatorType)) {
+ initiatorToSegments.set(initiatorType, new Set());
+ }
+ initiatorToSegments.get(initiatorType).add(segment);
+ }
+ }
+ }
+ return stageToInitiatorToSegments;
+ }
+
+ /**
+ * Returns a map of range in |ranges| to total cpu time used by |thread|
+ * during that range.
+ */
+ function computeCpuTimesForRanges_(ranges, thread) {
+ const rangeToCpuTime = new Map();
+ for (const range of ranges) {
+ rangeToCpuTime.set(range, getCpuTimeForThread(thread, range));
+ }
+ return rangeToCpuTime;
+ }
+
+ /**
+ * Returns a map of segment in |segments| to intersection of bounds of that
+ * segment and |rangeOfInterest|.
+ *
+ * We create this map so that we have a unique intersected range for each
+ * segment. This saves memory, and the unique range can be used as a key in
+ * other maps.
+ */
+ function getSegmentToBounds_(segments, rangeOfInterest) {
+ const segmentToBounds = new Map();
+ for (const segment of segments) {
+ segmentToBounds.set(segment,
benjhayden 2017/04/07 18:05:17 Drop empty intersections?
dproy 2017/04/07 18:35:10 Yes - I missed that. Will update.
dproy 2017/04/07 18:53:48 Actually I changed my mind. |segments| is already
+ rangeOfInterest.findIntersection(segment.range));
+ }
+ return segmentToBounds;
+ }
+
+ /**
+ * Returns a two level map of rail stage to initiator type to array of ranges
+ * associated with that stage and initiator type within |rangeOfInterest|.
+ *
+ * The returned data format is exactly similar to
+ * |getStageToInitiatorToSegments|, except this function has array segment
+ * bounds instead of array of segments in the leaf node. This function does
+ * all the work of intersecting segment bounds with |rangeOfInterest| so that
+ * the intersection logic does not have to sprinkled all over the rest of the
+ * code.
+ *
+ */
+ function getStageToInitiatorToSegmentBounds(segments, rangeOfInterest) {
+ const stageToInitiatorToSegments =
+ getStageToInitiatorToSegments(segments, rangeOfInterest);
+ const segmentToBounds = getSegmentToBounds_(
+ stageToInitiatorToSegments.get('all_stages').get('all_initiators'),
+ rangeOfInterest);
+
+ const stageToInitiatorToRanges = new Map();
+ for (const [stage, initiatorToSegments] of stageToInitiatorToSegments) {
+ const initiatorToRanges = new Map();
+ stageToInitiatorToRanges.set(stage, initiatorToRanges);
+ for (const [initiator, segments] of initiatorToSegments) {
+ const segmentBounds =
+ [...segments].map(
+ segment => segmentToBounds.get(segment));
+ initiatorToRanges.set(initiator, segmentBounds);
+ }
+ }
+
+ return stageToInitiatorToRanges;
+ }
+
+ /**
+ * Returns the root node of a MultiDimensionalView in TopDownTreeView for cpu
+ * time.
+ *
+ * The returned tree view is three dimensional (processType, threadType, and
+ * railStage + initiator). Rail stage and initiator are not separate
+ * dimensions because they are not independent - there is no such thing as CSS
+ * Response or Scroll Load.
+ *
+ * Each node in the tree view contains two values - cpuUsage and cpuTotal.
+ *
+ * When talking about multidimensional tree views, a useful abstration is
+ * "path", which uniquely determines a node in the tree: A path is a 3 element
+ * array, and each of these three elements is a possibly empty array of
+ * strings. Here is an example path:
+ * [ ["browser_process"], ["CrBrowserMain"], ["Animation", "CSS"] ]
+ * Dimension 1 Dimension 2 Dimension 3
+ *
+ * We can arrive at the node denoted by this path in many different ways
+ * starting from the root node, so this path is not to be confused with the
+ * graph theoretic notion of path. Here is one of the ways to reach the node
+ * (we show the intermediate paths during the traversal inline):
+ *
+ * const node = treeRoot // [[], [], []]
+ * // access children along first dimension
+ * .children[0]
+ * // [['browser_process'], [], []]
+ * .get('browser_process')
+ * // access children along third dimension
+ * .children[2]
+ * // [['browser_process'], [], ['Animation']]
+ * .get('Animation')
+ * // Access children along second dimension
+ * .children[1]
+ * // [['browser_process'], ['CrBrowserMain'], ['Animation']]
+ * .get('CrBrowserMain')
+ * // Go further down along third dimension
+ * .children[2]
+ * // [['browser_process'], ['CrBrowserMain'], ['Animation', 'CSS']]
+ * .get('CSS')
+ *
+ * Now node.values contains the cpu time data for the browser main thread
+ * during the CSS Animation stage.
+ * node.values[0] is cpuUsage - cpu time over per unit of wall clock time
+ * node.values[1] is cpuTotal - total miliseconds of used cpu time
+ *
+ * The path for the node that hold data for all threads of renderer process
+ * during scroll response expectations is
+ * [["renderer_process"], [], ["Response", "Scroll"]]
+ * As we can see, we simply have an empty array for the second dimension. This
+ * works similarly if we want to get data for all processes for a particular
+ * thread.
+
+ * However, if we want to access data for all rail stages and all initiator
+ * types, we have to use the special rail stage 'all_stages', and initiator
+ * type 'all_initiators'. For example, to get cpu data during all Response
+ * stages for all processes and threads, we use the node at path
+ * [[], [], ['Response', 'all_initiators']]
+ *
+ * To get cpu data for all rail stages for ChildIOThread, we use the path
+ * [[], ['ChildIOThread'], ['all_stages', 'all_initiators']]
+ *
+ * This is because the tree view automatically aggregates cpu time
+ * data along each dimension by summing values on the children nodes. For
+ * aggregating rail stages and initiator types, summing is not the right thing
+ * to do since
+ *
+ * 1. User Expectations can overlap (for example, one tab can go through a
+ * Video Animation while another tab is concurrently going through a CSS
+ * Animation - it's worth noting that user expectations are not scoped to a
+ * tab.)
+ *
+ * 2. Different rail stages have different durations (for example, if we
+ * have 200ms of Video Animation with 50% cpuUsage, and 500ms of CSS
+ * Animation with 60% cpuUage, cpuUsage for all Animations is clearly not
+ * 110%.)
+ *
+ * We therefore more manually do the appropriate aggregations and store the
+ * data in 'all_stages' and 'all_initiators' nodes.
+ *
+ * This function adds all the data for the leaf nodes to a
+ * |MultiDimensionalViewBuilder|. The builder then computes the rest of the
+ * tree upon calling |buildTopDownTreeView|.
+ */
+ function constructMultiDimensionalView(model, rangeOfInterest) {
+ const mdvBuilder = new tr.b.MultiDimensionalViewBuilder(
+ 3 /* dimensions (process, thread and rail stage / initiator) */,
+ 2 /* valueCount (cpuUsage and cpuTotal) */);
+
+ const stageToInitiatorToRanges =
+ getStageToInitiatorToSegmentBounds(
+ model.userModel.segments, rangeOfInterest);
+
+ const allSegmentBoundsInRange =
+ stageToInitiatorToRanges.get('all_stages').get('all_initiators');
+
+ for (const pid in model.processes) {
+ const process = model.processes[pid];
+ const processType =
+ tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);
+ for (const tid in process.threads) {
+ const thread = process.threads[tid];
+ const threadType = thread.type;
+
+ // Cache cpuTime for each segment bound.
+ const rangeToCpuTime = computeCpuTimesForRanges_(
+ allSegmentBoundsInRange, thread);
+
+ for (const [stage, initiatorToRanges] of stageToInitiatorToRanges) {
+ for (const [initiator, ranges] of initiatorToRanges) {
+ const cpuTime = tr.b.math.Statistics.sum(ranges,
+ range => rangeToCpuTime.get(range));
+ const duration = tr.b.math.Statistics.sum(ranges,
+ range => range.duration);
+ const cpuTimePerSecond = cpuTime / duration;
+ mdvBuilder.addPath(
+ [[processType], [threadType], [stage, initiator]],
+ [cpuTimePerSecond, cpuTime],
+ tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
+ }
+ }
+ }
+ }
+
+ return mdvBuilder.buildTopDownTreeView();
+ }
+
+ return {
+ getCpuTimeForThread,
+ getStageToInitiatorToSegments,
+ getStageToInitiatorToSegmentBounds,
+ constructMultiDimensionalView,
+ };
+});
+</script>

Powered by Google App Engine
This is Rietveld 408576698