UNPKG

@hippy/debug-server-next

Version:
201 lines (200 loc) 7.62 kB
"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;