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
@@ -0,0 +1,661 @@
1
+ """Comprehensive performance profiling for analysis workflows.
2
+
3
+ This module provides detailed profiling capabilities including CPU usage,
4
+ memory consumption, I/O operations, and bottleneck identification using
5
+ multiple profiling backends (cProfile, line_profiler, memory_profiler).
6
+
7
+ Example:
8
+ >>> profiler = PerformanceProfiler()
9
+ >>> profiler.start()
10
+ >>> # Run analysis code here
11
+ >>> result = profiler.stop()
12
+ >>> print(result.summary())
13
+ >>> result.export_json("profile.json")
14
+
15
+ References:
16
+ Python Profilers: https://docs.python.org/3/library/profile.html
17
+ IEEE 1685-2009: IP-XACT standard for profiling
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import cProfile
23
+ import functools
24
+ import io
25
+ import json
26
+ import logging
27
+ import pstats
28
+ import sys
29
+ import time
30
+ import tracemalloc
31
+ from contextlib import contextmanager
32
+ from dataclasses import asdict, dataclass, field
33
+ from enum import Enum
34
+ from pathlib import Path
35
+ from typing import TYPE_CHECKING, Any, TypeAlias
36
+
37
+ if TYPE_CHECKING:
38
+ from collections.abc import Callable, Iterator
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ __all__ = [
43
+ "FunctionStats",
44
+ "PerformanceProfiler",
45
+ "ProfilingMode",
46
+ "ProfilingResult",
47
+ ]
48
+
49
+ # Type aliases
50
+ HotspotDict: TypeAlias = dict[str, Any]
51
+ CallGraphDict: TypeAlias = dict[str, list[str]]
52
+
53
+
54
+ class ProfilingMode(Enum):
55
+ """Profiling mode selection.
56
+
57
+ Attributes:
58
+ FUNCTION: Function-level profiling (time per function, call counts)
59
+ LINE: Line-level profiling (time per line of code)
60
+ MEMORY: Memory profiling (allocation tracking, memory leaks)
61
+ IO: I/O profiling (disk reads/writes, network traffic)
62
+ FULL: All profiling modes combined
63
+ """
64
+
65
+ FUNCTION = "function"
66
+ LINE = "line"
67
+ MEMORY = "memory"
68
+ IO = "io"
69
+ FULL = "full"
70
+
71
+
72
+ @dataclass
73
+ class FunctionStats:
74
+ """Statistics for a profiled function.
75
+
76
+ Attributes:
77
+ name: Function name
78
+ calls: Number of times called
79
+ time: Total time spent in function (seconds)
80
+ cumulative_time: Cumulative time including sub-calls (seconds)
81
+ memory: Peak memory usage (bytes)
82
+ per_call_time: Average time per call (seconds)
83
+ per_call_memory: Average memory per call (bytes)
84
+ filename: Source file containing function
85
+ lineno: Line number where function is defined
86
+ """
87
+
88
+ name: str
89
+ calls: int
90
+ time: float
91
+ cumulative_time: float
92
+ memory: int = 0
93
+ per_call_time: float = 0.0
94
+ per_call_memory: int = 0
95
+ filename: str = ""
96
+ lineno: int = 0
97
+
98
+ def __post_init__(self) -> None:
99
+ """Calculate derived stats."""
100
+ if self.calls > 0:
101
+ self.per_call_time = self.time / self.calls
102
+ if self.memory > 0:
103
+ self.per_call_memory = self.memory // self.calls
104
+
105
+
106
+ @dataclass
107
+ class ProfilingResult:
108
+ """Complete profiling results.
109
+
110
+ Attributes:
111
+ function_stats: Statistics for all profiled functions
112
+ hotspots: Performance bottlenecks (top N slowest functions)
113
+ memory_stats: Memory usage statistics
114
+ call_graph: Function call hierarchy
115
+ total_time: Total execution time (seconds)
116
+ peak_memory: Peak memory usage (bytes)
117
+ mode: Profiling mode used
118
+ metadata: Additional metadata (timestamp, environment, etc.)
119
+ """
120
+
121
+ function_stats: dict[str, FunctionStats]
122
+ hotspots: list[HotspotDict]
123
+ memory_stats: dict[str, Any]
124
+ call_graph: CallGraphDict
125
+ total_time: float
126
+ peak_memory: int
127
+ mode: ProfilingMode
128
+ metadata: dict[str, Any] = field(default_factory=dict)
129
+
130
+ def summary(self) -> str:
131
+ """Generate text summary of profiling results.
132
+
133
+ Returns:
134
+ Human-readable summary string
135
+ """
136
+ lines = [
137
+ "=" * 80,
138
+ "Performance Profiling Report",
139
+ "=" * 80,
140
+ f"Mode: {self.mode.value}",
141
+ f"Total Time: {self.total_time:.4f}s",
142
+ f"Peak Memory: {self._format_bytes(self.peak_memory)}",
143
+ f"Functions Profiled: {len(self.function_stats)}",
144
+ "",
145
+ "Top 10 Hotspots (by cumulative time):",
146
+ "-" * 80,
147
+ ]
148
+
149
+ # Add hotspots
150
+ for i, hotspot in enumerate(self.hotspots[:10], 1):
151
+ func_name = hotspot["function"]
152
+ cum_time = hotspot["cumulative_time"]
153
+ calls = hotspot["calls"]
154
+ pct = hotspot["percent_time"]
155
+
156
+ lines.append(f"{i:2d}. {func_name:50s} {cum_time:8.4f}s ({calls:6d} calls) {pct:5.1f}%")
157
+
158
+ if self.mode == ProfilingMode.MEMORY or self.mode == ProfilingMode.FULL:
159
+ lines.extend(
160
+ [
161
+ "",
162
+ "Memory Statistics:",
163
+ "-" * 80,
164
+ f" Peak Usage: {self._format_bytes(self.memory_stats.get('peak', 0))}",
165
+ f" Current Usage: {self._format_bytes(self.memory_stats.get('current', 0))}",
166
+ f" Allocations: {self.memory_stats.get('allocations', 0):,}",
167
+ ]
168
+ )
169
+
170
+ lines.append("=" * 80)
171
+ return "\n".join(lines)
172
+
173
+ def export_json(self, filepath: str | Path) -> None:
174
+ """Export profiling results to JSON file.
175
+
176
+ Args:
177
+ filepath: Output file path
178
+ """
179
+ filepath = Path(filepath)
180
+
181
+ # Convert to serializable format
182
+ data = {
183
+ "function_stats": {name: asdict(stats) for name, stats in self.function_stats.items()},
184
+ "hotspots": self.hotspots,
185
+ "memory_stats": self.memory_stats,
186
+ "call_graph": self.call_graph,
187
+ "total_time": self.total_time,
188
+ "peak_memory": self.peak_memory,
189
+ "mode": self.mode.value,
190
+ "metadata": self.metadata,
191
+ }
192
+
193
+ with filepath.open("w") as f:
194
+ json.dump(data, f, indent=2)
195
+
196
+ logger.info(f"Profiling results exported to {filepath}")
197
+
198
+ def export_html(self, filepath: str | Path) -> None:
199
+ """Export profiling results to HTML file with flame graph.
200
+
201
+ Args:
202
+ filepath: Output file path
203
+ """
204
+ filepath = Path(filepath)
205
+
206
+ html = self._generate_html()
207
+
208
+ with filepath.open("w") as f:
209
+ f.write(html)
210
+
211
+ logger.info(f"HTML report exported to {filepath}")
212
+
213
+ def export_text(self, filepath: str | Path) -> None:
214
+ """Export profiling results to text file.
215
+
216
+ Args:
217
+ filepath: Output file path
218
+ """
219
+ filepath = Path(filepath)
220
+
221
+ with filepath.open("w") as f:
222
+ f.write(self.summary())
223
+
224
+ logger.info(f"Text report exported to {filepath}")
225
+
226
+ def _format_bytes(self, bytes_value: int) -> str:
227
+ """Format bytes as human-readable string.
228
+
229
+ Args:
230
+ bytes_value: Number of bytes
231
+
232
+ Returns:
233
+ Formatted string (e.g., "10.5 MB")
234
+ """
235
+ if bytes_value == 0:
236
+ return "0 B"
237
+
238
+ units = ["B", "KB", "MB", "GB", "TB"]
239
+ unit_index = 0
240
+ value = float(bytes_value)
241
+
242
+ while value >= 1024.0 and unit_index < len(units) - 1:
243
+ value /= 1024.0
244
+ unit_index += 1
245
+
246
+ return f"{value:.2f} {units[unit_index]}"
247
+
248
+ def _generate_html(self) -> str:
249
+ """Generate HTML report with styling.
250
+
251
+ Returns:
252
+ HTML string
253
+ """
254
+ html = f"""<!DOCTYPE html>
255
+ <html>
256
+ <head>
257
+ <title>Performance Profiling Report</title>
258
+ <style>
259
+ body {{ font-family: 'Courier New', monospace; margin: 20px; background: #1e1e1e; color: #d4d4d4; }}
260
+ h1 {{ color: #4ec9b0; }}
261
+ h2 {{ color: #569cd6; margin-top: 30px; }}
262
+ table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
263
+ th {{ background: #2d2d30; color: #4ec9b0; padding: 10px; text-align: left; }}
264
+ td {{ padding: 8px; border-bottom: 1px solid #3e3e42; }}
265
+ .hotspot {{ background: #3c2929; }}
266
+ .stats {{ margin: 10px 0; padding: 10px; background: #2d2d30; border-radius: 5px; }}
267
+ .metric {{ display: inline-block; margin: 10px 20px 10px 0; }}
268
+ .value {{ color: #ce9178; font-weight: bold; }}
269
+ </style>
270
+ </head>
271
+ <body>
272
+ <h1>Performance Profiling Report</h1>
273
+
274
+ <div class="stats">
275
+ <div class="metric">Mode: <span class="value">{self.mode.value}</span></div>
276
+ <div class="metric">Total Time: <span class="value">{self.total_time:.4f}s</span></div>
277
+ <div class="metric">Peak Memory: <span class="value">{self._format_bytes(self.peak_memory)}</span></div>
278
+ <div class="metric">Functions: <span class="value">{len(self.function_stats)}</span></div>
279
+ </div>
280
+
281
+ <h2>Top Hotspots</h2>
282
+ <table>
283
+ <tr>
284
+ <th>Rank</th>
285
+ <th>Function</th>
286
+ <th>Cumulative Time</th>
287
+ <th>Calls</th>
288
+ <th>% of Total</th>
289
+ </tr>
290
+ """
291
+
292
+ for i, hotspot in enumerate(self.hotspots[:20], 1):
293
+ css_class = "hotspot" if i <= 5 else ""
294
+ html += f""" <tr class="{css_class}">
295
+ <td>{i}</td>
296
+ <td>{hotspot["function"]}</td>
297
+ <td>{hotspot["cumulative_time"]:.4f}s</td>
298
+ <td>{hotspot["calls"]}</td>
299
+ <td>{hotspot["percent_time"]:.1f}%</td>
300
+ </tr>
301
+ """
302
+
303
+ html += """ </table>
304
+ </body>
305
+ </html>
306
+ """
307
+ return html
308
+
309
+
310
+ class PerformanceProfiler:
311
+ """Comprehensive performance profiler for analysis workflows.
312
+
313
+ Profiles CPU usage, memory consumption, I/O operations, and identifies
314
+ performance bottlenecks using multiple profiling backends.
315
+
316
+ Example:
317
+ Basic usage::
318
+
319
+ profiler = PerformanceProfiler()
320
+ profiler.start()
321
+ # Run analysis code
322
+ result = profiler.stop()
323
+ print(result.summary())
324
+
325
+ Context manager::
326
+
327
+ with PerformanceProfiler() as profiler:
328
+ # Run analysis code
329
+ pass
330
+ result = profiler.get_results()
331
+
332
+ Decorator::
333
+
334
+ @PerformanceProfiler.profile_function()
335
+ def my_analysis_function(data):
336
+ # Analysis code
337
+ pass
338
+
339
+ References:
340
+ Python cProfile: https://docs.python.org/3/library/profile.html
341
+ tracemalloc: https://docs.python.org/3/library/tracemalloc.html
342
+ """
343
+
344
+ def __init__(self, mode: ProfilingMode = ProfilingMode.FUNCTION) -> None:
345
+ """Initialize profiler.
346
+
347
+ Args:
348
+ mode: Profiling mode (FUNCTION, LINE, MEMORY, IO, FULL)
349
+ """
350
+ self.mode = mode
351
+ self._profiler: cProfile.Profile | None = None
352
+ self._start_time: float = 0.0
353
+ self._end_time: float = 0.0
354
+ self._memory_start: tuple[int, int] = (0, 0)
355
+ self._memory_peak: int = 0
356
+ self._result: ProfilingResult | None = None
357
+ self._is_running: bool = False
358
+
359
+ # Check optional dependencies
360
+ self._line_profiler_available = self._check_line_profiler()
361
+ self._memory_profiler_available = self._check_memory_profiler()
362
+
363
+ def _check_line_profiler(self) -> bool:
364
+ """Check if line_profiler is available.
365
+
366
+ Returns:
367
+ True if line_profiler is installed
368
+ """
369
+ try:
370
+ import line_profiler # type: ignore[import-not-found] # noqa: F401
371
+
372
+ return True
373
+ except ImportError:
374
+ if self.mode == ProfilingMode.LINE:
375
+ logger.warning(
376
+ "line_profiler not installed. Install with: pip install line_profiler"
377
+ )
378
+ return False
379
+
380
+ def _check_memory_profiler(self) -> bool:
381
+ """Check if memory_profiler is available.
382
+
383
+ Returns:
384
+ True if memory_profiler is installed
385
+ """
386
+ try:
387
+ import memory_profiler # type: ignore[import-not-found] # noqa: F401
388
+
389
+ return True
390
+ except ImportError:
391
+ if self.mode == ProfilingMode.MEMORY:
392
+ logger.info("memory_profiler not installed (optional). Using tracemalloc instead.")
393
+ return False
394
+
395
+ def start(self) -> None:
396
+ """Start profiling."""
397
+ if self._is_running:
398
+ logger.warning("Profiler is already running")
399
+ return
400
+
401
+ self._start_time = time.perf_counter()
402
+ self._is_running = True
403
+
404
+ # Start appropriate profiling backend
405
+ if self.mode in (ProfilingMode.FUNCTION, ProfilingMode.FULL):
406
+ self._profiler = cProfile.Profile()
407
+ try:
408
+ self._profiler.enable()
409
+ except ValueError as e:
410
+ # Handle nested profiler case
411
+ if "Another profiling tool is already active" in str(e):
412
+ logger.warning(
413
+ "Another profiler is active. This profiler will not collect detailed statistics."
414
+ )
415
+ self._profiler = None
416
+ else:
417
+ raise
418
+
419
+ if self.mode in (ProfilingMode.MEMORY, ProfilingMode.FULL):
420
+ if not tracemalloc.is_tracing():
421
+ tracemalloc.start()
422
+ self._memory_start = tracemalloc.get_traced_memory()
423
+
424
+ logger.info(f"Profiling started in {self.mode.value} mode")
425
+
426
+ def stop(self) -> ProfilingResult:
427
+ """Stop profiling and generate results.
428
+
429
+ Returns:
430
+ Profiling results
431
+
432
+ Raises:
433
+ RuntimeError: If profiler is not running
434
+ """
435
+ if not self._is_running:
436
+ raise RuntimeError("Profiler is not running. Call start() first.")
437
+
438
+ self._end_time = time.perf_counter()
439
+ total_time = self._end_time - self._start_time
440
+
441
+ # Stop profiling backends
442
+ if self._profiler is not None:
443
+ self._profiler.disable()
444
+
445
+ memory_stats: dict[str, Any] = {}
446
+ if self.mode in (ProfilingMode.MEMORY, ProfilingMode.FULL):
447
+ current, peak = tracemalloc.get_traced_memory()
448
+ self._memory_peak = peak
449
+ memory_stats = {
450
+ "current": current,
451
+ "peak": peak,
452
+ "allocations": tracemalloc.get_tracemalloc_memory(),
453
+ }
454
+ tracemalloc.stop()
455
+
456
+ # Extract statistics
457
+ function_stats = self._extract_function_stats()
458
+ hotspots = self._identify_hotspots(function_stats, total_time)
459
+ call_graph = self._build_call_graph()
460
+
461
+ # Create result
462
+ self._result = ProfilingResult(
463
+ function_stats=function_stats,
464
+ hotspots=hotspots,
465
+ memory_stats=memory_stats,
466
+ call_graph=call_graph,
467
+ total_time=total_time,
468
+ peak_memory=self._memory_peak,
469
+ mode=self.mode,
470
+ metadata={
471
+ "python_version": sys.version,
472
+ "platform": sys.platform,
473
+ "timestamp": time.time(),
474
+ },
475
+ )
476
+
477
+ self._is_running = False
478
+ logger.info(f"Profiling stopped. Total time: {total_time:.4f}s")
479
+
480
+ return self._result
481
+
482
+ def get_results(self) -> ProfilingResult | None:
483
+ """Get profiling results without stopping.
484
+
485
+ Returns:
486
+ Profiling results or None if not available
487
+ """
488
+ return self._result
489
+
490
+ @contextmanager
491
+ def profile(self) -> Iterator[PerformanceProfiler]:
492
+ """Context manager for profiling code blocks.
493
+
494
+ Yields:
495
+ PerformanceProfiler instance
496
+
497
+ Example:
498
+ >>> with PerformanceProfiler() as profiler:
499
+ ... # Code to profile
500
+ ... pass
501
+ >>> result = profiler.get_results()
502
+ """
503
+ self.start()
504
+ try:
505
+ yield self
506
+ finally:
507
+ self.stop()
508
+
509
+ def __enter__(self) -> PerformanceProfiler:
510
+ """Enter context manager."""
511
+ self.start()
512
+ return self
513
+
514
+ def __exit__(self, exc_type: type, exc_val: Exception, exc_tb: Any) -> None:
515
+ """Exit context manager."""
516
+ if self._is_running:
517
+ self.stop()
518
+
519
+ @staticmethod
520
+ def profile_function(
521
+ mode: ProfilingMode = ProfilingMode.FUNCTION,
522
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
523
+ """Decorator for profiling individual functions.
524
+
525
+ Args:
526
+ mode: Profiling mode
527
+
528
+ Returns:
529
+ Decorator function
530
+
531
+ Example:
532
+ >>> @PerformanceProfiler.profile_function()
533
+ >>> def my_function(x):
534
+ ... return x * 2
535
+ """
536
+
537
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
538
+ @functools.wraps(func)
539
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
540
+ profiler = PerformanceProfiler(mode=mode)
541
+ profiler.start()
542
+ try:
543
+ result = func(*args, **kwargs)
544
+ return result
545
+ finally:
546
+ profiling_result = profiler.stop()
547
+ logger.info(
548
+ f"\nProfiling results for {func.__name__}:\n{profiling_result.summary()}"
549
+ )
550
+
551
+ return wrapper
552
+
553
+ return decorator
554
+
555
+ def _extract_function_stats(self) -> dict[str, FunctionStats]:
556
+ """Extract function statistics from profiler.
557
+
558
+ Returns:
559
+ Dictionary mapping function names to their statistics
560
+ """
561
+ if self._profiler is None:
562
+ return {}
563
+
564
+ # Get statistics from cProfile
565
+ stats_stream = io.StringIO()
566
+ stats = pstats.Stats(self._profiler, stream=stats_stream)
567
+ stats.sort_stats(pstats.SortKey.CUMULATIVE)
568
+
569
+ function_stats: dict[str, FunctionStats] = {}
570
+
571
+ for func_key, (_cc, nc, tt, ct, _callers) in stats.stats.items(): # type: ignore[attr-defined]
572
+ filename, lineno, func_name = func_key
573
+
574
+ # Create readable function name
575
+ if filename == "~":
576
+ full_name = func_name
577
+ else:
578
+ full_name = f"{Path(filename).name}:{func_name}"
579
+
580
+ function_stats[full_name] = FunctionStats(
581
+ name=full_name,
582
+ calls=nc,
583
+ time=tt,
584
+ cumulative_time=ct,
585
+ filename=filename,
586
+ lineno=lineno,
587
+ )
588
+
589
+ return function_stats
590
+
591
+ def _identify_hotspots(
592
+ self, function_stats: dict[str, FunctionStats], total_time: float
593
+ ) -> list[HotspotDict]:
594
+ """Identify performance hotspots.
595
+
596
+ Args:
597
+ function_stats: Function statistics
598
+ total_time: Total execution time
599
+
600
+ Returns:
601
+ List of hotspot dictionaries sorted by cumulative time
602
+ """
603
+ hotspots: list[HotspotDict] = []
604
+
605
+ for func_name, stats in function_stats.items():
606
+ percent_time = (stats.cumulative_time / total_time * 100.0) if total_time > 0 else 0.0
607
+
608
+ hotspots.append(
609
+ {
610
+ "function": func_name,
611
+ "calls": stats.calls,
612
+ "time": stats.time,
613
+ "cumulative_time": stats.cumulative_time,
614
+ "percent_time": percent_time,
615
+ "per_call_time": stats.per_call_time,
616
+ }
617
+ )
618
+
619
+ # Sort by cumulative time (descending)
620
+ hotspots.sort(key=lambda x: x["cumulative_time"], reverse=True)
621
+
622
+ return hotspots
623
+
624
+ def _build_call_graph(self) -> CallGraphDict:
625
+ """Build function call graph.
626
+
627
+ Returns:
628
+ Dictionary mapping functions to list of called functions
629
+ """
630
+ if self._profiler is None:
631
+ return {}
632
+
633
+ call_graph: CallGraphDict = {}
634
+
635
+ stats = pstats.Stats(self._profiler)
636
+
637
+ for func_key in stats.stats: # type: ignore[attr-defined]
638
+ filename, lineno, func_name = func_key
639
+
640
+ # Create readable function name
641
+ if filename == "~":
642
+ full_name = func_name
643
+ else:
644
+ full_name = f"{Path(filename).name}:{func_name}"
645
+
646
+ # Get callees (functions called by this function)
647
+ # Note: all_callees may not be available in all Python versions
648
+ callees: list[str] = []
649
+ all_callees = getattr(stats, "all_callees", None)
650
+ if all_callees is not None and func_key in all_callees:
651
+ for callee_key in all_callees[func_key]:
652
+ callee_filename, callee_lineno, callee_name = callee_key
653
+ if callee_filename == "~":
654
+ callee_full = callee_name
655
+ else:
656
+ callee_full = f"{Path(callee_filename).name}:{callee_name}"
657
+ callees.append(callee_full)
658
+
659
+ call_graph[full_name] = callees
660
+
661
+ return call_graph
@@ -10,7 +10,7 @@ from abc import ABC, abstractmethod
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
12
  if TYPE_CHECKING:
13
- from ..core.types import WaveformTrace
13
+ from oscura.core.types import WaveformTrace
14
14
 
15
15
 
16
16
  class TraceTransformer(ABC):
@@ -8,7 +8,7 @@ from collections.abc import Callable
8
8
  from functools import reduce, wraps
9
9
  from typing import Any, TypeVar
10
10
 
11
- from ..core.types import WaveformTrace
11
+ from oscura.core.types import WaveformTrace
12
12
 
13
13
  # Type variables for generic composition
14
14
  T = TypeVar("T")
@@ -7,7 +7,7 @@ The ParallelPipeline maintains full API compatibility with the standard Pipeline
7
7
  while providing linear speedup for independent transformations.
8
8
 
9
9
  Example:
10
- >>> from oscura.pipeline import ParallelPipeline
10
+ >>> from oscura.utils.pipeline import ParallelPipeline
11
11
  >>> # Create pipeline with independent branches
12
12
  >>> pipeline = ParallelPipeline([
13
13
  ... ('filter1', LowPassFilter(cutoff=1e6)),
@@ -38,7 +38,8 @@ from .pipeline import Pipeline
38
38
  if TYPE_CHECKING:
39
39
  from collections.abc import Sequence
40
40
 
41
- from ..core.types import WaveformTrace
41
+ from oscura.core.types import WaveformTrace
42
+
42
43
  from .base import TraceTransformer
43
44
 
44
45
 
@@ -13,7 +13,7 @@ from .base import TraceTransformer
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Sequence
15
15
 
16
- from ..core.types import WaveformTrace
16
+ from oscura.core.types import WaveformTrace
17
17
 
18
18
 
19
19
  class Pipeline(TraceTransformer):