@gracker/smartperfetto 1.0.21 → 1.0.22

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.
@@ -0,0 +1,428 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (C) 2024-2026 Gracker (Chris)
3
+ # This file is part of SmartPerfetto. See LICENSE for details.
4
+
5
+ name: selection_range_cpu_sched_summary
6
+ version: "1.0"
7
+ type: composite
8
+ category: kernel
9
+ tier: B
10
+
11
+ meta:
12
+ display_name: "选区 CPU 调度与频率摘要"
13
+ description: "面向用户选区/可见窗口的快速 CPU 摆核、Running 排名、四象限和频率分布分析"
14
+ icon: "speed"
15
+ tags: [selection, range, cpu, sched, frequency, quadrant, migration, quick]
16
+
17
+ triggers:
18
+ keywords:
19
+ zh: [选区, 这一段, 这段, 摆核, 核心摆放, 平均频率, 频率分布, Running 排名, 四象限]
20
+ en: [selection range, current window, cpu placement, core placement, average frequency, frequency distribution, running ranking, quadrant]
21
+ patterns:
22
+ - ".*(选区|这段|这一段).*(CPU|摆核|核心|频率|Running|四象限).*"
23
+ - ".*(selected|current).*(range|window).*(cpu|core|frequency|running|quadrant).*"
24
+
25
+ prerequisites:
26
+ required_tables:
27
+ - thread_state
28
+ - thread
29
+ - process
30
+ modules:
31
+ - sched
32
+ - linux.cpu.frequency
33
+
34
+
35
+ inputs:
36
+ - name: start_ts
37
+ type: timestamp
38
+ required: true
39
+ description: "选区起始时间戳(ns)"
40
+ - name: end_ts
41
+ type: timestamp
42
+ required: true
43
+ description: "选区结束时间戳(ns)"
44
+ - name: package
45
+ type: string
46
+ required: false
47
+ description: "可选进程名过滤,支持 GLOB 前缀匹配"
48
+ - name: thread_name
49
+ type: string
50
+ required: false
51
+ description: "可选线程名包含匹配"
52
+ - name: top_k
53
+ type: number
54
+ required: false
55
+ description: "线程/进程排名返回数量"
56
+ - name: freq_bucket_mhz
57
+ type: number
58
+ required: false
59
+ description: "频率分布桶大小,单位 MHz"
60
+
61
+ steps:
62
+ - id: init_cpu_topology
63
+ type: skill
64
+ name: "初始化 CPU 拓扑"
65
+ skill: cpu_topology_view
66
+ params:
67
+ start_ts: "${start_ts}"
68
+ end_ts: "${end_ts}"
69
+ display:
70
+ level: hidden
71
+ optional: true
72
+
73
+ - id: running_thread_quadrants
74
+ type: atomic
75
+ name: "Running 线程四象限与摆核"
76
+ optional: true
77
+ display:
78
+ level: key
79
+ layer: overview
80
+ title: "选区 Running 线程四象限"
81
+ columns:
82
+ - name: thread_name
83
+ label: "线程"
84
+ type: string
85
+ - name: process_name
86
+ label: "进程"
87
+ type: string
88
+ - name: tid
89
+ label: "TID"
90
+ type: number
91
+ - name: total_cpu_ms
92
+ label: "CPU 时间"
93
+ type: duration
94
+ format: duration_ms
95
+ unit: ms
96
+ - name: q1_perf_running_ms
97
+ label: "Q1 性能核运行"
98
+ type: duration
99
+ format: duration_ms
100
+ unit: ms
101
+ - name: q2_little_running_ms
102
+ label: "Q2 小核运行"
103
+ type: duration
104
+ format: duration_ms
105
+ unit: ms
106
+ - name: q3_runnable_ms
107
+ label: "Q3 等待调度"
108
+ type: duration
109
+ format: duration_ms
110
+ unit: ms
111
+ - name: q4a_io_blocked_ms
112
+ label: "Q4a IO 阻塞"
113
+ type: duration
114
+ format: duration_ms
115
+ unit: ms
116
+ - name: q4b_sleeping_ms
117
+ label: "Q4b 睡眠等待"
118
+ type: duration
119
+ format: duration_ms
120
+ unit: ms
121
+ - name: perf_core_pct
122
+ label: "性能核占比"
123
+ type: percentage
124
+ format: percentage
125
+ - name: running_cpus
126
+ label: "运行 CPU"
127
+ type: string
128
+ - name: running_core_types
129
+ label: "核心类型"
130
+ type: string
131
+ - name: migrations
132
+ label: "核迁移"
133
+ type: number
134
+ - name: cross_cluster_migrations
135
+ label: "跨簇迁移"
136
+ type: number
137
+ sql: |
138
+ WITH
139
+ target_threads AS (
140
+ SELECT
141
+ t.utid,
142
+ t.tid,
143
+ COALESCE(t.name, '<unknown>') AS thread_name,
144
+ p.upid,
145
+ p.pid,
146
+ COALESCE(p.name, '<unknown>') AS process_name
147
+ FROM thread t
148
+ LEFT JOIN process p ON t.upid = p.upid
149
+ WHERE ('${package|}' = '' OR COALESCE(p.name, '') GLOB '${package|}*')
150
+ AND ('${thread_name|}' = '' OR COALESCE(t.name, '') GLOB '*${thread_name|}*')
151
+ ),
152
+ states AS (
153
+ SELECT
154
+ tt.utid,
155
+ tt.tid,
156
+ tt.thread_name,
157
+ tt.process_name,
158
+ ts.ts,
159
+ ts.state,
160
+ ts.cpu,
161
+ COALESCE(ct.core_type, 'unknown') AS core_type,
162
+ MIN(ts.ts + ts.dur, ${end_ts}) - MAX(ts.ts, ${start_ts}) AS clipped_dur
163
+ FROM thread_state ts
164
+ JOIN target_threads tt ON ts.utid = tt.utid
165
+ LEFT JOIN _cpu_topology ct ON ts.cpu = ct.cpu_id
166
+ WHERE ts.ts < ${end_ts}
167
+ AND ts.ts + ts.dur > ${start_ts}
168
+ AND ts.dur > 0
169
+ ),
170
+ running_events AS (
171
+ SELECT
172
+ utid,
173
+ ts,
174
+ cpu,
175
+ core_type,
176
+ LAG(cpu) OVER (PARTITION BY utid ORDER BY ts) AS prev_cpu,
177
+ LAG(core_type) OVER (PARTITION BY utid ORDER BY ts) AS prev_core_type
178
+ FROM states
179
+ WHERE state = 'Running' AND clipped_dur > 0
180
+ ),
181
+ migrations AS (
182
+ SELECT
183
+ utid,
184
+ SUM(CASE WHEN prev_cpu IS NOT NULL AND cpu != prev_cpu THEN 1 ELSE 0 END) AS migrations,
185
+ SUM(CASE WHEN prev_cpu IS NOT NULL AND cpu != prev_cpu AND core_type != prev_core_type THEN 1 ELSE 0 END) AS cross_cluster_migrations
186
+ FROM running_events
187
+ GROUP BY utid
188
+ )
189
+ SELECT
190
+ s.thread_name,
191
+ s.process_name,
192
+ s.tid,
193
+ ROUND(SUM(CASE WHEN s.state = 'Running' THEN s.clipped_dur ELSE 0 END) / 1e6, 2) AS total_cpu_ms,
194
+ ROUND(SUM(CASE WHEN s.state = 'Running' AND s.core_type IN ('prime', 'big', 'medium') THEN s.clipped_dur ELSE 0 END) / 1e6, 2) AS q1_perf_running_ms,
195
+ ROUND(SUM(CASE WHEN s.state = 'Running' AND s.core_type = 'little' THEN s.clipped_dur ELSE 0 END) / 1e6, 2) AS q2_little_running_ms,
196
+ ROUND(SUM(CASE WHEN s.state IN ('R', 'R+') THEN s.clipped_dur ELSE 0 END) / 1e6, 2) AS q3_runnable_ms,
197
+ ROUND(SUM(CASE WHEN s.state IN ('D', 'DK') THEN s.clipped_dur ELSE 0 END) / 1e6, 2) AS q4a_io_blocked_ms,
198
+ ROUND(SUM(CASE WHEN s.state IN ('S', 'I') THEN s.clipped_dur ELSE 0 END) / 1e6, 2) AS q4b_sleeping_ms,
199
+ ROUND(100.0 * SUM(CASE WHEN s.state = 'Running' AND s.core_type IN ('prime', 'big', 'medium') THEN s.clipped_dur ELSE 0 END)
200
+ / NULLIF(SUM(CASE WHEN s.state = 'Running' THEN s.clipped_dur ELSE 0 END), 0), 1) AS perf_core_pct,
201
+ GROUP_CONCAT(DISTINCT CASE WHEN s.state = 'Running' THEN s.cpu END) AS running_cpus,
202
+ GROUP_CONCAT(DISTINCT CASE WHEN s.state = 'Running' THEN s.core_type END) AS running_core_types,
203
+ COALESCE(m.migrations, 0) AS migrations,
204
+ COALESCE(m.cross_cluster_migrations, 0) AS cross_cluster_migrations
205
+ FROM states s
206
+ LEFT JOIN migrations m ON s.utid = m.utid
207
+ WHERE s.clipped_dur > 0
208
+ GROUP BY s.utid
209
+ HAVING total_cpu_ms > 0
210
+ ORDER BY total_cpu_ms DESC
211
+ LIMIT ${top_k|20}
212
+ save_as: running_thread_quadrants
213
+
214
+ - id: running_process_ranking
215
+ type: atomic
216
+ name: "Running 进程排名"
217
+ optional: true
218
+ display:
219
+ level: summary
220
+ layer: list
221
+ title: "选区 Running 进程排名"
222
+ columns:
223
+ - name: process_name
224
+ label: "进程"
225
+ type: string
226
+ - name: pid
227
+ label: "PID"
228
+ type: number
229
+ - name: running_ms
230
+ label: "Running 时间"
231
+ type: duration
232
+ format: duration_ms
233
+ unit: ms
234
+ - name: thread_count
235
+ label: "线程数"
236
+ type: number
237
+ sql: |
238
+ SELECT
239
+ COALESCE(p.name, '<unknown>') AS process_name,
240
+ p.pid,
241
+ ROUND(SUM(MIN(ts.ts + ts.dur, ${end_ts}) - MAX(ts.ts, ${start_ts})) / 1e6, 2) AS running_ms,
242
+ COUNT(DISTINCT ts.utid) AS thread_count
243
+ FROM thread_state ts
244
+ JOIN thread t ON ts.utid = t.utid
245
+ LEFT JOIN process p ON t.upid = p.upid
246
+ WHERE ts.ts < ${end_ts}
247
+ AND ts.ts + ts.dur > ${start_ts}
248
+ AND ts.dur > 0
249
+ AND ts.state = 'Running'
250
+ AND ('${package|}' = '' OR COALESCE(p.name, '') GLOB '${package|}*')
251
+ AND ('${thread_name|}' = '' OR COALESCE(t.name, '') GLOB '*${thread_name|}*')
252
+ GROUP BY p.upid
253
+ ORDER BY running_ms DESC
254
+ LIMIT ${top_k|20}
255
+ save_as: running_process_ranking
256
+
257
+ - id: cpu_freq_by_core
258
+ type: atomic
259
+ name: "各核 duration-weighted 频率"
260
+ optional: true
261
+ display:
262
+ level: summary
263
+ layer: list
264
+ title: "选区各核平均频率"
265
+ columns:
266
+ - name: cpu
267
+ label: "CPU"
268
+ type: number
269
+ - name: core_type
270
+ label: "核心类型"
271
+ type: string
272
+ - name: avg_freq_mhz
273
+ label: "平均频率"
274
+ type: number
275
+ - name: min_freq_mhz
276
+ label: "最低频率"
277
+ type: number
278
+ - name: max_freq_mhz
279
+ label: "最高频率"
280
+ type: number
281
+ - name: covered_ms
282
+ label: "覆盖时长"
283
+ type: duration
284
+ format: duration_ms
285
+ unit: ms
286
+ sql: |
287
+ WITH
288
+ cpu_tracks AS (
289
+ SELECT id, cpu
290
+ FROM cpu_counter_track
291
+ WHERE name = 'cpufreq' AND cpu IS NOT NULL
292
+ ),
293
+ freq_points AS (
294
+ SELECT
295
+ t.cpu,
296
+ ${start_ts} AS ts,
297
+ (
298
+ SELECT c2.value
299
+ FROM counter c2
300
+ WHERE c2.track_id = t.id AND c2.ts <= ${start_ts}
301
+ ORDER BY c2.ts DESC
302
+ LIMIT 1
303
+ ) AS freq_khz,
304
+ 0 AS source_order
305
+ FROM cpu_tracks t
306
+ UNION ALL
307
+ SELECT t.cpu, c.ts, c.value AS freq_khz, 1 AS source_order
308
+ FROM counter c
309
+ JOIN cpu_tracks t ON c.track_id = t.id
310
+ WHERE c.ts >= ${start_ts} AND c.ts < ${end_ts}
311
+ ),
312
+ freq_spans AS (
313
+ SELECT
314
+ cpu,
315
+ freq_khz,
316
+ ts,
317
+ LEAD(ts, 1, ${end_ts}) OVER (PARTITION BY cpu ORDER BY ts, source_order) AS next_ts
318
+ FROM freq_points
319
+ WHERE freq_khz IS NOT NULL AND freq_khz > 0
320
+ ),
321
+ clipped AS (
322
+ SELECT
323
+ cpu,
324
+ freq_khz,
325
+ MIN(next_ts, ${end_ts}) - MAX(ts, ${start_ts}) AS dur_ns
326
+ FROM freq_spans
327
+ WHERE ts < ${end_ts} AND next_ts > ${start_ts}
328
+ )
329
+ SELECT
330
+ c.cpu,
331
+ COALESCE(ct.core_type, 'unknown') AS core_type,
332
+ ROUND(SUM(c.freq_khz * c.dur_ns) / NULLIF(SUM(c.dur_ns), 0) / 1000, 0) AS avg_freq_mhz,
333
+ ROUND(MIN(c.freq_khz) / 1000, 0) AS min_freq_mhz,
334
+ ROUND(MAX(c.freq_khz) / 1000, 0) AS max_freq_mhz,
335
+ ROUND(SUM(c.dur_ns) / 1e6, 2) AS covered_ms
336
+ FROM clipped c
337
+ LEFT JOIN _cpu_topology ct ON c.cpu = ct.cpu_id
338
+ WHERE c.dur_ns > 0
339
+ GROUP BY c.cpu
340
+ ORDER BY c.cpu
341
+ save_as: cpu_freq_by_core
342
+
343
+ - id: cpu_freq_distribution
344
+ type: atomic
345
+ name: "各核频率分布"
346
+ optional: true
347
+ display:
348
+ level: detail
349
+ layer: deep
350
+ title: "选区各核频率分布"
351
+ columns:
352
+ - name: cpu
353
+ label: "CPU"
354
+ type: number
355
+ - name: core_type
356
+ label: "核心类型"
357
+ type: string
358
+ - name: freq_mhz_bucket
359
+ label: "频率桶"
360
+ type: number
361
+ - name: duration_ms
362
+ label: "时长"
363
+ type: duration
364
+ format: duration_ms
365
+ unit: ms
366
+ - name: pct_of_range
367
+ label: "区间占比"
368
+ type: percentage
369
+ format: percentage
370
+ sql: |
371
+ WITH
372
+ cpu_tracks AS (
373
+ SELECT id, cpu
374
+ FROM cpu_counter_track
375
+ WHERE name = 'cpufreq' AND cpu IS NOT NULL
376
+ ),
377
+ freq_points AS (
378
+ SELECT
379
+ t.cpu,
380
+ ${start_ts} AS ts,
381
+ (
382
+ SELECT c2.value
383
+ FROM counter c2
384
+ WHERE c2.track_id = t.id AND c2.ts <= ${start_ts}
385
+ ORDER BY c2.ts DESC
386
+ LIMIT 1
387
+ ) AS freq_khz,
388
+ 0 AS source_order
389
+ FROM cpu_tracks t
390
+ UNION ALL
391
+ SELECT t.cpu, c.ts, c.value AS freq_khz, 1 AS source_order
392
+ FROM counter c
393
+ JOIN cpu_tracks t ON c.track_id = t.id
394
+ WHERE c.ts >= ${start_ts} AND c.ts < ${end_ts}
395
+ ),
396
+ freq_spans AS (
397
+ SELECT
398
+ cpu,
399
+ freq_khz,
400
+ ts,
401
+ LEAD(ts, 1, ${end_ts}) OVER (PARTITION BY cpu ORDER BY ts, source_order) AS next_ts
402
+ FROM freq_points
403
+ WHERE freq_khz IS NOT NULL AND freq_khz > 0
404
+ ),
405
+ clipped AS (
406
+ SELECT
407
+ cpu,
408
+ CAST(ROUND(freq_khz / (${freq_bucket_mhz|100} * 1000.0)) * ${freq_bucket_mhz|100} AS INTEGER) AS freq_mhz_bucket,
409
+ MIN(next_ts, ${end_ts}) - MAX(ts, ${start_ts}) AS dur_ns
410
+ FROM freq_spans
411
+ WHERE ts < ${end_ts} AND next_ts > ${start_ts}
412
+ )
413
+ SELECT
414
+ c.cpu,
415
+ COALESCE(ct.core_type, 'unknown') AS core_type,
416
+ c.freq_mhz_bucket,
417
+ ROUND(SUM(c.dur_ns) / 1e6, 2) AS duration_ms,
418
+ ROUND(100.0 * SUM(c.dur_ns) / NULLIF(${end_ts} - ${start_ts}, 0), 1) AS pct_of_range
419
+ FROM clipped c
420
+ LEFT JOIN _cpu_topology ct ON c.cpu = ct.cpu_id
421
+ WHERE c.dur_ns > 0
422
+ GROUP BY c.cpu, c.freq_mhz_bucket
423
+ ORDER BY c.cpu, duration_ms DESC
424
+ LIMIT 100
425
+ save_as: cpu_freq_distribution
426
+
427
+ output:
428
+ format: structured
@@ -7,28 +7,40 @@
7
7
  {{durationMs}} - Duration in ms, e.g. "19.30"
