1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {assertDefined} from 'common/assert_utils'; 18import {PersistentStoreProxy} from 'common/persistent_store_proxy'; 19import {Store} from 'common/store'; 20import {Trace} from 'trace/trace'; 21import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 22import { 23 AbstractLogViewerPresenter, 24 NotifyLogViewCallbackType, 25} from 'viewers/common/abstract_log_viewer_presenter'; 26import {LogSelectFilter} from 'viewers/common/log_filters'; 27import {LogPresenter} from 'viewers/common/log_presenter'; 28import {PropertiesPresenter} from 'viewers/common/properties_presenter'; 29import {TextFilter} from 'viewers/common/text_filter'; 30import {LogField, LogHeader} from 'viewers/common/ui_data_log'; 31import {UserOptions} from 'viewers/common/user_options'; 32import {SetRootDisplayNames} from './operations/set_root_display_name'; 33import {TransactionsEntry, TransactionsEntryType, UiData} from './ui_data'; 34 35export class Presenter extends AbstractLogViewerPresenter< 36 UiData, 37 PropertyTreeNode 38> { 39 private static readonly COLUMNS = { 40 id: {name: 'TX ID', cssClass: 'transaction-id right-align'}, 41 vsyncId: {name: 'VSYNC ID', cssClass: 'vsyncid right-align'}, 42 pid: {name: 'PID', cssClass: 'pid right-align'}, 43 uid: {name: 'UID', cssClass: 'uid right-align'}, 44 type: {name: 'TYPE', cssClass: 'transaction-type'}, 45 layerOrDisplayId: { 46 name: 'LAYER/DISP ID', 47 cssClass: 'layer-or-display-id right-align', 48 }, 49 flags: {name: 'Flags', cssClass: 'flags'}, 50 }; 51 52 protected override keepCalculated = true; 53 protected override logPresenter = new LogPresenter<TransactionsEntry>(); 54 protected override propertiesPresenter = new PropertiesPresenter( 55 PersistentStoreProxy.new<UserOptions>( 56 'TransactionsPropertyOptions', 57 { 58 showDefaults: { 59 name: 'Show defaults', 60 enabled: false, 61 tooltip: ` 62 If checked, shows the value of all properties. 63 Otherwise, hides all properties whose value is 64 the default for its data type. 65 `, 66 }, 67 }, 68 this.storage, 69 ), 70 new TextFilter(), 71 [], 72 [new SetRootDisplayNames()], 73 ); 74 75 constructor( 76 trace: Trace<PropertyTreeNode>, 77 readonly storage: Store, 78 notifyViewCallback: NotifyLogViewCallbackType<UiData>, 79 ) { 80 super(trace, notifyViewCallback, UiData.createEmpty()); 81 } 82 83 protected override makeHeaders(): LogHeader[] { 84 return [ 85 new LogHeader( 86 Presenter.COLUMNS.id, 87 new LogSelectFilter([], false, '125'), 88 ), 89 new LogHeader( 90 Presenter.COLUMNS.vsyncId, 91 new LogSelectFilter([], false, '90'), 92 ), 93 new LogHeader(Presenter.COLUMNS.pid, new LogSelectFilter([])), 94 new LogHeader(Presenter.COLUMNS.uid, new LogSelectFilter([])), 95 new LogHeader( 96 Presenter.COLUMNS.type, 97 new LogSelectFilter([], false, '175'), 98 ), 99 new LogHeader( 100 Presenter.COLUMNS.layerOrDisplayId, 101 new LogSelectFilter([]), 102 ), 103 new LogHeader( 104 Presenter.COLUMNS.flags, 105 new LogSelectFilter([], true, '250', '100%'), 106 ), 107 ]; 108 } 109 110 protected override updateDefaultAllowlist( 111 tree: PropertyTreeNode | undefined, 112 ): void { 113 if (!tree) { 114 return; 115 } 116 const allowlist: string[] = []; 117 tree 118 .getChildByName('what') 119 ?.formattedValue() 120 .split(' | ') 121 .forEach((flag) => { 122 const properties = layerChangeFlagToPropertiesMap.get(flag); 123 if (properties !== undefined) { 124 allowlist.push(...properties); 125 } else if (flag.startsWith('e')) { 126 const candidateProperty = flag.split('Changed')[0].slice(1); 127 allowlist.push( 128 candidateProperty[0].toLowerCase() + candidateProperty.slice(1), 129 ); 130 } 131 }); 132 this.propertiesPresenter.updateDefaultAllowList(allowlist); 133 } 134 135 protected override async makeUiDataEntries(): Promise<TransactionsEntry[]> { 136 const entries: TransactionsEntry[] = []; 137 138 const entryProtos = await Promise.all( 139 this.trace.mapEntry(async (entry) => { 140 return await entry.getValue(); 141 }), 142 ); 143 144 for ( 145 let traceIndex = 0; 146 traceIndex < this.trace.lengthEntries; 147 ++traceIndex 148 ) { 149 const entry = this.trace.getEntry(traceIndex); 150 const entryNode = entryProtos[traceIndex]; 151 const vsyncId = Number( 152 assertDefined(entryNode.getChildByName('vsyncId')).getValue(), 153 ); 154 155 for (const transactionState of assertDefined( 156 entryNode.getChildByName('transactions'), 157 ).getAllChildren()) { 158 const transactionId = assertDefined( 159 transactionState.getChildByName('transactionId'), 160 ).formattedValue(); 161 const pid = assertDefined( 162 transactionState.getChildByName('pid'), 163 ).formattedValue(); 164 const uid = assertDefined( 165 transactionState.getChildByName('uid'), 166 ).formattedValue(); 167 const layerChanges = assertDefined( 168 transactionState.getChildByName('layerChanges'), 169 ).getAllChildren(); 170 171 for (const layerState of layerChanges) { 172 const fields: LogField[] = [ 173 {spec: Presenter.COLUMNS.id, value: transactionId}, 174 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 175 {spec: Presenter.COLUMNS.pid, value: pid}, 176 {spec: Presenter.COLUMNS.uid, value: uid}, 177 { 178 spec: Presenter.COLUMNS.type, 179 value: TransactionsEntryType.LAYER_CHANGED, 180 }, 181 { 182 spec: Presenter.COLUMNS.layerOrDisplayId, 183 value: assertDefined( 184 layerState.getChildByName('layerId'), 185 ).formattedValue(), 186 }, 187 { 188 spec: Presenter.COLUMNS.flags, 189 value: assertDefined( 190 layerState.getChildByName('what'), 191 ).formattedValue(), 192 }, 193 ]; 194 entries.push(new TransactionsEntry(entry, fields, layerState)); 195 } 196 197 const displayChanges = assertDefined( 198 transactionState.getChildByName('displayChanges'), 199 ).getAllChildren(); 200 for (const displayState of displayChanges) { 201 const fields: LogField[] = [ 202 {spec: Presenter.COLUMNS.id, value: transactionId}, 203 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 204 {spec: Presenter.COLUMNS.pid, value: pid}, 205 {spec: Presenter.COLUMNS.uid, value: uid}, 206 { 207 spec: Presenter.COLUMNS.type, 208 value: TransactionsEntryType.DISPLAY_CHANGED, 209 }, 210 { 211 spec: Presenter.COLUMNS.layerOrDisplayId, 212 value: assertDefined( 213 displayState.getChildByName('id'), 214 ).formattedValue(), 215 }, 216 { 217 spec: Presenter.COLUMNS.flags, 218 value: assertDefined( 219 displayState.getChildByName('what'), 220 ).formattedValue(), 221 }, 222 ]; 223 entries.push(new TransactionsEntry(entry, fields, displayState)); 224 } 225 226 if (layerChanges.length === 0 && displayChanges.length === 0) { 227 const fields: LogField[] = [ 228 {spec: Presenter.COLUMNS.id, value: transactionId}, 229 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 230 {spec: Presenter.COLUMNS.pid, value: pid}, 231 {spec: Presenter.COLUMNS.uid, value: uid}, 232 { 233 spec: Presenter.COLUMNS.type, 234 value: TransactionsEntryType.NO_OP, 235 }, 236 {spec: Presenter.COLUMNS.layerOrDisplayId, value: ''}, 237 {spec: Presenter.COLUMNS.flags, value: ''}, 238 ]; 239 entries.push(new TransactionsEntry(entry, fields, undefined)); 240 } 241 } 242 243 for (const layerCreationArgs of assertDefined( 244 entryNode.getChildByName('addedLayers'), 245 ).getAllChildren()) { 246 const fields: LogField[] = [ 247 {spec: Presenter.COLUMNS.id, value: ''}, 248 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 249 {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA}, 250 {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA}, 251 { 252 spec: Presenter.COLUMNS.type, 253 value: TransactionsEntryType.LAYER_ADDED, 254 }, 255 { 256 spec: Presenter.COLUMNS.layerOrDisplayId, 257 value: assertDefined( 258 layerCreationArgs.getChildByName('layerId'), 259 ).formattedValue(), 260 }, 261 {spec: Presenter.COLUMNS.flags, value: ''}, 262 ]; 263 entries.push(new TransactionsEntry(entry, fields, layerCreationArgs)); 264 } 265 266 for (const destroyedLayerId of assertDefined( 267 entryNode.getChildByName('destroyedLayers'), 268 ).getAllChildren()) { 269 const fields: LogField[] = [ 270 {spec: Presenter.COLUMNS.id, value: ''}, 271 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 272 {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA}, 273 {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA}, 274 { 275 spec: Presenter.COLUMNS.type, 276 value: TransactionsEntryType.LAYER_DESTROYED, 277 }, 278 { 279 spec: Presenter.COLUMNS.layerOrDisplayId, 280 value: destroyedLayerId.formattedValue(), 281 }, 282 {spec: Presenter.COLUMNS.flags, value: ''}, 283 ]; 284 entries.push(new TransactionsEntry(entry, fields, destroyedLayerId)); 285 } 286 287 for (const displayState of assertDefined( 288 entryNode.getChildByName('addedDisplays'), 289 ).getAllChildren()) { 290 const fields: LogField[] = [ 291 {spec: Presenter.COLUMNS.id, value: ''}, 292 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 293 {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA}, 294 {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA}, 295 { 296 spec: Presenter.COLUMNS.type, 297 value: TransactionsEntryType.DISPLAY_ADDED, 298 }, 299 { 300 spec: Presenter.COLUMNS.layerOrDisplayId, 301 value: assertDefined( 302 displayState.getChildByName('id'), 303 ).formattedValue(), 304 }, 305 { 306 spec: Presenter.COLUMNS.flags, 307 value: assertDefined( 308 displayState.getChildByName('what'), 309 ).formattedValue(), 310 }, 311 ]; 312 entries.push(new TransactionsEntry(entry, fields, displayState)); 313 } 314 315 for (const removedDisplayId of assertDefined( 316 entryNode.getChildByName('removedDisplays'), 317 ).getAllChildren()) { 318 const fields: LogField[] = [ 319 {spec: Presenter.COLUMNS.id, value: ''}, 320 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 321 {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA}, 322 {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA}, 323 { 324 spec: Presenter.COLUMNS.type, 325 value: TransactionsEntryType.DISPLAY_REMOVED, 326 }, 327 { 328 spec: Presenter.COLUMNS.layerOrDisplayId, 329 value: removedDisplayId.formattedValue(), 330 }, 331 {spec: Presenter.COLUMNS.flags, value: ''}, 332 ]; 333 entries.push(new TransactionsEntry(entry, fields, removedDisplayId)); 334 } 335 336 for (const destroyedLayerHandleId of assertDefined( 337 entryNode.getChildByName('destroyedLayerHandles'), 338 ).getAllChildren()) { 339 const fields: LogField[] = [ 340 {spec: Presenter.COLUMNS.id, value: ''}, 341 {spec: Presenter.COLUMNS.vsyncId, value: vsyncId}, 342 {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA}, 343 {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA}, 344 { 345 spec: Presenter.COLUMNS.type, 346 value: TransactionsEntryType.LAYER_HANDLE_DESTROYED, 347 }, 348 { 349 spec: Presenter.COLUMNS.layerOrDisplayId, 350 value: destroyedLayerHandleId.formattedValue(), 351 }, 352 {spec: Presenter.COLUMNS.flags, value: ''}, 353 ]; 354 entries.push( 355 new TransactionsEntry(entry, fields, destroyedLayerHandleId), 356 ); 357 } 358 } 359 360 return entries; 361 } 362 363 protected override updateFiltersInHeaders( 364 headers: LogHeader[], 365 allEntries: TransactionsEntry[], 366 ) { 367 for (const header of headers) { 368 if (header.spec === Presenter.COLUMNS.flags) { 369 (assertDefined(header.filter) as LogSelectFilter).options = 370 this.getUniqueUiDataEntryValues( 371 allEntries, 372 (entry: TransactionsEntry) => 373 assertDefined( 374 entry.fields.find((f) => f.spec === header.spec) 375 ?.value as string, 376 ) 377 .split('|') 378 .map((flag) => flag.trim()), 379 ); 380 } else { 381 (assertDefined(header.filter) as LogSelectFilter).options = 382 this.getUniqueUiDataEntryValues( 383 allEntries, 384 (entry: TransactionsEntry) => 385 assertDefined( 386 entry.fields.find((f) => f.spec === header.spec), 387 ).value.toString(), 388 ); 389 } 390 } 391 } 392 393 private getUniqueUiDataEntryValues<T>( 394 entries: TransactionsEntry[], 395 getValue: (entry: TransactionsEntry) => T | T[], 396 ): T[] { 397 const uniqueValues = new Set<T>(); 398 entries.forEach((entry: TransactionsEntry) => { 399 const value = getValue(entry); 400 if (Array.isArray(value)) { 401 value.forEach((val) => uniqueValues.add(val)); 402 } else { 403 uniqueValues.add(value); 404 } 405 }); 406 407 const result = [...uniqueValues]; 408 409 result.sort((a, b) => { 410 const aIsNumber = !isNaN(Number(a)); 411 const bIsNumber = !isNaN(Number(b)); 412 413 if (aIsNumber && bIsNumber) { 414 return Number(a) - Number(b); 415 } else if (aIsNumber) { 416 return 1; // place number after strings in the result 417 } else if (bIsNumber) { 418 return -1; // place number after strings in the result 419 } 420 421 // a and b are both strings 422 if (a < b) { 423 return -1; 424 } else if (a > b) { 425 return 1; 426 } else { 427 return 0; 428 } 429 }); 430 431 return result; 432 } 433} 434 435const layerChangeFlagToPropertiesMap = new Map([ 436 ['eReparent', ['parentId']], 437 ['eRelativeLayerChanged', ['relativeParentId']], 438 ['eLayerChanged', ['layerId']], 439 ['ePositionChanged', ['x', 'y', 'z']], 440]); 441