"use strict";
/*
 * Tencent is pleased to support the open source community by making
 * Hippy available.
 *
 * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company.
 * All rights reserved.
 *
 * 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.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const enum_1 = require("@debug-server-next/@types/enum");
const log_1 = require("@debug-server-next/utils/log");
const log = new log_1.Logger('trace-adapter');
// 理论上 1ms 采样 1 次，实际在 1ms 左右，这里按理论值绘图
const sampleInterval = 0.001;
const timeRatio = 1000000; // 单位：秒，转换为微秒，1s = 1000000 us
class TraceAdapter {
    constructor() {
        this.v8Json = [];
        this.nodeMap = {};
    }
    static newV8Frame(frame, { ph, ts }) {
        return {
            name: frame.name || frame.url,
            ph,
            pid: 0,
            tid: 0,
            ts,
        };
    }
    static getTraceEnd(trace, prevTrace) {
        const end = trace.stackFrames.length - 1;
        // 两 trace 采样间隔 1.5（含误差兼容）个 sampleInterval，认为是两棵树
        if (trace.timestamp - prevTrace.timestamp > sampleInterval * 1.5)
            return end;
        if (!(prevTrace === null || prevTrace === void 0 ? void 0 : prevTrace.stackFrames))
            return end;
        const isSameFrame = (frame1, frame2) => !['sourceID', 'line', 'column'].some((key) => frame1[key] !== frame2[key]);
        for (let i = end, j = prevTrace.stackFrames.length - 1; i >= 0; i--, j--) {
            const frame = trace.stackFrames[i];
            const prevTraceFrame = prevTrace.stackFrames[j];
            if (prevTraceFrame)
                if (isSameFrame(frame, prevTraceFrame)) {
                    continue;
                }
                else {
                    return i;
                }
            else {
                return i;
            }
        }
        return -1;
    }
    /**
     * 深度优先遍历树，并将树结构转为类似于 [B, B, E, B, E, E] 的一维数组结构
     */
    static dfs(tree, v8Frames = []) {
        if (!tree)
            return [];
        const beginFrame = TraceAdapter.newV8Frame(tree.data, {
            ph: enum_1.PH.Begin,
            ts: tree.startTs * timeRatio,
        });
        const endFrame = TraceAdapter.newV8Frame(tree.data, {
            ph: enum_1.PH.End,
            ts: tree.endTs * timeRatio,
        });
        v8Frames.push(beginFrame);
        for (const child of tree.children) {
            TraceAdapter.dfs(child, v8Frames);
        }
        v8Frames.push(endFrame);
        return v8Frames;
    }
    /**
     * 要保证每一个调用栈 id 唯一，同一行代码有可能执行多次，需要加采样时间戳
     */
    static getFrameId(frame, ts) {
        return `${ts}-${frame.sourceID}-${frame.line}-${frame.column}`;
    }
    /**
     * 将 jsc 的 trace 数据转为 v8 的 trace 数据
     */
    jsc2v8(json) {
        const traces = json.samples.stackTraces;
        const trees = [];
        let lastTree;
        let prevTrace;
        for (const trace of traces) {
            if (lastTree) {
                const end = TraceAdapter.getTraceEnd(trace, prevTrace);
                // 整个 trace 下的 frame 与前一帧没有共用的节点，说明该帧是一个新的调用栈，要构建为一颗单独的树
                if (end === trace.stackFrames.length - 1) {
                    trees.push(lastTree);
                    lastTree = this.buildTree(trace.stackFrames, trace.timestamp);
                }
                else if (end === -1) {
                    // trace 和 prevTrace 完全重合，更新其 sampleNum
                    this.updateSampleNum(trace, prevTrace, end);
                }
                else {
                    // 有共用的节点，可添加到上一颗树，同时更新相同节点的 sampleNum
                    this.appendToTree(trace, prevTrace, end);
                    this.updateSampleNum(trace, prevTrace, end);
                }
            }
            else {
                lastTree = this.buildTree(trace.stackFrames, trace.timestamp);
            }
            prevTrace = trace;
        }
        trees.push(lastTree);
        for (const tree of trees) {
            const v8Frames = TraceAdapter.dfs(tree);
            if (v8Frames === null || v8Frames === void 0 ? void 0 : v8Frames.length)
                this.v8Json.push(...v8Frames);
        }
        return this.v8Json;
    }
    /**
     * 将一个 trace 构建为一棵 🌲
     */
    buildTree(frames, ts) {
        let child;
        for (const frame of frames) {
            const id = TraceAdapter.getFrameId(frame, ts);
            const treeNode = {
                id,
                data: frame,
                children: child ? [child] : [],
                parent: null,
                startTs: ts,
                endTs: ts + sampleInterval,
            };
            this.nodeMap[id] = treeNode;
            if (child)
                child.parent = treeNode;
            child = treeNode;
        }
        return child;
    }
    /**
     * 将 trace 下的 frames [0, end] 范围内构建为子树，并追加到父节点下
     * end + 1 后的节点，是原有树共用的节点，更新器 sampleNum 值
     */
    appendToTree(trace, prevTrace, end) {
        const frames = trace.stackFrames.slice(0, end + 1);
        const subTree = this.buildTree(frames, trace.timestamp);
        const parentNodeIndex = prevTrace.stackFrames.length - (trace.stackFrames.length - end - 1);
        const parentFrame = prevTrace.stackFrames[parentNodeIndex];
        let parentNode;
        if (parentFrame)
            parentNode = this.nodeMap[TraceAdapter.getFrameId(parentFrame, prevTrace.timestamp)];
        if (subTree && parentNode) {
            subTree.parent = parentNode;
            parentNode.children.push(subTree);
        }
        else {
            log.error('subTree not exist!');
        }
    }
    updateSampleNum(trace, prevTrace, end) {
        let parentNodeIndex;
        if (end === -1) {
            parentNodeIndex = 0;
        }
        else {
            parentNodeIndex = prevTrace.stackFrames.length - (trace.stackFrames.length - end - 1);
        }
        const parentFrame = prevTrace.stackFrames[parentNodeIndex];
        let parentNode;
        if (parentFrame)
            parentNode = this.nodeMap[TraceAdapter.getFrameId(parentFrame, prevTrace.timestamp)];
        for (let i = parentNodeIndex; i < prevTrace.stackFrames.length; i++) {
            if (!parentNode)
                break;
            const id = TraceAdapter.getFrameId(prevTrace.stackFrames[i], prevTrace.timestamp);
            if (id === parentNode.id) {
                parentNode.endTs = trace.timestamp + sampleInterval;
                parentNode = parentNode.parent;
            }
            else {
                log.error('update frame sample time error, parent node not match!');
            }
        }
    }
}
exports.default = TraceAdapter;
