"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.FrameStatsAnalyzer = void 0;
const thresholds_1 = require("../../config/thresholds");
class FrameStatsAnalyzer {
    constructor(traceProcessor, config) {
        this.traceProcessor = traceProcessor;
        // Use configured value or derive from centralized VSync config.
        // For frame stats fallback, use 60Hz baseline unless trace metadata provides a better value.
        const defaultVsyncMs = Number((0, thresholds_1.inferVsyncPeriodNs)({
            detectedVsyncPeriodNs: config?.detectedVsyncPeriodNs,
            deviceRefreshRate: config?.deviceRefreshRate ?? 60,
        })) / 1000000;
        this.targetFrameTimeMs = config?.targetFrameTimeMs ?? defaultVsyncMs;
        // Jank threshold: default 2 VSyncs missed
        const jankVsyncThreshold = config?.jankVsyncThreshold ?? 2;
        this.jankThresholdMs = this.targetFrameTimeMs * jankVsyncThreshold;
    }
    /**
     * 执行帧率统计分析
     * @param traceId Trace ID
     * @param packageName 应用包名，可选
     * @param startTs 开始时间戳（纳秒），可选
     * @param endTs 结束时间戳（纳秒），可选
     */
    async analyze(traceId, packageName, startTs, endTs) {
        const timeCondition = this.buildTimeCondition(startTs, endTs, 'slice.ts');
        const packageCondition = packageName
            ? `AND process.name LIKE '%${this.escapeLikePattern(packageName)}%' ESCAPE '\\'`
            : '';
        // 查询所有帧的时长
        // 使用 actual_frame_timeline_slice 表（Android 的帧时间线）
        const query = `
      SELECT
        slice.ts AS ts,
        slice.dur / 1e6 AS dur_ms,
        slice.frame_number AS frame_number
      FROM actual_frame_timeline_slice slice
      JOIN process_track track ON slice.track_id = track.id
      JOIN process ON track.upid = process.upid
      WHERE slice.dur > 0
        ${timeCondition}
        ${packageCondition}
      ORDER BY slice.ts
    `;
        const result = await this.traceProcessor.query(traceId, query);
        const rows = this.resultToRows(result);
        if (rows.length === 0) {
            throw new Error('No frame data found. Make sure the trace contains frame timing information.');
        }
        // 计算基本统计
        const durations = rows.map(r => parseFloat(r.dur_ms));
        const totalFrames = durations.length;
        const avgDuration = durations.reduce((sum, d) => sum + d, 0) / totalFrames;
        // 计算时间范围（秒）
        const firstTs = parseInt(rows[0].ts);
        const lastTs = parseInt(rows[rows.length - 1].ts);
        const rawTotalTimeSec = (lastTs - firstTs) / 1e9;
        const totalTimeSec = rawTotalTimeSec > 0 ? rawTotalTimeSec : 1e-6;
        const avgFps = totalFrames / totalTimeSec;
        // 识别掉帧
        const allJankFrames = rows
            .filter(r => parseFloat(r.dur_ms) > this.jankThresholdMs)
            .map(r => ({
            timestamp: parseInt(r.ts),
            durationMs: parseFloat(r.dur_ms),
            vsyncsMissed: Math.floor(parseFloat(r.dur_ms) / this.targetFrameTimeMs),
        }));
        const jankFrames = allJankFrames.slice(0, 100); // 限制返回前 100 个掉帧点
        const jankCount = allJankFrames.length;
        const jankPercentage = (jankCount / totalFrames) * 100;
        // 计算百分位数
        const sortedDurations = [...durations].sort((a, b) => a - b);
        const percentiles = {
            p50Ms: this.getPercentile(sortedDurations, 50),
            p90Ms: this.getPercentile(sortedDurations, 90),
            p95Ms: this.getPercentile(sortedDurations, 95),
            p99Ms: this.getPercentile(sortedDurations, 99),
            maxMs: Math.max(...durations),
        };
        // 生成直方图数据
        const histogram = this.generateHistogram(durations);
        return {
            summary: {
                totalFrames,
                avgFps,
                jankCount,
                jankPercentage,
                avgFrameDurationMs: avgDuration,
            },
            percentiles,
            jankFrames,
            visualization: {
                type: 'histogram',
                data: histogram,
            },
        };
    }
    /**
     * 简化版分析：基于 slice 表（适用于没有 actual_frame_timeline_slice 的trace）
     */
    async analyzeFromSlices(traceId, processName, startTs, endTs) {
        const timeCondition = this.buildTimeCondition(startTs, endTs, 's.ts');
        // 查找渲染相关的 slice
        const query = `
      SELECT
        s.ts AS ts,
        s.dur / 1e6 AS dur_ms,
        s.name AS name
      FROM slice s
      WHERE (
        s.name LIKE 'Choreographer#doFrame'
        OR s.name LIKE 'DrawFrame'
        OR s.name LIKE 'performTraversals'
      )
      ${timeCondition}
      ORDER BY s.ts
    `;
        const result = await this.traceProcessor.query(traceId, query);
        const rows = this.resultToRows(result);
        if (rows.length === 0) {
            throw new Error('No frame data found in slice table');
        }
        // 使用类似的统计方法
        const durations = rows.map(r => parseFloat(r.dur_ms));
        const totalFrames = durations.length;
        const avgDuration = durations.reduce((sum, d) => sum + d, 0) / totalFrames;
        const firstTs = parseInt(rows[0].ts);
        const lastTs = parseInt(rows[rows.length - 1].ts);
        const rawTotalTimeSec = (lastTs - firstTs) / 1e9;
        const totalTimeSec = rawTotalTimeSec > 0 ? rawTotalTimeSec : 1e-6;
        const avgFps = totalFrames / totalTimeSec;
        const allJankFrames = rows
            .filter(r => parseFloat(r.dur_ms) > this.jankThresholdMs)
            .map(r => ({
            timestamp: parseInt(r.ts),
            durationMs: parseFloat(r.dur_ms),
            vsyncsMissed: Math.floor(parseFloat(r.dur_ms) / this.targetFrameTimeMs),
        }));
        const jankFrames = allJankFrames.slice(0, 100);
        const sortedDurations = [...durations].sort((a, b) => a - b);
        return {
            summary: {
                totalFrames,
                avgFps,
                jankCount: allJankFrames.length,
                jankPercentage: (allJankFrames.length / totalFrames) * 100,
                avgFrameDurationMs: avgDuration,
            },
            percentiles: {
                p50Ms: this.getPercentile(sortedDurations, 50),
                p90Ms: this.getPercentile(sortedDurations, 90),
                p95Ms: this.getPercentile(sortedDurations, 95),
                p99Ms: this.getPercentile(sortedDurations, 99),
                maxMs: Math.max(...durations),
            },
            jankFrames,
            visualization: {
                type: 'histogram',
                data: this.generateHistogram(durations),
            },
        };
    }
    getPercentile(sortedArray, percentile) {
        const index = Math.ceil((percentile / 100) * sortedArray.length) - 1;
        return sortedArray[Math.max(0, index)];
    }
    generateHistogram(durations) {
        // 创建直方图桶：0-8ms, 8-16ms, 16-24ms, 24-32ms, 32+ms
        const buckets = [
            { start: 0, end: 8, count: 0 },
            { start: 8, end: 16, count: 0 },
            { start: 16, end: 24, count: 0 },
            { start: 24, end: 32, count: 0 },
            { start: 32, end: Infinity, count: 0 },
        ];
        durations.forEach(dur => {
            const bucket = buckets.find(b => dur >= b.start && dur < b.end);
            if (bucket) {
                bucket.count++;
            }
        });
        return buckets.map(b => ({
            bucketStart: b.start,
            bucketEnd: b.end === Infinity ? 999 : b.end,
            count: b.count,
        }));
    }
    buildTimeCondition(startTs, endTs, tsColumn = 'ts') {
        const safeStart = this.normalizeTimestamp(startTs);
        const safeEnd = this.normalizeTimestamp(endTs);
        if (safeStart === undefined && safeEnd === undefined)
            return '';
        if (safeStart !== undefined && safeEnd === undefined)
            return `AND ${tsColumn} >= ${safeStart}`;
        if (safeStart === undefined && safeEnd !== undefined)
            return `AND ${tsColumn} <= ${safeEnd}`;
        return `AND ${tsColumn} >= ${safeStart} AND ${tsColumn} <= ${safeEnd}`;
    }
    normalizeTimestamp(ts) {
        if (ts === undefined || ts === null)
            return undefined;
        const n = Number(ts);
        if (!Number.isFinite(n))
            return undefined;
        return Math.floor(n);
    }
    escapeLikePattern(value) {
        return value
            .replace(/\\/g, '\\\\')
            .replace(/%/g, '\\%')
            .replace(/_/g, '\\_')
            .replace(/'/g, "''");
    }
    resultToRows(result) {
        if (!result || !result.columns || !result.rows) {
            return [];
        }
        return result.rows.map((row) => {
            const obj = {};
            result.columns.forEach((col, idx) => {
                obj[col] = row[idx];
            });
            return obj;
        });
    }
}
exports.FrameStatsAnalyzer = FrameStatsAnalyzer;
//# sourceMappingURL=frameStatsAnalysis.js.map