perfetto-analyzer 1.0.0 → 1.0.2
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
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.2] - 2026-07-09
|
|
4
|
+
|
|
5
|
+
### Corrigido
|
|
6
|
+
- **`generate_report.py`** — `TypeError: 'NoneType' object is not subscriptable` ao gerar relatório de traces do tipo `battery`. Ocorria quando `thread_name` estava presente no dicionário com valor `None` (amostras de CPU sem thread associada). Corrigido em 4 ocorrências com `(value or "?")[:N]` no lugar de `value[:N]`.
|
|
7
|
+
- **`memory.textproto`** — App travava durante coleta de memória por causa do `block_client: true` no heapprofd, que bloqueava as threads do app a cada chamada `malloc()`. Alterado para `block_client: false`.
|
|
8
|
+
- **`memory.textproto`** — Reduzido `sampling_interval_bytes` de `4096` para `8192` para diminuir o overhead de profiling.
|
|
9
|
+
- **`all.textproto`** — Mesmas correções de `block_client` e `sampling_interval_bytes` aplicadas ao tipo `all`.
|
|
10
|
+
- **`all.textproto`** — Adicionado `android.java_hprof` que estava ausente: o tipo `all` não capturava objetos Java/Kotlin retidos no heap ART.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [1.0.0] - 2026-07-09
|
|
15
|
+
|
|
16
|
+
### Adicionado
|
|
17
|
+
- Coleta automática de traces Perfetto via `adb` com suporte aos tipos `memory`, `cpu`, `frames`, `battery` e `all`.
|
|
18
|
+
- Abertura automática do app via launcher caso não esteja em execução no momento da coleta.
|
|
19
|
+
- Parsing de callstacks de CPU com CTE recursiva percorrendo a cadeia completa de `stack_profile_callsite` até profundidade 60.
|
|
20
|
+
- Identificação de hotspots do código do app filtrando frames pelo prefixo do package (com remoção de sufixos de build variant: `debug`, `release`, `staging`, etc.).
|
|
21
|
+
- Correlação de amostras de CPU com janelas de frames janked (`actual_frame_timeline_slice WHERE on_time_finish = 0`).
|
|
22
|
+
- Relatório markdown estruturado com Resumo Executivo, Memória, CPU, Frames, Bateria e Recomendações priorizadas por severidade.
|
|
23
|
+
- Instalação automática para 6 harness agents: Claude Code, GitHub Copilot, Cursor, Antigravity, Codex e Devin.
|
|
24
|
+
- CLI com comandos `install` e `uninstall` via `npx perfetto-analyzer`.
|
|
25
|
+
- Instalação automática via `postinstall` ao usar `npm install -g perfetto-analyzer`.
|
package/README.md
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
# perfetto-analyzer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Skill de IA para análise de performance Android com [Perfetto](https://perfetto.dev) — funciona com Claude Code, Cursor, GitHub Copilot, Codex, Antigravity e Devin.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Instale uma vez. Seu assistente de IA vira um especialista em performance Android: coleta traces Perfetto via `adb`, analisa callstacks de CPU, alocações de memória, timelines de frames e contadores de bateria, e gera um relatório markdown estruturado com recomendações acionáveis.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
# Run once — installs for all supported agents
|
|
11
|
-
npx perfetto-analyzer install
|
|
9
|
+
## Índice
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
- [Agentes de IA suportados](#agentes-de-ia-suportados)
|
|
12
|
+
- [Pré-requisitos](#pré-requisitos)
|
|
13
|
+
- [Instalação](#instalação)
|
|
14
|
+
- [Como usar](#como-usar)
|
|
15
|
+
- [Modo A — Coletar trace de um device conectado](#modo-a--coletar-trace-de-um-device-conectado)
|
|
16
|
+
- [Modo B — Analisar um arquivo .pb existente](#modo-b--analisar-um-arquivo-pb-existente)
|
|
17
|
+
- [Tipos de trace](#tipos-de-trace)
|
|
18
|
+
- [Entendendo o relatório](#entendendo-o-relatório)
|
|
19
|
+
- [Resumo Executivo](#resumo-executivo)
|
|
20
|
+
- [Memória](#memória)
|
|
21
|
+
- [CPU](#cpu)
|
|
22
|
+
- [Frames](#frames)
|
|
23
|
+
- [Bateria](#bateria)
|
|
24
|
+
- [Recomendações](#recomendações)
|
|
25
|
+
- [Referência de métricas](#referência-de-métricas)
|
|
26
|
+
- [Solução de problemas](#solução-de-problemas)
|
|
27
|
+
- [Desinstalar](#desinstalar)
|
|
28
|
+
|
|
29
|
+
---
|
|
16
30
|
|
|
17
|
-
##
|
|
31
|
+
## Agentes de IA suportados
|
|
18
32
|
|
|
19
|
-
|
|
33
|
+
O instalador configura automaticamente a skill em todos os agentes encontrados na sua máquina:
|
|
34
|
+
|
|
35
|
+
| Agente | Diretório da skill |
|
|
20
36
|
|---|---|
|
|
21
37
|
| Claude Code | `~/.claude/skills/perfetto-analyzer/` |
|
|
22
38
|
| GitHub Copilot | `~/.github/copilot/skills/perfetto-analyzer/` |
|
|
@@ -25,39 +41,386 @@ npm install -g perfetto-analyzer
|
|
|
25
41
|
| Codex (OpenAI) | `~/.codex/skills/perfetto-analyzer/` |
|
|
26
42
|
| Devin | `~/.devin/skills/perfetto-analyzer/` |
|
|
27
43
|
|
|
28
|
-
|
|
44
|
+
Os diretórios são criados automaticamente caso não existam.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Pré-requisitos
|
|
49
|
+
|
|
50
|
+
| Ferramenta | Como verificar | Instalação |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `adb` | `adb version` | [Android SDK Platform-Tools](https://developer.android.com/tools/releases/platform-tools) |
|
|
53
|
+
| Python 3.9+ | `python3 --version` | [python.org](https://python.org) |
|
|
54
|
+
| Lib Python do Perfetto | `python3 -c "import perfetto"` | `pip install perfetto` |
|
|
29
55
|
|
|
30
|
-
|
|
56
|
+
**Configuração do device:**
|
|
57
|
+
1. Ative as **Opções do Desenvolvedor** no Android (toque 7 vezes em "Número da versão" nas configurações)
|
|
58
|
+
2. Ative **Depuração USB** dentro das Opções do Desenvolvedor
|
|
59
|
+
3. Conecte via USB e aceite a solicitação de autorização no device
|
|
60
|
+
4. Verifique com `adb devices` — o device deve aparecer como `device` (não `offline` ou `unauthorized`)
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Instalação
|
|
65
|
+
|
|
66
|
+
### Opção 1 — npx (sem instalação prévia)
|
|
31
67
|
|
|
32
68
|
```bash
|
|
33
|
-
npx perfetto-analyzer
|
|
69
|
+
npx perfetto-analyzer install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Opção 2 — instalação global
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install -g perfetto-analyzer
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
O script `postinstall` copia os arquivos da skill automaticamente.
|
|
79
|
+
|
|
80
|
+
### Verificar instalação
|
|
81
|
+
|
|
82
|
+
Após instalar, reinicie seu assistente de IA e pergunte:
|
|
83
|
+
|
|
84
|
+
> "Analise a performance do meu app Android"
|
|
85
|
+
|
|
86
|
+
O assistente deve pedir o nome do pacote, tipo de trace e duração — isso confirma que a skill está ativa.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Como usar
|
|
91
|
+
|
|
92
|
+
### Modo A — Coletar trace de um device conectado
|
|
93
|
+
|
|
94
|
+
Descreva o que quer medir em linguagem natural:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Analise a performance de com.meuapp.debug — quero verificar jank na tela de feed
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Meu app com.exemplo.app está usando muita memória. Colete um trace de 30 segundos.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
O uso de CPU está alto em com.empresa.app, colete um trace de cpu por 15 segundos
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
O assistente vai:
|
|
109
|
+
|
|
110
|
+
1. **Verificar** se o device está conectado e o adb está funcionando
|
|
111
|
+
2. **Checar** se o app está aberto — se não estiver, abre automaticamente via launcher
|
|
112
|
+
3. **Coletar** o trace Perfetto (você verá uma contagem regressiva em tempo real)
|
|
113
|
+
4. **Analisar** callstacks de CPU, alocações de memória, timelines de frames e contadores de bateria
|
|
114
|
+
5. **Gerar** um relatório markdown completo com hotspots e recomendações
|
|
115
|
+
|
|
116
|
+
> **Durante a coleta:** interaja com o app normalmente — role, navegue, acione o fluxo lento que quer analisar. O trace captura o que acontece no device em tempo real.
|
|
117
|
+
|
|
118
|
+
Se não souber o package name do app, pergunte:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Como descubro o package name do meu app?
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
O assistente vai sugerir:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
adb shell pm list packages | grep <nome-do-app>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### Modo B — Analisar um arquivo .pb existente
|
|
133
|
+
|
|
134
|
+
Se você já tem um arquivo de trace Perfetto (`.pb`):
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Analise este trace: ~/Downloads/meu_trace.pb
|
|
34
138
|
```
|
|
35
139
|
|
|
36
|
-
|
|
140
|
+
```
|
|
141
|
+
Tenho um trace perfetto em /tmp/trace.pb, consegue encontrar memory leaks?
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Nenhuma conexão com device é necessária — o assistente analisa o arquivo diretamente.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Tipos de trace
|
|
149
|
+
|
|
150
|
+
Escolha o tipo de trace conforme o que quer investigar:
|
|
151
|
+
|
|
152
|
+
| Tipo | O que captura | Indicado para |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `frames` | Timeline de frames, causas de jank, amostras de CPU durante frames dropados | Travadas, scroll lento, jank na UI |
|
|
155
|
+
| `memory` | Alocações de heap nativo, retenção de objetos Java/Kotlin | Memory leaks, uso alto de heap, GC pressure |
|
|
156
|
+
| `cpu` | Amostragem de CPU com callstacks completos (Java, Kotlin, nativo) | Operações lentas, gargalos de CPU, funções quentes |
|
|
157
|
+
| `battery` | Contadores de drain da bateria, power rails | Drain rápido de bateria, wake locks, atividade em background |
|
|
158
|
+
| `all` | Todos os anteriores combinados | Auditoria completa de performance |
|
|
159
|
+
|
|
160
|
+
**Durações recomendadas:**
|
|
161
|
+
- `frames` / `cpu`: 10–15 segundos (acione a interação lenta uma vez)
|
|
162
|
+
- `memory`: 30–60 segundos (navegue por várias telas para acumular alocações)
|
|
163
|
+
- `battery`: 60–120 segundos (precisa de tempo suficiente para medir drain significativo)
|
|
164
|
+
|
|
165
|
+
---
|
|
37
166
|
|
|
38
|
-
|
|
167
|
+
## Entendendo o relatório
|
|
39
168
|
|
|
40
|
-
|
|
41
|
-
- **Analyze CPU, memory, frames (jank), and battery** from a `.pb` Perfetto trace
|
|
42
|
-
- **Generate a markdown report** with insights and actionable recommendations
|
|
169
|
+
O relatório é dividido em cinco seções. Veja o que cada uma significa.
|
|
43
170
|
|
|
44
|
-
###
|
|
171
|
+
### Resumo Executivo
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
## Resumo Executivo
|
|
175
|
+
> 🔴 43% das amostras de CPU na main thread — operações bloqueantes detectadas na UI
|
|
176
|
+
> 🟡 12% de frames dropados (P95: 48ms) — jank visível para o usuário
|
|
177
|
+
> 🟢 Memória estável — nenhum padrão de leak detectado
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Lista ranqueada dos problemas mais críticos, do mais ao menos impactante. Comece aqui para decidir o que corrigir primeiro.
|
|
181
|
+
|
|
182
|
+
Ícones de severidade:
|
|
183
|
+
- 🔴 **Crítico** — impacta diretamente a experiência do usuário, corrija imediatamente
|
|
184
|
+
- 🟡 **Atenção** — problema perceptível, deve ser endereçado
|
|
185
|
+
- 🟢 **OK** — dentro dos thresholds aceitáveis
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### Memória
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
## Memória
|
|
45
193
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
194
|
+
### Maiores Alocadores (Heap Nativo)
|
|
195
|
+
| Callstack | Bytes não liberados | % do total |
|
|
196
|
+
|-----------------------------------|---------------------|------------|
|
|
197
|
+
| Bitmap.createBitmap (ImageLoader) | 18,4 MB | 34% |
|
|
198
|
+
| RenderScript.create | 8,1 MB | 15% |
|
|
49
199
|
|
|
50
|
-
|
|
200
|
+
### Objetos Java/Kotlin Retidos (ART)
|
|
201
|
+
| Classe | Instâncias | Bytes |
|
|
202
|
+
|---------------------------|------------|---------|
|
|
203
|
+
| android.graphics.Bitmap | 847 | 42,3 MB |
|
|
204
|
+
| com.meuapp.cache.DataCache| 1 | 12,1 MB |
|
|
205
|
+
|
|
206
|
+
### Alertas
|
|
207
|
+
- ⚠️ Bytes não liberados crescendo ao longo do trace — possível memory leak no ImageLoader
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Conceitos-chave:**
|
|
211
|
+
|
|
212
|
+
| Termo | Significado |
|
|
213
|
+
|---|---|
|
|
214
|
+
| **Bytes não liberados** | Memória alocada e ainda não liberada — valores altos = possível leak |
|
|
215
|
+
| **Total de bytes** | Todas as alocações, incluindo as já liberadas — alto = muitos objetos temporários (GC pressure) |
|
|
216
|
+
| **Heap churn** | Razão `total / não liberado` > 10× = muitas alocações de curta duração → considere object pooling |
|
|
217
|
+
| **Dominators** | Objeto que mantém outro vivo na memória — Activity/Fragment como dominator = leak clássico |
|
|
218
|
+
|
|
219
|
+
**O que observar:**
|
|
220
|
+
- Bytes não liberados crescendo monotonicamente → memory leak
|
|
221
|
+
- A mesma classe aparecendo repetidamente no topo → hotspot de alocação
|
|
222
|
+
- `Activity` ou `Fragment` na lista de objetos retidos → problema de gerenciamento de ciclo de vida
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### CPU
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
## CPU
|
|
230
|
+
|
|
231
|
+
### Hotspots do App — Código Kotlin/Java
|
|
232
|
+
| Função | Thread | Amostras |
|
|
233
|
+
|-----------------------------------------------|-------------|----------|
|
|
234
|
+
| com.meuapp.feed.FeedAdapter.onBindViewHolder | main | 142 |
|
|
235
|
+
| com.meuapp.image.ImageDecoder.decode | AsyncTask#1 | 98 |
|
|
236
|
+
|
|
237
|
+
### APIs Android Ativas na Main Thread
|
|
238
|
+
| Função | Amostras |
|
|
239
|
+
|-------------------------------------------------|----------|
|
|
240
|
+
| androidx.compose.runtime.Recomposer · recompose | 201 |
|
|
241
|
+
| android.graphics.Bitmap · createScaledBitmap | 87 |
|
|
242
|
+
|
|
243
|
+
### Top Frames Nativos
|
|
244
|
+
| Função | Thread | Amostras |
|
|
245
|
+
|-----------------------|--------------|----------|
|
|
246
|
+
| libhwui.so!drawBitmap | RenderThread | 134 |
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Conceitos-chave:**
|
|
250
|
+
|
|
251
|
+
| Termo | Significado |
|
|
252
|
+
|---|---|
|
|
253
|
+
| **Amostras** | Quantas vezes a CPU foi pega executando essa função — mais amostras = mais tempo gasto |
|
|
254
|
+
| **Main thread** | Thread de UI do Android — trabalho pesado aqui causa jank diretamente |
|
|
255
|
+
| **Hotspots do app** | Seu código (filtrado pelo package name) — as funções que você pode de fato alterar |
|
|
256
|
+
| **APIs Android** | Funções do Android/Compose/AndroidX — indicam em que seu código está gastando tempo |
|
|
257
|
+
| **Frames nativos** | Código C/C++ em libs do sistema — `libhwui` = renderização, `libart` = overhead do runtime |
|
|
258
|
+
|
|
259
|
+
**O que observar:**
|
|
260
|
+
- Funções do seu pacote na main thread → mova o trabalho pesado para uma coroutine/thread em background
|
|
261
|
+
- `Bitmap.createScaledBitmap` ou decodificação de imagem na main thread → use Glide/Coil com carregamento assíncrono
|
|
262
|
+
- Funções de recomposição do Compose dominando → verifique recomposições desnecessárias com `@Stable` / `remember`
|
|
263
|
+
- Funções de GC (`art::gc::`) → reduza criação de objetos temporários, use object pools
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### Frames
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
## Frames
|
|
271
|
+
|
|
272
|
+
| Métrica | Valor | Threshold OK |
|
|
273
|
+
|-----------------|-----------|--------------|
|
|
274
|
+
| Total de frames | 1.842 | — |
|
|
275
|
+
| Frames dropados | 221 (12%) | < 1% |
|
|
276
|
+
| Frame P50 | 14ms | ≤ 16,67ms |
|
|
277
|
+
| Frame P95 | 48ms | ≤ 33,3ms |
|
|
278
|
+
| Frame P99 | 112ms | ≤ 50ms |
|
|
279
|
+
|
|
280
|
+
### Causas de Jank
|
|
281
|
+
| Causa | Ocorrências |
|
|
282
|
+
|------------------------------------|-------------|
|
|
283
|
+
| App Deadline Missed | 187 |
|
|
284
|
+
| SurfaceFlinger GPU Deadline Missed | 34 |
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Thresholds de frames dropados:**
|
|
288
|
+
|
|
289
|
+
| % Dropados | Avaliação |
|
|
290
|
+
|---|---|
|
|
291
|
+
| < 0,1% | Excelente |
|
|
292
|
+
| 0,1% – 1% | Bom |
|
|
293
|
+
| 1% – 5% | Aceitável (usuário pode perceber em cenários específicos) |
|
|
294
|
+
| > 5% | Ruim — jank visível |
|
|
295
|
+
|
|
296
|
+
**Thresholds de latência de frame (display 60 Hz):**
|
|
297
|
+
|
|
298
|
+
| Percentil | OK | Atenção | Crítico |
|
|
299
|
+
|---|---|---|---|
|
|
300
|
+
| P50 | ≤ 16,67ms | 16–33ms | > 33ms |
|
|
301
|
+
| P95 | ≤ 33,3ms | 33–50ms | > 50ms |
|
|
302
|
+
| P99 | ≤ 50ms | 50–100ms | > 100ms |
|
|
303
|
+
|
|
304
|
+
**Causas de jank explicadas:**
|
|
305
|
+
|
|
306
|
+
| Causa | Origem | Correção |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| `App Deadline Missed` | Seu app não terminou o frame a tempo | Reduza trabalho na UI thread, use `LaunchedEffect`, mova lógica para `ViewModel` |
|
|
309
|
+
| `SurfaceFlinger CPU Deadline Missed` | O compositor do sistema estava lento | Reduza o número de layers, evite overdraw |
|
|
310
|
+
| `SurfaceFlinger GPU Deadline Missed` | A renderização da GPU demorou muito | Simplifique shaders, reduza complexidade visual, use hardware layers |
|
|
311
|
+
| `Buffering` | App produz frames mais rápido do que o display consome | Geralmente benigno |
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
### Bateria
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
## Bateria
|
|
319
|
+
|
|
320
|
+
| Métrica | Valor |
|
|
321
|
+
|---------------------|----------|
|
|
322
|
+
| Carga drenada | 284 mC |
|
|
323
|
+
| Duração do trace | 60s |
|
|
324
|
+
| Taxa média de drain | 4,7 mC/s |
|
|
325
|
+
|
|
326
|
+
### Top Consumidores
|
|
327
|
+
| Componente | Consumo estimado |
|
|
328
|
+
|------------|-----------------|
|
|
329
|
+
| CPU (big) | 180 mW |
|
|
330
|
+
| GPU | 95 mW |
|
|
331
|
+
| Memória | 40 mW |
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Referência de consumo típico:**
|
|
335
|
+
|
|
336
|
+
| Estado do app | Consumo total típico |
|
|
337
|
+
|---|---|
|
|
338
|
+
| App em idle | 5–20 mW |
|
|
339
|
+
| App ativo (UI, rede) | 100–300 mW |
|
|
340
|
+
| Vídeo / jogo | 500–2000 mW |
|
|
341
|
+
|
|
342
|
+
**Contadores principais:**
|
|
343
|
+
|
|
344
|
+
| Contador | Significado |
|
|
345
|
+
|---|---|
|
|
346
|
+
| `batt_charge_uah` | Carga da bateria em µAh — o delta ao longo do trace = consumo |
|
|
347
|
+
| `batt_current_ua` | Corrente instantânea em µA — negativo = descarregando |
|
|
348
|
+
| Power rails (`cpu.big`, `gpu`, etc.) | Consumo por subsistema em mW (devices Qualcomm/Tensor) |
|
|
349
|
+
|
|
350
|
+
**O que observar:**
|
|
351
|
+
- `CPU (big)` dominando → operações computacionalmente caras nos cores de alto desempenho; considere `THREAD_PRIORITY_BACKGROUND` para trabalho não crítico
|
|
352
|
+
- Drain alto em repouso → wake locks, sync agendado, acesso a localização em background
|
|
353
|
+
- GPU alta mesmo em telas simples → overdraw, layouts complexos, animações desnecessárias
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Recomendações
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
## Recomendações
|
|
361
|
+
|
|
362
|
+
1. **Mova a decodificação de imagens para fora da main thread** — `ImageDecoder.decode` foi chamado
|
|
363
|
+
98 vezes na main thread, contribuindo para o P95 de 48ms. Use Coil ou Glide com carregamento
|
|
364
|
+
baseado em coroutines.
|
|
365
|
+
|
|
366
|
+
2. **Reduza recomposições do Compose** — Recomposer mostra 201 amostras. Audite leituras de state
|
|
367
|
+
em composables, aplique `@Stable` em data classes e use `remember` para cálculos custosos.
|
|
368
|
+
|
|
369
|
+
3. **Corrija memory leak no ImageLoader** — Bytes não liberados cresceram de 12 MB para 31 MB
|
|
370
|
+
durante o trace. Verifique se `BitmapPool.clear()` é chamado em `onStop()` / `onDestroy()`.
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Cada recomendação contém:
|
|
374
|
+
- **Título** — o que corrigir
|
|
375
|
+
- **Evidência** — o que o trace mostrou e a métrica específica que acionou o alerta
|
|
376
|
+
- **Ação** — um passo concreto para resolver o problema
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Referência de métricas
|
|
381
|
+
|
|
382
|
+
### Checklist de triagem rápida
|
|
383
|
+
|
|
384
|
+
Use esta sequência ao receber um relatório pela primeira vez:
|
|
385
|
+
|
|
386
|
+
1. **Frames** → `% dropados > 1%`? Se sim, verifique a timeline de CPU e as causas de jank.
|
|
387
|
+
2. **CPU** → A main thread está acima de 20% do total de CPU? Se sim, procure chamadas bloqueantes.
|
|
388
|
+
3. **Memória** → Os bytes não liberados estão crescendo ao longo do tempo? Se sim, há um leak.
|
|
389
|
+
4. **Bateria** → A taxa de drain está acima do esperado para o estado do app? Se sim, verifique wake locks e atividade em background.
|
|
390
|
+
|
|
391
|
+
### Perfetto UI
|
|
392
|
+
|
|
393
|
+
Para investigação profunda além do relatório, abra o arquivo `.pb` em [perfetto.dev/ui](https://perfetto.dev/ui):
|
|
394
|
+
- **Flame chart** → visualize a hierarquia completa de callstacks
|
|
395
|
+
- **Thread state** → veja exatamente quando as threads estão rodando, dormindo ou bloqueadas
|
|
396
|
+
- **SQL query** → execute queries customizadas via Trace Processor integrado
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Solução de problemas
|
|
401
|
+
|
|
402
|
+
| Erro | Causa | Solução |
|
|
403
|
+
|---|---|---|
|
|
404
|
+
| `no devices/emulators found` | Device não conectado ou depuração USB desativada | Execute `adb devices`, verifique as Opções do Desenvolvedor |
|
|
405
|
+
| `offline` em `adb devices` | Autorização não aceita no device | Desconecte e reconecte o USB, aceite o prompt no device |
|
|
406
|
+
| `ImportError: No module named 'perfetto'` | Biblioteca Python não instalada | `pip install perfetto` |
|
|
407
|
+
| `Error opening trace file` | Arquivo `.pb` corrompido ou versão incompatível | Valide o arquivo em [perfetto.dev/ui](https://perfetto.dev/ui) |
|
|
408
|
+
| Trace coletado mas dados zerados | Daemon `traced` não está rodando | `adb shell setprop persist.traced.enable 1 && adb shell start traced` |
|
|
409
|
+
| `permission denied` no device | App não é debuggable ou sem root | Use um build debug, ou `adb shell run-as <package>` |
|
|
410
|
+
| App não abre automaticamente | Package name incorreto ou app não instalado | Verifique com `adb shell pm list packages \| grep <nome>` |
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Desinstalar
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npx perfetto-analyzer uninstall
|
|
418
|
+
```
|
|
51
419
|
|
|
52
|
-
|
|
420
|
+
Remove a skill de todos os diretórios dos agentes suportados.
|
|
53
421
|
|
|
54
|
-
|
|
55
|
-
1. Verify the device is connected
|
|
56
|
-
2. Launch the app if it isn't running
|
|
57
|
-
3. Collect the Perfetto trace
|
|
58
|
-
4. Parse callstacks and frame timelines
|
|
59
|
-
5. Generate a report with hotspots and jank causes
|
|
422
|
+
---
|
|
60
423
|
|
|
61
|
-
##
|
|
424
|
+
## Licença
|
|
62
425
|
|
|
63
426
|
MIT
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perfetto-analyzer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
|
-
"skill/"
|
|
10
|
+
"skill/",
|
|
11
|
+
"CHANGELOG.md"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"postinstall": "node bin/cli.js install --postinstall"
|
|
@@ -11,15 +11,26 @@ data_sources {
|
|
|
11
11
|
config {
|
|
12
12
|
name: "android.heapprofd"
|
|
13
13
|
heapprofd_config {
|
|
14
|
-
sampling_interval_bytes:
|
|
14
|
+
sampling_interval_bytes: 8192
|
|
15
15
|
process_cmdline: "__PACKAGE__"
|
|
16
16
|
shmem_size_bytes: 8388608
|
|
17
|
-
block_client:
|
|
17
|
+
block_client: false
|
|
18
18
|
all_heaps: true
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
# Heap dump Java/Kotlin (ART) — objetos retidos e grafo de referências
|
|
24
|
+
data_sources {
|
|
25
|
+
config {
|
|
26
|
+
name: "android.java_hprof"
|
|
27
|
+
java_hprof_config {
|
|
28
|
+
process_cmdline: "__PACKAGE__"
|
|
29
|
+
dump_smaps: true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
# --- CPU ---
|
|
24
35
|
data_sources {
|
|
25
36
|
config {
|
|
@@ -10,10 +10,10 @@ data_sources {
|
|
|
10
10
|
config {
|
|
11
11
|
name: "android.heapprofd"
|
|
12
12
|
heapprofd_config {
|
|
13
|
-
sampling_interval_bytes:
|
|
13
|
+
sampling_interval_bytes: 8192
|
|
14
14
|
process_cmdline: "__PACKAGE__"
|
|
15
15
|
shmem_size_bytes: 8388608
|
|
16
|
-
block_client:
|
|
16
|
+
block_client: false
|
|
17
17
|
all_heaps: true
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -174,7 +174,7 @@ def generate_report(data, package, trace_type):
|
|
|
174
174
|
lines += ["| Processo | Tempo CPU (s) | Context Switches |"]
|
|
175
175
|
lines += ["|---|---|---|"]
|
|
176
176
|
for proc in top_procs[:10]:
|
|
177
|
-
name = proc.get("process_name"
|
|
177
|
+
name = (proc.get("process_name") or "?")[:50]
|
|
178
178
|
t = proc.get("total_cpu_seconds", 0)
|
|
179
179
|
sw = proc.get("context_switches", 0)
|
|
180
180
|
lines.append(f"| `{name}` | {t:.3f}s | {sw:,} |")
|
|
@@ -209,7 +209,7 @@ def generate_report(data, package, trace_type):
|
|
|
209
209
|
lines += ["|---|---|---|"]
|
|
210
210
|
for cs in app_callstacks[:20]:
|
|
211
211
|
fn = _shorten_jvm_name(cs.get("function_name", "?"))
|
|
212
|
-
thread = cs.get("thread_name"
|
|
212
|
+
thread = (cs.get("thread_name") or "?")[:25]
|
|
213
213
|
count = cs.get("sample_count", 0)
|
|
214
214
|
lines.append(f"| `{fn}` | `{thread}` | {count} |")
|
|
215
215
|
lines.append("")
|
|
@@ -225,7 +225,7 @@ def generate_report(data, package, trace_type):
|
|
|
225
225
|
fn = cs.get("function_name", "?")
|
|
226
226
|
fn = _demangle(fn)
|
|
227
227
|
fn = fn[:60] if len(fn) > 60 else fn
|
|
228
|
-
thread = cs.get("thread_name"
|
|
228
|
+
thread = (cs.get("thread_name") or "?")[:25]
|
|
229
229
|
count = cs.get("sample_count", 0)
|
|
230
230
|
lines.append(f"| `{fn}` | `{thread}` | {count} |")
|
|
231
231
|
lines.append("")
|
|
@@ -302,7 +302,7 @@ def generate_report(data, package, trace_type):
|
|
|
302
302
|
lines += ["|---|---|---|"]
|
|
303
303
|
for j in jank_app[:15]:
|
|
304
304
|
short = _shorten_jvm_name(j.get("function_name", "?"))
|
|
305
|
-
thread = j.get("thread_name"
|
|
305
|
+
thread = (j.get("thread_name") or "?")[:25]
|
|
306
306
|
lines.append(f"| `{short}` | `{thread}` | {j.get('sample_count', 0)} |")
|
|
307
307
|
lines.append("")
|
|
308
308
|
else:
|