perfetto-analyzer 1.0.3 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.5] - 2026-07-09
4
+
5
+ ### Adicionado
6
+ - **`parse_perfetto_pb.py`** — Nova função `extract_thread_states()` que analisa a tabela `thread_state` do Perfetto: detecta I/O síncrono (estado `D` — uninterruptible sleep com `blocked_function`) e contenção de locks Java (slices de Monitor) por thread do app. Dado ausente nos traces anteriores que agora resolve o padrão "app travou mas o profiler não mostra nada".
7
+ - **`parse_perfetto_pb.py`** — Nova função `extract_cpu_megacycles()` usando o stdlib module `linux.cpu.utilization.process` (Android 12+). Retorna `[]` silenciosamente em versões anteriores.
8
+ - **`scripts/configs/oom.textproto`** — Novo tipo de trace `oom`: captura heap dump automaticamente ao momento do `OutOfMemoryError` via trigger `android.java_hprof.oom`. Aguarda até 1h o crash acontecer.
9
+ - **`analyze_metrics.py`** — Nova função `analyze_thread_states()` que gera issue `critical` quando a main thread está em I/O síncrono >10ms e `warning` para contenção de lock >5ms.
10
+ - **`generate_report.py`** — Nova seção **"Contenção de Threads"** na seção CPU com tabelas separadas para I/O bloqueante e Monitor contention.
11
+ - **`generate_report.py`** — Nova seção **"CPU Megacycles por Processo"** exibida quando disponível (Android 12+).
12
+ - **`collect_trace.py`** — Suporte ao tipo `oom`: modo especial sem countdown, aguarda o crash via Ctrl+C para cancelar.
13
+
14
+ ### Referências (cookbooks do Perfetto que inspiraram estas melhorias)
15
+ - [Android Trace Analysis](https://perfetto.dev/docs/getting-started/android-trace-analysis)
16
+ - [Android OOM](https://perfetto.dev/docs/case-studies/android-outofmemoryerror)
17
+
18
+ ## [1.0.4] - 2026-07-09
19
+
20
+ ### Adicionado
21
+ - **`generate_report.py`** — Seção "Próximos Passos" agora inclui sugestões de traces de correlação automáticas quando fazem sentido:
22
+ - `cpu` com Compose: sugere `frames` para confirmar se as recomposições causam drops reais
23
+ - `cpu` com problemas: sugere `memory` para verificar heap churn nos mesmos hot paths
24
+ - `frames` com jank: sugere `cpu` para identificar a callstack durante o deadline miss
25
+ - `memory` com problemas: sugere `cpu` (hotspots de alocação) e `frames` (GC causando jank)
26
+ - `battery` com problemas: sugere `cpu` e `all`
27
+ - Trace tipo `all`: nenhuma sugestão (já captura tudo)
28
+
3
29
  ## [1.0.3] - 2026-07-09
4
30
 
5
31
  ### Corrigido
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfetto-analyzer",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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"
@@ -0,0 +1,25 @@
1
+ buffers: {
2
+ size_kb: 524288
3
+ fill_policy: DISCARD
4
+ }
5
+
6
+ data_sources: {
7
+ config {
8
+ name: "android.java_hprof.oom"
9
+ java_hprof_config {
10
+ process_cmdline: "__PACKAGE__"
11
+ }
12
+ }
13
+ }
14
+
15
+ # Dispara automaticamente ao detectar java.lang.OutOfMemoryError
16
+ # no processo monitorado. Para o trace 500ms após a detecção
17
+ # para garantir que o heap dump seja capturado.
18
+ trigger_config {
19
+ trigger_mode: STOP_TRACING
20
+ triggers {
21
+ name: "com.android.telemetry.art-outofmemory"
22
+ stop_delay_ms: 500
23
+ }
24
+ trigger_timeout_ms: 3600000
25
+ }
@@ -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
- lines += [
361
- "---",
362
- "",
363
- "## Próximos Passos",
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