@uwa4d/openapi-mcp 0.2.0-beta.2 → 0.2.0-beta.5
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 +1 -1
- package/dist/composite/overview-view.d.ts +5 -4
- package/dist/composite/overview-view.js +139 -18
- package/dist/composite/report-diagnosis.js +24 -7
- package/dist/composite/route-overview.d.ts +1 -0
- package/dist/composite/route-overview.js +2 -1
- package/dist/composite/top-resources.js +161 -82
- package/dist/server.d.ts +2 -1
- package/dist/server.js +15 -1
- package/dist/tools.js +14 -2
- package/dist/version-guide.d.ts +5 -0
- package/dist/version-guide.js +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ npx -y @uwa4d/openapi-mcp list-tools -t overview -e unity
|
|
|
128
128
|
| 工具 | 做什么 |
|
|
129
129
|
| --- | --- |
|
|
130
130
|
| `report_diagnosis` | 单份报告体检:自动选对统计 v1/v2,附带 Top 资源/函数与场景摘要 |
|
|
131
|
-
| `top_resources` | 资源内存 Top N
|
|
131
|
+
| `top_resources` | 资源内存 Top N(默认 11 种类型;`groupBy=merged` 全局 Top / `assetType` 按类型各 Top) |
|
|
132
132
|
| `top_functions` | 函数 Top N:下载堆栈树 + idmap,按函数名合并同名节点后再排序 |
|
|
133
133
|
|
|
134
134
|
不确定该调统计 1.0 还是 2.0 时,优先用这三个,不必自己判断日期和 SDK。
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Overview 统计 v1 / v2 返回结构差异很大:
|
|
3
|
-
* - v1
|
|
4
|
-
* -
|
|
2
|
+
* Overview 统计 v1 / v2 / UE 2.0 返回结构差异很大:
|
|
3
|
+
* - Unity v1:对象形 brief { fps_mean: [49] } + asset_stats
|
|
4
|
+
* - UE 2.0:数组形 brief [{ label, value }] + Resource 段
|
|
5
|
+
* - Unity v2(POST):categories[] + basicInfo{}
|
|
5
6
|
* 这里统一抽成复合工具能用的扁平视图。
|
|
6
7
|
*/
|
|
7
8
|
export interface OverviewView {
|
|
@@ -26,7 +27,7 @@ export interface OverviewView {
|
|
|
26
27
|
}[];
|
|
27
28
|
/** 是否开启资源采集;无法判定时为 null */
|
|
28
29
|
hasResource: boolean | null;
|
|
29
|
-
shape: 'v1' | 'v2' | 'unknown';
|
|
30
|
+
shape: 'v1' | 'v2' | 'ue-v2' | 'unknown';
|
|
30
31
|
}
|
|
31
32
|
/** 把 unwrap 后的报告或原始 data 统一成 OverviewView。 */
|
|
32
33
|
export declare function viewOverview(data: unknown): OverviewView;
|
|
@@ -23,6 +23,10 @@ function toBytes(value, unit) {
|
|
|
23
23
|
// 无单位时:很大的数当字节,否则当 MB(兼容 v1 brief 的 _mem_max_mb)
|
|
24
24
|
return value > 10_000 ? Math.round(value) : Math.round(value * 1024 * 1024);
|
|
25
25
|
}
|
|
26
|
+
function unitFromLabel(label) {
|
|
27
|
+
const m = /[((]\s*(MB|KB|GB|B|字节)\s*[))]/i.exec(label);
|
|
28
|
+
return m?.[1]?.toLowerCase() === '字节' ? 'b' : m?.[1]?.toLowerCase();
|
|
29
|
+
}
|
|
26
30
|
/** 排除「数量峰值」这类非内存指标。 */
|
|
27
31
|
function looksLikeMemoryMetric(label, key, unit) {
|
|
28
32
|
if (/数量|count|cnt/i.test(label) || /count|cnt/i.test(key))
|
|
@@ -32,8 +36,42 @@ function looksLikeMemoryMetric(label, key, unit) {
|
|
|
32
36
|
return /内存|memory|mem|reserved|used|heap|gfx|mono|lua/i.test(label + key);
|
|
33
37
|
}
|
|
34
38
|
function guessAssetType(label, fallback) {
|
|
35
|
-
const
|
|
36
|
-
|
|
39
|
+
const rules = [
|
|
40
|
+
[/RenderTexture/i, 'RenderTexture'],
|
|
41
|
+
[/纹理|Texture/i, 'Texture'],
|
|
42
|
+
[/网格|Mesh/i, 'Mesh'],
|
|
43
|
+
[/Shader/i, 'Shader'],
|
|
44
|
+
[/材质|Material/i, 'Material'],
|
|
45
|
+
[/动画|Animation/i, 'AnimationClip'],
|
|
46
|
+
[/音频|Audio/i, 'AudioClip'],
|
|
47
|
+
[/字体|Font/i, 'Font'],
|
|
48
|
+
[/AssetBundle/i, 'AssetBundle'],
|
|
49
|
+
[/TextAsset|文本资源/i, 'TextAsset'],
|
|
50
|
+
[/粒子|Particle/i, 'ParticleSystem'],
|
|
51
|
+
[/Lua/i, 'Lua'],
|
|
52
|
+
[/Mono/i, 'Mono'],
|
|
53
|
+
[/Gfx/i, 'Gfx'],
|
|
54
|
+
];
|
|
55
|
+
for (const [re, t] of rules)
|
|
56
|
+
if (re.test(label))
|
|
57
|
+
return t;
|
|
58
|
+
return fallback;
|
|
59
|
+
}
|
|
60
|
+
/** 把 summary / UE brief 一类 [{label,value}] 展开成 map。 */
|
|
61
|
+
function expandLabelValueRows(rows) {
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
if (!isObj(row))
|
|
65
|
+
continue;
|
|
66
|
+
const label = String(row['label'] ?? row['key'] ?? '');
|
|
67
|
+
if (!label)
|
|
68
|
+
continue;
|
|
69
|
+
out[label] = firstVal(row['value'] ?? row['data']);
|
|
70
|
+
const key = row['key'];
|
|
71
|
+
if (typeof key === 'string' && key && !(key in out))
|
|
72
|
+
out[key] = out[label];
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
37
75
|
}
|
|
38
76
|
function basicInfoMap(basicInfo) {
|
|
39
77
|
const out = {};
|
|
@@ -75,6 +113,61 @@ function parseHasResource(summary, brief) {
|
|
|
75
113
|
}
|
|
76
114
|
return null;
|
|
77
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* UE 2.0:从 Resource / resource 段判定资源采集。
|
|
118
|
+
* 「资源内存占用峰值」> 0 → true;段存在但无法确认时返回 null(不误报关闭)。
|
|
119
|
+
*/
|
|
120
|
+
function parseHasResourceFromResourceSection(report) {
|
|
121
|
+
const section = report['Resource'] ?? report['resource'];
|
|
122
|
+
if (!Array.isArray(section) || section.length === 0)
|
|
123
|
+
return null;
|
|
124
|
+
let peak = null;
|
|
125
|
+
let anyMemPositive = false;
|
|
126
|
+
for (const item of section) {
|
|
127
|
+
if (!isObj(item))
|
|
128
|
+
continue;
|
|
129
|
+
const label = String(item['label'] ?? item['key'] ?? '');
|
|
130
|
+
const n = numOf(item['value'] ?? item['data']);
|
|
131
|
+
if (n == null)
|
|
132
|
+
continue;
|
|
133
|
+
if (/^资源内存占用峰值/.test(label) || /资源内存占用峰值(MB)/.test(label))
|
|
134
|
+
peak = n;
|
|
135
|
+
const unit = unitFromLabel(label) ?? '';
|
|
136
|
+
if (looksLikeMemoryMetric(label, '', unit) && n > 0)
|
|
137
|
+
anyMemPositive = true;
|
|
138
|
+
}
|
|
139
|
+
if (peak != null && peak > 0)
|
|
140
|
+
return true;
|
|
141
|
+
if (anyMemPositive)
|
|
142
|
+
return true;
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/** 解析 Resource / resource 数组 → memory[](排除数量峰值)。 */
|
|
146
|
+
function memoryFromResourceSection(report) {
|
|
147
|
+
const section = report['Resource'] ?? report['resource'];
|
|
148
|
+
if (!Array.isArray(section))
|
|
149
|
+
return [];
|
|
150
|
+
const memory = [];
|
|
151
|
+
for (const item of section) {
|
|
152
|
+
if (!isObj(item))
|
|
153
|
+
continue;
|
|
154
|
+
const label = String(item['label'] ?? item['key'] ?? '');
|
|
155
|
+
const num = numOf(item['value'] ?? item['data']);
|
|
156
|
+
if (!label || num == null)
|
|
157
|
+
continue;
|
|
158
|
+
const unit = unitFromLabel(label) ?? 'mb';
|
|
159
|
+
if (!looksLikeMemoryMetric(label, '', unit))
|
|
160
|
+
continue;
|
|
161
|
+
// 总「资源内存占用峰值」不绑具体类型,仍保留便于看总量
|
|
162
|
+
memory.push({
|
|
163
|
+
name: label,
|
|
164
|
+
assetType: guessAssetType(label, 'Resource'),
|
|
165
|
+
memoryBytes: toBytes(num, unit),
|
|
166
|
+
_source: 'overview.Resource',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return memory;
|
|
170
|
+
}
|
|
78
171
|
function extractV2(data) {
|
|
79
172
|
const summary = basicInfoMap(data['basicInfo']);
|
|
80
173
|
const brief = {};
|
|
@@ -130,24 +223,21 @@ function extractV2(data) {
|
|
|
130
223
|
shape: 'v2',
|
|
131
224
|
};
|
|
132
225
|
}
|
|
133
|
-
function
|
|
226
|
+
function extractV1OrUe(report) {
|
|
134
227
|
const brief = {};
|
|
135
228
|
const summary = {};
|
|
136
229
|
const functions = [];
|
|
137
230
|
const memory = [];
|
|
138
|
-
|
|
231
|
+
const briefIsArray = Array.isArray(report['brief']);
|
|
232
|
+
if (briefIsArray) {
|
|
233
|
+
Object.assign(brief, expandLabelValueRows(report['brief']));
|
|
234
|
+
}
|
|
235
|
+
else if (isObj(report['brief'])) {
|
|
139
236
|
for (const [k, v] of Object.entries(report['brief']))
|
|
140
237
|
brief[k] = firstVal(v);
|
|
141
238
|
}
|
|
142
239
|
if (Array.isArray(report['summary'])) {
|
|
143
|
-
|
|
144
|
-
if (!isObj(row))
|
|
145
|
-
continue;
|
|
146
|
-
const label = String(row['label'] ?? row['key'] ?? '');
|
|
147
|
-
if (!label)
|
|
148
|
-
continue;
|
|
149
|
-
summary[label] = firstVal(row['value'] ?? row['data']);
|
|
150
|
-
}
|
|
240
|
+
Object.assign(summary, expandLabelValueRows(report['summary']));
|
|
151
241
|
}
|
|
152
242
|
else if (isObj(report['reportInfo'])) {
|
|
153
243
|
Object.assign(summary, report['reportInfo']);
|
|
@@ -178,6 +268,29 @@ function extractV1(report) {
|
|
|
178
268
|
});
|
|
179
269
|
}
|
|
180
270
|
}
|
|
271
|
+
// UE 数组 brief 里也可能夹带内存类指标(类型级)
|
|
272
|
+
if (briefIsArray) {
|
|
273
|
+
for (const [label, v] of Object.entries(brief)) {
|
|
274
|
+
if (!looksLikeMemoryMetric(label, '', unitFromLabel(label) ?? ''))
|
|
275
|
+
continue;
|
|
276
|
+
const num = numOf(v);
|
|
277
|
+
if (num == null)
|
|
278
|
+
continue;
|
|
279
|
+
if (memory.some((m) => m.name === label))
|
|
280
|
+
continue;
|
|
281
|
+
memory.push({
|
|
282
|
+
name: label,
|
|
283
|
+
assetType: guessAssetType(label, 'brief'),
|
|
284
|
+
memoryBytes: toBytes(num, unitFromLabel(label) ?? 'mb'),
|
|
285
|
+
_source: 'overview.brief',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const row of memoryFromResourceSection(report)) {
|
|
290
|
+
if (memory.some((m) => m.name === row.name))
|
|
291
|
+
continue;
|
|
292
|
+
memory.push(row);
|
|
293
|
+
}
|
|
181
294
|
if (Array.isArray(report['asset_stats'])) {
|
|
182
295
|
for (const item of report['asset_stats']) {
|
|
183
296
|
if (!isObj(item))
|
|
@@ -191,7 +304,7 @@ function extractV1(report) {
|
|
|
191
304
|
const looksMb = /mb|内存/i.test(label);
|
|
192
305
|
memory.push({
|
|
193
306
|
name: label,
|
|
194
|
-
assetType: label,
|
|
307
|
+
assetType: guessAssetType(label, label),
|
|
195
308
|
memoryBytes: looksMb ? toBytes(num, 'mb') : toBytes(num, 'b'),
|
|
196
309
|
_source: 'overview.asset_stats',
|
|
197
310
|
});
|
|
@@ -210,13 +323,15 @@ function extractV1(report) {
|
|
|
210
323
|
}
|
|
211
324
|
functions.sort((a, b) => b.value - a.value);
|
|
212
325
|
memory.sort((a, b) => b.memoryBytes - a.memoryBytes);
|
|
326
|
+
const fromSection = parseHasResourceFromResourceSection(report);
|
|
327
|
+
const fromFields = parseHasResource(summary, brief);
|
|
213
328
|
return {
|
|
214
329
|
brief,
|
|
215
330
|
summary,
|
|
216
331
|
functions,
|
|
217
332
|
memory,
|
|
218
|
-
hasResource:
|
|
219
|
-
shape: 'v1',
|
|
333
|
+
hasResource: fromSection ?? fromFields,
|
|
334
|
+
shape: briefIsArray ? 'ue-v2' : 'v1',
|
|
220
335
|
};
|
|
221
336
|
}
|
|
222
337
|
/** 把 unwrap 后的报告或原始 data 统一成 OverviewView。 */
|
|
@@ -243,10 +358,16 @@ export function viewOverview(data) {
|
|
|
243
358
|
if (Array.isArray(v) && v.length && isObj(v[0]))
|
|
244
359
|
return viewOverview(v[0]);
|
|
245
360
|
}
|
|
246
|
-
if ('brief' in root ||
|
|
247
|
-
|
|
361
|
+
if ('brief' in root ||
|
|
362
|
+
'func' in root ||
|
|
363
|
+
'summary' in root ||
|
|
364
|
+
'asset_stats' in root ||
|
|
365
|
+
'Resource' in root ||
|
|
366
|
+
'resource' in root) {
|
|
367
|
+
return extractV1OrUe(root);
|
|
368
|
+
}
|
|
248
369
|
// 可能整包就是 v2 包在更深一层
|
|
249
370
|
if (values.length && isObj(values[0]) && Array.isArray(values[0]['categories']))
|
|
250
371
|
return extractV2(values[0]);
|
|
251
|
-
return
|
|
372
|
+
return extractV1OrUe(root);
|
|
252
373
|
}
|
|
@@ -20,22 +20,33 @@ function summarizeScenes(data) {
|
|
|
20
20
|
}
|
|
21
21
|
return { keys: Object.keys(obj).slice(0, 30) };
|
|
22
22
|
}
|
|
23
|
-
/** brief
|
|
23
|
+
/** brief 全量可能很长,体检只保留常见关键指标(含 Unity key 与 UE 中文 label)。 */
|
|
24
24
|
function pickImportantBrief(brief) {
|
|
25
|
-
const
|
|
25
|
+
const preferExact = [
|
|
26
26
|
'fps_mean',
|
|
27
27
|
'fps',
|
|
28
28
|
'FPS均值',
|
|
29
|
+
'FPS均值(帧/秒)',
|
|
29
30
|
'jank_rate',
|
|
31
|
+
'Jank均值',
|
|
32
|
+
'Jank均值(次/分钟)',
|
|
30
33
|
'cpu_freq_mean',
|
|
31
34
|
'Total_Reserved_Memory_Bytes_maximum',
|
|
32
35
|
'Reserved Total峰值',
|
|
36
|
+
'设备内存峰值(MB)',
|
|
33
37
|
'lua_total_memory_maximum',
|
|
34
38
|
];
|
|
39
|
+
const preferLoose = [/fps/i, /jank/i, /设备内存/, /reserved.*memory/i, /卡顿/];
|
|
35
40
|
const out = {};
|
|
36
|
-
for (const k of
|
|
41
|
+
for (const k of preferExact)
|
|
37
42
|
if (brief[k] !== undefined)
|
|
38
43
|
out[k] = brief[k];
|
|
44
|
+
for (const [k, v] of Object.entries(brief)) {
|
|
45
|
+
if (k in out)
|
|
46
|
+
continue;
|
|
47
|
+
if (preferLoose.some((re) => re.test(k)))
|
|
48
|
+
out[k] = v;
|
|
49
|
+
}
|
|
39
50
|
let n = 0;
|
|
40
51
|
for (const [k, v] of Object.entries(brief)) {
|
|
41
52
|
if (k in out)
|
|
@@ -72,7 +83,6 @@ export const reportDiagnosisTool = {
|
|
|
72
83
|
const { target, data } = await fetchOverviewStatistic(ctx.client, identity);
|
|
73
84
|
apisUsed.push(target.id);
|
|
74
85
|
const view = viewOverview(data);
|
|
75
|
-
const hasRes = view.hasResource === true;
|
|
76
86
|
let topResources = null;
|
|
77
87
|
try {
|
|
78
88
|
topResources = await runTopResources(ctx.client, { ...key, topN: 10 });
|
|
@@ -116,8 +126,10 @@ export const reportDiagnosisTool = {
|
|
|
116
126
|
catch (e) {
|
|
117
127
|
limitations.push(`场景统计失败:${e instanceof Error ? e.message : String(e)}`);
|
|
118
128
|
}
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
// 仅在明确未开启时提示;hasResource === null 表示无法判定,不误报关闭
|
|
130
|
+
if (view.hasResource === false) {
|
|
131
|
+
limitations.push('未开启资源采集,逐资源 Top 可能为空');
|
|
132
|
+
}
|
|
121
133
|
const result = {
|
|
122
134
|
report: {
|
|
123
135
|
dataKey: identity.dataKey,
|
|
@@ -130,10 +142,15 @@ export const reportDiagnosisTool = {
|
|
|
130
142
|
},
|
|
131
143
|
routedOverview: target.id,
|
|
132
144
|
overviewShape: view.shape,
|
|
145
|
+
hasResource: view.hasResource,
|
|
133
146
|
summary: view.summary,
|
|
134
147
|
brief: pickImportantBrief(view.brief),
|
|
135
148
|
topResources: topResources
|
|
136
|
-
? {
|
|
149
|
+
? {
|
|
150
|
+
items: topResources['items'],
|
|
151
|
+
byType: topResources['byType'],
|
|
152
|
+
note: topResources['_note'],
|
|
153
|
+
}
|
|
137
154
|
: null,
|
|
138
155
|
topFunctions: topFunctions
|
|
139
156
|
? {
|
|
@@ -56,6 +56,7 @@ export declare function fetchReportIdentity(client: UwaClient, key: {
|
|
|
56
56
|
export declare function resolveOverviewTarget(identity: ReportIdentity): OverviewTarget;
|
|
57
57
|
/** Unity 场景统计路由;UE 无独立 scene v1/v2 对,返回 null。 */
|
|
58
58
|
export declare function resolveSceneTarget(identity: ReportIdentity): SceneTarget | null;
|
|
59
|
+
/** Unity / UE 共用 AT 资源总览列表;解析日 ≥ 2026-06-25 时优先走这条。 */
|
|
59
60
|
export declare function preferAtResourceTable(identity: ReportIdentity): boolean;
|
|
60
61
|
/** 调用已路由的 Overview 统计,返回原始 data + 路由元信息。 */
|
|
61
62
|
export declare function fetchOverviewStatistic(client: UwaClient, identity: ReportIdentity): Promise<{
|
|
@@ -136,8 +136,9 @@ export function resolveSceneTarget(identity) {
|
|
|
136
136
|
apiVersion: 'v1.0.1',
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
+
/** Unity / UE 共用 AT 资源总览列表;解析日 ≥ 2026-06-25 时优先走这条。 */
|
|
139
140
|
export function preferAtResourceTable(identity) {
|
|
140
|
-
return identity.
|
|
141
|
+
return identity.createDate != null && dayGte(identity.createDate, AT_RESOURCE_DAY);
|
|
141
142
|
}
|
|
142
143
|
/** 调用已路由的 Overview 统计,返回原始 data + 路由元信息。 */
|
|
143
144
|
export async function fetchOverviewStatistic(client, identity) {
|
|
@@ -2,28 +2,45 @@ import { z } from './types.js';
|
|
|
2
2
|
import { errorResult, isObj, jsonToolResult, requireReportKey, topNOf, } from './helpers.js';
|
|
3
3
|
import { fetchOverviewStatistic, fetchReportIdentity, preferAtResourceTable, } from './route-overview.js';
|
|
4
4
|
import { viewOverview } from './overview-view.js';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/** 与 OpenAPI memory/manage、AT overall 文档枚举对齐(11 种)。 */
|
|
6
|
+
const DEFAULT_ASSET_TYPES = [
|
|
7
|
+
'Texture',
|
|
8
|
+
'Mesh',
|
|
9
|
+
'AnimationClip',
|
|
10
|
+
'AudioClip',
|
|
11
|
+
'Material',
|
|
12
|
+
'Shader',
|
|
13
|
+
'Font',
|
|
14
|
+
'RenderTexture',
|
|
15
|
+
'ParticleSystem',
|
|
16
|
+
'AssetBundle',
|
|
17
|
+
'TextAsset',
|
|
18
|
+
];
|
|
19
|
+
/** AT 资源总览(Unity / UE 实测均可用)。 */
|
|
20
|
+
async function fetchAtAssetType(client, identity, assetType) {
|
|
21
|
+
const keyQuery = identity.dataKey
|
|
22
|
+
? { dataKey: identity.dataKey }
|
|
23
|
+
: { recordId: identity.recordId };
|
|
24
|
+
const meta = (await client.call('GET', '/openapi/v1/data/gotonline/overview/at/resource/overall/table/presign', 'v1.0.1', { ...keyQuery, assetType }));
|
|
25
|
+
const url = typeof meta['dataPresignUrl'] === 'string' ? meta['dataPresignUrl'] : null;
|
|
26
|
+
if (!url)
|
|
27
|
+
return [];
|
|
28
|
+
const payload = await client.downloadPresign(url);
|
|
29
|
+
const resources = extractAtResources(payload.json);
|
|
30
|
+
return resources.map((r) => ({
|
|
31
|
+
name: String(r['name'] ?? r['id'] ?? ''),
|
|
32
|
+
assetType,
|
|
33
|
+
memoryBytes: Number(r['memMaximum'] ?? 0) || 0,
|
|
34
|
+
count: r['countMaximum'] != null ? Number(r['countMaximum']) : undefined,
|
|
35
|
+
extras: pickExtras(r, ['id', 'typeName', 'tags', 'lifecycle', 'properties']),
|
|
36
|
+
_source: 'at/resource/overall/table/presign',
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/** Unity 旧路径:memory/manage(仅 Unity,且 AT 不可用时回退)。 */
|
|
40
|
+
async function fetchMemoryManageAssetType(client, identity, assetType) {
|
|
7
41
|
const keyQuery = identity.dataKey
|
|
8
42
|
? { dataKey: identity.dataKey }
|
|
9
43
|
: { recordId: identity.recordId };
|
|
10
|
-
if (useAtTable) {
|
|
11
|
-
const meta = (await client.call('GET', '/openapi/v1/data/gotonline/overview/at/resource/overall/table/presign', 'v1.0.1', { ...keyQuery, assetType }));
|
|
12
|
-
const url = typeof meta['dataPresignUrl'] === 'string' ? meta['dataPresignUrl'] : null;
|
|
13
|
-
if (!url)
|
|
14
|
-
return [];
|
|
15
|
-
const payload = await client.downloadPresign(url);
|
|
16
|
-
const json = payload.json;
|
|
17
|
-
const resources = extractAtResources(json);
|
|
18
|
-
return resources.map((r) => ({
|
|
19
|
-
name: String(r['name'] ?? r['id'] ?? ''),
|
|
20
|
-
assetType,
|
|
21
|
-
memoryBytes: Number(r['memMaximum'] ?? 0) || 0,
|
|
22
|
-
count: r['countMaximum'] != null ? Number(r['countMaximum']) : undefined,
|
|
23
|
-
extras: pickExtras(r, ['id', 'typeName', 'tags', 'lifecycle', 'properties']),
|
|
24
|
-
_source: 'at/resource/overall/table/presign',
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
44
|
const data = (await client.call('GET', '/openapi/v1/data/gotonline/overview/memory/manage/data/report', 'v1.0.1', { ...keyQuery, assetType }));
|
|
28
45
|
const dict = isObj(data['Asset_dict']) ? data['Asset_dict'] : {};
|
|
29
46
|
return Object.values(dict)
|
|
@@ -46,7 +63,6 @@ function extractAtResources(json) {
|
|
|
46
63
|
return [];
|
|
47
64
|
if (Array.isArray(json['resources']))
|
|
48
65
|
return json['resources'].filter(isObj);
|
|
49
|
-
// 偶发整包就是一张表
|
|
50
66
|
for (const v of Object.values(json)) {
|
|
51
67
|
if (Array.isArray(v) && v.length && isObj(v[0]) && ('memMaximum' in v[0] || 'name' in v[0])) {
|
|
52
68
|
return v.filter(isObj);
|
|
@@ -61,7 +77,6 @@ function pickExtras(r, keys) {
|
|
|
61
77
|
out[k] = r[k];
|
|
62
78
|
return Object.keys(out).length ? out : undefined;
|
|
63
79
|
}
|
|
64
|
-
/** 从 Overview 统计抽出类型级内存峰值(UE / 无逐资源列表时用)。 */
|
|
65
80
|
function typeLevelFromOverview(view) {
|
|
66
81
|
return view.memory.map((m) => ({
|
|
67
82
|
name: m.name,
|
|
@@ -70,84 +85,143 @@ function typeLevelFromOverview(view) {
|
|
|
70
85
|
_source: m._source,
|
|
71
86
|
}));
|
|
72
87
|
}
|
|
88
|
+
async function fetchPerAssetRows(client, identity, assetTypes, useAt) {
|
|
89
|
+
const apisUsed = [];
|
|
90
|
+
const errors = [];
|
|
91
|
+
const items = [];
|
|
92
|
+
const settled = await Promise.allSettled(assetTypes.map((t) => useAt ? fetchAtAssetType(client, identity, t) : fetchMemoryManageAssetType(client, identity, t)));
|
|
93
|
+
for (let i = 0; i < settled.length; i++) {
|
|
94
|
+
const r = settled[i];
|
|
95
|
+
const t = assetTypes[i];
|
|
96
|
+
if (r.status === 'fulfilled') {
|
|
97
|
+
items.push(...r.value);
|
|
98
|
+
apisUsed.push(useAt ? `at_resource_overall:${t}` : `memory_manage:${t}`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
errors.push(`${t}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
items,
|
|
106
|
+
apisUsed,
|
|
107
|
+
errors,
|
|
108
|
+
sourceKind: useAt ? 'at_resource_overall_presign' : 'memory_manage',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function groupByAssetType(items, assetTypes, topN) {
|
|
112
|
+
const byType = {};
|
|
113
|
+
for (const t of assetTypes) {
|
|
114
|
+
byType[t] = items
|
|
115
|
+
.filter((r) => r.assetType === t)
|
|
116
|
+
.sort((a, b) => b.memoryBytes - a.memoryBytes)
|
|
117
|
+
.slice(0, topN);
|
|
118
|
+
}
|
|
119
|
+
return byType;
|
|
120
|
+
}
|
|
121
|
+
function normalizeGroupBy(raw) {
|
|
122
|
+
return raw === 'assetType' ? 'assetType' : 'merged';
|
|
123
|
+
}
|
|
73
124
|
export async function runTopResources(client, args) {
|
|
74
125
|
const key = requireReportKey(args);
|
|
75
126
|
const topN = topNOf(args, 20);
|
|
76
127
|
const assetTypes = normalizeAssetTypes(args['assetTypes']);
|
|
128
|
+
const groupBy = normalizeGroupBy(args['groupBy']);
|
|
77
129
|
const identity = await fetchReportIdentity(client, key);
|
|
78
130
|
const apisUsed = ['get_report_detail'];
|
|
79
|
-
|
|
80
|
-
const { target, data } = await fetchOverviewStatistic(client, identity);
|
|
81
|
-
apisUsed.push(target.id);
|
|
82
|
-
const view = viewOverview(data);
|
|
83
|
-
const items = typeLevelFromOverview(view).slice(0, topN);
|
|
84
|
-
return {
|
|
85
|
-
engine: identity.engine,
|
|
86
|
-
dataKey: identity.dataKey,
|
|
87
|
-
topN,
|
|
88
|
-
items,
|
|
89
|
-
_note: 'UE 无逐资源内存列表接口,此处返回 Overview 统计中的类型级峰值(非单个资源)。Unity 报告可得到逐资源 Top N。',
|
|
90
|
-
_apisUsed: apisUsed,
|
|
91
|
-
_overviewShape: view.shape,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
// Unity
|
|
131
|
+
// 先拉 Overview,用于资源开关判定与最终回退
|
|
95
132
|
const { target, data } = await fetchOverviewStatistic(client, identity);
|
|
96
133
|
apisUsed.push(target.id);
|
|
97
134
|
const view = viewOverview(data);
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
135
|
+
const canAt = preferAtResourceTable(identity);
|
|
136
|
+
// Unity 旧报告可回退 memory/manage;UE 没有这条,只能 AT 或类型级峰值
|
|
137
|
+
const useAt = canAt || identity.engine === 'unreal';
|
|
138
|
+
const useMemoryManage = !useAt && identity.engine === 'unity';
|
|
139
|
+
const packResult = (items, extra) => {
|
|
140
|
+
const sorted = [...items].sort((a, b) => b.memoryBytes - a.memoryBytes);
|
|
141
|
+
if (groupBy === 'assetType') {
|
|
142
|
+
const byType = groupByAssetType(sorted, assetTypes, topN);
|
|
143
|
+
const flat = Object.values(byType).flat();
|
|
144
|
+
return {
|
|
145
|
+
engine: identity.engine,
|
|
146
|
+
dataKey: identity.dataKey,
|
|
147
|
+
createDate: identity.createDate,
|
|
148
|
+
sdkVersion: identity.sdkVersion,
|
|
149
|
+
assetTypes,
|
|
150
|
+
topN,
|
|
151
|
+
groupBy,
|
|
152
|
+
byType,
|
|
153
|
+
items: flat,
|
|
154
|
+
_overviewShape: view.shape,
|
|
155
|
+
...extra,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
101
158
|
return {
|
|
102
159
|
engine: identity.engine,
|
|
103
160
|
dataKey: identity.dataKey,
|
|
161
|
+
createDate: identity.createDate,
|
|
162
|
+
sdkVersion: identity.sdkVersion,
|
|
163
|
+
assetTypes,
|
|
104
164
|
topN,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
? '报告未开启资源采集(OverviewHasResource=0),无法拉逐资源列表;已回退为 Overview 类型级峰值。'
|
|
108
|
-
: '无法从统计结果判定是否开启资源采集;已回退为 Overview 类型级峰值。',
|
|
109
|
-
_apisUsed: apisUsed,
|
|
110
|
-
_limitations: [view.hasResource === false ? '未开启资源采集' : '资源采集状态未知'],
|
|
165
|
+
groupBy,
|
|
166
|
+
items: sorted.slice(0, topN),
|
|
111
167
|
_overviewShape: view.shape,
|
|
168
|
+
...extra,
|
|
112
169
|
};
|
|
170
|
+
};
|
|
171
|
+
if (!useAt && !useMemoryManage) {
|
|
172
|
+
return packResult(typeLevelFromOverview(view), {
|
|
173
|
+
_note: '无法使用 AT 资源列表,已回退为 Overview 类型级峰值。',
|
|
174
|
+
_apisUsed: apisUsed,
|
|
175
|
+
_limitations: ['at_resource_unavailable'],
|
|
176
|
+
});
|
|
113
177
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (r.status === 'fulfilled') {
|
|
122
|
-
items.push(...r.value);
|
|
123
|
-
apisUsed.push(useAt ? `at_resource_overall:${t}` : `memory_manage:${t}`);
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
errors.push(`${t}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`);
|
|
127
|
-
}
|
|
178
|
+
// Unity:明确未开资源采集时不必打 AT;UE 的 AT 实测可直接用,不依赖 OverviewHasResource 字段
|
|
179
|
+
if (identity.engine === 'unity' && view.hasResource === false && !canAt) {
|
|
180
|
+
return packResult(typeLevelFromOverview(view), {
|
|
181
|
+
_note: '报告未开启资源采集,无法拉逐资源列表;已回退为 Overview 类型级峰值。',
|
|
182
|
+
_apisUsed: apisUsed,
|
|
183
|
+
_limitations: ['未开启资源采集'],
|
|
184
|
+
});
|
|
128
185
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
186
|
+
let fetched = await fetchPerAssetRows(client, identity, assetTypes, useAt);
|
|
187
|
+
// AT 全部失败时,Unity 还可回退 memory/manage
|
|
188
|
+
if (useAt && fetched.items.length === 0 && identity.engine === 'unity') {
|
|
189
|
+
const fallback = await fetchPerAssetRows(client, identity, assetTypes, false);
|
|
190
|
+
if (fallback.items.length > 0 || fallback.errors.length === 0)
|
|
191
|
+
fetched = fallback;
|
|
192
|
+
}
|
|
193
|
+
apisUsed.push(...fetched.apisUsed);
|
|
194
|
+
if (fetched.items.length === 0) {
|
|
195
|
+
const fallbackItems = typeLevelFromOverview(view);
|
|
196
|
+
return packResult(fallbackItems, {
|
|
197
|
+
_sourceKind: fetched.sourceKind,
|
|
198
|
+
_apisUsed: apisUsed,
|
|
199
|
+
...(fetched.errors.length ? { _partialErrors: fetched.errors } : {}),
|
|
200
|
+
_note: fallbackItems.length > 0
|
|
201
|
+
? '逐资源列表为空,已回退为 Overview 类型级峰值。'
|
|
202
|
+
: '逐资源列表与类型级峰值均为空(报告可能未采集资源内存)。',
|
|
203
|
+
_limitations: ['per_asset_empty'],
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const note = groupBy === 'assetType'
|
|
207
|
+
? `已按类型各取 Top ${topN}(${fetched.sourceKind},共 ${assetTypes.length} 种)。`
|
|
208
|
+
: `已按内存峰值合并 ${assetTypes.length} 种资源类型(${fetched.sourceKind}),返回全局 Top ${topN}。`;
|
|
209
|
+
return packResult(fetched.items, {
|
|
210
|
+
totalCandidates: fetched.items.length,
|
|
211
|
+
_sourceKind: fetched.sourceKind,
|
|
141
212
|
_apisUsed: apisUsed,
|
|
142
|
-
...(errors.length ? { _partialErrors: errors } : {}),
|
|
143
|
-
_note:
|
|
144
|
-
};
|
|
213
|
+
...(fetched.errors.length ? { _partialErrors: fetched.errors } : {}),
|
|
214
|
+
_note: note,
|
|
215
|
+
});
|
|
145
216
|
}
|
|
146
217
|
function normalizeAssetTypes(raw) {
|
|
147
218
|
if (Array.isArray(raw) && raw.length)
|
|
148
219
|
return raw.map(String);
|
|
149
220
|
if (typeof raw === 'string' && raw.trim()) {
|
|
150
|
-
return raw
|
|
221
|
+
return raw
|
|
222
|
+
.split(/[,,]/)
|
|
223
|
+
.map((s) => s.trim())
|
|
224
|
+
.filter(Boolean);
|
|
151
225
|
}
|
|
152
226
|
return [...DEFAULT_ASSET_TYPES];
|
|
153
227
|
}
|
|
@@ -157,10 +231,11 @@ export const topResourcesTool = {
|
|
|
157
231
|
engines: ['unity', 'unreal'],
|
|
158
232
|
filterKeys: ['top_resources', 'composite', 'preset.default', 'overview'],
|
|
159
233
|
description: [
|
|
160
|
-
'聚合指定报告的资源内存占用 Top N
|
|
161
|
-
'适用引擎:Unity / UE
|
|
162
|
-
'Unity
|
|
163
|
-
'
|
|
234
|
+
'聚合指定报告的资源内存占用 Top N(逐资源)。',
|
|
235
|
+
'适用引擎:Unity / UE|复合工具',
|
|
236
|
+
'Unity / UE 均优先走 AT 资源总览预签名(at/resource/overall/table/presign),按 assetType 并行下载后合并排序。',
|
|
237
|
+
'groupBy=merged(默认):跨类型合并后取全局 Top N;groupBy=assetType:每种类型各取 Top N,返回 byType。',
|
|
238
|
+
'Unity 旧报告(解析日 < 2026-06-25)回退 memory/manage;拉不到逐资源时再回退 Overview 类型级峰值。',
|
|
164
239
|
'不确定报告版本时直接用本工具;需要原始全量列表时再调对应原子工具。',
|
|
165
240
|
].join('\n'),
|
|
166
241
|
inputSchema: {
|
|
@@ -169,8 +244,12 @@ export const topResourcesTool = {
|
|
|
169
244
|
assetTypes: z
|
|
170
245
|
.array(z.string())
|
|
171
246
|
.optional()
|
|
172
|
-
.describe(
|
|
173
|
-
|
|
247
|
+
.describe(`资源类型列表,默认全部 11 种:${DEFAULT_ASSET_TYPES.join(',')}。每次内部按类型各查一次`),
|
|
248
|
+
groupBy: z
|
|
249
|
+
.enum(['merged', 'assetType'])
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("分组模式:merged=跨类型全局 Top N(默认);assetType=每种类型各 Top N,返回 byType"),
|
|
252
|
+
topN: z.number().optional().describe('返回条数,默认 20,最大 200;groupBy=assetType 时为每类型条数'),
|
|
174
253
|
},
|
|
175
254
|
async handler(args, ctx) {
|
|
176
255
|
try {
|
package/dist/server.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ export interface ServerOptions {
|
|
|
17
17
|
timeoutMs: number;
|
|
18
18
|
specPath?: string;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
/** 与 package.json 同步,避免 MCP 握手版本和 npm 包不一致。 */
|
|
21
|
+
export declare const PACKAGE_VERSION: string;
|
|
21
22
|
export declare function createServer(opts: ServerOptions): {
|
|
22
23
|
server: McpServer;
|
|
23
24
|
toolCount: number;
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
1
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
6
|
import { UwaClient } from './client.js';
|
|
@@ -5,7 +8,18 @@ import { registerCompositeTools, COMPOSITE_TOOLS, selectCompositeTools } from '.
|
|
|
5
8
|
import { selectOperations } from './presets.js';
|
|
6
9
|
import { loadSpec } from './spec.js';
|
|
7
10
|
import { makeHandler, toolConfig, toolName } from './tools.js';
|
|
8
|
-
|
|
11
|
+
function readPackageVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
15
|
+
return pkg.version ?? '0.0.0';
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** 与 package.json 同步,避免 MCP 握手版本和 npm 包不一致。 */
|
|
22
|
+
export const PACKAGE_VERSION = readPackageVersion();
|
|
9
23
|
export function createServer(opts) {
|
|
10
24
|
const spec = loadSpec(opts.specPath);
|
|
11
25
|
const operations = selectOperations(spec.operations, { tools: opts.tools, engine: opts.engine });
|
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { UwaApiError } from './client.js';
|
|
3
|
-
import { versionGuideFor } from './version-guide.js';
|
|
3
|
+
import { INDICATOR_DASHBOARD_PANEL_GUIDE, versionGuideFor } from './version-guide.js';
|
|
4
4
|
const ENGINE_LABEL = { unity: 'Unity', unreal: 'UE' };
|
|
5
5
|
/**
|
|
6
6
|
* 预签名数据默认完整返回,不按条数截断——实测各接口解压后在 10 KB ~ 500 KB 之间,
|
|
@@ -41,6 +41,10 @@ function describe(op) {
|
|
|
41
41
|
const guide = versionGuideFor(op.id);
|
|
42
42
|
if (guide)
|
|
43
43
|
lines.push(guide);
|
|
44
|
+
else if (op.query.some((p) => p.name === 'indicatorDashboards')) {
|
|
45
|
+
// 带 indicatorDashboards 但未单独写 VERSION_GUIDE 的原子工具(兜底)
|
|
46
|
+
lines.push(INDICATOR_DASHBOARD_PANEL_GUIDE);
|
|
47
|
+
}
|
|
44
48
|
const engines = op.engines.map((e) => ENGINE_LABEL[e]).join(' / ');
|
|
45
49
|
lines.push(`适用引擎:${engines}|模块:${op.modules.join(',')}|${op.method} ${op.path}`);
|
|
46
50
|
const sdk = op.variants.find((v) => v.sdkRequirement)?.sdkRequirement;
|
|
@@ -84,7 +88,15 @@ function describe(op) {
|
|
|
84
88
|
export function buildInputSchema(op) {
|
|
85
89
|
const shape = {};
|
|
86
90
|
for (const p of [...op.query, ...op.body]) {
|
|
87
|
-
|
|
91
|
+
let desc = p.description;
|
|
92
|
+
if (p.name === 'indicatorDashboards') {
|
|
93
|
+
desc = [
|
|
94
|
+
desc,
|
|
95
|
+
'传参名为面板标识符:后缀 _max/_avg 等为历史命名,不表示只能取峰值或均值。',
|
|
96
|
+
'例:temperature_max 可拿到整机/CPU/GPU/电池温度的曲线及 mean/max/min 统计(见映射表子指标 key)。',
|
|
97
|
+
].join(' ');
|
|
98
|
+
}
|
|
99
|
+
const base = zodFor(p).describe(p.example ? `${desc}(示例:${p.example})` : desc);
|
|
88
100
|
shape[p.name] = p.required ? base : base.optional();
|
|
89
101
|
}
|
|
90
102
|
if (op.apiVersions.length > 1) {
|
package/dist/version-guide.d.ts
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* 集中手写、挂到 describe() 首段,避免散落在 notes 里被模型漏读。
|
|
5
5
|
* 不塞进 docs/ 生成链路——刷新官方文档镜像不会冲掉这里。
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* indicatorDashboards 传参名称说明(面板标识符,非统计维度后缀)。
|
|
9
|
+
* 挂到所有带 indicatorDashboards 的工具 description,避免模型见 _max 只读峰值。
|
|
10
|
+
*/
|
|
11
|
+
export declare const INDICATOR_DASHBOARD_PANEL_GUIDE: string;
|
|
7
12
|
/** 写入工具 description 的「选用规则」块,key 为 operation id。 */
|
|
8
13
|
export declare const VERSION_GUIDE: Record<string, string>;
|
|
9
14
|
export declare function versionGuideFor(opId: string): string | undefined;
|
package/dist/version-guide.js
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
* 集中手写、挂到 describe() 首段,避免散落在 notes 里被模型漏读。
|
|
5
5
|
* 不塞进 docs/ 生成链路——刷新官方文档镜像不会冲掉这里。
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* indicatorDashboards 传参名称说明(面板标识符,非统计维度后缀)。
|
|
9
|
+
* 挂到所有带 indicatorDashboards 的工具 description,避免模型见 _max 只读峰值。
|
|
10
|
+
*/
|
|
11
|
+
export const INDICATOR_DASHBOARD_PANEL_GUIDE = [
|
|
12
|
+
'【indicatorDashboards 传参名称——勿按后缀猜统计维度】',
|
|
13
|
+
'- 传参名(如 temperature_max、fps_avg、drawcall_cnt_max)是**面板标识符**,_max / _avg / @max 等为历史命名,**不限制**返回 mean / maximum / min 等统计类型。',
|
|
14
|
+
'- 例:temperature_max 面板含子指标 temperature、android_temper_cpu、android_temper_gpu、android_temper_battery;',
|
|
15
|
+
' · 曲线接口(indicator_curve / custom_dashboard)→ 各子指标逐帧曲线;',
|
|
16
|
+
' · 统计 2.0(indicator_statistic_dashboard)→ statistic 下 *_mean / *_maximum / *_min 及 scene_* 等全量统计。',
|
|
17
|
+
'- 查温度曲线/均值/峰值:Unity Overview 且 SDK ≥ 2.5.1 时传 indicatorDashboards=temperature_max,再读 y_axis 或 statistic;勿仅看 get_overview_statistic 的汇总段。',
|
|
18
|
+
'- UE Overview 当前 Open API 未开放 custom/indicator dashboard 路径(会 24052),UE 温度汇总见 get_ue_overview_statistic_v2 的 temperature 段。',
|
|
19
|
+
].join('\n');
|
|
7
20
|
/** 写入工具 description 的「选用规则」块,key 为 operation id。 */
|
|
8
21
|
export const VERSION_GUIDE = {
|
|
9
22
|
get_overview_statistic_v1: [
|
|
@@ -39,17 +52,28 @@ export const VERSION_GUIDE = {
|
|
|
39
52
|
'- 自定义面板统计 1.0(抽帧曲线)。不支持 GPU 类面板。',
|
|
40
53
|
'- 需要 GPU 指标或更多统计值类型时,改用 gotonline_overview_indicator_statistic_dashboard(2.0,需 SDK ≥ 2.5.1)。',
|
|
41
54
|
'- 逐帧曲线用 gotonline_overview_indicator_curve_dashboard(2.0,最多 3 个面板)。',
|
|
55
|
+
INDICATOR_DASHBOARD_PANEL_GUIDE,
|
|
42
56
|
].join('\n'),
|
|
43
57
|
gotonline_overview_indicator_statistic_dashboard: [
|
|
44
58
|
'【选用规则——务必先看】',
|
|
45
59
|
'- 自定义面板统计 2.0。需 SDK ≥ 2.5.1,支持 GPU 指标,最多 15 个面板。',
|
|
46
60
|
'- 旧接口(抽帧、无 GPU)见 gotonline_overview_custom_dashboard。',
|
|
47
61
|
'- 只要逐帧曲线不要统计值时用 gotonline_overview_indicator_curve_dashboard。',
|
|
62
|
+
INDICATOR_DASHBOARD_PANEL_GUIDE,
|
|
48
63
|
].join('\n'),
|
|
49
64
|
gotonline_overview_indicator_curve_dashboard: [
|
|
50
65
|
'【选用规则——务必先看】',
|
|
51
66
|
'- 自定义面板帧数据 2.0。需 SDK ≥ 2.5.1,最多 3 个面板;不支持内存类面板。',
|
|
52
67
|
'- 要统计值(含场景维度)用 gotonline_overview_indicator_statistic_dashboard。',
|
|
68
|
+
'- 【横轴——勿混用】外层 x_axis 为逐帧共享横轴(1、2、3…);y_axis.{指标名}.x_axis 为该曲线专属横轴(常见步长 30:0、30、60…)。',
|
|
69
|
+
'- 读取某条曲线时:若存在专属 x_axis 必须用专属,否则才用外层共享 x_axis。把共享横轴套到 FPS 等专属曲线会错位。',
|
|
70
|
+
'- 响应另含 _axisBinding / _axisHint,按其中 xAxisSource 取值即可。',
|
|
71
|
+
INDICATOR_DASHBOARD_PANEL_GUIDE,
|
|
72
|
+
].join('\n'),
|
|
73
|
+
gotonline_gpu_curve: [
|
|
74
|
+
'【选用规则——务必先看】',
|
|
75
|
+
'- GPU 模式指标面板逐帧曲线,indicatorDashboards 为 GPU 面板映射表中的传参名称。',
|
|
76
|
+
INDICATOR_DASHBOARD_PANEL_GUIDE,
|
|
53
77
|
].join('\n'),
|
|
54
78
|
gotonline_overview_memory_usage_snapshot: [
|
|
55
79
|
'【选用规则——务必先看】',
|