1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {duration, time} from '../base/time'; 16import {Store} from '../base/store'; 17import {assertUnreachable} from '../base/logging'; 18import {GenericSliceDetailsTabConfigBase} from './generic_slice_details_types'; 19 20export enum ProfileType { 21 HEAP_PROFILE = 'heap_profile', 22 MIXED_HEAP_PROFILE = 'heap_profile:com.android.art,libc.malloc', 23 NATIVE_HEAP_PROFILE = 'heap_profile:libc.malloc', 24 JAVA_HEAP_SAMPLES = 'heap_profile:com.android.art', 25 JAVA_HEAP_GRAPH = 'graph', 26 PERF_SAMPLE = 'perf', 27} 28 29// LEGACY Selection types: 30export interface SliceSelection { 31 kind: 'SCHED_SLICE'; 32 id: number; 33} 34 35export interface HeapProfileSelection { 36 kind: 'HEAP_PROFILE'; 37 id: number; 38 upid: number; 39 ts: time; 40 type: ProfileType; 41} 42 43export interface PerfSamplesSelection { 44 kind: 'PERF_SAMPLES'; 45 id: number; 46 upid: number; 47 leftTs: time; 48 rightTs: time; 49 type: ProfileType; 50} 51 52export interface CpuProfileSampleSelection { 53 kind: 'CPU_PROFILE_SAMPLE'; 54 id: number; 55 utid: number; 56 ts: time; 57} 58 59export interface ThreadSliceSelection { 60 kind: 'SLICE'; 61 id: number; 62 table?: string; 63} 64 65export interface ThreadStateSelection { 66 kind: 'THREAD_STATE'; 67 id: number; 68} 69 70export interface LogSelection { 71 kind: 'LOG'; 72 id: number; 73 trackKey: string; 74} 75 76export interface GenericSliceSelection { 77 kind: 'GENERIC_SLICE'; 78 id: number; 79 sqlTableName: string; 80 start: time; 81 duration: duration; 82 // NOTE: this config can be expanded for multiple details panel types. 83 detailsPanelConfig: {kind: string; config: GenericSliceDetailsTabConfigBase}; 84} 85 86export type LegacySelection = ( 87 | SliceSelection 88 | HeapProfileSelection 89 | CpuProfileSampleSelection 90 | ThreadSliceSelection 91 | ThreadStateSelection 92 | PerfSamplesSelection 93 | LogSelection 94 | GenericSliceSelection 95) & {trackKey?: string}; 96export type SelectionKind = LegacySelection['kind']; // 'THREAD_STATE' | 'SLICE' ... 97 98// New Selection types: 99export interface LegacySelectionWrapper { 100 kind: 'legacy'; 101 legacySelection: LegacySelection; 102} 103 104export interface SingleSelection { 105 kind: 'single'; 106 trackKey: string; 107 eventId: number; 108} 109 110export interface AreaSelection { 111 kind: 'area'; 112 tracks: string[]; 113 start: time; 114 end: time; 115} 116 117export interface NoteSelection { 118 kind: 'note'; 119 id: string; 120} 121 122export interface UnionSelection { 123 kind: 'union'; 124 selections: Selection[]; 125} 126 127export interface EmptySelection { 128 kind: 'empty'; 129} 130 131export type Selection = 132 | SingleSelection 133 | AreaSelection 134 | NoteSelection 135 | UnionSelection 136 | EmptySelection 137 | LegacySelectionWrapper; 138 139export function selectionToLegacySelection( 140 selection: Selection, 141): LegacySelection | null { 142 switch (selection.kind) { 143 case 'area': 144 case 'single': 145 case 'empty': 146 case 'note': 147 return null; 148 case 'union': 149 for (const child of selection.selections) { 150 const result = selectionToLegacySelection(child); 151 if (result !== null) { 152 return result; 153 } 154 } 155 return null; 156 case 'legacy': 157 return selection.legacySelection; 158 default: 159 assertUnreachable(selection); 160 return null; 161 } 162} 163 164interface SelectionState { 165 selection: Selection; 166} 167 168export class SelectionManager { 169 private store: Store<SelectionState>; 170 171 constructor(store: Store<SelectionState>) { 172 this.store = store; 173 } 174 175 clear(): void { 176 this.store.edit((draft) => { 177 draft.selection = { 178 kind: 'empty', 179 }; 180 }); 181 } 182 183 private addSelection(selection: Selection): void { 184 this.store.edit((draft) => { 185 switch (draft.selection.kind) { 186 case 'empty': 187 draft.selection = selection; 188 break; 189 case 'union': 190 draft.selection.selections.push(selection); 191 break; 192 case 'single': 193 case 'legacy': 194 case 'area': 195 case 'note': 196 draft.selection = { 197 kind: 'union', 198 selections: [draft.selection, selection], 199 }; 200 break; 201 default: 202 assertUnreachable(draft.selection); 203 break; 204 } 205 }); 206 } 207 208 // There is no matching addLegacy as we did not support multi-single 209 // selection with the legacy selection system. 210 setLegacy(legacySelection: LegacySelection): void { 211 this.clear(); 212 this.addSelection({ 213 kind: 'legacy', 214 legacySelection, 215 }); 216 } 217 218 setEvent( 219 trackKey: string, 220 eventId: number, 221 legacySelection?: LegacySelection, 222 ) { 223 this.clear(); 224 this.addEvent(trackKey, eventId, legacySelection); 225 } 226 227 addEvent( 228 trackKey: string, 229 eventId: number, 230 legacySelection?: LegacySelection, 231 ) { 232 this.addSelection({ 233 kind: 'single', 234 trackKey, 235 eventId, 236 }); 237 if (legacySelection) { 238 this.addSelection({ 239 kind: 'legacy', 240 legacySelection, 241 }); 242 } 243 } 244} 245