blob: 7abcf1ec57fb8b1b6918a5ff042e7206b36fde61 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2013 The Chromium Authors
[email protected]03bf84a2013-03-23 22:11:482// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
rbpotter5796b002021-03-10 18:49:215// The maximum number of data points buffered for each stats. Old data points
6// will be shifted out when the buffer is full.
7export const MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
8
[email protected]03bf84a2013-03-23 22:11:489/**
10 * A TimelineDataSeries collects an ordered series of (time, value) pairs,
11 * and converts them to graph points. It also keeps track of its color and
12 * current visibility state.
[email protected]81f1f262013-05-09 12:38:5313 * It keeps MAX_STATS_DATA_POINT_BUFFER_SIZE data points at most. Old data
14 * points will be dropped when it reaches this size.
[email protected]03bf84a2013-03-23 22:11:4815 */
rbpotter5796b002021-03-10 18:49:2116export class TimelineDataSeries {
Philipp Hanckeec3c9542022-07-15 09:19:2117 constructor(statsType) {
[email protected]03bf84a2013-03-23 22:11:4818 // List of DataPoints in chronological order.
19 this.dataPoints_ = [];
20
21 // Default color. Should always be overridden prior to display.
22 this.color_ = 'red';
23 // Whether or not the data series should be drawn.
24 this.isVisible_ = true;
25
26 this.cacheStartTime_ = null;
27 this.cacheStepSize_ = 0;
28 this.cacheValues_ = [];
Philipp Hanckeec3c9542022-07-15 09:19:2129 this.statsType_ = statsType;
[email protected]03bf84a2013-03-23 22:11:4830 }
31
rbpotter5796b002021-03-10 18:49:2132 /**
33 * @override
34 */
35 toJSON() {
36 if (this.dataPoints_.length < 1) {
37 return {};
Evan Shrubsoleb9464e52021-03-03 18:01:1038 }
rbpotter5796b002021-03-10 18:49:2139
Philipp Hancke0635d3142021-03-16 20:02:0940 const values = [];
41 for (let i = 0; i < this.dataPoints_.length; ++i) {
rbpotter5796b002021-03-10 18:49:2142 values.push(this.dataPoints_[i].value);
43 }
44 return {
45 startTime: this.dataPoints_[0].time,
46 endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
Philipp Hanckeec3c9542022-07-15 09:19:2147 statsType: this.statsType_,
rbpotter5796b002021-03-10 18:49:2148 values: JSON.stringify(values),
49 };
50 }
Evan Shrubsoleb9464e52021-03-03 18:01:1051
52 /**
rbpotter5796b002021-03-10 18:49:2153 * Adds a DataPoint to |this| with the specified time and value.
54 * DataPoints are assumed to be received in chronological order.
Evan Shrubsoleb9464e52021-03-03 18:01:1055 */
rbpotter5796b002021-03-10 18:49:2156 addPoint(timeTicks, value) {
Philipp Hancke0635d3142021-03-16 20:02:0957 const time = new Date(timeTicks);
rbpotter5796b002021-03-10 18:49:2158 this.dataPoints_.push(new DataPoint(time, value));
59
60 if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {
61 this.dataPoints_.shift();
62 }
63 }
64
65 isVisible() {
66 return this.isVisible_;
67 }
68
69 show(isVisible) {
70 this.isVisible_ = isVisible;
71 }
72
73 getColor() {
74 return this.color_;
75 }
76
77 setColor(color) {
78 this.color_ = color;
79 }
80
81 getCount() {
82 return this.dataPoints_.length;
83 }
84 /**
85 * Returns a list containing the values of the data series at |count|
86 * points, starting at |startTime|, and |stepSize| milliseconds apart.
87 * Caches values, so showing/hiding individual data series is fast.
88 */
89 getValues(startTime, stepSize, count) {
90 // Use cached values, if we can.
91 if (this.cacheStartTime_ === startTime &&
92 this.cacheStepSize_ === stepSize &&
93 this.cacheValues_.length === count) {
94 return this.cacheValues_;
95 }
96
97 // Do all the work.
98 this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
99 this.cacheStartTime_ = startTime;
100 this.cacheStepSize_ = stepSize;
101
102 return this.cacheValues_;
103 }
104
105 /**
106 * Returns the cached |values| in the specified time period.
107 */
108 getValuesInternal_(startTime, stepSize, count) {
Philipp Hancke0635d3142021-03-16 20:02:09109 const values = [];
110 let nextPoint = 0;
Philipp Hancke0635d3142021-03-16 20:02:09111 let time = startTime;
Emil Vardar18237f82025-02-13 11:33:52112 let previousValue = 0;
Philipp Hancke0635d3142021-03-16 20:02:09113 for (let i = 0; i < count; ++i) {
Emil Vardar18237f82025-02-13 11:33:52114 let currentValue = null;
115 let numPoints = 0;
rbpotter5796b002021-03-10 18:49:21116 while (nextPoint < this.dataPoints_.length &&
117 this.dataPoints_[nextPoint].time < time) {
Emil Vardar18237f82025-02-13 11:33:52118 // In case multiple dataPoints exist within the `stepSize`,
119 // get the average in that interval.
120 currentValue += this.dataPoints_[nextPoint].value;
rbpotter5796b002021-03-10 18:49:21121 ++nextPoint;
Emil Vardar18237f82025-02-13 11:33:52122 ++numPoints;
rbpotter5796b002021-03-10 18:49:21123 }
Emil Vardar18237f82025-02-13 11:33:52124 values[i] =
125 (currentValue == null) ? previousValue : (currentValue / numPoints);
126 previousValue = values[i];
rbpotter5796b002021-03-10 18:49:21127 time += stepSize;
128 }
129 return values;
130 }
131}
132
133/**
134 * A single point in a data series. Each point has a time, in the form of
135 * milliseconds since the Unix epoch, and a numeric value.
136 */
137class DataPoint {
138 constructor(time, value) {
[email protected]03bf84a2013-03-23 22:11:48139 this.time = time;
140 this.value = value;
141 }
rbpotter5796b002021-03-10 18:49:21142}