oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""Packet metrics for stream analysis.
|
|
2
|
+
|
|
3
|
+
This module provides throughput, jitter, and loss rate metrics
|
|
4
|
+
for packet stream analysis.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.packet.metrics import throughput, jitter, loss_rate
|
|
9
|
+
>>> rate = throughput(packets)
|
|
10
|
+
>>> jitter_stats = jitter(packets)
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
RFC 3550 for jitter calculation
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Iterator, Sequence
|
|
25
|
+
|
|
26
|
+
from numpy.typing import NDArray
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class PacketInfo:
|
|
31
|
+
"""Packet information for metrics calculation.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
timestamp: Packet arrival timestamp in seconds.
|
|
35
|
+
size: Packet size in bytes.
|
|
36
|
+
sequence: Optional sequence number for loss detection.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
timestamp: float
|
|
40
|
+
size: int
|
|
41
|
+
sequence: int | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ThroughputResult:
|
|
46
|
+
"""Throughput measurement result.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
bytes_per_second: Data rate in bytes/second.
|
|
50
|
+
bits_per_second: Data rate in bits/second.
|
|
51
|
+
packets_per_second: Packet rate.
|
|
52
|
+
total_bytes: Total bytes in measurement period.
|
|
53
|
+
total_packets: Total packets in measurement period.
|
|
54
|
+
duration: Measurement duration in seconds.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
bytes_per_second: float
|
|
58
|
+
bits_per_second: float
|
|
59
|
+
packets_per_second: float
|
|
60
|
+
total_bytes: int
|
|
61
|
+
total_packets: int
|
|
62
|
+
duration: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class JitterResult:
|
|
67
|
+
"""Jitter measurement result.
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
mean: Mean inter-arrival time.
|
|
71
|
+
std: Standard deviation of inter-arrival time.
|
|
72
|
+
min: Minimum inter-arrival time.
|
|
73
|
+
max: Maximum inter-arrival time.
|
|
74
|
+
jitter_rfc3550: RFC 3550 jitter estimate.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
mean: float
|
|
78
|
+
std: float
|
|
79
|
+
min: float
|
|
80
|
+
max: float
|
|
81
|
+
jitter_rfc3550: float
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class LossResult:
|
|
86
|
+
"""Packet loss measurement result.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
loss_rate: Loss rate as fraction (0-1).
|
|
90
|
+
loss_percentage: Loss rate as percentage.
|
|
91
|
+
packets_lost: Estimated number of lost packets.
|
|
92
|
+
packets_received: Number of received packets.
|
|
93
|
+
gaps: List of (start_seq, end_seq) gap ranges.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
loss_rate: float
|
|
97
|
+
loss_percentage: float
|
|
98
|
+
packets_lost: int
|
|
99
|
+
packets_received: int
|
|
100
|
+
gaps: list[tuple[int, int]]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class LatencyResult:
|
|
105
|
+
"""Request-response latency result.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
mean: Mean latency in seconds.
|
|
109
|
+
std: Standard deviation.
|
|
110
|
+
min: Minimum latency.
|
|
111
|
+
max: Maximum latency.
|
|
112
|
+
p50: Median latency.
|
|
113
|
+
p95: 95th percentile latency.
|
|
114
|
+
p99: 99th percentile latency.
|
|
115
|
+
samples: Number of samples.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
mean: float
|
|
119
|
+
std: float
|
|
120
|
+
min: float
|
|
121
|
+
max: float
|
|
122
|
+
p50: float
|
|
123
|
+
p95: float
|
|
124
|
+
p99: float
|
|
125
|
+
samples: int
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def throughput(
|
|
129
|
+
packets: Sequence[PacketInfo] | Iterator[PacketInfo],
|
|
130
|
+
*,
|
|
131
|
+
window_size: float | None = None,
|
|
132
|
+
) -> ThroughputResult:
|
|
133
|
+
"""Calculate throughput and packet rate.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
packets: Sequence or iterator of packets.
|
|
137
|
+
window_size: If provided, use sliding window of this duration.
|
|
138
|
+
If None, calculate over entire sequence.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
ThroughputResult with throughput metrics.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> packets = [PacketInfo(t, sz) for t, sz in data]
|
|
145
|
+
>>> result = throughput(packets)
|
|
146
|
+
>>> print(f"Throughput: {result.bits_per_second / 1e6:.2f} Mbps")
|
|
147
|
+
"""
|
|
148
|
+
packet_list = list(packets)
|
|
149
|
+
|
|
150
|
+
if len(packet_list) < 2:
|
|
151
|
+
return ThroughputResult(
|
|
152
|
+
bytes_per_second=0.0,
|
|
153
|
+
bits_per_second=0.0,
|
|
154
|
+
packets_per_second=0.0,
|
|
155
|
+
total_bytes=sum(p.size for p in packet_list),
|
|
156
|
+
total_packets=len(packet_list),
|
|
157
|
+
duration=0.0,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
total_bytes = sum(p.size for p in packet_list)
|
|
161
|
+
total_packets = len(packet_list)
|
|
162
|
+
|
|
163
|
+
# Sort by timestamp
|
|
164
|
+
sorted_packets = sorted(packet_list, key=lambda p: p.timestamp)
|
|
165
|
+
duration = sorted_packets[-1].timestamp - sorted_packets[0].timestamp
|
|
166
|
+
|
|
167
|
+
if duration <= 0:
|
|
168
|
+
duration = 1e-9 # Avoid division by zero
|
|
169
|
+
|
|
170
|
+
bytes_per_second = total_bytes / duration
|
|
171
|
+
packets_per_second = total_packets / duration
|
|
172
|
+
|
|
173
|
+
return ThroughputResult(
|
|
174
|
+
bytes_per_second=bytes_per_second,
|
|
175
|
+
bits_per_second=bytes_per_second * 8,
|
|
176
|
+
packets_per_second=packets_per_second,
|
|
177
|
+
total_bytes=total_bytes,
|
|
178
|
+
total_packets=total_packets,
|
|
179
|
+
duration=duration,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def jitter(
|
|
184
|
+
packets: Sequence[PacketInfo] | Iterator[PacketInfo],
|
|
185
|
+
) -> JitterResult:
|
|
186
|
+
"""Calculate inter-arrival time jitter.
|
|
187
|
+
|
|
188
|
+
Computes jitter statistics including RFC 3550 jitter estimate.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
packets: Sequence or iterator of packets with timestamps.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
JitterResult with jitter metrics.
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> result = jitter(packets)
|
|
198
|
+
>>> print(f"Jitter: {result.std * 1000:.3f} ms")
|
|
199
|
+
|
|
200
|
+
References:
|
|
201
|
+
RFC 3550 Section A.8 for jitter calculation
|
|
202
|
+
"""
|
|
203
|
+
packet_list = list(packets)
|
|
204
|
+
|
|
205
|
+
if len(packet_list) < 2:
|
|
206
|
+
return JitterResult(
|
|
207
|
+
mean=0.0,
|
|
208
|
+
std=0.0,
|
|
209
|
+
min=0.0,
|
|
210
|
+
max=0.0,
|
|
211
|
+
jitter_rfc3550=0.0,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Sort by timestamp
|
|
215
|
+
sorted_packets = sorted(packet_list, key=lambda p: p.timestamp)
|
|
216
|
+
|
|
217
|
+
# Calculate inter-arrival times
|
|
218
|
+
timestamps = np.array([p.timestamp for p in sorted_packets])
|
|
219
|
+
iat = np.diff(timestamps)
|
|
220
|
+
|
|
221
|
+
# RFC 3550 jitter estimate (smoothed absolute deviation)
|
|
222
|
+
# J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16
|
|
223
|
+
# where D(i-1,i) is the difference in inter-arrival times
|
|
224
|
+
if len(iat) > 1:
|
|
225
|
+
d = np.diff(iat) # Deviation from expected IAT
|
|
226
|
+
jitter_rfc = 0.0
|
|
227
|
+
for deviation in np.abs(d):
|
|
228
|
+
jitter_rfc = jitter_rfc + (deviation - jitter_rfc) / 16
|
|
229
|
+
else:
|
|
230
|
+
jitter_rfc = 0.0
|
|
231
|
+
|
|
232
|
+
return JitterResult(
|
|
233
|
+
mean=float(np.mean(iat)),
|
|
234
|
+
std=float(np.std(iat)),
|
|
235
|
+
min=float(np.min(iat)),
|
|
236
|
+
max=float(np.max(iat)),
|
|
237
|
+
jitter_rfc3550=float(jitter_rfc),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def loss_rate(
|
|
242
|
+
packets: Sequence[PacketInfo] | Iterator[PacketInfo],
|
|
243
|
+
) -> LossResult:
|
|
244
|
+
"""Detect and report packet loss from sequence numbers.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
packets: Sequence or iterator of packets with sequence numbers.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
LossResult with loss metrics.
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> result = loss_rate(packets)
|
|
254
|
+
>>> print(f"Loss rate: {result.loss_percentage:.2f}%")
|
|
255
|
+
>>> for start, end in result.gaps:
|
|
256
|
+
... print(f"Gap: {start} to {end}")
|
|
257
|
+
"""
|
|
258
|
+
packet_list = list(packets)
|
|
259
|
+
|
|
260
|
+
# Filter packets with sequence numbers
|
|
261
|
+
with_seq = [(p.sequence, p.timestamp) for p in packet_list if p.sequence is not None]
|
|
262
|
+
|
|
263
|
+
if len(with_seq) < 2:
|
|
264
|
+
return LossResult(
|
|
265
|
+
loss_rate=0.0,
|
|
266
|
+
loss_percentage=0.0,
|
|
267
|
+
packets_lost=0,
|
|
268
|
+
packets_received=len(packet_list),
|
|
269
|
+
gaps=[],
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Sort by sequence number
|
|
273
|
+
sorted_seqs = sorted(with_seq, key=lambda x: x[0])
|
|
274
|
+
sequences = [s[0] for s in sorted_seqs]
|
|
275
|
+
|
|
276
|
+
# Find gaps in sequence
|
|
277
|
+
gaps: list[tuple[int, int]] = []
|
|
278
|
+
packets_lost = 0
|
|
279
|
+
|
|
280
|
+
for i in range(1, len(sequences)):
|
|
281
|
+
expected = sequences[i - 1] + 1
|
|
282
|
+
actual = sequences[i]
|
|
283
|
+
|
|
284
|
+
if actual > expected:
|
|
285
|
+
# Gap detected
|
|
286
|
+
gaps.append((expected, actual - 1))
|
|
287
|
+
packets_lost += actual - expected
|
|
288
|
+
|
|
289
|
+
# Calculate loss rate
|
|
290
|
+
total_expected = sequences[-1] - sequences[0] + 1
|
|
291
|
+
packets_received = len(sequences)
|
|
292
|
+
|
|
293
|
+
loss_frac = packets_lost / total_expected if total_expected > 0 else 0.0
|
|
294
|
+
|
|
295
|
+
return LossResult(
|
|
296
|
+
loss_rate=loss_frac,
|
|
297
|
+
loss_percentage=loss_frac * 100,
|
|
298
|
+
packets_lost=packets_lost,
|
|
299
|
+
packets_received=packets_received,
|
|
300
|
+
gaps=gaps,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def latency(
|
|
305
|
+
request_times: Sequence[float] | NDArray[np.floating[Any]],
|
|
306
|
+
response_times: Sequence[float] | NDArray[np.floating[Any]],
|
|
307
|
+
) -> LatencyResult:
|
|
308
|
+
"""Calculate request-response latency statistics.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
request_times: Array of request timestamps.
|
|
312
|
+
response_times: Array of corresponding response timestamps.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
LatencyResult with latency statistics.
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
ValueError: If request and response arrays have different lengths.
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
>>> result = latency(request_times, response_times)
|
|
322
|
+
>>> print(f"Mean latency: {result.mean * 1000:.2f} ms")
|
|
323
|
+
>>> print(f"P99 latency: {result.p99 * 1000:.2f} ms")
|
|
324
|
+
"""
|
|
325
|
+
req = np.asarray(request_times)
|
|
326
|
+
resp = np.asarray(response_times)
|
|
327
|
+
|
|
328
|
+
if len(req) != len(resp):
|
|
329
|
+
raise ValueError("Request and response arrays must have same length")
|
|
330
|
+
|
|
331
|
+
if len(req) == 0:
|
|
332
|
+
return LatencyResult(
|
|
333
|
+
mean=0.0,
|
|
334
|
+
std=0.0,
|
|
335
|
+
min=0.0,
|
|
336
|
+
max=0.0,
|
|
337
|
+
p50=0.0,
|
|
338
|
+
p95=0.0,
|
|
339
|
+
p99=0.0,
|
|
340
|
+
samples=0,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
latencies = resp - req
|
|
344
|
+
|
|
345
|
+
# Filter out negative latencies (invalid pairings)
|
|
346
|
+
valid = latencies >= 0
|
|
347
|
+
latencies = latencies[valid]
|
|
348
|
+
|
|
349
|
+
if len(latencies) == 0:
|
|
350
|
+
return LatencyResult(
|
|
351
|
+
mean=0.0,
|
|
352
|
+
std=0.0,
|
|
353
|
+
min=0.0,
|
|
354
|
+
max=0.0,
|
|
355
|
+
p50=0.0,
|
|
356
|
+
p95=0.0,
|
|
357
|
+
p99=0.0,
|
|
358
|
+
samples=0,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return LatencyResult(
|
|
362
|
+
mean=float(np.mean(latencies)),
|
|
363
|
+
std=float(np.std(latencies)),
|
|
364
|
+
min=float(np.min(latencies)),
|
|
365
|
+
max=float(np.max(latencies)),
|
|
366
|
+
p50=float(np.percentile(latencies, 50)),
|
|
367
|
+
p95=float(np.percentile(latencies, 95)),
|
|
368
|
+
p99=float(np.percentile(latencies, 99)),
|
|
369
|
+
samples=len(latencies),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def windowed_throughput(
|
|
374
|
+
packets: Sequence[PacketInfo],
|
|
375
|
+
window_size: float,
|
|
376
|
+
step_size: float | None = None,
|
|
377
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
378
|
+
"""Calculate throughput over sliding windows.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
packets: Sequence of packets.
|
|
382
|
+
window_size: Window size in seconds.
|
|
383
|
+
step_size: Step size in seconds (default: window_size / 2).
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
(times, throughputs) - Center times and throughput values.
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
>>> times, rates = windowed_throughput(packets, window_size=1.0)
|
|
390
|
+
>>> plt.plot(times, rates / 1e6)
|
|
391
|
+
>>> plt.ylabel("Throughput (Mbps)")
|
|
392
|
+
"""
|
|
393
|
+
if step_size is None:
|
|
394
|
+
step_size = window_size / 2
|
|
395
|
+
|
|
396
|
+
packet_list = sorted(packets, key=lambda p: p.timestamp)
|
|
397
|
+
|
|
398
|
+
if len(packet_list) < 2:
|
|
399
|
+
return np.array([]), np.array([])
|
|
400
|
+
|
|
401
|
+
start_time = packet_list[0].timestamp
|
|
402
|
+
end_time = packet_list[-1].timestamp
|
|
403
|
+
|
|
404
|
+
times = []
|
|
405
|
+
throughputs = []
|
|
406
|
+
|
|
407
|
+
window_start = start_time
|
|
408
|
+
|
|
409
|
+
while window_start + window_size <= end_time:
|
|
410
|
+
window_end = window_start + window_size
|
|
411
|
+
|
|
412
|
+
# Count bytes in window
|
|
413
|
+
window_bytes = sum(p.size for p in packet_list if window_start <= p.timestamp < window_end)
|
|
414
|
+
|
|
415
|
+
center_time = window_start + window_size / 2
|
|
416
|
+
rate = window_bytes / window_size * 8 # bits/second
|
|
417
|
+
|
|
418
|
+
times.append(center_time)
|
|
419
|
+
throughputs.append(rate)
|
|
420
|
+
|
|
421
|
+
window_start += step_size
|
|
422
|
+
|
|
423
|
+
return np.array(times), np.array(throughputs)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
__all__ = [
|
|
427
|
+
"JitterResult",
|
|
428
|
+
"LatencyResult",
|
|
429
|
+
"LossResult",
|
|
430
|
+
"PacketInfo",
|
|
431
|
+
"ThroughputResult",
|
|
432
|
+
"jitter",
|
|
433
|
+
"latency",
|
|
434
|
+
"loss_rate",
|
|
435
|
+
"throughput",
|
|
436
|
+
"windowed_throughput",
|
|
437
|
+
]
|