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
oscura/core/cache.py
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"""Persistent LRU cache with disk spillover for TraceKit intermediate results.
|
|
2
|
+
|
|
3
|
+
This module provides memory-bounded caching for expensive operations like FFT,
|
|
4
|
+
spectrograms, and filtered traces with automatic disk spillover when memory
|
|
5
|
+
limits are exceeded.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.core.cache import TraceKitCache, get_cache
|
|
10
|
+
>>> cache = get_cache(max_memory="2GB")
|
|
11
|
+
>>> result = cache.get_or_compute("fft_key", compute_fft, signal, 1024)
|
|
12
|
+
>>> cache.show_stats()
|
|
13
|
+
Cache Statistics: 42 hits, 15 misses (73.7% hit rate)
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
Python functools.lru_cache
|
|
17
|
+
Python pickle for serialization
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import contextlib
|
|
23
|
+
import hashlib
|
|
24
|
+
import pickle
|
|
25
|
+
import tempfile
|
|
26
|
+
import threading
|
|
27
|
+
import time
|
|
28
|
+
from collections import OrderedDict
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from collections.abc import Callable
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class CacheEntry:
|
|
44
|
+
"""Single cache entry with metadata.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
key: Cache key (hash of inputs).
|
|
48
|
+
value: Cached value (in memory).
|
|
49
|
+
disk_path: Path to disk file if spilled.
|
|
50
|
+
size_bytes: Size of cached value in bytes.
|
|
51
|
+
created_at: Creation timestamp.
|
|
52
|
+
last_accessed: Last access timestamp.
|
|
53
|
+
access_count: Number of times accessed.
|
|
54
|
+
in_memory: True if value is in memory.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
key: str
|
|
58
|
+
value: Any | None
|
|
59
|
+
disk_path: Path | None
|
|
60
|
+
size_bytes: int
|
|
61
|
+
created_at: float
|
|
62
|
+
last_accessed: float
|
|
63
|
+
access_count: int
|
|
64
|
+
in_memory: bool
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class CacheStats:
|
|
69
|
+
"""Cache statistics.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
hits: Number of cache hits.
|
|
74
|
+
misses: Number of cache misses.
|
|
75
|
+
evictions: Number of entries evicted.
|
|
76
|
+
disk_spills: Number of entries spilled to disk.
|
|
77
|
+
current_memory: Current memory usage (bytes).
|
|
78
|
+
current_entries: Number of entries in cache.
|
|
79
|
+
disk_entries: Number of entries on disk.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
hits: int
|
|
83
|
+
misses: int
|
|
84
|
+
evictions: int
|
|
85
|
+
disk_spills: int
|
|
86
|
+
current_memory: int
|
|
87
|
+
current_entries: int
|
|
88
|
+
disk_entries: int
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def hit_rate(self) -> float:
|
|
92
|
+
"""Calculate cache hit rate (0.0-1.0)."""
|
|
93
|
+
total = self.hits + self.misses
|
|
94
|
+
return self.hits / total if total > 0 else 0.0
|
|
95
|
+
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
"""Format stats as human-readable string."""
|
|
98
|
+
return (
|
|
99
|
+
f"Cache Statistics:\n"
|
|
100
|
+
f" Hits: {self.hits}\n"
|
|
101
|
+
f" Misses: {self.misses}\n"
|
|
102
|
+
f" Hit Rate: {self.hit_rate * 100:.1f}%\n"
|
|
103
|
+
f" Evictions: {self.evictions}\n"
|
|
104
|
+
f" Disk Spills: {self.disk_spills}\n"
|
|
105
|
+
f" Memory Usage: {self.current_memory / 1e9:.2f} GB\n"
|
|
106
|
+
f" Entries (Memory): {self.current_entries}\n"
|
|
107
|
+
f" Entries (Disk): {self.disk_entries}\n"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TraceKitCache:
|
|
112
|
+
"""LRU cache with disk spillover for intermediate results.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
Caches expensive computation results with automatic memory management.
|
|
116
|
+
When memory limit is exceeded, least-recently-used entries are spilled
|
|
117
|
+
to disk. Automatic cleanup on exit.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
max_memory: Maximum memory for cache (bytes or string like "2GB").
|
|
121
|
+
cache_dir: Directory for disk cache (default: /tmp/oscura_cache).
|
|
122
|
+
auto_cleanup: Clean up disk cache on exit (default: True).
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> cache = TraceKitCache(max_memory="1GB")
|
|
126
|
+
>>> result = cache.get_or_compute("key", expensive_func, arg1, arg2)
|
|
127
|
+
>>> stats = cache.get_stats()
|
|
128
|
+
>>> cache.clear()
|
|
129
|
+
|
|
130
|
+
Security Note:
|
|
131
|
+
Cache files use pickle serialization. Cache directory should be in
|
|
132
|
+
a secure location with appropriate permissions. Do not share cache
|
|
133
|
+
directories across security boundaries. The cache is intended for
|
|
134
|
+
single-user, local computation only.
|
|
135
|
+
|
|
136
|
+
References:
|
|
137
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
138
|
+
MEM-029: LRU Cache for Intermediate Results
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
max_memory: int | str = "2GB",
|
|
144
|
+
*,
|
|
145
|
+
cache_dir: str | Path | None = None,
|
|
146
|
+
auto_cleanup: bool = True,
|
|
147
|
+
):
|
|
148
|
+
"""Initialize cache.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
max_memory: Maximum memory (bytes or string).
|
|
152
|
+
cache_dir: Directory for disk cache.
|
|
153
|
+
auto_cleanup: Clean up on exit.
|
|
154
|
+
"""
|
|
155
|
+
# Parse max_memory
|
|
156
|
+
if isinstance(max_memory, str):
|
|
157
|
+
self.max_memory = self._parse_memory_string(max_memory)
|
|
158
|
+
else:
|
|
159
|
+
self.max_memory = int(max_memory)
|
|
160
|
+
|
|
161
|
+
# Set up cache directory
|
|
162
|
+
if cache_dir is None:
|
|
163
|
+
self.cache_dir = Path(tempfile.gettempdir()) / "oscura_cache"
|
|
164
|
+
else:
|
|
165
|
+
self.cache_dir = Path(cache_dir)
|
|
166
|
+
|
|
167
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
self.auto_cleanup = auto_cleanup
|
|
169
|
+
|
|
170
|
+
# Cache storage (LRU via OrderedDict)
|
|
171
|
+
self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
|
|
172
|
+
|
|
173
|
+
# Thread lock for thread-safe operations (MEM-031)
|
|
174
|
+
self._lock = threading.RLock()
|
|
175
|
+
|
|
176
|
+
# Statistics
|
|
177
|
+
self._hits = 0
|
|
178
|
+
self._misses = 0
|
|
179
|
+
self._evictions = 0
|
|
180
|
+
self._disk_spills = 0
|
|
181
|
+
self._current_memory = 0
|
|
182
|
+
|
|
183
|
+
def __enter__(self) -> TraceKitCache:
|
|
184
|
+
"""Enter context."""
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore[no-untyped-def]
|
|
188
|
+
"""Exit context and clean up if enabled."""
|
|
189
|
+
# Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
|
|
190
|
+
if self.auto_cleanup:
|
|
191
|
+
self.clear()
|
|
192
|
+
|
|
193
|
+
def get(self, key: str) -> Any | None:
|
|
194
|
+
"""Get value from cache.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
key: Cache key.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Cached value or None if not found.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> value = cache.get("my_key")
|
|
205
|
+
>>> if value is None:
|
|
206
|
+
... value = compute_value()
|
|
207
|
+
... cache.put("my_key", value)
|
|
208
|
+
|
|
209
|
+
References:
|
|
210
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
211
|
+
"""
|
|
212
|
+
with self._lock:
|
|
213
|
+
if key not in self._cache:
|
|
214
|
+
self._misses += 1
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
# Cache hit
|
|
218
|
+
self._hits += 1
|
|
219
|
+
entry = self._cache[key]
|
|
220
|
+
|
|
221
|
+
# Update access metadata
|
|
222
|
+
entry.last_accessed = time.time()
|
|
223
|
+
entry.access_count += 1
|
|
224
|
+
|
|
225
|
+
# Move to end (most recently used)
|
|
226
|
+
self._cache.move_to_end(key)
|
|
227
|
+
|
|
228
|
+
# Load from disk if needed
|
|
229
|
+
if not entry.in_memory:
|
|
230
|
+
entry.value = self._load_from_disk(entry.disk_path) # type: ignore[arg-type]
|
|
231
|
+
entry.in_memory = True
|
|
232
|
+
self._current_memory += entry.size_bytes
|
|
233
|
+
|
|
234
|
+
# Check if we need to spill to make room
|
|
235
|
+
self._ensure_memory_limit()
|
|
236
|
+
|
|
237
|
+
return entry.value
|
|
238
|
+
|
|
239
|
+
def put(self, key: str, value: Any) -> None:
|
|
240
|
+
"""Put value in cache.
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
key: Cache key.
|
|
245
|
+
value: Value to cache.
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
>>> cache.put("result_key", computed_result)
|
|
249
|
+
|
|
250
|
+
References:
|
|
251
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
252
|
+
"""
|
|
253
|
+
# Calculate size outside lock (potentially expensive)
|
|
254
|
+
size_bytes = self._estimate_size(value)
|
|
255
|
+
|
|
256
|
+
with self._lock:
|
|
257
|
+
# Remove old entry if exists
|
|
258
|
+
if key in self._cache:
|
|
259
|
+
old_entry = self._cache[key]
|
|
260
|
+
self._current_memory -= old_entry.size_bytes
|
|
261
|
+
if old_entry.disk_path and old_entry.disk_path.exists():
|
|
262
|
+
old_entry.disk_path.unlink()
|
|
263
|
+
del self._cache[key]
|
|
264
|
+
|
|
265
|
+
# Create new entry
|
|
266
|
+
entry = CacheEntry(
|
|
267
|
+
key=key,
|
|
268
|
+
value=value,
|
|
269
|
+
disk_path=None,
|
|
270
|
+
size_bytes=size_bytes,
|
|
271
|
+
created_at=time.time(),
|
|
272
|
+
last_accessed=time.time(),
|
|
273
|
+
access_count=0,
|
|
274
|
+
in_memory=True,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Add to cache
|
|
278
|
+
self._cache[key] = entry
|
|
279
|
+
self._current_memory += size_bytes
|
|
280
|
+
|
|
281
|
+
# Ensure memory limit
|
|
282
|
+
self._ensure_memory_limit()
|
|
283
|
+
|
|
284
|
+
def get_or_compute(
|
|
285
|
+
self,
|
|
286
|
+
key: str,
|
|
287
|
+
compute_fn: Callable[..., T],
|
|
288
|
+
*args: Any,
|
|
289
|
+
**kwargs: Any,
|
|
290
|
+
) -> T:
|
|
291
|
+
"""Get cached value or compute and cache it.
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
key: Cache key.
|
|
296
|
+
compute_fn: Function to compute value if not cached.
|
|
297
|
+
args: Arguments to compute_fn.
|
|
298
|
+
kwargs: Keyword arguments to compute_fn.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Cached or computed value.
|
|
302
|
+
|
|
303
|
+
Example:
|
|
304
|
+
>>> result = cache.get_or_compute("fft_key", np.fft.fft, signal)
|
|
305
|
+
|
|
306
|
+
References:
|
|
307
|
+
MEM-029: LRU Cache for Intermediate Results
|
|
308
|
+
"""
|
|
309
|
+
value = self.get(key)
|
|
310
|
+
if value is not None:
|
|
311
|
+
return value # type: ignore[no-any-return]
|
|
312
|
+
|
|
313
|
+
# Compute value
|
|
314
|
+
value = compute_fn(*args, **kwargs)
|
|
315
|
+
|
|
316
|
+
# Cache it
|
|
317
|
+
self.put(key, value)
|
|
318
|
+
|
|
319
|
+
return value
|
|
320
|
+
|
|
321
|
+
def clear(self) -> None:
|
|
322
|
+
"""Clear all cache entries and disk files.
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
>>> cache.clear()
|
|
327
|
+
|
|
328
|
+
References:
|
|
329
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
330
|
+
"""
|
|
331
|
+
with self._lock:
|
|
332
|
+
# Remove disk files
|
|
333
|
+
for entry in self._cache.values():
|
|
334
|
+
if entry.disk_path and entry.disk_path.exists():
|
|
335
|
+
with contextlib.suppress(OSError):
|
|
336
|
+
entry.disk_path.unlink()
|
|
337
|
+
|
|
338
|
+
# Clear cache
|
|
339
|
+
self._cache.clear()
|
|
340
|
+
self._current_memory = 0
|
|
341
|
+
|
|
342
|
+
# Try to remove cache directory if empty (outside lock, not critical)
|
|
343
|
+
try:
|
|
344
|
+
if self.cache_dir.exists() and not any(self.cache_dir.iterdir()):
|
|
345
|
+
self.cache_dir.rmdir()
|
|
346
|
+
except OSError:
|
|
347
|
+
pass
|
|
348
|
+
|
|
349
|
+
def get_stats(self) -> CacheStats:
|
|
350
|
+
"""Get cache statistics.
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
CacheStats object.
|
|
355
|
+
|
|
356
|
+
Example:
|
|
357
|
+
>>> stats = cache.get_stats()
|
|
358
|
+
>>> print(f"Hit rate: {stats.hit_rate * 100:.1f}%")
|
|
359
|
+
|
|
360
|
+
References:
|
|
361
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
362
|
+
"""
|
|
363
|
+
with self._lock:
|
|
364
|
+
disk_entries = sum(1 for e in self._cache.values() if not e.in_memory)
|
|
365
|
+
return CacheStats(
|
|
366
|
+
hits=self._hits,
|
|
367
|
+
misses=self._misses,
|
|
368
|
+
evictions=self._evictions,
|
|
369
|
+
disk_spills=self._disk_spills,
|
|
370
|
+
current_memory=self._current_memory,
|
|
371
|
+
current_entries=len(self._cache),
|
|
372
|
+
disk_entries=disk_entries,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def show_stats(self) -> None:
|
|
376
|
+
"""Print cache statistics.
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
Example:
|
|
380
|
+
>>> cache.show_stats()
|
|
381
|
+
Cache Statistics: 42 hits, 15 misses (73.7% hit rate)
|
|
382
|
+
|
|
383
|
+
References:
|
|
384
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
385
|
+
"""
|
|
386
|
+
stats = self.get_stats()
|
|
387
|
+
print(stats)
|
|
388
|
+
|
|
389
|
+
def compute_key(self, *args: Any, **kwargs: Any) -> str:
|
|
390
|
+
"""Compute cache key from arguments.
|
|
391
|
+
|
|
392
|
+
Creates a hash key from arbitrary arguments for cache lookups.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
args: Positional arguments.
|
|
396
|
+
kwargs: Keyword arguments.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Hash key string.
|
|
400
|
+
|
|
401
|
+
Example:
|
|
402
|
+
>>> key = cache.compute_key("operation", param1=10, param2="value")
|
|
403
|
+
|
|
404
|
+
References:
|
|
405
|
+
MEM-029: LRU Cache for Intermediate Results
|
|
406
|
+
"""
|
|
407
|
+
# Create hashable representation
|
|
408
|
+
hash_obj = hashlib.sha256()
|
|
409
|
+
|
|
410
|
+
# Hash positional args
|
|
411
|
+
for arg in args:
|
|
412
|
+
hash_obj.update(self._make_hashable(arg))
|
|
413
|
+
|
|
414
|
+
# Hash keyword args (sorted for consistency)
|
|
415
|
+
for k in sorted(kwargs.keys()):
|
|
416
|
+
hash_obj.update(k.encode())
|
|
417
|
+
hash_obj.update(self._make_hashable(kwargs[k]))
|
|
418
|
+
|
|
419
|
+
return hash_obj.hexdigest()
|
|
420
|
+
|
|
421
|
+
def _ensure_memory_limit(self) -> None:
|
|
422
|
+
"""Ensure cache memory usage is within limit."""
|
|
423
|
+
while self._current_memory > self.max_memory and self._cache:
|
|
424
|
+
# Evict least recently used entry
|
|
425
|
+
key, entry = self._cache.popitem(last=False)
|
|
426
|
+
|
|
427
|
+
if entry.in_memory:
|
|
428
|
+
# Spill to disk if not already there
|
|
429
|
+
if entry.disk_path is None:
|
|
430
|
+
entry.disk_path = self._spill_to_disk(key, entry.value)
|
|
431
|
+
entry.in_memory = False
|
|
432
|
+
entry.value = None
|
|
433
|
+
self._disk_spills += 1
|
|
434
|
+
|
|
435
|
+
# Put back in cache (on disk)
|
|
436
|
+
self._cache[key] = entry
|
|
437
|
+
self._cache.move_to_end(key, last=False)
|
|
438
|
+
|
|
439
|
+
self._current_memory -= entry.size_bytes
|
|
440
|
+
|
|
441
|
+
self._evictions += 1
|
|
442
|
+
|
|
443
|
+
def _spill_to_disk(self, key: str, value: Any) -> Path:
|
|
444
|
+
"""Write value to disk.
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
key: Cache key.
|
|
449
|
+
value: Value to write.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Path to disk file.
|
|
453
|
+
"""
|
|
454
|
+
disk_path = self.cache_dir / f"{key}.pkl"
|
|
455
|
+
with open(disk_path, "wb") as f:
|
|
456
|
+
pickle.dump(value, f, protocol=pickle.HIGHEST_PROTOCOL)
|
|
457
|
+
return disk_path
|
|
458
|
+
|
|
459
|
+
def _load_from_disk(self, disk_path: Path) -> Any:
|
|
460
|
+
"""Load value from disk.
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
disk_path: Path to disk file.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Loaded value.
|
|
468
|
+
"""
|
|
469
|
+
with open(disk_path, "rb") as f:
|
|
470
|
+
return pickle.load(f)
|
|
471
|
+
|
|
472
|
+
def _estimate_size(self, value: Any) -> int:
|
|
473
|
+
"""Estimate size of value in bytes."""
|
|
474
|
+
if isinstance(value, np.ndarray):
|
|
475
|
+
return value.nbytes # type: ignore[no-any-return]
|
|
476
|
+
elif isinstance(value, list | tuple):
|
|
477
|
+
return sum(self._estimate_size(item) for item in value)
|
|
478
|
+
elif isinstance(value, dict):
|
|
479
|
+
return sum(self._estimate_size(k) + self._estimate_size(v) for k, v in value.items())
|
|
480
|
+
else:
|
|
481
|
+
# Fallback: use pickle size
|
|
482
|
+
try:
|
|
483
|
+
return len(pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL))
|
|
484
|
+
except (TypeError, pickle.PicklingError):
|
|
485
|
+
return 1024 # Default estimate
|
|
486
|
+
|
|
487
|
+
def _make_hashable(self, obj: Any) -> bytes:
|
|
488
|
+
"""Convert object to hashable bytes."""
|
|
489
|
+
if isinstance(obj, np.ndarray):
|
|
490
|
+
# Use array bytes for hashing
|
|
491
|
+
return obj.tobytes() # type: ignore[no-any-return]
|
|
492
|
+
elif isinstance(obj, str | bytes):
|
|
493
|
+
return obj.encode() if isinstance(obj, str) else obj
|
|
494
|
+
elif isinstance(obj, int | float | bool):
|
|
495
|
+
return str(obj).encode()
|
|
496
|
+
elif isinstance(obj, list | tuple):
|
|
497
|
+
return b"".join(self._make_hashable(item) for item in obj)
|
|
498
|
+
else:
|
|
499
|
+
# Fallback: use string representation
|
|
500
|
+
return str(obj).encode()
|
|
501
|
+
|
|
502
|
+
def _parse_memory_string(self, memory_str: str) -> int:
|
|
503
|
+
"""Parse memory string like '2GB' to bytes."""
|
|
504
|
+
memory_str = memory_str.strip().upper()
|
|
505
|
+
|
|
506
|
+
if memory_str.endswith("GB"):
|
|
507
|
+
return int(float(memory_str[:-2]) * 1e9)
|
|
508
|
+
elif memory_str.endswith("MB"):
|
|
509
|
+
return int(float(memory_str[:-2]) * 1e6)
|
|
510
|
+
elif memory_str.endswith("KB"):
|
|
511
|
+
return int(float(memory_str[:-2]) * 1e3)
|
|
512
|
+
else:
|
|
513
|
+
return int(memory_str)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# Global cache instance
|
|
517
|
+
_global_cache: TraceKitCache | None = None
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def get_cache(
|
|
521
|
+
max_memory: int | str = "2GB",
|
|
522
|
+
*,
|
|
523
|
+
cache_dir: str | Path | None = None,
|
|
524
|
+
) -> TraceKitCache:
|
|
525
|
+
"""Get or create global cache instance.
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
max_memory: Maximum memory for cache.
|
|
530
|
+
cache_dir: Cache directory.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
Global TraceKitCache instance.
|
|
534
|
+
|
|
535
|
+
Example:
|
|
536
|
+
>>> cache = get_cache(max_memory="1GB")
|
|
537
|
+
>>> result = cache.get_or_compute("key", compute_fn, args)
|
|
538
|
+
|
|
539
|
+
References:
|
|
540
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
541
|
+
"""
|
|
542
|
+
global _global_cache
|
|
543
|
+
|
|
544
|
+
if _global_cache is None:
|
|
545
|
+
_global_cache = TraceKitCache(max_memory, cache_dir=cache_dir)
|
|
546
|
+
|
|
547
|
+
return _global_cache
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def clear_cache() -> None:
|
|
551
|
+
"""Clear global cache.
|
|
552
|
+
|
|
553
|
+
Example:
|
|
554
|
+
>>> clear_cache()
|
|
555
|
+
|
|
556
|
+
References:
|
|
557
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
558
|
+
"""
|
|
559
|
+
global _global_cache
|
|
560
|
+
|
|
561
|
+
if _global_cache is not None:
|
|
562
|
+
_global_cache.clear()
|
|
563
|
+
_global_cache = None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def show_cache_stats() -> None:
|
|
567
|
+
"""Show global cache statistics.
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
Example:
|
|
571
|
+
>>> show_cache_stats()
|
|
572
|
+
Cache Statistics: 42 hits, 15 misses (73.7% hit rate)
|
|
573
|
+
|
|
574
|
+
References:
|
|
575
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
576
|
+
"""
|
|
577
|
+
if _global_cache is not None:
|
|
578
|
+
_global_cache.show_stats()
|
|
579
|
+
else:
|
|
580
|
+
print("Cache not initialized")
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
__all__ = [
|
|
584
|
+
"CacheEntry",
|
|
585
|
+
"CacheStats",
|
|
586
|
+
"TraceKitCache",
|
|
587
|
+
"clear_cache",
|
|
588
|
+
"get_cache",
|
|
589
|
+
"show_cache_stats",
|
|
590
|
+
]
|