@uwa4d/openapi-mcp 0.2.0-beta.0 → 0.2.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -9
- package/dist/annotate-dashboard.d.ts +48 -0
- package/dist/annotate-dashboard.js +413 -0
- package/dist/cli.js +29 -6
- package/dist/client.js +4 -13
- package/dist/composite/helpers.d.ts +19 -0
- package/dist/composite/helpers.js +90 -0
- package/dist/composite/index.d.ts +25 -0
- package/dist/composite/index.js +46 -0
- package/dist/composite/name-pattern.d.ts +8 -0
- package/dist/composite/name-pattern.js +25 -0
- package/dist/composite/overview-view.d.ts +33 -0
- package/dist/composite/overview-view.js +373 -0
- package/dist/composite/report-diagnosis.d.ts +2 -0
- package/dist/composite/report-diagnosis.js +347 -0
- package/dist/composite/route-overview.d.ts +72 -0
- package/dist/composite/route-overview.js +233 -0
- package/dist/composite/stack-agg.d.ts +77 -0
- package/dist/composite/stack-agg.js +409 -0
- package/dist/composite/stack-test-mode.d.ts +33 -0
- package/dist/composite/stack-test-mode.js +60 -0
- package/dist/composite/top-functions.d.ts +21 -0
- package/dist/composite/top-functions.js +178 -0
- package/dist/composite/top-resources.d.ts +12 -0
- package/dist/composite/top-resources.js +263 -0
- package/dist/composite/types.d.ts +25 -0
- package/dist/composite/types.js +2 -0
- package/dist/error-hints.d.ts +23 -0
- package/dist/error-hints.js +80 -0
- package/dist/indicator-dashboard-keys.d.ts +12 -0
- package/dist/indicator-dashboard-keys.js +50 -0
- package/dist/indicator-dashboard-keys.json +338 -0
- package/dist/presets.js +9 -0
- package/dist/server-instructions.d.ts +5 -0
- package/dist/server-instructions.js +17 -0
- package/dist/server.d.ts +6 -1
- package/dist/server.js +33 -5
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +399 -18
- package/dist/version-check.d.ts +14 -0
- package/dist/version-check.js +94 -0
- package/dist/version-guide.d.ts +19 -0
- package/dist/version-guide.js +223 -0
- package/package.json +2 -2
- package/spec/overrides.json +26 -0
- package/spec/uwa-openapi.json +495 -111
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { UwaClient } from '../client.js';
|
|
2
|
+
import type { ReportIdentity } from './route-overview.js';
|
|
3
|
+
import { type OverviewStackTestMode } from './stack-test-mode.js';
|
|
4
|
+
export type { OverviewStackTestMode, OverviewStackTestModeProfile } from './stack-test-mode.js';
|
|
5
|
+
export type StackMetric = 'selfTime' | 'totalTime' | 'callCount' | 'selfMemory';
|
|
6
|
+
export interface StackAggRow {
|
|
7
|
+
methodId: string;
|
|
8
|
+
name: string;
|
|
9
|
+
/** 自身耗时合计(微秒);CPU 类模式 */
|
|
10
|
+
selfTimeUs: number;
|
|
11
|
+
/** 总耗时合计(微秒);仅供参考,同名合并时可能有父子重叠语义问题 */
|
|
12
|
+
totalTimeUs: number;
|
|
13
|
+
callCount: number;
|
|
14
|
+
callFrameCount: number;
|
|
15
|
+
/** 自身内存合计(KB);Lua 内存类模式 */
|
|
16
|
+
selfMemoryKb: number;
|
|
17
|
+
totalMemoryKb: number;
|
|
18
|
+
/** 该函数在树中出现的节点数(合并前) */
|
|
19
|
+
nodeCount: number;
|
|
20
|
+
}
|
|
21
|
+
export interface StackAggResult {
|
|
22
|
+
testMode: string | null;
|
|
23
|
+
/** 仅 Overview:堆栈采集模式 CPU_ONLY / LUA_MEM_ONLY / CPU_AND_LUA_MEM */
|
|
24
|
+
stackTestMode?: OverviewStackTestMode | null;
|
|
25
|
+
availableMetrics?: StackMetric[];
|
|
26
|
+
availableFields?: string[];
|
|
27
|
+
metricGuide?: string;
|
|
28
|
+
metric: StackMetric;
|
|
29
|
+
unit: string;
|
|
30
|
+
rows: Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
methodId: string;
|
|
33
|
+
stackMethodId?: string;
|
|
34
|
+
value: number;
|
|
35
|
+
unit: string;
|
|
36
|
+
selfTimeMean?: number | null;
|
|
37
|
+
totalTimeMean?: number | null;
|
|
38
|
+
callCountTotal?: number | null;
|
|
39
|
+
selfMemoryMean?: number | null;
|
|
40
|
+
selfMemoryTotal?: number | null;
|
|
41
|
+
selfMemoryUnit?: string | null;
|
|
42
|
+
selfMemoryKb?: number;
|
|
43
|
+
callCount: number;
|
|
44
|
+
callFrameCount: number;
|
|
45
|
+
nodeCount: number;
|
|
46
|
+
source: string;
|
|
47
|
+
}>;
|
|
48
|
+
totalNodes: number;
|
|
49
|
+
uniqueFunctions: number;
|
|
50
|
+
apisUsed: string[];
|
|
51
|
+
}
|
|
52
|
+
interface TreeNode {
|
|
53
|
+
depth: number;
|
|
54
|
+
sampleId: string;
|
|
55
|
+
methodId: string;
|
|
56
|
+
totalTimeUs: number;
|
|
57
|
+
selfTimeUs: number;
|
|
58
|
+
callCount: number;
|
|
59
|
+
callFrameCount: number;
|
|
60
|
+
totalMemoryKb: number;
|
|
61
|
+
selfMemoryKb: number;
|
|
62
|
+
}
|
|
63
|
+
export declare function aggregateOverviewStackFromStatistic(client: UwaClient, identity: ReportIdentity, topN: number, metricInput?: StackMetric, threadName?: string, namePattern?: string): Promise<StackAggResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Overview:线程堆栈树(主线程)+ 函数 idmap → 按函数名合并节点。
|
|
66
|
+
* 同名(同一 methodId)节点的 selfTime / callCount 相加,再按指标排序取 Top N。
|
|
67
|
+
*/
|
|
68
|
+
export declare function aggregateOverviewStack(client: UwaClient, identity: ReportIdentity, topN: number, metric?: StackMetric): Promise<StackAggResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Mono:正向堆栈树 + Mono 函数 idmap。
|
|
71
|
+
* 文本格式与 Overview CSV 不同时走宽松解析;失败则抛错由上层处理。
|
|
72
|
+
*/
|
|
73
|
+
export declare function aggregateMonoStack(client: UwaClient, identity: ReportIdentity, topN: number, metric?: StackMetric): Promise<StackAggResult>;
|
|
74
|
+
/**
|
|
75
|
+
* 解析无表头 CSV。列数随 testMode 变化;UE 固定 7 列(与 CPU_ONLY 同形)。
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseStackCsv(csvText: string, testMode: string | null): TreeNode[];
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { isObj } from './helpers.js';
|
|
2
|
+
import { assertOverviewMetricSupported, inferOverviewStackTestMode, normalizeOverviewStackTestMode, OVERVIEW_STACK_TEST_MODE_PROFILES, resolveOverviewDefaultMetric, } from './stack-test-mode.js';
|
|
3
|
+
import { matchesNamePattern } from './name-pattern.js';
|
|
4
|
+
/**
|
|
5
|
+
* Overview:函数 idmap 统计(v1.0.2)+ Overview stackTestMode → Top N。
|
|
6
|
+
* stackTestMode 仅 Overview 有效;决定哪些统计字段/metric 可用。
|
|
7
|
+
*/
|
|
8
|
+
function parseStatNumber(obj) {
|
|
9
|
+
if (!isObj(obj) || obj['value'] == null)
|
|
10
|
+
return null;
|
|
11
|
+
const n = Number(obj['value']);
|
|
12
|
+
return Number.isFinite(n) ? n : null;
|
|
13
|
+
}
|
|
14
|
+
function parseStatUnit(obj) {
|
|
15
|
+
if (!isObj(obj))
|
|
16
|
+
return null;
|
|
17
|
+
const unit = obj['unit'];
|
|
18
|
+
if (!isObj(unit) || unit['value'] == null)
|
|
19
|
+
return null;
|
|
20
|
+
return String(unit['value']);
|
|
21
|
+
}
|
|
22
|
+
async function fetchOverviewStackTestMode(client, identity) {
|
|
23
|
+
if (identity.engine === 'unreal') {
|
|
24
|
+
return { stackTestMode: 'CPU_ONLY', apisUsed: [] };
|
|
25
|
+
}
|
|
26
|
+
const keyQuery = identity.dataKey
|
|
27
|
+
? { dataKey: identity.dataKey }
|
|
28
|
+
: { recordId: identity.recordId };
|
|
29
|
+
try {
|
|
30
|
+
const meta = (await client.call('GET', '/openapi/v1/data/gotonline/overview/stack/overall/tree/presign', 'v1.0.1', keyQuery));
|
|
31
|
+
return {
|
|
32
|
+
stackTestMode: normalizeOverviewStackTestMode(meta['testMode']),
|
|
33
|
+
apisUsed: ['gotonline_overview_stack_overall_tree_presign'],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { stackTestMode: null, apisUsed: [] };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function aggregateOverviewStackFromStatistic(client, identity, topN, metricInput, threadName, namePattern) {
|
|
41
|
+
const keyQuery = identity.dataKey
|
|
42
|
+
? { dataKey: identity.dataKey }
|
|
43
|
+
: { recordId: identity.recordId };
|
|
44
|
+
if (identity.engine === 'unreal') {
|
|
45
|
+
keyQuery['threadName'] = threadName?.trim() || 'GameThread';
|
|
46
|
+
}
|
|
47
|
+
const apisUsed = [];
|
|
48
|
+
const [{ stackTestMode: presignMode, apisUsed: presignApis }, raw] = await Promise.all([
|
|
49
|
+
fetchOverviewStackTestMode(client, identity),
|
|
50
|
+
client.call('GET', '/openapi/v1/data/gotonline/overview/method/idmap/statistic', 'v1.0.2', keyQuery),
|
|
51
|
+
]);
|
|
52
|
+
apisUsed.push(...presignApis, 'gotonline_overview_method_idmap_statistic');
|
|
53
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
54
|
+
if (!list.length) {
|
|
55
|
+
throw new Error('函数 idmap 统计返回空列表(报告可能未开堆栈或 SDK < 2.5.1)');
|
|
56
|
+
}
|
|
57
|
+
const rows = [];
|
|
58
|
+
for (const item of list) {
|
|
59
|
+
if (!isObj(item))
|
|
60
|
+
continue;
|
|
61
|
+
const methodId = item['stackMethodId'] != null ? String(item['stackMethodId']) : '';
|
|
62
|
+
const name = item['stackMethodName'] != null ? String(item['stackMethodName']) : methodId;
|
|
63
|
+
if (!methodId)
|
|
64
|
+
continue;
|
|
65
|
+
const memMeanObj = item['selfMemoryMean'];
|
|
66
|
+
const memTotalObj = item['selfMemoryTotal'];
|
|
67
|
+
const selfTimeMean = parseStatNumber(item['selfTimeMean']);
|
|
68
|
+
const totalTimeMean = parseStatNumber(item['totalTimeMean']);
|
|
69
|
+
const callCountTotal = parseStatNumber(item['callCountTotal']);
|
|
70
|
+
const selfMemoryMean = parseStatNumber(memMeanObj);
|
|
71
|
+
const selfMemoryTotal = parseStatNumber(memTotalObj);
|
|
72
|
+
const selfMemoryUnit = parseStatUnit(memMeanObj) ?? parseStatUnit(memTotalObj);
|
|
73
|
+
if (selfTimeMean == null &&
|
|
74
|
+
totalTimeMean == null &&
|
|
75
|
+
callCountTotal == null &&
|
|
76
|
+
selfMemoryMean == null &&
|
|
77
|
+
selfMemoryTotal == null) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
rows.push({
|
|
81
|
+
stackMethodId: methodId,
|
|
82
|
+
name,
|
|
83
|
+
selfTimeMean,
|
|
84
|
+
totalTimeMean,
|
|
85
|
+
callCountTotal,
|
|
86
|
+
selfMemoryMean,
|
|
87
|
+
selfMemoryTotal,
|
|
88
|
+
selfMemoryUnit,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (!rows.length) {
|
|
92
|
+
throw new Error('函数 idmap 统计无有效数据');
|
|
93
|
+
}
|
|
94
|
+
const hasTime = rows.some((r) => r.selfTimeMean != null || r.totalTimeMean != null);
|
|
95
|
+
const hasMem = rows.some((r) => r.selfMemoryMean != null || r.selfMemoryTotal != null);
|
|
96
|
+
const stackTestMode = presignMode ?? inferOverviewStackTestMode({ hasTime, hasMem });
|
|
97
|
+
if (!stackTestMode) {
|
|
98
|
+
throw new Error('无法判断 Overview stackTestMode');
|
|
99
|
+
}
|
|
100
|
+
const profile = OVERVIEW_STACK_TEST_MODE_PROFILES[stackTestMode];
|
|
101
|
+
const metric = metricInput ?? resolveOverviewDefaultMetric(stackTestMode);
|
|
102
|
+
assertOverviewMetricSupported(metric, profile);
|
|
103
|
+
const metricValue = (r) => {
|
|
104
|
+
if (metric === 'totalTime')
|
|
105
|
+
return r.totalTimeMean != null ? r.totalTimeMean : -1;
|
|
106
|
+
if (metric === 'callCount')
|
|
107
|
+
return r.callCountTotal != null ? r.callCountTotal : -1;
|
|
108
|
+
if (metric === 'selfMemory') {
|
|
109
|
+
if (r.selfMemoryMean != null)
|
|
110
|
+
return r.selfMemoryMean;
|
|
111
|
+
if (r.selfMemoryTotal != null)
|
|
112
|
+
return r.selfMemoryTotal;
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
return r.selfTimeMean != null ? r.selfTimeMean : -1;
|
|
116
|
+
};
|
|
117
|
+
const sorted = rows.sort((a, b) => metricValue(b) - metricValue(a));
|
|
118
|
+
const pattern = namePattern?.trim();
|
|
119
|
+
const matched = pattern ? sorted.filter((r) => matchesNamePattern(r.name, pattern)) : sorted;
|
|
120
|
+
const unit = metric === 'callCount' ? '次' : metric === 'selfMemory' ? rows.find((r) => r.selfMemoryUnit)?.selfMemoryUnit ?? 'KB' : 'ms';
|
|
121
|
+
return {
|
|
122
|
+
testMode: stackTestMode,
|
|
123
|
+
stackTestMode,
|
|
124
|
+
availableMetrics: [...profile.availableMetrics],
|
|
125
|
+
availableFields: [...profile.availableFields],
|
|
126
|
+
metricGuide: profile.description,
|
|
127
|
+
metric,
|
|
128
|
+
unit,
|
|
129
|
+
rows: matched.slice(0, topN).map((r) => ({
|
|
130
|
+
name: r.name,
|
|
131
|
+
methodId: r.stackMethodId,
|
|
132
|
+
stackMethodId: r.stackMethodId,
|
|
133
|
+
selfTimeMean: r.selfTimeMean != null ? round2(r.selfTimeMean) : null,
|
|
134
|
+
totalTimeMean: r.totalTimeMean != null ? round2(r.totalTimeMean) : null,
|
|
135
|
+
callCountTotal: r.callCountTotal != null ? Math.round(r.callCountTotal) : null,
|
|
136
|
+
selfMemoryMean: r.selfMemoryMean != null ? round2(r.selfMemoryMean) : null,
|
|
137
|
+
selfMemoryTotal: r.selfMemoryTotal != null ? round2(r.selfMemoryTotal) : null,
|
|
138
|
+
selfMemoryUnit: r.selfMemoryUnit,
|
|
139
|
+
value: metric === 'callCount'
|
|
140
|
+
? Math.round(metricValue(r) >= 0 ? metricValue(r) : 0)
|
|
141
|
+
: round2(metricValue(r) >= 0 ? metricValue(r) : 0),
|
|
142
|
+
unit,
|
|
143
|
+
callCount: r.callCountTotal != null ? Math.round(r.callCountTotal) : 0,
|
|
144
|
+
callFrameCount: 0,
|
|
145
|
+
nodeCount: 1,
|
|
146
|
+
source: 'overview.method.idmap.statistic',
|
|
147
|
+
})),
|
|
148
|
+
totalNodes: rows.length,
|
|
149
|
+
uniqueFunctions: rows.length,
|
|
150
|
+
apisUsed,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Overview:线程堆栈树(主线程)+ 函数 idmap → 按函数名合并节点。
|
|
155
|
+
* 同名(同一 methodId)节点的 selfTime / callCount 相加,再按指标排序取 Top N。
|
|
156
|
+
*/
|
|
157
|
+
export async function aggregateOverviewStack(client, identity, topN, metric = 'selfTime') {
|
|
158
|
+
const keyQuery = identity.dataKey
|
|
159
|
+
? { dataKey: identity.dataKey }
|
|
160
|
+
: { recordId: identity.recordId };
|
|
161
|
+
const apisUsed = [];
|
|
162
|
+
const [treeMeta, idMap] = await Promise.all([
|
|
163
|
+
client.call('GET', '/openapi/v1/data/gotonline/overview/stack/overall/tree/presign', 'v1.0.1', keyQuery),
|
|
164
|
+
fetchFunctionIdMap(client, keyQuery),
|
|
165
|
+
]);
|
|
166
|
+
apisUsed.push('gotonline_overview_stack_overall_tree_presign', 'gotonline_overview_stack_id_map');
|
|
167
|
+
const url = typeof treeMeta['dataPresignUrl'] === 'string' ? treeMeta['dataPresignUrl'] : null;
|
|
168
|
+
if (!url)
|
|
169
|
+
throw new Error('线程堆栈树未返回 dataPresignUrl(报告可能未开 CPU 堆栈或 SDK < 2.5.1)');
|
|
170
|
+
const testMode = treeMeta['testMode'] != null ? String(treeMeta['testMode']) : null;
|
|
171
|
+
const payload = await client.downloadPresign(url);
|
|
172
|
+
const csvText = payload.kind === 'text' ? (payload.text ?? '') : JSON.stringify(payload.json ?? '');
|
|
173
|
+
const nodes = parseStackCsv(csvText, testMode);
|
|
174
|
+
return finalizeAgg(nodes, idMap, topN, metric, testMode, apisUsed, 'overview.stack.overall');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Mono:正向堆栈树 + Mono 函数 idmap。
|
|
178
|
+
* 文本格式与 Overview CSV 不同时走宽松解析;失败则抛错由上层处理。
|
|
179
|
+
*/
|
|
180
|
+
export async function aggregateMonoStack(client, identity, topN, metric = 'selfMemory') {
|
|
181
|
+
const keyQuery = identity.dataKey
|
|
182
|
+
? { dataKey: identity.dataKey }
|
|
183
|
+
: { recordId: identity.recordId };
|
|
184
|
+
const apisUsed = [];
|
|
185
|
+
const [treeMeta, idMapRaw] = await Promise.all([
|
|
186
|
+
client.call('GET', '/openapi/v1/data/gotonline/mono/stack/tree/overall/presign', 'v1.0.1', keyQuery),
|
|
187
|
+
client.call('GET', '/openapi/v1/data/gotonline/mono/stack/method/idmap/presign', 'v1.0.1', keyQuery),
|
|
188
|
+
]);
|
|
189
|
+
apisUsed.push('gotonline_mono_stack_tree_overall_presign', 'gotonline_mono_stack_method_idmap_presign');
|
|
190
|
+
const treeUrl = typeof treeMeta['dataPresignUrl'] === 'string' ? treeMeta['dataPresignUrl'] : null;
|
|
191
|
+
const mapUrl = typeof idMapRaw['dataPresignUrl'] === 'string' ? idMapRaw['dataPresignUrl'] : null;
|
|
192
|
+
if (!treeUrl)
|
|
193
|
+
throw new Error('Mono 堆栈树未返回 dataPresignUrl');
|
|
194
|
+
if (!mapUrl)
|
|
195
|
+
throw new Error('Mono 函数 idmap 未返回 dataPresignUrl');
|
|
196
|
+
const [treePayload, mapPayload] = await Promise.all([
|
|
197
|
+
client.downloadPresign(treeUrl),
|
|
198
|
+
client.downloadPresign(mapUrl),
|
|
199
|
+
]);
|
|
200
|
+
const idMap = parseIdMapPayload(mapPayload.json ?? mapPayload.text);
|
|
201
|
+
const csvText = treePayload.kind === 'text' ? (treePayload.text ?? '') : JSON.stringify(treePayload.json ?? '');
|
|
202
|
+
// Mono 正向树多为文本/CSV;按 CPU_ONLY/内存列尝试解析
|
|
203
|
+
const nodes = parseStackCsv(csvText, 'LUA_MEM_ONLY');
|
|
204
|
+
const useMetric = metric === 'selfTime' || metric === 'totalTime' ? 'selfMemory' : metric;
|
|
205
|
+
return finalizeAgg(nodes, idMap, topN, useMetric, 'LUA_MEM_ONLY', apisUsed, 'mono.stack.overall');
|
|
206
|
+
}
|
|
207
|
+
async function fetchFunctionIdMap(client, keyQuery) {
|
|
208
|
+
const raw = await client.call('GET', '/openapi/v1/data/gotonline/overview/stack/id/map', 'v1.0.1', keyQuery);
|
|
209
|
+
return parseIdMapPayload(raw);
|
|
210
|
+
}
|
|
211
|
+
function parseIdMapPayload(raw) {
|
|
212
|
+
const map = new Map();
|
|
213
|
+
let list = null;
|
|
214
|
+
if (isObj(raw)) {
|
|
215
|
+
list = raw['id_map'] ?? raw['stats_id_map'] ?? raw['idMap'];
|
|
216
|
+
}
|
|
217
|
+
if (!list && typeof raw === 'string') {
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(raw);
|
|
220
|
+
if (isObj(parsed))
|
|
221
|
+
list = parsed['id_map'] ?? parsed['stats_id_map'];
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* ignore */
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!Array.isArray(list))
|
|
228
|
+
return map;
|
|
229
|
+
for (const item of list) {
|
|
230
|
+
if (!isObj(item))
|
|
231
|
+
continue;
|
|
232
|
+
const id = item['id'] != null ? String(item['id']) : '';
|
|
233
|
+
const name = item['name'] != null ? String(item['name']) : '';
|
|
234
|
+
if (id)
|
|
235
|
+
map.set(id, name || id);
|
|
236
|
+
}
|
|
237
|
+
return map;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 解析无表头 CSV。列数随 testMode 变化;UE 固定 7 列(与 CPU_ONLY 同形)。
|
|
241
|
+
*/
|
|
242
|
+
export function parseStackCsv(csvText, testMode) {
|
|
243
|
+
const lines = csvText
|
|
244
|
+
.replace(/^\uFEFF/, '')
|
|
245
|
+
.split(/\r?\n/)
|
|
246
|
+
.map((l) => l.trim())
|
|
247
|
+
.filter(Boolean);
|
|
248
|
+
const nodes = [];
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
const cols = splitCsvLine(line);
|
|
251
|
+
if (cols.length < 6)
|
|
252
|
+
continue;
|
|
253
|
+
const depth = Number(cols[0]);
|
|
254
|
+
const sampleId = String(cols[1] ?? '');
|
|
255
|
+
const methodId = String(cols[2] ?? '');
|
|
256
|
+
if (!methodId || Number.isNaN(depth))
|
|
257
|
+
continue;
|
|
258
|
+
const mode = (testMode ?? inferMode(cols.length)).toUpperCase();
|
|
259
|
+
let totalTimeUs = 0;
|
|
260
|
+
let selfTimeUs = 0;
|
|
261
|
+
let callCount = 0;
|
|
262
|
+
let callFrameCount = 0;
|
|
263
|
+
let totalMemoryKb = 0;
|
|
264
|
+
let selfMemoryKb = 0;
|
|
265
|
+
if (mode === 'LUA_MEM_ONLY' || (cols.length === 7 && mode.includes('LUA') && !mode.includes('CPU'))) {
|
|
266
|
+
totalMemoryKb = num(cols[3]);
|
|
267
|
+
selfMemoryKb = num(cols[4]);
|
|
268
|
+
callCount = num(cols[5]);
|
|
269
|
+
callFrameCount = num(cols[6]);
|
|
270
|
+
}
|
|
271
|
+
else if (mode === 'CPU_AND_LUA_MEM' || cols.length >= 9) {
|
|
272
|
+
totalTimeUs = num(cols[3]);
|
|
273
|
+
selfTimeUs = num(cols[4]);
|
|
274
|
+
callCount = num(cols[5]);
|
|
275
|
+
totalMemoryKb = num(cols[6]);
|
|
276
|
+
selfMemoryKb = num(cols[7]);
|
|
277
|
+
callFrameCount = num(cols[8]);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// CPU_ONLY / UE 7 列
|
|
281
|
+
totalTimeUs = num(cols[3]);
|
|
282
|
+
selfTimeUs = num(cols[4]);
|
|
283
|
+
callCount = num(cols[5]);
|
|
284
|
+
callFrameCount = num(cols[6]);
|
|
285
|
+
}
|
|
286
|
+
nodes.push({
|
|
287
|
+
depth,
|
|
288
|
+
sampleId,
|
|
289
|
+
methodId,
|
|
290
|
+
totalTimeUs,
|
|
291
|
+
selfTimeUs,
|
|
292
|
+
callCount,
|
|
293
|
+
callFrameCount,
|
|
294
|
+
totalMemoryKb,
|
|
295
|
+
selfMemoryKb,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return nodes;
|
|
299
|
+
}
|
|
300
|
+
function inferMode(colCount) {
|
|
301
|
+
if (colCount >= 9)
|
|
302
|
+
return 'CPU_AND_LUA_MEM';
|
|
303
|
+
return 'CPU_ONLY';
|
|
304
|
+
}
|
|
305
|
+
function num(v) {
|
|
306
|
+
if (v == null || v === '')
|
|
307
|
+
return 0;
|
|
308
|
+
const n = Number(v);
|
|
309
|
+
return Number.isFinite(n) ? n : 0;
|
|
310
|
+
}
|
|
311
|
+
/** 简单 CSV 分割:堆栈树字段不含引号逗号,按逗号切即可;兼容制表符。 */
|
|
312
|
+
function splitCsvLine(line) {
|
|
313
|
+
if (line.includes('\t') && !line.includes(','))
|
|
314
|
+
return line.split('\t');
|
|
315
|
+
return line.split(',');
|
|
316
|
+
}
|
|
317
|
+
function finalizeAgg(nodes, idMap, topN, metric, testMode, apisUsed, source) {
|
|
318
|
+
const byId = new Map();
|
|
319
|
+
for (const n of nodes) {
|
|
320
|
+
let row = byId.get(n.methodId);
|
|
321
|
+
if (!row) {
|
|
322
|
+
row = {
|
|
323
|
+
methodId: n.methodId,
|
|
324
|
+
name: idMap.get(n.methodId) ?? n.methodId,
|
|
325
|
+
selfTimeUs: 0,
|
|
326
|
+
totalTimeUs: 0,
|
|
327
|
+
callCount: 0,
|
|
328
|
+
callFrameCount: 0,
|
|
329
|
+
selfMemoryKb: 0,
|
|
330
|
+
totalMemoryKb: 0,
|
|
331
|
+
nodeCount: 0,
|
|
332
|
+
};
|
|
333
|
+
byId.set(n.methodId, row);
|
|
334
|
+
}
|
|
335
|
+
row.selfTimeUs += n.selfTimeUs;
|
|
336
|
+
row.totalTimeUs += n.totalTimeUs;
|
|
337
|
+
row.callCount += n.callCount;
|
|
338
|
+
row.callFrameCount += n.callFrameCount;
|
|
339
|
+
row.selfMemoryKb += n.selfMemoryKb;
|
|
340
|
+
row.totalMemoryKb += n.totalMemoryKb;
|
|
341
|
+
row.nodeCount += 1;
|
|
342
|
+
}
|
|
343
|
+
// 同名不同 methodId 再按 name 合并一次(文档要求「同一个函数的节点还需要合并」)
|
|
344
|
+
const byName = new Map();
|
|
345
|
+
for (const row of byId.values()) {
|
|
346
|
+
const key = row.name || row.methodId;
|
|
347
|
+
const exist = byName.get(key);
|
|
348
|
+
if (!exist) {
|
|
349
|
+
byName.set(key, { ...row });
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
exist.selfTimeUs += row.selfTimeUs;
|
|
353
|
+
exist.totalTimeUs += row.totalTimeUs;
|
|
354
|
+
exist.callCount += row.callCount;
|
|
355
|
+
exist.callFrameCount += row.callFrameCount;
|
|
356
|
+
exist.selfMemoryKb += row.selfMemoryKb;
|
|
357
|
+
exist.totalMemoryKb += row.totalMemoryKb;
|
|
358
|
+
exist.nodeCount += row.nodeCount;
|
|
359
|
+
// 保留首次 methodId
|
|
360
|
+
}
|
|
361
|
+
const metricOf = (r) => {
|
|
362
|
+
switch (metric) {
|
|
363
|
+
case 'totalTime':
|
|
364
|
+
return r.totalTimeUs;
|
|
365
|
+
case 'callCount':
|
|
366
|
+
return r.callCount;
|
|
367
|
+
case 'selfMemory':
|
|
368
|
+
return r.selfMemoryKb;
|
|
369
|
+
case 'selfTime':
|
|
370
|
+
default:
|
|
371
|
+
return r.selfTimeUs;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const unit = metric === 'selfMemory' ? 'KB' : metric === 'callCount' ? '次' : 'ms';
|
|
375
|
+
const sorted = [...byName.values()].sort((a, b) => metricOf(b) - metricOf(a));
|
|
376
|
+
const rows = sorted.slice(0, topN).map((r) => {
|
|
377
|
+
const value = metric === 'callCount'
|
|
378
|
+
? r.callCount
|
|
379
|
+
: metric === 'selfMemory'
|
|
380
|
+
? round2(r.selfMemoryKb)
|
|
381
|
+
: round2(metricOf(r) / 1000); // μs → ms
|
|
382
|
+
return {
|
|
383
|
+
name: r.name,
|
|
384
|
+
methodId: r.methodId,
|
|
385
|
+
stackMethodId: r.methodId,
|
|
386
|
+
selfTimeMean: round2(r.selfTimeUs / 1000),
|
|
387
|
+
totalTimeMean: round2(r.totalTimeUs / 1000),
|
|
388
|
+
value,
|
|
389
|
+
unit,
|
|
390
|
+
selfMemoryKb: round2(r.selfMemoryKb),
|
|
391
|
+
callCount: r.callCount,
|
|
392
|
+
callFrameCount: r.callFrameCount,
|
|
393
|
+
nodeCount: r.nodeCount,
|
|
394
|
+
source,
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
testMode,
|
|
399
|
+
metric,
|
|
400
|
+
unit,
|
|
401
|
+
rows,
|
|
402
|
+
totalNodes: nodes.length,
|
|
403
|
+
uniqueFunctions: byName.size,
|
|
404
|
+
apisUsed,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function round2(n) {
|
|
408
|
+
return Math.round(n * 100) / 100;
|
|
409
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StackMetric } from './stack-agg.js';
|
|
2
|
+
/**
|
|
3
|
+
* Overview 堆栈采集模式(来自 overview/stack/overall/tree/presign 的 testMode)。
|
|
4
|
+
* 仅适用于 Overview 报告;Mono 报告走独立堆栈,不使用本枚举。
|
|
5
|
+
*/
|
|
6
|
+
export type OverviewStackTestMode = 'CPU_ONLY' | 'LUA_MEM_ONLY' | 'CPU_AND_LUA_MEM';
|
|
7
|
+
/** @deprecated 使用 OverviewStackTestMode */
|
|
8
|
+
export type StackTestMode = OverviewStackTestMode;
|
|
9
|
+
export interface OverviewStackTestModeProfile {
|
|
10
|
+
stackTestMode: OverviewStackTestMode;
|
|
11
|
+
availableMetrics: StackMetric[];
|
|
12
|
+
availableFields: readonly string[];
|
|
13
|
+
description: string;
|
|
14
|
+
}
|
|
15
|
+
/** @deprecated 使用 OverviewStackTestModeProfile */
|
|
16
|
+
export type StackTestModeProfile = OverviewStackTestModeProfile;
|
|
17
|
+
export declare const OVERVIEW_STACK_TEST_MODE_PROFILES: Record<OverviewStackTestMode, OverviewStackTestModeProfile>;
|
|
18
|
+
export declare const STACK_TEST_MODE_PROFILES: Record<OverviewStackTestMode, OverviewStackTestModeProfile>;
|
|
19
|
+
export declare function normalizeOverviewStackTestMode(raw: unknown): OverviewStackTestMode | null;
|
|
20
|
+
/** @deprecated 使用 normalizeOverviewStackTestMode */
|
|
21
|
+
export declare const normalizeStackTestMode: typeof normalizeOverviewStackTestMode;
|
|
22
|
+
export declare function inferOverviewStackTestMode(flags: {
|
|
23
|
+
hasTime: boolean;
|
|
24
|
+
hasMem: boolean;
|
|
25
|
+
}): OverviewStackTestMode | null;
|
|
26
|
+
/** @deprecated 使用 inferOverviewStackTestMode */
|
|
27
|
+
export declare const inferStackTestMode: typeof inferOverviewStackTestMode;
|
|
28
|
+
export declare function resolveOverviewDefaultMetric(stackTestMode: OverviewStackTestMode): StackMetric;
|
|
29
|
+
/** @deprecated 使用 resolveOverviewDefaultMetric */
|
|
30
|
+
export declare const resolveDefaultMetric: typeof resolveOverviewDefaultMetric;
|
|
31
|
+
export declare function assertOverviewMetricSupported(metric: StackMetric, profile: OverviewStackTestModeProfile): void;
|
|
32
|
+
/** @deprecated 使用 assertOverviewMetricSupported */
|
|
33
|
+
export declare const assertMetricSupported: typeof assertOverviewMetricSupported;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const OVERVIEW_STACK_TEST_MODE_PROFILES = {
|
|
2
|
+
CPU_ONLY: {
|
|
3
|
+
stackTestMode: 'CPU_ONLY',
|
|
4
|
+
availableMetrics: ['selfTime', 'totalTime', 'callCount'],
|
|
5
|
+
availableFields: ['selfTimeMean', 'totalTimeMean', 'callCountTotal'],
|
|
6
|
+
description: 'CPU_ONLY:仅有耗时(selfTimeMean/totalTimeMean,ms)与调用次数(callCountTotal);内存字段恒为 null。',
|
|
7
|
+
},
|
|
8
|
+
LUA_MEM_ONLY: {
|
|
9
|
+
stackTestMode: 'LUA_MEM_ONLY',
|
|
10
|
+
availableMetrics: ['callCount', 'selfMemory'],
|
|
11
|
+
availableFields: ['callCountTotal', 'selfMemoryMean', 'selfMemoryTotal'],
|
|
12
|
+
description: 'LUA_MEM_ONLY:仅有调用次数(callCountTotal)与内存(selfMemoryMean/selfMemoryTotal);耗时字段恒为 null。',
|
|
13
|
+
},
|
|
14
|
+
CPU_AND_LUA_MEM: {
|
|
15
|
+
stackTestMode: 'CPU_AND_LUA_MEM',
|
|
16
|
+
availableMetrics: ['selfTime', 'totalTime', 'callCount', 'selfMemory'],
|
|
17
|
+
availableFields: [
|
|
18
|
+
'selfTimeMean',
|
|
19
|
+
'totalTimeMean',
|
|
20
|
+
'callCountTotal',
|
|
21
|
+
'selfMemoryMean',
|
|
22
|
+
'selfMemoryTotal',
|
|
23
|
+
],
|
|
24
|
+
description: 'CPU_AND_LUA_MEM:耗时、调用次数、内存均可用。',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export const STACK_TEST_MODE_PROFILES = OVERVIEW_STACK_TEST_MODE_PROFILES;
|
|
28
|
+
export function normalizeOverviewStackTestMode(raw) {
|
|
29
|
+
const tm = String(raw ?? '').toUpperCase();
|
|
30
|
+
if (tm === 'CPU_ONLY' || tm === 'LUA_MEM_ONLY' || tm === 'CPU_AND_LUA_MEM') {
|
|
31
|
+
return tm;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
/** @deprecated 使用 normalizeOverviewStackTestMode */
|
|
36
|
+
export const normalizeStackTestMode = normalizeOverviewStackTestMode;
|
|
37
|
+
export function inferOverviewStackTestMode(flags) {
|
|
38
|
+
if (flags.hasTime && flags.hasMem)
|
|
39
|
+
return 'CPU_AND_LUA_MEM';
|
|
40
|
+
if (flags.hasMem && !flags.hasTime)
|
|
41
|
+
return 'LUA_MEM_ONLY';
|
|
42
|
+
if (flags.hasTime)
|
|
43
|
+
return 'CPU_ONLY';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** @deprecated 使用 inferOverviewStackTestMode */
|
|
47
|
+
export const inferStackTestMode = inferOverviewStackTestMode;
|
|
48
|
+
export function resolveOverviewDefaultMetric(stackTestMode) {
|
|
49
|
+
return stackTestMode === 'LUA_MEM_ONLY' ? 'selfMemory' : 'selfTime';
|
|
50
|
+
}
|
|
51
|
+
/** @deprecated 使用 resolveOverviewDefaultMetric */
|
|
52
|
+
export const resolveDefaultMetric = resolveOverviewDefaultMetric;
|
|
53
|
+
export function assertOverviewMetricSupported(metric, profile) {
|
|
54
|
+
if (!profile.availableMetrics.includes(metric)) {
|
|
55
|
+
throw new Error(`当前 Overview stackTestMode=${profile.stackTestMode} 不支持 metric=${metric}。` +
|
|
56
|
+
`可用:${profile.availableMetrics.join('、')}。${profile.description}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** @deprecated 使用 assertOverviewMetricSupported */
|
|
60
|
+
export const assertMetricSupported = assertOverviewMetricSupported;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { UwaClient } from '../client.js';
|
|
2
|
+
import type { CompositeTool } from './types.js';
|
|
3
|
+
export interface FunctionRow {
|
|
4
|
+
name: string;
|
|
5
|
+
value: number;
|
|
6
|
+
unit?: string;
|
|
7
|
+
source: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
methodId?: string;
|
|
10
|
+
stackMethodId?: string;
|
|
11
|
+
selfTimeMean?: number | null;
|
|
12
|
+
totalTimeMean?: number | null;
|
|
13
|
+
callCountTotal?: number | null;
|
|
14
|
+
selfMemoryMean?: number | null;
|
|
15
|
+
selfMemoryTotal?: number | null;
|
|
16
|
+
selfMemoryUnit?: string | null;
|
|
17
|
+
callCount?: number;
|
|
18
|
+
nodeCount?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function runTopFunctions(client: UwaClient, args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
21
|
+
export declare const topFunctionsTool: CompositeTool;
|