diff options
Diffstat (limited to 'ui/src/common')
-rw-r--r-- | ui/src/common/actions.ts | 123 | ||||
-rw-r--r-- | ui/src/common/actions_unittest.ts | 79 | ||||
-rw-r--r-- | ui/src/common/arg_types.ts | 2 | ||||
-rw-r--r-- | ui/src/common/empty_state.ts | 4 | ||||
-rw-r--r-- | ui/src/common/flamegraph_unittest.ts | 3 | ||||
-rw-r--r-- | ui/src/common/flamegraph_util.ts | 79 | ||||
-rw-r--r-- | ui/src/common/metatracing.ts | 47 | ||||
-rw-r--r-- | ui/src/common/plugins.ts | 33 | ||||
-rw-r--r-- | ui/src/common/queries.ts | 77 | ||||
-rw-r--r-- | ui/src/common/state.ts | 83 | ||||
-rw-r--r-- | ui/src/common/state_unittest.ts | 8 |
11 files changed, 199 insertions, 339 deletions
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts index 4cebecb05..c8ba1e621 100644 --- a/ui/src/common/actions.ts +++ b/ui/src/common/actions.ts @@ -15,7 +15,7 @@ import {Draft} from 'immer'; import {assertExists, assertTrue} from '../base/logging'; -import {duration, Time, time} from '../base/time'; +import {duration, time} from '../base/time'; import {RecordConfig} from '../controller/record_config_types'; import { GenericSliceDetailsTabConfig, @@ -37,7 +37,6 @@ import { performReordering, } from './dragndrop_logic'; import {createEmptyState} from './empty_state'; -import {defaultViewingOption} from './flamegraph_util'; import { MetatraceTrackId, traceEventBegin, @@ -47,9 +46,7 @@ import { import { AdbRecordingTarget, Area, - CallsiteInfo, EngineMode, - FlamegraphStateViewingOption, LoadedConfig, NewEngineMode, OmniboxMode, @@ -253,15 +250,15 @@ export const StateActions = { // the reducer. args: { name: string; - id: string; + key: string; summaryTrackKey?: string; collapsed: boolean; fixedOrdering?: boolean; }, ): void { - state.trackGroups[args.id] = { + state.trackGroups[args.key] = { name: args.name, - id: args.id, + key: args.key, collapsed: args.collapsed, tracks: [], summaryTrack: args.summaryTrackKey, @@ -394,12 +391,8 @@ export const StateActions = { } }, - toggleTrackGroupCollapsed( - state: StateDraft, - args: {trackGroupId: string}, - ): void { - const id = args.trackGroupId; - const trackGroup = assertExists(state.trackGroups[id]); + toggleTrackGroupCollapsed(state: StateDraft, args: {groupKey: string}): void { + const trackGroup = assertExists(state.trackGroups[args.groupKey]); trackGroup.collapsed = !trackGroup.collapsed; }, @@ -448,31 +441,6 @@ export const StateActions = { } }, - createPermalink(state: StateDraft, args: {isRecordingConfig: boolean}): void { - state.permalink = { - requestId: generateNextId(state), - hash: undefined, - isRecordingConfig: args.isRecordingConfig, - }; - }, - - setPermalink( - state: StateDraft, - args: {requestId: string; hash: string}, - ): void { - // Drop any links for old requests. - if (state.permalink.requestId !== args.requestId) return; - state.permalink = args; - }, - - loadPermalink(state: StateDraft, args: {hash: string}): void { - state.permalink = {requestId: generateNextId(state), hash: args.hash}; - }, - - clearPermalink(state: StateDraft, _: {}): void { - state.permalink = {}; - }, - updateStatus(state: StateDraft, args: Status): void { if (statusTraceEvent) { traceEventEnd(statusTraceEvent); @@ -666,13 +634,6 @@ export const StateActions = { type: args.type, }, }; - this.openFlamegraph(state, { - type: args.type, - start: Time.ZERO, - end: args.ts, - upids: [args.upid], - viewingOption: defaultViewingOption(args.type), - }); }, selectPerfSamples( @@ -696,35 +657,6 @@ export const StateActions = { type: args.type, }, }; - this.openFlamegraph(state, { - type: args.type, - start: args.leftTs, - end: args.rightTs, - upids: [args.upid], - viewingOption: defaultViewingOption(args.type), - }); - }, - - openFlamegraph( - state: StateDraft, - args: { - upids: number[]; - start: time; - end: time; - type: ProfileType; - viewingOption: FlamegraphStateViewingOption; - }, - ): void { - state.currentFlamegraphState = { - kind: 'FLAMEGRAPH_STATE', - upids: args.upids, - start: args.start, - end: args.end, - type: args.type, - viewingOption: args.viewingOption, - focusRegex: '', - expandedCallsiteByViewingOption: {}, - }; }, selectCpuProfileSample( @@ -742,43 +674,14 @@ export const StateActions = { }; }, - expandFlamegraphState( - state: StateDraft, - args: { - expandedCallsite?: CallsiteInfo; - viewingOption: FlamegraphStateViewingOption; - }, - ): void { - if (state.currentFlamegraphState === null) return; - state.currentFlamegraphState.expandedCallsiteByViewingOption[ - args.viewingOption - ] = args.expandedCallsite; - }, - - changeViewFlamegraphState( - state: StateDraft, - args: {viewingOption: FlamegraphStateViewingOption}, - ): void { - if (state.currentFlamegraphState === null) return; - state.currentFlamegraphState.viewingOption = args.viewingOption; - }, - - changeFocusFlamegraphState( - state: StateDraft, - args: {focusRegex: string}, - ): void { - if (state.currentFlamegraphState === null) return; - state.currentFlamegraphState.focusRegex = args.focusRegex; - }, - - selectChromeSlice( + selectSlice( state: StateDraft, args: {id: number; trackKey: string; table?: string; scroll?: boolean}, ): void { state.selection = { kind: 'legacy', legacySelection: { - kind: 'CHROME_SLICE', + kind: 'SLICE', id: args.id, trackKey: args.trackKey, table: args.table, @@ -920,7 +823,7 @@ export const StateActions = { toggleTrackSelection( state: StateDraft, - args: {id: string; isTrackGroup: boolean}, + args: {key: string; isTrackGroup: boolean}, ) { const selection = state.selection; if ( @@ -930,12 +833,12 @@ export const StateActions = { return; } const areaId = selection.legacySelection.areaId; - const index = state.areas[areaId].tracks.indexOf(args.id); + const index = state.areas[areaId].tracks.indexOf(args.key); if (index > -1) { state.areas[areaId].tracks.splice(index, 1); if (args.isTrackGroup) { // Also remove all child tracks. - for (const childTrack of state.trackGroups[args.id].tracks) { + for (const childTrack of state.trackGroups[args.key].tracks) { const childIndex = state.areas[areaId].tracks.indexOf(childTrack); if (childIndex > -1) { state.areas[areaId].tracks.splice(childIndex, 1); @@ -943,10 +846,10 @@ export const StateActions = { } } } else { - state.areas[areaId].tracks.push(args.id); + state.areas[areaId].tracks.push(args.key); if (args.isTrackGroup) { // Also add all child tracks. - for (const childTrack of state.trackGroups[args.id].tracks) { + for (const childTrack of state.trackGroups[args.key].tracks) { if (!state.areas[areaId].tracks.includes(childTrack)) { state.areas[areaId].tracks.push(childTrack); } diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts index de497d7f6..468755e34 100644 --- a/ui/src/common/actions_unittest.ts +++ b/ui/src/common/actions_unittest.ts @@ -15,7 +15,6 @@ import {produce} from 'immer'; import {assertExists} from '../base/logging'; -import {Time} from '../base/time'; import {PrimaryTrackSortKey} from '../public'; import {HEAP_PROFILE_TRACK_KIND} from '../core_plugins/heap_profile'; import {PROCESS_SCHEDULING_TRACK_KIND} from '../core_plugins/process_summary/process_scheduling_track'; @@ -25,13 +24,12 @@ import {StateActions} from './actions'; import {createEmptyState} from './empty_state'; import { InThreadTrackSortKey, - ProfileType, SCROLLING_TRACK_GROUP, State, TraceUrlSource, TrackSortKey, } from './state'; -import {SLICE_TRACK_KIND} from '../core_plugins/chrome_slices/chrome_slice_track'; +import {THREAD_SLICE_TRACK_KIND} from '../core_plugins/thread_slice/thread_slice_track'; function fakeTrack( state: State, @@ -60,14 +58,14 @@ function fakeTrack( function fakeTrackGroup( state: State, - args: {id: string; summaryTrackId: string}, + args: {key: string; summaryTrackKey: string}, ): State { return produce(state, (draft) => { StateActions.addTrackGroup(draft, { name: 'A group', - id: args.id, + key: args.key, collapsed: false, - summaryTrackKey: args.summaryTrackId, + summaryTrackKey: args.summaryTrackKey, }); }); } @@ -117,7 +115,7 @@ test('add track to track group', () => { const afterGroup = produce(state, (draft) => { StateActions.addTrackGroup(draft, { name: 'A track group', - id: '123-123-123', + key: '123-123-123', summaryTrackKey: 's', collapsed: false, }); @@ -151,19 +149,19 @@ test('reorder tracks', () => { }); }); - const firstTrackId = once.scrollingTracks[0]; - const secondTrackId = once.scrollingTracks[1]; + const firstTrackKey = once.scrollingTracks[0]; + const secondTrackKey = once.scrollingTracks[1]; const twice = produce(once, (draft) => { StateActions.moveTrack(draft, { - srcId: `${firstTrackId}`, + srcId: `${firstTrackKey}`, op: 'after', - dstId: `${secondTrackId}`, + dstId: `${secondTrackKey}`, }); }); - expect(twice.scrollingTracks[0]).toBe(secondTrackId); - expect(twice.scrollingTracks[1]).toBe(firstTrackId); + expect(twice.scrollingTracks[0]).toBe(secondTrackKey); + expect(twice.scrollingTracks[1]).toBe(firstTrackKey); }); test('reorder pinned to scrolling', () => { @@ -326,7 +324,7 @@ test('setEngineReady', () => { test('sortTracksByPriority', () => { let state = createEmptyState(); - state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'}); + state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'}); state = fakeTrack(state, { key: 'b', uri: HEAP_PROFILE_TRACK_KIND, @@ -351,7 +349,7 @@ test('sortTracksByPriority', () => { test('sortTracksByPriorityAndKindAndName', () => { let state = createEmptyState(); - state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'}); + state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'b'}); state = fakeTrack(state, { key: 'a', uri: PROCESS_SCHEDULING_TRACK_KIND, @@ -360,19 +358,19 @@ test('sortTracksByPriorityAndKindAndName', () => { }); state = fakeTrack(state, { key: 'b', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackGroup: 'g', trackSortKey: PrimaryTrackSortKey.MAIN_THREAD, }); state = fakeTrack(state, { key: 'c', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackGroup: 'g', trackSortKey: PrimaryTrackSortKey.RENDER_THREAD, }); state = fakeTrack(state, { key: 'd', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackGroup: 'g', trackSortKey: PrimaryTrackSortKey.GPU_COMPLETION_THREAD, }); @@ -383,13 +381,13 @@ test('sortTracksByPriorityAndKindAndName', () => { }); state = fakeTrack(state, { key: 'f', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackGroup: 'g', name: 'T2', }); state = fakeTrack(state, { key: 'g', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackGroup: 'g', name: 'T10', }); @@ -416,10 +414,10 @@ test('sortTracksByPriorityAndKindAndName', () => { test('sortTracksByTidThenName', () => { let state = createEmptyState(); - state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'a'}); + state = fakeTrackGroup(state, {key: 'g', summaryTrackKey: 'a'}); state = fakeTrack(state, { key: 'a', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackSortKey: { utid: 1, priority: InThreadTrackSortKey.ORDINARY, @@ -430,7 +428,7 @@ test('sortTracksByTidThenName', () => { }); state = fakeTrack(state, { key: 'b', - uri: SLICE_TRACK_KIND, + uri: THREAD_SLICE_TRACK_KIND, trackSortKey: { utid: 2, priority: InThreadTrackSortKey.ORDINARY, @@ -457,38 +455,3 @@ test('sortTracksByTidThenName', () => { expect(after.trackGroups['g'].tracks).toEqual(['a', 'c', 'b']); }); - -test('perf samples open flamegraph', () => { - const state = createEmptyState(); - - const afterSelectingPerf = produce(state, (draft) => { - StateActions.selectPerfSamples(draft, { - id: 0, - upid: 0, - leftTs: Time.fromRaw(0n), - rightTs: Time.fromRaw(0n), - type: ProfileType.PERF_SAMPLE, - }); - }); - - expect(assertExists(afterSelectingPerf.currentFlamegraphState).type).toBe( - ProfileType.PERF_SAMPLE, - ); -}); - -test('heap profile opens flamegraph', () => { - const state = createEmptyState(); - - const afterSelectingPerf = produce(state, (draft) => { - StateActions.selectHeapProfile(draft, { - id: 0, - upid: 0, - ts: Time.fromRaw(0n), - type: ProfileType.JAVA_HEAP_GRAPH, - }); - }); - - expect(assertExists(afterSelectingPerf.currentFlamegraphState).type).toBe( - ProfileType.JAVA_HEAP_GRAPH, - ); -}); diff --git a/ui/src/common/arg_types.ts b/ui/src/common/arg_types.ts index 853cf7972..551333b22 100644 --- a/ui/src/common/arg_types.ts +++ b/ui/src/common/arg_types.ts @@ -14,5 +14,5 @@ export type ArgValue = | string - | {kind: 'SLICE'; trackId: string; sliceId: number; rawValue: string}; + | {kind: 'SCHED_SLICE'; trackId: string; sliceId: number; rawValue: string}; export type Args = Map<string, ArgValue>; diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts index e3ed2b75b..4c945f631 100644 --- a/ui/src/common/empty_state.ts +++ b/ui/src/common/empty_state.ts @@ -95,7 +95,6 @@ export function createEmptyState(): State { scrollingTracks: [], areas: {}, queries: {}, - permalink: {}, notes: {}, recordConfig: AUTOLOAD_STARTED_CONFIG_FLAG.get() @@ -107,7 +106,7 @@ export function createEmptyState(): State { frontendLocalState: { visibleState: { start: Time.ZERO, - end: Time.ZERO, + end: Time.fromSeconds(10), lastUpdate: 0, resolution: 0n, }, @@ -122,7 +121,6 @@ export function createEmptyState(): State { selection: { kind: 'empty', }, - currentFlamegraphState: null, traceConversionInProgress: false, perfDebug: false, diff --git a/ui/src/common/flamegraph_unittest.ts b/ui/src/common/flamegraph_unittest.ts index a9034bff7..e0b99bf52 100644 --- a/ui/src/common/flamegraph_unittest.ts +++ b/ui/src/common/flamegraph_unittest.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {mergeCallsites} from './flamegraph_util'; -import {CallsiteInfo} from './state'; +import {CallsiteInfo, mergeCallsites} from './flamegraph_util'; test('zeroCallsitesMerged', () => { const callsites: CallsiteInfo[] = [ diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts index acf2ee827..7e87e1c3e 100644 --- a/ui/src/common/flamegraph_util.ts +++ b/ui/src/common/flamegraph_util.ts @@ -13,13 +13,36 @@ // limitations under the License. import {featureFlags} from '../core/feature_flags'; -import {CallsiteInfo, FlamegraphStateViewingOption, ProfileType} from './state'; +import {ProfileType} from './state'; + +export enum FlamegraphViewingOption { + SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE', + ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE', + OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS', + OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS', + PERF_SAMPLES_KEY = 'PERF_SAMPLES', + DOMINATOR_TREE_OBJ_SIZE_KEY = 'DOMINATED_OBJ_SIZE', + DOMINATOR_TREE_OBJ_COUNT_KEY = 'DOMINATED_OBJ_COUNT', +} interface ViewingOption { - option: FlamegraphStateViewingOption; + option: FlamegraphViewingOption; name: string; } +export interface CallsiteInfo { + id: number; + parentId: number; + depth: number; + name?: string; + totalSize: number; + selfSize: number; + mapping: string; + merged: boolean; + highlighted: boolean; + location?: string; +} + const SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG = featureFlags.register({ id: 'showHeapGraphDominatorTree', name: 'Show heap graph dominator tree', @@ -32,32 +55,29 @@ export function viewingOptions(profileType: ProfileType): Array<ViewingOption> { case ProfileType.PERF_SAMPLE: return [ { - option: FlamegraphStateViewingOption.PERF_SAMPLES_KEY, + option: FlamegraphViewingOption.PERF_SAMPLES_KEY, name: 'Samples', }, ]; case ProfileType.JAVA_HEAP_GRAPH: return [ { - option: - FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, + option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, name: 'Size', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY, name: 'Objects', }, ].concat( SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG.get() ? [ { - option: - FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY, + option: FlamegraphViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY, name: 'Dominated size', }, { - option: - FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY, + option: FlamegraphViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY, name: 'Dominated objects', }, ] @@ -66,62 +86,60 @@ export function viewingOptions(profileType: ProfileType): Array<ViewingOption> { case ProfileType.HEAP_PROFILE: return [ { - option: - FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, + option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, name: 'Unreleased size', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY, name: 'Unreleased count', }, { - option: FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, + option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, name: 'Total size', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY, name: 'Total count', }, ]; case ProfileType.NATIVE_HEAP_PROFILE: return [ { - option: - FlamegraphStateViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, + option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, name: 'Unreleased malloc size', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY, name: 'Unreleased malloc count', }, { - option: FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, + option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, name: 'Total malloc size', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY, name: 'Total malloc count', }, ]; case ProfileType.JAVA_HEAP_SAMPLES: return [ { - option: FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, + option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, name: 'Total allocation size', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY, name: 'Total allocation count', }, ]; case ProfileType.MIXED_HEAP_PROFILE: return [ { - option: FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, + option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY, name: 'Total allocation size (malloc + java)', }, { - option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY, + option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY, name: 'Total allocation count (malloc + java)', }, ]; @@ -133,14 +151,14 @@ export function viewingOptions(profileType: ProfileType): Array<ViewingOption> { export function defaultViewingOption( profileType: ProfileType, -): FlamegraphStateViewingOption { +): FlamegraphViewingOption { return viewingOptions(profileType)[0].option; } export function expandCallsites( - data: CallsiteInfo[], + data: ReadonlyArray<CallsiteInfo>, clickedCallsiteIndex: number, -): CallsiteInfo[] { +): ReadonlyArray<CallsiteInfo> { if (clickedCallsiteIndex === -1) return data; const expandedCallsites: CallsiteInfo[] = []; if (clickedCallsiteIndex >= data.length || clickedCallsiteIndex < -1) { @@ -170,7 +188,10 @@ export function expandCallsites( // Merge callsites that have approximately width less than // MIN_PIXEL_DISPLAYED. All small callsites in the same depth and with same // parent will be merged to one with total size of all merged callsites. -export function mergeCallsites(data: CallsiteInfo[], minSizeDisplayed: number) { +export function mergeCallsites( + data: ReadonlyArray<CallsiteInfo>, + minSizeDisplayed: number, +) { const mergedData: CallsiteInfo[] = []; const mergedCallsites: Map<number, number> = new Map(); for (let i = 0; i < data.length; i++) { @@ -238,7 +259,7 @@ function getCallsitesParentHash( ? +map.get(callsite.parentId)! : callsite.parentId; } -export function findRootSize(data: CallsiteInfo[]) { +export function findRootSize(data: ReadonlyArray<CallsiteInfo>) { let totalSize = 0; let i = 0; while (i < data.length && data[i].depth === 0) { diff --git a/ui/src/common/metatracing.ts b/ui/src/common/metatracing.ts index 756d68dd8..cdd43b494 100644 --- a/ui/src/common/metatracing.ts +++ b/ui/src/common/metatracing.ts @@ -13,12 +13,8 @@ // limitations under the License. import {featureFlags} from '../core/feature_flags'; -import { - MetatraceCategories, - PerfettoMetatrace, - Trace, - TracePacket, -} from '../protos'; +import {MetatraceCategories, PerfettoMetatrace} from '../protos'; +import protobuf from 'protobufjs/minimal'; const METATRACING_BUFFER_SIZE = 100000; @@ -83,7 +79,7 @@ interface TraceEvent { const traceEvents: TraceEvent[] = []; function readMetatrace(): Uint8Array { - const eventToPacket = (e: TraceEvent): TracePacket => { + const eventToPacket = (e: TraceEvent): Uint8Array => { const metatraceEvent = PerfettoMetatrace.create({ eventName: e.eventName, threadId: e.track, @@ -97,20 +93,37 @@ function readMetatrace(): Uint8Array { }), ); } - return TracePacket.create({ - timestamp: e.startNs, - timestampClockId: 1, - perfettoMetatrace: metatraceEvent, - }); + const PROTO_VARINT_TYPE = 0; + const PROTO_LEN_DELIMITED_WIRE_TYPE = 2; + const TRACE_PACKET_PROTO_TAG = (1 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE; + const TRACE_PACKET_TIMESTAMP_TAG = (8 << 3) | PROTO_VARINT_TYPE; + const TRACE_PACKET_CLOCK_ID_TAG = (58 << 3) | PROTO_VARINT_TYPE; + const TRACE_PACKET_METATRACE_TAG = + (49 << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE; + + const wri = protobuf.Writer.create(); + wri.uint32(TRACE_PACKET_PROTO_TAG); + wri.fork(); // Start of Trace Packet. + wri.uint32(TRACE_PACKET_TIMESTAMP_TAG).int64(e.startNs); + wri.uint32(TRACE_PACKET_CLOCK_ID_TAG).int32(1); + wri + .uint32(TRACE_PACKET_METATRACE_TAG) + .bytes(PerfettoMetatrace.encode(metatraceEvent).finish()); + wri.ldelim(); + return wri.finish(); }; - const packets: TracePacket[] = []; + const packets: Uint8Array[] = []; for (const event of traceEvents) { packets.push(eventToPacket(event)); } - const trace = Trace.create({ - packet: packets, - }); - return Trace.encode(trace).finish(); + const totalLength = packets.reduce((acc, arr) => acc + arr.length, 0); + const trace = new Uint8Array(totalLength); + let offset = 0; + for (const packet of packets) { + trace.set(packet, offset); + offset += packet.length; + } + return trace; } interface TraceEventParams { diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts index 3a5899ed7..145545815 100644 --- a/ui/src/common/plugins.ts +++ b/ui/src/common/plugins.ts @@ -17,7 +17,7 @@ import {v4 as uuidv4} from 'uuid'; import {Disposable, Trash} from '../base/disposable'; import {Registry} from '../base/registry'; import {Span, duration, time} from '../base/time'; -import {globals} from '../frontend/globals'; +import {TraceContext, globals} from '../frontend/globals'; import { Command, DetailsPanel, @@ -45,6 +45,7 @@ import {assertExists} from '../base/logging'; import {raf} from '../core/raf_scheduler'; import {defaultPlugins} from '../core/default_plugins'; import {HighPrecisionTimeSpan} from './high_precision_time'; +import {PromptOption} from '../frontend/omnibox_manager'; // Every plugin gets its own PluginContext. This is how we keep track // what each plugin is doing and how we can blame issues on particular @@ -221,9 +222,12 @@ class PluginContextTraceImpl implements PluginContextTrace, Disposable { pinTracksByPredicate(predicate: TrackPredicate) { const tracks = Object.values(globals.state.tracks); + const groups = globals.state.trackGroups; for (const track of tracks) { const tags = { name: track.name, + groupName: (track.trackGroup ? groups[track.trackGroup] : undefined) + ?.name, }; if (predicate(tags) && !isPinned(track.key)) { globals.dispatch( @@ -275,10 +279,10 @@ class PluginContextTraceImpl implements PluginContextTrace, Disposable { }; return predicate(ref); }) - .map((group) => group.id); + .map((group) => group.key); - for (const trackGroupId of groupsToExpand) { - globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId})); + for (const groupKey of groupsToExpand) { + globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey})); } }, @@ -293,10 +297,10 @@ class PluginContextTraceImpl implements PluginContextTrace, Disposable { }; return predicate(ref); }) - .map((group) => group.id); + .map((group) => group.key); - for (const trackGroupId of groupsToCollapse) { - globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId})); + for (const groupKey of groupsToCollapse) { + globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey})); } }, @@ -342,11 +346,16 @@ class PluginContextTraceImpl implements PluginContextTrace, Disposable { return globals.store.createSubStore(['plugins', this.pluginId], migrate); } - readonly trace = { - get span(): Span<time, duration> { - return globals.stateTraceTimeTP(); - }, - }; + get trace(): TraceContext { + return globals.traceContext; + } + + async prompt( + text: string, + options?: PromptOption[] | undefined, + ): Promise<string> { + return globals.omnibox.prompt(text, options); + } } function isPinned(trackId: string): boolean { diff --git a/ui/src/common/queries.ts b/ui/src/common/queries.ts index a6c461b8d..227b9cd60 100644 --- a/ui/src/common/queries.ts +++ b/ui/src/common/queries.ts @@ -40,48 +40,59 @@ export async function runQuery( params?: QueryRunParams, ): Promise<QueryResponse> { const startMs = performance.now(); - const queryRes = engine.execute(sqlQuery); // TODO(primiano): once the controller thread is gone we should pass down // the result objects directly to the frontend, iterate over the result // and deal with pagination there. For now we keep the old behavior and // truncate to 10k rows. - try { - await queryRes.waitAllRows(); - } catch { + const maybeResult = await engine.tryQuery(sqlQuery); + + if (maybeResult.success) { + const queryRes = maybeResult.result; + const convertNullsToString = params?.convertNullsToString ?? true; + + const durationMs = performance.now() - startMs; + const rows: Row[] = []; + const columns = queryRes.columns(); + let numRows = 0; + for (const iter = queryRes.iter({}); iter.valid(); iter.next()) { + const row: Row = {}; + for (const colName of columns) { + const value = iter.get(colName); + row[colName] = value === null && convertNullsToString ? 'NULL' : value; + } + rows.push(row); + if (++numRows >= MAX_DISPLAY_ROWS) break; + } + + const result: QueryResponse = { + query: sqlQuery, + durationMs, + error: queryRes.error(), + totalRowCount: queryRes.numRows(), + columns, + rows, + statementCount: queryRes.statementCount(), + statementWithOutputCount: queryRes.statementWithOutputCount(), + lastStatementSql: queryRes.lastStatementSql(), + }; + return result; + } else { // In the case of a query error we don't want the exception to bubble up // as a crash. The |queryRes| object will be populated anyways. // queryRes.error() is used to tell if the query errored or not. If it // errored, the frontend will show a graceful message instead. + return { + query: sqlQuery, + durationMs: performance.now() - startMs, + error: maybeResult.error.message, + totalRowCount: 0, + columns: [], + rows: [], + statementCount: 0, + statementWithOutputCount: 0, + lastStatementSql: '', + }; } - - const convertNullsToString = params?.convertNullsToString ?? true; - - const durationMs = performance.now() - startMs; - const rows: Row[] = []; - const columns = queryRes.columns(); - let numRows = 0; - for (const iter = queryRes.iter({}); iter.valid(); iter.next()) { - const row: Row = {}; - for (const colName of columns) { - const value = iter.get(colName); - row[colName] = value === null && convertNullsToString ? 'NULL' : value; - } - rows.push(row); - if (++numRows >= MAX_DISPLAY_ROWS) break; - } - - const result: QueryResponse = { - query: sqlQuery, - durationMs, - error: queryRes.error(), - totalRowCount: queryRes.numRows(), - columns, - rows, - statementCount: queryRes.statementCount(), - statementWithOutputCount: queryRes.statementWithOutputCount(), - lastStatementSql: queryRes.lastStatementSql(), - }; - return result; } diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts index fc3018e83..884c598ab 100644 --- a/ui/src/common/state.ts +++ b/ui/src/common/state.ts @@ -28,7 +28,6 @@ import { selectionToLegacySelection, Selection, LegacySelection, - ProfileType, } from '../core/selection_manager'; export { @@ -42,7 +41,7 @@ export { LegacySelection, AreaSelection, ProfileType, - ChromeSliceSelection, + ThreadSliceSelection, CpuProfileSampleSelection, } from '../core/selection_manager'; @@ -151,7 +150,10 @@ export const MAX_TIME = 180; // 52. Update track group state - don't make the summary track the first track. // 53. Remove android log state. // 54. Remove traceTime. -export const STATE_VERSION = 54; +// 55. Rename TrackGroupState.id -> TrackGroupState.key. +// 56. Renamed chrome slice to thread slice everywhere. +// 57. Remove flamegraph related code from state. +export const STATE_VERSION = 57; export const SCROLLING_TRACK_GROUP = 'ScrollingTracks'; @@ -187,56 +189,6 @@ export type UtidToTrackSortKey = { }; }; -export enum FlamegraphStateViewingOption { - SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE', - ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE', - OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS', - OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS', - PERF_SAMPLES_KEY = 'PERF_SAMPLES', - DOMINATOR_TREE_OBJ_SIZE_KEY = 'DOMINATED_OBJ_SIZE', - DOMINATOR_TREE_OBJ_COUNT_KEY = 'DOMINATED_OBJ_COUNT', -} - -const HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS = [ - FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY, - FlamegraphStateViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY, -] as const; - -export type HeapGraphDominatorTreeViewingOption = - (typeof HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS)[number]; - -export function isHeapGraphDominatorTreeViewingOption( - option: FlamegraphStateViewingOption, -): option is HeapGraphDominatorTreeViewingOption { - return ( - HEAP_GRAPH_DOMINATOR_TREE_VIEWING_OPTIONS as readonly FlamegraphStateViewingOption[] - ).includes(option); -} - -export interface FlamegraphState { - kind: 'FLAMEGRAPH_STATE'; - upids: number[]; - start: time; - end: time; - type: ProfileType; - viewingOption: FlamegraphStateViewingOption; - focusRegex: string; - expandedCallsiteByViewingOption: {[key: string]: CallsiteInfo | undefined}; -} - -export interface CallsiteInfo { - id: number; - parentId: number; - depth: number; - name?: string; - totalSize: number; - selfSize: number; - mapping: string; - merged: boolean; - highlighted: boolean; - location?: string; -} - export interface TraceFileSource { type: 'FILE'; file: File; @@ -288,7 +240,7 @@ export interface TrackState { } export interface TrackGroupState { - id: string; + key: string; name: string; collapsed: boolean; tracks: string[]; // Child track ids. @@ -310,12 +262,6 @@ export interface QueryConfig { query: string; } -export interface PermalinkConfig { - requestId?: string; // Set by the frontend to request a new permalink. - hash?: string; // Set by the controller when the link has been created. - isRecordingConfig?: boolean; // this permalink request is for a recording config only -} - export interface FrontendLocalState { visibleState: VisibleState; } @@ -476,7 +422,7 @@ export interface State { newEngineMode: NewEngineMode; engine?: EngineConfig; traceUuid?: string; - trackGroups: ObjectById<TrackGroupState>; + trackGroups: ObjectByKey<TrackGroupState>; tracks: ObjectByKey<TrackState>; utidToThreadSortKey: UtidToTrackSortKey; areas: ObjectById<AreaById>; @@ -486,12 +432,11 @@ export interface State { debugTrackId?: string; lastTrackReloadRequest?: number; queries: ObjectById<QueryConfig>; - permalink: PermalinkConfig; notes: ObjectById<Note | AreaNote>; status: Status; selection: Selection; - currentFlamegraphState: FlamegraphState | null; traceConversionInProgress: boolean; + flamegraphModalDismissed: boolean; /** * This state is updated on the frontend at 60Hz and eventually syncronised to @@ -527,7 +472,6 @@ export interface State { recordingInProgress: boolean; recordingCancelled: boolean; extensionInstalled: boolean; - flamegraphModalDismissed: boolean; recordingTarget: RecordingTarget; availableAdbDevices: AdbRecordingTarget[]; lastRecordingError?: string; @@ -915,20 +859,19 @@ export function getBuiltinChromeCategoryList(): string[] { ]; } -export function getContainingTrackId( +export function getContainingGroupKey( state: State, trackKey: string, ): null | string { const track = state.tracks[trackKey]; - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (!track) { + if (track === undefined) { return null; } - const parentId = track.trackGroup; - if (!parentId) { + const parentGroupKey = track.trackGroup; + if (!parentGroupKey) { return null; } - return parentId; + return parentGroupKey; } export function getLegacySelection(state: State): LegacySelection | null { diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts index be66340d9..ff111de6b 100644 --- a/ui/src/common/state_unittest.ts +++ b/ui/src/common/state_unittest.ts @@ -15,7 +15,7 @@ import {PrimaryTrackSortKey} from '../public'; import {createEmptyState} from './empty_state'; -import {getContainingTrackId, State} from './state'; +import {getContainingGroupKey, State} from './state'; import {deserializeStateObject, serializeStateObject} from './upload_utils'; test('createEmptyState', () => { @@ -40,9 +40,9 @@ test('getContainingTrackId', () => { trackGroup: 'containsB', }; - expect(getContainingTrackId(state, 'z')).toEqual(null); - expect(getContainingTrackId(state, 'a')).toEqual(null); - expect(getContainingTrackId(state, 'b')).toEqual('containsB'); + expect(getContainingGroupKey(state, 'z')).toEqual(null); + expect(getContainingGroupKey(state, 'a')).toEqual(null); + expect(getContainingGroupKey(state, 'b')).toEqual('containsB'); }); test('state is serializable', () => { |