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,453 @@
|
|
|
1
|
+
"""Memory monitoring and OOM prevention for TraceKit.
|
|
2
|
+
|
|
3
|
+
This module provides runtime memory monitoring to prevent out-of-memory crashes
|
|
4
|
+
and gracefully handle memory exhaustion scenarios.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.memory_monitor import monitor_memory, MemoryMonitor
|
|
9
|
+
>>> with MemoryMonitor('spectrogram', max_memory="4GB") as monitor:
|
|
10
|
+
... for i in range(1000):
|
|
11
|
+
... # Perform work
|
|
12
|
+
... monitor.check(i) # Check memory periodically
|
|
13
|
+
... stats = monitor.get_stats()
|
|
14
|
+
>>> print(f"Peak memory: {stats['peak'] / 1e9:.2f} GB")
|
|
15
|
+
|
|
16
|
+
References:
|
|
17
|
+
psutil documentation for memory monitoring
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import time
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import TYPE_CHECKING, Any
|
|
26
|
+
|
|
27
|
+
from oscura.config.memory import get_memory_config
|
|
28
|
+
from oscura.utils.memory import get_available_memory, get_max_memory
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from collections.abc import Callable, Iterator
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class MemorySnapshot:
|
|
36
|
+
"""Snapshot of memory state at a point in time.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
timestamp: Time of snapshot (seconds since epoch).
|
|
40
|
+
available: Available system memory (bytes).
|
|
41
|
+
process_rss: Process resident set size (bytes).
|
|
42
|
+
process_vms: Process virtual memory size (bytes).
|
|
43
|
+
pressure: Memory pressure (0.0-1.0).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
timestamp: float
|
|
47
|
+
available: int
|
|
48
|
+
process_rss: int
|
|
49
|
+
process_vms: int
|
|
50
|
+
pressure: float
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MemoryMonitor:
|
|
54
|
+
"""Context manager for monitoring memory usage during operations.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
Monitors memory usage during long-running operations and aborts
|
|
58
|
+
before system crashes if memory pressure becomes critical.
|
|
59
|
+
|
|
60
|
+
Attributes:
|
|
61
|
+
operation: Name of the operation being monitored.
|
|
62
|
+
max_memory: Maximum allowed memory (None = use global config).
|
|
63
|
+
check_interval: How often to check memory (number of iterations).
|
|
64
|
+
abort_on_critical: Whether to abort when critical threshold reached.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> with MemoryMonitor('fft', max_memory="2GB") as monitor:
|
|
68
|
+
... result = compute_fft(data)
|
|
69
|
+
... stats = monitor.get_stats()
|
|
70
|
+
>>> print(f"Peak: {stats['peak'] / 1e6:.1f} MB")
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
MemoryError: If memory usage approaches critical limit.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
operation: str,
|
|
79
|
+
*,
|
|
80
|
+
max_memory: int | str | None = None,
|
|
81
|
+
check_interval: int = 100,
|
|
82
|
+
abort_on_critical: bool = True,
|
|
83
|
+
):
|
|
84
|
+
"""Initialize memory monitor.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
operation: Name of operation being monitored.
|
|
88
|
+
max_memory: Maximum memory limit (bytes, string like "4GB", or None for auto).
|
|
89
|
+
check_interval: Check memory every N iterations.
|
|
90
|
+
abort_on_critical: Abort operation if critical threshold reached.
|
|
91
|
+
"""
|
|
92
|
+
self.operation = operation
|
|
93
|
+
self.check_interval = check_interval
|
|
94
|
+
self.abort_on_critical = abort_on_critical
|
|
95
|
+
|
|
96
|
+
# Parse max_memory
|
|
97
|
+
if max_memory is None:
|
|
98
|
+
self.max_memory = get_max_memory()
|
|
99
|
+
elif isinstance(max_memory, str):
|
|
100
|
+
from oscura.config.memory import _parse_memory_string
|
|
101
|
+
|
|
102
|
+
self.max_memory = _parse_memory_string(max_memory)
|
|
103
|
+
else:
|
|
104
|
+
self.max_memory = int(max_memory)
|
|
105
|
+
|
|
106
|
+
# State
|
|
107
|
+
self.start_memory = 0
|
|
108
|
+
self.peak_memory = 0
|
|
109
|
+
self.current_memory = 0
|
|
110
|
+
self._iteration = 0
|
|
111
|
+
self._snapshots: list[MemorySnapshot] = []
|
|
112
|
+
self._start_time = 0.0
|
|
113
|
+
|
|
114
|
+
def __enter__(self) -> MemoryMonitor:
|
|
115
|
+
"""Enter context and record starting memory."""
|
|
116
|
+
self.start_memory = self._get_process_memory()
|
|
117
|
+
self.peak_memory = self.start_memory
|
|
118
|
+
self.current_memory = self.start_memory
|
|
119
|
+
self._start_time = time.time()
|
|
120
|
+
|
|
121
|
+
# Take initial snapshot
|
|
122
|
+
self._take_snapshot()
|
|
123
|
+
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
def __exit__(
|
|
127
|
+
self,
|
|
128
|
+
exc_type: type[BaseException] | None,
|
|
129
|
+
exc_val: BaseException | None,
|
|
130
|
+
exc_tb: Any,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Exit context and finalize monitoring."""
|
|
133
|
+
# Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
|
|
134
|
+
# Take final snapshot
|
|
135
|
+
self._take_snapshot()
|
|
136
|
+
|
|
137
|
+
def check(self, iteration: int | None = None) -> None:
|
|
138
|
+
"""Check memory usage and raise error if limit approached.
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
iteration: Current iteration number (for periodic checking).
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
MemoryError: If memory usage exceeds critical threshold.
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
>>> with MemoryMonitor('operation') as monitor:
|
|
149
|
+
... for i in range(10000):
|
|
150
|
+
... # Do work
|
|
151
|
+
... monitor.check(i) # Check every 100 iterations
|
|
152
|
+
"""
|
|
153
|
+
self._iteration += 1
|
|
154
|
+
|
|
155
|
+
# Only check periodically to reduce overhead
|
|
156
|
+
if iteration is not None and iteration % self.check_interval != 0:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
self.current_memory = self._get_process_memory()
|
|
160
|
+
self.peak_memory = max(self.peak_memory, self.current_memory)
|
|
161
|
+
|
|
162
|
+
# Check against available memory and thresholds
|
|
163
|
+
available = get_available_memory()
|
|
164
|
+
config = get_memory_config()
|
|
165
|
+
|
|
166
|
+
# Calculate pressure
|
|
167
|
+
pressure = 1.0 - (available / self.max_memory) if self.max_memory > 0 else 0.0
|
|
168
|
+
|
|
169
|
+
# Take snapshot if significant time passed
|
|
170
|
+
if self._snapshots and (time.time() - self._snapshots[-1].timestamp) > 1.0:
|
|
171
|
+
self._take_snapshot()
|
|
172
|
+
|
|
173
|
+
# Check critical threshold
|
|
174
|
+
if self.abort_on_critical and pressure >= config.critical_threshold:
|
|
175
|
+
raise MemoryError(
|
|
176
|
+
f"Critical memory pressure during {self.operation}. "
|
|
177
|
+
f"Available: {available / 1e9:.2f} GB, "
|
|
178
|
+
f"Pressure: {pressure * 100:.1f}%, "
|
|
179
|
+
f"Limit: {self.max_memory / 1e9:.2f} GB. "
|
|
180
|
+
f"Operation aborted to prevent system crash. "
|
|
181
|
+
f"Suggestion: Reduce dataset size, increase memory limit, "
|
|
182
|
+
f"or use chunked processing."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def get_stats(self) -> dict[str, int | float]:
|
|
186
|
+
"""Get memory statistics for this monitoring session.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Dictionary with memory statistics including:
|
|
190
|
+
- start: Starting memory (bytes)
|
|
191
|
+
- current: Current memory (bytes)
|
|
192
|
+
- peak: Peak memory usage (bytes)
|
|
193
|
+
- delta: Memory increase since start (bytes)
|
|
194
|
+
- duration: Monitoring duration (seconds)
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> with MemoryMonitor('operation') as monitor:
|
|
198
|
+
... # ... do work ...
|
|
199
|
+
... stats = monitor.get_stats()
|
|
200
|
+
>>> print(f"Peak: {stats['peak'] / 1e6:.1f} MB")
|
|
201
|
+
"""
|
|
202
|
+
duration = time.time() - self._start_time if self._start_time > 0 else 0.0
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
"start": self.start_memory,
|
|
206
|
+
"current": self.current_memory,
|
|
207
|
+
"peak": self.peak_memory,
|
|
208
|
+
"delta": self.peak_memory - self.start_memory,
|
|
209
|
+
"duration": duration,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def get_snapshots(self) -> list[MemorySnapshot]:
|
|
213
|
+
"""Get all memory snapshots taken during monitoring.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
List of MemorySnapshot objects.
|
|
217
|
+
|
|
218
|
+
Example:
|
|
219
|
+
>>> with MemoryMonitor('operation') as monitor:
|
|
220
|
+
... # ... work ...
|
|
221
|
+
... pass
|
|
222
|
+
>>> for snap in monitor.get_snapshots():
|
|
223
|
+
... print(f"t={snap.timestamp:.1f}s: {snap.available/1e9:.2f} GB available")
|
|
224
|
+
"""
|
|
225
|
+
return self._snapshots.copy()
|
|
226
|
+
|
|
227
|
+
def _get_process_memory(self) -> int:
|
|
228
|
+
"""Get current process memory usage in bytes.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Resident set size (RSS) in bytes.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
import psutil
|
|
235
|
+
|
|
236
|
+
process = psutil.Process()
|
|
237
|
+
return process.memory_info().rss # type: ignore[no-any-return]
|
|
238
|
+
except ImportError:
|
|
239
|
+
# Fallback: estimate from system memory
|
|
240
|
+
from oscura.utils.memory import get_total_memory
|
|
241
|
+
|
|
242
|
+
return get_total_memory() - get_available_memory()
|
|
243
|
+
|
|
244
|
+
def _take_snapshot(self) -> None:
|
|
245
|
+
"""Take a snapshot of current memory state."""
|
|
246
|
+
try:
|
|
247
|
+
import psutil
|
|
248
|
+
|
|
249
|
+
process = psutil.Process()
|
|
250
|
+
mem_info = process.memory_info()
|
|
251
|
+
available = get_available_memory()
|
|
252
|
+
|
|
253
|
+
from oscura.utils.memory import get_memory_pressure
|
|
254
|
+
|
|
255
|
+
pressure = get_memory_pressure()
|
|
256
|
+
|
|
257
|
+
snapshot = MemorySnapshot(
|
|
258
|
+
timestamp=time.time(),
|
|
259
|
+
available=available,
|
|
260
|
+
process_rss=mem_info.rss,
|
|
261
|
+
process_vms=mem_info.vms,
|
|
262
|
+
pressure=pressure,
|
|
263
|
+
)
|
|
264
|
+
self._snapshots.append(snapshot)
|
|
265
|
+
except ImportError:
|
|
266
|
+
# Skip snapshots if psutil not available
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@contextmanager
|
|
271
|
+
def monitor_memory(
|
|
272
|
+
operation: str,
|
|
273
|
+
*,
|
|
274
|
+
max_memory: int | str | None = None,
|
|
275
|
+
check_interval: int = 100,
|
|
276
|
+
) -> Iterator[MemoryMonitor]:
|
|
277
|
+
"""Context manager for monitoring memory usage.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
Convenience function that wraps MemoryMonitor.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
operation: Name of operation being monitored.
|
|
284
|
+
max_memory: Maximum memory limit.
|
|
285
|
+
check_interval: Check memory every N iterations.
|
|
286
|
+
|
|
287
|
+
Yields:
|
|
288
|
+
MemoryMonitor instance.
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
>>> with monitor_memory('spectrogram', max_memory="4GB") as mon:
|
|
292
|
+
... for i in range(1000):
|
|
293
|
+
... # Work
|
|
294
|
+
... mon.check(i)
|
|
295
|
+
"""
|
|
296
|
+
monitor = MemoryMonitor(
|
|
297
|
+
operation,
|
|
298
|
+
max_memory=max_memory,
|
|
299
|
+
check_interval=check_interval,
|
|
300
|
+
)
|
|
301
|
+
with monitor:
|
|
302
|
+
yield monitor
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class ProgressWithMemory:
|
|
307
|
+
"""Progress information with memory metrics.
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
Attributes:
|
|
311
|
+
current: Current progress value.
|
|
312
|
+
total: Total progress value.
|
|
313
|
+
eta_seconds: Estimated time to completion (seconds).
|
|
314
|
+
memory_used: Current memory usage (bytes).
|
|
315
|
+
memory_peak: Peak memory usage (bytes).
|
|
316
|
+
memory_available: Available system memory (bytes).
|
|
317
|
+
operation: Name of operation.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
current: int
|
|
321
|
+
total: int
|
|
322
|
+
eta_seconds: float
|
|
323
|
+
memory_used: int
|
|
324
|
+
memory_peak: int
|
|
325
|
+
memory_available: int
|
|
326
|
+
operation: str
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def percent(self) -> float:
|
|
330
|
+
"""Progress percentage (0.0-100.0)."""
|
|
331
|
+
if self.total == 0:
|
|
332
|
+
return 100.0
|
|
333
|
+
return (self.current / self.total) * 100.0
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def memory_pressure(self) -> float:
|
|
337
|
+
"""Memory pressure (0.0-1.0)."""
|
|
338
|
+
from oscura.utils.memory import get_memory_pressure
|
|
339
|
+
|
|
340
|
+
return get_memory_pressure()
|
|
341
|
+
|
|
342
|
+
def format_progress(self) -> str:
|
|
343
|
+
"""Format progress as human-readable string.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Formatted progress string with memory info.
|
|
347
|
+
|
|
348
|
+
Example:
|
|
349
|
+
>>> progress = ProgressWithMemory(42, 100, 5.0, 1.2e9, 2.1e9, 6e9, "fft")
|
|
350
|
+
>>> print(progress.format_progress())
|
|
351
|
+
42.0% | 1.20 GB used | 2.10 GB peak | 6.00 GB avail | ETA 5s
|
|
352
|
+
"""
|
|
353
|
+
return (
|
|
354
|
+
f"{self.percent:.1f}% | "
|
|
355
|
+
f"{self.memory_used / 1e9:.2f} GB used | "
|
|
356
|
+
f"{self.memory_peak / 1e9:.2f} GB peak | "
|
|
357
|
+
f"{self.memory_available / 1e9:.2f} GB avail | "
|
|
358
|
+
f"ETA {self.eta_seconds:.0f}s"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class ProgressMonitor:
|
|
363
|
+
"""Combined progress and memory monitoring.
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
Tracks both operation progress and memory usage, providing
|
|
367
|
+
unified progress updates with memory metrics.
|
|
368
|
+
|
|
369
|
+
Example:
|
|
370
|
+
>>> monitor = ProgressMonitor('spectrogram', total=1000)
|
|
371
|
+
>>> for i in range(1000):
|
|
372
|
+
... # Work
|
|
373
|
+
... monitor.update(i)
|
|
374
|
+
... if i % 100 == 0:
|
|
375
|
+
... progress = monitor.get_progress()
|
|
376
|
+
... print(progress.format_progress())
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
def __init__(
|
|
380
|
+
self,
|
|
381
|
+
operation: str,
|
|
382
|
+
total: int,
|
|
383
|
+
*,
|
|
384
|
+
callback: Callable[[ProgressWithMemory], None] | None = None,
|
|
385
|
+
update_interval: int = 1,
|
|
386
|
+
):
|
|
387
|
+
"""Initialize progress monitor.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
operation: Name of operation.
|
|
391
|
+
total: Total number of items to process.
|
|
392
|
+
callback: Optional callback function called on each update.
|
|
393
|
+
update_interval: Call callback every N updates.
|
|
394
|
+
"""
|
|
395
|
+
self.operation = operation
|
|
396
|
+
self.total = total
|
|
397
|
+
self.callback = callback
|
|
398
|
+
self.update_interval = update_interval
|
|
399
|
+
self.current = 0
|
|
400
|
+
self._start_time = time.time()
|
|
401
|
+
self._memory_monitor = MemoryMonitor(operation, check_interval=1)
|
|
402
|
+
self._update_count = 0
|
|
403
|
+
|
|
404
|
+
def update(self, current: int | None = None) -> None:
|
|
405
|
+
"""Update progress.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
current: Current progress value (if None, increments by 1).
|
|
409
|
+
"""
|
|
410
|
+
if current is not None:
|
|
411
|
+
self.current = current
|
|
412
|
+
else:
|
|
413
|
+
self.current += 1
|
|
414
|
+
|
|
415
|
+
self._update_count += 1
|
|
416
|
+
|
|
417
|
+
# Check memory
|
|
418
|
+
self._memory_monitor.check(self._update_count)
|
|
419
|
+
|
|
420
|
+
# Call callback if interval reached
|
|
421
|
+
if self.callback and self._update_count % self.update_interval == 0:
|
|
422
|
+
progress = self.get_progress()
|
|
423
|
+
self.callback(progress)
|
|
424
|
+
|
|
425
|
+
def get_progress(self) -> ProgressWithMemory:
|
|
426
|
+
"""Get current progress with memory metrics.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
ProgressWithMemory instance.
|
|
430
|
+
"""
|
|
431
|
+
elapsed = time.time() - self._start_time
|
|
432
|
+
eta = elapsed / self.current * (self.total - self.current) if self.current > 0 else 0.0
|
|
433
|
+
|
|
434
|
+
stats = self._memory_monitor.get_stats()
|
|
435
|
+
|
|
436
|
+
return ProgressWithMemory(
|
|
437
|
+
current=self.current,
|
|
438
|
+
total=self.total,
|
|
439
|
+
eta_seconds=eta,
|
|
440
|
+
memory_used=stats["current"], # type: ignore[arg-type]
|
|
441
|
+
memory_peak=stats["peak"], # type: ignore[arg-type]
|
|
442
|
+
memory_available=get_available_memory(),
|
|
443
|
+
operation=self.operation,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
__all__ = [
|
|
448
|
+
"MemoryMonitor",
|
|
449
|
+
"MemorySnapshot",
|
|
450
|
+
"ProgressMonitor",
|
|
451
|
+
"ProgressWithMemory",
|
|
452
|
+
"monitor_memory",
|
|
453
|
+
]
|