perfetto-analyzer 1.0.0

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,204 @@
1
+ #!/usr/bin/env python3
2
+ """Coleta um trace Perfetto de um device Android via adb."""
3
+
4
+ import argparse
5
+ import subprocess
6
+ import sys
7
+ import os
8
+ import time
9
+ import shutil
10
+
11
+ SKILL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12
+ CONFIGS_DIR = os.path.join(SKILL_DIR, "scripts", "configs")
13
+
14
+
15
+ def run(cmd, check=True, capture=False):
16
+ kwargs = {"check": check}
17
+ if capture:
18
+ kwargs["capture_output"] = True
19
+ kwargs["text"] = True
20
+ return subprocess.run(cmd, **kwargs)
21
+
22
+
23
+ def check_adb():
24
+ if not shutil.which("adb"):
25
+ print("ERRO: 'adb' não encontrado no PATH.", file=sys.stderr)
26
+ print("Instale Android SDK Platform-Tools e adicione ao PATH.", file=sys.stderr)
27
+ sys.exit(1)
28
+
29
+
30
+ def check_device():
31
+ result = run(["adb", "devices"], capture=True)
32
+ lines = result.stdout.strip().splitlines()
33
+ devices = [l for l in lines[1:] if l.strip() and "device" in l]
34
+ if not devices:
35
+ print("ERRO: Nenhum device Android conectado.", file=sys.stderr)
36
+ print("Verifique:", file=sys.stderr)
37
+ print(" 1. Depuração USB ativada no device (Opções do Desenvolvedor)", file=sys.stderr)
38
+ print(" 2. Cabo USB conectado e aceito no device", file=sys.stderr)
39
+ print(" 3. Execute 'adb devices' para verificar", file=sys.stderr)
40
+ sys.exit(1)
41
+ if len(devices) > 1:
42
+ print(f"AVISO: Múltiplos devices detectados. Usando o primeiro: {devices[0].split()[0]}", file=sys.stderr)
43
+ return devices[0].split()[0]
44
+
45
+
46
+ def get_config_path(trace_type):
47
+ config_map = {
48
+ "memory": "memory.textproto",
49
+ "cpu": "cpu.textproto",
50
+ "frames": "frames.textproto",
51
+ "battery": "battery.textproto",
52
+ "all": "all.textproto",
53
+ }
54
+ filename = config_map.get(trace_type)
55
+ if not filename:
56
+ print(f"ERRO: Tipo de trace '{trace_type}' inválido.", file=sys.stderr)
57
+ print(f"Tipos disponíveis: {', '.join(config_map.keys())}", file=sys.stderr)
58
+ sys.exit(1)
59
+ path = os.path.join(CONFIGS_DIR, filename)
60
+ if not os.path.exists(path):
61
+ print(f"ERRO: Config não encontrada: {path}", file=sys.stderr)
62
+ sys.exit(1)
63
+ return path
64
+
65
+
66
+ def ensure_app_running(adb, package):
67
+ """Verifica se o app está aberto; inicia via launcher se não estiver."""
68
+ result = run(adb + ["shell", f"pidof {package}"], capture=True, check=False)
69
+ pid = result.stdout.strip()
70
+
71
+ if pid:
72
+ print(f"App em execução (PID: {pid})")
73
+ return
74
+
75
+ print(f"App não está aberto. Iniciando {package}...")
76
+ run(
77
+ adb + ["shell", "monkey", "-p", package,
78
+ "-c", "android.intent.category.LAUNCHER", "1"],
79
+ capture=True,
80
+ check=False,
81
+ )
82
+
83
+ # Aguarda até 15s para o processo aparecer
84
+ for attempt in range(15):
85
+ time.sleep(1)
86
+ result = run(adb + ["shell", f"pidof {package}"], capture=True, check=False)
87
+ if result.stdout.strip():
88
+ print(f"App iniciado (PID: {result.stdout.strip()})")
89
+ # Pausa extra para deixar a UI estabilizar antes de coletar
90
+ time.sleep(2)
91
+ return
92
+ print(f" Aguardando inicialização... {attempt + 1}s", end="\r", flush=True)
93
+
94
+ print(f"\nAVISO: Não foi possível confirmar a abertura do app após 15s. Continuando mesmo assim.")
95
+
96
+
97
+ def inject_package(config_path, package, duration_ms):
98
+ """Lê o textproto e injeta package e duração se necessário."""
99
+ with open(config_path) as f:
100
+ content = f.read()
101
+ content = content.replace("__PACKAGE__", package)
102
+ content = content.replace("__DURATION_MS__", str(duration_ms))
103
+ tmp_config = "/tmp/perfetto_config_patched.textproto"
104
+ with open(tmp_config, "w") as f:
105
+ f.write(content)
106
+ return tmp_config
107
+
108
+
109
+ # O daemon traced escreve em /data/misc/perfetto-traces/ — é o único path que ele tem acesso.
110
+ DEVICE_TRACE_PATH = "/data/misc/perfetto-traces/perfetto_trace.pb"
111
+
112
+
113
+ def collect_trace(device_id, config_path, duration_seconds, output_path):
114
+ adb = ["adb", "-s", device_id]
115
+
116
+ # Limpar arquivo anterior — se foi criado por outro UID, o daemon não consegue sobrescrever.
117
+ run(adb + ["shell", f"rm -f {DEVICE_TRACE_PATH}"], check=False)
118
+
119
+ print(f"[1/3] Coletando trace ({duration_seconds}s) — interaja com o app agora!")
120
+
121
+ with open(config_path, "rb") as config_file:
122
+ config_content = config_file.read()
123
+
124
+ proc = subprocess.Popen(
125
+ adb + ["shell", f"perfetto --txt -c - -o {DEVICE_TRACE_PATH}"],
126
+ stdin=subprocess.PIPE,
127
+ stdout=subprocess.PIPE,
128
+ stderr=subprocess.PIPE,
129
+ )
130
+ proc.stdin.write(config_content)
131
+ proc.stdin.close()
132
+
133
+ # Countdown baseado em tempo real — atualiza só quando o segundo muda
134
+ start = time.monotonic()
135
+ last_sec = -1
136
+ while True:
137
+ elapsed = time.monotonic() - start
138
+ if elapsed >= duration_seconds:
139
+ break
140
+ current_sec = int(elapsed)
141
+ if current_sec != last_sec:
142
+ print(f" ... {current_sec}/{duration_seconds}s", end="\r", flush=True)
143
+ last_sec = current_sec
144
+ time.sleep(0.1)
145
+ print(f" ... {duration_seconds}/{duration_seconds}s")
146
+ print("Finalizando coleta...")
147
+
148
+ # Aguarda o perfetto terminar com timeout fixo para evitar hang
149
+ try:
150
+ proc.wait(timeout=20)
151
+ except subprocess.TimeoutExpired:
152
+ proc.kill()
153
+ proc.wait()
154
+ print("ERRO: Perfetto não finalizou em 20s após a coleta.", file=sys.stderr)
155
+ sys.exit(1)
156
+
157
+ if proc.returncode != 0:
158
+ stderr = proc.stderr.read().decode(errors="replace")
159
+ print(f"ERRO ao coletar trace (exit {proc.returncode}): {stderr}", file=sys.stderr)
160
+ sys.exit(1)
161
+
162
+ print(f"[2/3] Baixando trace do device...")
163
+ run(adb + ["pull", DEVICE_TRACE_PATH, output_path])
164
+
165
+ print(f"[3/3] Limpando arquivo do device...")
166
+ run(adb + ["shell", f"rm -f {DEVICE_TRACE_PATH}"], check=False)
167
+
168
+ size_kb = os.path.getsize(output_path) // 1024
169
+ if size_kb == 0:
170
+ print("ERRO: Trace vazio (0 bytes). Verifique se o traced daemon está ativo.", file=sys.stderr)
171
+ print("Dica: adb shell 'setprop persist.traced.enable 1 && start traced'", file=sys.stderr)
172
+ sys.exit(1)
173
+
174
+ print(f"\nTrace coletado: {output_path} ({size_kb} KB)")
175
+
176
+
177
+ def main():
178
+ parser = argparse.ArgumentParser(description="Coleta trace Perfetto via adb")
179
+ parser.add_argument("--package", required=True, help="Package do app (ex: com.example.app)")
180
+ parser.add_argument("--type", required=True, choices=["memory", "cpu", "frames", "battery", "all"],
181
+ dest="trace_type", help="Tipo de trace a coletar")
182
+ parser.add_argument("--duration", type=int, default=30, help="Duração em segundos (padrão: 30)")
183
+ parser.add_argument("--output", default="/tmp/perfetto_trace.pb", help="Caminho de saída do .pb")
184
+ args = parser.parse_args()
185
+
186
+ check_adb()
187
+ device_id = check_device()
188
+ adb = ["adb", "-s", device_id]
189
+
190
+ print(f"Device: {device_id}")
191
+ print(f"Package: {args.package}")
192
+ print(f"Tipo: {args.trace_type}")
193
+ print(f"Duração: {args.duration}s\n")
194
+
195
+ ensure_app_running(adb, args.package)
196
+
197
+ raw_config = get_config_path(args.trace_type)
198
+ patched_config = inject_package(raw_config, args.package, args.duration * 1000)
199
+
200
+ collect_trace(device_id, patched_config, args.duration, args.output)
201
+
202
+
203
+ if __name__ == "__main__":
204
+ main()
@@ -0,0 +1,111 @@
1
+ duration_ms: __DURATION_MS__
2
+
3
+ # Buffer grande para captura completa
4
+ buffers {
5
+ size_kb: 131072
6
+ fill_policy: RING_BUFFER
7
+ }
8
+
9
+ # --- MEMÓRIA ---
10
+ data_sources {
11
+ config {
12
+ name: "android.heapprofd"
13
+ heapprofd_config {
14
+ sampling_interval_bytes: 4096
15
+ process_cmdline: "__PACKAGE__"
16
+ shmem_size_bytes: 8388608
17
+ block_client: true
18
+ all_heaps: true
19
+ }
20
+ }
21
+ }
22
+
23
+ # --- CPU ---
24
+ data_sources {
25
+ config {
26
+ name: "linux.perf"
27
+ perf_event_config {
28
+ timebase {
29
+ frequency: 100
30
+ counter: SW_CPU_CLOCK
31
+ }
32
+ callstack_sampling {
33
+ scope {
34
+ target_cmdline: "__PACKAGE__"
35
+ }
36
+ kernel_frames: true
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ # --- FRAMES ---
43
+ data_sources {
44
+ config {
45
+ name: "android.surfaceflinger.frametimeline"
46
+ }
47
+ }
48
+
49
+ # --- BATTERY ---
50
+ data_sources {
51
+ config {
52
+ name: "android.power"
53
+ android_power_config {
54
+ battery_poll_ms: 1000
55
+ battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT
56
+ battery_counters: BATTERY_COUNTER_CHARGE
57
+ battery_counters: BATTERY_COUNTER_CURRENT
58
+ battery_counters: BATTERY_COUNTER_VOLTAGE
59
+ collect_power_rails: true
60
+ }
61
+ }
62
+ }
63
+
64
+ # --- ATRACE + FTRACE (formato compatível Perfetto v25+) ---
65
+ data_sources {
66
+ config {
67
+ name: "linux.ftrace"
68
+ ftrace_config {
69
+ ftrace_events: "sched/sched_switch"
70
+ ftrace_events: "sched/sched_wakeup"
71
+ ftrace_events: "sched/sched_waking"
72
+ ftrace_events: "power/cpu_frequency"
73
+ ftrace_events: "power/cpu_idle"
74
+ ftrace_events: "power/wakeup_source_activate"
75
+ ftrace_events: "power/wakeup_source_deactivate"
76
+ ftrace_events: "mm_event/mm_event_record"
77
+ atrace_categories: "gfx"
78
+ atrace_categories: "view"
79
+ atrace_categories: "wm"
80
+ atrace_categories: "am"
81
+ atrace_categories: "input"
82
+ atrace_categories: "dalvik"
83
+ atrace_apps: "__PACKAGE__"
84
+ }
85
+ }
86
+ }
87
+
88
+ data_sources {
89
+ config {
90
+ name: "linux.sys_stats"
91
+ sys_stats_config {
92
+ meminfo_period_ms: 1000
93
+ meminfo_counters: MEMINFO_MEM_TOTAL
94
+ meminfo_counters: MEMINFO_MEM_FREE
95
+ meminfo_counters: MEMINFO_MEM_AVAILABLE
96
+ stat_period_ms: 1000
97
+ stat_counters: STAT_CPU_TIMES
98
+ cpufreq_period_ms: 500
99
+ }
100
+ }
101
+ }
102
+
103
+ data_sources {
104
+ config {
105
+ name: "linux.process_stats"
106
+ process_stats_config {
107
+ scan_all_processes_on_start: true
108
+ proc_stats_poll_ms: 1000
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,65 @@
1
+ duration_ms: __DURATION_MS__
2
+
3
+ buffers {
4
+ size_kb: 32768
5
+ fill_policy: RING_BUFFER
6
+ }
7
+
8
+ # Contadores de bateria do Android
9
+ data_sources {
10
+ config {
11
+ name: "android.power"
12
+ android_power_config {
13
+ battery_poll_ms: 1000
14
+ battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT
15
+ battery_counters: BATTERY_COUNTER_CHARGE
16
+ battery_counters: BATTERY_COUNTER_CURRENT
17
+ battery_counters: BATTERY_COUNTER_VOLTAGE
18
+ collect_power_rails: true
19
+ }
20
+ }
21
+ }
22
+
23
+ # CPU callstack sampling a 60 Hz — identifica qual código do app drena a bateria
24
+ data_sources {
25
+ config {
26
+ name: "linux.perf"
27
+ perf_event_config {
28
+ timebase {
29
+ frequency: 60
30
+ counter: SW_CPU_CLOCK
31
+ }
32
+ callstack_sampling {
33
+ scope {
34
+ target_cmdline: "__PACKAGE__"
35
+ }
36
+ kernel_frames: true
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ # Wakelock tracking
43
+ data_sources {
44
+ config {
45
+ name: "linux.ftrace"
46
+ ftrace_config {
47
+ ftrace_events: "power/wakeup_source_activate"
48
+ ftrace_events: "power/wakeup_source_deactivate"
49
+ ftrace_events: "power/suspend_resume"
50
+ ftrace_events: "power/cpu_frequency"
51
+ ftrace_events: "power/cpu_idle"
52
+ }
53
+ }
54
+ }
55
+
56
+ # System stats para correlação
57
+ data_sources {
58
+ config {
59
+ name: "linux.sys_stats"
60
+ sys_stats_config {
61
+ stat_period_ms: 1000
62
+ stat_counters: STAT_CPU_TIMES
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,68 @@
1
+ duration_ms: __DURATION_MS__
2
+
3
+ buffers {
4
+ size_kb: 65536
5
+ fill_policy: RING_BUFFER
6
+ }
7
+
8
+ # CPU sampling (Linux perf events)
9
+ data_sources {
10
+ config {
11
+ name: "linux.perf"
12
+ perf_event_config {
13
+ timebase {
14
+ frequency: 100
15
+ counter: SW_CPU_CLOCK
16
+ }
17
+ callstack_sampling {
18
+ scope {
19
+ target_cmdline: "__PACKAGE__"
20
+ }
21
+ kernel_frames: true
22
+ }
23
+ }
24
+ }
25
+ }
26
+
27
+ # Scheduling events via ftrace
28
+ data_sources {
29
+ config {
30
+ name: "linux.ftrace"
31
+ ftrace_config {
32
+ ftrace_events: "sched/sched_switch"
33
+ ftrace_events: "sched/sched_wakeup"
34
+ ftrace_events: "sched/sched_wakeup_new"
35
+ ftrace_events: "sched/sched_waking"
36
+ ftrace_events: "sched/sched_process_exit"
37
+ ftrace_events: "sched/sched_process_free"
38
+ ftrace_events: "task/task_newtask"
39
+ ftrace_events: "task/task_rename"
40
+ ftrace_events: "power/cpu_frequency"
41
+ ftrace_events: "power/cpu_idle"
42
+ }
43
+ }
44
+ }
45
+
46
+ # System stats (CPU usage)
47
+ data_sources {
48
+ config {
49
+ name: "linux.sys_stats"
50
+ sys_stats_config {
51
+ stat_period_ms: 1000
52
+ stat_counters: STAT_CPU_TIMES
53
+ stat_counters: STAT_FORK_COUNT
54
+ cpufreq_period_ms: 500
55
+ }
56
+ }
57
+ }
58
+
59
+ # Processos e threads
60
+ data_sources {
61
+ config {
62
+ name: "linux.process_stats"
63
+ process_stats_config {
64
+ scan_all_processes_on_start: true
65
+ proc_stats_poll_ms: 1000
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,62 @@
1
+ duration_ms: __DURATION_MS__
2
+
3
+ buffers {
4
+ size_kb: 65536
5
+ fill_policy: RING_BUFFER
6
+ }
7
+
8
+ # Android Frame Timeline (Android 12+ / API 31+)
9
+ data_sources {
10
+ config {
11
+ name: "android.surfaceflinger.frametimeline"
12
+ }
13
+ }
14
+
15
+ # Ftrace com ATrace categories (formato compatível com Perfetto v25+)
16
+ data_sources {
17
+ config {
18
+ name: "linux.ftrace"
19
+ ftrace_config {
20
+ ftrace_events: "sched/sched_switch"
21
+ ftrace_events: "sched/sched_wakeup"
22
+ ftrace_events: "drm/drm_vblank_event"
23
+ ftrace_events: "gpu_mem/gpu_mem_total"
24
+ atrace_categories: "gfx"
25
+ atrace_categories: "view"
26
+ atrace_categories: "wm"
27
+ atrace_categories: "am"
28
+ atrace_categories: "input"
29
+ atrace_categories: "res"
30
+ atrace_categories: "dalvik"
31
+ atrace_apps: "__PACKAGE__"
32
+ }
33
+ }
34
+ }
35
+
36
+ # CPU callstack sampling a 60 Hz — identifica qual função causou cada frame drop
37
+ data_sources {
38
+ config {
39
+ name: "linux.perf"
40
+ perf_event_config {
41
+ timebase {
42
+ frequency: 60
43
+ counter: SW_CPU_CLOCK
44
+ }
45
+ callstack_sampling {
46
+ scope {
47
+ target_cmdline: "__PACKAGE__"
48
+ }
49
+ kernel_frames: true
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ data_sources {
56
+ config {
57
+ name: "linux.process_stats"
58
+ process_stats_config {
59
+ scan_all_processes_on_start: true
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,57 @@
1
+ duration_ms: __DURATION_MS__
2
+
3
+ # Heap profiling nativo (C/C++/Rust via heapprofd)
4
+ buffers {
5
+ size_kb: 65536
6
+ fill_policy: RING_BUFFER
7
+ }
8
+
9
+ data_sources {
10
+ config {
11
+ name: "android.heapprofd"
12
+ heapprofd_config {
13
+ sampling_interval_bytes: 4096
14
+ process_cmdline: "__PACKAGE__"
15
+ shmem_size_bytes: 8388608
16
+ block_client: true
17
+ all_heaps: true
18
+ }
19
+ }
20
+ }
21
+
22
+ # Heap dumps Java/Kotlin (ART)
23
+ data_sources {
24
+ config {
25
+ name: "android.java_hprof"
26
+ java_hprof_config {
27
+ process_cmdline: "__PACKAGE__"
28
+ dump_smaps: true
29
+ }
30
+ }
31
+ }
32
+
33
+ # Contadores de memória do sistema
34
+ data_sources {
35
+ config {
36
+ name: "linux.sys_stats"
37
+ sys_stats_config {
38
+ meminfo_period_ms: 1000
39
+ meminfo_counters: MEMINFO_MEM_TOTAL
40
+ meminfo_counters: MEMINFO_MEM_FREE
41
+ meminfo_counters: MEMINFO_MEM_AVAILABLE
42
+ meminfo_counters: MEMINFO_CACHED
43
+ }
44
+ }
45
+ }
46
+
47
+ # Ftrace para rastrear eventos de memória
48
+ data_sources {
49
+ config {
50
+ name: "linux.ftrace"
51
+ ftrace_config {
52
+ ftrace_events: "mm_event/mm_event_record"
53
+ ftrace_events: "lowmemorykiller/lowmemory_kill"
54
+ ftrace_events: "oom/mark_victim"
55
+ }
56
+ }
57
+ }