Convert protolog legacy to perfetto.
Bug: 411363817
Test: npm run test:unit:ci
Change-Id: I8fe15affa5dd162c4e22f63700f113bc71cad600
diff --git a/tools/winscope/src/parsers/legacy/abstract_parser.ts b/tools/winscope/src/parsers/legacy/abstract_parser.ts
index c2ecc81..45a6658 100644
--- a/tools/winscope/src/parsers/legacy/abstract_parser.ts
+++ b/tools/winscope/src/parsers/legacy/abstract_parser.ts
@@ -97,7 +97,11 @@
throw new Error('Not implemented');
}
- convertToPerfettoPackets(sequenceId: number): perfetto.protos.TracePacket[] {
+ convertToPerfettoPackets(
+ sequenceId: number,
+ trustedPid: number,
+ trustedUid: number,
+ ): perfetto.protos.TracePacket[] {
throw new Error('not implemented');
}
diff --git a/tools/winscope/src/parsers/legacy_to_perfetto_converter.ts b/tools/winscope/src/parsers/legacy_to_perfetto_converter.ts
index c112fe8..91f9b4f 100644
--- a/tools/winscope/src/parsers/legacy_to_perfetto_converter.ts
+++ b/tools/winscope/src/parsers/legacy_to_perfetto_converter.ts
@@ -209,6 +209,17 @@
legacyParsers: Array<Parser<object>>,
trace: perfetto.protos.Trace,
): perfetto.protos.TracePacket[] {
+ const [largestUid, largestPid] = trace.packet.reduce(
+ ([uid, pid], packet) => {
+ return [
+ Math.max(packet.trustedUid ?? 0, uid),
+ Math.max(packet.trustedPid ?? 0, pid),
+ ];
+ },
+ [0, 0],
+ );
+ const [trustedUid, trustedPid] = [largestUid + 1, largestPid + 1];
+
const packets: perfetto.protos.TracePacket[] = [];
let sequenceId =
Math.max(
@@ -217,7 +228,11 @@
for (const parser of legacyParsers) {
if (parser.convertToPerfettoPackets) {
try {
- const legacyPackets = parser.convertToPerfettoPackets(sequenceId);
+ const legacyPackets = parser.convertToPerfettoPackets(
+ sequenceId,
+ trustedUid,
+ trustedPid,
+ );
if (legacyPackets.length > 0) {
legacyPackets[0].firstPacketOnSequence = true;
packets.push(...legacyPackets);
diff --git a/tools/winscope/src/parsers/protolog/legacy/legacy_to_perfetto_configs.ts b/tools/winscope/src/parsers/protolog/legacy/legacy_to_perfetto_configs.ts
new file mode 100644
index 0000000..99f2510
--- /dev/null
+++ b/tools/winscope/src/parsers/protolog/legacy/legacy_to_perfetto_configs.ts
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 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 Long from 'long';
+import {perfetto} from 'protos/perfetto/trace/static';
+import configJson32 from '../../../../configs/services.core.protolog32.json'; // eslint-disable-line no-restricted-imports
+import configJson64 from '../../../../configs/services.core.protolog64.json'; // eslint-disable-line no-restricted-imports
+
+interface LegacyConfig {
+ groups: {[key: string]: {tag: string}};
+ messages: {
+ [key: string]: {
+ message: string;
+ level: string;
+ group: string;
+ at: string;
+ };
+ };
+}
+
+function makeProtologViewerConfig(
+ configJson: LegacyConfig,
+): perfetto.protos.ProtoLogViewerConfig {
+ const groupNameToId = new Map<string, number>();
+
+ const groups: perfetto.protos.ProtoLogViewerConfig.Group[] = Object.entries(
+ configJson.groups,
+ ).map(([name, {tag}], index) => {
+ const group = perfetto.protos.ProtoLogViewerConfig.Group.fromObject({
+ id: index + 1,
+ name,
+ tag,
+ });
+ groupNameToId.set(group.name, group.id);
+ return group;
+ });
+
+ const messages: perfetto.protos.ProtoLogViewerConfig.MessageData[] =
+ Object.entries(configJson.messages).map(
+ ([id, {message, level, group, at}]) => {
+ let protologLevel: perfetto.protos.ProtoLogLevel;
+ switch (level) {
+ case 'DEBUG':
+ protologLevel = perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG;
+ break;
+ case 'VERBOSE':
+ protologLevel =
+ perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE;
+ break;
+ case 'INFO':
+ protologLevel = perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_INFO;
+ break;
+ case 'WARN':
+ protologLevel = perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_WARN;
+ break;
+ case 'ERROR':
+ protologLevel = perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_ERROR;
+ break;
+ case 'WTF':
+ protologLevel = perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_WTF;
+ break;
+ default:
+ protologLevel =
+ perfetto.protos.ProtoLogLevel.PROTOLOG_LEVEL_UNDEFINED;
+ }
+ return perfetto.protos.ProtoLogViewerConfig.MessageData.fromObject({
+ messageId: Long.fromString(id),
+ message,
+ level: protologLevel,
+ groupId: groupNameToId.get(group),
+ location: at,
+ });
+ },
+ );
+ return perfetto.protos.ProtoLogViewerConfig.fromObject({
+ messages,
+ groups,
+ });
+}
+
+export const CONFIG_32 = makeProtologViewerConfig(configJson32);
+export const CONFIG_64 = makeProtologViewerConfig(configJson64);
diff --git a/tools/winscope/src/parsers/protolog/legacy/parser_protolog.ts b/tools/winscope/src/parsers/protolog/legacy/parser_protolog.ts
index fe35b62..9790dd6 100644
--- a/tools/winscope/src/parsers/protolog/legacy/parser_protolog.ts
+++ b/tools/winscope/src/parsers/protolog/legacy/parser_protolog.ts
@@ -15,20 +15,25 @@
*/
import {assertDefined} from 'common/assert_utils';
+import {utf8Encode} from 'common/string_utils';
import {Timestamp} from 'common/time/time';
+import Long from 'long';
import {AbstractParser} from 'parsers/legacy/abstract_parser';
-import {LogMessage} from 'parsers/protolog/log_message';
-import {ParserProtologUtils} from 'parsers/protolog/parser_protolog_utils';
+import {perfetto} from 'protos/perfetto/trace/static';
import root from 'protos/protolog/udc/json';
import {com} from 'protos/protolog/udc/static';
import {TraceType} from 'trace/trace_type';
import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
import configJson32 from '../../../../configs/services.core.protolog32.json'; // eslint-disable-line no-restricted-imports
import configJson64 from '../../../../configs/services.core.protolog64.json'; // eslint-disable-line no-restricted-imports
+import {CONFIG_32, CONFIG_64} from './legacy_to_perfetto_configs';
type ProtoLogMessage = com.android.internal.protolog.IProtoLogMessage;
-class ParserProtoLog extends AbstractParser<PropertyTreeNode, ProtoLogMessage> {
+export class ParserProtoLog extends AbstractParser<
+ PropertyTreeNode,
+ ProtoLogMessage
+> {
private static readonly ProtoLogFileProto = root.lookupType(
'com.android.internal.protolog.ProtoLogFileProto',
);
@@ -61,15 +66,15 @@
buffer,
) as com.android.internal.protolog.IProtoLogFileProto;
- if (fileProto.version === ParserProtoLog.PROTOLOG_32_BIT_VERSION) {
+ if (this.is32BitVersion(fileProto.log?.at(0))) {
if (configJson32.version !== ParserProtoLog.PROTOLOG_32_BIT_VERSION) {
- const message = `Unsupported ProtoLog JSON config version ${configJson32.version} expected ${ParserProtoLog.PROTOLOG_32_BIT_VERSION}`;
+ const message = `Unsupported ProtoLog JSON config version ${configJson32.version}. Expected ${ParserProtoLog.PROTOLOG_32_BIT_VERSION}`;
console.log(message);
throw new TypeError(message);
}
- } else if (fileProto.version === ParserProtoLog.PROTOLOG_64_BIT_VERSION) {
+ } else if (this.is64BitVersion(fileProto.log?.at(0))) {
if (configJson64.version !== ParserProtoLog.PROTOLOG_64_BIT_VERSION) {
- const message = `Unsupported ProtoLog JSON config version ${configJson64.version} expected ${ParserProtoLog.PROTOLOG_64_BIT_VERSION}`;
+ const message = `Unsupported ProtoLog JSON config version ${configJson64.version}. Expected ${ParserProtoLog.PROTOLOG_64_BIT_VERSION}`;
console.log(message);
throw new TypeError(message);
}
@@ -95,185 +100,124 @@
return fileProto.log;
}
+ private is32BitVersion(entry: ProtoLogMessage | undefined): boolean {
+ return (entry?.messageHashLegacy ?? 0) > 0;
+ }
+
+ private is64BitVersion(entry: ProtoLogMessage | undefined): boolean {
+ return (
+ entry?.messageHash instanceof Long &&
+ (entry.messageHash.toString() ?? '0') !== '0'
+ );
+ }
+
+ override convertToPerfettoPackets(
+ sequenceId: number,
+ trustedUid = 1,
+ trustedPid = 1,
+ ): perfetto.protos.TracePacket[] {
+ const packets = [];
+ const firstPacket = this.createPacket(sequenceId, trustedUid, trustedPid);
+ firstPacket.sequenceFlags =
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_INCREMENTAL_STATE_CLEARED;
+ packets.push(firstPacket);
+ packets.push(this.makeViewerConfigPacket(sequenceId, trustedUid));
+
+ const stringToIid = new Map<string, number>();
+ let stringIid = 1;
+
+ for (const entry of this.decodedEntries) {
+ const packet = this.createPacket(sequenceId, trustedUid, trustedPid);
+ packet.timestamp = assertDefined(entry.elapsedRealtimeNanos);
+
+ let messageId: Long;
+ if (this.is64BitVersion(entry)) {
+ messageId = assertDefined(entry.messageHash);
+ } else {
+ messageId = Long.fromNumber(assertDefined(entry.messageHashLegacy));
+ }
+
+ const strParamIids: number[] = [];
+
+ entry.strParams?.forEach((param) => {
+ const iid = stringToIid.get(param);
+ if (iid !== undefined) {
+ strParamIids.push(iid);
+ } else {
+ stringToIid.set(param, stringIid);
+ const packet = this.createPacket(sequenceId, trustedUid, trustedPid);
+ this.updateInternedDataPacket(packet, param, stringIid);
+ packets.push(packet);
+ strParamIids.push(stringIid);
+ stringIid++;
+ }
+ });
+
+ if (strParamIids.length > 0) {
+ packet.sequenceFlags =
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_NEEDS_INCREMENTAL_STATE;
+ }
+
+ packet.protologMessage = perfetto.protos.ProtoLogMessage.create({
+ messageId,
+ strParamIids,
+ sint64Params: entry.sint64Params,
+ doubleParams: entry.doubleParams,
+ booleanParams: entry.booleanParams?.map((param) => {
+ return param ? 1 : 0;
+ }),
+ });
+ packets.push(packet);
+ }
+
+ return packets;
+ }
+
protected override getTimestamp(entry: ProtoLogMessage): Timestamp {
return this.timestampConverter.makeTimestampFromBootTimeNs(
BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()),
);
}
- override processDecodedEntry(
- index: number,
- entry: ProtoLogMessage,
- ): PropertyTreeNode {
- let messageHash = assertDefined(entry.messageHash).toString();
- let config: ProtologConfig | undefined = undefined;
- if (messageHash !== null && messageHash !== '0') {
- config = assertDefined(configJson64) as ProtologConfig;
+ private makeViewerConfigPacket(
+ sequenceId: number,
+ trustedUid: number,
+ ): perfetto.protos.TracePacket {
+ const packet = this.createPacket(sequenceId, trustedUid, undefined);
+ if (this.is64BitVersion(this.decodedEntries[0])) {
+ packet.protologViewerConfig = CONFIG_64;
} else {
- messageHash = assertDefined(entry.messageHashLegacy).toString();
- config = assertDefined(configJson32) as ProtologConfig;
+ packet.protologViewerConfig = CONFIG_32;
}
-
- const message: ConfigMessage | undefined = config.messages[messageHash];
- const tag: string | undefined = message
- ? config.groups[message.group].tag
- : undefined;
-
- const logMessage = this.makeLogMessage(entry, message, tag);
- return ParserProtologUtils.makeMessagePropertiesTree(
- logMessage,
- this.timestampConverter,
- this.getRealToMonotonicTimeOffsetNs() !== undefined,
- );
+ return packet;
}
- private makeLogMessage(
- entry: ProtoLogMessage,
- message: ConfigMessage | undefined,
- tag: string | undefined,
- ): LogMessage {
- if (!message || !tag) {
- return this.makeLogMessageWithoutFormat(entry);
- }
- try {
- return this.makeLogMessageWithFormat(entry, message, tag);
- } catch (error) {
- if (error instanceof FormatStringMismatchError) {
- return this.makeLogMessageWithoutFormat(entry);
- }
- throw this.createParsingError((error as Error).message);
- }
+ private updateInternedDataPacket(
+ packet: perfetto.protos.TracePacket,
+ str: string,
+ iid: number,
+ ): perfetto.protos.TracePacket {
+ const internedString = perfetto.protos.InternedString.fromObject({
+ iid: Long.fromNumber(iid),
+ str: utf8Encode(str),
+ });
+ packet.internedData = perfetto.protos.InternedData.fromObject({
+ protologStringArgs: [internedString],
+ });
+ return packet;
}
- private makeLogMessageWithFormat(
- entry: ProtoLogMessage,
- message: ConfigMessage,
- tag: string,
- ): LogMessage {
- let text = '';
-
- const strParams: string[] = assertDefined(entry.strParams);
- let strParamsIdx = 0;
- const sint64Params: Array<bigint> = assertDefined(entry.sint64Params).map(
- (param) => BigInt(param.toString()),
- );
- let sint64ParamsIdx = 0;
- const doubleParams: number[] = assertDefined(entry.doubleParams);
- let doubleParamsIdx = 0;
- const booleanParams: boolean[] = assertDefined(entry.booleanParams);
- let booleanParamsIdx = 0;
-
- const messageFormat = message.message;
- for (let i = 0; i < messageFormat.length; ) {
- if (messageFormat[i] === '%') {
- if (i + 1 >= messageFormat.length) {
- // Should never happen - protologtool checks for that
- throw this.createParsingError('invalid format string');
- }
- switch (messageFormat[i + 1]) {
- case '%':
- text += '%';
- break;
- case 'd':
- text += this.getParam(sint64Params, sint64ParamsIdx++).toString(10);
- break;
- case 'o':
- text += this.getParam(sint64Params, sint64ParamsIdx++).toString(8);
- break;
- case 'x':
- text += this.getParam(sint64Params, sint64ParamsIdx++).toString(16);
- break;
- case 'f':
- text += this.getParam(doubleParams, doubleParamsIdx++).toFixed(6);
- break;
- case 'e':
- text += this.getParam(
- doubleParams,
- doubleParamsIdx++,
- ).toExponential();
- break;
- case 'g':
- text += this.getParam(doubleParams, doubleParamsIdx++).toString();
- break;
- case 's':
- text += this.getParam(strParams, strParamsIdx++);
- break;
- case 'b':
- text += this.getParam(booleanParams, booleanParamsIdx++).toString();
- break;
- default:
- // Should never happen - protologtool checks for that
- throw this.createParsingError(
- 'invalid format string conversion: ' + messageFormat[i + 1],
- );
- }
- i += 2;
- } else {
- text += messageFormat[i];
- i += 1;
- }
+ private createPacket(
+ sequenceId: number,
+ trustedUid: number,
+ trustedPid: number | undefined,
+ ): perfetto.protos.TracePacket {
+ const packet = perfetto.protos.TracePacket.create();
+ packet.trustedPacketSequenceId = sequenceId;
+ packet.trustedUid = trustedUid;
+ if (trustedPid) {
+ packet.trustedPid = trustedPid;
}
-
- return {
- text,
- tag,
- level: message.level,
- at: message.at,
- timestamp: BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()),
- };
- }
-
- private getParam<T>(arr: T[], idx: number): T {
- if (arr.length <= idx) {
- throw this.createParsingError('no param for format string conversion');
- }
- return arr[idx];
- }
-
- private makeLogMessageWithoutFormat(entry: ProtoLogMessage): LogMessage {
- const text =
- assertDefined(entry.messageHash).toString() +
- ' - [' +
- assertDefined(entry.strParams).toString() +
- '] [' +
- assertDefined(entry.sint64Params).toString() +
- '] [' +
- assertDefined(entry.doubleParams).toString() +
- '] [' +
- assertDefined(entry.booleanParams).toString() +
- ']';
-
- return {
- text,
- tag: 'INVALID',
- level: 'invalid',
- at: '',
- timestamp: BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()),
- };
- }
-
- private createParsingError(msg: string) {
- return new Error(`Protolog parsing error: ${msg}`);
+ return packet;
}
}
-
-class FormatStringMismatchError extends Error {
- constructor(message: string) {
- super(message);
- }
-}
-
-interface ProtologConfig {
- version: string;
- messages: {[key: string]: ConfigMessage};
- groups: {[key: string]: {tag: string}};
-}
-
-interface ConfigMessage {
- message: string;
- level: string;
- group: string;
- at: string;
-}
-
-export {ParserProtoLog};
diff --git a/tools/winscope/src/parsers/protolog/legacy/parser_protolog_test.ts b/tools/winscope/src/parsers/protolog/legacy/parser_protolog_test.ts
index a91f9f5..b3f4bea 100644
--- a/tools/winscope/src/parsers/protolog/legacy/parser_protolog_test.ts
+++ b/tools/winscope/src/parsers/protolog/legacy/parser_protolog_test.ts
@@ -15,139 +15,382 @@
*/
import {assertDefined} from 'common/assert_utils';
+import {utf8Encode} from 'common/string_utils';
import {
TimestampConverterUtils,
timestampEqualityTester,
} from 'common/time/test_utils';
import {Timestamp} from 'common/time/time';
+import Long from 'long';
+import {perfetto} from 'protos/perfetto/trace/static';
import {LegacyParserProvider} from 'test/unit/fixture_utils';
import {CoarseVersion} from 'trace/coarse_version';
import {Parser} from 'trace/parser';
import {TraceType} from 'trace/trace_type';
import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {CONFIG_32, CONFIG_64} from './legacy_to_perfetto_configs';
-interface ExpectedMessage {
- 'message': string;
- 'ts': string;
- 'at': string;
- 'level': string;
- 'tag': string;
+interface ExpectedInternedData {
+ packetIndex: number;
+ str: string;
+ iid: number;
}
-const genProtoLogTest =
- (
- traceFile: string,
- timestampCount: number,
- first3ExpectedRealTimestamps: Timestamp[],
- expectedFirstMessage: ExpectedMessage,
- ) =>
- () => {
- let parser: Parser<PropertyTreeNode>;
+interface ExpectedMessagePacket {
+ packetIndex: number;
+ sequenceFlags: perfetto.protos.TracePacket.SequenceFlags;
+ timestamp: Long;
+ messageId: Long;
+ strParamIids: number[];
+ sint64Params: Long[];
+ doubleParams: number[];
+ booleanParams: number[];
+}
- beforeAll(async () => {
- jasmine.addCustomEqualityTester(timestampEqualityTester);
- parser = await new LegacyParserProvider()
- .addFilename(traceFile)
- .getParser<PropertyTreeNode>();
+interface ExpectedMessage {
+ message: string;
+ ts: string;
+ at: string;
+ level: string;
+ tag: string;
+}
+
+abstract class ParserProtologTest {
+ abstract readonly traceFile: string;
+ abstract readonly timestampCount: number;
+ abstract readonly first3ExpectedRealTimestamps: Timestamp[];
+ abstract readonly expectedConfig: perfetto.protos.IProtoLogViewerConfig;
+ abstract readonly internedData1: ExpectedInternedData;
+ abstract readonly internedData2: ExpectedInternedData;
+ abstract readonly messagePacketWithInternedStrings: ExpectedMessagePacket;
+ abstract readonly messagePacketNoInternedStrings: ExpectedMessagePacket;
+ abstract readonly expectedFirstMessage: ExpectedMessage;
+
+ execute() {
+ describe('ParserProtologTest', () => {
+ const [sequenceId, trustedUid, trustedPid] = [10, 3, 5];
+ let parser: Parser<PropertyTreeNode>;
+
+ beforeAll(async () => {
+ jasmine.addCustomEqualityTester(timestampEqualityTester);
+ parser = await new LegacyParserProvider()
+ .addFilename(this.traceFile)
+ .getParser<PropertyTreeNode>();
+ });
+
+ it('has expected trace type', () => {
+ expect(parser.getTraceType()).toEqual(TraceType.PROTO_LOG);
+ });
+
+ it('has expected coarse version', () => {
+ expect(parser.getCoarseVersion()).toEqual(CoarseVersion.LEGACY);
+ });
+
+ it('has expected length', () => {
+ expect(parser.getLengthEntries()).toEqual(this.timestampCount);
+ });
+
+ it('provides timestamps', () => {
+ const timestamps = assertDefined(parser.getTimestamps());
+ expect(timestamps.length).toEqual(this.timestampCount);
+
+ expect(timestamps.slice(0, 3)).toEqual(
+ this.first3ExpectedRealTimestamps,
+ );
+ });
+
+ it('does not provide entry', () => {
+ expect(parser.getEntry).toThrow();
+ });
+
+ it('converts to valid perfetto packets', async () => {
+ const packets = parser.convertToPerfettoPackets!(
+ sequenceId,
+ trustedUid,
+ trustedPid,
+ );
+ expect(
+ packets.filter((packet) => packet.protologMessage).length,
+ ).toEqual(this.timestampCount);
+
+ const firstPacket = packets[0];
+ expect(firstPacket.trustedPacketSequenceId).toEqual(sequenceId);
+ expect(firstPacket.sequenceFlags).toEqual(
+ perfetto.protos.TracePacket.SequenceFlags
+ .SEQ_INCREMENTAL_STATE_CLEARED,
+ );
+ expect(firstPacket.trustedUid).toEqual(trustedUid);
+ expect(firstPacket.trustedPid).toEqual(trustedPid);
+ expect(firstPacket.internedData).toBeNull();
+ expect(firstPacket.protologViewerConfig).toBeNull();
+ expect(firstPacket.protologMessage).toBeNull();
+
+ const viewerConfigPacket = packets[1];
+ expect(viewerConfigPacket.trustedPacketSequenceId).toEqual(sequenceId);
+ expect(viewerConfigPacket.sequenceFlags).toEqual(
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_UNSPECIFIED,
+ );
+ expect(viewerConfigPacket.protologViewerConfig).toEqual(
+ this.expectedConfig,
+ );
+ expect(viewerConfigPacket.trustedUid).toEqual(trustedUid);
+ expect(viewerConfigPacket.trustedPid).toEqual(0);
+ expect(viewerConfigPacket.internedData).toBeNull();
+ expect(viewerConfigPacket.protologMessage).toBeNull();
+
+ checkInternedDataPacket(packets, this.internedData1);
+ checkInternedDataPacket(packets, this.internedData2);
+
+ checkMessagePacket(packets, this.messagePacketNoInternedStrings);
+ checkMessagePacket(packets, this.messagePacketWithInternedStrings);
+ });
+
+ function checkMessagePacket(
+ packets: perfetto.protos.TracePacket[],
+ expectedMsg: ExpectedMessagePacket,
+ ) {
+ const packet = packets[expectedMsg.packetIndex];
+ expect(packet.trustedPacketSequenceId).toEqual(sequenceId);
+ expect(packet.sequenceFlags).toEqual(expectedMsg.sequenceFlags);
+ expect(packet.trustedUid).toEqual(trustedUid);
+ expect(packet.trustedPid).toEqual(trustedPid);
+ const ts1 = expectedMsg.timestamp;
+ ts1.unsigned = true;
+ expect(packet.timestamp).toEqual(ts1);
+ expect(packet.protologMessage?.messageId).toEqual(
+ expectedMsg.messageId,
+ );
+ expect(packet.protologMessage?.strParamIids).toEqual(
+ expectedMsg.strParamIids,
+ );
+ expect(packet.protologMessage?.booleanParams).toEqual(
+ expectedMsg.booleanParams,
+ );
+ expect(packet.protologMessage?.doubleParams).toEqual(
+ expectedMsg.doubleParams,
+ );
+ expect(packet.protologMessage?.sint64Params).toEqual(
+ expectedMsg.sint64Params,
+ );
+ expect(packet.protologViewerConfig).toBeNull();
+ expect(packet.internedData).toBeNull();
+ }
+
+ function checkInternedDataPacket(
+ packets: perfetto.protos.TracePacket[],
+ expectedData: ExpectedInternedData,
+ ) {
+ const packet = packets[expectedData.packetIndex];
+ expect(packet.trustedPacketSequenceId).toEqual(sequenceId);
+ expect(packet.sequenceFlags).toEqual(
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_UNSPECIFIED,
+ );
+ expect(packet.trustedUid).toEqual(trustedUid);
+ expect(packet.trustedPid).toEqual(trustedPid);
+ expect(packet.internedData?.protologStringArgs).toEqual([
+ perfetto.protos.InternedString.fromObject({
+ iid: Long.fromNumber(expectedData.iid),
+ str: utf8Encode(expectedData.str),
+ }),
+ ]);
+ expect(packet.protologViewerConfig).toBeNull();
+ expect(packet.protologMessage).toBeNull();
+ }
+
+ it('converts to valid perfetto trace', async () => {
+ const perfettoParser = await new LegacyParserProvider()
+ .addFilename(this.traceFile)
+ .setConvertToPerfetto(true)
+ .setLatestRealToElapsedTimeOffsetNs(
+ assertDefined(parser.getRealToBootTimeOffsetNs()),
+ )
+ .getParser<PropertyTreeNode>();
+
+ expect(perfettoParser.getTimestamps()?.slice(0, 3)).toEqual(
+ this.first3ExpectedRealTimestamps,
+ );
+
+ const message = await perfettoParser.getEntry(0);
+
+ expect(
+ assertDefined(message.getChildByName('text')).formattedValue(),
+ ).toEqual(this.expectedFirstMessage.message);
+ expect(
+ assertDefined(message.getChildByName('timestamp')).formattedValue(),
+ ).toEqual(this.expectedFirstMessage.ts);
+ expect(
+ assertDefined(message.getChildByName('tag')).formattedValue(),
+ ).toEqual(this.expectedFirstMessage.tag);
+ expect(
+ assertDefined(message.getChildByName('level')).formattedValue(),
+ ).toEqual(this.expectedFirstMessage.level);
+ expect(
+ assertDefined(message.getChildByName('at')).formattedValue(),
+ ).toEqual(this.expectedFirstMessage.at);
+ });
});
+ }
+}
- it('has expected trace type', () => {
- expect(parser.getTraceType()).toEqual(TraceType.PROTO_LOG);
- });
-
- it('has expected coarse version', () => {
- expect(parser.getCoarseVersion()).toEqual(CoarseVersion.LEGACY);
- });
-
- it('has expected length', () => {
- expect(parser.getLengthEntries()).toEqual(timestampCount);
- });
-
- it('provides timestamps', () => {
- const timestamps = assertDefined(parser.getTimestamps());
- expect(timestamps.length).toEqual(timestampCount);
-
- expect(timestamps.slice(0, 3)).toEqual(first3ExpectedRealTimestamps);
- });
-
- it('reconstructs human-readable log message', async () => {
- const message = await parser.getEntry(0);
-
- expect(
- assertDefined(message.getChildByName('text')).formattedValue(),
- ).toEqual(expectedFirstMessage['message']);
- expect(
- assertDefined(message.getChildByName('timestamp')).formattedValue(),
- ).toEqual(expectedFirstMessage['ts']);
- expect(
- assertDefined(message.getChildByName('tag')).formattedValue(),
- ).toEqual(expectedFirstMessage['tag']);
- expect(
- assertDefined(message.getChildByName('level')).formattedValue(),
- ).toEqual(expectedFirstMessage['level']);
- expect(
- assertDefined(message.getChildByName('at')).formattedValue(),
- ).toEqual(expectedFirstMessage['at']);
- });
+class ParserProtolog32Test extends ParserProtologTest {
+ override readonly traceFile =
+ 'traces/elapsed_and_real_timestamp/ProtoLog32.pb';
+ override readonly timestampCount = 50;
+ override readonly first3ExpectedRealTimestamps = [
+ TimestampConverterUtils.makeRealTimestamp(1655727125377266486n),
+ TimestampConverterUtils.makeRealTimestamp(1655727125377336718n),
+ TimestampConverterUtils.makeRealTimestamp(1655727125377350430n),
+ ];
+ override readonly expectedConfig = CONFIG_32;
+ override readonly internedData1: ExpectedInternedData = {
+ packetIndex: 2,
+ iid: 1,
+ str: 'ITYPE_IME',
};
+ override readonly internedData2: ExpectedInternedData = {
+ packetIndex: 3,
+ iid: 2,
+ str: 'false',
+ };
+ override readonly messagePacketNoInternedStrings: ExpectedMessagePacket = {
+ packetIndex: 50,
+ sequenceFlags: perfetto.protos.TracePacket.SequenceFlags.SEQ_UNSPECIFIED,
+ timestamp: Long.fromNumber(850755642097),
+ messageId: Long.fromNumber(1984782949),
+ strParamIids: [],
+ sint64Params: [],
+ booleanParams: [],
+ doubleParams: [],
+ };
+ override readonly messagePacketWithInternedStrings: ExpectedMessagePacket = {
+ packetIndex: 4,
+ sequenceFlags:
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_NEEDS_INCREMENTAL_STATE,
+ timestamp: Long.fromNumber(850746266486),
+ messageId: Long.fromNumber(2070726247),
+ strParamIids: [1, 2, 2],
+ sint64Params: [],
+ booleanParams: [],
+ doubleParams: [],
+ };
+ override readonly expectedFirstMessage: ExpectedMessage = {
+ message:
+ 'InsetsSource updateVisibility for ITYPE_IME, serverVisible: false clientVisible: false',
+ ts: '2022-06-20, 12:12:05.377',
+ tag: 'WindowManager',
+ level: 'DEBUG',
+ at: 'com/android/server/wm/InsetsSourceProvider.java',
+ };
+}
-describe('ParserProtoLog', () => {
- describe(
- '32',
- genProtoLogTest(
- 'traces/elapsed_and_real_timestamp/ProtoLog32.pb',
- 50,
- [
- TimestampConverterUtils.makeRealTimestamp(1655727125377266486n),
- TimestampConverterUtils.makeRealTimestamp(1655727125377336718n),
- TimestampConverterUtils.makeRealTimestamp(1655727125377350430n),
- ],
- {
- 'message':
- 'InsetsSource updateVisibility for ITYPE_IME, serverVisible: false clientVisible: false',
- 'ts': '2022-06-20, 12:12:05.377',
- 'tag': 'WindowManager',
- 'level': 'DEBUG',
- 'at': 'com/android/server/wm/InsetsSourceProvider.java',
- },
- ),
- );
- describe(
- '64',
- genProtoLogTest(
- 'traces/elapsed_and_real_timestamp/ProtoLog64.pb',
- 4615,
- [
- TimestampConverterUtils.makeRealTimestamp(1709196806399529939n),
- TimestampConverterUtils.makeRealTimestamp(1709196806399763866n),
- TimestampConverterUtils.makeRealTimestamp(1709196806400297151n),
- ],
- {
- 'message': 'Starting activity when config will change = false',
- 'ts': '2024-02-29, 08:53:26.400',
- 'tag': 'WindowManager',
- 'level': 'VERBOSE',
- 'at': 'com/android/server/wm/ActivityStarter.java',
- },
- ),
- );
- describe(
- 'Missing config message',
- genProtoLogTest(
- 'traces/elapsed_and_real_timestamp/ProtoLogMissingConfigMessage.pb',
- 7295,
- [
- TimestampConverterUtils.makeRealTimestamp(1669053909777144978n),
- TimestampConverterUtils.makeRealTimestamp(1669053909778011697n),
- TimestampConverterUtils.makeRealTimestamp(1669053909778800707n),
- ],
- {
- 'message': 'SURFACE isColorSpaceAgnostic=true: NotificationShade',
- 'ts': '2022-11-21, 18:05:09.777',
- 'tag': 'WindowManager',
- 'level': 'INFO',
- 'at': 'com/android/server/wm/WindowSurfaceController.java',
- },
- ),
- );
+class ParserProtolog64Test extends ParserProtologTest {
+ override readonly traceFile =
+ 'traces/elapsed_and_real_timestamp/ProtoLog64.pb';
+ override readonly timestampCount = 4615;
+ override readonly first3ExpectedRealTimestamps = [
+ TimestampConverterUtils.makeRealTimestamp(1709196806399529939n),
+ TimestampConverterUtils.makeRealTimestamp(1709196806399763866n),
+ TimestampConverterUtils.makeRealTimestamp(1709196806400297151n),
+ ];
+ override readonly expectedConfig = CONFIG_64;
+ override readonly internedData1: ExpectedInternedData = {
+ packetIndex: 5,
+ iid: 1,
+ str: 'ActivityRecord{e361a5d u0 com.google.android.gm/.ConversationListActivityGmail',
+ };
+ override readonly internedData2: ExpectedInternedData = {
+ packetIndex: 6,
+ iid: 2,
+ str: 'null',
+ };
+ override readonly messagePacketNoInternedStrings: ExpectedMessagePacket = {
+ packetIndex: 2,
+ sequenceFlags: perfetto.protos.TracePacket.SequenceFlags.SEQ_UNSPECIFIED,
+ timestamp: Long.fromNumber(1315553529939),
+ messageId: Long.fromString('1665699123574159131'),
+ strParamIids: [],
+ sint64Params: [],
+ booleanParams: [0],
+ doubleParams: [],
+ };
+ override readonly messagePacketWithInternedStrings: ExpectedMessagePacket = {
+ packetIndex: 9,
+ sequenceFlags:
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_NEEDS_INCREMENTAL_STATE,
+ timestamp: Long.fromNumber(1315574594310),
+ messageId: Long.fromString('-6873410057142191118'),
+ strParamIids: [1, 2, 3, 4],
+ sint64Params: [],
+ booleanParams: [],
+ doubleParams: [],
+ };
+ override readonly expectedFirstMessage: ExpectedMessage = {
+ message: 'Starting activity when config will change = false',
+ ts: '2024-02-29, 08:53:26.400',
+ tag: 'WindowManager',
+ level: 'VERBOSE',
+ at: 'com/android/server/wm/ActivityStarter.java',
+ };
+}
+
+class ParserProtologMissingConfigTest extends ParserProtologTest {
+ override readonly traceFile =
+ 'traces/elapsed_and_real_timestamp/ProtoLogMissingConfigMessage.pb';
+ override readonly timestampCount = 7295;
+ override readonly first3ExpectedRealTimestamps = [
+ TimestampConverterUtils.makeRealTimestamp(1669053909777144978n),
+ TimestampConverterUtils.makeRealTimestamp(1669053909778011697n),
+ TimestampConverterUtils.makeRealTimestamp(1669053909778800707n),
+ ];
+ override readonly expectedConfig = CONFIG_32;
+ override readonly internedData1: ExpectedInternedData = {
+ packetIndex: 2,
+ iid: 1,
+ str: 'NotificationShade',
+ };
+ override readonly internedData2: ExpectedInternedData = {
+ packetIndex: 4,
+ iid: 2,
+ str: 'Window{f199162 u0 NotificationShade}',
+ };
+ override readonly messagePacketNoInternedStrings: ExpectedMessagePacket = {
+ packetIndex: 92,
+ sequenceFlags: perfetto.protos.TracePacket.SequenceFlags.SEQ_UNSPECIFIED,
+ timestamp: Long.fromNumber(24398203599667),
+ messageId: Long.fromString('1381227466'),
+ strParamIids: [],
+ sint64Params: [Long.fromNumber(2), Long.fromNumber(0)],
+ booleanParams: [],
+ doubleParams: [],
+ };
+ override readonly messagePacketWithInternedStrings: ExpectedMessagePacket = {
+ packetIndex: 3,
+ sequenceFlags:
+ perfetto.protos.TracePacket.SequenceFlags.SEQ_NEEDS_INCREMENTAL_STATE,
+ timestamp: Long.fromNumber(24398190144978),
+ messageId: Long.fromNumber(585096182),
+ strParamIids: [1],
+ sint64Params: [],
+ booleanParams: [1],
+ doubleParams: [],
+ };
+ override readonly expectedFirstMessage: ExpectedMessage = {
+ message: 'SURFACE isColorSpaceAgnostic=true: NotificationShade',
+ ts: '2022-11-21, 18:05:09.777',
+ tag: 'WindowManager',
+ level: 'INFO',
+ at: 'com/android/server/wm/WindowSurfaceController.java',
+ };
+}
+
+describe('32', () => {
+ new ParserProtolog32Test().execute();
+});
+
+describe('64', () => {
+ new ParserProtolog64Test().execute();
+});
+
+describe('Missing config', () => {
+ new ParserProtologMissingConfigTest().execute();
});
diff --git a/tools/winscope/src/parsers/protolog/parser_protolog_utils.ts b/tools/winscope/src/parsers/protolog/parser_protolog_utils.ts
index d16b35f..8b55a10 100644
--- a/tools/winscope/src/parsers/protolog/parser_protolog_utils.ts
+++ b/tools/winscope/src/parsers/protolog/parser_protolog_utils.ts
@@ -27,7 +27,6 @@
static makeMessagePropertiesTree(
logMessage: LogMessage,
timestampConverter: ParserTimestampConverter,
- isMonotonic: boolean,
): PropertyTreeNode {
const tree = new PropertyTreeBuilderFromProto()
.setData(logMessage)
@@ -39,9 +38,6 @@
const customFormatters = new Map([['timestamp', TIMESTAMP_NODE_FORMATTER]]);
const strategy: MakeTimestampStrategyType = (valueNs: bigint) => {
- if (isMonotonic) {
- return timestampConverter.makeTimestampFromMonotonicNs(valueNs);
- }
return timestampConverter.makeTimestampFromBootTimeNs(valueNs);
};
diff --git a/tools/winscope/src/parsers/protolog/perfetto/parser_protolog.ts b/tools/winscope/src/parsers/protolog/perfetto/parser_protolog.ts
index 2d07355..a0e5235 100644
--- a/tools/winscope/src/parsers/protolog/perfetto/parser_protolog.ts
+++ b/tools/winscope/src/parsers/protolog/perfetto/parser_protolog.ts
@@ -60,7 +60,6 @@
return ParserProtologUtils.makeMessagePropertiesTree(
logMessage,
this.timestampConverter,
- false,
);
}
diff --git a/tools/winscope/src/trace/parser.ts b/tools/winscope/src/trace/parser.ts
index 03f90f5..7e87eae 100644
--- a/tools/winscope/src/trace/parser.ts
+++ b/tools/winscope/src/trace/parser.ts
@@ -40,5 +40,9 @@
getRealToMonotonicTimeOffsetNs(): bigint | undefined;
getRealToBootTimeOffsetNs(): bigint | undefined;
createTimestamps(): void;
- convertToPerfettoPackets?(sequenceId: number): perfetto.protos.TracePacket[];
+ convertToPerfettoPackets?(
+ sequenceId: number,
+ trustedUid?: number,
+ trustedPid?: number,
+ ): perfetto.protos.TracePacket[];
}