Merge "ui: Add delta mode to counters"
diff --git a/src/trace_processor/dynamic/experimental_counter_dur_generator.cc b/src/trace_processor/dynamic/experimental_counter_dur_generator.cc
index 13417bf..88d167f 100644
--- a/src/trace_processor/dynamic/experimental_counter_dur_generator.cc
+++ b/src/trace_processor/dynamic/experimental_counter_dur_generator.cc
@@ -29,6 +29,9 @@
schema.columns.emplace_back(
Table::Schema::Column{"dur", SqlValue::Type::kLong, false /* is_id */,
false /* is_sorted */, false /* is_hidden */});
+ schema.columns.emplace_back(
+ Table::Schema::Column{"delta", SqlValue::Type::kLong, false /* is_id */,
+ false /* is_sorted */, false /* is_hidden */});
return schema;
}
@@ -51,9 +54,17 @@
if (!dur_column_) {
dur_column_.reset(
new NullableVector<int64_t>(ComputeDurColumn(*counter_table_)));
+ delta_column_.reset(
+ new NullableVector<double>(ComputeDeltaColumn(*counter_table_)));
}
- return std::unique_ptr<Table>(new Table(counter_table_->ExtendWithColumn(
- "dur", dur_column_.get(), TypedColumn<int64_t>::default_flags())));
+
+ Table t = counter_table_
+ ->ExtendWithColumn("dur", std::move(dur_column_.get()),
+ TypedColumn<int64_t>::default_flags())
+ .ExtendWithColumn("delta", std::move(delta_column_.get()),
+ TypedColumn<int64_t>::default_flags());
+
+ return std::unique_ptr<Table>(new Table(t.Copy()));
}
// static
@@ -91,5 +102,38 @@
return dur;
}
+// static
+NullableVector<double> ExperimentalCounterDurGenerator::ComputeDeltaColumn(
+ const Table& table) {
+ // Keep track of the last seen row for each track id.
+ std::unordered_map<TrackId, uint32_t> last_row_for_track_id;
+ NullableVector<double> delta;
+
+ const auto* value_col =
+ TypedColumn<double>::FromColumn(table.GetColumnByName("value"));
+ const auto* track_id_col =
+ TypedColumn<tables::CounterTrackTable::Id>::FromColumn(
+ table.GetColumnByName("track_id"));
+
+ for (uint32_t i = 0; i < table.row_count(); ++i) {
+ // Check if we already have a previous row for the current track id.
+ TrackId track_id = (*track_id_col)[i];
+ auto it = last_row_for_track_id.find(track_id);
+ if (it == last_row_for_track_id.end()) {
+ // This means we don't have any row - start tracking this row for the
+ // future.
+ last_row_for_track_id.emplace(track_id, i);
+ } else {
+ // This means we have an previous row for the current track id. Update
+ // the duration of the previous row to be up to the current ts.
+ uint32_t old_row = it->second;
+ it->second = i;
+ delta.Set(old_row, (*value_col)[i] - (*value_col)[old_row]);
+ }
+ delta.Append(0);
+ }
+ return delta;
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/dynamic/experimental_counter_dur_generator.h b/src/trace_processor/dynamic/experimental_counter_dur_generator.h
index 9265e47..38f9773 100644
--- a/src/trace_processor/dynamic/experimental_counter_dur_generator.h
+++ b/src/trace_processor/dynamic/experimental_counter_dur_generator.h
@@ -39,10 +39,12 @@
// public + static for testing
static NullableVector<int64_t> ComputeDurColumn(const Table& table);
+ static NullableVector<double> ComputeDeltaColumn(const Table& table);
private:
const tables::CounterTable* counter_table_ = nullptr;
std::unique_ptr<NullableVector<int64_t>> dur_column_;
+ std::unique_ptr<NullableVector<double>> delta_column_;
};
} // namespace trace_processor
diff --git a/ui/src/tracks/counter/common.ts b/ui/src/tracks/counter/common.ts
index 8e1380a..3751161 100644
--- a/ui/src/tracks/counter/common.ts
+++ b/ui/src/tracks/counter/common.ts
@@ -16,14 +16,19 @@
export const COUNTER_TRACK_KIND = 'CounterTrack';
+export type CounterScaleOptions = 'DEFAULT'|'RELATIVE'|'DELTA';
+
export interface Data extends TrackData {
maximumValue: number;
minimumValue: number;
+ maximumDelta: number;
+ minimumDelta: number;
timestamps: Float64Array;
lastIds: Float64Array;
minValues: Float64Array;
maxValues: Float64Array;
lastValues: Float64Array;
+ totalDeltas: Float64Array;
}
export interface Config {
@@ -34,5 +39,5 @@
endTs?: number;
namespace: string;
trackId: number;
- scale?: 'DEFAULT'|'RELATIVE';
+ scale?: CounterScaleOptions;
}
diff --git a/ui/src/tracks/counter/controller.ts b/ui/src/tracks/counter/controller.ts
index fa678cc..22dffe2 100644
--- a/ui/src/tracks/counter/controller.ts
+++ b/ui/src/tracks/counter/controller.ts
@@ -30,6 +30,8 @@
private setup = false;
private maximumValueSeen = 0;
private minimumValueSeen = 0;
+ private maximumDeltaSeen = 0;
+ private minimumDeltaSeen = 0;
private maxDurNs = 0;
async onBoundsChange(start: number, end: number, resolution: number):
@@ -51,7 +53,8 @@
id,
ts,
dur,
- value
+ value,
+ delta
from experimental_counter_dur
where track_id = ${this.config.trackId};
`);
@@ -62,6 +65,7 @@
id,
ts,
lead(ts, 1, ts) over (order by ts) - ts as dur,
+ lead(value, 1, value) over (order by ts) - value as delta,
value
from ${this.namespaceTable('counter')}
where track_id = ${this.config.trackId};
@@ -80,10 +84,16 @@
}
const result = await this.query(`
- select max(value), min(value)
+ select
+ max(value) as maxValue,
+ min(value) as minValue,
+ max(delta) as maxDelta,
+ min(delta) as minDelta
from ${this.tableName('counter_view')}`);
this.maximumValueSeen = +result.columns[0].doubleValues![0];
this.minimumValueSeen = +result.columns[1].doubleValues![0];
+ this.maximumDeltaSeen = +result.columns[2].doubleValues![0];
+ this.minimumDeltaSeen = +result.columns[3].doubleValues![0];
this.setup = true;
}
@@ -93,6 +103,7 @@
(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
min(value) as minValue,
max(value) as maxValue,
+ sum(delta) as totalDelta,
value_at_max_ts(ts, id) as lastId,
value_at_max_ts(ts, value) as lastValue
from ${this.tableName('counter_view')}
@@ -109,12 +120,15 @@
length: numRows,
maximumValue: this.maximumValue(),
minimumValue: this.minimumValue(),
+ maximumDelta: this.maximumDeltaSeen,
+ minimumDelta: this.minimumDeltaSeen,
resolution,
timestamps: new Float64Array(numRows),
lastIds: new Float64Array(numRows),
minValues: new Float64Array(numRows),
maxValues: new Float64Array(numRows),
lastValues: new Float64Array(numRows),
+ totalDeltas: new Float64Array(numRows),
};
const it = iter(
@@ -123,7 +137,8 @@
'lastId': NUM,
'minValue': NUM,
'maxValue': NUM,
- 'lastValue': NUM
+ 'lastValue': NUM,
+ 'totalDelta': NUM,
},
rawResult);
for (let i = 0; it.valid(); ++i, it.next()) {
@@ -132,6 +147,7 @@
data.minValues[i] = it.row.minValue;
data.maxValues[i] = it.row.maxValue;
data.lastValues[i] = it.row.lastValue;
+ data.totalDeltas[i] = it.row.totalDelta;
}
return data;
diff --git a/ui/src/tracks/counter/frontend.ts b/ui/src/tracks/counter/frontend.ts
index 7fd9203..693d449 100644
--- a/ui/src/tracks/counter/frontend.ts
+++ b/ui/src/tracks/counter/frontend.ts
@@ -28,6 +28,7 @@
import {
Config,
COUNTER_TRACK_KIND,
+ CounterScaleOptions,
Data,
} from './common';
@@ -35,6 +36,41 @@
const MARGIN_TOP = 3.5;
const RECT_HEIGHT = 24.5;
+function scaleTooltip(scale?: CounterScaleOptions): string {
+ switch (scale) {
+ case 'RELATIVE':
+ return 'Use zero-based scale';
+ case 'DELTA':
+ return 'Use deta scale';
+ case 'DEFAULT':
+ default:
+ return 'Use zero-based scale';
+ }
+}
+
+function scaleIcon(scale?: CounterScaleOptions): string {
+ switch (scale) {
+ case 'DELTA':
+ return 'bar_chart';
+ case 'RELATIVE':
+ case 'DEFAULT':
+ default:
+ return 'show_chart';
+ }
+}
+
+function nextScale(scale?: CounterScaleOptions): CounterScaleOptions {
+ switch (scale) {
+ case 'RELATIVE':
+ return 'DELTA';
+ case 'DELTA':
+ return 'DEFAULT';
+ case 'DEFAULT':
+ default:
+ return 'RELATIVE';
+ }
+}
+
class CounterTrack extends Track<Config, Data> {
static readonly kind = COUNTER_TRACK_KIND;
static create(trackState: TrackState): CounterTrack {
@@ -58,19 +94,14 @@
const buttons: Array<m.Vnode<TrackButtonAttrs>> = [];
buttons.push(m(TrackButton, {
action: () => {
- if (this.config.scale === 'RELATIVE') {
- this.config.scale = 'DEFAULT';
- } else {
- this.config.scale = 'RELATIVE';
- }
+ this.config.scale = nextScale(this.config.scale);
Actions.updateTrackConfig(
{id: this.trackState.id, config: this.config});
globals.rafScheduler.scheduleFullRedraw();
},
- i: 'show_chart',
- tooltip: (this.config.scale === 'RELATIVE') ? 'Use zero-based scale' :
- 'Use relative scale',
- showButton: this.config.scale === 'RELATIVE',
+ i: scaleIcon(this.config.scale),
+ tooltip: scaleTooltip(this.config.scale),
+ showButton: !!this.config.scale && this.config.scale !== 'DEFAULT',
}));
return buttons;
}
@@ -88,14 +119,30 @@
assertTrue(data.timestamps.length === data.minValues.length);
assertTrue(data.timestamps.length === data.maxValues.length);
assertTrue(data.timestamps.length === data.lastValues.length);
+ assertTrue(data.timestamps.length === data.totalDeltas.length);
+
+ const scale: CounterScaleOptions = this.config.scale || 'DEFAULT';
+
+ let minValues = data.minValues;
+ let maxValues = data.maxValues;
+ let lastValues = data.lastValues;
+ let maximumValue = data.maximumValue;
+ let minimumValue = data.minimumValue;
+ if (scale === 'DELTA') {
+ lastValues = data.totalDeltas;
+ minValues = data.totalDeltas;
+ maxValues = data.totalDeltas;
+ maximumValue = data.maximumDelta;
+ minimumValue = data.minimumDelta;
+ }
const endPx = Math.floor(timeScale.timeToPx(visibleWindowTime.end));
- const zeroY = MARGIN_TOP + RECT_HEIGHT / (data.minimumValue < 0 ? 2 : 1);
+ const zeroY = MARGIN_TOP + RECT_HEIGHT / (minimumValue < 0 ? 2 : 1);
// Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
- const maxValue = Math.max(data.maximumValue, 0);
+ const maxValue = Math.max(maximumValue, 0);
- let yMax = Math.max(Math.abs(data.minimumValue), maxValue);
+ let yMax = Math.max(Math.abs(minimumValue), maxValue);
const kUnits = ['', 'K', 'M', 'G', 'T', 'E'];
const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
const pow10 = Math.pow(10, exp);
@@ -104,14 +151,17 @@
const unitGroup = Math.floor(exp / 3);
let yMin = 0;
let yLabel = '';
- if (this.config.scale === 'RELATIVE') {
- yRange = data.maximumValue - data.minimumValue;
- yMin = data.minimumValue;
+ if (scale === 'RELATIVE') {
+ yRange = maximumValue - minimumValue;
+ yMin = minimumValue;
yLabel = 'min - max';
} else {
- yRange = data.minimumValue < 0 ? yMax * 2 : yMax;
- yMin = data.minimumValue < 0 ? -yMax : 0;
+ yRange = minimumValue < 0 ? yMax * 2 : yMax;
+ yMin = minimumValue < 0 ? -yMax : 0;
yLabel = `${yMax / Math.pow(10, unitGroup * 3)} ${kUnits[unitGroup]}`;
+ if (scale === 'DELTA') {
+ yLabel += '\u0394';
+ }
}
// There are 360deg of hue. We want a scale that starts at green with
@@ -132,7 +182,8 @@
return Math.floor(timeScale.timeToPx(ts));
};
const calculateY = (value: number) => {
- return zeroY - Math.round(((value - yMin) / yRange) * RECT_HEIGHT);
+ return MARGIN_TOP + RECT_HEIGHT -
+ Math.round(((value - yMin) / yRange) * RECT_HEIGHT);
};
ctx.beginPath();
@@ -140,9 +191,9 @@
let lastDrawnY = zeroY;
for (let i = 0; i < data.timestamps.length; i++) {
const x = calculateX(data.timestamps[i]);
- const minY = calculateY(data.minValues[i]);
- const maxY = calculateY(data.maxValues[i]);
- const lastY = calculateY(data.lastValues[i]);
+ const minY = calculateY(minValues[i]);
+ const maxY = calculateY(maxValues[i]);
+ const lastY = calculateY(lastValues[i]);
ctx.lineTo(x, lastDrawnY);
if (minY === maxY) {
@@ -175,7 +226,7 @@
if (this.hoveredValue !== undefined && this.hoveredTs !== undefined) {
// TODO(hjd): Add units.
- let text = 'value: ';
+ let text = scale === 'DELTA' ? 'delta: ' : 'value: ';
text += `${this.hoveredValue.toLocaleString()}`;
ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
@@ -185,7 +236,7 @@
const xEnd = this.hoveredTsEnd === undefined ?
endPx :
Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
- const y = zeroY -
+ const y = MARGIN_TOP + RECT_HEIGHT -
Math.round(((this.hoveredValue - yMin) / yRange) * RECT_HEIGHT);
// Highlight line.
@@ -245,10 +296,12 @@
const {timeScale} = globals.frontendLocalState;
const time = timeScale.pxToTime(x);
+ const values =
+ this.config.scale === 'DELTA' ? data.totalDeltas : data.lastValues;
const [left, right] = searchSegment(data.timestamps, time);
this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right];
- this.hoveredValue = left === -1 ? undefined : data.lastValues[left];
+ this.hoveredValue = left === -1 ? undefined : values[left];
}
onMouseOut() {