← MonitorHarmony 手机端性能监控的实现

MonitorHarmony 数据处理示例

metrics.mjs 是不依赖设备 API 的教学用 JavaScript 模块,解释本文涉及的单位、新鲜度、摘要窗口和 FPS 样本统计;它不包含 HDC 客户端、采集服务、完整 ArkTS UI,也不是可直接安装的监控应用。

import { summaryWindow, summarizeFps } from './metrics.mjs';
const window = summaryWindow(record.samples, 5000);
const summary = window.insufficient ? null : summarizeFps(window.samples);

record 来自实际会话 JSON。模块返回 null 表示没有有效指标;原应用内部使用 -1 哨兵,两者语义相同。

summaryWindow 不修改原数组;这里额外排序以便独立消费者安全使用。summarizeFps 统计采样窗口内的 FPS 值,而不是逐帧时长。低帧 5% 取 max(1, floor(N×0.05)) 个最低有效样本的算术平均,标准差使用总体公式。

电池功率输入单位为微伏与毫安,输出瓦;返回功率幅值,不含充放电方向。

文件

metrics.mjs 45 行 下载 ↓
/** Educational helpers derived from MonitorHarmony's unit and window rules. */
export function batteryPowerWatts(voltageMicroV, currentMilliA) {
  if (!Number.isFinite(voltageMicroV) || voltageMicroV <= 0 ||
      !Number.isFinite(currentMilliA)) return null;
  const watts = Math.abs(voltageMicroV * currentMilliA) / 1e9;
  return Number.isFinite(watts) && watts <= 100 ? watts : null;
}

export function freshValue(value, updatedAt, now, maxAgeMs = 3500) {
  if (![value, updatedAt, now, maxAgeMs].every(Number.isFinite)) return null;
  if (value < 0 || updatedAt <= 0 || maxAgeMs < 0) return null;
  const age = now - updatedAt;
  return age >= 0 && age <= maxAgeMs ? value : null;
}

export function summaryWindow(samples, trimMs = 5000) {
  if (!Number.isFinite(trimMs) || trimMs < 0) throw new RangeError('trimMs');
  const ordered = samples.filter(s => Number.isFinite(s.timestamp))
    .slice().sort((a, b) => a.timestamp - b.timestamp);
  if (ordered.length < 2) return { samples: [], insufficient: true };
  const startAt = ordered[0].timestamp + trimMs;
  const endAt = ordered[ordered.length - 1].timestamp - trimMs;
  const selected = endAt > startAt
    ? ordered.filter(s => s.timestamp >= startAt && s.timestamp <= endAt) : [];
  return { startAt, endAt, samples: selected, insufficient: selected.length < 2 };
}

export function summarizeFps(samples) {
  const values = samples.map(s => s.fps).filter(v => Number.isFinite(v) && v >= 0);
  if (!values.length) return null;
  const average = values.reduce((a, b) => a + b, 0) / values.length;
  const variance = values.reduce((a, b) => a + (b - average) ** 2, 0) / values.length;
  const sorted = values.slice().sort((a, b) => a - b);
  const lowCount = Math.max(1, Math.floor(sorted.length * 0.05));
  return {
    count: values.length,
    average,
    min: sorted[0],
    max: sorted[sorted.length - 1],
    standardDeviation: Math.sqrt(variance),
    atLeast45Percent: values.filter(v => v >= 45).length * 100 / values.length,
    lowFivePercentSampleAverage: sorted.slice(0, lowCount).reduce((a, b) => a + b, 0) / lowCount
  };
}