oscura 0.5.1__py3-none-any.whl → 0.7.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.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.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() # type: ignore[misc]
28
- @click.argument("pattern") # type: ignore[misc]
29
- @click.option( # type: ignore[misc]
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( # type: ignore[misc]
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( # type: ignore[misc]
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( # type: ignore[misc]
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( # type: ignore[misc]
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 # type: ignore[misc]
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) # noqa: PTH207
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 _perform_batch_analysis(
149
- files: list[str],
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
- files: List of file paths to process.
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
- List of result dictionaries, one per file.
168
-
169
- Raises:
170
- Exception: If analysis fails and continue_on_error is False.
156
+ Dictionary containing analysis results.
171
157
  """
172
- from concurrent.futures import ThreadPoolExecutor, as_completed
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
- def analyze_single_file(file_path: str) -> dict[str, Any]:
175
- """Analyze a single file and return results.
176
-
177
- Args:
178
- file_path: Path to waveform file to analyze.
179
-
180
- Returns:
181
- Dictionary containing analysis results.
182
- """
183
- import numpy as np
184
-
185
- from oscura.analyzers.waveform.measurements import fall_time, rise_time
186
- from oscura.analyzers.waveform.spectral import fft, thd
187
- from oscura.inference import detect_protocol
188
- from oscura.loaders import load
189
-
190
- # Load trace
191
- trace = load(file_path)
192
- sample_rate = trace.metadata.sample_rate
193
-
194
- # Base result
195
- result: dict[str, Any] = {
196
- "file": str(Path(file_path).name),
197
- "status": "success",
198
- "analysis_type": analysis_type,
199
- "samples": len(trace.data), # type: ignore[union-attr]
200
- "sample_rate": f"{sample_rate / 1e6:.1f} MHz",
201
- }
202
-
203
- # Add analysis-specific results
204
- if analysis_type == "characterize":
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
- return result
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
- if parallel > 1:
243
- # Parallel processing using ThreadPoolExecutor
244
- with ThreadPoolExecutor(max_workers=parallel) as executor:
245
- future_to_file = {executor.submit(analyze_single_file, f): f for f in files}
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)}] Processing {file_path}")
229
+ logger.info(f"[{i}/{len(files)}] Completed {Path(file_path).name}")
272
230
 
273
231
  try:
274
- result = analyze_single_file(file_path)
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
 
@@ -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()