perfetto-analyzer 1.0.2 → 1.0.4
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.4] - 2026-07-09
|
|
4
|
+
|
|
5
|
+
### Adicionado
|
|
6
|
+
- **`generate_report.py`** — Seção "Próximos Passos" agora inclui sugestões de traces de correlação automáticas quando fazem sentido:
|
|
7
|
+
- `cpu` com Compose: sugere `frames` para confirmar se as recomposições causam drops reais
|
|
8
|
+
- `cpu` com problemas: sugere `memory` para verificar heap churn nos mesmos hot paths
|
|
9
|
+
- `frames` com jank: sugere `cpu` para identificar a callstack durante o deadline miss
|
|
10
|
+
- `memory` com problemas: sugere `cpu` (hotspots de alocação) e `frames` (GC causando jank)
|
|
11
|
+
- `battery` com problemas: sugere `cpu` e `all`
|
|
12
|
+
- Trace tipo `all`: nenhuma sugestão (já captura tudo)
|
|
13
|
+
|
|
14
|
+
## [1.0.3] - 2026-07-09
|
|
15
|
+
|
|
16
|
+
### Corrigido
|
|
17
|
+
- **`parse_perfetto_pb.py`** — Dados de frames vazios em devices físicos. O fallback para Android < 12 usava correspondência exata do nome do slice (`'Choreographer#doFrame'`), mas em muitos devices físicos o nome vem com sufixo de frame number (`'Choreographer#doFrame 12345'`). Corrigido usando `LIKE 'Choreographer#doFrame%'` e `LIKE '%DrawFrame%'`.
|
|
18
|
+
|
|
3
19
|
## [1.0.2] - 2026-07-09
|
|
4
20
|
|
|
5
21
|
### Corrigido
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perfetto-analyzer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "AI skill for Android performance analysis using Perfetto — works with Claude Code, Cursor, GitHub Copilot, Codex, Antigravity and Devin",
|
|
5
5
|
"bin": {
|
|
6
6
|
"perfetto-analyzer": "bin/cli.js"
|
|
@@ -77,6 +77,66 @@ def severity_emoji(s):
|
|
|
77
77
|
JVM_NOISE = {"RuntimeInit", "ZygoteInit", "NativeStart", "Looper.loop"}
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def _correlations(trace_type, recommendations):
|
|
81
|
+
"""Sugere traces complementares com base no tipo atual e nos problemas encontrados."""
|
|
82
|
+
if trace_type == "all":
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
categories = {r.get("category") for r in recommendations}
|
|
86
|
+
titles = " ".join(r.get("title", "") for r in recommendations)
|
|
87
|
+
suggestions = []
|
|
88
|
+
|
|
89
|
+
if trace_type == "cpu":
|
|
90
|
+
if "Recomposição" in titles or "Compose" in titles:
|
|
91
|
+
suggestions.append((
|
|
92
|
+
"frames",
|
|
93
|
+
"Correlacionar recomposições com drops reais — o trace de frames mostra se as "
|
|
94
|
+
"recomposições detectadas aqui estão de fato causando frames janked visíveis.",
|
|
95
|
+
))
|
|
96
|
+
if "cpu" in categories:
|
|
97
|
+
suggestions.append((
|
|
98
|
+
"memory",
|
|
99
|
+
"Verificar se heap churn está pressionando a CPU — GC frequente aparece como "
|
|
100
|
+
"picos de CPU; o trace de memória confirma alocações excessivas nos mesmos hot paths.",
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
elif trace_type == "frames":
|
|
104
|
+
if "frames" in categories:
|
|
105
|
+
suggestions.append((
|
|
106
|
+
"cpu",
|
|
107
|
+
"Identificar a callstack responsável pelos drops — o trace de CPU a 60 Hz mostra "
|
|
108
|
+
"exatamente qual função estava rodando durante cada deadline miss.",
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
elif trace_type == "memory":
|
|
112
|
+
if "memory" in categories:
|
|
113
|
+
suggestions.append((
|
|
114
|
+
"cpu",
|
|
115
|
+
"Localizar os hotspots de alocação no callstack — o trace de CPU revela quais "
|
|
116
|
+
"funções disparam as alocações identificadas aqui.",
|
|
117
|
+
))
|
|
118
|
+
suggestions.append((
|
|
119
|
+
"frames",
|
|
120
|
+
"Verificar se o churn de memória/GC está causando jank — pausas de GC aparecem "
|
|
121
|
+
"como frames dropados no trace de frames.",
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
elif trace_type == "battery":
|
|
125
|
+
suggestions.append((
|
|
126
|
+
"cpu",
|
|
127
|
+
"Identificar quais funções consomem mais CPU e drenam a bateria — o trace de CPU "
|
|
128
|
+
"mapeia o gasto energético a callstacks específicos.",
|
|
129
|
+
))
|
|
130
|
+
if recommendations:
|
|
131
|
+
suggestions.append((
|
|
132
|
+
"all",
|
|
133
|
+
"Trace completo para correlação total — combina CPU, frames e memória para uma "
|
|
134
|
+
"visão unificada do drain.",
|
|
135
|
+
))
|
|
136
|
+
|
|
137
|
+
return suggestions
|
|
138
|
+
|
|
139
|
+
|
|
80
140
|
def generate_report(data, package, trace_type):
|
|
81
141
|
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
82
142
|
duration = data.get("trace_duration_seconds", 0)
|
|
@@ -357,11 +417,16 @@ def generate_report(data, package, trace_type):
|
|
|
357
417
|
lines += ["Nenhum problema detectado automaticamente. Revise o trace manualmente no [Perfetto UI](https://perfetto.dev/ui).", ""]
|
|
358
418
|
|
|
359
419
|
# Próximos Passos
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
420
|
+
correlations = _correlations(trace_type, recommendations)
|
|
421
|
+
|
|
422
|
+
next_steps = ["---", "", "## Próximos Passos", ""]
|
|
423
|
+
|
|
424
|
+
if correlations:
|
|
425
|
+
for next_type, reason in correlations:
|
|
426
|
+
next_steps.append(f"- [ ] **Trace `{next_type}`** — {reason}")
|
|
427
|
+
next_steps.append("")
|
|
428
|
+
|
|
429
|
+
next_steps += [
|
|
365
430
|
"- [ ] Abrir o arquivo `.pb` em [perfetto.dev/ui](https://perfetto.dev/ui) para análise visual",
|
|
366
431
|
"- [ ] Usar o SQL Explorer do Perfetto UI para queries customizadas",
|
|
367
432
|
"- [ ] Correlacionar picos de CPU/memória com interações do usuário na timeline",
|
|
@@ -372,6 +437,8 @@ def generate_report(data, package, trace_type):
|
|
|
372
437
|
f"*Gerado por perfetto-analyzer em {now}*",
|
|
373
438
|
]
|
|
374
439
|
|
|
440
|
+
lines += next_steps
|
|
441
|
+
|
|
375
442
|
return "\n".join(lines)
|
|
376
443
|
|
|
377
444
|
|
|
@@ -164,12 +164,16 @@ def extract_frames(tp):
|
|
|
164
164
|
""")
|
|
165
165
|
|
|
166
166
|
if not frames:
|
|
167
|
+
# Fallback para Android < 12 ou devices sem frame timeline.
|
|
168
|
+
# O nome do slice varia por versão: 'Choreographer#doFrame', 'Choreographer#doFrame 12345', etc.
|
|
167
169
|
frames = query_to_dicts(tp, """
|
|
168
170
|
SELECT
|
|
169
171
|
s.name,
|
|
170
172
|
s.dur / 1e6 AS duration_ms
|
|
171
173
|
FROM slice s
|
|
172
|
-
WHERE s.name
|
|
174
|
+
WHERE s.name LIKE 'Choreographer#doFrame%'
|
|
175
|
+
OR s.name LIKE '%DrawFrame%'
|
|
176
|
+
OR s.name = 'doFrame'
|
|
173
177
|
ORDER BY s.ts
|
|
174
178
|
LIMIT 5000
|
|
175
179
|
""")
|