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/cli/batch.py
CHANGED
|
@@ -24,38 +24,38 @@ from oscura.cli.main import format_output
|
|
|
24
24
|
logger = logging.getLogger("oscura.cli.batch")
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
@click.command()
|
|
28
|
-
@click.argument("pattern")
|
|
29
|
-
@click.option(
|
|
27
|
+
@click.command()
|
|
28
|
+
@click.argument("pattern")
|
|
29
|
+
@click.option(
|
|
30
30
|
"--analysis",
|
|
31
31
|
type=click.Choice(["characterize", "decode", "spectrum"], case_sensitive=False),
|
|
32
32
|
required=True,
|
|
33
33
|
help="Type of analysis to perform on each file.",
|
|
34
34
|
)
|
|
35
|
-
@click.option(
|
|
35
|
+
@click.option(
|
|
36
36
|
"--parallel",
|
|
37
37
|
type=int,
|
|
38
38
|
default=1,
|
|
39
39
|
help="Number of files to process concurrently (default: 1).",
|
|
40
40
|
)
|
|
41
|
-
@click.option(
|
|
41
|
+
@click.option(
|
|
42
42
|
"--output",
|
|
43
43
|
type=click.Choice(["json", "csv", "html", "table"], case_sensitive=False),
|
|
44
44
|
default="table",
|
|
45
45
|
help="Output format (default: table).",
|
|
46
46
|
)
|
|
47
|
-
@click.option(
|
|
47
|
+
@click.option(
|
|
48
48
|
"--save-summary",
|
|
49
49
|
type=click.Path(),
|
|
50
50
|
default=None,
|
|
51
51
|
help="Save aggregated results to file (CSV format).",
|
|
52
52
|
)
|
|
53
|
-
@click.option(
|
|
53
|
+
@click.option(
|
|
54
54
|
"--continue-on-error",
|
|
55
55
|
is_flag=True,
|
|
56
56
|
help="Continue processing even if individual files fail.",
|
|
57
57
|
)
|
|
58
|
-
@click.pass_context
|
|
58
|
+
@click.pass_context
|
|
59
59
|
def batch(
|
|
60
60
|
ctx: click.Context,
|
|
61
61
|
pattern: str,
|
|
@@ -110,7 +110,7 @@ def batch(
|
|
|
110
110
|
|
|
111
111
|
try:
|
|
112
112
|
# Expand glob pattern
|
|
113
|
-
files = glob.glob(pattern, recursive=True)
|
|
113
|
+
files = glob.glob(pattern, recursive=True)
|
|
114
114
|
|
|
115
115
|
if not files:
|
|
116
116
|
click.echo(f"No files matched pattern: {pattern}", err=True)
|
|
@@ -145,133 +145,91 @@ def batch(
|
|
|
145
145
|
ctx.exit(1)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
def
|
|
149
|
-
|
|
150
|
-
analysis_type: str,
|
|
151
|
-
parallel: int,
|
|
152
|
-
continue_on_error: bool,
|
|
153
|
-
verbose: int,
|
|
154
|
-
) -> list[dict[str, Any]]:
|
|
155
|
-
"""Perform batch analysis on multiple files.
|
|
156
|
-
|
|
157
|
-
Uses concurrent.futures for parallel processing when parallel > 1.
|
|
148
|
+
def _analyze_single_file(file_path: str, analysis_type: str) -> dict[str, Any]:
|
|
149
|
+
"""Analyze a single file and return results.
|
|
158
150
|
|
|
159
151
|
Args:
|
|
160
|
-
|
|
152
|
+
file_path: Path to waveform file to analyze.
|
|
161
153
|
analysis_type: Type of analysis to perform.
|
|
162
|
-
parallel: Number of parallel workers.
|
|
163
|
-
continue_on_error: Whether to continue on errors.
|
|
164
|
-
verbose: Verbosity level.
|
|
165
154
|
|
|
166
155
|
Returns:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Raises:
|
|
170
|
-
Exception: If analysis fails and continue_on_error is False.
|
|
156
|
+
Dictionary containing analysis results.
|
|
171
157
|
"""
|
|
172
|
-
|
|
158
|
+
import numpy as np
|
|
159
|
+
|
|
160
|
+
from oscura.analyzers.waveform.measurements import fall_time, rise_time
|
|
161
|
+
from oscura.analyzers.waveform.spectral import fft, thd
|
|
162
|
+
from oscura.inference import detect_protocol
|
|
163
|
+
from oscura.loaders import load
|
|
164
|
+
|
|
165
|
+
trace = load(file_path)
|
|
166
|
+
sample_rate = trace.metadata.sample_rate
|
|
167
|
+
|
|
168
|
+
result: dict[str, Any] = {
|
|
169
|
+
"file": str(Path(file_path).name),
|
|
170
|
+
"status": "success",
|
|
171
|
+
"analysis_type": analysis_type,
|
|
172
|
+
"samples": len(trace.data), # type: ignore[union-attr]
|
|
173
|
+
"sample_rate": f"{sample_rate / 1e6:.1f} MHz",
|
|
174
|
+
}
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# Pass WaveformTrace directly to functions (they expect WaveformTrace)
|
|
206
|
-
rt = rise_time(trace) # type: ignore[arg-type]
|
|
207
|
-
ft = fall_time(trace) # type: ignore[arg-type]
|
|
208
|
-
result.update(
|
|
209
|
-
{
|
|
210
|
-
"rise_time": f"{rt * 1e9:.2f} ns" if not np.isnan(rt) else "N/A",
|
|
211
|
-
"fall_time": f"{ft * 1e9:.2f} ns" if not np.isnan(ft) else "N/A",
|
|
212
|
-
}
|
|
213
|
-
)
|
|
214
|
-
elif analysis_type == "decode":
|
|
215
|
-
detected = detect_protocol(trace) # type: ignore[arg-type]
|
|
216
|
-
result.update(
|
|
217
|
-
{
|
|
218
|
-
"protocol": detected.get("protocol", "unknown"),
|
|
219
|
-
"confidence": f"{detected.get('confidence', 0) * 100:.0f}%",
|
|
220
|
-
}
|
|
221
|
-
)
|
|
222
|
-
elif analysis_type == "spectrum":
|
|
223
|
-
# Pass WaveformTrace directly to FFT functions
|
|
224
|
-
freqs, mags = fft(trace) # type: ignore[misc, arg-type]
|
|
225
|
-
if len(mags) > 0:
|
|
226
|
-
peak_idx = int(np.argmax(mags))
|
|
227
|
-
peak_freq = freqs[peak_idx]
|
|
228
|
-
else:
|
|
229
|
-
peak_freq = 0.0
|
|
230
|
-
thd_val = thd(trace) # type: ignore[arg-type]
|
|
231
|
-
result.update(
|
|
232
|
-
{
|
|
233
|
-
"peak_frequency": f"{peak_freq / 1e6:.3f} MHz",
|
|
234
|
-
"thd": f"{thd_val:.1f} dB" if not np.isnan(thd_val) else "N/A",
|
|
235
|
-
}
|
|
236
|
-
)
|
|
176
|
+
if analysis_type == "characterize":
|
|
177
|
+
rt = rise_time(trace) # type: ignore[arg-type]
|
|
178
|
+
ft = fall_time(trace) # type: ignore[arg-type]
|
|
179
|
+
result.update(
|
|
180
|
+
{
|
|
181
|
+
"rise_time": f"{rt * 1e9:.2f} ns" if not np.isnan(rt) else "N/A",
|
|
182
|
+
"fall_time": f"{ft * 1e9:.2f} ns" if not np.isnan(ft) else "N/A",
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
elif analysis_type == "decode":
|
|
186
|
+
detected = detect_protocol(trace) # type: ignore[arg-type]
|
|
187
|
+
result.update(
|
|
188
|
+
{
|
|
189
|
+
"protocol": detected.get("protocol", "unknown"),
|
|
190
|
+
"confidence": f"{detected.get('confidence', 0) * 100:.0f}%",
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
elif analysis_type == "spectrum":
|
|
194
|
+
freqs, mags = fft(trace) # type: ignore[misc, arg-type]
|
|
195
|
+
if len(mags) > 0:
|
|
196
|
+
peak_idx = int(np.argmax(mags))
|
|
197
|
+
peak_freq = freqs[peak_idx]
|
|
198
|
+
else:
|
|
199
|
+
peak_freq = 0.0
|
|
200
|
+
thd_val = thd(trace) # type: ignore[arg-type]
|
|
201
|
+
result.update(
|
|
202
|
+
{
|
|
203
|
+
"peak_frequency": f"{peak_freq / 1e6:.3f} MHz",
|
|
204
|
+
"thd": f"{thd_val:.1f} dB" if not np.isnan(thd_val) else "N/A",
|
|
205
|
+
}
|
|
206
|
+
)
|
|
237
207
|
|
|
238
|
-
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _process_parallel(
|
|
212
|
+
files: list[str],
|
|
213
|
+
analysis_type: str,
|
|
214
|
+
parallel: int,
|
|
215
|
+
continue_on_error: bool,
|
|
216
|
+
verbose: int,
|
|
217
|
+
) -> list[dict[str, Any]]:
|
|
218
|
+
"""Process files in parallel using ThreadPoolExecutor."""
|
|
219
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
239
220
|
|
|
240
221
|
results: list[dict[str, Any]] = []
|
|
241
222
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
for i, future in enumerate(as_completed(future_to_file), 1):
|
|
248
|
-
file_path = future_to_file[future]
|
|
249
|
-
if verbose:
|
|
250
|
-
logger.info(f"[{i}/{len(files)}] Completed {Path(file_path).name}")
|
|
251
|
-
|
|
252
|
-
try:
|
|
253
|
-
result = future.result()
|
|
254
|
-
results.append(result)
|
|
255
|
-
except Exception as e:
|
|
256
|
-
logger.error(f"Failed to process {file_path}: {e}")
|
|
257
|
-
if continue_on_error:
|
|
258
|
-
results.append(
|
|
259
|
-
{
|
|
260
|
-
"file": str(Path(file_path).name),
|
|
261
|
-
"status": "error",
|
|
262
|
-
"error": str(e),
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
else:
|
|
266
|
-
raise
|
|
267
|
-
else:
|
|
268
|
-
# Sequential processing
|
|
269
|
-
for i, file_path in enumerate(files, 1):
|
|
223
|
+
with ThreadPoolExecutor(max_workers=parallel) as executor:
|
|
224
|
+
future_to_file = {executor.submit(_analyze_single_file, f, analysis_type): f for f in files}
|
|
225
|
+
|
|
226
|
+
for i, future in enumerate(as_completed(future_to_file), 1):
|
|
227
|
+
file_path = future_to_file[future]
|
|
270
228
|
if verbose:
|
|
271
|
-
logger.info(f"[{i}/{len(files)}]
|
|
229
|
+
logger.info(f"[{i}/{len(files)}] Completed {Path(file_path).name}")
|
|
272
230
|
|
|
273
231
|
try:
|
|
274
|
-
result =
|
|
232
|
+
result = future.result()
|
|
275
233
|
results.append(result)
|
|
276
234
|
except Exception as e:
|
|
277
235
|
logger.error(f"Failed to process {file_path}: {e}")
|
|
@@ -289,6 +247,68 @@ def _perform_batch_analysis(
|
|
|
289
247
|
return results
|
|
290
248
|
|
|
291
249
|
|
|
250
|
+
def _process_sequential(
|
|
251
|
+
files: list[str],
|
|
252
|
+
analysis_type: str,
|
|
253
|
+
continue_on_error: bool,
|
|
254
|
+
verbose: int,
|
|
255
|
+
) -> list[dict[str, Any]]:
|
|
256
|
+
"""Process files sequentially."""
|
|
257
|
+
results: list[dict[str, Any]] = []
|
|
258
|
+
|
|
259
|
+
for i, file_path in enumerate(files, 1):
|
|
260
|
+
if verbose:
|
|
261
|
+
logger.info(f"[{i}/{len(files)}] Processing {file_path}")
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
result = _analyze_single_file(file_path, analysis_type)
|
|
265
|
+
results.append(result)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error(f"Failed to process {file_path}: {e}")
|
|
268
|
+
if continue_on_error:
|
|
269
|
+
results.append(
|
|
270
|
+
{
|
|
271
|
+
"file": str(Path(file_path).name),
|
|
272
|
+
"status": "error",
|
|
273
|
+
"error": str(e),
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
raise
|
|
278
|
+
|
|
279
|
+
return results
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _perform_batch_analysis(
|
|
283
|
+
files: list[str],
|
|
284
|
+
analysis_type: str,
|
|
285
|
+
parallel: int,
|
|
286
|
+
continue_on_error: bool,
|
|
287
|
+
verbose: int,
|
|
288
|
+
) -> list[dict[str, Any]]:
|
|
289
|
+
"""Perform batch analysis on multiple files.
|
|
290
|
+
|
|
291
|
+
Uses concurrent.futures for parallel processing when parallel > 1.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
files: List of file paths to process.
|
|
295
|
+
analysis_type: Type of analysis to perform.
|
|
296
|
+
parallel: Number of parallel workers.
|
|
297
|
+
continue_on_error: Whether to continue on errors.
|
|
298
|
+
verbose: Verbosity level.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
List of result dictionaries, one per file.
|
|
302
|
+
|
|
303
|
+
Raises:
|
|
304
|
+
Exception: If analysis fails and continue_on_error is False.
|
|
305
|
+
"""
|
|
306
|
+
if parallel > 1:
|
|
307
|
+
return _process_parallel(files, analysis_type, parallel, continue_on_error, verbose)
|
|
308
|
+
else:
|
|
309
|
+
return _process_sequential(files, analysis_type, continue_on_error, verbose)
|
|
310
|
+
|
|
311
|
+
|
|
292
312
|
def _generate_summary(results: list[dict[str, Any]]) -> dict[str, Any]:
|
|
293
313
|
"""Generate summary statistics from batch results.
|
|
294
314
|
|
oscura/cli/benchmark.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Oscura Benchmark Command - Performance Benchmarking.
|
|
2
|
+
|
|
3
|
+
Provides CLI for performance benchmarking of analysis operations.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
$ oscura benchmark --operations all
|
|
8
|
+
$ oscura benchmark --operations decode --protocol uart
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("oscura.cli.benchmark")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
@click.option(
|
|
24
|
+
"--operations",
|
|
25
|
+
type=click.Choice(["all", "load", "decode", "fft", "measurements"], case_sensitive=False),
|
|
26
|
+
default="all",
|
|
27
|
+
help="Operations to benchmark.",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--protocol",
|
|
31
|
+
type=str,
|
|
32
|
+
default="uart",
|
|
33
|
+
help="Protocol for decode benchmark.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--iterations",
|
|
37
|
+
type=int,
|
|
38
|
+
default=10,
|
|
39
|
+
help="Number of iterations per benchmark.",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--output",
|
|
43
|
+
type=click.Choice(["json", "table"], case_sensitive=False),
|
|
44
|
+
default="table",
|
|
45
|
+
help="Output format.",
|
|
46
|
+
)
|
|
47
|
+
@click.pass_context
|
|
48
|
+
def benchmark(
|
|
49
|
+
ctx: click.Context,
|
|
50
|
+
operations: str,
|
|
51
|
+
protocol: str,
|
|
52
|
+
iterations: int,
|
|
53
|
+
output: str,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Run performance benchmarks.
|
|
56
|
+
|
|
57
|
+
Measures performance of core operations like loading, decoding,
|
|
58
|
+
FFT, and measurements.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
ctx: Click context object.
|
|
62
|
+
operations: Operations to benchmark.
|
|
63
|
+
protocol: Protocol for decode benchmark.
|
|
64
|
+
iterations: Number of iterations.
|
|
65
|
+
output: Output format.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
|
|
69
|
+
\b
|
|
70
|
+
# Benchmark all operations
|
|
71
|
+
$ oscura benchmark --operations all
|
|
72
|
+
|
|
73
|
+
\b
|
|
74
|
+
# Benchmark specific operation
|
|
75
|
+
$ oscura benchmark --operations decode --protocol uart --iterations 100
|
|
76
|
+
"""
|
|
77
|
+
verbose = ctx.obj.get("verbose", 0)
|
|
78
|
+
|
|
79
|
+
if verbose:
|
|
80
|
+
logger.info(f"Running benchmark: {operations}")
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
results: dict[str, Any] = {"iterations": iterations, "benchmarks": {}}
|
|
84
|
+
|
|
85
|
+
# Generate test data
|
|
86
|
+
test_data = _generate_test_data()
|
|
87
|
+
|
|
88
|
+
# Run benchmarks
|
|
89
|
+
if operations in ["all", "load"]:
|
|
90
|
+
results["benchmarks"]["load"] = _benchmark_load(test_data, iterations)
|
|
91
|
+
|
|
92
|
+
if operations in ["all", "decode"]:
|
|
93
|
+
results["benchmarks"]["decode"] = _benchmark_decode(test_data, protocol, iterations)
|
|
94
|
+
|
|
95
|
+
if operations in ["all", "fft"]:
|
|
96
|
+
results["benchmarks"]["fft"] = _benchmark_fft(test_data, iterations)
|
|
97
|
+
|
|
98
|
+
if operations in ["all", "measurements"]:
|
|
99
|
+
results["benchmarks"]["measurements"] = _benchmark_measurements(test_data, iterations)
|
|
100
|
+
|
|
101
|
+
# Output results
|
|
102
|
+
if output == "json":
|
|
103
|
+
import json
|
|
104
|
+
|
|
105
|
+
click.echo(json.dumps(results, indent=2))
|
|
106
|
+
else:
|
|
107
|
+
_print_table(results)
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"Benchmark failed: {e}")
|
|
111
|
+
if verbose > 1:
|
|
112
|
+
raise
|
|
113
|
+
click.echo(f"Error: {e}", err=True)
|
|
114
|
+
ctx.exit(1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _generate_test_data() -> Any:
|
|
118
|
+
"""Generate test data for benchmarking.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Test waveform trace.
|
|
122
|
+
"""
|
|
123
|
+
import numpy as np
|
|
124
|
+
|
|
125
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
126
|
+
|
|
127
|
+
# Generate 100k sample waveform
|
|
128
|
+
samples = 100000
|
|
129
|
+
sample_rate = 1e6
|
|
130
|
+
t = np.arange(samples) / sample_rate
|
|
131
|
+
|
|
132
|
+
# Mix of sine waves
|
|
133
|
+
data = np.sin(2 * np.pi * 1000 * t) + 0.5 * np.sin(2 * np.pi * 5000 * t)
|
|
134
|
+
|
|
135
|
+
return WaveformTrace(data=data, metadata=TraceMetadata(sample_rate=sample_rate))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _benchmark_load(test_data: Any, iterations: int) -> dict[str, Any]:
|
|
139
|
+
"""Benchmark data loading.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
test_data: Test data.
|
|
143
|
+
iterations: Number of iterations.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Benchmark results.
|
|
147
|
+
"""
|
|
148
|
+
import tempfile
|
|
149
|
+
from pathlib import Path
|
|
150
|
+
|
|
151
|
+
import numpy as np
|
|
152
|
+
|
|
153
|
+
from oscura.loaders import load
|
|
154
|
+
|
|
155
|
+
# Save test data to temp file (use .npz which is supported)
|
|
156
|
+
with tempfile.NamedTemporaryFile(suffix=".npz", delete=False) as f:
|
|
157
|
+
temp_path = Path(f.name)
|
|
158
|
+
np.savez(temp_path, data=test_data.data)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
start = time.time()
|
|
162
|
+
for _ in range(iterations):
|
|
163
|
+
_ = load(str(temp_path))
|
|
164
|
+
elapsed = time.time() - start
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"total_time": f"{elapsed:.3f}s",
|
|
168
|
+
"avg_time": f"{elapsed / iterations * 1000:.2f}ms",
|
|
169
|
+
"throughput": f"{iterations / elapsed:.1f} ops/sec",
|
|
170
|
+
}
|
|
171
|
+
finally:
|
|
172
|
+
temp_path.unlink()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _benchmark_decode(test_data: Any, protocol: str, iterations: int) -> dict[str, Any]:
|
|
176
|
+
"""Benchmark protocol decoding.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
test_data: Test data.
|
|
180
|
+
protocol: Protocol name.
|
|
181
|
+
iterations: Number of iterations.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Benchmark results.
|
|
185
|
+
"""
|
|
186
|
+
import numpy as np
|
|
187
|
+
|
|
188
|
+
from oscura.core.types import DigitalTrace
|
|
189
|
+
|
|
190
|
+
# Convert to digital
|
|
191
|
+
threshold = np.mean(test_data.data)
|
|
192
|
+
digital = test_data.data > threshold
|
|
193
|
+
digital_trace = DigitalTrace(data=digital, metadata=test_data.metadata)
|
|
194
|
+
|
|
195
|
+
start = time.time()
|
|
196
|
+
for _ in range(iterations):
|
|
197
|
+
if protocol.lower() == "uart":
|
|
198
|
+
from oscura.analyzers.protocols.uart import UARTDecoder
|
|
199
|
+
|
|
200
|
+
decoder = UARTDecoder(baudrate=9600)
|
|
201
|
+
_ = list(decoder.decode(digital_trace))
|
|
202
|
+
|
|
203
|
+
elapsed = time.time() - start
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
"protocol": protocol,
|
|
207
|
+
"total_time": f"{elapsed:.3f}s",
|
|
208
|
+
"avg_time": f"{elapsed / iterations * 1000:.2f}ms",
|
|
209
|
+
"throughput": f"{iterations / elapsed:.1f} ops/sec",
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _benchmark_fft(test_data: Any, iterations: int) -> dict[str, Any]:
|
|
214
|
+
"""Benchmark FFT computation.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
test_data: Test data.
|
|
218
|
+
iterations: Number of iterations.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Benchmark results.
|
|
222
|
+
"""
|
|
223
|
+
from oscura.analyzers.waveform.spectral import fft
|
|
224
|
+
|
|
225
|
+
start = time.time()
|
|
226
|
+
for _ in range(iterations):
|
|
227
|
+
_ = fft(test_data)
|
|
228
|
+
elapsed = time.time() - start
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"total_time": f"{elapsed:.3f}s",
|
|
232
|
+
"avg_time": f"{elapsed / iterations * 1000:.2f}ms",
|
|
233
|
+
"throughput": f"{iterations / elapsed:.1f} ops/sec",
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _benchmark_measurements(test_data: Any, iterations: int) -> dict[str, Any]:
|
|
238
|
+
"""Benchmark waveform measurements.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
test_data: Test data.
|
|
242
|
+
iterations: Number of iterations.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Benchmark results.
|
|
246
|
+
"""
|
|
247
|
+
from oscura.analyzers.waveform.measurements import fall_time, rise_time
|
|
248
|
+
|
|
249
|
+
start = time.time()
|
|
250
|
+
for _ in range(iterations):
|
|
251
|
+
_ = rise_time(test_data)
|
|
252
|
+
_ = fall_time(test_data)
|
|
253
|
+
elapsed = time.time() - start
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
"total_time": f"{elapsed:.3f}s",
|
|
257
|
+
"avg_time": f"{elapsed / iterations * 1000:.2f}ms",
|
|
258
|
+
"throughput": f"{iterations / elapsed:.1f} ops/sec",
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _print_table(results: dict[str, Any]) -> None:
|
|
263
|
+
"""Print results as table.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
results: Benchmark results.
|
|
267
|
+
"""
|
|
268
|
+
click.echo("\n=== Benchmark Results ===\n")
|
|
269
|
+
click.echo(f"Iterations: {results['iterations']}\n")
|
|
270
|
+
|
|
271
|
+
for name, bench in results["benchmarks"].items():
|
|
272
|
+
click.echo(f"{name.upper()}:")
|
|
273
|
+
for key, value in bench.items():
|
|
274
|
+
click.echo(f" {key}: {value}")
|
|
275
|
+
click.echo()
|