Add viewer ProtoLog
Fixes: 251159338 Test: npm run build:all && npm run test:all Change-Id: Ibd6774ed0bf2bc91a5d128eb8258ed4073279166
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { BrowserModule } from "@angular/platform-browser";
|
||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { MatCardModule } from "@angular/material/card";
|
||||
import { MatButtonModule } from "@angular/material/button";
|
||||
@@ -22,29 +23,31 @@ import { MatToolbarModule } from "@angular/material/toolbar";
|
||||
import { MatTabsModule } from "@angular/material/tabs";
|
||||
import { MatSnackBarModule } from "@angular/material/snack-bar";
|
||||
|
||||
import { AppComponent } from "./components/app.component";
|
||||
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { CollectTracesComponent } from "./components/collect_traces.component";
|
||||
import { AdbProxyComponent } from "./components/adb_proxy.component";
|
||||
import { WebAdbComponent } from "./components/web_adb.component";
|
||||
import { AppComponent } from "./components/app.component";
|
||||
import { CollectTracesComponent } from "./components/collect_traces.component";
|
||||
import { ParserErrorSnackBarComponent } from "./components/parser_error_snack_bar_component";
|
||||
import { TraceConfigComponent } from "./components/trace_config.component";
|
||||
import { UploadTracesComponent } from "./components/upload_traces.component";
|
||||
import { HierarchyComponent } from "viewers/components/hierarchy.component";
|
||||
import { PropertiesComponent } from "viewers/components/properties.component";
|
||||
import { RectsComponent } from "viewers/components/rects/rects.component";
|
||||
import { TraceViewComponent } from "./components/trace_view.component";
|
||||
import { UploadTracesComponent } from "./components/upload_traces.component";
|
||||
import { WebAdbComponent } from "./components/web_adb.component";
|
||||
|
||||
import { CoordinatesTableComponent } from "viewers/components/coordinates_table.component";
|
||||
import { HierarchyComponent } from "viewers/components/hierarchy.component";
|
||||
import { ImeAdditionalPropertiesComponent } from "viewers/components/ime_additional_properties.component";
|
||||
import { PropertiesComponent } from "viewers/components/properties.component";
|
||||
import { PropertiesTableComponent } from "viewers/components/properties_table.component";
|
||||
import { PropertyGroupsComponent } from "viewers/components/property_groups.component";
|
||||
import { RectsComponent } from "viewers/components/rects/rects.component";
|
||||
import { TransformMatrixComponent } from "viewers/components/transform_matrix.component";
|
||||
import { TreeComponent } from "viewers/components/tree.component";
|
||||
import { TreeNodeComponent } from "viewers/components/tree_node.component";
|
||||
import { TreeNodeDataViewComponent } from "viewers/components/tree_node_data_view.component";
|
||||
import { TreeNodePropertiesDataViewComponent } from "viewers/components/tree_node_properties_data_view.component";
|
||||
import { PropertyGroupsComponent } from "viewers/components/property_groups.component";
|
||||
import { TransformMatrixComponent } from "viewers/components/transform_matrix.component";
|
||||
import { ParserErrorSnackBarComponent } from "./components/parser_error_snack_bar_component";
|
||||
import { ViewerInputMethodComponent } from "viewers/components/viewer_input_method.component";
|
||||
import { PropertiesTableComponent } from "viewers/components/properties_table.component";
|
||||
import { ImeAdditionalPropertiesComponent } from "viewers/components/ime_additional_properties.component";
|
||||
import { CoordinatesTableComponent } from "viewers/components/coordinates_table.component";
|
||||
import { ViewerProtologComponent} from "viewers/viewer_protolog/viewer_protolog.component";
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -52,6 +55,7 @@ import { CoordinatesTableComponent } from "viewers/components/coordinates_table.
|
||||
ViewerWindowManagerComponent,
|
||||
ViewerSurfaceFlingerComponent,
|
||||
ViewerInputMethodComponent,
|
||||
ViewerProtologComponent,
|
||||
CollectTracesComponent,
|
||||
UploadTracesComponent,
|
||||
AdbProxyComponent,
|
||||
@@ -96,6 +100,7 @@ import { CoordinatesTableComponent } from "viewers/components/coordinates_table.
|
||||
MatToolbarModule,
|
||||
MatTabsModule,
|
||||
MatSnackBarModule,
|
||||
ScrollingModule,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
*/
|
||||
import { Component, Injector, Inject, ViewEncapsulation, Input } from "@angular/core";
|
||||
import { createCustomElement } from "@angular/elements";
|
||||
import { TraceCoordinator } from "../trace_coordinator";
|
||||
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { Timestamp } from "common/trace/timestamp";
|
||||
import { MatSliderChange } from "@angular/material/slider";
|
||||
import { TraceCoordinator } from "app/trace_coordinator";
|
||||
import { PersistentStore } from "common/persistent_store";
|
||||
import { Timestamp } from "common/trace/timestamp";
|
||||
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
|
||||
import { ViewerInputMethodComponent } from "viewers/components/viewer_input_method.component";
|
||||
import { ViewerProtologComponent} from "viewers/viewer_protolog/viewer_protolog.component";
|
||||
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
|
||||
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
@@ -133,17 +134,22 @@ export class AppComponent {
|
||||
@Inject(Injector) injector: Injector
|
||||
) {
|
||||
this.traceCoordinator = new TraceCoordinator();
|
||||
if (!customElements.get("viewer-window-manager")) {
|
||||
customElements.define("viewer-window-manager",
|
||||
createCustomElement(ViewerWindowManagerComponent, {injector}));
|
||||
|
||||
if (!customElements.get("viewer-input-method")) {
|
||||
customElements.define("viewer-input-method",
|
||||
createCustomElement(ViewerInputMethodComponent, {injector}));
|
||||
}
|
||||
if (!customElements.get("viewer-protolog")) {
|
||||
customElements.define("viewer-protolog",
|
||||
createCustomElement(ViewerProtologComponent, {injector}));
|
||||
}
|
||||
if (!customElements.get("viewer-surface-flinger")) {
|
||||
customElements.define("viewer-surface-flinger",
|
||||
createCustomElement(ViewerSurfaceFlingerComponent, {injector}));
|
||||
}
|
||||
if (!customElements.get("viewer-input-method")) {
|
||||
customElements.define("viewer-input-method",
|
||||
createCustomElement(ViewerInputMethodComponent, {injector}));
|
||||
if (!customElements.get("viewer-window-manager")) {
|
||||
customElements.define("viewer-window-manager",
|
||||
createCustomElement(ViewerWindowManagerComponent, {injector}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const SURFACE_FLINGER_ICON = "layers";
|
||||
const SCREEN_RECORDING_ICON = "videocam";
|
||||
const TRANSACTION_ICON = "show_chart";
|
||||
const WAYLAND_ICON = "filter_none";
|
||||
const PROTO_LOG_ICON = "text_ad";
|
||||
const PROTO_LOG_ICON = "notes";
|
||||
const SYSTEM_UI_ICON = "filter_none";
|
||||
const LAUNCHER_ICON = "filter_none";
|
||||
const IME_ICON = "keyboard_alt";
|
||||
@@ -54,7 +54,7 @@ export const TRACE_INFO: traceInfoMap = {
|
||||
icon: WAYLAND_ICON
|
||||
},
|
||||
[TraceType.PROTO_LOG]: {
|
||||
name: "Proto Log",
|
||||
name: "ProtoLog",
|
||||
icon: PROTO_LOG_ICON
|
||||
},
|
||||
[TraceType.SYSTEM_UI]: {
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
import {StringUtils} from "common/utils/string_utils";
|
||||
import configJson from "../../../../../../frameworks/base/data/etc/services.core.protolog.json";
|
||||
|
||||
class ProtoLogTraceEntry {
|
||||
constructor(public messages: LogMessage[], public currentMessageIndex: number) {
|
||||
}
|
||||
}
|
||||
|
||||
class LogMessage {
|
||||
text: string;
|
||||
time: string;
|
||||
@@ -133,4 +138,4 @@ function getParam<T>(arr: T[], idx: number): T {
|
||||
return arr[idx];
|
||||
}
|
||||
|
||||
export {FormattedLogMessage, LogMessage, UnformattedLogMessage};
|
||||
export {FormattedLogMessage, LogMessage, ProtoLogTraceEntry, UnformattedLogMessage};
|
||||
|
||||
@@ -65,6 +65,7 @@ abstract class Parser {
|
||||
return this.timestamps.get(type);
|
||||
}
|
||||
|
||||
//TODO: factor out timestamp search policy. Receive index parameter instead.
|
||||
public getTraceEntry(timestamp: Timestamp): undefined|any {
|
||||
const timestamps = this.getTimestamps(timestamp.getType());
|
||||
if (timestamps === undefined) {
|
||||
@@ -75,19 +76,13 @@ abstract class Parser {
|
||||
if (index === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return this.processDecodedEntry(this.decodedEntries[index]);
|
||||
}
|
||||
|
||||
public getTraceEntries(): any[] {
|
||||
throw new Error("Batch retrieval of trace entries not implemented for this parser!" +
|
||||
" Note that the usage of this functionality is discouraged," +
|
||||
" since creating all the trace entry objects may consume too much memory.");
|
||||
return this.processDecodedEntry(index, this.decodedEntries[index]);
|
||||
}
|
||||
|
||||
protected abstract getMagicNumber(): undefined|number[];
|
||||
protected abstract decodeTrace(trace: Uint8Array): any[];
|
||||
protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined|Timestamp;
|
||||
protected abstract processDecodedEntry(decodedEntry: any): any;
|
||||
protected abstract processDecodedEntry(index: number, decodedEntry: any): any;
|
||||
|
||||
protected trace: File;
|
||||
protected decodedEntries: any[] = [];
|
||||
|
||||
@@ -53,7 +53,7 @@ class ParserAccessibility extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: any): any {
|
||||
override processDecodedEntry(index: number, entryProto: any): any {
|
||||
return entryProto;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class ParserInputMethodClients extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: TraceTreeNode): TraceTreeNode {
|
||||
override processDecodedEntry(index: number, entryProto: TraceTreeNode): TraceTreeNode {
|
||||
return {
|
||||
name: StringUtils.nanosecondsToHuman(entryProto.elapsedRealtimeNanos ?? 0) + " - " + entryProto.where,
|
||||
kind: "InputMethodClient entry",
|
||||
|
||||
@@ -55,7 +55,7 @@ class ParserInputMethodManagerService extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected override processDecodedEntry(entryProto: TraceTreeNode): TraceTreeNode {
|
||||
protected override processDecodedEntry(index: number, entryProto: TraceTreeNode): TraceTreeNode {
|
||||
return {
|
||||
name: StringUtils.nanosecondsToHuman(entryProto.elapsedRealtimeNanos ?? 0) + " - " + entryProto.where,
|
||||
kind: "InputMethodManagerService entry",
|
||||
|
||||
@@ -56,7 +56,7 @@ class ParserInputMethodService extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: TraceTreeNode): TraceTreeNode {
|
||||
override processDecodedEntry(index: number, entryProto: TraceTreeNode): TraceTreeNode {
|
||||
return {
|
||||
name: StringUtils.nanosecondsToHuman(entryProto.elapsedRealtimeNanos ?? 0) + " - " + entryProto.where,
|
||||
kind: "InputMethodService entry",
|
||||
|
||||
@@ -69,22 +69,14 @@ describe("ParserProtoLog", () => {
|
||||
|
||||
it("reconstructs human-readable log message", () => {
|
||||
const timestamp = new Timestamp(TimestampType.ELAPSED, 850746266486n);
|
||||
const actualMessage = parser.getTraceEntry(timestamp)!;
|
||||
const entry = parser.getTraceEntry(timestamp)!;
|
||||
|
||||
expect(actualMessage).toBeInstanceOf(LogMessage);
|
||||
expect(Object.assign({}, actualMessage)).toEqual(expectedFirstLogMessage);
|
||||
});
|
||||
expect(entry.currentMessageIndex).toEqual(0);
|
||||
|
||||
it("allows retrieving all the log messages", () => {
|
||||
const actualMessages = parser.getTraceEntries();
|
||||
|
||||
expect(actualMessages.length).toEqual(50);
|
||||
|
||||
actualMessages.forEach(message => {
|
||||
expect(entry.messages.length).toEqual(50);
|
||||
expect(Object.assign({}, entry.messages[0])).toEqual(expectedFirstLogMessage);
|
||||
entry.messages.forEach((message: any) => {
|
||||
expect(message).toBeInstanceOf(LogMessage);
|
||||
});
|
||||
|
||||
const actualFirstLogMessage = Object.assign({}, actualMessages[0]);
|
||||
expect(actualFirstLogMessage).toEqual(expectedFirstLogMessage);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {FormattedLogMessage, LogMessage, UnformattedLogMessage} from "common/trace/protolog";
|
||||
import {FormattedLogMessage, LogMessage, ProtoLogTraceEntry, UnformattedLogMessage} from "common/trace/protolog";
|
||||
import {Timestamp, TimestampType} from "common/trace/timestamp";
|
||||
import {TraceType} from "common/trace/trace_type";
|
||||
import {Parser} from "./parser";
|
||||
@@ -67,7 +67,17 @@ class ParserProtoLog extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: any): LogMessage {
|
||||
override processDecodedEntry(index: number, entryProto: any): ProtoLogTraceEntry {
|
||||
if (!this.decodedMessages) {
|
||||
this.decodedMessages = this.decodedEntries.map((entryProto: any) => {
|
||||
return this.decodeProtoLogMessage(entryProto);
|
||||
});
|
||||
}
|
||||
|
||||
return new ProtoLogTraceEntry(this.decodedMessages, index);
|
||||
}
|
||||
|
||||
private decodeProtoLogMessage(entryProto: any): LogMessage {
|
||||
const message = (<any>configJson).messages[entryProto.messageHash];
|
||||
if (!message) {
|
||||
return new FormattedLogMessage(entryProto);
|
||||
@@ -84,12 +94,7 @@ class ParserProtoLog extends Parser {
|
||||
}
|
||||
}
|
||||
|
||||
override getTraceEntries(): LogMessage[] {
|
||||
return this.decodedEntries.map((entryProto: any) => {
|
||||
return this.processDecodedEntry(entryProto);
|
||||
});
|
||||
}
|
||||
|
||||
private decodedMessages?: LogMessage[];
|
||||
private realToElapsedTimeOffsetNs: undefined|bigint = undefined;
|
||||
private static readonly MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47]; // .PROTOLOG
|
||||
private static readonly PROTOLOG_VERSION = "1.0.0";
|
||||
|
||||
@@ -73,7 +73,7 @@ class ParserScreenRecording extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entry: ScreenRecordingMetadataEntry): ScreenRecordingTraceEntry {
|
||||
override processDecodedEntry(index: number, entry: ScreenRecordingMetadataEntry): ScreenRecordingTraceEntry {
|
||||
const initialTimestampNs = this.getTimestamps(TimestampType.ELAPSED)![0].getValueNs();
|
||||
const currentTimestampNs = entry.timestampMonotonicNs;
|
||||
const videoTimeSeconds = Number(currentTimestampNs - initialTimestampNs) / 1000000000;
|
||||
|
||||
@@ -45,7 +45,7 @@ class ParserScreenRecordingLegacy extends Parser {
|
||||
return decodedEntry;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entry: Timestamp): ScreenRecordingTraceEntry {
|
||||
override processDecodedEntry(index: number, entry: Timestamp): ScreenRecordingTraceEntry {
|
||||
const currentTimestamp = entry;
|
||||
const initialTimestamp = this.getTimestamps(TimestampType.ELAPSED)![0];
|
||||
const videoTimeSeconds =
|
||||
|
||||
@@ -59,7 +59,7 @@ class ParserSurfaceFlinger extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: any): LayerTraceEntry {
|
||||
override processDecodedEntry(index: number, entryProto: any): LayerTraceEntry {
|
||||
return LayerTraceEntry.fromProto(entryProto.layers.layers, entryProto.displays, entryProto.elapsedRealtimeNanos, entryProto.hwcBlob);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class ParserTransactions extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: any): any {
|
||||
override processDecodedEntry(index: number, entryProto: any): any {
|
||||
return entryProto;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class ParserWindowManager extends Parser {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: any): WindowManagerState {
|
||||
override processDecodedEntry(index: number, entryProto: any): WindowManagerState {
|
||||
return WindowManagerState.fromProto(entryProto.windowManagerService, entryProto.elapsedRealtimeNanos, entryProto.where);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class ParserWindowManagerDump extends Parser {
|
||||
return new Timestamp(TimestampType.ELAPSED, 0n);
|
||||
}
|
||||
|
||||
override processDecodedEntry(entryProto: any): WindowManagerState {
|
||||
override processDecodedEntry(index: number, entryProto: any): WindowManagerState {
|
||||
return WindowManagerState.fromProto(entryProto);
|
||||
}
|
||||
}
|
||||
|
||||
38
tools/winscope-ng/src/test/e2e/viewer_protolog.spec.ts
Normal file
38
tools/winscope-ng/src/test/e2e/viewer_protolog.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 {browser, element, by} from "protractor";
|
||||
import {E2eTestUtils} from "./utils";
|
||||
|
||||
describe("Viewer ProtoLog", () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get("file://" + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
|
||||
it("processes trace and renders view", async () => {
|
||||
const inputFile = element(by.css("input[type=\"file\"]"));
|
||||
await inputFile.sendKeys(E2eTestUtils.getFixturePath("traces/elapsed_and_real_timestamp/ProtoLog.pb"));
|
||||
|
||||
const loadData = element(by.css(".load-btn"));
|
||||
await loadData.click();
|
||||
|
||||
const isViewerRendered = await element(by.css("viewer-protolog")).isPresent();
|
||||
expect(isViewerRendered).toBeTruthy();
|
||||
|
||||
const isFirstMessageRendered = await element(by.css("viewer-protolog .scroll-messages .message")).isPresent();
|
||||
expect(isFirstMessageRendered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -15,19 +15,21 @@
|
||||
*/
|
||||
import { TraceType } from "common/trace/trace_type";
|
||||
import { Viewer } from "./viewer";
|
||||
import { ViewerWindowManager } from "./viewer_window_manager/viewer_window_manager";
|
||||
import { ViewerSurfaceFlinger } from "./viewer_surface_flinger/viewer_surface_flinger";
|
||||
import { ViewerInputMethodClients } from "./viewer_input_method_clients/viewer_input_method_clients";
|
||||
import { ViewerInputMethodService } from "./viewer_input_method_service/viewer_input_method_service";
|
||||
import { ViewerInputMethodManagerService } from "./viewer_input_method_manager_service/viewer_input_method_manager_service";
|
||||
import { ViewerProtoLog } from "./viewer_protolog/viewer_protolog";
|
||||
import { ViewerSurfaceFlinger } from "./viewer_surface_flinger/viewer_surface_flinger";
|
||||
import { ViewerWindowManager } from "./viewer_window_manager/viewer_window_manager";
|
||||
|
||||
class ViewerFactory {
|
||||
static readonly VIEWERS = [
|
||||
ViewerWindowManager,
|
||||
ViewerSurfaceFlinger,
|
||||
ViewerInputMethodClients,
|
||||
ViewerInputMethodService,
|
||||
ViewerInputMethodManagerService,
|
||||
ViewerInputMethodService,
|
||||
ViewerProtoLog,
|
||||
ViewerSurfaceFlinger,
|
||||
ViewerWindowManager,
|
||||
];
|
||||
|
||||
public createViewers(activeTraceTypes: Set<TraceType>): Viewer[] {
|
||||
|
||||
23
tools/winscope-ng/src/viewers/viewer_protolog/events.ts
Normal file
23
tools/winscope-ng/src/viewers/viewer_protolog/events.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
class Events {
|
||||
public static LogLevelsFilterChanged = "ViewerProtoLogEvent_LogLevelsFilterChanged";
|
||||
public static TagsFilterChanged = "ViewerProtoLogEvent_TagsFilterChanged";
|
||||
public static SourceFilesFilterChanged = "ViewerProtoLogEvent_SourceFilesFilterChanged";
|
||||
public static SearchStringFilterChanged = "ViewerProtoLogEvent_SearchStringFilterChanged";
|
||||
}
|
||||
|
||||
export {Events};
|
||||
151
tools/winscope-ng/src/viewers/viewer_protolog/presenter.spec.ts
Normal file
151
tools/winscope-ng/src/viewers/viewer_protolog/presenter.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 ANYf KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {LogMessage, ProtoLogTraceEntry} from "common/trace/protolog";
|
||||
import {TraceType} from "common/trace/trace_type";
|
||||
import {Presenter} from "./presenter";
|
||||
import {UiData} from "./ui_data";
|
||||
|
||||
describe("ViewerProtoLogPresenter", () => {
|
||||
let presenter: Presenter;
|
||||
let inputMessages: LogMessage[];
|
||||
let inputTraceEntries: Map<TraceType, any>;
|
||||
let outputUiData: undefined|UiData;
|
||||
|
||||
beforeEach(async () => {
|
||||
inputMessages = [
|
||||
new LogMessage("text0", "time", "tag0", "level0", "sourcefile0", 10),
|
||||
new LogMessage("text1", "time", "tag1", "level1", "sourcefile1", 10),
|
||||
new LogMessage("text2", "time", "tag2", "level2", "sourcefile2", 10),
|
||||
];
|
||||
inputTraceEntries = new Map<TraceType, any>();
|
||||
inputTraceEntries.set(TraceType.PROTO_LOG, [new ProtoLogTraceEntry(inputMessages, 0)]);
|
||||
|
||||
outputUiData = undefined;
|
||||
|
||||
presenter = new Presenter((data: UiData) => {
|
||||
outputUiData = data;
|
||||
});
|
||||
});
|
||||
|
||||
it("is robust to undefined trace entry", () => {
|
||||
presenter.notifyCurrentTraceEntries(new Map<TraceType, any>());
|
||||
expect(outputUiData!.messages).toEqual([]);
|
||||
expect(outputUiData!.currentMessageIndex).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores undefined trace entry and doesn't discard displayed messages", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.notifyCurrentTraceEntries(new Map<TraceType, any>());
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it("processes current trace entries", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
|
||||
expect(outputUiData!.allLogLevels).toEqual(["level0", "level1", "level2"]);
|
||||
expect(outputUiData!.allTags).toEqual(["tag0", "tag1", "tag2"]);
|
||||
expect(outputUiData!.allSourceFiles).toEqual(["sourcefile0", "sourcefile1", "sourcefile2"]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
});
|
||||
|
||||
it("updated displayed messages according to log levels filter", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(["level1"]);
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(["level0", "level1", "level2"]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it("updates displayed messages according to tags filter", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onTagsFilterChanged([]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onTagsFilterChanged(["tag1"]);
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
|
||||
presenter.onTagsFilterChanged(["tag0", "tag1", "tag2"]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it("updates displayed messages according to source files filter", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSourceFilesFilterChanged([]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSourceFilesFilterChanged(["sourcefile1"]);
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
|
||||
presenter.onSourceFilesFilterChanged(["sourcefile0", "sourcefile1", "sourcefile2"]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it("updates displayed messages according to search string filter", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSearchStringFilterChanged("");
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSearchStringFilterChanged("text");
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSearchStringFilterChanged("text0");
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[0]]);
|
||||
|
||||
presenter.onSearchStringFilterChanged("text1");
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
});
|
||||
|
||||
it("computes current message index", () => {
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(["level0"]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
|
||||
(<ProtoLogTraceEntry>inputTraceEntries.get(TraceType.PROTO_LOG)[0]).currentMessageIndex = 1;
|
||||
presenter.notifyCurrentTraceEntries(inputTraceEntries);
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(1);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(["level0"]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(["level1"]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(["level0", "level1"]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(1);
|
||||
});
|
||||
});
|
||||
139
tools/winscope-ng/src/viewers/viewer_protolog/presenter.ts
Normal file
139
tools/winscope-ng/src/viewers/viewer_protolog/presenter.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 {UiData} from "./ui_data";
|
||||
import {ArrayUtils} from "common/utils/array_utils";
|
||||
import {LogMessage, ProtoLogTraceEntry} from "common/trace/protolog";
|
||||
import {TraceType} from "common/trace/trace_type";
|
||||
|
||||
export class Presenter {
|
||||
constructor(
|
||||
notifyUiDataCallback: (data: UiData) => void) {
|
||||
this.notifyUiDataCallback = notifyUiDataCallback;
|
||||
this.originalIndicesOfFilteredOutputMessages = [];
|
||||
this.uiData = UiData.EMPTY;
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
//TODO: replace input with something like iterator/cursor (same for other viewers/presenters)
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
|
||||
this.entry = entries.get(TraceType.PROTO_LOG) ? entries.get(TraceType.PROTO_LOG)[0] : undefined;
|
||||
if (this.uiData === UiData.EMPTY) {
|
||||
this.computeUiDataMessages();
|
||||
}
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
public onLogLevelsFilterChanged(levels: string[]) {
|
||||
this.levels = levels;
|
||||
this.computeUiDataMessages();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
public onTagsFilterChanged(tags: string[]) {
|
||||
this.tags = tags;
|
||||
this.computeUiDataMessages();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
public onSourceFilesFilterChanged(files: string[]) {
|
||||
this.files = files;
|
||||
this.computeUiDataMessages();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
public onSearchStringFilterChanged(searchString: string) {
|
||||
this.searchString = searchString;
|
||||
this.computeUiDataMessages();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
private computeUiDataMessages() {
|
||||
if (!this.entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allLogLevels = this.getUniqueMessageValues(
|
||||
this.entry!.messages,
|
||||
(message: LogMessage) => message.level);
|
||||
const allTags = this.getUniqueMessageValues(
|
||||
this.entry!.messages,
|
||||
(message: LogMessage) => message.tag);
|
||||
const allSourceFiles = this.getUniqueMessageValues(
|
||||
this.entry!.messages,
|
||||
(message: LogMessage) => message.at);
|
||||
|
||||
let filteredMessagesAndOriginalIndex: [number, LogMessage][] = [...this.entry!.messages.entries()];
|
||||
|
||||
if (this.levels.length > 0) {
|
||||
filteredMessagesAndOriginalIndex =
|
||||
filteredMessagesAndOriginalIndex.filter(value => this.levels.includes(value[1].level));
|
||||
}
|
||||
|
||||
if (this.tags.length > 0) {
|
||||
filteredMessagesAndOriginalIndex =
|
||||
filteredMessagesAndOriginalIndex.filter(value => this.tags.includes(value[1].tag));
|
||||
}
|
||||
|
||||
if (this.files.length > 0) {
|
||||
filteredMessagesAndOriginalIndex =
|
||||
filteredMessagesAndOriginalIndex.filter(value => this.files.includes(value[1].at));
|
||||
}
|
||||
|
||||
filteredMessagesAndOriginalIndex =
|
||||
filteredMessagesAndOriginalIndex.filter(value => value[1].text.includes(this.searchString));
|
||||
|
||||
this.originalIndicesOfFilteredOutputMessages = filteredMessagesAndOriginalIndex.map(value => value[0]);
|
||||
const filteredMessages = filteredMessagesAndOriginalIndex.map(value => value[1]);
|
||||
|
||||
this.uiData = new UiData(allLogLevels, allTags, allSourceFiles, filteredMessages, 0);
|
||||
}
|
||||
|
||||
private computeUiDataCurrentMessageIndex() {
|
||||
if (!this.entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uiData.currentMessageIndex = ArrayUtils.binarySearchLowerOrEqual(
|
||||
this.originalIndicesOfFilteredOutputMessages,
|
||||
this.entry.currentMessageIndex
|
||||
);
|
||||
}
|
||||
|
||||
private getUniqueMessageValues(messages: LogMessage[], getValue: (message :LogMessage) => string): string[] {
|
||||
const uniqueValues = new Set<string>();
|
||||
messages.forEach(message => {
|
||||
uniqueValues.add(getValue(message));
|
||||
});
|
||||
const result = [...uniqueValues];
|
||||
result.sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
private entry?: ProtoLogTraceEntry;
|
||||
private originalIndicesOfFilteredOutputMessages: number[];
|
||||
private uiData: UiData;
|
||||
private readonly notifyUiDataCallback: (data: UiData) => void;
|
||||
|
||||
private tags: string[] = [];
|
||||
private files: string[] = [];
|
||||
private levels: string[] = [];
|
||||
private searchString = "";
|
||||
}
|
||||
30
tools/winscope-ng/src/viewers/viewer_protolog/ui_data.ts
Normal file
30
tools/winscope-ng/src/viewers/viewer_protolog/ui_data.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 {LogMessage} from "common/trace/protolog";
|
||||
|
||||
class UiData {
|
||||
constructor(
|
||||
public allLogLevels: string[],
|
||||
public allTags: string[],
|
||||
public allSourceFiles: string[],
|
||||
public messages: LogMessage[],
|
||||
public currentMessageIndex: undefined|number) {
|
||||
}
|
||||
|
||||
public static EMPTY = new UiData([], [], [], [], undefined);
|
||||
}
|
||||
|
||||
export {UiData};
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 {ScrollingModule} from "@angular/cdk/scrolling";
|
||||
import {ComponentFixture, ComponentFixtureAutoDetect, TestBed} from "@angular/core/testing";
|
||||
import {ViewerProtologComponent} from "./viewer_protolog.component";
|
||||
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
|
||||
describe("ViewerProtologComponent", () => {
|
||||
let fixture: ComponentFixture<ViewerProtologComponent>;
|
||||
let component: ViewerProtologComponent;
|
||||
let htmlElement: HTMLElement;
|
||||
|
||||
beforeAll(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ComponentFixtureAutoDetect, useValue: true }
|
||||
],
|
||||
imports: [
|
||||
ScrollingModule
|
||||
],
|
||||
declarations: [
|
||||
ViewerProtologComponent,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ViewerProtologComponent);
|
||||
component = fixture.componentInstance;
|
||||
htmlElement = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it("can be created", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("creates message filters", () => {
|
||||
expect(htmlElement.querySelector(".filters .log-level")).toBeTruthy();
|
||||
expect(htmlElement.querySelector(".filters .tag")).toBeTruthy();
|
||||
expect(htmlElement.querySelector(".filters .source-file")).toBeTruthy();
|
||||
expect(htmlElement.querySelector(".filters .text")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders log messages", () => {
|
||||
expect(htmlElement.querySelector(".scroll-messages")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";
|
||||
import {
|
||||
Component, ElementRef, Inject, Input, ViewChild
|
||||
} from "@angular/core";
|
||||
import {MatSelectChange} from "@angular/material/select";
|
||||
import {Events} from "./events";
|
||||
import {UiData} from "./ui_data";
|
||||
|
||||
@Component({
|
||||
selector: "viewer-protolog",
|
||||
template: `
|
||||
<div class="card-grid container">
|
||||
<div class="filters">
|
||||
<div class="log-level">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Log level</mat-label>
|
||||
<mat-select (selectionChange)="onLogLevelsChange($event)" multiple>
|
||||
<mat-option *ngFor="let level of uiData.allLogLevels"
|
||||
[value]="level">
|
||||
{{level}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="tag">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Tags</mat-label>
|
||||
<mat-select (selectionChange)="onTagsChange($event)" multiple>
|
||||
<mat-option *ngFor="let tag of uiData.allTags"
|
||||
[value]="tag">
|
||||
{{tag}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="source-file">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Source files</mat-label>
|
||||
<mat-select (selectionChange)="onSourceFilesChange($event)" multiple>
|
||||
<mat-option *ngFor="let file of uiData.allSourceFiles"
|
||||
[value]="file">
|
||||
{{file}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="text">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Search text</mat-label>
|
||||
<input matInput [(ngModel)]="searchString" (input)="onSearchStringChange()">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<cdk-virtual-scroll-viewport itemSize="16" class="scroll-messages">
|
||||
<div *cdkVirtualFor="let message of uiData.messages; let i = index;" class="message" [class.current-message]="isCurrentMessage(i)">
|
||||
<div class="time"><span class="mat-body-1">{{message.time}}</span></div>
|
||||
<div class="log-level"><span class="mat-body-1">{{message.level}}</span></div>
|
||||
<div class="tag"><span class="mat-body-1">{{message.tag}}</span></div>
|
||||
<div class="source-file"><span class="mat-body-1">{{message.at}}</span></div>
|
||||
<div class="text"><span class="mat-body-1">{{message.text}}</span></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.container {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.scroll-messages {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.message.current-message {
|
||||
background-color: #365179;color: white;
|
||||
}
|
||||
|
||||
.time {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filters .log-level {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.tag {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.source-file {
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 10;
|
||||
}
|
||||
|
||||
.filters div {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.message div {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
]
|
||||
})
|
||||
export class ViewerProtologComponent {
|
||||
constructor(@Inject(ElementRef) elementRef: ElementRef) {
|
||||
this.elementRef = elementRef;
|
||||
}
|
||||
|
||||
@Input()
|
||||
public set inputData(data: UiData) {
|
||||
this.uiData = data;
|
||||
if (this.uiData.currentMessageIndex !== undefined && this.scrollComponent) {
|
||||
this.scrollComponent.scrollToIndex(this.uiData.currentMessageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public onLogLevelsChange(event: MatSelectChange) {
|
||||
this.emitEvent(Events.LogLevelsFilterChanged, event.value);
|
||||
}
|
||||
|
||||
public onTagsChange(event: MatSelectChange) {
|
||||
this.emitEvent(Events.TagsFilterChanged, event.value);
|
||||
}
|
||||
|
||||
public onSourceFilesChange(event: MatSelectChange) {
|
||||
this.emitEvent(Events.SourceFilesFilterChanged, event.value);
|
||||
}
|
||||
|
||||
public onSearchStringChange() {
|
||||
this.emitEvent(Events.SearchStringFilterChanged, this.searchString);
|
||||
}
|
||||
|
||||
public isCurrentMessage(index: number): boolean {
|
||||
return index === this.uiData.currentMessageIndex;
|
||||
}
|
||||
|
||||
private emitEvent(event: string, data: any) {
|
||||
const customEvent = new CustomEvent(
|
||||
event,
|
||||
{
|
||||
bubbles: true,
|
||||
detail: data
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
@ViewChild(CdkVirtualScrollViewport) scrollComponent!: CdkVirtualScrollViewport;
|
||||
|
||||
public uiData: UiData = UiData.EMPTY;
|
||||
private searchString = "";
|
||||
private elementRef: ElementRef;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 {TraceType} from "common/trace/trace_type";
|
||||
import {Viewer} from "viewers/viewer";
|
||||
import {Presenter} from "./presenter";
|
||||
import {Events} from "./events";
|
||||
import {UiData} from "./ui_data";
|
||||
|
||||
class ViewerProtoLog implements Viewer {
|
||||
constructor() {
|
||||
this.view = document.createElement("viewer-protolog");
|
||||
|
||||
this.presenter = new Presenter((data: UiData) => {
|
||||
(this.view as any).inputData = data;
|
||||
});
|
||||
|
||||
this.view.addEventListener(Events.LogLevelsFilterChanged, (event) => {
|
||||
return this.presenter.onLogLevelsFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
this.view.addEventListener(Events.TagsFilterChanged, (event) => {
|
||||
return this.presenter.onTagsFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
this.view.addEventListener(Events.SourceFilesFilterChanged, (event) => {
|
||||
return this.presenter.onSourceFilesFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
this.view.addEventListener(Events.SearchStringFilterChanged, (event) => {
|
||||
return this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
}
|
||||
|
||||
public notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
|
||||
this.presenter.notifyCurrentTraceEntries(entries);
|
||||
}
|
||||
|
||||
public getView(): HTMLElement {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
public getTitle() {
|
||||
return "ProtoLog";
|
||||
}
|
||||
|
||||
public getDependencies(): TraceType[] {
|
||||
return ViewerProtoLog.DEPENDENCIES;
|
||||
}
|
||||
|
||||
public static readonly DEPENDENCIES: TraceType[] = [TraceType.PROTO_LOG];
|
||||
private view: HTMLElement;
|
||||
private presenter: Presenter;
|
||||
}
|
||||
|
||||
export {ViewerProtoLog};
|
||||
Reference in New Issue
Block a user