* Copyright (c) 2025 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const {spawn} = require('child_process');
const yargs = require('yargs');
const argv = yargs
.option('t', {
alias: 'time',
default: '10',
type: 'string',
description: 'Set the time(length) of the benchmark.',
})
.option('d', {
alias: 'delay',
default: '3',
type: 'number',
description: 'Delay frame times aggregation.',
})
.option('stdin', {
alias: 'i',
default: false,
type: 'boolean',
description:
'Use stdin as source of hitrace traces. This allows to manually capture traces.',
})
.example(
'$0 -t 10',
'measures the frame times (the time between each in the next 10 seconds using hitrace graphic',
)
.example(
'hdc shell hitrace graphic --trace_finish | node $0 --stdin',
'finishes capturing traces and calculates the frame-times based on them',
)
.help('h')
.alias('h', 'help').argv;
const getFrameTimes = hitraceOutput => {
const regex =
/^(\s+\S+)(\s+\()(\s+\d+\))(\s+\[\d+\])\s+\.{4}\s+(\d+\.\d+):\s+(\w+:\s+)(.*)/;
const traceCandidates = hitraceOutput
.trim()
.split('\n')
.map(line => {
const match = line.match(regex);
return match
? {
timestamp: Number(match[5].trim()),
functionDetails: match[7].trim(),
}
: null;
});
const parsedTraces = traceCandidates.filter(
trace => trace && trace.timestamp && trace.functionDetails
);
const repaintTimestamps = parsedTraces
.filter(
trace =>
trace.functionDetails.includes('Repaint') ||
trace.functionDetails.includes('finishFrame'),
)
.map(trace => trace.timestamp);
if (repaintTimestamps.length === 0) {
console.error('no repaint traces captured!');
return [];
}
const frameTimes = [];
for (let i = 1; i < repaintTimestamps.length; i++) {
frameTimes.push(repaintTimestamps[i] - repaintTimestamps[i - 1]);
}
return frameTimes;
};
const run = () => {
const command = 'hdc';
const {t: time, stdin} = argv;
const args = ['shell', 'hitrace', 'graphic', '-t', time];
let hitraceOutput = '';
if (stdin) {
process.stdin.on('data', chunk => {
hitraceOutput += chunk;
});
process.stdin.on('end', () => {
let frameTimes = getFrameTimes(hitraceOutput);
console.log(JSON.stringify(frameTimes));
});
} else {
const child = spawn(command, args);
let intervalExecutionCounter = 0;
const interval = setInterval(() => {
console.warn(argv.d - intervalExecutionCounter - 1 + '...');
intervalExecutionCounter++;
}, 1000);
setTimeout(() => {
clearInterval(interval);
console.warn('Recording started...');
child.stdout.on('data', chunk => {
hitraceOutput += chunk;
});
}, argv.d * 1000);
child.stderr.on('data', data => {
console.error(`stderr: ${data}`);
});
child.on('error', error => {
console.error(`spawn error: ${error}`);
});
child.on('close', () => {
let frameTimes = getFrameTimes(hitraceOutput);
console.log(JSON.stringify(frameTimes));
});
}
};
run();