8
8
  {{trackCount}} - Number of selected tracks (number or "未知")
9
9
  {{trackSummary}} - Pre-formatted track list grouped by process (string, may be empty)
10
+ {{sourceLabel}} - Selection source label, e.g. Perfetto area/time-range selection or current visible timeline window
10
11
  -->
11
12
  ## 用户选区上下文
12
13
 
13
- 用户在 Perfetto UI 中选择了一段时间区间(按 M 键标记):
14
+ 用户当前问题带有一个明确的时间范围 scope(来源: {{sourceLabel}}):
14
15
  - **起始时间:** {{startNs}} ns
15
16
  - **结束时间:** {{endNs}} ns
16
17
  - **持续时间:** {{durationMs}} ms
17
18
  - **选中 Track 数:** {{trackCount}}{{trackSummary}}
18
19
 
19
20
  **分析约束:**
20
- - 你的 SQL 查询必须使用 `WHERE ts >= {{startNs}} AND ts <= {{endNs}}` 来限制时间范围
21
+ - 选区/窗口只定义时间和可选 track scope;用户真正要看的指标由用户问题决定,不要用固定 pattern 代替意图判断
22
+ - SQL 查询必须限制在上述时间范围。对 `slice` / `thread_state` / `sched_slice` 这类带持续时间的表,优先使用 overlap clipping:`ts < {{endNs}} AND ts + dur > {{startNs}}`,并用 `MIN(ts + dur, {{endNs}}) - MAX(ts, {{startNs}})` 计算区间内贡献
21
23
  - 上述时间戳是 trace_processor 原始时间戳(ns),可直接用于 slice/thread_state/sched 等所有表的 ts 列
