oscura 0.5.0__py3-none-any.whl → 0.6.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 +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/core/gpu_backend.py
CHANGED
|
@@ -445,7 +445,7 @@ class GPUBackend:
|
|
|
445
445
|
return self._to_cpu(counts), self._to_cpu(edges)
|
|
446
446
|
else:
|
|
447
447
|
# CPU fallback
|
|
448
|
-
return np.histogram(data, bins=bins, range=range, density=density)
|
|
448
|
+
return np.histogram(data, bins=bins, range=range, density=density)
|
|
449
449
|
|
|
450
450
|
def dot(
|
|
451
451
|
self,
|
|
@@ -477,10 +477,12 @@ class GPUBackend:
|
|
|
477
477
|
gpu_a = self._to_gpu(a)
|
|
478
478
|
gpu_b = self._to_gpu(b)
|
|
479
479
|
result = self._cp.dot(gpu_a, gpu_b)
|
|
480
|
-
return self._to_cpu(result)
|
|
480
|
+
return self._to_cpu(result)
|
|
481
481
|
else:
|
|
482
|
-
# CPU fallback
|
|
483
|
-
|
|
482
|
+
# CPU fallback - cast result to expected type
|
|
483
|
+
from typing import cast
|
|
484
|
+
|
|
485
|
+
return cast("NDArray[np.float64] | np.float64", np.dot(a, b))
|
|
484
486
|
|
|
485
487
|
def matmul(
|
|
486
488
|
self,
|
|
@@ -511,10 +513,12 @@ class GPUBackend:
|
|
|
511
513
|
gpu_a = self._to_gpu(a)
|
|
512
514
|
gpu_b = self._to_gpu(b)
|
|
513
515
|
result = self._cp.matmul(gpu_a, gpu_b)
|
|
514
|
-
|
|
516
|
+
cpu_result = self._to_cpu(result)
|
|
517
|
+
return cpu_result
|
|
515
518
|
else:
|
|
516
|
-
# CPU fallback
|
|
517
|
-
|
|
519
|
+
# CPU fallback - cast from Any to expected type
|
|
520
|
+
result_cpu: NDArray[np.float64] = np.matmul(a, b)
|
|
521
|
+
return result_cpu
|
|
518
522
|
|
|
519
523
|
|
|
520
524
|
# Module-level singleton for convenient access
|
oscura/core/log_query.py
CHANGED
|
@@ -218,7 +218,34 @@ class LogQuery:
|
|
|
218
218
|
"""
|
|
219
219
|
results = self._records.copy()
|
|
220
220
|
|
|
221
|
-
#
|
|
221
|
+
# Apply all filters
|
|
222
|
+
results = self._filter_by_time(results, start_time, end_time)
|
|
223
|
+
results = self._filter_by_level(results, level)
|
|
224
|
+
results = self._filter_by_module(results, module, module_pattern)
|
|
225
|
+
results = self._filter_by_correlation(results, correlation_id)
|
|
226
|
+
results = self._filter_by_message(results, message_pattern)
|
|
227
|
+
|
|
228
|
+
# Apply pagination
|
|
229
|
+
results = self._apply_pagination(results, offset, limit)
|
|
230
|
+
|
|
231
|
+
return results
|
|
232
|
+
|
|
233
|
+
def _filter_by_time(
|
|
234
|
+
self,
|
|
235
|
+
results: list[LogRecord],
|
|
236
|
+
start_time: datetime | None,
|
|
237
|
+
end_time: datetime | None,
|
|
238
|
+
) -> list[LogRecord]:
|
|
239
|
+
"""Filter records by timestamp range.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
results: Input records.
|
|
243
|
+
start_time: Start time filter.
|
|
244
|
+
end_time: End time filter.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Filtered records.
|
|
248
|
+
"""
|
|
222
249
|
if start_time is not None:
|
|
223
250
|
start_str = format_timestamp(start_time, format="iso8601")
|
|
224
251
|
results = [r for r in results if r.timestamp >= start_str]
|
|
@@ -227,36 +254,99 @@ class LogQuery:
|
|
|
227
254
|
end_str = format_timestamp(end_time, format="iso8601")
|
|
228
255
|
results = [r for r in results if r.timestamp <= end_str]
|
|
229
256
|
|
|
230
|
-
|
|
257
|
+
return results
|
|
258
|
+
|
|
259
|
+
def _filter_by_level(self, results: list[LogRecord], level: str | None) -> list[LogRecord]:
|
|
260
|
+
"""Filter records by log level.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
results: Input records.
|
|
264
|
+
level: Level filter.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Filtered records.
|
|
268
|
+
"""
|
|
231
269
|
if level is not None:
|
|
232
|
-
|
|
270
|
+
return [r for r in results if r.level == level.upper()]
|
|
271
|
+
return results
|
|
233
272
|
|
|
234
|
-
|
|
273
|
+
def _filter_by_module(
|
|
274
|
+
self,
|
|
275
|
+
results: list[LogRecord],
|
|
276
|
+
module: str | None,
|
|
277
|
+
module_pattern: str | None,
|
|
278
|
+
) -> list[LogRecord]:
|
|
279
|
+
"""Filter records by module name.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
results: Input records.
|
|
283
|
+
module: Exact module filter.
|
|
284
|
+
module_pattern: Module pattern filter.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Filtered records.
|
|
288
|
+
"""
|
|
235
289
|
if module is not None:
|
|
236
290
|
results = [r for r in results if r.module == module]
|
|
237
291
|
|
|
238
|
-
# Filter by module pattern
|
|
239
292
|
if module_pattern is not None:
|
|
240
293
|
# Convert glob pattern to regex
|
|
241
294
|
pattern = module_pattern.replace(".", r"\.").replace("*", ".*")
|
|
242
295
|
regex = re.compile(f"^{pattern}$")
|
|
243
296
|
results = [r for r in results if regex.match(r.module)]
|
|
244
297
|
|
|
245
|
-
|
|
298
|
+
return results
|
|
299
|
+
|
|
300
|
+
def _filter_by_correlation(
|
|
301
|
+
self, results: list[LogRecord], correlation_id: str | None
|
|
302
|
+
) -> list[LogRecord]:
|
|
303
|
+
"""Filter records by correlation ID.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
results: Input records.
|
|
307
|
+
correlation_id: Correlation ID filter.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Filtered records.
|
|
311
|
+
"""
|
|
246
312
|
if correlation_id is not None:
|
|
247
|
-
|
|
313
|
+
return [r for r in results if r.correlation_id == correlation_id]
|
|
314
|
+
return results
|
|
248
315
|
|
|
249
|
-
|
|
316
|
+
def _filter_by_message(
|
|
317
|
+
self, results: list[LogRecord], message_pattern: str | None
|
|
318
|
+
) -> list[LogRecord]:
|
|
319
|
+
"""Filter records by message pattern.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
results: Input records.
|
|
323
|
+
message_pattern: Message pattern filter.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Filtered records.
|
|
327
|
+
"""
|
|
250
328
|
if message_pattern is not None:
|
|
251
329
|
regex = re.compile(message_pattern)
|
|
252
|
-
|
|
330
|
+
return [r for r in results if regex.search(r.message)]
|
|
331
|
+
return results
|
|
253
332
|
|
|
254
|
-
|
|
333
|
+
def _apply_pagination(
|
|
334
|
+
self, results: list[LogRecord], offset: int, limit: int | None
|
|
335
|
+
) -> list[LogRecord]:
|
|
336
|
+
"""Apply pagination to results.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
results: Input records.
|
|
340
|
+
offset: Number to skip.
|
|
341
|
+
limit: Maximum to return.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Paginated records.
|
|
345
|
+
"""
|
|
255
346
|
if offset > 0:
|
|
256
347
|
results = results[offset:]
|
|
257
348
|
if limit is not None:
|
|
258
349
|
results = results[:limit]
|
|
259
|
-
|
|
260
350
|
return results
|
|
261
351
|
|
|
262
352
|
def export_logs(
|
oscura/core/logging.py
CHANGED
|
@@ -226,9 +226,7 @@ class CompressingTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHand
|
|
|
226
226
|
# Determine the file that just got rotated
|
|
227
227
|
current_time = int(self.rolloverAt - self.interval)
|
|
228
228
|
time_tuple = time.gmtime(current_time) if self.utc else time.localtime(current_time)
|
|
229
|
-
dfn = self.rotation_filename(
|
|
230
|
-
self.baseFilename + "." + self.suffix % time_tuple[:6] # type: ignore[arg-type]
|
|
231
|
-
)
|
|
229
|
+
dfn = self.rotation_filename(self.baseFilename + "." + self.suffix % time_tuple[:6])
|
|
232
230
|
|
|
233
231
|
# Handle the existing rotated file
|
|
234
232
|
if Path(dfn).exists():
|
|
@@ -511,7 +509,7 @@ def configure_logging(
|
|
|
511
509
|
LOG-002: Hierarchical Log Levels
|
|
512
510
|
LOG-003: Automatic Log Rotation and Retention Policies
|
|
513
511
|
"""
|
|
514
|
-
global _logging_configured, _config
|
|
512
|
+
global _logging_configured, _config
|
|
515
513
|
|
|
516
514
|
# Update config
|
|
517
515
|
_config.level = level
|
|
@@ -523,64 +521,138 @@ def configure_logging(
|
|
|
523
521
|
root_logger.setLevel(getattr(logging, level.upper()))
|
|
524
522
|
|
|
525
523
|
# Remove existing handlers and close them to prevent resource leaks
|
|
526
|
-
|
|
527
|
-
handler.close()
|
|
528
|
-
root_logger.removeHandler(handler)
|
|
524
|
+
_cleanup_existing_handlers(root_logger)
|
|
529
525
|
|
|
530
526
|
# Create formatter
|
|
531
527
|
formatter = StructuredFormatter(format, timestamp_format)
|
|
532
528
|
|
|
533
529
|
# Add handlers
|
|
534
530
|
if handlers:
|
|
535
|
-
|
|
536
|
-
if name == "console":
|
|
537
|
-
handler = logging.StreamHandler(sys.stderr)
|
|
538
|
-
handler.setLevel(getattr(logging, config.get("level", level).upper()))
|
|
539
|
-
handler.setFormatter(formatter)
|
|
540
|
-
root_logger.addHandler(handler)
|
|
541
|
-
elif name == "file":
|
|
542
|
-
filename = config.get("filename", "oscura.log")
|
|
543
|
-
handler_level = config.get("level", "DEBUG")
|
|
544
|
-
backup_count = int(config.get("backup_count", 5))
|
|
545
|
-
compress = config.get("compress", False)
|
|
546
|
-
|
|
547
|
-
# Check if time-based rotation is requested
|
|
548
|
-
when = config.get("when")
|
|
549
|
-
if when:
|
|
550
|
-
# Time-based rotation (LOG-003)
|
|
551
|
-
interval = int(config.get("interval", 1))
|
|
552
|
-
max_age = config.get("max_age")
|
|
553
|
-
handler = CompressingTimedRotatingFileHandler(
|
|
554
|
-
filename,
|
|
555
|
-
when=when,
|
|
556
|
-
interval=interval,
|
|
557
|
-
backupCount=backup_count,
|
|
558
|
-
compress=compress,
|
|
559
|
-
max_age=max_age,
|
|
560
|
-
)
|
|
561
|
-
else:
|
|
562
|
-
# Size-based rotation
|
|
563
|
-
max_bytes = int(config.get("max_bytes", 10_000_000))
|
|
564
|
-
handler = CompressingRotatingFileHandler(
|
|
565
|
-
filename,
|
|
566
|
-
maxBytes=max_bytes,
|
|
567
|
-
backupCount=backup_count,
|
|
568
|
-
compress=compress,
|
|
569
|
-
)
|
|
570
|
-
|
|
571
|
-
handler.setLevel(getattr(logging, handler_level.upper()))
|
|
572
|
-
handler.setFormatter(formatter)
|
|
573
|
-
root_logger.addHandler(handler)
|
|
531
|
+
_add_configured_handlers(root_logger, handlers, formatter, level)
|
|
574
532
|
else:
|
|
575
|
-
|
|
576
|
-
handler = logging.StreamHandler(sys.stderr)
|
|
577
|
-
handler.setLevel(getattr(logging, level.upper()))
|
|
578
|
-
handler.setFormatter(formatter)
|
|
579
|
-
root_logger.addHandler(handler)
|
|
533
|
+
_add_default_console_handler(root_logger, formatter, level)
|
|
580
534
|
|
|
581
535
|
_logging_configured = True
|
|
582
536
|
|
|
583
537
|
|
|
538
|
+
def _cleanup_existing_handlers(logger: logging.Logger) -> None:
|
|
539
|
+
"""Remove and close existing handlers.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
logger: Logger to cleanup.
|
|
543
|
+
"""
|
|
544
|
+
for handler in logger.handlers[:]:
|
|
545
|
+
handler.close()
|
|
546
|
+
logger.removeHandler(handler)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _add_configured_handlers(
|
|
550
|
+
logger: logging.Logger,
|
|
551
|
+
handlers: dict[str, dict[str, Any]],
|
|
552
|
+
formatter: StructuredFormatter,
|
|
553
|
+
default_level: str,
|
|
554
|
+
) -> None:
|
|
555
|
+
"""Add configured handlers to logger.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
logger: Logger to add handlers to.
|
|
559
|
+
handlers: Handler configuration dict.
|
|
560
|
+
formatter: Formatter to use.
|
|
561
|
+
default_level: Default log level.
|
|
562
|
+
"""
|
|
563
|
+
for name, config in handlers.items():
|
|
564
|
+
if name == "console":
|
|
565
|
+
_add_console_handler(logger, config, formatter, default_level)
|
|
566
|
+
elif name == "file":
|
|
567
|
+
_add_file_handler(logger, config, formatter)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _add_console_handler(
|
|
571
|
+
logger: logging.Logger,
|
|
572
|
+
config: dict[str, Any],
|
|
573
|
+
formatter: StructuredFormatter,
|
|
574
|
+
default_level: str,
|
|
575
|
+
) -> None:
|
|
576
|
+
"""Add console handler to logger.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
logger: Logger to add handler to.
|
|
580
|
+
config: Handler configuration.
|
|
581
|
+
formatter: Formatter to use.
|
|
582
|
+
default_level: Default log level.
|
|
583
|
+
"""
|
|
584
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
585
|
+
handler.setLevel(getattr(logging, config.get("level", default_level).upper()))
|
|
586
|
+
handler.setFormatter(formatter)
|
|
587
|
+
logger.addHandler(handler)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _add_file_handler(
|
|
591
|
+
logger: logging.Logger,
|
|
592
|
+
config: dict[str, Any],
|
|
593
|
+
formatter: StructuredFormatter,
|
|
594
|
+
) -> None:
|
|
595
|
+
"""Add file handler to logger with rotation support.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
logger: Logger to add handler to.
|
|
599
|
+
config: Handler configuration.
|
|
600
|
+
formatter: Formatter to use.
|
|
601
|
+
"""
|
|
602
|
+
filename = config.get("filename", "oscura.log")
|
|
603
|
+
handler_level = config.get("level", "DEBUG")
|
|
604
|
+
backup_count = int(config.get("backup_count", 5))
|
|
605
|
+
compress = config.get("compress", False)
|
|
606
|
+
|
|
607
|
+
# Check if time-based rotation is requested
|
|
608
|
+
when = config.get("when")
|
|
609
|
+
if when:
|
|
610
|
+
# Time-based rotation
|
|
611
|
+
interval = int(config.get("interval", 1))
|
|
612
|
+
max_age = config.get("max_age")
|
|
613
|
+
time_handler = CompressingTimedRotatingFileHandler(
|
|
614
|
+
filename,
|
|
615
|
+
when=when,
|
|
616
|
+
interval=interval,
|
|
617
|
+
backupCount=backup_count,
|
|
618
|
+
compress=compress,
|
|
619
|
+
max_age=max_age,
|
|
620
|
+
)
|
|
621
|
+
time_handler.setLevel(getattr(logging, handler_level.upper()))
|
|
622
|
+
time_handler.setFormatter(formatter)
|
|
623
|
+
logger.addHandler(time_handler)
|
|
624
|
+
else:
|
|
625
|
+
# Size-based rotation
|
|
626
|
+
max_bytes = int(config.get("max_bytes", 10_000_000))
|
|
627
|
+
size_handler = CompressingRotatingFileHandler(
|
|
628
|
+
filename,
|
|
629
|
+
maxBytes=max_bytes,
|
|
630
|
+
backupCount=backup_count,
|
|
631
|
+
compress=compress,
|
|
632
|
+
)
|
|
633
|
+
size_handler.setLevel(getattr(logging, handler_level.upper()))
|
|
634
|
+
size_handler.setFormatter(formatter)
|
|
635
|
+
logger.addHandler(size_handler)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _add_default_console_handler(
|
|
639
|
+
logger: logging.Logger,
|
|
640
|
+
formatter: StructuredFormatter,
|
|
641
|
+
level: str,
|
|
642
|
+
) -> None:
|
|
643
|
+
"""Add default console handler when no handlers configured.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
logger: Logger to add handler to.
|
|
647
|
+
formatter: Formatter to use.
|
|
648
|
+
level: Log level.
|
|
649
|
+
"""
|
|
650
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
651
|
+
handler.setLevel(getattr(logging, level.upper()))
|
|
652
|
+
handler.setFormatter(formatter)
|
|
653
|
+
logger.addHandler(handler)
|
|
654
|
+
|
|
655
|
+
|
|
584
656
|
def get_logger(name: str) -> logging.Logger:
|
|
585
657
|
"""Get a logger with the specified name.
|
|
586
658
|
|
|
@@ -675,7 +747,7 @@ class ErrorContextCapture:
|
|
|
675
747
|
def from_exception(
|
|
676
748
|
cls,
|
|
677
749
|
exc: BaseException,
|
|
678
|
-
include_locals: bool = True,
|
|
750
|
+
include_locals: bool = True,
|
|
679
751
|
additional_context: dict[str, Any] | None = None,
|
|
680
752
|
) -> ErrorContextCapture:
|
|
681
753
|
"""Create error context from an exception.
|
|
@@ -885,14 +957,14 @@ _init_logging()
|
|
|
885
957
|
|
|
886
958
|
# Re-export correlation and performance functions for convenience
|
|
887
959
|
# These provide LOG-004 and LOG-006 functionality through this module
|
|
888
|
-
from oscura.core.correlation import (
|
|
960
|
+
from oscura.core.correlation import (
|
|
889
961
|
CorrelationContext,
|
|
890
962
|
generate_correlation_id,
|
|
891
963
|
get_correlation_id,
|
|
892
964
|
set_correlation_id,
|
|
893
965
|
with_correlation_id,
|
|
894
966
|
)
|
|
895
|
-
from oscura.core.performance import (
|
|
967
|
+
from oscura.core.performance import (
|
|
896
968
|
PerformanceContext,
|
|
897
969
|
PerformanceRecord,
|
|
898
970
|
clear_performance_data,
|
oscura/core/logging_advanced.py
CHANGED
|
@@ -603,11 +603,11 @@ class CompressedLogHandler(logging.Handler):
|
|
|
603
603
|
|
|
604
604
|
def _open_file(self) -> None:
|
|
605
605
|
"""Open current log file."""
|
|
606
|
-
self._current_file = gzip.open(
|
|
606
|
+
self._current_file = gzip.open(
|
|
607
607
|
f"{self.filename}.gz", "ab", compresslevel=self.compression_level
|
|
608
608
|
)
|
|
609
609
|
try:
|
|
610
|
-
self._current_size = os.path.getsize(f"{self.filename}.gz")
|
|
610
|
+
self._current_size = os.path.getsize(f"{self.filename}.gz")
|
|
611
611
|
except OSError:
|
|
612
612
|
self._current_size = 0
|
|
613
613
|
|
|
@@ -621,11 +621,11 @@ class CompressedLogHandler(logging.Handler):
|
|
|
621
621
|
src = f"{self.filename}.{i}.gz"
|
|
622
622
|
dst = f"{self.filename}.{i + 1}.gz"
|
|
623
623
|
if os.path.exists(src):
|
|
624
|
-
os.rename(src, dst)
|
|
624
|
+
os.rename(src, dst)
|
|
625
625
|
|
|
626
626
|
# Move current to .1
|
|
627
627
|
if os.path.exists(f"{self.filename}.gz"):
|
|
628
|
-
os.rename(f"{self.filename}.gz", f"{self.filename}.1.gz")
|
|
628
|
+
os.rename(f"{self.filename}.gz", f"{self.filename}.1.gz")
|
|
629
629
|
|
|
630
630
|
self._open_file()
|
|
631
631
|
|
|
@@ -667,7 +667,7 @@ class EncryptedLogHandler(logging.Handler):
|
|
|
667
667
|
|
|
668
668
|
with self._lock:
|
|
669
669
|
if self._file is None:
|
|
670
|
-
self._file = open(self.filename, "ab")
|
|
670
|
+
self._file = open(self.filename, "ab")
|
|
671
671
|
|
|
672
672
|
# Write length-prefixed encrypted message
|
|
673
673
|
length = len(encrypted).to_bytes(4, "big")
|
oscura/core/memory_limits.py
CHANGED
|
@@ -16,7 +16,7 @@ References:
|
|
|
16
16
|
import warnings
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
|
-
from oscura.config.memory import get_memory_config
|
|
19
|
+
from oscura.core.config.memory import get_memory_config
|
|
20
20
|
from oscura.utils.memory import estimate_memory
|
|
21
21
|
|
|
22
22
|
|
|
@@ -93,83 +93,123 @@ def apply_memory_limit(
|
|
|
93
93
|
If parameters cannot be adjusted to fit memory, a warning is issued
|
|
94
94
|
and the original parameters are returned.
|
|
95
95
|
"""
|
|
96
|
-
|
|
97
|
-
limit_bytes = parse_memory_limit(max_memory)
|
|
96
|
+
limit_bytes = _get_effective_memory_limit(max_memory)
|
|
98
97
|
if limit_bytes is None:
|
|
99
|
-
|
|
100
|
-
config = get_memory_config()
|
|
101
|
-
limit_bytes = config.max_memory
|
|
102
|
-
if limit_bytes is None:
|
|
103
|
-
# No limit, return params unchanged
|
|
104
|
-
return params
|
|
98
|
+
return params
|
|
105
99
|
|
|
106
100
|
samples = int(samples)
|
|
107
|
-
|
|
108
|
-
# Estimate with current parameters
|
|
109
101
|
current_estimate = estimate_memory(operation, samples, **params)
|
|
110
102
|
|
|
111
103
|
if current_estimate.total <= limit_bytes:
|
|
112
|
-
# Already within limit
|
|
113
104
|
return params
|
|
114
105
|
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
adjusted_params = _adjust_parameters_for_operation(
|
|
107
|
+
operation, samples, limit_bytes, params.copy()
|
|
108
|
+
)
|
|
109
|
+
_validate_adjusted_params(operation, samples, adjusted_params, limit_bytes)
|
|
110
|
+
|
|
111
|
+
return adjusted_params
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _get_effective_memory_limit(max_memory: int | str | None) -> int | None:
|
|
115
|
+
"""Get effective memory limit from parameter or config."""
|
|
116
|
+
limit_bytes = parse_memory_limit(max_memory)
|
|
117
|
+
if limit_bytes is not None:
|
|
118
|
+
return limit_bytes
|
|
117
119
|
|
|
120
|
+
config = get_memory_config()
|
|
121
|
+
return config.max_memory
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _adjust_parameters_for_operation(
|
|
125
|
+
operation: str, samples: int, limit_bytes: int, params: dict[str, Any]
|
|
126
|
+
) -> dict[str, Any]:
|
|
127
|
+
"""Adjust parameters based on operation type."""
|
|
118
128
|
if operation in ("fft", "psd"):
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
129
|
+
return _adjust_fft_params(operation, samples, limit_bytes, params)
|
|
130
|
+
|
|
131
|
+
if operation == "spectrogram":
|
|
132
|
+
return _adjust_spectrogram_params(samples, limit_bytes, params)
|
|
133
|
+
|
|
134
|
+
if operation == "eye_diagram":
|
|
135
|
+
return _adjust_eye_diagram_params(limit_bytes, params)
|
|
136
|
+
|
|
137
|
+
return params
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _adjust_fft_params(
|
|
141
|
+
operation: str, samples: int, limit_bytes: int, params: dict[str, Any]
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
"""Adjust FFT/PSD parameters to fit memory limit."""
|
|
144
|
+
if "nfft" not in params:
|
|
145
|
+
return params
|
|
146
|
+
|
|
147
|
+
original_nfft = params["nfft"]
|
|
148
|
+
nfft = _find_max_nfft(operation, samples, limit_bytes, **params)
|
|
149
|
+
|
|
150
|
+
if nfft < original_nfft:
|
|
151
|
+
params["nfft"] = nfft
|
|
152
|
+
warnings.warn(
|
|
153
|
+
f"Reduced nfft from {original_nfft} to {nfft} to fit {limit_bytes / 1e6:.1f} MB limit",
|
|
154
|
+
UserWarning,
|
|
155
|
+
stacklevel=2,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return params
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _adjust_spectrogram_params(
|
|
162
|
+
samples: int, limit_bytes: int, params: dict[str, Any]
|
|
163
|
+
) -> dict[str, Any]:
|
|
164
|
+
"""Adjust spectrogram parameters to fit memory limit."""
|
|
165
|
+
original_nperseg = params.get("nperseg", 256)
|
|
166
|
+
nperseg = _find_max_nperseg(samples, limit_bytes, noverlap=params.get("noverlap"))
|
|
167
|
+
|
|
168
|
+
if nperseg < original_nperseg:
|
|
169
|
+
params["nperseg"] = nperseg
|
|
170
|
+
|
|
171
|
+
if "noverlap" in params:
|
|
172
|
+
overlap_ratio = params["noverlap"] / original_nperseg
|
|
173
|
+
params["noverlap"] = int(nperseg * overlap_ratio)
|
|
174
|
+
|
|
175
|
+
warnings.warn(
|
|
176
|
+
f"Reduced nperseg from {original_nperseg} to {nperseg} to fit {limit_bytes / 1e6:.1f} MB limit",
|
|
177
|
+
UserWarning,
|
|
178
|
+
stacklevel=2,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if "nfft" in params and params["nfft"] > nperseg:
|
|
182
|
+
params["nfft"] = nperseg
|
|
183
|
+
|
|
184
|
+
return params
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _adjust_eye_diagram_params(limit_bytes: int, params: dict[str, Any]) -> dict[str, Any]:
|
|
188
|
+
"""Adjust eye diagram parameters to fit memory limit."""
|
|
189
|
+
if "num_uis" not in params:
|
|
190
|
+
return params
|
|
191
|
+
|
|
192
|
+
original_num_uis = params["num_uis"]
|
|
193
|
+
samples_per_ui = params.get("samples_per_ui", 100)
|
|
194
|
+
max_num_uis = _find_max_num_uis(limit_bytes, samples_per_ui)
|
|
195
|
+
|
|
196
|
+
if max_num_uis < original_num_uis:
|
|
197
|
+
params["num_uis"] = max_num_uis
|
|
198
|
+
warnings.warn(
|
|
199
|
+
f"Reduced num_uis from {original_num_uis} to {max_num_uis} to fit {limit_bytes / 1e6:.1f} MB limit",
|
|
200
|
+
UserWarning,
|
|
201
|
+
stacklevel=2,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return params
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _validate_adjusted_params(
|
|
208
|
+
operation: str, samples: int, params: dict[str, Any], limit_bytes: int
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Verify that adjusted parameters fit within memory limit."""
|
|
211
|
+
final_estimate = estimate_memory(operation, samples, **params)
|
|
212
|
+
|
|
173
213
|
if final_estimate.total > limit_bytes:
|
|
174
214
|
warnings.warn(
|
|
175
215
|
f"Could not adjust parameters to fit {limit_bytes / 1e6:.1f} MB limit. "
|
|
@@ -179,8 +219,6 @@ def apply_memory_limit(
|
|
|
179
219
|
stacklevel=2,
|
|
180
220
|
)
|
|
181
221
|
|
|
182
|
-
return adjusted_params
|
|
183
|
-
|
|
184
222
|
|
|
185
223
|
def _find_max_nfft(operation: str, samples: int, limit_bytes: int, **params: Any) -> int:
|
|
186
224
|
"""Binary search for maximum nfft that fits memory limit.
|