diff options
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | BUILD | 2 | ||||
-rw-r--r-- | src/trace_processor/perfetto_sql/intrinsics/operators/BUILD.gn | 3 | ||||
-rw-r--r-- | src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.cc | 265 | ||||
-rw-r--r-- | src/trace_processor/perfetto_sql/intrinsics/operators/counter_mipmap_operator.h | 127 | ||||
-rw-r--r-- | src/trace_processor/trace_processor_impl.cc | 4 | ||||
-rw-r--r-- | ui/src/frontend/base_counter_track.ts | 171 | ||||
-rw-r--r-- | ui/src/tracks/counter/index.ts | 32 |
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", ], @@ -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) => { |