blob: f4c71e9f549f5817b56a58fe34dd1c016277ef16 [file] [log] [blame]
/*
* 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 {assertDefined, assertTrue} from 'common/assert_utils';
import {getTimestampConverter} from 'common/time/test_utils';
import {TimestampConverter} from 'common/time/timestamp_converter';
import {getRootUrl} from 'common/url_utils';
import {FileAndParser} from 'parsers/file_and_parser';
import {ParserFactory as LegacyParserFactory} from 'parsers/legacy/parser_factory';
import {LegacyToPerfettoConverter} from 'parsers/legacy_to_perfetto_converter';
import {
getParserWithLatestRealToBootTimeOffset,
getParserWithLatestRealToMonotonicTimeOffset,
} from 'parsers/parser_time_utils';
import {ParserFactory as PerfettoParserFactory} from 'parsers/perfetto/parser_factory';
import {TracesParserFactory} from 'parsers/traces/traces_parser_factory';
import {Parser} from 'trace/parser';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceFile} from 'trace/trace_file';
import {TraceMetadata} from 'trace/trace_metadata';
import {TraceEntryTypeMap, TraceType} from 'trace/trace_type';
import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
import {TraceBuilder} from './trace_builder';
export async function getFixtureFile(
srcFilename: string,
dstFilename: string = srcFilename,
): Promise<File> {
const url = getRootUrl() + 'base/src/test/fixtures/' + srcFilename;
const response = await fetch(url);
expect(response.ok).toBeTrue();
const blob = await response.blob();
const file = new File([blob], dstFilename);
return file;
}
export class LegacyParserProvider {
private filenames: string[] = [];
private timestampConverter = getTimestampConverter();
private initializeRealToElapsedTimeOffsetNs = true;
private metadata: TraceMetadata = {};
private convertToPerfetto = false;
private existingPerfettoFile: TraceFile | undefined;
addFilename(value: string) {
this.filenames.push(value);
return this;
}
setTimestampConverter(value: TimestampConverter) {
this.timestampConverter = value;
return this;
}
setInitializeRealToElapsedTimeOffsetNs(value: boolean) {
this.initializeRealToElapsedTimeOffsetNs = value;
return this;
}
setMetadata(value: TraceMetadata) {
this.metadata = value;
return this;
}
setConvertToPerfetto(value: boolean) {
this.convertToPerfetto = value;
return this;
}
setExistingPerfettoFile(value: TraceFile) {
this.existingPerfettoFile = value;
return this;
}
async getParser<T>(): Promise<Parser<T>> {
const parsers = await this.getParsers();
expect(parsers.length)
.withContext(
`Should have been able to create a parser for ${this.filenames.join(
', ',
)}`,
)
.toBeGreaterThanOrEqual(1);
return parsers[0] as Parser<T>;
}
async getParsers(): Promise<Array<Parser<object>>> {
const files = [];
for (const filename of this.filenames) {
const file = new TraceFile(
await getFixtureFile(assertDefined(filename)),
undefined,
);
files.push(file);
}
const processedFiles = await new LegacyParserFactory().processFiles(
files,
this.timestampConverter,
this.metadata,
);
createTimestamps(
processedFiles.parsers,
this.initializeRealToElapsedTimeOffsetNs,
this.timestampConverter,
);
const fileAndParsers = this.convertToPerfetto
? await convertToPerfettoTrace(
processedFiles.parsers,
this.timestampConverter,
this.existingPerfettoFile,
)
: processedFiles.parsers;
this.timestampConverter.clear();
createTimestamps(
fileAndParsers,
this.initializeRealToElapsedTimeOffsetNs,
this.timestampConverter,
);
return fileAndParsers.map((fileAndParser) => {
return fileAndParser.parser;
});
}
}
export async function convertToPerfettoTrace(
fileAndParsers: FileAndParser[],
timestampConverter: TimestampConverter,
existingPerfettoFile?: TraceFile,
): Promise<FileAndParser[]> {
const parsers = fileAndParsers.map((p) => p.parser);
const perfettoTrace =
await LegacyToPerfettoConverter.convertToSinglePerfettoFile(
parsers,
parsers,
existingPerfettoFile,
);
if (perfettoTrace) {
const processed = await new PerfettoParserFactory().processFile(
perfettoTrace,
timestampConverter,
);
fileAndParsers = processed.parsers.map((parser) => {
return new FileAndParser(perfettoTrace, parser);
});
}
return fileAndParsers;
}
export async function getTrace<T extends TraceType>(
type: T,
filename: string,
): Promise<Trace<T>> {
const converter = getTimestampConverter(false);
const legacyParsers = await new LegacyParserProvider()
.addFilename(filename)
.setTimestampConverter(converter)
.getParsers();
expect(legacyParsers.length).toBeLessThanOrEqual(1);
if (legacyParsers.length === 1) {
expect(legacyParsers[0].getTraceType()).toEqual(type);
return new TraceBuilder<T>()
.setType(type)
.setParser(legacyParsers[0] as unknown as Parser<T>)
.build();
}
const perfettoParsers = await getPerfettoParsers(filename);
expect(perfettoParsers.length).toEqual(1);
expect(perfettoParsers[0].getTraceType()).toEqual(type);
return new TraceBuilder<T>()
.setType(type)
.setParser(perfettoParsers[0] as unknown as Parser<T>)
.build();
}
function createTimestamps(
fileAndParsers: FileAndParser[],
initializeRealToElapsedTimeOffsetNs: boolean,
converter: TimestampConverter,
) {
if (initializeRealToElapsedTimeOffsetNs) {
const monotonicOffset = getParserWithLatestRealToMonotonicTimeOffset(
fileAndParsers.map((fileAndParser) => fileAndParser.parser),
)?.getRealToMonotonicTimeOffsetNs();
if (monotonicOffset !== undefined) {
converter.setRealToMonotonicTimeOffsetNs(monotonicOffset);
}
const boottimeOffset = getParserWithLatestRealToBootTimeOffset(
fileAndParsers.map((fileAndParser) => fileAndParser.parser),
)?.getRealToBootTimeOffsetNs();
if (boottimeOffset !== undefined) {
converter.setRealToBootTimeOffsetNs(boottimeOffset);
}
}
fileAndParsers.forEach((fileAndParser) => {
fileAndParser.parser.createTimestamps();
});
}
export async function getPerfettoParser<T extends TraceType>(
traceType: T,
fixturePath: string,
withUTCOffset = false,
): Promise<Parser<TraceEntryTypeMap[T]>> {
const parsers = await getPerfettoParsers(fixturePath, withUTCOffset);
const parser = assertDefined(
parsers.find((parser) => parser.getTraceType() === traceType),
);
return parser as Parser<TraceEntryTypeMap[T]>;
}
export async function getPerfettoParsers(
fixturePath: string,
withUTCOffset = false,
isPerfetto?: boolean,
): Promise<Array<Parser<object>>> {
const file = await getFixtureFile(fixturePath);
const traceFile = new TraceFile(file);
const converter = getTimestampConverter(withUTCOffset);
const {parsers, isPerfettoTrace} =
await new PerfettoParserFactory().processFile(
traceFile,
converter,
undefined,
);
if (isPerfetto !== undefined) {
expect(isPerfettoTrace).toEqual(isPerfetto);
}
createTimestamps(
parsers.map((parser) => {
return new FileAndParser(traceFile, parser);
}),
true,
converter,
);
return parsers;
}
export async function getTracesParser(
filenames: string[],
withUTCOffset = false,
): Promise<{
tracesParser: Parser<object>;
constituentParsers: Array<Parser<object>>;
}> {
const converter = getTimestampConverter(withUTCOffset);
const provider = new LegacyParserProvider();
filenames.forEach((filename) => provider.addFilename(filename));
const legacyParsers = await provider
.setTimestampConverter(converter)
.setInitializeRealToElapsedTimeOffsetNs(true)
.getParsers();
const perfettoParsers = (
await Promise.all(
filenames.map(async (filename) => getPerfettoParsers(filename)),
)
).reduce((acc, cur) => acc.concat(cur), []);
const parsersArray = legacyParsers.concat(perfettoParsers);
const offset =
getParserWithLatestRealToBootTimeOffset(
parsersArray,
)?.getRealToBootTimeOffsetNs();
if (offset !== undefined) {
converter.setRealToBootTimeOffsetNs(offset);
}
const traces = new Traces();
parsersArray.forEach((parser) => {
const trace = Trace.fromParser(parser);
traces.addTrace(trace);
});
const tracesParsers = await new TracesParserFactory().createParsers(
traces,
converter,
);
assertTrue(
tracesParsers.length === 1,
() =>
`Should have been able to create a traces parser for [${filenames.join()}]`,
);
return {tracesParser: tracesParsers[0], constituentParsers: parsersArray};
}
export async function getWindowManagerState(
index = 0,
): Promise<HierarchyTreeNode> {
return getTraceEntry(
'traces/elapsed_and_real_timestamp/WindowManager.pb',
index,
);
}
export async function getImeTraceEntries(): Promise<
[Map<TraceType, HierarchyTreeNode>, Map<TraceType, HierarchyTreeNode>]
> {
const [clientsParser, managerServiceParser, serviceParser, sfParser] =
(await new LegacyParserProvider()
.addFilename('traces/ime/SurfaceFlinger_with_IME.pb')
.addFilename('traces/ime/InputMethodService.pb')
.addFilename('traces/ime/InputMethodManagerService.pb')
.addFilename('traces/ime/InputMethodClients.pb')
.setConvertToPerfetto(true)
.getParsers()) as Array<Parser<HierarchyTreeNode>>;
const surfaceFlingerEntry = await sfParser.getEntry(5);
const imServiceEntry = await serviceParser.getEntry(0);
const imManagerServiceEntry = await managerServiceParser.getEntry(0);
const clientsEntry0 = await clientsParser.getEntry(0);
const clientsEntry1 = await clientsParser.getEntry(1);
const windowManagerEntry = await getTraceEntry<HierarchyTreeNode>(
'traces/ime/WindowManager_with_IME.pb',
2,
);
const entries = new Map<TraceType, HierarchyTreeNode>();
entries.set(TraceType.INPUT_METHOD_CLIENTS, clientsEntry0);
entries.set(TraceType.INPUT_METHOD_MANAGER_SERVICE, imManagerServiceEntry);
entries.set(TraceType.INPUT_METHOD_SERVICE, imServiceEntry);
entries.set(TraceType.SURFACE_FLINGER, surfaceFlingerEntry);
entries.set(TraceType.WINDOW_MANAGER, windowManagerEntry);
const secondEntries = new Map<TraceType, HierarchyTreeNode>();
secondEntries.set(TraceType.INPUT_METHOD_CLIENTS, clientsEntry1);
secondEntries.set(TraceType.SURFACE_FLINGER, surfaceFlingerEntry);
secondEntries.set(TraceType.WINDOW_MANAGER, windowManagerEntry);
return [entries, secondEntries];
}
async function getTraceEntry<T>(filename: string, index = 0) {
const parser = await new LegacyParserProvider()
.addFilename(filename)
.getParser<T>();
return parser.getEntry(index);
}