blob: 49a06ecf72f3d7470e1236e5618ade86b9f34c03 [file] [log] [blame]
/*
* 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 {assertDefined} from 'common/assert_utils';
import {NOT_IMPLEMENTED_ERROR} from 'common/errors';
import {UserNotifier} from 'common/user_notifier';
import Long from 'long';
import {FailedToConvertLegacyTraces} from 'messaging/user_warnings';
import {perfetto} from 'protos/perfetto/trace/static';
import {Parser} from 'trace/parser';
import {TraceFile} from 'trace/trace_file';
import {
getParserWithLatestRealToBootTimeOffset,
getParserWithLatestRealToMonotonicTimeOffset,
} from './parser_time_utils';
export interface ClockSnapshot {
realtime: bigint;
boottime: bigint | undefined;
monotonic: bigint | undefined;
}
export class LegacyToPerfettoConverter {
static async convertToSinglePerfettoFile(
legacyParsers: Array<Parser<object>>,
allParsers: Array<Parser<object>>,
perfettoFile?: TraceFile,
): Promise<TraceFile | undefined> {
let trace: perfetto.protos.Trace;
try {
trace = await LegacyToPerfettoConverter.makePerfettoTrace(
allParsers,
perfettoFile,
);
} catch (e) {
console.error(e);
UserNotifier.add(
new FailedToConvertLegacyTraces((e as Error).message),
).notify();
return perfettoFile;
}
const legacyPackets = LegacyToPerfettoConverter.makeTraceDataPackets(
legacyParsers,
trace,
);
if (legacyPackets.length === 0) {
return undefined;
}
trace.packet.push(...legacyPackets);
const data = perfetto.protos.Trace.encode(trace).finish();
return new TraceFile(
new File([data], 'combined_winscope_trace.perfetto-trace'),
);
}
private static async makePerfettoTrace(
allParsers: Array<Parser<object>>,
perfettoFile?: TraceFile,
): Promise<perfetto.protos.Trace> {
let trace: perfetto.protos.Trace;
if (!perfettoFile) {
const clockSnapshots =
LegacyToPerfettoConverter.makeClockSnapshots(allParsers);
trace = perfetto.protos.Trace.create();
if (clockSnapshots.length === 0) {
throw new Error('no parsers or Perfetto file provided');
}
clockSnapshots.forEach((snapshot) => {
const clockSnapshot =
LegacyToPerfettoConverter.makeTracePacketWithClockSnapshot(snapshot);
trace.packet.push(clockSnapshot);
});
} else {
const fileBuffer = new Uint8Array(await perfettoFile.file.arrayBuffer());
trace = perfetto.protos.Trace.decode(fileBuffer);
}
return trace;
}
private static makeClockSnapshots(
parsers: Array<Parser<object>>,
): ClockSnapshot[] {
if (parsers.length === 0) {
return [];
}
const clockSnapshots: ClockSnapshot[] = [];
const boottimeParser = getParserWithLatestRealToBootTimeOffset(parsers);
const monotonicParser =
getParserWithLatestRealToMonotonicTimeOffset(parsers);
const boottimeSnapshots: ClockSnapshot[] = [];
const monotonicSnapshots: ClockSnapshot[] = [];
if (boottimeParser === undefined && monotonicParser === undefined) {
LegacyToPerfettoConverter.getRealTimestampsForClockSnapshots(
parsers[0],
).forEach((realtime) => {
clockSnapshots.push({
realtime,
boottime: realtime,
monotonic: realtime,
});
});
}
if (boottimeParser) {
const boottimeOffset = boottimeParser?.getRealToBootTimeOffsetNs();
LegacyToPerfettoConverter.getRealTimestampsForClockSnapshots(
boottimeParser,
).forEach((realtime) => {
const boottime = realtime - assertDefined(boottimeOffset);
boottimeSnapshots.push({realtime, boottime, monotonic: undefined});
});
}
if (monotonicParser) {
const monotonicOffset = monotonicParser?.getRealToMonotonicTimeOffsetNs();
LegacyToPerfettoConverter.getRealTimestampsForClockSnapshots(
monotonicParser,
).forEach((realtime) => {
const monotonic = realtime - assertDefined(monotonicOffset);
// Monotonic snapshots must contain a boottime timestamp for TP to be able
// to convert monotonic timestamps to boottime
let boottime: bigint;
if (boottimeParser) {
const snapshotB = boottimeSnapshots[boottimeSnapshots.length - 1];
const realtimeDiff = snapshotB.realtime - realtime;
boottime = assertDefined(snapshotB.boottime) - realtimeDiff;
} else {
boottime = monotonic;
}
monotonicSnapshots.push({realtime, boottime, monotonic});
});
}
clockSnapshots.push(...boottimeSnapshots);
clockSnapshots.push(...monotonicSnapshots);
return clockSnapshots;
}
private static getRealTimestampsForClockSnapshots(
parser: Parser<object>,
): Array<bigint> {
const ts = assertDefined(parser.getTimestamps());
const realTs: Array<bigint> = [];
if (ts.length > 0) {
realTs.push(ts[0].getValueNs());
}
if (ts.length > 1) {
// to adjust against drift in TP, we add clock snapshots at the
// start and end of the trace
realTs.push(ts[parser.getLengthEntries() - 1].getValueNs());
}
return realTs;
}
private static makeTracePacketWithClockSnapshot(
legacySnapshot: ClockSnapshot,
): perfetto.protos.TracePacket {
const packet = perfetto.protos.TracePacket.create();
packet.trustedPacketSequenceId = 1;
const snapshot = perfetto.protos.ClockSnapshot.create();
const realtime = Long.fromString(legacySnapshot.realtime.toString());
const clockRealtimeCoarse = perfetto.protos.ClockSnapshot.Clock.create();
clockRealtimeCoarse.clockId =
perfetto.protos.ClockSnapshot.Clock.BuiltinClocks.REALTIME_COARSE;
clockRealtimeCoarse.timestamp = realtime;
snapshot.clocks.push(clockRealtimeCoarse);
const clockRealtime = perfetto.protos.ClockSnapshot.Clock.create();
clockRealtime.clockId =
perfetto.protos.ClockSnapshot.Clock.BuiltinClocks.REALTIME;
clockRealtime.timestamp = realtime;
snapshot.clocks.push(clockRealtime);
if (legacySnapshot.boottime !== undefined) {
const boottime = Long.fromString(legacySnapshot.boottime.toString());
const clockBoottime = perfetto.protos.ClockSnapshot.Clock.create();
clockBoottime.clockId =
perfetto.protos.ClockSnapshot.Clock.BuiltinClocks.BOOTTIME;
clockBoottime.timestamp = boottime;
snapshot.clocks.push(clockBoottime);
}
if (legacySnapshot.monotonic !== undefined) {
const monotonic = Long.fromString(legacySnapshot.monotonic.toString());
const clockMonotonic = perfetto.protos.ClockSnapshot.Clock.create();
clockMonotonic.clockId =
perfetto.protos.ClockSnapshot.Clock.BuiltinClocks.MONOTONIC;
clockMonotonic.timestamp = monotonic;
snapshot.clocks.push(clockMonotonic);
const clockMonotonicCoarse = perfetto.protos.ClockSnapshot.Clock.create();
clockMonotonicCoarse.clockId =
perfetto.protos.ClockSnapshot.Clock.BuiltinClocks.MONOTONIC_COARSE;
clockMonotonicCoarse.timestamp = monotonic;
snapshot.clocks.push(clockMonotonicCoarse);
const clockMonotonicRaw = perfetto.protos.ClockSnapshot.Clock.create();
clockMonotonicRaw.clockId =
perfetto.protos.ClockSnapshot.Clock.BuiltinClocks.MONOTONIC_RAW;
clockMonotonicRaw.timestamp = monotonic;
snapshot.clocks.push(clockMonotonicRaw);
}
packet.clockSnapshot = snapshot;
return packet;
}
private static makeTraceDataPackets(
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],
);
let [trustedUid, trustedPid] = [largestUid + 1, largestPid + 1];
const packets: perfetto.protos.TracePacket[] = [];
let sequenceId =
Math.max(
...trace.packet.map((packet) => packet.trustedPacketSequenceId ?? 0),
) + 1;
for (const parser of legacyParsers) {
if (parser.canConvertToPerfetto()) {
try {
const legacyPackets = parser.convertToPerfettoPackets!(
sequenceId,
trustedUid,
trustedPid,
);
if (legacyPackets.length > 0) {
legacyPackets[0].firstPacketOnSequence = true;
packets.push(...legacyPackets);
sequenceId++;
trustedUid++;
trustedPid++;
}
} catch (e) {
// swallow
if (e !== NOT_IMPLEMENTED_ERROR) {
console.error(e);
}
}
}
}
return packets;
}
}