simple-python-audit 1.1.0__tar.gz → 1.3.0__tar.gz

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,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple-python-audit
3
+ Version: 1.3.0
4
+ Summary: Ferramenta de profiling com widget flutuante, exportação CSV e servidor de gerenciamento de logs.
5
+ Author-email: Sadson Diego <sadsondiego@gmail.com>
6
+ License: MIT
7
+ Keywords: odoo,profiler,performance,pyinstrument,audit
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Framework :: Odoo
14
+ Classifier: Topic :: Software Development :: Debuggers
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: pyinstrument>=4.6.0
18
+ Requires-Dist: typing-extensions>=4.0.0
19
+
20
+ # Simple Python Audit Tool 🚀
21
+
22
+ Ferramenta completa para medição de performance e auditoria de variáveis para Python.
23
+
24
+ ## 📦 Instalação
25
+
26
+ ```bash
27
+ pip install .
28
+ # Ou via GitHub
29
+ pip install git+https://github.com/sadson/simple_python_audit.git
30
+ ```
31
+
32
+ ## 🎯 Como Usar
33
+
34
+ ```python
35
+ from simple_python_audit import profile
36
+
37
+ @profile(
38
+ html=True,
39
+ trace=True,
40
+ deep=True,
41
+ print_terminal=True,
42
+ save_csv=True,
43
+ output_path="/tmp/simple_python_audit_perf",
44
+ )
45
+ def meu_metodo_lento(self):
46
+ # Seu código aqui
47
+ ```
48
+
49
+ ### Parâmetros do decorador
50
+
51
+ | Parâmetro | Padrão | Descrição |
52
+ |------------------|-------------------------------------|---------------------------------------------------------------------------|
53
+ | `html` | `False` | Gera relatório interativo em HTML com widget flutuante |
54
+ | `trace` | `False` | Rastreia chamadas de funções e coleta métricas por função |
55
+ | `deep` | `False` | Aumenta granularidade do profiler (intervalo de 0,0001 s em vez de 0,001 s) |
56
+ | `print_terminal` | `False` | Imprime tabela de métricas formatada no terminal ao final da execução |
57
+ | `save_csv` | `False` | Salva os dados em arquivos CSV prontos para análise com pandas/IA |
58
+ | `output_path` | `"/tmp/simple_python_audit_perf"` | Diretório onde os relatórios (HTML e CSV) serão gravados |
59
+
60
+ ---
61
+
62
+ ## 📊 Saída no Terminal (`print_terminal=True`)
63
+
64
+ Exibe uma tabela formatada diretamente no stdout ao término da função:
65
+
66
+ ```
67
+ ==============================================================
68
+ AUDIT: meu_metodo_lento | 1.2345s
69
+ ==============================================================
70
+ CPU Total: 0.4567s
71
+ CPU Usage: 37.0%
72
+ Memory Current: 12.345 MB
73
+ Memory Peak: 15.678 MB
74
+ Memory Avg/Call: 2.34 KB/call
75
+ Total Calls Traced: 1234
76
+ --------------------------------------------------------------
77
+ Top Functions by CPU Time:
78
+ Function Calls Total Avg Peak
79
+ meu_metodo_lento 1 1.2300s 1.23000s 1.23000s
80
+ _query 42 0.4500s 0.01071s 0.02100s
81
+ ...
82
+ --------------------------------------------------------------
83
+ Top Functions by Memory:
84
+ Function Calls Total KB Avg KB Peak KB
85
+ _query 42 512.30 12.20 48.50
86
+ ...
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 💾 Exportação CSV (`save_csv=True`)
92
+
93
+ Gera dois arquivos CSV em `output_path`, ideais para carregar em um `DataFrame` do pandas, alimentar uma IA ou construir dashboards.
94
+
95
+ ### `AUDIT_{func}_{timestamp}_summary.csv`
96
+
97
+ Uma linha por execução com as métricas globais da chamada. Acumulando múltiplas execuções é possível rastrear regressões de performance ao longo do tempo.
98
+
99
+ | Coluna | Tipo | Descrição |
100
+ |-------------------------|---------|------------------------------------------------|
101
+ | `timestamp` | int | Unix timestamp da execução |
102
+ | `func_name` | str | Nome da função decorada |
103
+ | `duration_s` | float | Tempo total de execução (segundos) |
104
+ | `cpu_total_s` | float | Tempo de CPU consumido (segundos) |
105
+ | `cpu_pct` | float | Percentual de uso de CPU |
106
+ | `mem_current_mb` | float | Memória alocada ao final (MB) |
107
+ | `mem_peak_mb` | float | Pico de memória durante a execução (MB) |
108
+ | `mem_avg_kb_per_call` | float | Média de memória por chamada de função (KB) |
109
+ | `total_calls_traced` | int | Total de chamadas de função rastreadas |
110
+
111
+ ### `AUDIT_{func}_{timestamp}_trace.csv`
112
+
113
+ Uma linha por função rastreada (requer `trace=True`). Permite identificar os gargalos com precisão.
114
+
115
+ | Coluna | Tipo | Descrição |
116
+ |--------------------|-------|-----------------------------------------------|
117
+ | `timestamp` | int | Unix timestamp — chave para junção com summary |
118
+ | `func_name` | str | Nome da função decorada |
119
+ | `called_function` | str | Nome da função rastreada |
120
+ | `call_count` | int | Número de chamadas |
121
+ | `total_time_s` | float | Tempo total acumulado (segundos) |
122
+ | `avg_time_s` | float | Tempo médio por chamada (segundos) |
123
+ | `peak_time_s` | float | Pico de tempo em uma única chamada (segundos) |
124
+ | `total_mem_kb` | float | Memória total alocada (KB) |
125
+ | `avg_mem_kb` | float | Memória média por chamada (KB) |
126
+ | `peak_mem_kb` | float | Pico de memória em uma única chamada (KB) |
127
+
128
+ ### Exemplo de análise com pandas
129
+
130
+ ```python
131
+ import pandas as pd
132
+ import glob
133
+
134
+ # Carregar todos os resumos de execuções
135
+ summary = pd.concat([pd.read_csv(f) for f in glob.glob("/tmp/simple_python_audit_perf/*_summary.csv")])
136
+ summary["timestamp"] = pd.to_datetime(summary["timestamp"], unit="s")
137
+ summary.sort_values("timestamp").plot(x="timestamp", y="duration_s", title="Evolução do tempo de execução")
138
+
139
+ # Carregar trace de uma execução específica
140
+ trace = pd.read_csv("/tmp/simple_python_audit_perf/AUDIT_meu_metodo_lento_1234567890_trace.csv")
141
+ trace.sort_values("total_time_s", ascending=False).head(10)
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 🌐 Relatório HTML (`html=True`)
147
+
148
+ Gera um arquivo `AUDIT_{func}_{timestamp}.html` com um widget flutuante interativo contendo:
149
+
150
+ - **Painel de rastreamento (🚀):** tabela de chamadas de função com busca em tempo real
151
+ - **Painel de métricas (📊):** cards de CPU e memória, tabelas de CPU e memória por função
152
+
153
+ ---
154
+
155
+ ## 🛠️ Servidor de Auditoria
156
+
157
+ Interface web para visualizar e gerenciar os arquivos de relatório gerados.
158
+
159
+ ```bash
160
+ # Via linha de comando
161
+ simple-python-audit-server --port 8080 --dir /tmp/simple_python_audit_perf
162
+
163
+ # Via Python
164
+ from simple_python_audit import start_server
165
+ start_server(port=8080, directory="/tmp/simple_python_audit_perf")
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 🔗 Integração com Odoo
171
+
172
+ Ao instalar em um ambiente com Odoo, o decorador `profile` é injetado automaticamente em `odoo.api`:
173
+
174
+ ```python
175
+ from odoo import api
176
+
177
+ @api.profile(html=True, trace=True, print_terminal=True, save_csv=True)
178
+ def action_processar(self):
179
+ ...
180
+ ```
@@ -0,0 +1,161 @@
1
+ # Simple Python Audit Tool 🚀
2
+
3
+ Ferramenta completa para medição de performance e auditoria de variáveis para Python.
4
+
5
+ ## 📦 Instalação
6
+
7
+ ```bash
8
+ pip install .
9
+ # Ou via GitHub
10
+ pip install git+https://github.com/sadson/simple_python_audit.git
11
+ ```
12
+
13
+ ## 🎯 Como Usar
14
+
15
+ ```python
16
+ from simple_python_audit import profile
17
+
18
+ @profile(
19
+ html=True,
20
+ trace=True,
21
+ deep=True,
22
+ print_terminal=True,
23
+ save_csv=True,
24
+ output_path="/tmp/simple_python_audit_perf",
25
+ )
26
+ def meu_metodo_lento(self):
27
+ # Seu código aqui
28
+ ```
29
+
30
+ ### Parâmetros do decorador
31
+
32
+ | Parâmetro | Padrão | Descrição |
33
+ |------------------|-------------------------------------|---------------------------------------------------------------------------|
34
+ | `html` | `False` | Gera relatório interativo em HTML com widget flutuante |
35
+ | `trace` | `False` | Rastreia chamadas de funções e coleta métricas por função |
36
+ | `deep` | `False` | Aumenta granularidade do profiler (intervalo de 0,0001 s em vez de 0,001 s) |
37
+ | `print_terminal` | `False` | Imprime tabela de métricas formatada no terminal ao final da execução |
38
+ | `save_csv` | `False` | Salva os dados em arquivos CSV prontos para análise com pandas/IA |
39
+ | `output_path` | `"/tmp/simple_python_audit_perf"` | Diretório onde os relatórios (HTML e CSV) serão gravados |
40
+
41
+ ---
42
+
43
+ ## 📊 Saída no Terminal (`print_terminal=True`)
44
+
45
+ Exibe uma tabela formatada diretamente no stdout ao término da função:
46
+
47
+ ```
48
+ ==============================================================
49
+ AUDIT: meu_metodo_lento | 1.2345s
50
+ ==============================================================
51
+ CPU Total: 0.4567s
52
+ CPU Usage: 37.0%
53
+ Memory Current: 12.345 MB
54
+ Memory Peak: 15.678 MB
55
+ Memory Avg/Call: 2.34 KB/call
56
+ Total Calls Traced: 1234
57
+ --------------------------------------------------------------
58
+ Top Functions by CPU Time:
59
+ Function Calls Total Avg Peak
60
+ meu_metodo_lento 1 1.2300s 1.23000s 1.23000s
61
+ _query 42 0.4500s 0.01071s 0.02100s
62
+ ...
63
+ --------------------------------------------------------------
64
+ Top Functions by Memory:
65
+ Function Calls Total KB Avg KB Peak KB
66
+ _query 42 512.30 12.20 48.50
67
+ ...
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 💾 Exportação CSV (`save_csv=True`)
73
+
74
+ Gera dois arquivos CSV em `output_path`, ideais para carregar em um `DataFrame` do pandas, alimentar uma IA ou construir dashboards.
75
+
76
+ ### `AUDIT_{func}_{timestamp}_summary.csv`
77
+
78
+ Uma linha por execução com as métricas globais da chamada. Acumulando múltiplas execuções é possível rastrear regressões de performance ao longo do tempo.
79
+
80
+ | Coluna | Tipo | Descrição |
81
+ |-------------------------|---------|------------------------------------------------|
82
+ | `timestamp` | int | Unix timestamp da execução |
83
+ | `func_name` | str | Nome da função decorada |
84
+ | `duration_s` | float | Tempo total de execução (segundos) |
85
+ | `cpu_total_s` | float | Tempo de CPU consumido (segundos) |
86
+ | `cpu_pct` | float | Percentual de uso de CPU |
87
+ | `mem_current_mb` | float | Memória alocada ao final (MB) |
88
+ | `mem_peak_mb` | float | Pico de memória durante a execução (MB) |
89
+ | `mem_avg_kb_per_call` | float | Média de memória por chamada de função (KB) |
90
+ | `total_calls_traced` | int | Total de chamadas de função rastreadas |
91
+
92
+ ### `AUDIT_{func}_{timestamp}_trace.csv`
93
+
94
+ Uma linha por função rastreada (requer `trace=True`). Permite identificar os gargalos com precisão.
95
+
96
+ | Coluna | Tipo | Descrição |
97
+ |--------------------|-------|-----------------------------------------------|
98
+ | `timestamp` | int | Unix timestamp — chave para junção com summary |
99
+ | `func_name` | str | Nome da função decorada |
100
+ | `called_function` | str | Nome da função rastreada |
101
+ | `call_count` | int | Número de chamadas |
102
+ | `total_time_s` | float | Tempo total acumulado (segundos) |
103
+ | `avg_time_s` | float | Tempo médio por chamada (segundos) |
104
+ | `peak_time_s` | float | Pico de tempo em uma única chamada (segundos) |
105
+ | `total_mem_kb` | float | Memória total alocada (KB) |
106
+ | `avg_mem_kb` | float | Memória média por chamada (KB) |
107
+ | `peak_mem_kb` | float | Pico de memória em uma única chamada (KB) |
108
+
109
+ ### Exemplo de análise com pandas
110
+
111
+ ```python
112
+ import pandas as pd
113
+ import glob
114
+
115
+ # Carregar todos os resumos de execuções
116
+ summary = pd.concat([pd.read_csv(f) for f in glob.glob("/tmp/simple_python_audit_perf/*_summary.csv")])
117
+ summary["timestamp"] = pd.to_datetime(summary["timestamp"], unit="s")
118
+ summary.sort_values("timestamp").plot(x="timestamp", y="duration_s", title="Evolução do tempo de execução")
119
+
120
+ # Carregar trace de uma execução específica
121
+ trace = pd.read_csv("/tmp/simple_python_audit_perf/AUDIT_meu_metodo_lento_1234567890_trace.csv")
122
+ trace.sort_values("total_time_s", ascending=False).head(10)
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 🌐 Relatório HTML (`html=True`)
128
+
129
+ Gera um arquivo `AUDIT_{func}_{timestamp}.html` com um widget flutuante interativo contendo:
130
+
131
+ - **Painel de rastreamento (🚀):** tabela de chamadas de função com busca em tempo real
132
+ - **Painel de métricas (📊):** cards de CPU e memória, tabelas de CPU e memória por função
133
+
134
+ ---
135
+
136
+ ## 🛠️ Servidor de Auditoria
137
+
138
+ Interface web para visualizar e gerenciar os arquivos de relatório gerados.
139
+
140
+ ```bash
141
+ # Via linha de comando
142
+ simple-python-audit-server --port 8080 --dir /tmp/simple_python_audit_perf
143
+
144
+ # Via Python
145
+ from simple_python_audit import start_server
146
+ start_server(port=8080, directory="/tmp/simple_python_audit_perf")
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 🔗 Integração com Odoo
152
+
153
+ Ao instalar em um ambiente com Odoo, o decorador `profile` é injetado automaticamente em `odoo.api`:
154
+
155
+ ```python
156
+ from odoo import api
157
+
158
+ @api.profile(html=True, trace=True, print_terminal=True, save_csv=True)
159
+ def action_processar(self):
160
+ ...
161
+ ```
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simple-python-audit"
7
- version = "1.1.0"
8
- description = "Ferramenta de profiling com widget flutuante e servidor de gerenciamento de logs."
7
+ version = "1.3.0"
8
+ description = "Ferramenta de profiling com widget flutuante, exportação CSV e servidor de gerenciamento de logs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
11
11
  license = {text = "MIT"}
