simple-python-audit 1.1.0__tar.gz → 1.2.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.
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/PKG-INFO +1 -1
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/pyproject.toml +1 -1
- simple_python_audit-1.2.0/src/simple_python_audit/engine.py +226 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/PKG-INFO +1 -1
- simple_python_audit-1.1.0/src/simple_python_audit/engine.py +0 -116
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/README.md +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/setup.cfg +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit/__init__.py +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit/server.py +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/SOURCES.txt +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/dependency_links.txt +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/entry_points.txt +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/requires.txt +0 -0
- {simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simple-python-audit"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
description = "Ferramenta de profiling com widget flutuante e servidor de gerenciamento de logs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
import tracemalloc
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from pyinstrument import Profiler
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
_logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def html_escape(text):
|
|
14
|
+
return str(text).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
15
|
+
|
|
16
|
+
def profile(html=False, output_path="/tmp/simple_python_audit_perf", deep=False, trace=False):
|
|
17
|
+
def decorator(func):
|
|
18
|
+
@wraps(func)
|
|
19
|
+
def wrapper(*args, **kwargs):
|
|
20
|
+
interval = 0.0001 if deep else 0.001
|
|
21
|
+
profiler = Profiler(interval=interval)
|
|
22
|
+
call_stats = defaultdict(lambda: {
|
|
23
|
+
'count': 0, 'total_time': 0.0, 'peak_time': 0.0,
|
|
24
|
+
'total_mem': 0, 'peak_mem': 0,
|
|
25
|
+
'stack': [], 'mem_stack': []
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
def trace_calls(frame, event, arg):
|
|
29
|
+
if event == 'call':
|
|
30
|
+
f_name = frame.f_code.co_name
|
|
31
|
+
call_stats[f_name]['count'] += 1
|
|
32
|
+
call_stats[f_name]['stack'].append(time.time())
|
|
33
|
+
call_stats[f_name]['mem_stack'].append(
|
|
34
|
+
tracemalloc.get_traced_memory()[0] if tracemalloc.is_tracing() else 0
|
|
35
|
+
)
|
|
36
|
+
elif event == 'return':
|
|
37
|
+
f_name = frame.f_code.co_name
|
|
38
|
+
if call_stats[f_name]['stack']:
|
|
39
|
+
elapsed = time.time() - call_stats[f_name]['stack'].pop()
|
|
40
|
+
call_stats[f_name]['total_time'] += elapsed
|
|
41
|
+
call_stats[f_name]['peak_time'] = max(call_stats[f_name]['peak_time'], elapsed)
|
|
42
|
+
if call_stats[f_name]['mem_stack']:
|
|
43
|
+
start_mem = call_stats[f_name]['mem_stack'].pop()
|
|
44
|
+
if tracemalloc.is_tracing():
|
|
45
|
+
mem_delta = max(0, tracemalloc.get_traced_memory()[0] - start_mem)
|
|
46
|
+
call_stats[f_name]['total_mem'] += mem_delta
|
|
47
|
+
call_stats[f_name]['peak_mem'] = max(call_stats[f_name]['peak_mem'], mem_delta)
|
|
48
|
+
return trace_calls
|
|
49
|
+
|
|
50
|
+
if html:
|
|
51
|
+
_was_tracing = tracemalloc.is_tracing()
|
|
52
|
+
if not _was_tracing:
|
|
53
|
+
tracemalloc.start()
|
|
54
|
+
cpu_start = time.process_time()
|
|
55
|
+
|
|
56
|
+
if trace:
|
|
57
|
+
sys.settrace(trace_calls)
|
|
58
|
+
|
|
59
|
+
profiler.start()
|
|
60
|
+
start_real_time = time.time()
|
|
61
|
+
try:
|
|
62
|
+
return func(*args, **kwargs)
|
|
63
|
+
finally:
|
|
64
|
+
profiler.stop()
|
|
65
|
+
if trace:
|
|
66
|
+
sys.settrace(None)
|
|
67
|
+
|
|
68
|
+
duration = time.time() - start_real_time
|
|
69
|
+
|
|
70
|
+
if html:
|
|
71
|
+
cpu_total = time.process_time() - cpu_start
|
|
72
|
+
if tracemalloc.is_tracing():
|
|
73
|
+
current_mem, peak_mem = tracemalloc.get_traced_memory()
|
|
74
|
+
else:
|
|
75
|
+
current_mem, peak_mem = 0, 0
|
|
76
|
+
if not _was_tracing:
|
|
77
|
+
tracemalloc.stop()
|
|
78
|
+
|
|
79
|
+
if not os.path.exists(output_path):
|
|
80
|
+
os.makedirs(output_path)
|
|
81
|
+
|
|
82
|
+
pyinstrument_html = profiler.output_html()
|
|
83
|
+
vars_snap = {"args": [str(a)[:500] for a in args], "kwargs": {k: str(v)[:500] for k, v in kwargs.items()}}
|
|
84
|
+
|
|
85
|
+
stats_data = []
|
|
86
|
+
mem_stats_data = []
|
|
87
|
+
if trace:
|
|
88
|
+
stats_data = [
|
|
89
|
+
{
|
|
90
|
+
"name": k,
|
|
91
|
+
"count": v['count'],
|
|
92
|
+
"total": round(v['total_time'], 4),
|
|
93
|
+
"avg": round(v['total_time'] / v['count'], 5),
|
|
94
|
+
"peak": round(v['peak_time'], 5),
|
|
95
|
+
}
|
|
96
|
+
for k, v in sorted(call_stats.items(), key=lambda x: x[1]['total_time'], reverse=True)
|
|
97
|
+
if v['count'] > 0
|
|
98
|
+
][:50]
|
|
99
|
+
mem_stats_data = [
|
|
100
|
+
{
|
|
101
|
+
"name": k,
|
|
102
|
+
"count": v['count'],
|
|
103
|
+
"total_mem": round(v['total_mem'] / 1024, 2),
|
|
104
|
+
"avg_mem": round(v['total_mem'] / v['count'] / 1024, 2) if v['count'] > 0 else 0,
|
|
105
|
+
"peak_mem": round(v['peak_mem'] / 1024, 2),
|
|
106
|
+
}
|
|
107
|
+
for k, v in sorted(call_stats.items(), key=lambda x: x[1]['total_mem'], reverse=True)
|
|
108
|
+
if v['count'] > 0
|
|
109
|
+
][:50]
|
|
110
|
+
|
|
111
|
+
total_calls = sum(v['count'] for v in call_stats.values()) if trace else 0
|
|
112
|
+
|
|
113
|
+
perf_data = {
|
|
114
|
+
"func": func.__name__,
|
|
115
|
+
"dur": f"{duration:.4f}s",
|
|
116
|
+
"cpu_total": f"{cpu_total:.4f}s",
|
|
117
|
+
"cpu_pct": f"{round(cpu_total / duration * 100, 1) if duration > 0 else 0}%",
|
|
118
|
+
"mem_current": f"{round(current_mem / 1024 / 1024, 3)} MB",
|
|
119
|
+
"mem_peak": f"{round(peak_mem / 1024 / 1024, 3)} MB",
|
|
120
|
+
"mem_avg": f"{round(peak_mem / total_calls / 1024, 2)} KB/call" if total_calls > 0 else "N/A",
|
|
121
|
+
"has_trace": trace,
|
|
122
|
+
"stats": stats_data,
|
|
123
|
+
"mem_stats": mem_stats_data,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
injection_script = f"""
|
|
127
|
+
<script>
|
|
128
|
+
(function() {{
|
|
129
|
+
const d = {{ func: "{func.__name__}", dur: "{duration:.4f}s", vars: {json.dumps(vars_snap)}, stats: {json.dumps(stats_data)} }};
|
|
130
|
+
const p = {json.dumps(perf_data)};
|
|
131
|
+
function render() {{
|
|
132
|
+
if (document.getElementById('odoo-audit-host')) return;
|
|
133
|
+
const host = document.createElement('div');
|
|
134
|
+
host.id = 'odoo-audit-host';
|
|
135
|
+
host.style = 'position:fixed;bottom:20px;right:20px;z-index:2147483647;';
|
|
136
|
+
document.documentElement.appendChild(host);
|
|
137
|
+
const shadow = host.attachShadow({{mode:'open'}});
|
|
138
|
+
const root = document.createElement('div');
|
|
139
|
+
root.innerHTML = `
|
|
140
|
+
<style>
|
|
141
|
+
:host {{ all: initial; }}
|
|
142
|
+
.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; }}
|
|
143
|
+
.fab:hover {{ transform:scale(1.1); }}
|
|
144
|
+
.fab1 {{ background:#714B67;right:20px; }}
|
|
145
|
+
.fab2 {{ background:#1a6b8a;right:85px; }}
|
|
146
|
+
.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; }}
|
|
147
|
+
.modal.open {{ display:flex; }}
|
|
148
|
+
.hdr {{ color:white;padding:12px;display:flex;justify-content:space-between;font-weight:bold; }}
|
|
149
|
+
.hdr1 {{ background:#714B67; }}
|
|
150
|
+
.hdr2 {{ background:#1a6b8a; }}
|
|
151
|
+
.body {{ padding:15px;overflow-y:auto;background:white;color:#333; }}
|
|
152
|
+
input[type=text] {{ width:95%;padding:8px;margin:10px 0;border:1px solid #ddd;border-radius:4px; }}
|
|
153
|
+
table {{ width:100%;border-collapse:collapse;font-size:12px; }}
|
|
154
|
+
th {{ background:#f4f4f4;padding:8px;text-align:left;position:sticky;top:0; }}
|
|
155
|
+
td {{ padding:8px;border-bottom:1px solid #eee; }}
|
|
156
|
+
pre {{ background:#1e1e1e;color:#9cdcfe;padding:10px;font-size:11px;border-radius:4px;max-height:150px;overflow:auto; }}
|
|
157
|
+
.grid {{ display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px; }}
|
|
158
|
+
.card {{ background:#f8f9fa;border-radius:8px;padding:10px;border-left:4px solid #1a6b8a; }}
|
|
159
|
+
.card.warn {{ border-left-color:#e53e3e; }}
|
|
160
|
+
.card .lbl {{ font-size:10px;color:#666;text-transform:uppercase;letter-spacing:.5px; }}
|
|
161
|
+
.card .val {{ font-size:17px;font-weight:bold;color:#1a6b8a;margin-top:3px; }}
|
|
162
|
+
.card.warn .val {{ color:#e53e3e; }}
|
|
163
|
+
.sec {{ font-size:12px;font-weight:bold;color:#444;margin:10px 0 5px;border-bottom:1px solid #eee;padding-bottom:3px; }}
|
|
164
|
+
.no-trace {{ background:#fff3cd;padding:10px;border-radius:6px;font-size:12px;color:#856404;text-align:center;margin:8px 0; }}
|
|
165
|
+
</style>
|
|
166
|
+
<div class="modal" id="m1">
|
|
167
|
+
<div class="hdr hdr1"><span>🚀 ${{d.func}}</span><span>${{d.dur}}</span></div>
|
|
168
|
+
<div class="body">
|
|
169
|
+
<details><summary style="cursor:pointer;color:#714B67">📦 Vars</summary><pre>${{JSON.stringify(d.vars,null,2)}}</pre></details>
|
|
170
|
+
<input type="text" id="s1" placeholder="Buscar função...">
|
|
171
|
+
<table><thead><tr><th>Função</th><th>Calls</th><th>Total</th><th>Média</th><th>Pico</th></tr></thead>
|
|
172
|
+
<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>
|
|
173
|
+
</table>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="modal" id="m2">
|
|
177
|
+
<div class="hdr hdr2"><span>📊 ${{p.func}}</span><span>${{p.dur}}</span></div>
|
|
178
|
+
<div class="body">
|
|
179
|
+
<div class="grid">
|
|
180
|
+
<div class="card"><div class="lbl">CPU Total</div><div class="val">${{p.cpu_total}}</div></div>
|
|
181
|
+
<div class="card"><div class="lbl">Uso de CPU</div><div class="val">${{p.cpu_pct}}</div></div>
|
|
182
|
+
<div class="card"><div class="lbl">Memória Final</div><div class="val">${{p.mem_current}}</div></div>
|
|
183
|
+
<div class="card warn"><div class="lbl">Pico de Memória</div><div class="val">${{p.mem_peak}}</div></div>
|
|
184
|
+
<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>
|
|
185
|
+
</div>
|
|
186
|
+
${{p.has_trace ?
|
|
187
|
+
`<div class="sec">⏱ CPU por Função</div>
|
|
188
|
+
<input type="text" id="s2" placeholder="Buscar função...">
|
|
189
|
+
<table><thead><tr><th>Função</th><th>Calls</th><th>Total</th><th>Média</th><th>Pico</th></tr></thead>
|
|
190
|
+
<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>
|
|
191
|
+
</table>
|
|
192
|
+
<div class="sec">💾 Memória por Função</div>
|
|
193
|
+
<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>
|
|
194
|
+
<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>
|
|
195
|
+
</table>`
|
|
196
|
+
: `<div class="no-trace">⚠️ Habilite <b>trace=True</b> para métricas por função</div>`
|
|
197
|
+
}}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="fab fab1" id="b1">🚀</div>
|
|
201
|
+
<div class="fab fab2" id="b2">📊</div>
|
|
202
|
+
`;
|
|
203
|
+
const m1 = root.querySelector('#m1');
|
|
204
|
+
const m2 = root.querySelector('#m2');
|
|
205
|
+
root.querySelector('#b1').onclick = () => {{ m1.classList.toggle('open'); m2.classList.remove('open'); }};
|
|
206
|
+
root.querySelector('#b2').onclick = () => {{ m2.classList.toggle('open'); m1.classList.remove('open'); }};
|
|
207
|
+
root.querySelector('#s1').oninput = (e) => {{
|
|
208
|
+
const v = e.target.value.toLowerCase();
|
|
209
|
+
root.querySelectorAll('.r1').forEach(r => r.style.display = r.querySelector('.n1').textContent.toLowerCase().includes(v) ? '' : 'none');
|
|
210
|
+
}};
|
|
211
|
+
const s2 = root.querySelector('#s2');
|
|
212
|
+
if (s2) s2.oninput = (e) => {{
|
|
213
|
+
const v = e.target.value.toLowerCase();
|
|
214
|
+
root.querySelectorAll('.r2').forEach(r => r.style.display = r.querySelector('.n2').textContent.toLowerCase().includes(v) ? '' : 'none');
|
|
215
|
+
}};
|
|
216
|
+
shadow.appendChild(root);
|
|
217
|
+
}}
|
|
218
|
+
setInterval(render, 1000); render();
|
|
219
|
+
}})();
|
|
220
|
+
</script>
|
|
221
|
+
"""
|
|
222
|
+
final_html = pyinstrument_html.replace('</html>', f'{injection_script}</html>')
|
|
223
|
+
filepath = os.path.join(output_path, f"AUDIT_{func.__name__}_{int(time.time())}.html")
|
|
224
|
+
with open(filepath, "w", encoding="utf-8") as f: f.write(final_html)
|
|
225
|
+
return wrapper
|
|
226
|
+
return decorator
|
|
@@ -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("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_python_audit-1.1.0 → simple_python_audit-1.2.0}/src/simple_python_audit.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|