profilis 0.1.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.
Files changed (36) hide show
  1. profilis-0.1.0/LICENSE +21 -0
  2. profilis-0.1.0/PKG-INFO +406 -0
  3. profilis-0.1.0/README.md +337 -0
  4. profilis-0.1.0/pyproject.toml +93 -0
  5. profilis-0.1.0/setup.cfg +4 -0
  6. profilis-0.1.0/src/profilis/__init__.py +2 -0
  7. profilis-0.1.0/src/profilis/core/async_collector.py +153 -0
  8. profilis-0.1.0/src/profilis/core/emitter.py +43 -0
  9. profilis-0.1.0/src/profilis/core/stats.py +66 -0
  10. profilis-0.1.0/src/profilis/decorators/profile.py +121 -0
  11. profilis-0.1.0/src/profilis/exporters/console.py +45 -0
  12. profilis-0.1.0/src/profilis/exporters/jsonl.py +146 -0
  13. profilis-0.1.0/src/profilis/flask/adapter.py +193 -0
  14. profilis-0.1.0/src/profilis/flask/ui.py +378 -0
  15. profilis-0.1.0/src/profilis/py.typed +1 -0
  16. profilis-0.1.0/src/profilis/runtime/__init__.py +34 -0
  17. profilis-0.1.0/src/profilis/runtime/clock.py +21 -0
  18. profilis-0.1.0/src/profilis/runtime/context.py +77 -0
  19. profilis-0.1.0/src/profilis/runtime/ids.py +34 -0
  20. profilis-0.1.0/src/profilis/sqlalchemy/instrumentation.py +97 -0
  21. profilis-0.1.0/src/profilis.egg-info/PKG-INFO +406 -0
  22. profilis-0.1.0/src/profilis.egg-info/SOURCES.txt +34 -0
  23. profilis-0.1.0/src/profilis.egg-info/dependency_links.txt +1 -0
  24. profilis-0.1.0/src/profilis.egg-info/requires.txt +52 -0
  25. profilis-0.1.0/src/profilis.egg-info/top_level.txt +1 -0
  26. profilis-0.1.0/tests/test_async_collector.py +70 -0
  27. profilis-0.1.0/tests/test_emitter_and_stats.py +44 -0
  28. profilis-0.1.0/tests/test_exporters.py +65 -0
  29. profilis-0.1.0/tests/test_flask_adapter.py +107 -0
  30. profilis-0.1.0/tests/test_flask_ui.py +68 -0
  31. profilis-0.1.0/tests/test_import.py +6 -0
  32. profilis-0.1.0/tests/test_profile_decorator.py +78 -0
  33. profilis-0.1.0/tests/test_runtime_clock.py +9 -0
  34. profilis-0.1.0/tests/test_runtime_context.py +59 -0
  35. profilis-0.1.0/tests/test_runtime_ids.py +19 -0
  36. profilis-0.1.0/tests/test_sqlalchemy_instrumentation.py +93 -0
