diff --git a/tools/winscope-ng/src/viewers/components/tree.component.ts b/tools/winscope-ng/src/viewers/components/tree.component.ts index 168e98bb9..0f41214ba 100644 --- a/tools/winscope-ng/src/viewers/components/tree.component.ts +++ b/tools/winscope-ng/src/viewers/components/tree.component.ts @@ -24,7 +24,7 @@ import { TraceType } from "common/trace/trace_type"; changeDetection: ChangeDetectionStrategy.OnPush, template: ` = []; @Input() store!: PersistentStore; @Input() isFlattened? = false; @@ -86,8 +86,10 @@ export class TreeComponent { @Input() itemsClickable?: boolean; @Input() useGlobalCollapsedState?: boolean; @Input() isAlwaysCollapsed?: boolean; - @Input() showNode: (item?: any) => boolean = () => true; - @Input() isLeaf: (item: any) => boolean = (item: any) => !item.children || item.children.length === 0; + @Input() showNode = (item: UiTreeNode) => true; + @Input() isLeaf = (item?: UiTreeNode) => { + return !item || !item.children || item.children.length === 0; + }; @Output() highlightedItemChange = new EventEmitter(); @Output() selectedTreeChange = new EventEmitter(); @@ -212,24 +214,27 @@ export class TreeComponent { } if (this.useGlobalCollapsedState) { - return this.store.get(`collapsedState.item.${this.dependencies}.${this.item.stableId}`)==="true" + return this.store.get(`collapsedState.item.${this.dependencies}.${this.item?.stableId}`)==="true" ?? this.isCollapsedByDefault; } return this.localCollapsedState; } public children(): UiTreeNode[] { - return this.item.children ?? []; + return this.item?.children ?? []; } public hasChildren() { + if (!this.item) { + return false; + } const isParentEntryInFlatView = UiTreeUtils.isParentNode(this.item.kind ?? "") && this.isFlattened; return (!this.isFlattened || isParentEntryInFlatView) && !this.isLeaf(this.item); } private setCollapseValue(isCollapsed: boolean) { if (this.useGlobalCollapsedState) { - this.store.add(`collapsedState.item.${this.dependencies}.${this.item.stableId}`, `${isCollapsed}`); + this.store.add(`collapsedState.item.${this.dependencies}.${this.item?.stableId}`, `${isCollapsed}`); } else { this.localCollapsedState = isCollapsed; } diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/events.ts b/tools/winscope-ng/src/viewers/viewer_transactions/events.ts index db97840e3..c55529264 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/events.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/events.ts @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + class Events { + public static VSyncIdFilterChanged = "ViewerTransactionsEvent_VSyncIdFilterChanged"; public static PidFilterChanged = "ViewerTransactionsEvent_PidFilterChanged"; public static UidFilterChanged = "ViewerTransactionsEvent_UidFilterChanged"; public static TypeFilterChanged = "ViewerTransactionsEvent_TypeFilterChanged"; public static IdFilterChanged = "ViewerTransactionsEvent_IdFilterChanged"; + public static WhatSearchStringChanged = "ViewerTransactionsEvent_WhatSearchStringChanged"; public static EntryClicked = "ViewerTransactionsEvent_EntryClicked"; } diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts index 924c2d9c0..25f528472 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.spec.ts @@ -94,6 +94,22 @@ describe("ViewerTransactionsPresenter", () => { expect(outputUiData!.scrollToIndex).toEqual(13); }); + it("filters entries according to VSYNC ID filter", () => { + presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed); + + presenter.onVSyncIdFilterChanged([]); + expect(outputUiData!.entries.length) + .toEqual(TOTAL_OUTPUT_ENTRIES); + + presenter.onVSyncIdFilterChanged(["1"]); + expect(new Set(outputUiData!.entries.map(entry => entry.vsyncId))) + .toEqual(new Set([1])); + + presenter.onVSyncIdFilterChanged(["1", "3", "10"]); + expect(new Set(outputUiData!.entries.map(entry => entry.vsyncId))) + .toEqual(new Set([1, 3, 10])); + }); + it("filters entries according to PID filter", () => { presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed); @@ -164,6 +180,20 @@ describe("ViewerTransactionsPresenter", () => { .toEqual(new Set(["1", "3"])); }); + it("filters entries according to \"what\" search string", () => { + presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed); + expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES); + + presenter.onWhatSearchStringChanged(""); + expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES); + + presenter.onWhatSearchStringChanged("Crop"); + expect(outputUiData!.entries.length).toBeLessThan(TOTAL_OUTPUT_ENTRIES); + + presenter.onWhatSearchStringChanged("STRING_WITH_NO_MATCHES"); + expect(outputUiData!.entries.length).toEqual(0); + }); + it ("updates selected entry and properties tree when entry is clicked", () => { presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed); expect(outputUiData!.currentEntryIndex).toEqual(0); diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts index faa3e5fac..8fe35261f 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/presenter.ts @@ -23,7 +23,19 @@ import {TimeUtils} from "common/utils/time_utils"; import { ElapsedTimestamp, RealTimestamp, TimestampType } from "common/trace/timestamp"; import ObjectFormatter from "common/trace/flickerlib/ObjectFormatter"; -class Presenter { +export class Presenter { + private entry?: TransactionsTraceEntry; + private originalIndicesOfUiDataEntries: number[]; + private uiData: UiData; + private readonly notifyUiDataCallback: (data: UiData) => void; + private static readonly VALUE_NA = "N/A"; + private vsyncIdFilter: string[] = []; + private pidFilter: string[] = []; + private uidFilter: string[] = []; + private typeFilter: string[] = []; + private idFilter: string[] = []; + private whatSearchString = ""; + constructor(notifyUiDataCallback: (data: UiData) => void) { this.notifyUiDataCallback = notifyUiDataCallback; this.originalIndicesOfUiDataEntries = []; @@ -50,6 +62,12 @@ class Presenter { this.notifyUiDataCallback(this.uiData); } + public onVSyncIdFilterChanged(vsyncIds: string[]) { + this.vsyncIdFilter = vsyncIds; + this.computeUiData(); + this.notifyUiDataCallback(this.uiData); + } + public onPidFilterChanged(pids: string[]) { this.pidFilter = pids; this.computeUiData(); @@ -74,6 +92,12 @@ class Presenter { this.notifyUiDataCallback(this.uiData); } + public onWhatSearchStringChanged(searchString: string) { + this.whatSearchString = searchString; + this.computeUiData(); + this.notifyUiDataCallback(this.uiData); + } + public onEntryClicked(index: number) { if (this.uiData.selectedEntryIndex === index) { this.uiData.selectedEntryIndex = undefined; // remove selection when clicked again @@ -99,6 +123,10 @@ class Presenter { const entries = this.makeUiDataEntries(this.entry!); + const allVSyncIds = this.getUniqueUiDataEntryValues( + entries, + (entry: UiDataEntry) => entry.vsyncId.toString() + ); const allPids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.pid); const allUids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.uid); const allTypes = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.type); @@ -106,6 +134,11 @@ class Presenter { let filteredEntries = entries; + if (this.vsyncIdFilter.length > 0) { + filteredEntries = + filteredEntries.filter(entry => this.vsyncIdFilter.includes(entry.vsyncId.toString())); + } + if (this.pidFilter.length > 0) { filteredEntries = filteredEntries.filter(entry => this.pidFilter.includes(entry.pid)); @@ -126,6 +159,8 @@ class Presenter { filteredEntries.filter(entry => this.idFilter.includes(entry.id)); } + filteredEntries = filteredEntries.filter(entry => entry.what.includes(this.whatSearchString)); + this.originalIndicesOfUiDataEntries = filteredEntries.map(entry => entry.originalIndexInTraceEntry); const currentEntryIndex = this.computeCurrentEntryIndex(); @@ -133,6 +168,7 @@ class Presenter { const currentPropertiesTree = this.computeCurrentPropertiesTree(filteredEntries, currentEntryIndex, selectedEntryIndex); this.uiData = new UiData( + allVSyncIds, allPids, allUids, allTypes, @@ -189,6 +225,7 @@ class Presenter { transactionStateProto.uid.toString(), UiDataEntryType.LayerChanged, layerStateProto.layerId.toString(), + layerStateProto.what, treeGenerator.generate("LayerState", ObjectFormatter.format(layerStateProto)) )); } @@ -202,6 +239,7 @@ class Presenter { transactionStateProto.uid.toString(), UiDataEntryType.DisplayChanged, displayStateProto.id.toString(), + displayStateProto.what, treeGenerator.generate("DisplayState", ObjectFormatter.format(displayStateProto)) )); } @@ -216,6 +254,7 @@ class Presenter { Presenter.VALUE_NA, UiDataEntryType.LayerAdded, layerCreationArgsProto.layerId.toString(), + "", treeGenerator.generate("LayerCreationArgs", ObjectFormatter.format(layerCreationArgsProto)) )); } @@ -229,6 +268,7 @@ class Presenter { Presenter.VALUE_NA, UiDataEntryType.LayerRemoved, removedLayerId.toString(), + "", treeGenerator.generate("RemovedLayerId", ObjectFormatter.format(removedLayerId)) )); } @@ -242,6 +282,7 @@ class Presenter { Presenter.VALUE_NA, UiDataEntryType.DisplayAdded, displayStateProto.id.toString(), + displayStateProto.what, treeGenerator.generate("DisplayState", ObjectFormatter.format(displayStateProto)) )); } @@ -255,6 +296,7 @@ class Presenter { Presenter.VALUE_NA, UiDataEntryType.DisplayRemoved, removedDisplayId.toString(), + "", treeGenerator.generate("RemovedDisplayId", ObjectFormatter.format(removedDisplayId)) )); } @@ -268,6 +310,7 @@ class Presenter { Presenter.VALUE_NA, UiDataEntryType.LayerHandleRemoved, removedLayerHandleId.toString(), + "", treeGenerator.generate("RemovedLayerHandleId", ObjectFormatter.format(removedLayerHandleId)) )); } @@ -285,8 +328,8 @@ class Presenter { } } - private getUniqueUiDataEntryValues(entries: UiDataEntry[], getValue: (entry: UiDataEntry) => string): string[] { - const uniqueValues = new Set(); + private getUniqueUiDataEntryValues(entries: UiDataEntry[], getValue: (entry: UiDataEntry) => T): T[] { + const uniqueValues = new Set(); entries.forEach((entry: UiDataEntry) => { uniqueValues.add(getValue(entry)); }); @@ -321,16 +364,4 @@ class Presenter { return result; } - - private entry?: TransactionsTraceEntry; - private originalIndicesOfUiDataEntries: number[]; - private uiData: UiData; - private readonly notifyUiDataCallback: (data: UiData) => void; - private static readonly VALUE_NA = "N/A"; - private pidFilter: string[] = []; - private uidFilter: string[] = []; - private typeFilter: string[] = []; - private idFilter: string[] = []; } - -export {Presenter}; diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/ui_data.ts b/tools/winscope-ng/src/viewers/viewer_transactions/ui_data.ts index 4eb7919e9..56679c270 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/ui_data.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/ui_data.ts @@ -17,6 +17,7 @@ import {PropertiesTreeNode} from "viewers/common/ui_tree_utils"; class UiData { constructor( + public allVSyncIds: string[], public allPids: string[], public allUids: string[], public allTypes: string[], @@ -34,6 +35,7 @@ class UiData { [], [], [], + [], undefined, undefined, undefined, @@ -49,6 +51,7 @@ class UiDataEntry { public uid: string, public type: string, public id: string, + public what: string, public propertiesTree?: PropertiesTreeNode ) { } diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.spec.ts b/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.spec.ts index 31786b63d..789b595a7 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.spec.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.spec.ts @@ -71,6 +71,7 @@ describe("ViewerTransactionsComponent", () => { expect(entry!.innerHTML).toContain("UID_VALUE"); expect(entry!.innerHTML).toContain("TYPE_VALUE"); expect(entry!.innerHTML).toContain("ID_VALUE"); + expect(entry!.innerHTML).toContain("flag1 | flag2 | ..."); }); it("renders properties", () => { @@ -89,6 +90,7 @@ async function makeUiData(): Promise { "UID_VALUE", "TYPE_VALUE", "ID_VALUE", + "flag1 | flag2 | ...", propertiesTree); return new UiData( @@ -96,6 +98,7 @@ async function makeUiData(): Promise { [], [], [], + [], [entry], 0, 0, diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.ts b/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.ts index 2f5fe0d67..6e1444714 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.component.ts @@ -29,9 +29,17 @@ import {UiData} from "./ui_data";
-
- VSYNC ID +
+ + VSYNC ID + + + {{vsyncId}} + + +
@@ -70,8 +78,8 @@ import {UiData} from "./ui_data";
- - LAYER/DISPLAY ID + + LAYER/DISP ID
+
+ + Search text + + +
@@ -107,6 +122,9 @@ import {UiData} from "./ui_data";
{{entry.id}}
+
+ {{entry.what}} +
@@ -116,6 +134,7 @@ import {UiData} from "./ui_data";

Properties - Proto Dump

@@ -153,12 +172,44 @@ import {UiData} from "./ui_data"; .filters div { flex: 1; - margin-right: 8px; + padding: 4px; + } + + .filters .vsyncid mat-form-field { + width: 120px; + } + + .filters div.time { + flex: 2; + } + + .filters div.what { + flex: 3; + } + + .filters .id mat-form-field { + width: 150px; + } + + .filters .what { + margin-right: 16px; + } + + .filters .what mat-form-field { + width: 250px; } .entry div { flex: 1; - margin: 4px; + padding: 4px; + } + + .entry div.time { + flex: 2; + } + + .entry div.what { + flex: 3; } .entry.current-entry { @@ -174,10 +225,22 @@ import {UiData} from "./ui_data"; mat-form-field { width: 100px; } + + ::ng-deep .mat-select-panel-wrap { + overflow: scroll; + overflow-x: hidden; + max-height: 75vh; + } `, ] }) class ViewerTransactionsComponent { + public uiData: UiData = UiData.EMPTY; + public whatSearchString = ""; + + @ViewChild(CdkVirtualScrollViewport) private scrollComponent?: CdkVirtualScrollViewport; + private elementRef: ElementRef; + constructor(@Inject(ElementRef) elementRef: ElementRef) { this.elementRef = elementRef; } @@ -190,6 +253,10 @@ class ViewerTransactionsComponent { } } + public onVSyncIdFilterChanged(event: MatSelectChange) { + this.emitEvent(Events.VSyncIdFilterChanged, event.value); + } + public onPidFilterChanged(event: MatSelectChange) { this.emitEvent(Events.PidFilterChanged, event.value); } @@ -206,6 +273,10 @@ class ViewerTransactionsComponent { this.emitEvent(Events.IdFilterChanged, event.value); } + public onWhatSearchStringChange() { + this.emitEvent(Events.WhatSearchStringChanged, this.whatSearchString); + } + public onEntryClicked(index: number) { this.emitEvent(Events.EntryClicked, index); } @@ -227,11 +298,6 @@ class ViewerTransactionsComponent { }); this.elementRef.nativeElement.dispatchEvent(customEvent); } - - @ViewChild(CdkVirtualScrollViewport) scrollComponent?: CdkVirtualScrollViewport; - - public uiData: UiData = UiData.EMPTY; - private elementRef: ElementRef; } export {ViewerTransactionsComponent}; diff --git a/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.ts b/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.ts index 9586f14f0..ea85a32d4 100644 --- a/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.ts +++ b/tools/winscope-ng/src/viewers/viewer_transactions/viewer_transactions.ts @@ -27,6 +27,10 @@ class ViewerTransactions implements Viewer { (this.htmlElement as any).inputData = data; }); + this.htmlElement.addEventListener(Events.VSyncIdFilterChanged, (event) => { + this.presenter.onVSyncIdFilterChanged((event as CustomEvent).detail); + }); + this.htmlElement.addEventListener(Events.PidFilterChanged, (event) => { this.presenter.onPidFilterChanged((event as CustomEvent).detail); }); @@ -43,6 +47,10 @@ class ViewerTransactions implements Viewer { this.presenter.onIdFilterChanged((event as CustomEvent).detail); }); + this.htmlElement.addEventListener(Events.WhatSearchStringChanged, (event) => { + this.presenter.onWhatSearchStringChanged((event as CustomEvent).detail); + }); + this.htmlElement.addEventListener(Events.EntryClicked, (event) => { this.presenter.onEntryClicked((event as CustomEvent).detail); });