blob: 8a94510596144042766a1c2b67e99a713a16a83d [file] [edit]
// Copyright (C) 2026 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 {z} from 'zod';
import {
expandProcessName,
type BlockingCallMetricData,
type CujMetricData,
type CujScopedMetricData,
type FullTraceMetricData,
type JankType,
} from './metricUtils';
export enum PinIntentKind {
Cuj = 'cuj',
CujScopedJank = 'cuj_scoped_jank',
CujBlockingCall = 'cuj_blocking_call',
FullTraceJank = 'full_trace_jank',
}
export type PinIntent =
| ({kind: PinIntentKind.Cuj} & CujMetricData)
| ({kind: PinIntentKind.CujScopedJank} & CujScopedMetricData)
| ({kind: PinIntentKind.CujBlockingCall} & BlockingCallMetricData)
| ({kind: PinIntentKind.FullTraceJank} & FullTraceMetricData);
const pinRequestDictSchema = z.record(z.string(), z.string().optional());
const pinRequestItemSchema = z.union([
pinRequestDictSchema,
z
.string()
.transform((str) => JSON.parse(str))
.pipe(pinRequestDictSchema),
]);
export const pinRequestsInputSchema = z.array(pinRequestItemSchema);
export type PinRequestsInput = z.infer<typeof pinRequestsInputSchema>;
/**
* Parses and validates an array of open parameter dictionaries (or JSON-encoded
* strings from URL startup commands) into a list of PinIntents using a Zod schema.
*
* @param {unknown} input Array of parameter dictionaries passed to the pin command
* @returns {PinIntent[]} List of parsed pin intents
*/
export function parsePinIntents(input: unknown): PinIntent[] {
const items = pinRequestsInputSchema.parse(input);
const intents: PinIntent[] = [];
for (const dict of items) {
const parsed = parseDictIntent(dict);
if (parsed) {
intents.push(parsed);
}
}
return intents;
}
function parseDictIntent(
dict: Record<string, string | undefined>,
): PinIntent | undefined {
const {
blockingCall,
cuj,
process: rawProcess,
aggregation,
fullTrace,
jankType,
isWeighted,
allJankCujs,
allLatencyCujs,
} = dict;
const process = rawProcess ? expandProcessName(rawProcess) : undefined;
// CUJ blocking call
if (process && cuj && blockingCall && aggregation) {
return {
kind: PinIntentKind.CujBlockingCall,
process,
cujName: cuj,
blockingCallName: blockingCall,
aggregation,
};
}
// Full trace jank
if (fullTrace === 'true' && process) {
return {
kind: PinIntentKind.FullTraceJank,
process,
jankType: (jankType ?? 'frames') as JankType,
isWeighted: isWeighted === 'true',
};
}
// CUJ scoped jank
if (process && cuj && cuj !== '*') {
return {
kind: PinIntentKind.CujScopedJank,
process,
cujName: cuj,
jankType: (jankType ?? 'frames') as JankType,
isWeighted: isWeighted === 'true',
};
}
// Generic CUJ / Wildcard
if (cuj) {
return {kind: PinIntentKind.Cuj, cujName: cuj};
}
if (allJankCujs === 'true' || allLatencyCujs === 'true') {
return {kind: PinIntentKind.Cuj, cujName: '*'};
}
return undefined;
}