profilis-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ankan Dutta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,406 @@
1
+ Metadata-Version: 2.4
2
+ Name: profilis
3
+ Version: 0.1.0
4
+ Summary: High performance, non blocking profiler for Python web apps.
5
+ Author: Ankan Dutta
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ankan97dutta/profilis
8
+ Project-URL: Documentation, https://ankan97dutta.github.io/profilis/
9
+ Project-URL: Bug Tracker, https://github.com/ankan97dutta/profilis/issues
10
+ Keywords: profiler,python,flask,fastapi,sanic,sql,sqlalchemy,pyodbc,mongo,neo4j,asyncio,monitoring,performance
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Topic :: System :: Monitoring
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Operating System :: OS Independent
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typing_extensions>=4.0
27
+ Provides-Extra: flask
28
+ Requires-Dist: flask[async]>=3.0; extra == "flask"
29
+ Provides-Extra: fastapi
30
+ Requires-Dist: fastapi>=0.110; extra == "fastapi"
31
+ Requires-Dist: starlette>=0.37; extra == "fastapi"
32
+ Provides-Extra: sanic
33
+ Requires-Dist: sanic>=23.0; extra == "sanic"
34
+ Provides-Extra: sqlalchemy
35
+ Requires-Dist: sqlalchemy>=2.0; extra == "sqlalchemy"
36
+ Requires-Dist: aiosqlite; extra == "sqlalchemy"
37
+ Requires-Dist: greenlet; extra == "sqlalchemy"
38
+ Provides-Extra: pyodbc
39
+ Requires-Dist: pyodbc; extra == "pyodbc"
40
+ Provides-Extra: mongo
41
+ Requires-Dist: pymongo>=4.3; extra == "mongo"
42
+ Requires-Dist: motor>=3.3; extra == "mongo"
43
+ Provides-Extra: neo4j
44
+ Requires-Dist: neo4j>=5.14; extra == "neo4j"
45
+ Provides-Extra: perf
46
+ Requires-Dist: orjson>=3.8; extra == "perf"
47
+ Provides-Extra: all
48
+ Requires-Dist: flask[async]>=3.0; extra == "all"
49
+ Requires-Dist: fastapi>=0.110; extra == "all"
50
+ Requires-Dist: starlette>=0.37; extra == "all"
51
+ Requires-Dist: sanic>=23.0; extra == "all"
52
+ Requires-Dist: sqlalchemy>=2.0; extra == "all"
53
+ Requires-Dist: aiosqlite; extra == "all"
54
+ Requires-Dist: greenlet; extra == "all"
55
+ Requires-Dist: pyodbc; extra == "all"
56
+ Requires-Dist: pymongo>=4.3; extra == "all"
57
+ Requires-Dist: motor>=3.3; extra == "all"
58
+ Requires-Dist: neo4j>=5.14; extra == "all"
59
+ Requires-Dist: orjson>=3.8; extra == "all"
60
+ Provides-Extra: dev
61
+ Requires-Dist: pytest>=7.0; extra == "dev"
62
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
63
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
64
+ Requires-Dist: mypy>=1.0; extra == "dev"
65
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
66
+ Requires-Dist: black>=23.0; extra == "dev"
67
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
68
+ Dynamic: license-file
69
+
70
+ <img width="64" height="64" alt="image" src="https://github.com/user-attachments/assets/663b4497-d023-49a6-9ce9-60c50c86df02" />
71
+
72
+ # Profilis
73
+
74
+ > A high performance, non-blocking profiler for Python web applications.
75
+
76
+ [![Docs](https://github.com/ankan97dutta/profilis/actions/workflows/docs.yml/badge.svg)](https://ankan97dutta.github.io/profilis/)
77
+ [![CI](https://github.com/ankan97dutta/profilis/actions/workflows/ci.yml/badge.svg)](https://github.com/ankan97dutta/profilis/actions/workflows/ci.yml)
78
+ ---
79
+
80
+ ## Overview
81
+
82
+ Profilis provides drop-in observability across APIs, functions, and database queries with minimal performance impact. It's designed to be:
83
+
84
+ - **Non blocking**: Async collection with configurable batching and backpressure handling
85
+ - **Framework agnostic**: Works with Flask and custom applications (FastAPI/Sanic planned)
86
+ - **Database aware**: Built-in support for SQLAlchemy (pyodbc/MongoDB/Neo4j planned)
87
+ - **Production ready**: Configurable sampling, error tracking, and multiple export formats
88
+
89
+ <img width="1126" height="642" alt="Screenshot 2025-09-01 at 12 38 50 PM" src="https://github.com/user-attachments/assets/7c9d541b-4984-4575-92fb-8c0ec48dff55" />
90
+
91
+
92
+ ## Features
93
+
94
+ - **Request Profiling**: Automatic HTTP request/response timing and status tracking
95
+ - **Function Profiling**: Decorator-based function timing with exception tracking
96
+ - **Database Instrumentation**: SQLAlchemy query performance monitoring with row counts
97
+ - **Built-in UI**: Real-time dashboard for monitoring and debugging
98
+ - **Multiple Exporters**: JSONL (with rotation), Console
99
+ - **Runtime Context**: Distributed tracing with trace/span ID management
100
+ - **Configurable Sampling**: Control data collection volume in production
101
+
102
+
103
+ ## Installation
104
+
105
+ Install the core package with optional dependencies for your specific needs:
106
+
107
+ ### Option 1: Using pip with extras (Recommended)
108
+
109
+ ```bash
110
+ # Core package only
111
+ pip install profilis
112
+
113
+ # With Flask support
114
+ pip install profilis[flask]
115
+
116
+ # With database support
117
+ pip install profilis[flask,sqlalchemy]
118
+
119
+ # With all integrations
120
+ pip install profilis[all]
121
+ ```
122
+
123
+ ### Option 2: Using requirements files
124
+
125
+ ```bash
126
+ # Minimal setup (core only)
127
+ pip install -r requirements-minimal.txt
128
+
129
+ # Flask integration
130
+ pip install -r requirements-flask.txt
131
+
132
+ # SQLAlchemy integration
133
+ pip install -r requirements-sqlalchemy.txt
134
+
135
+ # All integrations
136
+ pip install -r requirements-all.txt
137
+ ```
138
+
139
+ ### Option 3: Manual installation
140
+
141
+ ```bash
142
+ # Core dependencies
143
+ pip install typing_extensions>=4.0
144
+
145
+ # Flask support
146
+ pip install flask[async]>=3.0
147
+
148
+ # SQLAlchemy support
149
+ pip install sqlalchemy>=2.0 aiosqlite greenlet
150
+
151
+ # Performance optimization
152
+ pip install orjson>=3.8
153
+ ```
154
+
155
+ ## Quick Start
156
+
157
+ ### Flask Integration
158
+
159
+ ```python
160
+ from flask import Flask
161
+ from profilis.flask.adapter import ProfilisFlask
162
+ from profilis.exporters.jsonl import JSONLExporter
163
+ from profilis.core.async_collector import AsyncCollector
164
+
165
+ # Setup exporter and collector
166
+ exporter = JSONLExporter(dir="./logs", rotate_bytes=1024*1024, rotate_secs=3600)
167
+ collector = AsyncCollector(exporter, queue_size=2048, batch_max=128, flush_interval=0.1)
168
+
169
+ # Create Flask app and integrate Profilis
170
+ app = Flask(__name__)
171
+ profilis = ProfilisFlask(
172
+ app,
173
+ collector=collector,
174
+ exclude_routes=["/health", "/metrics"],
175
+ sample=1.0 # 100% sampling
176
+ )
177
+
178
+ @app.route('/api/users')
179
+ def get_users():
180
+ return {"users": ["alice", "bob"]}
181
+
182
+ # Start the app
183
+ if __name__ == "__main__":
184
+ app.run(debug=True)
185
+ ```
186
+
187
+ ### Function Profiling
188
+
189
+ ```python
190
+ from profilis.decorators.profile import profile_function
191
+ from profilis.core.emitter import Emitter
192
+ from profilis.exporters.console import ConsoleExporter
193
+ from profilis.core.async_collector import AsyncCollector
194
+
195
+ # Setup profiling
196
+ exporter = ConsoleExporter(pretty=True)
197
+ collector = AsyncCollector(exporter, queue_size=128, flush_interval=0.2)
198
+ emitter = Emitter(collector)
199
+
200
+ @profile_function(emitter)
201
+ def expensive_calculation(n: int) -> int:
202
+ """This function will be automatically profiled."""
203
+ result = sum(i * i for i in range(n))
204
+ return result
205
+
206
+ @profile_function(emitter)
207
+ async def async_operation(data: list) -> list:
208
+ """Async functions are also supported."""
209
+ processed = [item * 2 for item in data]
210
+ return processed
211
+
212
+ # Use the profiled functions
213
+ result = expensive_calculation(1000)
214
+ ```
215
+
216
+ ### Manual Event Emission
217
+
218
+ ```python
219
+ from profilis.core.emitter import Emitter
220
+ from profilis.exporters.jsonl import JSONLExporter
221
+ from profilis.core.async_collector import AsyncCollector
222
+ from profilis.runtime import use_span, span_id
223
+
224
+ # Setup
225
+ exporter = JSONLExporter(dir="./logs")
226
+ collector = AsyncCollector(exporter)
227
+ emitter = Emitter(collector)
228
+
229
+ # Create a trace context
230
+ with use_span(trace_id=span_id()):
231
+ # Emit custom events
232
+ emitter.emit_req("/api/custom", 200, dur_ns=15000000) # 15ms
233
+ emitter.emit_fn("custom_function", dur_ns=5000000) # 5ms
234
+ emitter.emit_db("SELECT * FROM users", dur_ns=8000000, rows=100)
235
+
236
+ # Close collector to flush remaining events
237
+ collector.close()
238
+ ```
239
+
240
+ ### Built-in Dashboard
241
+
242
+ ```python
243
+ from flask import Flask
244
+ from profilis.flask.ui import make_ui_blueprint
245
+ from profilis.core.stats import StatsStore
246
+
247
+ app = Flask(__name__)
248
+ stats = StatsStore() # 15-minute rolling window
249
+
250
+ # Mount the dashboard at /_profilis
251
+ ui_bp = make_ui_blueprint(stats, ui_prefix="/_profilis")
252
+ app.register_blueprint(ui_bp)
253
+
254
+ # Visit http://localhost:5000/_profilis to see the dashboard
255
+ ```
256
+
257
+ ## Advanced Usage
258
+
259
+ ### Custom Exporters
260
+
261
+ ```python
262
+ from profilis.core.async_collector import AsyncCollector
263
+ from profilis.exporters.base import BaseExporter
264
+
265
+ class CustomExporter(BaseExporter):
266
+ def export(self, events: list[dict]) -> None:
267
+ for event in events:
268
+ # Custom export logic
269
+ print(f"Custom export: {event}")
270
+
271
+ # Use custom exporter
272
+ exporter = CustomExporter()
273
+ collector = AsyncCollector(exporter)
274
+ ```
275
+
276
+ ### Runtime Context Management
277
+
278
+ ```python
279
+ from profilis.runtime import use_span, span_id, get_trace_id, get_span_id
280
+
281
+ # Create distributed trace context
282
+ with use_span(trace_id="trace-123", span_id="span-456"):
283
+ current_trace = get_trace_id() # "trace-123"
284
+ current_span = get_span_id() # "span-456"
285
+
286
+ # Nested spans inherit trace context
287
+ with use_span(span_id="span-789"):
288
+ nested_span = get_span_id() # "span-789"
289
+ parent_trace = get_trace_id() # "trace-123"
290
+ ```
291
+
292
+ ### Performance Tuning
293
+
294
+ ```python
295
+ from profilis.core.async_collector import AsyncCollector
296
+
297
+ # High-throughput configuration
298
+ collector = AsyncCollector(
299
+ exporter,
300
+ queue_size=8192, # Large queue for high concurrency
301
+ batch_max=256, # Larger batches for efficiency
302
+ flush_interval=0.05, # More frequent flushing
303
+ drop_oldest=True # Drop events under backpressure
304
+ )
305
+
306
+ # Low-latency configuration
307
+ collector = AsyncCollector(
308
+ exporter,
309
+ queue_size=512, # Smaller queue for lower latency
310
+ batch_max=32, # Smaller batches for faster processing
311
+ flush_interval=0.01, # Very frequent flushing
312
+ drop_oldest=False # Don't drop events
313
+ )
314
+ ```
315
+
316
+ ## Configuration
317
+
318
+ ### Environment Variables
319
+
320
+ ```bash
321
+ # Note: Environment variable support is planned for future releases
322
+ # Currently, all configuration is done programmatically
323
+ ```
324
+
325
+ ### Sampling Strategies
326
+
327
+ ```python
328
+ # Random sampling
329
+ profilis = ProfilisFlask(app, collector=collector, sample=0.1) # 10% of requests
330
+
331
+ # Route-based sampling
332
+ profilis = ProfilisFlask(
333
+ app,
334
+ collector=collector,
335
+ exclude_routes=["/health", "/metrics", "/static"],
336
+ sample=1.0
337
+ )
338
+ ```
339
+
340
+ ## Exporters
341
+
342
+ ### JSONL Exporter
343
+ ```python
344
+ from profilis.exporters.jsonl import JSONLExporter
345
+
346
+ # With rotation
347
+ exporter = JSONLExporter(
348
+ dir="./logs",
349
+ rotate_bytes=1024*1024, # 1MB per file
350
+ rotate_secs=3600 # Rotate every hour
351
+ )
352
+ ```
353
+
354
+ ### Console Exporter
355
+ ```python
356
+ from profilis.exporters.console import ConsoleExporter
357
+
358
+ # Pretty-printed output for development
359
+ exporter = ConsoleExporter(pretty=True)
360
+
361
+ # Compact output for production
362
+ exporter = ConsoleExporter(pretty=False)
363
+ ```
364
+
365
+ ## Performance Characteristics
366
+
367
+ - **Event Creation**: ≤15µs per event
368
+ - **Memory Overhead**: ~100 bytes per event
369
+ - **Throughput**: 100K+ events/second on modern hardware
370
+ - **Latency**: Sub-millisecond collection overhead
371
+
372
+ ## Documentation
373
+
374
+ Full documentation is available at: [Profilis Docs](https://ankan97dutta.github.io/profilis/)
375
+
376
+ Docs are written in Markdown under [`docs/`](./docs) and built with [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
377
+
378
+ ### Available Documentation
379
+
380
+ - **[Getting Started](https://ankan97dutta.github.io/profilis/guides/getting-started/)** - Quick setup and basic usage
381
+ - **[Configuration](https://ankan97dutta.github.io/profilis/guides/configuration/)** - Tuning and customization
382
+ - **[Flask Integration](https://ankan97dutta.github.io/profilis/adapters/flask/)** - Flask adapter documentation
383
+ - **[SQLAlchemy Support](https://ankan97dutta.github.io/profilis/databases/sqlalchemy/)** - Database instrumentation
384
+ - **[JSONL Exporter](https://ankan97dutta.github.io/profilis/exporters/jsonl/)** - Log file output
385
+ - **[Built-in UI](https://ankan97dutta.github.io/profilis/ui/ui/)** - Dashboard documentation
386
+ - **[Architecture](https://ankan97dutta.github.io/profilis/architecture/architecture/)** - System design
387
+
388
+ To preview locally:
389
+ ```bash
390
+ pip install mkdocs mkdocs-material mkdocs-mermaid2-plugin
391
+ mkdocs serve
392
+ ```
393
+
394
+ ## Development
395
+
396
+ - See [Contributing](./docs/meta/contributing.md) and [Development Guidelines](./docs/meta/development-guidelines.md).
397
+ - Branch strategy: trunk‑based (`feat/*`, `fix/*`, `perf/*`, `chore/*`).
398
+ - Commits follow [Conventional Commits](https://www.conventionalcommits.org/).
399
+
400
+ ## Roadmap
401
+
402
+ See [Profilis – v0 Roadmap Project](https://github.com/ankan97dutta/profilis/projects) and [`docs/overview/roadmap.md`](./docs/overview/roadmap.md).
403
+
404
+ ## License
405
+
406
+ [MIT](./LICENSE)