oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""Performance timing and monitoring infrastructure.
|
|
2
|
+
|
|
3
|
+
This module provides automatic performance timing for operations,
|
|
4
|
+
metric collection, and performance summary reporting.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.performance import timed, get_performance_summary
|
|
9
|
+
>>> @timed(threshold=0.1)
|
|
10
|
+
... def slow_operation(data):
|
|
11
|
+
... result = expensive_computation(data)
|
|
12
|
+
... return result
|
|
13
|
+
>>> summary = get_performance_summary()
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
- Python time.perf_counter() for high-resolution timing
|
|
17
|
+
- Performance monitoring best practices
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import functools
|
|
23
|
+
import time
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from datetime import UTC, datetime
|
|
28
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
29
|
+
|
|
30
|
+
import numpy as np
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from numpy.typing import NDArray
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class PerformanceRecord:
|
|
38
|
+
"""Record of a timed operation.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
operation: Name of the operation (typically function name).
|
|
42
|
+
duration: Duration in seconds.
|
|
43
|
+
timestamp: ISO 8601 timestamp of when operation completed.
|
|
44
|
+
metadata: Optional metadata about the operation.
|
|
45
|
+
|
|
46
|
+
References:
|
|
47
|
+
LOG-006: Automatic Performance Timing
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
operation: str
|
|
51
|
+
duration: float
|
|
52
|
+
timestamp: str
|
|
53
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class PerformanceCollector:
|
|
57
|
+
"""Collects and aggregates performance timing data.
|
|
58
|
+
|
|
59
|
+
Thread-safe collector for recording and analyzing operation timings.
|
|
60
|
+
Provides summary statistics per operation type.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> collector = PerformanceCollector()
|
|
64
|
+
>>> collector.record("fft_computation", 1.23, samples=1000000)
|
|
65
|
+
>>> collector.record("fft_computation", 1.45, samples=2000000)
|
|
66
|
+
>>> summary = collector.get_summary()
|
|
67
|
+
>>> print(summary["fft_computation"]["mean"])
|
|
68
|
+
1.34
|
|
69
|
+
|
|
70
|
+
References:
|
|
71
|
+
LOG-006: Automatic Performance Timing
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self) -> None:
|
|
75
|
+
"""Initialize the performance collector."""
|
|
76
|
+
self.records: list[PerformanceRecord] = []
|
|
77
|
+
|
|
78
|
+
def record(
|
|
79
|
+
self,
|
|
80
|
+
operation: str,
|
|
81
|
+
duration: float,
|
|
82
|
+
**metadata: Any,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Record a performance measurement.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
operation: Name of the operation being timed.
|
|
88
|
+
duration: Duration in seconds (use time.perf_counter()).
|
|
89
|
+
**metadata: Additional metadata about the operation.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> collector.record("load_trace", 0.5, file_size_mb=100)
|
|
93
|
+
>>> collector.record("compute_fft", 2.3, samples=10000000)
|
|
94
|
+
|
|
95
|
+
References:
|
|
96
|
+
LOG-006: Automatic Performance Timing
|
|
97
|
+
"""
|
|
98
|
+
self.records.append(
|
|
99
|
+
PerformanceRecord(
|
|
100
|
+
operation=operation,
|
|
101
|
+
duration=duration,
|
|
102
|
+
timestamp=datetime.now(UTC).isoformat(),
|
|
103
|
+
metadata=metadata,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def get_summary(self) -> dict[str, dict[str, float]]:
|
|
108
|
+
"""Get summary statistics per operation.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary mapping operation names to statistics:
|
|
112
|
+
- count: Number of times operation was performed
|
|
113
|
+
- mean: Mean duration in seconds
|
|
114
|
+
- std: Standard deviation of duration
|
|
115
|
+
- min: Minimum duration
|
|
116
|
+
- max: Maximum duration
|
|
117
|
+
- total: Total time spent in operation
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> summary = collector.get_summary()
|
|
121
|
+
>>> fft_stats = summary["fft_computation"]
|
|
122
|
+
>>> print(f"FFT avg: {fft_stats['mean']:.3f}s")
|
|
123
|
+
|
|
124
|
+
References:
|
|
125
|
+
LOG-006: Automatic Performance Timing
|
|
126
|
+
"""
|
|
127
|
+
by_op: dict[str, list[float]] = defaultdict(list)
|
|
128
|
+
for record in self.records:
|
|
129
|
+
by_op[record.operation].append(record.duration)
|
|
130
|
+
|
|
131
|
+
summary: dict[str, dict[str, float]] = {}
|
|
132
|
+
for op, durations in by_op.items():
|
|
133
|
+
arr: NDArray[np.float64] = np.array(durations)
|
|
134
|
+
summary[op] = {
|
|
135
|
+
"count": float(len(arr)),
|
|
136
|
+
"mean": float(np.mean(arr)),
|
|
137
|
+
"std": float(np.std(arr)),
|
|
138
|
+
"min": float(np.min(arr)),
|
|
139
|
+
"max": float(np.max(arr)),
|
|
140
|
+
"total": float(np.sum(arr)),
|
|
141
|
+
}
|
|
142
|
+
return summary
|
|
143
|
+
|
|
144
|
+
def clear(self) -> None:
|
|
145
|
+
"""Clear all performance records.
|
|
146
|
+
|
|
147
|
+
Useful for resetting measurements between test runs or
|
|
148
|
+
analysis sessions.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> collector.clear()
|
|
152
|
+
"""
|
|
153
|
+
self.records.clear()
|
|
154
|
+
|
|
155
|
+
def get_records(
|
|
156
|
+
self,
|
|
157
|
+
operation: str | None = None,
|
|
158
|
+
since: datetime | None = None,
|
|
159
|
+
) -> list[PerformanceRecord]:
|
|
160
|
+
"""Get performance records with optional filtering.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
operation: Filter by operation name, or None for all.
|
|
164
|
+
since: Filter records after this timestamp, or None for all.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
List of matching performance records.
|
|
168
|
+
|
|
169
|
+
Example:
|
|
170
|
+
>>> from datetime import datetime, timezone, timedelta
|
|
171
|
+
>>> one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1)
|
|
172
|
+
>>> recent = collector.get_records(since=one_hour_ago)
|
|
173
|
+
"""
|
|
174
|
+
records = self.records
|
|
175
|
+
|
|
176
|
+
if operation is not None:
|
|
177
|
+
records = [r for r in records if r.operation == operation]
|
|
178
|
+
|
|
179
|
+
if since is not None:
|
|
180
|
+
since_iso = since.isoformat()
|
|
181
|
+
records = [r for r in records if r.timestamp >= since_iso]
|
|
182
|
+
|
|
183
|
+
return records
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Global performance collector
|
|
187
|
+
_global_collector = PerformanceCollector()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def timed(
|
|
194
|
+
threshold: float | None = None,
|
|
195
|
+
log_level: str = "DEBUG",
|
|
196
|
+
collect: bool = True,
|
|
197
|
+
) -> Callable[[F], F]:
|
|
198
|
+
"""Decorator to time and log function execution.
|
|
199
|
+
|
|
200
|
+
Automatically times function execution using time.perf_counter()
|
|
201
|
+
and optionally logs if duration exceeds threshold.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
threshold: Only log if duration exceeds this (seconds). None logs all.
|
|
205
|
+
log_level: Log level for timing messages (DEBUG, INFO, WARNING, etc.).
|
|
206
|
+
collect: Whether to collect timing in global PerformanceCollector.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Decorated function.
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
>>> @timed(threshold=0.1)
|
|
213
|
+
... def slow_function(data):
|
|
214
|
+
... time.sleep(0.2)
|
|
215
|
+
... return process(data)
|
|
216
|
+
|
|
217
|
+
>>> @timed(log_level="INFO")
|
|
218
|
+
... def important_operation():
|
|
219
|
+
... return compute_result()
|
|
220
|
+
|
|
221
|
+
References:
|
|
222
|
+
LOG-006: Automatic Performance Timing
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def decorator(func: F) -> F:
|
|
226
|
+
@functools.wraps(func)
|
|
227
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
228
|
+
start = time.perf_counter()
|
|
229
|
+
try:
|
|
230
|
+
return func(*args, **kwargs)
|
|
231
|
+
finally:
|
|
232
|
+
duration = time.perf_counter() - start
|
|
233
|
+
|
|
234
|
+
# Record in global collector
|
|
235
|
+
if collect:
|
|
236
|
+
_global_collector.record(func.__qualname__, duration)
|
|
237
|
+
|
|
238
|
+
# Log if threshold met
|
|
239
|
+
if threshold is None or duration >= threshold:
|
|
240
|
+
# Lazy import to avoid circular dependency
|
|
241
|
+
from oscura.core.logging import get_logger
|
|
242
|
+
|
|
243
|
+
logger = get_logger(func.__module__ or "oscura")
|
|
244
|
+
log_func = getattr(logger, log_level.lower())
|
|
245
|
+
log_func(
|
|
246
|
+
f"Function {func.__qualname__} completed",
|
|
247
|
+
extra={"duration_seconds": round(duration, 6)},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return wrapper # type: ignore[return-value]
|
|
251
|
+
|
|
252
|
+
return decorator
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_performance_summary() -> dict[str, dict[str, float]]:
|
|
256
|
+
"""Get summary of collected performance data from global collector.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Dictionary of performance statistics per operation.
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
>>> summary = get_performance_summary()
|
|
263
|
+
>>> for op, stats in summary.items():
|
|
264
|
+
... print(f"{op}: {stats['mean']:.3f}s avg, {stats['count']:.0f} calls")
|
|
265
|
+
|
|
266
|
+
References:
|
|
267
|
+
LOG-006: Automatic Performance Timing
|
|
268
|
+
"""
|
|
269
|
+
return _global_collector.get_summary()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def clear_performance_data() -> None:
|
|
273
|
+
"""Clear all collected performance data from global collector.
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
>>> clear_performance_data()
|
|
277
|
+
"""
|
|
278
|
+
_global_collector.clear()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def get_performance_records(
|
|
282
|
+
operation: str | None = None,
|
|
283
|
+
since: datetime | None = None,
|
|
284
|
+
) -> list[PerformanceRecord]:
|
|
285
|
+
"""Get performance records from global collector.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
operation: Filter by operation name, or None for all.
|
|
289
|
+
since: Filter records after this timestamp, or None for all.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of matching performance records.
|
|
293
|
+
|
|
294
|
+
Example:
|
|
295
|
+
>>> records = get_performance_records(operation="fft_computation")
|
|
296
|
+
>>> for record in records:
|
|
297
|
+
... print(f"{record.timestamp}: {record.duration:.3f}s")
|
|
298
|
+
"""
|
|
299
|
+
return _global_collector.get_records(operation=operation, since=since)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class PerformanceContext:
|
|
303
|
+
"""Context manager for timing a block of code.
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
>>> with PerformanceContext("data_loading") as ctx:
|
|
307
|
+
... data = load_large_file()
|
|
308
|
+
>>> print(f"Loading took {ctx.duration:.3f}s")
|
|
309
|
+
|
|
310
|
+
References:
|
|
311
|
+
LOG-006: Automatic Performance Timing
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
def __init__(
|
|
315
|
+
self,
|
|
316
|
+
operation: str,
|
|
317
|
+
log_threshold: float | None = None,
|
|
318
|
+
collect: bool = True,
|
|
319
|
+
):
|
|
320
|
+
"""Initialize performance context.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
operation: Name of the operation being timed.
|
|
324
|
+
log_threshold: Log if duration exceeds threshold (seconds).
|
|
325
|
+
collect: Whether to collect in global collector.
|
|
326
|
+
"""
|
|
327
|
+
self.operation = operation
|
|
328
|
+
self.log_threshold = log_threshold
|
|
329
|
+
self.collect = collect
|
|
330
|
+
self.start_time: float = 0.0
|
|
331
|
+
self.duration: float = 0.0
|
|
332
|
+
|
|
333
|
+
def __enter__(self) -> PerformanceContext:
|
|
334
|
+
"""Start timing."""
|
|
335
|
+
self.start_time = time.perf_counter()
|
|
336
|
+
return self
|
|
337
|
+
|
|
338
|
+
def __exit__(self, *args: Any) -> None:
|
|
339
|
+
"""Stop timing and record."""
|
|
340
|
+
self.duration = time.perf_counter() - self.start_time
|
|
341
|
+
|
|
342
|
+
if self.collect:
|
|
343
|
+
_global_collector.record(self.operation, self.duration)
|
|
344
|
+
|
|
345
|
+
if self.log_threshold is None or self.duration >= self.log_threshold:
|
|
346
|
+
from oscura.core.logging import get_logger
|
|
347
|
+
|
|
348
|
+
logger = get_logger("oscura.performance")
|
|
349
|
+
logger.debug(
|
|
350
|
+
f"Operation {self.operation} completed",
|
|
351
|
+
extra={"duration_seconds": round(self.duration, 6)},
|
|
352
|
+
)
|