aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLalit Maganti <lalitm@google.com>2024-04-26 00:47:08 +0100
committerLalit Maganti <lalitm@google.com>2024-04-26 00:47:08 +0100
commit5045687984fb3e60bbbb3a2ff7a910066b9ec723 (patch)
tree6309c40933b8e27199121d283a484aadbb4b336a
parent167a19d8463f5ab36e21bb7c6ee840842efc7a8e (diff)
downloadperfetto-5045687984fb3e60bbbb3a2ff7a910066b9ec723.tar.gz
ui: utilise segment forest to improve counter tracks on large traces
This CL introduces the use of the segment forest to significantly speed up the queries of counter tracks on large traces. Specifically the data structure is exposed to Typescript via the use of a new "CounterMipmap" trace processor operator. Also while I'm here, fix a few subtle issues I found in the rendering of counter tracks: 1) Don't draw things deep in negative x: this can cause disappearing tracks when very very zoomed in 2) Improve perf of counter panels query. 3) Fix some subtle overlap between counter units and the hover line: this is pixel peeping but still nice to fix. Specifically, on my monitor, I saw the bottom 3/4 pixels overlapping with the top of the counter line when the counter was at its maximum. Change-Id: I603faaccd080f5e5ff07c37462a039e6850803ea
-rw-r--r--Android.bp1
-rw-r--r--BUILD2
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn3
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc265
-rw-r--r--src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h127
-rw-r--r--src/trace_processor/trace_processor_impl.cc4
-rw-r--r--ui/src/frontend/base_counter_track.ts171
-rw-r--r--ui/src/tracks/counter/index.ts32
8 files changed, 478 insertions, 127 deletions
diff --git a/Android.bp b/Android.bp
index 8a4ba32ec..c66ad4e84 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12286,6 +12286,7 @@ filegroup {
filegroup {
name: "perfetto_src_trace_processor_perfetto_sql_intrinsics_operators_operators",
srcs: [
+ "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc",
"src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc",
"src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc",
],
diff --git a/BUILD b/BUILD
index c9f3381cb..57b3bf60e 100644
--- a/BUILD
+++ b/BUILD
@@ -2313,6 +2313,8 @@ perfetto_cc_tp_tables(
perfetto_filegroup(
name = "src_trace_processor_perfetto_sql_intrinsics_operators_operators",
srcs = [
+ "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc",
+ "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h",
"src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc",
"src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h",
"src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.cc",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn
index bbcb771fb..2dca8b627 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn
@@ -18,6 +18,8 @@ assert(enable_perfetto_trace_processor_sqlite)
source_set("operators") {
sources = [
+ "counter_mipmap_operator.cc",
+ "counter_mipmap_operator.h",
"span_join_operator.cc",
"span_join_operator.h",
"window_operator.cc",
@@ -30,6 +32,7 @@ source_set("operators") {
"../../../../../include/perfetto/trace_processor",
"../../../../../protos/perfetto/trace_processor:zero",
"../../../../base",
+ "../../../containers",
"../../../sqlite",
"../../../util",
"../../engine",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc
new file mode 100644
index 000000000..83321ce05
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h"
+
+#include <sqlite3.h>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sql_source.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+constexpr char kSchema[] = R"(
+ CREATE TABLE x(
+ in_window_start BIGINT HIDDEN,
+ in_window_end BIGINT HIDDEN,
+ in_window_step BIGINT HIDDEN,
+ min_value DOUBLE,
+ max_value DOUBLE,
+ last_ts BIGINT,
+ last_value DOUBLE,
+ PRIMARY KEY(last_ts)
+ ) WITHOUT ROWID
+)";
+
+enum ColumnIndex : size_t {
+ kInWindowStart = 0,
+ kInWindowEnd,
+ kInWindowStep,
+
+ kMinValue,
+ kMaxValue,
+ kLastTs,
+ kLastValue,
+};
+
+constexpr size_t kArgCount = kInWindowStep + 1;
+
+bool IsArgColumn(size_t index) {
+ return index < kArgCount;
+}
+
+using Counter = CounterMipmapOperator::Counter;
+using Agg = CounterMipmapOperator::Agg;
+using Forest = ImplicitSegmentForest<Counter, Agg>;
+
+} // namespace
+
+int CounterMipmapOperator::Create(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char** zErr) {
+ if (argc != 4) {
+ *zErr = sqlite3_mprintf("counter_mipmap: wrong number of arguments");
+ return SQLITE_ERROR;
+ }
+
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+
+ auto* ctx = GetContext(raw_ctx);
+ auto state = std::make_unique<State>();
+
+ std::string sql = "SELECT ts, value FROM ";
+ sql.append(argv[3]);
+ auto res = ctx->engine->ExecuteUntilLastStatement(
+ SqlSource::FromTraceProcessorImplementation(std::move(sql)));
+ if (!res.ok()) {
+ *zErr = sqlite3_mprintf("%s", res.status().c_message());
+ return SQLITE_ERROR;
+ }
+ do {
+ int64_t ts = sqlite3_column_int64(res->stmt.sqlite_stmt(), 0);
+ auto value =
+ static_cast<float>(sqlite3_column_double(res->stmt.sqlite_stmt(), 1));
+ state->timestamps.push_back(ts);
+ state->forest.Push(Counter{value, value});
+ } while (res->stmt.Step());
+ if (!res->stmt.status().ok()) {
+ *zErr = sqlite3_mprintf("%s", res->stmt.status().c_message());
+ return SQLITE_ERROR;
+ }
+
+ std::unique_ptr<Vtab> vtab_res = std::make_unique<Vtab>();
+ vtab_res->state = ctx->manager.OnCreate(argv, std::move(state));
+ *vtab = vtab_res.release();
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Destroy(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<CounterMipmapOperator>::OnDestroy(tab->state);
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Connect(sqlite3* db,
+ void* raw_ctx,
+ int argc,
+ const char* const* argv,
+ sqlite3_vtab** vtab,
+ char**) {
+ PERFETTO_CHECK(argc == 4);
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ auto* ctx = GetContext(raw_ctx);
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ res->state = ctx->manager.OnConnect(argv);
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Disconnect(sqlite3_vtab* vtab) {
+ std::unique_ptr<Vtab> tab(GetVtab(vtab));
+ sqlite::ModuleStateManager<CounterMipmapOperator>::OnDisconnect(tab->state);
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::BestIndex(sqlite3_vtab*, sqlite3_index_info* info) {
+ base::Status status =
+ sqlite::utils::ValidateFunctionArguments(info, kArgCount, IsArgColumn);
+ if (!status.ok()) {
+ return SQLITE_CONSTRAINT;
+ }
+ if (info->nConstraint != kArgCount) {
+ return SQLITE_CONSTRAINT;
+ }
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Open(sqlite3_vtab*, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Close(sqlite3_vtab_cursor* cursor) {
+ std::unique_ptr<Cursor> c(GetCursor(cursor));
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Filter(sqlite3_vtab_cursor* cursor,
+ int,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cursor);
+ auto* t = GetVtab(c->pVtab);
+ auto* state =
+ sqlite::ModuleStateManager<CounterMipmapOperator>::GetState(t->state);
+ PERFETTO_CHECK(argc == kArgCount);
+
+ int64_t start_ts = sqlite3_value_int64(argv[0]);
+ int64_t end_ts = sqlite3_value_int64(argv[1]);
+ int64_t step_ts = sqlite3_value_int64(argv[2]);
+ if (start_ts == end_ts) {
+ return sqlite::utils::SetError(t, "counter_mipmap: empty range provided");
+ }
+
+ c->index = 0;
+ c->counters.clear();
+
+ // If there is a counter value before the start of this window, include it in
+ // the aggregation as well becaue it contributes to what should be rendered
+ // here.
+ auto ts_lb = std::lower_bound(state->timestamps.begin(),
+ state->timestamps.end(), start_ts);
+ if (ts_lb != state->timestamps.begin() &&
+ (ts_lb == state->timestamps.end() || *ts_lb != start_ts)) {
+ --ts_lb;
+ }
+ int64_t start_idx = std::distance(state->timestamps.begin(), ts_lb);
+ for (int64_t s = start_ts; s < end_ts; s += step_ts) {
+ int64_t end_idx =
+ std::distance(state->timestamps.begin(),
+ std::lower_bound(state->timestamps.begin() +
+ static_cast<int64_t>(start_idx),
+ state->timestamps.end(), s + step_ts));
+ if (start_idx == end_idx) {
+ continue;
+ }
+ c->counters.emplace_back(Cursor::Result{
+ state->forest.Query(static_cast<uint32_t>(start_idx),
+ static_cast<uint32_t>(end_idx)),
+ state->forest[static_cast<uint32_t>(end_idx) - 1],
+ state->timestamps[static_cast<uint32_t>(end_idx) - 1],
+ });
+ start_idx = end_idx;
+ }
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Next(sqlite3_vtab_cursor* cursor) {
+ GetCursor(cursor)->index++;
+ return SQLITE_OK;
+}
+
+int CounterMipmapOperator::Eof(sqlite3_vtab_cursor* cursor) {
+ auto* c = GetCursor(cursor);
+ return c->index >= c->counters.size();
+}
+
+int CounterMipmapOperator::Column(sqlite3_vtab_cursor* cursor,
+ sqlite3_context* ctx,
+ int N) {
+ auto* t = GetVtab(cursor->pVtab);
+ auto* c = GetCursor(cursor);
+ const auto& res = c->counters[c->index];
+ switch (N) {
+ case ColumnIndex::kMinValue:
+ sqlite::result::Double(ctx, static_cast<double>(res.min_max_counter.min));
+ return SQLITE_OK;
+ case ColumnIndex::kMaxValue:
+ sqlite::result::Double(ctx, static_cast<double>(res.min_max_counter.max));
+ return SQLITE_OK;
+ case ColumnIndex::kLastTs:
+ sqlite::result::Long(ctx, res.last_ts);
+ return SQLITE_OK;
+ case ColumnIndex::kLastValue:
+ PERFETTO_DCHECK(
+ std::equal_to<>()(res.last_counter.min, res.last_counter.max));
+ sqlite::result::Double(ctx, static_cast<double>(res.last_counter.min));
+ return SQLITE_OK;
+ default:
+ return sqlite::utils::SetError(t, "Bad column");
+ }
+ PERFETTO_FATAL("For GCC");
+}
+
+int CounterMipmapOperator::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h
new file mode 100644
index 000000000..1b0897cc0
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_COUNTER_MIPMAP_OPERATOR_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_COUNTER_MIPMAP_OPERATOR_H_
+
+#include <sqlite3.h>
+#include <cstdint>
+#include <vector>
+
+#include "src/trace_processor/containers/implicit_segment_forest.h"
+#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+
+namespace perfetto::trace_processor {
+
+// Operator for building "mipmaps" [1] over the counter-like tracks.
+//
+// In the context of trace data, mipmap really means aggregating the counter
+// values in a given time period into the {min, max, last} value for that
+// period, allowing UIs to efficiently display the contents of a counter track
+// when very zoomed out.
+//
+// Specifically, we are computing the query:
+// ```
+// select
+// last_value(ts),
+// min(value),
+// max(value),
+// last_value(value)
+// from $input in
+// where in.ts_end >= $window_start and in.ts <= $window_end
+// group by ts / $window_resolution
+// order by ts
+// ```
+// but in O(logn) time by using a segment-tree like data structure (see
+// ImplicitSegmentForest).
+//
+// [1] https://en.wikipedia.org/wiki/Mipmap
+struct CounterMipmapOperator : sqlite::Module<CounterMipmapOperator> {
+ struct Counter {
+ float min;
+ float max;
+ };
+ struct Agg {
+ Counter operator()(const Counter& a, const Counter& b) {
+ Counter res;
+ res.min = b.min < a.min ? b.min : a.min;
+ res.max = b.max > a.max ? b.max : a.max;
+ return res;
+ }
+ };
+ struct State {
+ ImplicitSegmentForest<Counter, Agg> forest;
+ std::vector<int64_t> timestamps;
+ };
+ struct Context {
+ explicit Context(PerfettoSqlEngine* _engine) : engine(_engine) {}
+ PerfettoSqlEngine* engine;
+ sqlite::ModuleStateManager<CounterMipmapOperator> manager;
+ };
+ struct Vtab : sqlite::Module<CounterMipmapOperator>::Vtab {
+ sqlite::ModuleStateManager<CounterMipmapOperator>::PerVtabState* state;
+ };
+ struct Cursor : sqlite::Module<CounterMipmapOperator>::Cursor {
+ struct Result {
+ Counter min_max_counter;
+ Counter last_counter;
+ int64_t last_ts;
+ };
+ std::vector<Result> counters;
+ uint32_t index;
+ };
+
+ static constexpr auto kType = kCreateOnly;
+ static constexpr bool kSupportsWrites = false;
+ static constexpr bool kDoesOverloadFunctions = false;
+
+ static int Create(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Destroy(sqlite3_vtab*);
+
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
+
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_OPERATORS_COUNTER_MIPMAP_OPERATOR_H_
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index b36a63a81..2deba7ac7 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -81,6 +81,7 @@
#include "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/utils.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/window_functions.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h"
#include "src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.h"
#include "src/trace_processor/perfetto_sql/intrinsics/operators/window_operator.h"
#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.h"
@@ -747,6 +748,9 @@ void TraceProcessorImpl::InitPerfettoSqlEngine() {
std::make_unique<SpanJoinOperatorModule::Context>(engine_.get()));
engine_->sqlite_engine()->RegisterVirtualTableModule<WindowOperatorModule>(
"window", std::make_unique<WindowOperatorModule::Context>());
+ engine_->sqlite_engine()->RegisterVirtualTableModule<CounterMipmapOperator>(
+ "__intrinsic_counter_mipmap",
+ std::make_unique<CounterMipmapOperator::Context>(engine_.get()));
// Initalize the tables and views in the prelude.
InitializePreludeTablesViews(db);
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index 656ec56da..6285b277c 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -17,7 +17,7 @@ import m from 'mithril';
import {searchSegment} from '../base/binary_search';
import {Disposable, NullDisposable} from '../base/disposable';
import {assertTrue, assertUnreachable} from '../base/logging';
-import {duration, Time, time} from '../base/time';
+import {Time, time} from '../base/time';
import {drawTrackHoverTooltip} from '../common/canvas_utils';
import {raf} from '../core/raf_scheduler';
import {EngineProxy, LONG, NUM, Track} from '../public';
@@ -27,9 +27,8 @@ import {MenuItem, MenuDivider, PopupMenu2} from '../widgets/menu';
import {checkerboardExcept} from './checkerboard';
import {globals} from './globals';
import {PanelSize} from './panel';
-import {constraintsToQuerySuffix} from './sql_utils';
import {NewTrackArgs} from './track';
-import {CacheKey, TimelineCache} from '../core/timeline_cache';
+import {CacheKey} from '../core/timeline_cache';
import {featureFlags} from '../core/feature_flags';
export const COUNTER_DEBUG_MENU_ITEMS = featureFlags.register({
@@ -128,8 +127,6 @@ class RangeSharer {
interface CounterData {
timestamps: BigInt64Array;
- counts: Uint32Array;
- avgValues: Float64Array;
minDisplayValues: Float64Array;
maxDisplayValues: Float64Array;
lastDisplayValues: Float64Array;
@@ -142,13 +139,10 @@ const MARGIN_TOP = 3.5;
interface CounterLimits {
maxDisplayValue: number;
minDisplayValue: number;
- maxDurNs: duration;
}
interface CounterTooltipState {
lastDisplayValue: number;
- avgValue: number;
- count: number;
ts: time;
tsEnd?: time;
}
@@ -207,16 +201,12 @@ export abstract class BaseCounterTrack implements Track {
private counters: CounterData = {
timestamps: new BigInt64Array(0),
- counts: new Uint32Array(0),
- avgValues: new Float64Array(0),
minDisplayValues: new Float64Array(0),
maxDisplayValues: new Float64Array(0),
lastDisplayValues: new Float64Array(0),
displayValueRange: [0, 0],
};
- private cache: TimelineCache<CounterData> = new TimelineCache(5);
-
// Cleanup hook for onInit.
private initState?: Disposable;
@@ -266,7 +256,7 @@ export abstract class BaseCounterTrack implements Track {
constructor(args: BaseCounterTrackArgs) {
this.engine = args.engine;
- this.trackKey = args.trackKey;
+ this.trackKey = args.trackKey.replaceAll('-', '_');
this.defaultOptions = args.options ?? {};
}
@@ -427,12 +417,9 @@ export abstract class BaseCounterTrack implements Track {
protected invalidate() {
this.limits = undefined;
- this.cache.invalidate();
this.countersKey = CacheKey.zero();
this.counters = {
timestamps: new BigInt64Array(0),
- counts: new Uint32Array(0),
- avgValues: new Float64Array(0),
minDisplayValues: new Float64Array(0),
maxDisplayValues: new Float64Array(0),
lastDisplayValues: new Float64Array(0),
@@ -479,10 +466,7 @@ export abstract class BaseCounterTrack implements Track {
}
render(ctx: CanvasRenderingContext2D, size: PanelSize) {
- const {
- visibleTimeScale: timeScale,
- // visibleWindowTime: vizTime,
- } = globals.timeline;
+ const {visibleTimeScale: timeScale} = globals.timeline;
// In any case, draw whatever we have (which might be stale/incomplete).
@@ -490,11 +474,17 @@ export abstract class BaseCounterTrack implements Track {
const data = this.counters;
if (data.timestamps.length === 0 || limits === undefined) {
+ checkerboardExcept(
+ ctx,
+ this.getHeight(),
+ 0,
+ size.width,
+ timeScale.timeToPx(this.countersKey.start),
+ timeScale.timeToPx(this.countersKey.end),
+ );
return;
}
- assertTrue(data.timestamps.length === data.counts.length);
- assertTrue(data.timestamps.length === data.avgValues.length);
assertTrue(data.timestamps.length === data.minDisplayValues.length);
assertTrue(data.timestamps.length === data.maxDisplayValues.length);
assertTrue(data.timestamps.length === data.lastDisplayValues.length);
@@ -541,11 +531,11 @@ export abstract class BaseCounterTrack implements Track {
ctx.beginPath();
const timestamp = Time.fromRaw(timestamps[0]);
- ctx.moveTo(calculateX(timestamp), zeroY);
+ ctx.moveTo(Math.max(0, calculateX(timestamp)), zeroY);
let lastDrawnY = zeroY;
for (let i = 0; i < timestamps.length; i++) {
const timestamp = Time.fromRaw(timestamps[i]);
- const x = calculateX(timestamp);
+ const x = Math.max(0, calculateX(timestamp));
const minY = calculateY(minValues[i]);
const maxY = calculateY(maxValues[i]);
const lastY = calculateY(lastValues[i]);
@@ -582,7 +572,7 @@ export abstract class BaseCounterTrack implements Track {
const hover = this.hover;
if (hover !== undefined) {
- let text = `${hover.avgValue.toLocaleString()}`;
+ let text = `${hover.lastDisplayValue.toLocaleString()}`;
const unit = this.unit;
switch (options.yMode) {
@@ -600,14 +590,11 @@ export abstract class BaseCounterTrack implements Track {
break;
}
- if (hover.count > 1) {
- text += ` (avg of ${hover.count})`;
- }
-
ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
- const xStart = Math.floor(timeScale.timeToPx(hover.ts));
+ const rawXStart = calculateX(hover.ts);
+ const xStart = Math.max(0, rawXStart);
const xEnd =
hover.tsEnd === undefined
? endPx
@@ -627,17 +614,19 @@ export abstract class BaseCounterTrack implements Track {
ctx.stroke();
ctx.lineWidth = 1;
- // Draw change marker.
- ctx.beginPath();
- ctx.arc(
- xStart,
- y,
- 3 /* r*/,
- 0 /* start angle*/,
- 2 * Math.PI /* end angle*/,
- );
- ctx.fill();
- ctx.stroke();
+ // Draw change marker if it would be visible.
+ if (rawXStart >= -6) {
+ ctx.beginPath();
+ ctx.arc(
+ xStart,
+ y,
+ 3 /* r*/,
+ 0 /* start angle*/,
+ 2 * Math.PI /* end angle*/,
+ );
+ ctx.fill();
+ ctx.stroke();
+ }
// Draw the tooltip.
drawTrackHoverTooltip(ctx, this.mousePos, this.getHeight(), text);
@@ -645,11 +634,11 @@ export abstract class BaseCounterTrack implements Track {
// Write the Y scale on the top left corner.
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
- ctx.fillRect(0, 0, 42, 16);
+ ctx.fillRect(0, 0, 42, 13);
ctx.fillStyle = '#666';
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
- ctx.fillText(`${yLabel}`, 5, 14);
+ ctx.fillText(`${yLabel}`, 5, 11);
// TODO(hjd): Refactor this into checkerboardExcept
{
@@ -691,14 +680,10 @@ export abstract class BaseCounterTrack implements Track {
const tsEnd =
right === -1 ? undefined : Time.fromRaw(data.timestamps[right]);
const lastDisplayValue = data.lastDisplayValues[left];
- const count = data.counts[left];
- const avgValue = data.avgValues[left];
this.hover = {
ts,
tsEnd,
lastDisplayValue,
- count,
- avgValue,
};
}
@@ -785,7 +770,6 @@ export abstract class BaseCounterTrack implements Track {
break;
default:
assertUnreachable(options.yMode);
- break;
}
if (options.yDisplay === 'log') {
@@ -805,7 +789,6 @@ export abstract class BaseCounterTrack implements Track {
const options = this.getCounterOptions();
let valueExpr;
-
switch (options.yMode) {
case 'value':
valueExpr = 'value';
@@ -819,7 +802,6 @@ export abstract class BaseCounterTrack implements Track {
break;
default:
assertUnreachable(options.yMode);
- break;
}
let displayValueExpr = valueExpr;
@@ -831,7 +813,6 @@ export abstract class BaseCounterTrack implements Track {
WITH data AS (
SELECT
ts,
- ${valueExpr} as value,
${displayValueExpr} as displayValue
FROM (${this.getSqlSource()})
)
@@ -841,39 +822,32 @@ export abstract class BaseCounterTrack implements Track {
private async maybeRequestData(rawCountersKey: CacheKey) {
let limits = this.limits;
if (limits === undefined) {
- const maxDurQuery = await this.engine.query(`
- ${this.getSqlPreamble()}
- SELECT
- max(dur) as maxDur
- FROM (
+ const displayValueQuery = await this.engine.query(`
+ drop table if exists counter_${this.trackKey};
+
+ create virtual table counter_${this.trackKey}
+ using __intrinsic_counter_mipmap((
+ ${this.getSqlPreamble()}
SELECT
- lead(ts, 1, ts) over (order by ts) - ts as dur
+ ts,
+ displayValue as value
FROM data
- )
- `);
- const maxDurRow = maxDurQuery.firstRow({
- maxDur: LONG,
- });
- const maxDurNs = maxDurRow.maxDur;
+ ));
- const displayValueQuery = await this.engine.query(`
- ${this.getSqlPreamble()}
- SELECT
- max(displayValue) as maxDisplayValue,
- min(displayValue) as minDisplayValue
- FROM data
+ select
+ min_value as minDisplayValue,
+ max_value as maxDisplayValue
+ from counter_${this.trackKey}(
+ trace_start(), trace_end(), trace_dur()
+ );
`);
- const displayValueRow = displayValueQuery.firstRow({
+ const {minDisplayValue, maxDisplayValue} = displayValueQuery.firstRow({
minDisplayValue: NUM,
maxDisplayValue: NUM,
});
-
- const minDisplayValue = displayValueRow.minDisplayValue;
- const maxDisplayValue = displayValueRow.maxDisplayValue;
limits = this.limits = {
minDisplayValue,
maxDisplayValue,
- maxDurNs,
};
}
@@ -888,42 +862,21 @@ export abstract class BaseCounterTrack implements Track {
);
}
- const maybeCachedCounters = this.cache.lookup(countersKey);
- if (maybeCachedCounters) {
- this.countersKey = countersKey;
- this.counters = maybeCachedCounters;
- return;
- }
-
- const bucketNs = countersKey.bucketSize;
-
- const constraint = constraintsToQuerySuffix({
- filters: [
- `ts >= ${countersKey.start} - ${limits.maxDurNs}`,
- `ts <= ${countersKey.end}`,
- `value is not null`,
- ],
- groupBy: ['tsq'],
- orderBy: ['tsq'],
- });
-
const queryRes = await this.engine.query(`
- ${this.getSqlPreamble()}
SELECT
- (ts + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs} as tsq,
- count(value) as count,
- avg(value) as avgValue,
- min(displayValue) as minDisplayValue,
- max(displayValue) as maxDisplayValue,
- value_at_max_ts(ts, displayValue) as lastDisplayValue
- FROM data
- ${constraint}
+ min_value as minDisplayValue,
+ max_value as maxDisplayValue,
+ last_ts as ts,
+ last_value as lastDisplayValue
+ FROM counter_${this.trackKey}(
+ ${countersKey.start},
+ ${countersKey.end},
+ ${countersKey.bucketSize}
+ );
`);
const it = queryRes.iter({
- tsq: LONG,
- count: NUM,
- avgValue: NUM,
+ ts: LONG,
minDisplayValue: NUM,
maxDisplayValue: NUM,
lastDisplayValue: NUM,
@@ -932,8 +885,6 @@ export abstract class BaseCounterTrack implements Track {
const numRows = queryRes.numRows();
const data: CounterData = {
timestamps: new BigInt64Array(numRows),
- counts: new Uint32Array(numRows),
- avgValues: new Float64Array(numRows),
minDisplayValues: new Float64Array(numRows),
maxDisplayValues: new Float64Array(numRows),
lastDisplayValues: new Float64Array(numRows),
@@ -943,10 +894,7 @@ export abstract class BaseCounterTrack implements Track {
let min = 0;
let max = 0;
for (let row = 0; it.valid(); it.next(), row++) {
- const ts = Time.fromRaw(it.tsq);
- data.timestamps[row] = ts;
- data.counts[row] = it.count;
- data.avgValues[row] = it.avgValue;
+ data.timestamps[row] = Time.fromRaw(it.ts);
data.minDisplayValues[row] = it.minDisplayValue;
data.maxDisplayValues[row] = it.maxDisplayValue;
data.lastDisplayValues[row] = it.lastDisplayValue;
@@ -956,7 +904,6 @@ export abstract class BaseCounterTrack implements Track {
data.displayValueRange = [min, max];
- this.cache.insert(countersKey, data);
this.countersKey = countersKey;
this.counters = data;
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 5fdf93821..55773cd87 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -136,22 +136,24 @@ export class TraceProcessorCounterTrack extends BaseCounterTrack {
const time = visibleTimeScale.pxToHpTime(x).toTime('floor');
const query = `
- WITH X AS (
- SELECT
- id,
- ts AS leftTs,
- LEAD(ts) OVER (ORDER BY ts) AS rightTs
- FROM counter
- WHERE track_id = ${this.trackId}
- ORDER BY ts
- )
- SELECT
+ select
id,
- leftTs,
- rightTs
- FROM X
- WHERE rightTs > ${time}
- LIMIT 1
+ ts as leftTs,
+ (
+ select ts
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts >= ${time}
+ order by ts
+ limit 1
+ ) as rightTs
+ from ${this.rootTable}
+ where
+ track_id = ${this.trackId}
+ and ts < ${time}
+ order by ts DESC
+ limit 1
`;
this.engine.query(query).then((result) => {