diff options
author | Steve Golton <stevegolton@google.com> | 2024-05-08 17:17:52 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-05-08 17:17:52 +0000 |
commit | fa413e8c9b44535bd3a97d65069628e22eb21437 (patch) | |
tree | 889f872cff80e8e38f53ae1364a588b35ad02c8d | |
parent | eca31d752567b4c1655edd87d6577cbfb7967ebf (diff) | |
parent | 53b648b0d2e149375a779124b2bcf6dc9e310028 (diff) | |
download | perfetto-fa413e8c9b44535bd3a97d65069628e22eb21437.tar.gz |
Merge "ui: Remove the last of the "V1" slice tracks" into main
17 files changed, 234 insertions, 738 deletions
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 index 81c36d88e..ea62427a6 100644 --- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 +++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 @@ -1 +1 @@ -ff45b08442a6f59a4768f3ad697cffb43d19889f1f6523d5ed4ef83aff944864
\ No newline at end of file +8b878dd1d284fda4558de3f184d078293546c09a1438e14183161eb6040cee26
\ No newline at end of file diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 index 4d7b25a06..e6a988a37 100644 --- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 +++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 @@ -1 +1 @@ -161c05f6bae9b0e2142e73e83f3bcf7a9c689c58411db9e345a63ba882605557
\ No newline at end of file +d3e7cdfbb83dd4e1b674c5d7b90554fd915dab8747f3981a2b6dbb1fff955f61
\ No newline at end of file diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 index 2d4876158..1be3a540d 100644 --- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 +++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 @@ -1 +1 @@ -c0bf63de1c6c738fb994d64e5f2f7c5c0e4315f8627fc5fd68f5b906acbb814d
\ No newline at end of file +e1d34e03cd7540d476eb93e159a785ea18a80083b3410898a967a56f9d7af5f8
\ No newline at end of file diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts index b31d2ae9c..078440292 100644 --- a/ui/src/common/actions_unittest.ts +++ b/ui/src/common/actions_unittest.ts @@ -17,7 +17,6 @@ import {produce} from 'immer'; import {assertExists} from '../base/logging'; import {Time} from '../base/time'; import {PrimaryTrackSortKey} from '../public'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile'; import {PROCESS_SCHEDULING_TRACK_KIND} from '../tracks/process_summary/process_scheduling_track'; import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state'; @@ -32,6 +31,7 @@ import { TraceUrlSource, TrackSortKey, } from './state'; +import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/chrome_slice_track'; function fakeTrack( state: State, diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts index 62085946c..3e18dd040 100644 --- a/ui/src/controller/aggregation/slice_aggregation_controller.ts +++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts @@ -17,7 +17,7 @@ import {Area, Sorting} from '../../common/state'; import {globals} from '../../frontend/globals'; import {Engine} from '../../trace_processor/engine'; import {ASYNC_SLICE_TRACK_KIND} from '../../tracks/async_slices'; -import {SLICE_TRACK_KIND} from '../../tracks/chrome_slices'; +import {SLICE_TRACK_KIND} from '../../tracks/chrome_slices/chrome_slice_track'; import {AggregationController} from './aggregation_controller'; diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts index d53142c81..183786287 100644 --- a/ui/src/controller/flow_events_controller.ts +++ b/ui/src/controller/flow_events_controller.ts @@ -20,7 +20,7 @@ import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish'; import {asSliceSqlId} from '../frontend/sql_types'; import {Engine} from '../trace_processor/engine'; import {LONG, NUM, STR_NULL} from '../trace_processor/query_result'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; +import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/chrome_slice_track'; import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/frames'; import {Controller} from './controller'; diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts index d30b458b3..ce9b15f18 100644 --- a/ui/src/controller/selection_controller.ts +++ b/ui/src/controller/selection_controller.ts @@ -37,7 +37,7 @@ import { STR_NULL, timeFromSql, } from '../trace_processor/query_result'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; +import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/chrome_slice_track'; import {Controller} from './controller'; diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts index 16c6453ac..58f77c003 100644 --- a/ui/src/controller/track_decider.ts +++ b/ui/src/controller/track_decider.ts @@ -35,7 +35,6 @@ import { getScrollJankTracks, } from '../tracks/chrome_scroll_jank'; import {decideTracks as scrollJankDecideTracks} from '../tracks/chrome_scroll_jank/chrome_tasks_scroll_jank_track'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; import {COUNTER_TRACK_KIND} from '../tracks/counter'; import { ACTUAL_FRAMES_SLICE_TRACK_KIND, @@ -43,6 +42,7 @@ import { } from '../tracks/frames'; import {decideTracks as screenshotDecideTracks} from '../tracks/screenshots'; import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state'; +import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/chrome_slice_track'; const MEM_DMA_COUNTER_NAME = 'mem.dma_heap'; const MEM_DMA = 'mem.dma_buffer'; diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts index f42d4b60a..49196fa21 100644 --- a/ui/src/frontend/chrome_slice_details_tab.ts +++ b/ui/src/frontend/chrome_slice_details_tab.ts @@ -222,7 +222,7 @@ async function getSliceDetails( id: number, table: string, ): Promise<SliceDetails | undefined> { - if (table === 'annotation') { + if (table === 'annotation_slice') { return getAnnotationSlice(engine, id); } else { return getSlice(engine, asSliceSqlId(id)); diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts index 5eff6af74..9d0eb75f7 100644 --- a/ui/src/frontend/named_slice_track.ts +++ b/ui/src/frontend/named_slice_track.ts @@ -81,9 +81,6 @@ export abstract class NamedSliceTrack< kind: 'CHROME_SLICE', id: args.slice.id, trackKey: this.trackKey, - // |table| here can be either 'slice' or 'annotation'. The - // AnnotationSliceTrack overrides the onSliceClick and sets this to - // 'annotation' table: 'slice', }, { diff --git a/ui/src/frontend/slice_track.ts b/ui/src/frontend/slice_track.ts deleted file mode 100644 index e12cd7377..000000000 --- a/ui/src/frontend/slice_track.ts +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (C) 2023 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. - -import {duration, Time, time} from '../base/time'; -import {Actions} from '../common/actions'; -import {cropText, drawIncompleteSlice} from '../common/canvas_utils'; -import {getColorForSlice} from '../core/colorizer'; -import {HighPrecisionTime} from '../common/high_precision_time'; -import {TrackData} from '../common/track_data'; -import {TimelineFetcher} from '../common/track_helper'; -import {SliceRect, Track} from '../public'; -import {getLegacySelection} from '../common/state'; - -import {CROP_INCOMPLETE_SLICE_FLAG} from './base_slice_track'; -import {checkerboardExcept} from './checkerboard'; -import {globals} from './globals'; -import {PanelSize} from './panel'; - -export const SLICE_TRACK_KIND = 'ChromeSliceTrack'; -const SLICE_HEIGHT = 18; -const TRACK_PADDING = 2; -const CHEVRON_WIDTH_PX = 10; -const HALF_CHEVRON_WIDTH_PX = CHEVRON_WIDTH_PX / 2; -const INCOMPLETE_SLICE_WIDTH_PX = 20; - -export interface SliceData extends TrackData { - // Slices are stored in a columnar fashion. - strings: string[]; - sliceIds: Float64Array; - starts: BigInt64Array; - ends: BigInt64Array; - depths: Uint16Array; - titles: Uint16Array; // Index into strings. - colors?: Uint16Array; // Index into strings. - isInstant: Uint16Array; - isIncomplete: Uint16Array; - cpuTimeRatio?: Float64Array; -} - -// Track base class which handles rendering slices in a generic way. -// This is the old way of rendering slices - i.e. "track v1" format - and -// exists as a patch to allow old tracks to be converted to controller-less -// tracks before they are ported to v2. -// Slice tracks should extend this class and implement the abstract methods, -// notably onBoundsChange(). -// Note: This class is deprecated and should not be used for new tracks. Use -// |BaseSliceTrack| instead. -export abstract class SliceTrackLEGACY implements Track { - private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this)); - - constructor( - private maxDepth: number, - protected trackKey: string, - private tableName: string, - private namespace?: string, - ) {} - - async onUpdate(): Promise<void> { - await this.fetcher.requestDataForCurrentTime(); - } - - async onDestroy(): Promise<void> { - this.fetcher.dispose(); - } - - abstract onBoundsChange( - start: time, - end: time, - resolution: duration, - ): Promise<SliceData>; - - protected namespaceTable(tableName: string = this.tableName): string { - if (this.namespace) { - return this.namespace + '_' + tableName; - } else { - return tableName; - } - } - - private hoveredTitleId = -1; - - // Font used to render the slice name on the current track. - protected getFont() { - return '12px Roboto Condensed'; - } - - render(ctx: CanvasRenderingContext2D, size: PanelSize): void { - // TODO: fonts and colors should come from the CSS and not hardcoded here. - const data = this.fetcher.data; - if (data === undefined) return; // Can't possibly draw anything. - - const {visibleTimeSpan, visibleTimeScale} = globals.timeline; - - // If the cached trace slices don't fully cover the visible time range, - // show a gray rectangle with a "Loading..." label. - checkerboardExcept( - ctx, - this.getHeight(), - 0, - size.width, - visibleTimeScale.timeToPx(data.start), - visibleTimeScale.timeToPx(data.end), - ); - - ctx.textAlign = 'center'; - - // measuretext is expensive so we only use it once. - const charWidth = ctx.measureText('ACBDLqsdfg').width / 10; - - // The draw of the rect on the selected slice must happen after the other - // drawings, otherwise it would result under another rect. - let drawRectOnSelected = () => {}; - - for (let i = 0; i < data.starts.length; i++) { - const tStart = Time.fromRaw(data.starts[i]); - let tEnd = Time.fromRaw(data.ends[i]); - const depth = data.depths[i]; - const titleId = data.titles[i]; - const sliceId = data.sliceIds[i]; - const isInstant = data.isInstant[i]; - const isIncomplete = data.isIncomplete[i]; - const title = data.strings[titleId]; - const colorOverride = data.colors && data.strings[data.colors[i]]; - if (isIncomplete) { - // incomplete slice - // TODO(stevegolton): This isn't exactly equivalent, ideally we should - // choose tEnd once we've converted to screen space coords. - tEnd = this.getEndTimeIfInComplete(tStart); - } - - if (!visibleTimeSpan.intersects(tStart, tEnd)) { - continue; - } - - const pxEnd = size.width; - const left = Math.max(visibleTimeScale.timeToPx(tStart), 0); - const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd); - - const rect = { - left, - width: Math.max(right - left, 1), - top: TRACK_PADDING + depth * SLICE_HEIGHT, - height: SLICE_HEIGHT, - }; - - const currentSelection = getLegacySelection(globals.state); - const isSelected = - currentSelection && - currentSelection.kind === 'CHROME_SLICE' && - currentSelection.id !== undefined && - currentSelection.id === sliceId; - - const highlighted = - titleId === this.hoveredTitleId || - globals.state.highlightedSliceId === sliceId; - - const hasFocus = highlighted || isSelected; - const colorScheme = getColorForSlice(title); - const colorObj = hasFocus ? colorScheme.variant : colorScheme.base; - const textColor = hasFocus - ? colorScheme.textVariant - : colorScheme.textBase; - - let color: string; - if (colorOverride === undefined) { - color = colorObj.cssString; - } else { - color = colorOverride; - } - ctx.fillStyle = color; - - // We draw instant events as upward facing chevrons starting at A: - // A - // ### - // ##C## - // ## ## - // D B - // Then B, C, D and back to A: - if (isInstant) { - if (isSelected) { - drawRectOnSelected = () => { - ctx.save(); - ctx.translate(rect.left, rect.top); - - // Draw a rectangle around the selected slice - ctx.strokeStyle = colorObj.setHSL({s: 100, l: 10}).cssString; - ctx.beginPath(); - ctx.lineWidth = 3; - ctx.strokeRect( - -HALF_CHEVRON_WIDTH_PX, - 0, - CHEVRON_WIDTH_PX, - SLICE_HEIGHT, - ); - ctx.closePath(); - - // Draw inner chevron as interior - ctx.fillStyle = color; - this.drawChevron(ctx); - - ctx.restore(); - }; - } else { - ctx.save(); - ctx.translate(rect.left, rect.top); - this.drawChevron(ctx); - ctx.restore(); - } - continue; - } - - if (isIncomplete && rect.width > SLICE_HEIGHT / 4) { - drawIncompleteSlice( - ctx, - rect.left, - rect.top, - rect.width, - SLICE_HEIGHT, - !CROP_INCOMPLETE_SLICE_FLAG.get(), - ); - } else if ( - data.cpuTimeRatio !== undefined && - data.cpuTimeRatio[i] < 1 - 1e-9 - ) { - // We draw two rectangles, representing the ratio between wall time and - // time spent on cpu. - const cpuTimeRatio = data.cpuTimeRatio![i]; - const firstPartWidth = rect.width * cpuTimeRatio; - const secondPartWidth = rect.width * (1 - cpuTimeRatio); - ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT); - ctx.fillStyle = '#FFFFFF50'; - ctx.fillRect( - rect.left + firstPartWidth, - rect.top, - secondPartWidth, - SLICE_HEIGHT, - ); - } else { - ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT); - } - - // Selected case - if (isSelected) { - drawRectOnSelected = () => { - ctx.strokeStyle = colorObj.setHSL({s: 100, l: 10}).cssString; - ctx.beginPath(); - ctx.lineWidth = 3; - ctx.strokeRect( - rect.left, - rect.top - 1.5, - rect.width, - SLICE_HEIGHT + 3, - ); - ctx.closePath(); - }; - } - - // Don't render text when we have less than 5px to play with. - if (rect.width >= 5) { - ctx.fillStyle = textColor.cssString; - const displayText = cropText(title, charWidth, rect.width); - const rectXCenter = rect.left + rect.width / 2; - ctx.textBaseline = 'middle'; - ctx.font = this.getFont(); - ctx.fillText(displayText, rectXCenter, rect.top + SLICE_HEIGHT / 2); - } - } - drawRectOnSelected(); - } - - drawChevron(ctx: CanvasRenderingContext2D) { - // Draw a chevron at a fixed location and size. Should be used with - // ctx.translate and ctx.scale to alter location and size. - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo(HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT); - ctx.lineTo(0, SLICE_HEIGHT - HALF_CHEVRON_WIDTH_PX); - ctx.lineTo(-HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT); - ctx.lineTo(0, 0); - ctx.fill(); - } - - getSliceIndex({x, y}: {x: number; y: number}): number | void { - const data = this.fetcher.data; - if (data === undefined) return; - const {visibleTimeScale: timeScale} = globals.timeline; - if (y < TRACK_PADDING) return; - const instantWidthTime = timeScale.pxDeltaToDuration(HALF_CHEVRON_WIDTH_PX); - const t = timeScale.pxToHpTime(x); - const depth = Math.floor((y - TRACK_PADDING) / SLICE_HEIGHT); - - for (let i = 0; i < data.starts.length; i++) { - if (depth !== data.depths[i]) { - continue; - } - const start = Time.fromRaw(data.starts[i]); - const tStart = HighPrecisionTime.fromTime(start); - if (data.isInstant[i]) { - if (tStart.sub(t).abs().lt(instantWidthTime)) { - return i; - } - } else { - const end = Time.fromRaw(data.ends[i]); - let tEnd = HighPrecisionTime.fromTime(end); - if (data.isIncomplete[i]) { - const endTime = this.getEndTimeIfInComplete(start); - tEnd = HighPrecisionTime.fromTime(endTime); - } - if (tStart.lte(t) && t.lte(tEnd)) { - return i; - } - } - } - } - - getEndTimeIfInComplete(start: time): time { - const {visibleTimeScale, visibleWindowTime} = globals.timeline; - - let end = visibleWindowTime.end.toTime('ceil'); - if (CROP_INCOMPLETE_SLICE_FLAG.get()) { - const widthTime = visibleTimeScale - .pxDeltaToDuration(INCOMPLETE_SLICE_WIDTH_PX) - .toTime(); - end = Time.add(start, widthTime); - } - - return end; - } - - onMouseMove({x, y}: {x: number; y: number}) { - this.hoveredTitleId = -1; - globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})); - const sliceIndex = this.getSliceIndex({x, y}); - if (sliceIndex === undefined) return; - const data = this.fetcher.data; - if (data === undefined) return; - this.hoveredTitleId = data.titles[sliceIndex]; - const sliceId = data.sliceIds[sliceIndex]; - globals.dispatch(Actions.setHighlightedSliceId({sliceId})); - } - - onMouseOut() { - this.hoveredTitleId = -1; - globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})); - } - - onMouseClick({x, y}: {x: number; y: number}): boolean { - const sliceIndex = this.getSliceIndex({x, y}); - if (sliceIndex === undefined) return false; - const data = this.fetcher.data; - if (data === undefined) return false; - const sliceId = data.sliceIds[sliceIndex]; - if (sliceId !== undefined && sliceId !== -1) { - globals.setLegacySelection( - { - kind: 'CHROME_SLICE', - id: sliceId, - trackKey: this.trackKey, - table: this.namespace, - }, - { - clearSearch: true, - pendingScrollId: undefined, - switchToCurrentSelectionTab: true, - }, - ); - return true; - } - return false; - } - - getHeight() { - return SLICE_HEIGHT * (this.maxDepth + 1) + 2 * TRACK_PADDING; - } - - getSliceRect(tStart: time, tEnd: time, depth: number): SliceRect | undefined { - const {windowSpan, visibleTimeScale, visibleTimeSpan} = globals.timeline; - - const pxEnd = windowSpan.end; - const left = Math.max(visibleTimeScale.timeToPx(tStart), 0); - const right = Math.min(visibleTimeScale.timeToPx(tEnd), pxEnd); - - const visible = visibleTimeSpan.intersects(tStart, tEnd); - - return { - left, - width: Math.max(right - left, 1), - top: TRACK_PADDING + depth * SLICE_HEIGHT, - height: SLICE_HEIGHT, - visible, - }; - } -} diff --git a/ui/src/tracks/annotation/index.ts b/ui/src/tracks/annotation/index.ts index d36e9a20d..bda66f3fd 100644 --- a/ui/src/tracks/annotation/index.ts +++ b/ui/src/tracks/annotation/index.ts @@ -13,8 +13,11 @@ // limitations under the License. import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public'; +import { + ChromeSliceTrack, + SLICE_TRACK_KIND, +} from '../chrome_slices/chrome_slice_track'; import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result'; -import {ChromeSliceTrack, SLICE_TRACK_KIND} from '../chrome_slices/'; import {COUNTER_TRACK_KIND, TraceProcessorCounterTrack} from '../counter'; class AnnotationPlugin implements Plugin { @@ -49,7 +52,15 @@ class AnnotationPlugin implements Plugin { metric: true, }, trackFactory: ({trackKey}) => { - return new ChromeSliceTrack(engine, 0, trackKey, id, 'annotation'); + return new ChromeSliceTrack( + { + engine: ctx.engine, + trackKey, + }, + id, + 0, + 'annotation_slice', + ); }, }); } diff --git a/ui/src/tracks/chrome_slices/chrome_slice_track.ts b/ui/src/tracks/chrome_slices/chrome_slice_track.ts new file mode 100644 index 000000000..c9fda8bff --- /dev/null +++ b/ui/src/tracks/chrome_slices/chrome_slice_track.ts @@ -0,0 +1,109 @@ +// 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. + +import {BigintMath as BIMath} from '../../base/bigint_math'; +import {clamp} from '../../base/math_utils'; +import {OnSliceClickArgs} from '../../frontend/base_slice_track'; +import {globals} from '../../frontend/globals'; +import { + NAMED_ROW, + NamedSliceTrack, + NamedSliceTrackTypes, +} from '../../frontend/named_slice_track'; +import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout'; +import {NewTrackArgs} from '../../frontend/track'; +import {LONG_NULL} from '../../trace_processor/query_result'; + +export const SLICE_TRACK_KIND = 'ChromeSliceTrack'; + +export const CHROME_SLICE_ROW = { + // Base columns (tsq, ts, dur, id, depth). + ...NAMED_ROW, + + // Chrome-specific columns. + threadDur: LONG_NULL, +}; +export type ChromeSliceRow = typeof CHROME_SLICE_ROW; + +export interface ChromeSliceTrackTypes extends NamedSliceTrackTypes { + row: ChromeSliceRow; +} + +export class ChromeSliceTrack extends NamedSliceTrack<ChromeSliceTrackTypes> { + constructor( + args: NewTrackArgs, + private trackId: number, + maxDepth: number, + private tableName: string = 'slice', + ) { + super(args); + this.sliceLayout = { + ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS, + depthGuess: maxDepth, + }; + } + + // This is used by the base class to call iter(). + getRowSpec() { + return CHROME_SLICE_ROW; + } + + getSqlSource(): string { + return `select + ts, + dur, + id, + depth, + ifnull(name, '') as name, + thread_dur as threadDur + from ${this.tableName} + where track_id = ${this.trackId}`; + } + + // Converts a SQL result row to an "Impl" Slice. + rowToSlice( + row: ChromeSliceTrackTypes['row'], + ): ChromeSliceTrackTypes['slice'] { + const namedSlice = super.rowToSlice(row); + + if (row.dur > 0n && row.threadDur !== null) { + const fillRatio = clamp(BIMath.ratio(row.threadDur, row.dur), 0, 1); + return {...namedSlice, fillRatio}; + } else { + return namedSlice; + } + } + + onUpdatedSlices(slices: ChromeSliceTrackTypes['slice'][]) { + for (const slice of slices) { + slice.isHighlighted = slice === this.hoveredSlice; + } + } + + onSliceClick(args: OnSliceClickArgs<ChromeSliceTrackTypes['slice']>) { + globals.setLegacySelection( + { + kind: 'CHROME_SLICE', + id: args.slice.id, + trackKey: this.trackKey, + table: this.tableName, + }, + { + clearSearch: true, + pendingScrollId: undefined, + switchToCurrentSelectionTab: true, + }, + ); + } +} diff --git a/ui/src/tracks/chrome_slices/generic_slice_track.ts b/ui/src/tracks/chrome_slices/generic_slice_track.ts deleted file mode 100644 index 1e817a894..000000000 --- a/ui/src/tracks/chrome_slices/generic_slice_track.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2021 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. - -import { - NamedSliceTrack, - NamedSliceTrackTypes, -} from '../../frontend/named_slice_track'; -import {NewTrackArgs} from '../../frontend/track'; - -export class GenericSliceTrack extends NamedSliceTrack<NamedSliceTrackTypes> { - constructor(args: NewTrackArgs, private sqlTrackId: number) { - super(args); - } - - getSqlSource(): string { - return `select ts, dur, id, depth, ifnull(name, '') as name - from slice where track_id = ${this.sqlTrackId}`; - } -} diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts index cae255728..830b7e4e9 100644 --- a/ui/src/tracks/chrome_slices/index.ts +++ b/ui/src/tracks/chrome_slices/index.ts @@ -12,213 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {BigintMath as BIMath} from '../../base/bigint_math'; -import {clamp} from '../../base/math_utils'; -import {Duration, duration, time} from '../../base/time'; import {uuidv4} from '../../base/uuid'; import {ChromeSliceDetailsTab} from '../../frontend/chrome_slice_details_tab'; import { - NAMED_ROW, - NamedSliceTrack, - NamedSliceTrackTypes, -} from '../../frontend/named_slice_track'; -import {SLICE_LAYOUT_FIT_CONTENT_DEFAULTS} from '../../frontend/slice_layout'; -import {SliceData, SliceTrackLEGACY} from '../../frontend/slice_track'; -import {NewTrackArgs} from '../../frontend/track'; -import { BottomTabToSCSAdapter, - EngineProxy, Plugin, PluginContextTrace, PluginDescriptor, } from '../../public'; import {getTrackName} from '../../public/utils'; -import { - LONG, - LONG_NULL, - NUM, - NUM_NULL, - STR, - STR_NULL, -} from '../../trace_processor/query_result'; - -export const SLICE_TRACK_KIND = 'ChromeSliceTrack'; - -export class ChromeSliceTrack extends SliceTrackLEGACY { - private maxDurNs: duration = 0n; - - constructor( - protected engine: EngineProxy, - maxDepth: number, - trackKey: string, - private trackId: number, - namespace?: string, - ) { - super(maxDepth, trackKey, 'slice', namespace); - } - - async onBoundsChange( - start: time, - end: time, - resolution: duration, - ): Promise<SliceData> { - const tableName = this.namespaceTable('slice'); - - if (this.maxDurNs === Duration.ZERO) { - const query = ` - SELECT max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) - AS maxDur FROM ${tableName} WHERE track_id = ${this.trackId}`; - const queryRes = await this.engine.query(query); - this.maxDurNs = queryRes.firstRow({maxDur: LONG_NULL}).maxDur ?? 0n; - } - - const query = ` - SELECT - (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq, - ts, - max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur, - depth, - id as sliceId, - ifnull(name, '[null]') as name, - dur = 0 as isInstant, - dur = -1 as isIncomplete, - thread_dur as threadDur - FROM ${tableName} - WHERE track_id = ${this.trackId} AND - ts >= (${start - this.maxDurNs}) AND - ts <= ${end} - GROUP BY depth, tsq`; - const queryRes = await this.engine.query(query); - - const numRows = queryRes.numRows(); - const slices: SliceData = { - start, - end, - resolution, - length: numRows, - strings: [], - sliceIds: new Float64Array(numRows), - starts: new BigInt64Array(numRows), - ends: new BigInt64Array(numRows), - depths: new Uint16Array(numRows), - titles: new Uint16Array(numRows), - isInstant: new Uint16Array(numRows), - isIncomplete: new Uint16Array(numRows), - cpuTimeRatio: new Float64Array(numRows), - }; - - const stringIndexes = new Map<string, number>(); - function internString(str: string) { - let idx = stringIndexes.get(str); - if (idx !== undefined) return idx; - idx = slices.strings.length; - slices.strings.push(str); - stringIndexes.set(str, idx); - return idx; - } - - const it = queryRes.iter({ - tsq: LONG, - ts: LONG, - dur: LONG, - depth: NUM, - sliceId: NUM, - name: STR, - isInstant: NUM, - isIncomplete: NUM, - threadDur: LONG_NULL, - }); - for (let row = 0; it.valid(); it.next(), row++) { - const startQ = it.tsq; - const start = it.ts; - const dur = it.dur; - const end = start + dur; - const minEnd = startQ + resolution; - const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd); - - slices.starts[row] = startQ; - slices.ends[row] = endQ; - slices.depths[row] = it.depth; - slices.sliceIds[row] = it.sliceId; - slices.titles[row] = internString(it.name); - slices.isInstant[row] = it.isInstant; - slices.isIncomplete[row] = it.isIncomplete; - - let cpuTimeRatio = 1; - if (!it.isInstant && !it.isIncomplete && it.threadDur !== null) { - // Rounding the CPU time ratio to two decimal places and ensuring - // it is less than or equal to one, incase the thread duration exceeds - // the total duration. - cpuTimeRatio = Math.min( - Math.round(BIMath.ratio(it.threadDur, it.dur) * 100) / 100, - 1, - ); - } - slices.cpuTimeRatio![row] = cpuTimeRatio; - } - return slices; - } -} - -export const CHROME_SLICE_ROW = { - // Base columns (tsq, ts, dur, id, depth). - ...NAMED_ROW, - - // Chrome-specific columns. - threadDur: LONG_NULL, -}; -export type ChromeSliceRow = typeof CHROME_SLICE_ROW; - -export interface ChromeSliceTrackTypes extends NamedSliceTrackTypes { - row: ChromeSliceRow; -} - -export class ChromeSliceTrackV2 extends NamedSliceTrack<ChromeSliceTrackTypes> { - constructor(args: NewTrackArgs, private trackId: number, maxDepth: number) { - super(args); - this.sliceLayout = { - ...SLICE_LAYOUT_FIT_CONTENT_DEFAULTS, - depthGuess: maxDepth, - }; - } - - // This is used by the base class to call iter(). - getRowSpec() { - return CHROME_SLICE_ROW; - } - - getSqlSource(): string { - return `select - ts, - dur, - id, - depth, - ifnull(name, '') as name, - thread_dur as threadDur - from slice - where track_id = ${this.trackId}`; - } - - // Converts a SQL result row to an "Impl" Slice. - rowToSlice( - row: ChromeSliceTrackTypes['row'], - ): ChromeSliceTrackTypes['slice'] { - const namedSlice = super.rowToSlice(row); - - if (row.dur > 0n && row.threadDur !== null) { - const fillRatio = clamp(BIMath.ratio(row.threadDur, row.dur), 0, 1); - return {...namedSlice, fillRatio}; - } else { - return namedSlice; - } - } - - onUpdatedSlices(slices: ChromeSliceTrackTypes['slice'][]) { - for (const slice of slices) { - slice.isHighlighted = slice === this.hoveredSlice; - } - } -} +import {NUM, NUM_NULL, STR_NULL} from '../../trace_processor/query_result'; +import {ChromeSliceTrack, SLICE_TRACK_KIND} from './chrome_slice_track'; class ChromeSlicesPlugin implements Plugin { async onTraceLoad(ctx: PluginContextTrace): Promise<void> { @@ -281,7 +85,7 @@ class ChromeSlicesPlugin implements Plugin { engine: ctx.engine, trackKey, }; - return new ChromeSliceTrackV2(newTrackArgs, trackId, maxDepth); + return new ChromeSliceTrack(newTrackArgs, trackId, maxDepth); }, }); } diff --git a/ui/src/tracks/visualised_args/index.ts b/ui/src/tracks/visualised_args/index.ts index 9e34de3f1..459a7ad4a 100644 --- a/ui/src/tracks/visualised_args/index.ts +++ b/ui/src/tracks/visualised_args/index.ts @@ -12,98 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import m from 'mithril'; -import {v4 as uuidv4} from 'uuid'; - -import {Actions} from '../../common/actions'; -import {globals} from '../../frontend/globals'; -import { - EngineProxy, - Plugin, - PluginContextTrace, - PluginDescriptor, - TrackContext, -} from '../../public'; -import {ChromeSliceTrack} from '../chrome_slices'; +import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public'; import { VISUALISED_ARGS_SLICE_TRACK_URI, VisualisedArgsState, } from '../../frontend/visualized_args_tracks'; -import {Button} from '../../widgets/button'; -import {Icons} from '../../base/semantic_icons'; - -export class VisualisedArgsTrack extends ChromeSliceTrack { - private helperViewName: string; - - constructor( - engine: EngineProxy, - maxDepth: number, - trackKey: string, - trackId: number, - private argName: string, - ) { - const uuid = uuidv4(); - const namespace = `__arg_visualisation_helper_${argName}_${uuid}`; - const escapedNamespace = namespace.replace(/[^a-zA-Z]/g, '_'); - super(engine, maxDepth, trackKey, trackId, escapedNamespace); - this.helperViewName = `${escapedNamespace}_slice`; - } - - async onCreate(_ctx: TrackContext): Promise<void> { - // Create the helper view - just one which is relevant to this slice - await this.engine.query(` - create view ${this.helperViewName} as - with slice_with_arg as ( - select - slice.id, - slice.track_id, - slice.ts, - slice.dur, - slice.thread_dur, - NULL as cat, - args.display_value as name - from slice - join args using (arg_set_id) - where args.key='${this.argName}' - ) - select - *, - (select count() - from ancestor_slice(s1.id) s2 - join slice_with_arg s3 on s2.id=s3.id - ) as depth - from slice_with_arg s1 - order by id; - `); - } - - async onDestroy(): Promise<void> { - if (this.engine.isAlive) { - await this.engine.query(`drop view ${this.helperViewName}`); - } - } - - getFont() { - return 'italic 11px Roboto'; - } - - getTrackShellButtons(): m.Children { - return m(Button, { - onclick: () => { - // This behavior differs to the original behavior a little. - // Originally, hitting the close button on a single track removed ALL - // tracks with this argName, whereas this one only closes the single - // track. - // This will be easily fixable once we transition to using dynamic - // tracks instead of this "initial state" approach to add these tracks. - globals.dispatch(Actions.removeTracks({trackKeys: [this.trackKey]})); - }, - icon: Icons.Close, - title: 'Close', - compact: true, - }); - } -} +import {VisualisedArgsTrack} from './visualized_args_track'; class VisualisedArgsPlugin implements Plugin { async onTraceLoad(ctx: PluginContextTrace): Promise<void> { @@ -117,10 +31,12 @@ class VisualisedArgsPlugin implements Plugin { // worse than the situation we had before with track config. const params = trackCtx.params as VisualisedArgsState; return new VisualisedArgsTrack( - ctx.engine, - params.maxDepth, - trackCtx.trackKey, + { + engine: ctx.engine, + trackKey: trackCtx.trackKey, + }, params.trackId, + params.maxDepth, params.argName, ); }, diff --git a/ui/src/tracks/visualised_args/visualized_args_track.ts b/ui/src/tracks/visualised_args/visualized_args_track.ts new file mode 100644 index 000000000..8de85d34f --- /dev/null +++ b/ui/src/tracks/visualised_args/visualized_args_track.ts @@ -0,0 +1,93 @@ +// 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. + +import m from 'mithril'; + +import {Actions} from '../../common/actions'; +import {globals} from '../../frontend/globals'; +import {Button} from '../../widgets/button'; +import {Icons} from '../../base/semantic_icons'; +import {ChromeSliceTrack} from '../chrome_slices/chrome_slice_track'; +import {uuidv4Sql} from '../../base/uuid'; +import {NewTrackArgs} from '../../frontend/track'; +import {Disposable, DisposableCallback} from '../../base/disposable'; + +// Similar to a SliceTrack, but creates a view +export class VisualisedArgsTrack extends ChromeSliceTrack { + private viewName: string; + + constructor( + args: NewTrackArgs, + trackId: number, + maxDepth: number, + private argName: string, + ) { + const uuid = uuidv4Sql(); + const escapedArgName = argName.replace(/[^a-zA-Z]/g, '_'); + const viewName = `__arg_visualisation_helper_${escapedArgName}_${uuid}_slice`; + super(args, trackId, maxDepth, viewName); + this.viewName = viewName; + } + + async onInit(): Promise<Disposable> { + // Create the helper view - just one which is relevant to this slice + await this.engine.query(` + create view ${this.viewName} as + with slice_with_arg as ( + select + slice.id, + slice.track_id, + slice.ts, + slice.dur, + slice.thread_dur, + NULL as cat, + args.display_value as name + from slice + join args using (arg_set_id) + where args.key='${this.argName}' + ) + select + *, + (select count() + from ancestor_slice(s1.id) s2 + join slice_with_arg s3 on s2.id=s3.id + ) as depth + from slice_with_arg s1 + order by id; + `); + + return new DisposableCallback(() => { + if (this.engine.isAlive) { + this.engine.query(`drop view ${this.viewName}`); + } + }); + } + + getTrackShellButtons(): m.Children { + return m(Button, { + onclick: () => { + // This behavior differs to the original behavior a little. + // Originally, hitting the close button on a single track removed ALL + // tracks with this argName, whereas this one only closes the single + // track. + // This will be easily fixable once we transition to using dynamic + // tracks instead of this "initial state" approach to add these tracks. + globals.dispatch(Actions.removeTracks({trackKeys: [this.trackKey]})); + }, + icon: Icons.Close, + title: 'Close', + compact: true, + }); + } +} |