@@ -0,0 +1,352 @@
1
+ import os
2
+ import csv as _csv
3
+ import time
4
+ import json
5
+ import sys
6
+ import logging
7
+ import tracemalloc
8
+ from functools import wraps
9
+ from pyinstrument import Profiler
10
+ from collections import defaultdict
11
+
12
+ _logger = logging.getLogger(__name__)
13
+
14
+ def html_escape(text):
15
+ return str(text).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
16
+
17
+ def _print_terminal(func_name, duration, cpu_total, current_mem, peak_mem,
18
+ total_calls, stats_data, mem_stats_data, trace):
19
+ """Print audit metrics as a formatted table to stdout."""
20
+ cpu_pct = round(cpu_total / duration * 100, 1) if duration > 0 else 0
21
+ mem_current_mb = round(current_mem / 1024 / 1024, 3)
22
+ mem_peak_mb = round(peak_mem / 1024 / 1024, 3)
23
+ mem_avg = f"{round(peak_mem / total_calls / 1024, 2)} KB/call" if total_calls > 0 else "N/A"
24
+
25
+ width = 62
26
+ sep = "=" * width
27
+ thin = "-" * width
28
+
29
+ print(f"\n{sep}")
30
+ print(f" AUDIT: {func_name} | {duration:.4f}s")
31
+ print(sep)
32
+ print(f" {'CPU Total:':<22} {cpu_total:.4f}s")
33
+ print(f" {'CPU Usage:':<22} {cpu_pct}%")
34
+ print(f" {'Memory Current:':<22} {mem_current_mb} MB")
35
+ print(f" {'Memory Peak:':<22} {mem_peak_mb} MB")
36
+ print(f" {'Memory Avg/Call:':<22} {mem_avg}")
37
+ if trace:
38
+ print(f" {'Total Calls Traced:':<22} {total_calls}")
39
+ print(thin)
40
+
41
+ if trace and stats_data:
42
+ print(f" Top Functions by CPU Time:")
43
+ header = f" {'Function':<30} {'Calls':>6} {'Total':>9} {'Avg':>9} {'Peak':>9}"
44
+ print(header)
45
+ print(f" {'-'*30} {'-'*6} {'-'*9} {'-'*9} {'-'*9}")
46
+ for s in stats_data[:15]:
47
+ name = s['name'][:30]
48
+ print(f" {name:<30} {s['count']:>6} {s['total']:>8}s {s['avg']:>8}s {s['peak']:>8}s")
49
+ print(thin)
50
+
51
+ if trace and mem_stats_data:
52
+ print(f" Top Functions by Memory:")
53
+ header = f" {'Function':<30} {'Calls':>6} {'Total KB':>10} {'Avg KB':>8} {'Peak KB':>8}"
54
+ print(header)
55
+ print(f" {'-'*30} {'-'*6} {'-'*10} {'-'*8} {'-'*8}")
56
+ for s in mem_stats_data[:15]:
57
+ name = s['name'][:30]
58
+ print(f" {name:<30} {s['count']:>6} {s['total_mem']:>10} {s['avg_mem']:>8} {s['peak_mem']:>8}")
59
+ print(thin)
60
+
61
+ print()
62
+
63
+
64
+ def _save_csv(output_path, func_name, timestamp, duration, cpu_total, current_mem, peak_mem,
65
+ total_calls, stats_data, mem_stats_data, trace):
66
+ """
67
+ Save audit metrics to CSV files suitable for pandas/AI analysis.
68
+
69
+ Generates two files:
70
+ - AUDIT_{func_name}_{timestamp}_summary.csv : one row with all summary metrics
71
+ - AUDIT_{func_name}_{timestamp}_trace.csv : per-function trace rows (when trace=True)
72
+ """
73
+ cpu_pct = round(cpu_total / duration * 100, 1) if duration > 0 else 0
74
+
75
+ # --- Summary CSV ---
76
+ summary_path = os.path.join(output_path, f"AUDIT_{func_name}_{timestamp}_summary.csv")
77
+ summary_row = {
78
+ "timestamp": timestamp,
79
+ "func_name": func_name,
80
+ "duration_s": round(duration, 6),
81
+ "cpu_total_s": round(cpu_total, 6),
82
+ "cpu_pct": cpu_pct,
83
+ "mem_current_mb": round(current_mem / 1024 / 1024, 6),
84
+ "mem_peak_mb": round(peak_mem / 1024 / 1024, 6),
85
+ "mem_avg_kb_per_call": round(peak_mem / total_calls / 1024, 4) if total_calls > 0 else None,
86
+ "total_calls_traced": total_calls if trace else None,
87
+ }
88
+ with open(summary_path, "w", newline="", encoding="utf-8") as f:
89
+ writer = _csv.DictWriter(f, fieldnames=list(summary_row.keys()))
90
+ writer.writeheader()
91
+ writer.writerow(summary_row)
92
+
93
+ # --- Trace CSV (only when trace=True) ---
94
+ if trace and (stats_data or mem_stats_data):
95
+ trace_path = os.path.join(output_path, f"AUDIT_{func_name}_{timestamp}_trace.csv")
96
+ # Merge cpu and mem stats by function name
97
+ mem_by_name = {s["name"]: s for s in mem_stats_data}
98
+ fieldnames = [
99
+ "timestamp", "func_name", "called_function",
100
+ "call_count",
101
+ "total_time_s", "avg_time_s", "peak_time_s",
102
+ "total_mem_kb", "avg_mem_kb", "peak_mem_kb",
103
+ ]
104
+ with open(trace_path, "w", newline="", encoding="utf-8") as f:
105
+ writer = _csv.DictWriter(f, fieldnames=fieldnames)
106
+ writer.writeheader()
107
+ for s in stats_data:
108
+ mem = mem_by_name.get(s["name"], {})
109
+ writer.writerow({
110
+ "timestamp": timestamp,
111
+ "func_name": func_name,
112
+ "called_function": s["name"],
113
+ "call_count": s["count"],
114
+ "total_time_s": s["total"],
115
+ "avg_time_s": s["avg"],
116
+ "peak_time_s": s["peak"],
117
+ "total_mem_kb": mem.get("total_mem", None),
118
+ "avg_mem_kb": mem.get("avg_mem", None),
119
+ "peak_mem_kb": mem.get("peak_mem", None),
120
+ })
121
+
122
+ return summary_path
123
+
124
+
125
+ def profile(html=False, output_path="/tmp/simple_python_audit_perf", deep=False, trace=False,
126
+ print_terminal=False, save_csv=False):
127
+ def decorator(func):
128
+ @wraps(func)
129
+ def wrapper(*args, **kwargs):
130
+ interval = 0.0001 if deep else 0.001
131
+ profiler = Profiler(interval=interval)
132
+ call_stats = defaultdict(lambda: {
133
+ 'count': 0, 'total_time': 0.0, 'peak_time': 0.0,
134
+ 'total_mem': 0, 'peak_mem': 0,
135
+ 'stack': [], 'mem_stack': []
136
+ })
137
+
138
+ def trace_calls(frame, event, arg):
139
+ if event == 'call':
140
+ f_name = frame.f_code.co_name
141
+ call_stats[f_name]['count'] += 1
142
+ call_stats[f_name]['stack'].append(time.time())
143
+ call_stats[f_name]['mem_stack'].append(
144
+ tracemalloc.get_traced_memory()[0] if tracemalloc.is_tracing() else 0
145
+ )
146
+ elif event == 'return':
147
+ f_name = frame.f_code.co_name
148
+ if call_stats[f_name]['stack']:
149
+ elapsed = time.time() - call_stats[f_name]['stack'].pop()
150
+ call_stats[f_name]['total_time'] += elapsed
151
+ call_stats[f_name]['peak_time'] = max(call_stats[f_name]['peak_time'], elapsed)
152
+ if call_stats[f_name]['mem_stack']:
153
+ start_mem = call_stats[f_name]['mem_stack'].pop()
154
+ if tracemalloc.is_tracing():
155
+ mem_delta = max(0, tracemalloc.get_traced_memory()[0] - start_mem)
156
+ call_stats[f_name]['total_mem'] += mem_delta
157
+ call_stats[f_name]['peak_mem'] = max(call_stats[f_name]['peak_mem'], mem_delta)
158
+ return trace_calls
159
+
160
+ needs_output = html or print_terminal or save_csv
161
+ if needs_output:
162
+ _was_tracing = tracemalloc.is_tracing()
163
+ if not _was_tracing:
164
+ tracemalloc.start()
165
+ cpu_start = time.process_time()
166
+
167
+ if trace:
168
+ sys.settrace(trace_calls)
169
+
170
+ profiler.start()
171
+ start_real_time = time.time()
172
+ try:
173
+ return func(*args, **kwargs)
174
+ finally:
175
+ profiler.stop()
176
+ if trace:
177
+ sys.settrace(None)
178
+
179
+ duration = time.time() - start_real_time
180
+
181
+ if needs_output:
182
+ cpu_total = time.process_time() - cpu_start
183
+ if tracemalloc.is_tracing():
184
+ current_mem, peak_mem = tracemalloc.get_traced_memory()
185
+ else:
186
+ current_mem, peak_mem = 0, 0
187
+ if not _was_tracing:
188
+ tracemalloc.stop()
189
+
190
+ if not os.path.exists(output_path):
191
+ os.makedirs(output_path)
192
+
193
+ stats_data = []
194
+ mem_stats_data = []
195
+ if trace:
196
+ stats_data = [
197
+ {
198
+ "name": k,
199
+ "count": v['count'],
200
+ "total": round(v['total_time'], 4),
201
+ "avg": round(v['total_time'] / v['count'], 5),
202
+ "peak": round(v['peak_time'], 5),
203
+ }
204
+ for k, v in sorted(call_stats.items(), key=lambda x: x[1]['total_time'], reverse=True)
205
+ if v['count'] > 0
206
+ ][:50]
207
+ mem_stats_data = [
208
+ {
209
+ "name": k,
210
+ "count": v['count'],
211
+ "total_mem": round(v['total_mem'] / 1024, 2),
212
+ "avg_mem": round(v['total_mem'] / v['count'] / 1024, 2) if v['count'] > 0 else 0,
213
+ "peak_mem": round(v['peak_mem'] / 1024, 2),
214
+ }
215
+ for k, v in sorted(call_stats.items(), key=lambda x: x[1]['total_mem'], reverse=True)
216
+ if v['count'] > 0
217
+ ][:50]
218
+
219
+ total_calls = sum(v['count'] for v in call_stats.values()) if trace else 0
220
+
221
+ if html:
222
+ vars_snap = {"args": [str(a)[:500] for a in args], "kwargs": {k: str(v)[:500] for k, v in kwargs.items()}}
223
+ perf_data = {
224
+ "func": func.__name__,
225
+ "dur": f"{duration:.4f}s",
226
+ "cpu_total": f"{cpu_total:.4f}s",
227
+ "cpu_pct": f"{round(cpu_total / duration * 100, 1) if duration > 0 else 0}%",
228
+ "mem_current": f"{round(current_mem / 1024 / 1024, 3)} MB",
229
+ "mem_peak": f"{round(peak_mem / 1024 / 1024, 3)} MB",
230
+ "mem_avg": f"{round(peak_mem / total_calls / 1024, 2)} KB/call" if total_calls > 0 else "N/A",
231
+ "has_trace": trace,
232
+ "stats": stats_data,
233
+ "mem_stats": mem_stats_data,
234
+ }
235
+
236
+ injection_script = f"""
237
+ <script>
238
+ (function() {{
239
+ const d = {{ func: "{func.__name__}", dur: "{duration:.4f}s", vars: {json.dumps(vars_snap)}, stats: {json.dumps(stats_data)} }};
240
+ const p = {json.dumps(perf_data)};
241
+ function render() {{
242
+ if (document.getElementById('odoo-audit-host')) return;
243
+ const host = document.createElement('div');
244
+ host.id = 'odoo-audit-host';
245
+ host.style = 'position:fixed;bottom:20px;right:20px;z-index:2147483647;';
246
+ document.documentElement.appendChild(host);
247
+ const shadow = host.attachShadow({{mode:'open'}});
248
+ const root = document.createElement('div');
249
+ root.innerHTML = `
250
+ <style>
251
+ :host {{ all: initial; }}
252
+ .fab {{ width:55px;height:55px;color:white;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 15px rgba(0,0,0,0.4);font-size:22px;border:2px solid white;position:fixed;bottom:20px;transition:0.2s; }}
253
+ .fab:hover {{ transform:scale(1.1); }}
254
+ .fab1 {{ background:#714B67;right:20px; }}
255
+ .fab2 {{ background:#1a6b8a;right:85px; }}
256
+ .modal {{ display:none;position:fixed;bottom:85px;right:20px;width:500px;max-height:75vh;background:white;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,0.4);flex-direction:column;font-family:sans-serif;overflow:hidden; }}
257
+ .modal.open {{ display:flex; }}
258
+ .hdr {{ color:white;padding:12px;display:flex;justify-content:space-between;font-weight:bold; }}
259
+ .hdr1 {{ background:#714B67; }}
260
+ .hdr2 {{ background:#1a6b8a; }}
261
+ .body {{ padding:15px;overflow-y:auto;background:white;color:#333; }}
262
+ input[type=text] {{ width:95%;padding:8px;margin:10px 0;border:1px solid #ddd;border-radius:4px; }}
263
+ table {{ width:100%;border-collapse:collapse;font-size:12px; }}
264
+ th {{ background:#f4f4f4;padding:8px;text-align:left;position:sticky;top:0; }}
265
+ td {{ padding:8px;border-bottom:1px solid #eee; }}
266
+ pre {{ background:#1e1e1e;color:#9cdcfe;padding:10px;font-size:11px;border-radius:4px;max-height:150px;overflow:auto; }}
267
+ .grid {{ display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px; }}
268
+ .card {{ background:#f8f9fa;border-radius:8px;padding:10px;border-left:4px solid #1a6b8a; }}
269
+ .card.warn {{ border-left-color:#e53e3e; }}
270
+ .card .lbl {{ font-size:10px;color:#666;text-transform:uppercase;letter-spacing:.5px; }}
271
+ .card .val {{ font-size:17px;font-weight:bold;color:#1a6b8a;margin-top:3px; }}
272
+ .card.warn .val {{ color:#e53e3e; }}
273
+ .sec {{ font-size:12px;font-weight:bold;color:#444;margin:10px 0 5px;border-bottom:1px solid #eee;padding-bottom:3px; }}
274
+ .no-trace {{ background:#fff3cd;padding:10px;border-radius:6px;font-size:12px;color:#856404;text-align:center;margin:8px 0; }}
275
+ </style>
276
+ <div class="modal" id="m1">
277
+ <div class="hdr hdr1"><span>🚀 ${{d.func}}</span><span>${{d.dur}}</span></div>
278
+ <div class="body">
279
+ <details><summary style="cursor:pointer;color:#714B67">📦 Vars</summary><pre>${{JSON.stringify(d.vars,null,2)}}</pre></details>
280
+ <input type="text" id="s1" placeholder="Buscar função...">
281
+ <table><thead><tr><th>Função</th><th>Calls</th><th>Total</th><th>Média</th><th>Pico</th></tr></thead>
282
+ <tbody id="tb1">${{d.stats.map(s=>`<tr class="r1"><td class="n1" style="font-family:monospace">${{s.name}}</td><td align="center">${{s.count}}</td><td align="right">${{s.total}}s</td><td align="right">${{s.avg}}s</td><td align="right">${{s.peak||'-'}}s</td></tr>`).join('')}}</tbody>
283
+ </table>
284
+ </div>
285
+ </div>
286
+ <div class="modal" id="m2">
287
+ <div class="hdr hdr2"><span>📊 ${{p.func}}</span><span>${{p.dur}}</span></div>
288
+ <div class="body">
289
+ <div class="grid">
290
+ <div class="card"><div class="lbl">CPU Total</div><div class="val">${{p.cpu_total}}</div></div>
291
+ <div class="card"><div class="lbl">Uso de CPU</div><div class="val">${{p.cpu_pct}}</div></div>
292
+ <div class="card"><div class="lbl">Memória Final</div><div class="val">${{p.mem_current}}</div></div>
293
+ <div class="card warn"><div class="lbl">Pico de Memória</div><div class="val">${{p.mem_peak}}</div></div>
294
+ <div class="card" style="grid-column:1/-1"><div class="lbl">Memória Média por Chamada</div><div class="val">${{p.mem_avg}}</div></div>
295
+ </div>
296
+ ${{p.has_trace ?
297
+ `<div class="sec">⏱ CPU por Função</div>
298
+ <input type="text" id="s2" placeholder="Buscar função...">
299
+ <table><thead><tr><th>Função</th><th>Calls</th><th>Total</th><th>Média</th><th>Pico</th></tr></thead>
300
+ <tbody id="tb2">${{p.stats.map(s=>`<tr class="r2"><td class="n2" style="font-family:monospace">${{s.name}}</td><td align="center">${{s.count}}</td><td align="right">${{s.total}}s</td><td align="right">${{s.avg}}s</td><td align="right">${{s.peak}}s</td></tr>`).join('')}}</tbody>
301
+ </table>
302
+ <div class="sec">💾 Memória por Função</div>
303
+ <table><thead><tr><th>Função</th><th>Calls</th><th>Total (KB)</th><th>Média (KB)</th><th>Pico (KB)</th></tr></thead>
304
+ <tbody>${{p.mem_stats.map(s=>`<tr><td style="font-family:monospace">${{s.name}}</td><td align="center">${{s.count}}</td><td align="right">${{s.total_mem}}</td><td align="right">${{s.avg_mem}}</td><td align="right">${{s.peak_mem}}</td></tr>`).join('')}}</tbody>
305
+ </table>`
306
+ : `<div class="no-trace">⚠️ Habilite <b>trace=True</b> para métricas por função</div>`
307
+ }}
308
+ </div>
309
+ </div>
310
+ <div class="fab fab1" id="b1">🚀</div>
311
+ <div class="fab fab2" id="b2">📊</div>
312
+ `;
313
+ const m1 = root.querySelector('#m1');
314
+ const m2 = root.querySelector('#m2');
315
+ root.querySelector('#b1').onclick = () => {{ m1.classList.toggle('open'); m2.classList.remove('open'); }};
316
+ root.querySelector('#b2').onclick = () => {{ m2.classList.toggle('open'); m1.classList.remove('open'); }};
317
+ root.querySelector('#s1').oninput = (e) => {{
318
+ const v = e.target.value.toLowerCase();
319
+ root.querySelectorAll('.r1').forEach(r => r.style.display = r.querySelector('.n1').textContent.toLowerCase().includes(v) ? '' : 'none');
320
+ }};
321
+ const s2 = root.querySelector('#s2');
322
+ if (s2) s2.oninput = (e) => {{
323
+ const v = e.target.value.toLowerCase();
324
+ root.querySelectorAll('.r2').forEach(r => r.style.display = r.querySelector('.n2').textContent.toLowerCase().includes(v) ? '' : 'none');
325
+ }};
326
+ shadow.appendChild(root);
327
+ }}
328
+ setInterval(render, 1000); render();
329
+ }})();
330
+ </script>
331
+ """
332
+ pyinstrument_html = profiler.output_html()
333
+ final_html = pyinstrument_html.replace('</html>', f'{injection_script}</html>')
334
+ filepath = os.path.join(output_path, f"AUDIT_{func.__name__}_{int(time.time())}.html")
335
+ with open(filepath, "w", encoding="utf-8") as f:
336
+ f.write(final_html)
337
+
338
+ if print_terminal:
339
+ _print_terminal(
340
+ func.__name__, duration, cpu_total, current_mem, peak_mem,
341
+ total_calls, stats_data, mem_stats_data, trace,
342
+ )
343
+
344
+ if save_csv:
345
+ _save_csv(
346
+ output_path, func.__name__, int(time.time()),
347
+ duration, cpu_total, current_mem, peak_mem,
348
+ total_calls, stats_data, mem_stats_data, trace,
349
+ )
350
+
351
+ return wrapper
352
+ return decorator
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple-python-audit
3
+ Version: 1.3.0
4
+ Summary: Ferramenta de profiling com widget flutuante, exportação CSV e servidor de gerenciamento de logs.
5
+ Author-email: Sadson Diego <sadsondiego@gmail.com>
6
+ License: MIT
7
+ Keywords: odoo,profiler,performance,pyinstrument,audit
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Framework :: Odoo
14
+ Classifier: Topic :: Software Development :: Debuggers
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: pyinstrument>=4.6.0
18
+ Requires-Dist: typing-extensions>=4.0.0
19
+
20
+ # Simple Python Audit Tool 🚀
21
+
22
+ Ferramenta completa para medição de performance e auditoria de variáveis para Python.
23
+
24
+ ## 📦 Instalação
25
+
26
+ ```bash
27
+ pip install .
28
+ # Ou via GitHub
29
+ pip install git+https://github.com/sadson/simple_python_audit.git
30
+ ```
31
+
32
+ ## 🎯 Como Usar
33
+
34
+ ```python
35
+ from simple_python_audit import profile
36
+
37
+ @profile(
38
+ html=True,
39
+ trace=True,
40
+ deep=True,
41
+ print_terminal=True,
42
+ save_csv=True,
43
+ output_path="/tmp/simple_python_audit_perf",
44
+ )
45
+ def meu_metodo_lento(self):
46
+ # Seu código aqui
47
+ ```
48
+
49
+ ### Parâmetros do decorador
50
+
51
+ | Parâmetro | Padrão | Descrição |
52
+ |------------------|-------------------------------------|---------------------------------------------------------------------------|
53
+ | `html` | `False` | Gera relatório interativo em HTML com widget flutuante |
54
+ | `trace` | `False` | Rastreia chamadas de funções e coleta métricas por função |
55
+ | `deep` | `False` | Aumenta granularidade do profiler (intervalo de 0,0001 s em vez de 0,001 s) |
56
+ | `print_terminal` | `False` | Imprime tabela de métricas formatada no terminal ao final da execução |
57
+ | `save_csv` | `False` | Salva os dados em arquivos CSV prontos para análise com pandas/IA |
58
+ | `output_path` | `"/tmp/simple_python_audit_perf"` | Diretório onde os relatórios (HTML e CSV) serão gravados |
59
+
60
+ ---
61
+
62
+ ## 📊 Saída no Terminal (`print_terminal=True`)
63
+
64
+ Exibe uma tabela formatada diretamente no stdout ao término da função:
65
+
66
+ ```
67
+ ==============================================================
68
+ AUDIT: meu_metodo_lento | 1.2345s
69
+ ==============================================================
70
+ CPU Total: 0.4567s
71
+ CPU Usage: 37.0%
72
+ Memory Current: 12.345 MB
73
+ Memory Peak: 15.678 MB
74
+ Memory Avg/Call: 2.34 KB/call
75
+ Total Calls Traced: 1234
76
+ --------------------------------------------------------------
77
+ Top Functions by CPU Time:
78
+ Function Calls Total Avg Peak
79
+ meu_metodo_lento 1 1.2300s 1.23000s 1.23000s
80
+ _query 42 0.4500s 0.01071s 0.02100s
81
+ ...
82
+ --------------------------------------------------------------
83
+ Top Functions by Memory:
84
+ Function Calls Total KB Avg KB Peak KB
85
+ _query 42 512.30 12.20 48.50
86
+ ...
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 💾 Exportação CSV (`save_csv=True`)
92
+
93
+ Gera dois arquivos CSV em `output_path`, ideais para carregar em um `DataFrame` do pandas, alimentar uma IA ou construir dashboards.
94
+
95
+ ### `AUDIT_{func}_{timestamp}_summary.csv`
96
+
97
+ Uma linha por execução com as métricas globais da chamada. Acumulando múltiplas execuções é possível rastrear regressões de performance ao longo do tempo.
98
+
99
+ | Coluna | Tipo | Descrição |
100
+ |-------------------------|---------|------------------------------------------------|
101
+ | `timestamp` | int | Unix timestamp da execução |
102
+ | `func_name` | str | Nome da função decorada |
103
+ | `duration_s` | float | Tempo total de execução (segundos) |
104
+ | `cpu_total_s` | float | Tempo de CPU consumido (segundos) |
105
+ | `cpu_pct` | float | Percentual de uso de CPU |
106
+ | `mem_current_mb` | float | Memória alocada ao final (MB) |
107
+ | `mem_peak_mb` | float | Pico de memória durante a execução (MB) |
108
+ | `mem_avg_kb_per_call` | float | Média de memória por chamada de função (KB) |
109
+ | `total_calls_traced` | int | Total de chamadas de função rastreadas |
110
+
111
+ ### `AUDIT_{func}_{timestamp}_trace.csv`
112
+
113
+ Uma linha por função rastreada (requer `trace=True`). Permite identificar os gargalos com precisão.
114
+
115
+ | Coluna | Tipo | Descrição |
116
+ |--------------------|-------|-----------------------------------------------|
117
+ | `timestamp` | int | Unix timestamp — chave para junção com summary |
118
+ | `func_name` | str | Nome da função decorada |
119
+ | `called_function` | str | Nome da função rastreada |
120
+ | `call_count` | int | Número de chamadas |
121
+ | `total_time_s` | float | Tempo total acumulado (segundos) |
122
+ | `avg_time_s` | float | Tempo médio por chamada (segundos) |
123
+ | `peak_time_s` | float | Pico de tempo em uma única chamada (segundos) |
124
+ | `total_mem_kb` | float | Memória total alocada (KB) |
125
+ | `avg_mem_kb` | float | Memória média por chamada (KB) |
126
+ | `peak_mem_kb` | float | Pico de memória em uma única chamada (KB) |
127
+
128
+ ### Exemplo de análise com pandas
129
+
130
+ ```python
131
+ import pandas as pd
132
+ import glob
133
+
134
+ # Carregar todos os resumos de execuções
135
+ summary = pd.concat([pd.read_csv(f) for f in glob.glob("/tmp/simple_python_audit_perf/*_summary.csv")])
136
+ summary["timestamp"] = pd.to_datetime(summary["timestamp"], unit="s")
137
+ summary.sort_values("timestamp").plot(x="timestamp", y="duration_s", title="Evolução do tempo de execução")
138
+
139
+ # Carregar trace de uma execução específica
140
+ trace = pd.read_csv("/tmp/simple_python_audit_perf/AUDIT_meu_metodo_lento_1234567890_trace.csv")
141
+ trace.sort_values("total_time_s", ascending=False).head(10)
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 🌐 Relatório HTML (`html=True`)
147
+
148
+ Gera um arquivo `AUDIT_{func}_{timestamp}.html` com um widget flutuante interativo contendo:
149
+
150
+ - **Painel de rastreamento (🚀):** tabela de chamadas de função com busca em tempo real
151
+ - **Painel de métricas (📊):** cards de CPU e memória, tabelas de CPU e memória por função
152
+
153
+ ---
154
+
155
+ ## 🛠️ Servidor de Auditoria
156
+
157
+ Interface web para visualizar e gerenciar os arquivos de relatório gerados.
158
+
159
+ ```bash
160
+ # Via linha de comando
161
+ simple-python-audit-server --port 8080 --dir /tmp/simple_python_audit_perf
162
+
163
+ # Via Python
164
+ from simple_python_audit import start_server
165
+ start_server(port=8080, directory="/tmp/simple_python_audit_perf")
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 🔗 Integração com Odoo
171
+
172
+ Ao instalar em um ambiente com Odoo, o decorador `profile` é injetado automaticamente em `odoo.api`:
173
+
174
+ ```python
175
+ from odoo import api
176
+
177
+ @api.profile(html=True, trace=True, print_terminal=True, save_csv=True)
178
+ def action_processar(self):
179
+ ...
180
+ ```
@@ -1,56 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: simple-python-audit
3
- Version: 1.1.0
4
- Summary: Ferramenta de profiling com widget flutuante e servidor de gerenciamento de logs.
5
- Author-email: Sadson Diego <sadsondiego@gmail.com>
6
- License: MIT
7
- Keywords: odoo,profiler,performance,pyinstrument,audit
8
- Classifier: Development Status :: 5 - Production/Stable
9
- Classifier: Programming Language :: Python :: 3.8
10
- Classifier: Programming Language :: Python :: 3.9
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Framework :: Odoo
14
- Classifier: Topic :: Software Development :: Debuggers
15
- Requires-Python: >=3.8
16
- Description-Content-Type: text/markdown
17
- Requires-Dist: pyinstrument>=4.6.0
18
- Requires-Dist: typing-extensions>=4.0.0
19
-
20
- # Simple Python Audit Tool 🚀
21
-
22
- Ferramenta completa para medição de performance e auditoria de variáveis para python.
23
-
24
- ## 📦 Instalação
25
-
26
- ```bash
27
- pip install .
28
- # Ou via GitHub
29
- pip install git+[https://github.com/sadson/simple_python_audit.git](https://github.com/sadson/simple_python_audit.git)
30
-
31
- ## 🎯 Como Usar
32
-
33
- ```python
34
- from simple_python_audit import profile
35
-
36
- @profile(html=True, output_path="/tmp/simple_python_audit_perf" ,trace=True, deep=True)
37
- def meu_metodo_lento(self):
38
- # Seu código aqui
39
- ```
40
-
41
- **Parâmetros:**
42
- - `html=True`: Gera relatório em HTML
43
- - `trace=True`: Rastreia chamadas de funções
44
- - `deep=True`: Rastreia chamadas com tempo de execução a partir de 0.0001 ms
45
- - `output_path`: Caminho para salvar os relatórios gerados
46
-
47
- ## 🛠️ Executar Servidor de Auditoria
48
-
49
- ```bash
50
- simple-python-audit-server --port 8080 --dir /tmp/odoo_perf
51
- ou
52
- via python
53
-
54
- from simple_python_audit import start_server
55
- start_server(port=8080, directory="/tmp/odoo_perf")
56
- ```
@@ -1,37 +0,0 @@
1
- # Simple Python Audit Tool 🚀
2
-
3
- Ferramenta completa para medição de performance e auditoria de variáveis para python.
4
-
5
- ## 📦 Instalação
6
-
7
- ```bash
8
- pip install .
9
- # Ou via GitHub
10
- pip install git+[https://github.com/sadson/simple_python_audit.git](https://github.com/sadson/simple_python_audit.git)
11
-
12
- ## 🎯 Como Usar
13
-
14
- ```python
15
- from simple_python_audit import profile
16
-
17
- @profile(html=True, output_path="/tmp/simple_python_audit_perf" ,trace=True, deep=True)
18
- def meu_metodo_lento(self):
19
- # Seu código aqui
20
- ```
21
-
22
- **Parâmetros:**
23
- - `html=True`: Gera relatório em HTML
24
- - `trace=True`: Rastreia chamadas de funções
25
- - `deep=True`: Rastreia chamadas com tempo de execução a partir de 0.0001 ms
26
- - `output_path`: Caminho para salvar os relatórios gerados
27
-
28
- ## 🛠️ Executar Servidor de Auditoria
29
-
30
- ```bash
31
- simple-python-audit-server --port 8080 --dir /tmp/odoo_perf
32
- ou
33
- via python
34
-
35
- from simple_python_audit import start_server
36
- start_server(port=8080, directory="/tmp/odoo_perf")
37
- ```
@@ -1,116 +0,0 @@
1
- import os
2
- import time
3
- import json
4
- import sys
5
- import logging
6
- from functools import wraps
7
- from pyinstrument import Profiler
8
- from collections import defaultdict
9
-
10
- _logger = logging.getLogger(__name__)
11
-
12
- def html_escape(text):
13
- return str(text).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
14
-
15
- def profile(html=False, output_path="/tmp/simple_python_audit_perf", deep=False, trace=False):
16
- def decorator(func):
17
- @wraps(func)
18
- def wrapper(*args, **kwargs):
19
- interval = 0.0001 if deep else 0.001
20
- profiler = Profiler(interval=interval)
21
- call_stats = defaultdict(lambda: {'count': 0, 'total_time': 0.0, 'stack': []})
22
-
23
- def trace_calls(frame, event, arg):
24
- if event == 'call':
25
- f_name = frame.f_code.co_name
26
- call_stats[f_name]['count'] += 1
27
- call_stats[f_name]['stack'].append(time.time())
28
- elif event == 'return':
29
- f_name = frame.f_code.co_name
30
- if f_name in call_stats and call_stats[f_name]['stack']:
31
- start_t = call_stats[f_name]['stack'].pop()
32
- call_stats[f_name]['total_time'] += (time.time() - start_t)
33
- return trace_calls
34
-
35
- if trace:
36
- sys.settrace(trace_calls)
37
-
38
- profiler.start()
39
- start_real_time = time.time()
40
- try:
41
- return func(*args, **kwargs)
42
- finally:
43
- profiler.stop()
44
- if trace:
45
- sys.settrace(None)
46
-
47
- duration = time.time() - start_real_time
48
- if html:
49
- if not os.path.exists(output_path):
50
- os.makedirs(output_path)
51
-
52
- pyinstrument_html = profiler.output_html()
53
- vars_snap = {"args": [str(a)[:500] for a in args], "kwargs": {k: str(v)[:500] for k, v in kwargs.items()}}
54
-
55
- stats_data = []
56
- if trace:
57
- stats_data = [
58
- {"name": k, "count": v['count'], "total": round(v['total_time'], 4), "avg": round(v['total_time']/v['count'], 5)}
59
- for k, v in sorted(call_stats.items(), key=lambda x: x[1]['total_time'], reverse=True) if v['count'] > 0
60
- ][:50]
61
-
62
- injection_script = f"""
63
- <script>
64
- (function() {{
65
- const d = {{ func: "{func.__name__}", dur: "{duration:.4f}s", vars: {json.dumps(vars_snap)}, stats: {json.dumps(stats_data)} }};
66
- function render() {{
67
- if (document.getElementById('odoo-audit-host')) return;
68
- const host = document.createElement('div');
69
- host.id = 'odoo-audit-host';
70
- host.style = 'position:fixed; bottom:20px; right:20px; z-index:2147483647;';
71
- document.documentElement.appendChild(host);
72
- const shadow = host.attachShadow({{mode:'open'}});
73
- const root = document.createElement('div');
74
- root.innerHTML = `
75
- <style>
76
- :host {{ all: initial; }}
77
- .fab {{ width:55px; height:55px; background:#714B67; color:white; border-radius:50%; display:flex; align-items:center; justify-content:center; cursor:pointer; box-shadow:0 4px 15px rgba(0,0,0,0.4); font-size:22px; border:2px solid white; position:fixed; bottom:20px; right:20px; transition:0.2s; }}
78
- .fab:hover {{ transform:scale(1.1); }}
79
- .modal {{ display:none; position:fixed; bottom:85px; right:20px; width:500px; max-height:75vh; background:white; border-radius:12px; border:1px solid #714B67; box-shadow:0 10px 40px rgba(0,0,0,0.4); flex-direction:column; font-family:sans-serif; overflow:hidden; }}
80
- .modal.open {{ display:flex; }}
81
- .header {{ background:#714B67; color:white; padding:12px; display:flex; justify-content:space-between; font-weight:bold; }}
82
- .body {{ padding:15px; overflow-y:auto; background:white; color:#333; }}
83
- input {{ width:95%; padding:8px; margin:10px 0; border:1px solid #ddd; border-radius:4px; }}
84
- table {{ width:100%; border-collapse:collapse; font-size:12px; }}
85
- th {{ background:#f4f4f4; padding:8px; text-align:left; sticky; top:0; }}
86
- td {{ padding:8px; border-bottom:1px solid #eee; }}
87
- pre {{ background:#1e1e1e; color:#9cdcfe; padding:10px; font-size:11px; border-radius:4px; max-height:150px; overflow:auto; }}
88
- </style>
89
- <div class="modal" id="m">
90
- <div class="header"><span>🚀 ${{d.func}}</span><span>${{d.dur}}</span></div>
91
- <div class="body">
92
- <details><summary style="cursor:pointer;color:#714B67">📦 Vars</summary><pre>${{JSON.stringify(d.vars,null,2)}}</pre></details>
93
- <input type="text" id="s" placeholder="Buscar função...">
94
- <table><thead><tr><th>Função</th><th>Calls</th><th>Total</th><th>Média</th></tr></thead>
95
- <tbody id="t">${{d.stats.map(s=>`<tr class="r"><td style="font-family:monospace" class="n">${{s.name}}</td><td align="center">${{s.count}}</td><td align="right">${{s.total}}s</td><td align="right">${{s.avg}}s</td></tr>`).join('')}}</tbody>
96
- </table>
97
- </div>
98
- </div>
99
- <div class="fab" id="b">🚀</div>`;
100
- const m = root.querySelector('#m');
101
- root.querySelector('#b').onclick = () => m.classList.toggle('open');
102
- root.querySelector('#s').oninput = (e) => {{
103
- const v = e.target.value.toLowerCase();
104
- root.querySelectorAll('.r').forEach(r => r.style.display = r.querySelector('.n').textContent.toLowerCase().includes(v) ? '' : 'none');
105
- }};
106
- shadow.appendChild(root);
107
- }}
108
- setInterval(render, 1000); render();
109
- }})();
110
- </script>
111
- """
112
- final_html = pyinstrument_html.replace('</html>', f'{injection_script}</html>')
113
- filepath = os.path.join(output_path, f"AUDIT_{func.__name__}_{int(time.time())}.html")
114
- with open(filepath, "w", encoding="utf-8") as f: f.write(final_html)
115
- return wrapper
116
- return decorator
@@ -1,56 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: simple-python-audit
3
- Version: 1.1.0
4
- Summary: Ferramenta de profiling com widget flutuante e servidor de gerenciamento de logs.
5
- Author-email: Sadson Diego <sadsondiego@gmail.com>
6
- License: MIT
7
- Keywords: odoo,profiler,performance,pyinstrument,audit
8
- Classifier: Development Status :: 5 - Production/Stable
9
- Classifier: Programming Language :: Python :: 3.8
10
- Classifier: Programming Language :: Python :: 3.9
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Framework :: Odoo
14
- Classifier: Topic :: Software Development :: Debuggers
15
- Requires-Python: >=3.8
16
- Description-Content-Type: text/markdown
17
- Requires-Dist: pyinstrument>=4.6.0
18
- Requires-Dist: typing-extensions>=4.0.0
19
-
20
- # Simple Python Audit Tool 🚀
21
-
22
- Ferramenta completa para medição de performance e auditoria de variáveis para python.
23
-
24
- ## 📦 Instalação
25
-
26
- ```bash
27
- pip install .
28
- # Ou via GitHub
29
- pip install git+[https://github.com/sadson/simple_python_audit.git](https://github.com/sadson/simple_python_audit.git)
30
-
31
- ## 🎯 Como Usar
32
-
33
- ```python
34
- from simple_python_audit import profile
35
-
36
- @profile(html=True, output_path="/tmp/simple_python_audit_perf" ,trace=True, deep=True)
37
- def meu_metodo_lento(self):
38
- # Seu código aqui
39
- ```
40
-
41
- **Parâmetros:**
42
- - `html=True`: Gera relatório em HTML
43
- - `trace=True`: Rastreia chamadas de funções
44
- - `deep=True`: Rastreia chamadas com tempo de execução a partir de 0.0001 ms
45
- - `output_path`: Caminho para salvar os relatórios gerados
46
-
47
- ## 🛠️ Executar Servidor de Auditoria
48
-
49
- ```bash
50
- simple-python-audit-server --port 8080 --dir /tmp/odoo_perf
51
- ou
52
- via python
53
-
54
- from simple_python_audit import start_server
55
- start_server(port=8080, directory="/tmp/odoo_perf")
56
- ```