22
24
  - 分析结论应聚焦于用户选择的这段区间
23
25
  - 如果需要全局上下文(如整体 VSync 周期)来做对比,可以额外查询,但核心分析范围是选区内
24
- - 当用户提到"选中的区间"/"这一段"/"选择的范围"/"marked area"等,指的就是上述时间窗口
26
+ - 当用户提到"选中的区间"/"这一段"/"选择的范围"/"marked area"/"current window"等,指的就是上述时间窗口
27
+ - 如果前端请求附带了 `traceContext` datasets,优先复用其中已经预取的选区数据;缺少用户所问的指标时,再调用工具补齐
25
28
 
26
- **选区内常用 SQL 查询模板:**
29
+ **快速路径建议:**
30
+ - 对 CPU 摆核、task/core placement、各核平均频率、频率分布、Running task/process 排名、Running 四象限这类选区问题,优先调用:
31
+ `invoke_skill(skillId="selection_range_cpu_sched_summary", params={start_ts: {{startNs}}, end_ts: {{endNs}}})`
32
+ - 如果用户限定某个进程或线程,把它作为 `package` 或 `thread_name` 参数传给该 Skill;没有限定时保持未过滤,让结果按数据排序
33
+ - 如果用户只问一个 Skill 未覆盖的小指标,可以直接 `execute_sql`,但仍必须使用上述时间范围和 overlap clipping
34
+
35
+ **选区内常用 SQL 查询模板(需要自定义 SQL 时使用):**
27
36
  ```sql
28
37
  -- 1) 选区内某线程的调度状态分布(大小核、Running/Sleeping/Runnable)
29
- SELECT cpu, state, SUM(dur)/1e6 AS total_ms, COUNT(*) AS count
38
+ SELECT cpu, state,
39
+ SUM(MIN(ts + dur, {{endNs}}) - MAX(ts, {{startNs}}))/1e6 AS total_ms,
40
+ COUNT(*) AS count
30
41
  FROM thread_state
31
- WHERE utid = <UTID> AND ts >= {{startNs}} AND ts <= {{endNs}}
42
+ WHERE utid = <UTID>
43
+ AND ts < {{endNs}} AND ts + dur > {{startNs}}
32
44
  GROUP BY cpu, state ORDER BY total_ms DESC;
33
45
 
34
46
  -- 2) 选区内 CPU 频率变化(使用 counter + cpu_counter_track,不要用 cpu_frequency_counters)
@@ -38,9 +50,12 @@ WHERE ct.name = 'cpufreq' AND c.ts >= {{startNs}} AND c.ts <= {{endNs}}
38
50
  ORDER BY ct.cpu, c.ts;
39
51
 
40
52
  -- 3) 选区内某线程的 Slice 热点(通过 thread_track 关联)
41
- SELECT s.name, s.dur/1e6 AS dur_ms, s.ts, s.depth
53
+ SELECT s.name,
54
+ (MIN(s.ts + s.dur, {{endNs}}) - MAX(s.ts, {{startNs}}))/1e6 AS dur_ms,
55
+ s.ts, s.depth
42
56
  FROM slice s JOIN thread_track tt ON s.track_id = tt.id
43
- WHERE tt.utid = <UTID> AND s.ts >= {{startNs}} AND s.ts <= {{endNs}}
57
+ WHERE tt.utid = <UTID>
58
+ AND s.ts < {{endNs}} AND s.ts + s.dur > {{startNs}}
44
59
  ORDER BY s.dur DESC LIMIT 20;
45
60
  ```
46
- > 注意: 不要猜测表名。如果不确定表是否存在,先用 `lookup_sql_schema` 工具查询。
61
+ > 注意: 不要猜测表名。如果不确定表是否存在,先用 `lookup_sql_schema` 工具查询。