rosotacom-dev 2.5.dev42__tar.gz → 2.5.dev47__tar.gz
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.
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/ffmpeg-keyframes.md +24 -5
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/README.md +1 -0
- rosotacom_dev-2.5.dev47/docs/findings/reorder-becomes-reader-loss.md +69 -0
- rosotacom_dev-2.5.dev47/docs/findings/repro/reorder_drop_repro.py +170 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/forensics-report.md +16 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/session-configuration.md +21 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/forensics.py +143 -24
- rosotacom_dev-2.5.dev47/src/rosotacom/resources/examples/dist/field-20260817.dist +513 -0
- rosotacom_dev-2.5.dev47/src/rosotacom/resources/examples/profiles.yaml +171 -0
- rosotacom_dev-2.5.dev47/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +58 -0
- rosotacom_dev-2.5.dev47/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +100 -0
- rosotacom_dev-2.5.dev47/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +83 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +8 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +35 -30
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +1 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/stream_stats.py +8 -1
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/SOURCES.txt +9 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/scm_file_list.json +9 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
- rosotacom_dev-2.5.dev47/tests/unit/test_ffmpeg_flags.py +130 -0
- rosotacom_dev-2.5.dev47/tests/unit/test_field_profiles.py +51 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_forensics.py +153 -0
- rosotacom_dev-2.5.dev47/tests/unit/test_playout_pacer.py +81 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_status_overview.py +27 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_status_overview_observer.py +106 -5
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_stream_stats.py +12 -0
- rosotacom_dev-2.5.dev42/src/rosotacom/resources/examples/profiles.yaml +0 -85
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.devcontainer/devcontainer.json.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.devcontainer/re_generate_devcontainer_json.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/CODEOWNERS +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/actions/setup-just/action.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/dependabot.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/benchmark-calibrate.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/benchmark-gate.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/dev-release.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/image-scan.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/nightly-e2e.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/pr-lightweight.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/pr-merge-gate.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.github/workflows/release.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/.pre-commit-config.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/AGENTS.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/CONCURRENCY.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/CONTRIBUTING.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/DEVELOPMENT_PRINCIPLES.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/MANIFEST.in +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/README.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/budgets.jsonl +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/README.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/capacity-size-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-nominal-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-tight-fastdds.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/calibration/probe-loss-tight-zenoh.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/codecov.yml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/deployment-configuration.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/benchmark-ab.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/benchmark-bag-as-load.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/ci.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/delay-alone-no-loss.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/head-of-line-irregularity.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/lighter-message-loss.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/geo-link-map.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/github-repository-settings.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/link-bandwidth.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/link-trace.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/owner-runbook.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/performance-bands.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/playbooks/README.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/quality-model.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.1.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.2.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.3.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release-notes/v2.4.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/release.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0002-expectation-concepts.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0003-metric-backbone.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0004-network-profiles.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0006-dynamic-qos.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/0007-regression-gate.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/rfcs/README.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/testing.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/docs/work-items.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/install.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/justfile +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/control_center.png +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/echo_test.png +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/forensics-report-example.png +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/host.png +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/media/vehicle.png +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/pyproject.toml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/requirements.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/__init__.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/__main__.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/anonymize.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/benched_set.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/benchmark.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/cli.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/deployment.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/geomap.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/image_cache.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/plots.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/py.typed +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/benched-set.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/.gitignore +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/README.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/__init__.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ros2docker.json.example +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/status_eval.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/transit.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom/video_quality.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/requires.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/terminology.md +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_benched_set_registry.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_findings.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_markdown_links.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_ota_source_bundle.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_package_metadata.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_packaged_resources.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_profile_covers_generated_commands.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_pytest_policy.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_readme_examples.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_security_maintenance_config.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_session_configuration_doc.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_session_template_substitution.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/contract/test_workflow_contracts.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/conftest.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_anonymize_e2e.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_benchmark_ab.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_benchmark_capacity.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_benchmark_replay.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_parallel_smoke.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_smoke.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_timeline_stepping.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/e2e/test_video_quality_e2e.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/generate_rmw_matrix.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_anonymize.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_benched_set.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_catmux_pane_logging.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_cli.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_concurrency.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_deployment.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_geomap.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_image_cache.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_link_trace.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_plots.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_qos_fallback_logging.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_relay_optional_parameters.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_session_pipeline.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_status_eval.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_transit.py +0 -0
- {rosotacom_dev-2.5.dev42 → rosotacom_dev-2.5.dev47}/tests/unit/test_video_quality.py +0 -0
|
@@ -45,6 +45,18 @@ Grouping a stream into GOPs is then just splitting at keyframes. Note the
|
|
|
45
45
|
encoder may emit keyframes *early* (scene cuts), so GOPs can be shorter than
|
|
46
46
|
`gop_size` — assert spacing `<= gop_size`, not `== gop_size`.
|
|
47
47
|
|
|
48
|
+
## Transit records carry the flag
|
|
49
|
+
|
|
50
|
+
The receiving status overview applies the same parse live: when an inbound
|
|
51
|
+
`OtaStamped` envelope's `msg_type` names an FFMPEGPacket, it walks the wrapped
|
|
52
|
+
CDR bytes to `flags` (`com_py/ffmpeg_flags.py`, a dependency-free mirror of the
|
|
53
|
+
host parser) and writes `keyframe: true/false` on every delivered RFC 0003
|
|
54
|
+
transit row in `events.jsonl`. Synthesized lost rows have no payload and carry
|
|
55
|
+
no such field; a payload the walk cannot parse simply omits it. Downstream,
|
|
56
|
+
`rosotacom report` annotates a stream from these real flags whenever they cover
|
|
57
|
+
more than 90% of its delivered rows (provenance `ffmpeg_flags (transit
|
|
58
|
+
records)`), and only recordings without the field fall back to size bimodality.
|
|
59
|
+
|
|
48
60
|
## Automated stream-stage stats
|
|
49
61
|
|
|
50
62
|
`rosotacom stream-stats` applies the same packet parser and GOP grouping to
|
|
@@ -69,8 +81,9 @@ Inputs are explicit and repeatable:
|
|
|
69
81
|
rosbag2 storage backends fall back to `rosbag2_py` in a ROS environment.
|
|
70
82
|
- `--events LABEL=PATH:/topic` reads delivered RFC 0003 transit rows from
|
|
71
83
|
`events.jsonl`, using receiver-side `t_com_in` timestamps and `size_bytes`.
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
Rows carrying the per-message `keyframe` field annotate GOPs from the real
|
|
85
|
+
flags; older recordings without it use the documented size-bimodality
|
|
86
|
+
fallback when the keyframe share looks like a real GOP.
|
|
74
87
|
|
|
75
88
|
The Markdown output starts with the comparison table:
|
|
76
89
|
|
|
@@ -105,9 +118,9 @@ averages 43,723 B, while positions 1-4 average 3,738 B, 4,512 B, 4,834 B, and
|
|
|
105
118
|
|
|
106
119
|
## Fallback: size bimodality
|
|
107
120
|
|
|
108
|
-
If `flags` is unavailable (foreign recordings,
|
|
109
|
-
|
|
110
|
-
classes: delta frames dominate the stream (a GOP of N contributes N−1), so
|
|
121
|
+
If `flags` is unavailable (foreign recordings, bags anonymized before flag
|
|
122
|
+
preservation existed, or `events.jsonl` written before transit rows carried
|
|
123
|
+
`keyframe`), the size distribution still separates the two classes: delta frames dominate the stream (a GOP of N contributes N−1), so
|
|
111
124
|
the median frame size sits on the delta mode, while keyframes are typically
|
|
112
125
|
5–100× larger. `rosotacom.ffmpeg_packet.keyframes_by_size` marks frames
|
|
113
126
|
above `3 × median` as keyframes. This misclassifies only when delta frames
|
|
@@ -119,6 +132,12 @@ scene at a very low bitrate) — prefer `flags` whenever present.
|
|
|
119
132
|
- `tests/unit/test_ffmpeg_packet.py` — CDR parsing (alignment across
|
|
120
133
|
odd-length strings), flag semantics, and the size fallback on a synthetic
|
|
121
134
|
GOP pattern.
|
|
135
|
+
- `tests/unit/test_ffmpeg_flags.py` — the receiver-side keyframe-bit walk in
|
|
136
|
+
`com_py/ffmpeg_flags.py`: agreement with the host parser on hand-built
|
|
137
|
+
packets, and `None` (never an exception) on truncated or foreign bytes.
|
|
138
|
+
- `tests/unit/test_forensics.py` — `rosotacom report` prefers transit-row
|
|
139
|
+
`keyframe` flags over size bimodality, and falls back below the coverage
|
|
140
|
+
gate.
|
|
122
141
|
- `tests/unit/test_stream_stats.py` — exact stream statistics, per-GOP-position
|
|
123
142
|
tables from hand-built CDR packets, events-row source loading, comparison
|
|
124
143
|
table shape, and CLI output writing.
|
|
@@ -28,6 +28,7 @@ living verification hook.
|
|
|
28
28
|
|
|
29
29
|
## Findings
|
|
30
30
|
|
|
31
|
+
- [Best-effort reordering becomes reader-side loss, independent of history depth](reorder-becomes-reader-loss.md) - under pure delay jitter a Cyclone best-effort reader discards every overtaken sample (57% at 100 Hz under 50+-45 ms), delivery stays strictly monotonic, and depth 50 loses the same as depth 1.
|
|
31
32
|
- [Jitter causes loss while bandwidth shortage builds latency](jitter-loss-bandwidth-latency.md) - 18 KB at 20 Hz separates jitter-induced loss from bandwidth-induced queueing.
|
|
32
33
|
- [Lighter alternating messages can lose more than steady messages](lighter-message-loss.md) - a 1x18KB+1x0KB pattern lost under a tight emulated profile while the steady 18 KB comparison did not.
|
|
33
34
|
- [Delay alone was not the loss boundary for the 18 KB at 20 Hz probe](delay-alone-no-loss.md) - pure delay profiles showed no material loss, and the 300 ms delay-only probe delivered all messages.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Best-Effort Reordering Becomes Reader-Side Loss, Independent Of History Depth
|
|
2
|
+
|
|
3
|
+
## Claim
|
|
4
|
+
|
|
5
|
+
Under delay jitter large enough to reorder packets — with ZERO configured
|
|
6
|
+
loss — a CycloneDDS best-effort reader discards every overtaken sample to
|
|
7
|
+
preserve per-writer order: delivery is strictly monotonic, the discarded
|
|
8
|
+
samples count as loss, and subscription history depth does not recover any of
|
|
9
|
+
it (the drop happens in the reader proxy, before history). Variance is
|
|
10
|
+
therefore a loss mechanism of its own, and it is the one QoS depth cannot
|
|
11
|
+
help against — unlike bundled-publication overwrite, where depth does help.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
- Host pair / topology: two Docker containers on one machine (the repository's
|
|
16
|
+
communication image; any image with `rclpy`, `rmw_cyclonedds_cpp` and `tc`
|
|
17
|
+
works), unicast Cyclone peers (`AllowMulticast=false`, static IPs), one
|
|
18
|
+
`std_msgs/Int64` stream at 100 Hz, publisher history depth 10, best-effort;
|
|
19
|
+
the receiver subscribes the SAME topic twice: depth 1 and depth 50. netem on
|
|
20
|
+
the publisher's egress only, delay+jitter, no loss, no rate limit.
|
|
21
|
+
- rosotacom SHA: measured at `3227daf`; re-runs record theirs.
|
|
22
|
+
- Profile: inline netem args (`delay 50ms 45ms` and `delay 50ms 20ms`), not a
|
|
23
|
+
packaged profile — the experiment is below rosotacom, at the RMW layer.
|
|
24
|
+
- Seed policy: seedless single runs (`n=1` per jitter level); the effect size
|
|
25
|
+
(tens of percent) dwarfs run-to-run noise.
|
|
26
|
+
|
|
27
|
+
## Evidence
|
|
28
|
+
|
|
29
|
+
Evidence grade: scripted container reproduction, committed next to this file.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 docs/findings/repro/reorder_drop_repro.py orchestrate --rate 100 --duration 40
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2026-08-18 run (4000 messages sent per scenario, zero configured loss):
|
|
36
|
+
|
|
37
|
+
| netem | depth 1 delivered | depth 50 delivered | inversions | loss of span |
|
|
38
|
+
|---|---|---|---|---|
|
|
39
|
+
| `delay 50ms 45ms` | 1709 | 1727 | 0 | **57%** |
|
|
40
|
+
| `delay 50ms 20ms` | 2626 | 2659 | 0 | **34%** |
|
|
41
|
+
|
|
42
|
+
Three readings, one per column group: delivery is strictly monotonic (zero
|
|
43
|
+
inversions in every subscription), depth 50 loses the same as depth 1 (the
|
|
44
|
+
0.5–1 pp difference is executor noise, not recovery), and the loss tracks the
|
|
45
|
+
overtake probability — at 100 Hz the inter-message gap is 10 ms, so jitter
|
|
46
|
+
±45 ms makes "the next message arrives first" the common case.
|
|
47
|
+
|
|
48
|
+
Field context (2026-08-17 CCNG drive): the operational 10 Hz streams have a
|
|
49
|
+
100 ms gap against ~18 ms steady jitter, so this channel is minor there
|
|
50
|
+
(neighbour-delay overtake proxy: 9.6% around steady losses vs 6.0% baseline).
|
|
51
|
+
It grows quadratically relevant as rates rise or jitter grows — any stream
|
|
52
|
+
whose inter-message gap approaches the link's jitter pays it.
|
|
53
|
+
|
|
54
|
+
Verification: manual: run the command above from a source checkout with
|
|
55
|
+
Docker (build the image via the packaged `ros2docker.json.example` or pass
|
|
56
|
+
`--image`); the script prints and writes `out/reorder_results.json`.
|
|
57
|
+
|
|
58
|
+
## Status
|
|
59
|
+
|
|
60
|
+
confirmed, 2026-08-18.
|
|
61
|
+
|
|
62
|
+
## Publication notes
|
|
63
|
+
|
|
64
|
+
This is the mechanism behind the jitter side of
|
|
65
|
+
[jitter-loss-bandwidth-latency.md](jitter-loss-bandwidth-latency.md): the
|
|
66
|
+
jitter boundary is not radio noise but the reader's monotonicity rule. For
|
|
67
|
+
papers: plot delivered share vs jitter/gap ratio, and state explicitly that
|
|
68
|
+
history depth is not a mitigation here — pacing (larger gaps), FEC, or
|
|
69
|
+
reliable QoS are.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Does jitter-induced reordering become LOSS at a CycloneDDS best-effort
|
|
3
|
+
reader, and does history depth help?
|
|
4
|
+
|
|
5
|
+
Two containers (rcd image, cyclone RMW). Publisher shapes its egress with
|
|
6
|
+
pure delay+jitter (NO configured loss, no rate limit). Receiver subscribes
|
|
7
|
+
the same topic twice: depth 1 and depth 50.
|
|
8
|
+
|
|
9
|
+
Discriminates two mechanisms:
|
|
10
|
+
- loss seen by depth-50 sub = reader-proxy drop of overtaken samples
|
|
11
|
+
(depth cannot help) (or nothing, if cyclone delivers OOO)
|
|
12
|
+
- extra loss of depth-1 sub = history overwrite from arrival bunching
|
|
13
|
+
(depth DOES help)
|
|
14
|
+
- delivered inversions = out-of-order delivery (would mean no drop)
|
|
15
|
+
|
|
16
|
+
Modes: orchestrate (host) | pub | sub (in container).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
IMG = "ros-communication:latest" # any image with rclpy + rmw_cyclonedds + tc (--image overrides)
|
|
27
|
+
NET = "claude_reorder_net"
|
|
28
|
+
TOPIC = "/reorder_probe"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def mode_pub(args):
|
|
32
|
+
import rclpy
|
|
33
|
+
from rclpy.node import Node
|
|
34
|
+
from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy
|
|
35
|
+
from std_msgs.msg import Int64
|
|
36
|
+
|
|
37
|
+
rclpy.init()
|
|
38
|
+
node = Node("reorder_pub")
|
|
39
|
+
qos = QoSProfile(reliability=ReliabilityPolicy.BEST_EFFORT, history=HistoryPolicy.KEEP_LAST, depth=10)
|
|
40
|
+
pub = node.create_publisher(Int64, TOPIC, qos)
|
|
41
|
+
time.sleep(5.0) # discovery
|
|
42
|
+
n = int(args.rate * args.duration)
|
|
43
|
+
period = 1.0 / args.rate
|
|
44
|
+
t0 = time.monotonic()
|
|
45
|
+
for i in range(n):
|
|
46
|
+
while time.monotonic() < t0 + i * period:
|
|
47
|
+
time.sleep(0.0005)
|
|
48
|
+
m = Int64()
|
|
49
|
+
m.data = i
|
|
50
|
+
pub.publish(m)
|
|
51
|
+
time.sleep(3.0)
|
|
52
|
+
print(f"published {n}")
|
|
53
|
+
node.destroy_node()
|
|
54
|
+
rclpy.shutdown()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def mode_sub(args):
|
|
58
|
+
import rclpy
|
|
59
|
+
from rclpy.node import Node
|
|
60
|
+
from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy
|
|
61
|
+
from std_msgs.msg import Int64
|
|
62
|
+
|
|
63
|
+
rclpy.init()
|
|
64
|
+
node = Node("reorder_sub")
|
|
65
|
+
got = {1: [], 50: []}
|
|
66
|
+
|
|
67
|
+
def make_cb(depth):
|
|
68
|
+
def cb(msg):
|
|
69
|
+
got[depth].append(msg.data)
|
|
70
|
+
|
|
71
|
+
return cb
|
|
72
|
+
|
|
73
|
+
subs = []
|
|
74
|
+
for depth in (1, 50):
|
|
75
|
+
qos = QoSProfile(reliability=ReliabilityPolicy.BEST_EFFORT, history=HistoryPolicy.KEEP_LAST, depth=depth)
|
|
76
|
+
subs.append(node.create_subscription(Int64, TOPIC, make_cb(depth), qos))
|
|
77
|
+
t_end = time.monotonic() + args.duration + 20
|
|
78
|
+
while time.monotonic() < t_end:
|
|
79
|
+
rclpy.spin_once(node, timeout_sec=0.05)
|
|
80
|
+
out = {}
|
|
81
|
+
for depth, seqs in got.items():
|
|
82
|
+
inv = sum(1 for a, b in zip(seqs, seqs[1:], strict=False) if b < a)
|
|
83
|
+
out[f"depth{depth}"] = {
|
|
84
|
+
"received": len(seqs),
|
|
85
|
+
"unique": len(set(seqs)),
|
|
86
|
+
"inversions": inv,
|
|
87
|
+
"first": seqs[0] if seqs else None,
|
|
88
|
+
"last": max(seqs) if seqs else None,
|
|
89
|
+
}
|
|
90
|
+
Path(args.out).write_text(json.dumps(out, indent=2))
|
|
91
|
+
print(json.dumps(out))
|
|
92
|
+
node.destroy_node()
|
|
93
|
+
rclpy.shutdown()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run(cmd):
|
|
97
|
+
r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
98
|
+
if r.returncode != 0:
|
|
99
|
+
raise RuntimeError(f"{cmd}\n{r.stderr or r.stdout}")
|
|
100
|
+
return r.stdout
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def mode_orchestrate(args):
|
|
104
|
+
global IMG
|
|
105
|
+
IMG = args.image
|
|
106
|
+
me = Path(__file__).resolve()
|
|
107
|
+
outdir = me.parent / "out"
|
|
108
|
+
scenarios = [
|
|
109
|
+
("jitter45", "tc qdisc replace dev eth0 root netem delay 50ms 45ms"),
|
|
110
|
+
("jitter20", "tc qdisc replace dev eth0 root netem delay 50ms 20ms"),
|
|
111
|
+
]
|
|
112
|
+
ip_sub, ip_pub = "10.97.0.2", "10.97.0.3"
|
|
113
|
+
ddsuri = (
|
|
114
|
+
"<CycloneDDS><Domain><General><AllowMulticast>false</AllowMulticast>"
|
|
115
|
+
'</General><Discovery><Peers><Peer address=\\"' + ip_sub + '\\"/>'
|
|
116
|
+
'<Peer address=\\"' + ip_pub + '\\"/></Peers>'
|
|
117
|
+
"<ParticipantIndex>auto</ParticipantIndex></Discovery></Domain></CycloneDDS>"
|
|
118
|
+
)
|
|
119
|
+
subprocess.run(f"docker network rm {NET}", shell=True, capture_output=True)
|
|
120
|
+
run(f"docker network create --subnet 10.97.0.0/24 {NET}")
|
|
121
|
+
results = {}
|
|
122
|
+
try:
|
|
123
|
+
for name, netem in scenarios:
|
|
124
|
+
subprocess.run("docker rm -f claude_reorder_pub claude_reorder_sub", shell=True, capture_output=True)
|
|
125
|
+
env = f'-e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp -e ROS_DOMAIN_ID=77 -e CYCLONEDDS_URI="{ddsuri}"'
|
|
126
|
+
run(
|
|
127
|
+
f"docker run -d --name claude_reorder_sub --network {NET} --ip {ip_sub} {env} "
|
|
128
|
+
f"-v {me.parent}:/w -v {outdir}:/out {IMG} bash -lc "
|
|
129
|
+
f"'python3 /w/{me.name} sub --duration {args.duration} "
|
|
130
|
+
f"--out /out/reorder_{name}.json'"
|
|
131
|
+
)
|
|
132
|
+
time.sleep(2)
|
|
133
|
+
run(
|
|
134
|
+
f"docker run -d --name claude_reorder_pub --network {NET} --ip {ip_pub} "
|
|
135
|
+
f"--cap-add NET_ADMIN {env} -v {me.parent}:/w {IMG} bash -lc "
|
|
136
|
+
f"'{netem} && python3 /w/{me.name} pub --rate {args.rate} "
|
|
137
|
+
f"--duration {args.duration}'"
|
|
138
|
+
)
|
|
139
|
+
run("docker wait claude_reorder_pub")
|
|
140
|
+
run("docker wait claude_reorder_sub")
|
|
141
|
+
data = json.loads((outdir / f"reorder_{name}.json").read_text())
|
|
142
|
+
sent = int(args.rate * args.duration)
|
|
143
|
+
for d in ("depth1", "depth50"):
|
|
144
|
+
span = (data[d]["last"] or 0) - (data[d]["first"] or 0) + 1
|
|
145
|
+
data[d]["loss_pct_of_span"] = round(100 * (1 - data[d]["unique"] / max(span, 1)), 2)
|
|
146
|
+
data["sent"] = sent
|
|
147
|
+
results[name] = data
|
|
148
|
+
print(f"== {name}: {json.dumps(data)}")
|
|
149
|
+
finally:
|
|
150
|
+
subprocess.run("docker rm -f claude_reorder_pub claude_reorder_sub", shell=True, capture_output=True)
|
|
151
|
+
subprocess.run(f"docker network rm {NET}", shell=True, capture_output=True)
|
|
152
|
+
(outdir / "reorder_results.json").write_text(json.dumps(results, indent=2))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def main():
|
|
156
|
+
ap = argparse.ArgumentParser()
|
|
157
|
+
sub = ap.add_subparsers(dest="mode", required=True)
|
|
158
|
+
for m in ("orchestrate", "pub", "sub"):
|
|
159
|
+
p = sub.add_parser(m)
|
|
160
|
+
p.add_argument("--rate", type=float, default=100.0)
|
|
161
|
+
p.add_argument("--duration", type=float, default=40.0)
|
|
162
|
+
p.add_argument("--image", default=IMG)
|
|
163
|
+
if m == "sub":
|
|
164
|
+
p.add_argument("--out", required=True)
|
|
165
|
+
args = ap.parse_args()
|
|
166
|
+
{"pub": mode_pub, "sub": mode_sub, "orchestrate": mode_orchestrate}[args.mode](args)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
sys.exit(main() or 0)
|
|
@@ -77,6 +77,22 @@ missing tail after the last record is not judged either — without a sender-sid
|
|
|
77
77
|
end marker it is indistinguishable from a normal shutdown (RFC 0003, honest
|
|
78
78
|
limits).
|
|
79
79
|
|
|
80
|
+
A receiver-only view also cannot tell a broken link from a source that simply
|
|
81
|
+
stopped publishing — on the 2026-08-17 CCNG drive, "0 Hz collapses" on the
|
|
82
|
+
planner visualization were the source pausing during manual interventions while
|
|
83
|
+
the link delivered everything offered. When the instance also contains the
|
|
84
|
+
sending peer's own transit records for a stream (`direction: outbound`, stage
|
|
85
|
+
`com_out`), the report bins their `t_wrap` stamps into a per-bin **offered**
|
|
86
|
+
rate on the same publish timeline and each collapse event names its culprit in
|
|
87
|
+
`details.cause`: `source` when every collapsed bin was offered below the same
|
|
88
|
+
threshold — the collapse is fully explained by the source, and the markdown
|
|
89
|
+
line reads "source paused" (0 Hz offered) or "source slowed"; `link` when at
|
|
90
|
+
least one collapsed bin was offered at/above threshold and the link still
|
|
91
|
+
delivered below it; `unknown` without sender-side records. `min_offered_hz` /
|
|
92
|
+
`max_offered_hz` accompany the verdict. Sender-side rows feed only the offered
|
|
93
|
+
rate — they never enter the delivered view, where the cross-peer join would
|
|
94
|
+
upgrade receiver-inferred lost rows to delivered.
|
|
95
|
+
|
|
80
96
|
Events across streams and kinds are grouped into **incidents** when their
|
|
81
97
|
windows touch (merge gap defaults to one bin): one degradation moment, several
|
|
82
98
|
lenses on it.
|
|
@@ -568,6 +568,27 @@ number being reported.
|
|
|
568
568
|
- `bit_rate` (int)
|
|
569
569
|
- `encoder_av_options` (string)
|
|
570
570
|
|
|
571
|
+
#### Receiver-side playout pacing (manual node)
|
|
572
|
+
|
|
573
|
+
A receiver that hands every packet to the decoder the moment it arrives turns
|
|
574
|
+
network delay jitter into display stutter. `com_py playout_pacer` re-times a
|
|
575
|
+
received packet stream to `stamp + budget` (budget adaptive above the fastest
|
|
576
|
+
observed path, clock-offset-proof; late packets pass through immediately, order
|
|
577
|
+
always preserved — the decoder chain sees every packet):
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
ros2 run com_py playout_pacer --ros-args \
|
|
581
|
+
-p topic:=/cam/image/compressed/drop1of2/ffmpeg \
|
|
582
|
+
-p target_ms:=350.0 -p adaptive:=true
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
It republishes on `<topic>/paced` plus `.../paced/budget_ms` and
|
|
586
|
+
`.../paced/queue_depth` debug topics; point the reverse republish (or any
|
|
587
|
+
decoder) at the paced name. On the 2026-08-17 CCNG field trace this removes
|
|
588
|
+
~95 % of delay-caused >200 ms display gaps at ~100 ms median added age
|
|
589
|
+
(adaptive mode). Declarative wiring as a `transport.playout` block is tracked
|
|
590
|
+
in issue #284.
|
|
591
|
+
|
|
571
592
|
`type: foxglove` supports (CompressedVideo):
|
|
572
593
|
- `gop_size` (int)
|
|
573
594
|
- `bit_rate` (int)
|
|
@@ -42,6 +42,11 @@ RATE_COLLAPSE = "rate_collapse"
|
|
|
42
42
|
KEYFRAME_SHARE_MIN = 0.02
|
|
43
43
|
KEYFRAME_SHARE_MAX = 0.4
|
|
44
44
|
|
|
45
|
+
# Real per-message FFMPEG flags win over the size heuristic once they cover
|
|
46
|
+
# most delivered rows; the slack tolerates isolated parse failures in a
|
|
47
|
+
# recording that otherwise carries the field.
|
|
48
|
+
KEYFRAME_FLAG_COVERAGE_MIN = 0.9
|
|
49
|
+
|
|
45
50
|
CAVEAT = (
|
|
46
51
|
"Context is correlation, not causation: a link-trace sample or profile step that overlaps an event "
|
|
47
52
|
"does not prove it caused the event. The causal test is reproduction under an emulated profile."
|
|
@@ -228,13 +233,27 @@ class Stream:
|
|
|
228
233
|
|
|
229
234
|
|
|
230
235
|
def _annotate_keyframes(stream: Stream, *, declared: bool) -> None:
|
|
231
|
-
"""Mark keyframes
|
|
232
|
-
|
|
233
|
-
``
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
"""Mark keyframes, preferring the real FFMPEG flags in the transit records.
|
|
237
|
+
|
|
238
|
+
The receiver parses ``AV_PKT_FLAG_KEY`` out of the wrapped FFMPEGPacket CDR
|
|
239
|
+
at com_in and writes it as ``keyframe`` on delivered rows, so when the field
|
|
240
|
+
covers (nearly) all delivered rows the real flags are authoritative — the
|
|
241
|
+
field only exists where the wrapper's ``msg_type`` named an FFMPEGPacket,
|
|
242
|
+
which is a stronger declaration than status.json, so no share gate applies.
|
|
243
|
+
|
|
244
|
+
Recordings from before the field existed fall back to
|
|
245
|
+
``rosotacom.ffmpeg_packet.keyframes_by_size``: declared FFMPEGPacket streams
|
|
246
|
+
(type from status.json) are always annotated; other streams only when the
|
|
247
|
+
flagged share looks like a real GOP structure, so uniform or spiky
|
|
248
|
+
non-video streams are not mislabeled.
|
|
237
249
|
"""
|
|
250
|
+
delivered = [record for record in stream.records if record.get("status") != "lost"]
|
|
251
|
+
flagged = [record for record in delivered if isinstance(record.get("keyframe"), bool)]
|
|
252
|
+
if delivered and len(flagged) / len(delivered) > KEYFRAME_FLAG_COVERAGE_MIN:
|
|
253
|
+
stream.keyframe_seqs = frozenset(int(record["seq"]) for record in flagged if record["keyframe"])
|
|
254
|
+
stream.keyframe_provenance = "ffmpeg_flags (transit records)"
|
|
255
|
+
return
|
|
256
|
+
|
|
238
257
|
sized = [record for record in stream.records if record.get("size_bytes") is not None]
|
|
239
258
|
if not sized:
|
|
240
259
|
return
|
|
@@ -246,7 +265,7 @@ def _annotate_keyframes(stream: Stream, *, declared: bool) -> None:
|
|
|
246
265
|
return
|
|
247
266
|
stream.keyframe_seqs = frozenset(int(record["seq"]) for record, flag in zip(sized, flags, strict=True) if flag)
|
|
248
267
|
origin = "declared FFMPEGPacket stream" if declared else f"size share {share * 100.0:.1f}%"
|
|
249
|
-
stream.keyframe_provenance = f"size_bimodality ({origin}); transit records carry no FFMPEG flags"
|
|
268
|
+
stream.keyframe_provenance = f"size_bimodality ({origin}); these transit records carry no FFMPEG flags"
|
|
250
269
|
|
|
251
270
|
|
|
252
271
|
def build_streams(records: list[dict[str, Any]], *, declared_ffmpeg_topics: Collection[str] = ()) -> list[Stream]:
|
|
@@ -342,6 +361,52 @@ def build_stream_bins(stream: Stream, *, run_start: float, bin_s: float) -> list
|
|
|
342
361
|
return bins
|
|
343
362
|
|
|
344
363
|
|
|
364
|
+
def build_offered_timelines(
|
|
365
|
+
offered_records: list[dict[str, Any]],
|
|
366
|
+
streams: list[Stream],
|
|
367
|
+
timelines: dict[str, list[dict[str, Any]]],
|
|
368
|
+
*,
|
|
369
|
+
run_start: float,
|
|
370
|
+
bin_s: float,
|
|
371
|
+
) -> dict[str, list[float]]:
|
|
372
|
+
"""Per-bin offered rate per stream, from the sending peer's own transit records.
|
|
373
|
+
|
|
374
|
+
``offered_records`` are ``direction: outbound`` rows (stage ``com_out``):
|
|
375
|
+
what the source handed to the link. Their ``t_wrap`` is the same header
|
|
376
|
+
stamp the receiving side records, so offered and delivered bins share one
|
|
377
|
+
publish timeline with no clock join. Streams without a single stamped
|
|
378
|
+
sender-side row are absent from the result — their collapse cause stays
|
|
379
|
+
unknowable.
|
|
380
|
+
"""
|
|
381
|
+
grouped: dict[tuple[str, str, str], list[float]] = {}
|
|
382
|
+
for record in join_transit_records(offered_records):
|
|
383
|
+
if record.get("t_wrap") is None:
|
|
384
|
+
continue
|
|
385
|
+
key = (
|
|
386
|
+
str(record.get("source") or ""),
|
|
387
|
+
str(record.get("target") or ""),
|
|
388
|
+
str(record.get("topic") or ""),
|
|
389
|
+
)
|
|
390
|
+
grouped.setdefault(key, []).append(float(record["t_wrap"]))
|
|
391
|
+
|
|
392
|
+
offered: dict[str, list[float]] = {}
|
|
393
|
+
for stream in streams:
|
|
394
|
+
stamps = grouped.get((stream.source, stream.target, stream.topic))
|
|
395
|
+
bins = timelines[stream.label]
|
|
396
|
+
if not stamps or not bins:
|
|
397
|
+
continue
|
|
398
|
+
# bin_start_epoch is run_start + index * bin_s by construction; round
|
|
399
|
+
# instead of floor so float error cannot shift the alignment by one.
|
|
400
|
+
first_index = round((float(bins[0]["bin_start_epoch"]) - run_start) / bin_s)
|
|
401
|
+
counts = [0] * len(bins)
|
|
402
|
+
for stamp in stamps:
|
|
403
|
+
index = math.floor((stamp - run_start) / bin_s) - first_index
|
|
404
|
+
if 0 <= index < len(counts):
|
|
405
|
+
counts[index] += 1
|
|
406
|
+
offered[stream.label] = [round(count / bin_s, 3) for count in counts]
|
|
407
|
+
return offered
|
|
408
|
+
|
|
409
|
+
|
|
345
410
|
# --------------------------------------------------------------------------- #
|
|
346
411
|
# Event detection
|
|
347
412
|
# --------------------------------------------------------------------------- #
|
|
@@ -470,7 +535,11 @@ def detect_latency_excursions(stream: Stream, config: DetectionConfig) -> list[D
|
|
|
470
535
|
|
|
471
536
|
|
|
472
537
|
def detect_rate_collapses(
|
|
473
|
-
stream: Stream,
|
|
538
|
+
stream: Stream,
|
|
539
|
+
bins: list[dict[str, Any]],
|
|
540
|
+
config: DetectionConfig,
|
|
541
|
+
*,
|
|
542
|
+
offered_hz: list[float] | None = None,
|
|
474
543
|
) -> list[DegradationEvent]:
|
|
475
544
|
"""Runs of >= ``rate_collapse_min_bins`` interior bins delivering far below nominal.
|
|
476
545
|
|
|
@@ -478,45 +547,75 @@ def detect_rate_collapses(
|
|
|
478
547
|
becomes lost rows when a later message arrives) and throttled-but-delivering
|
|
479
548
|
regimes. Needs a nominal rate; the first and last bin of a stream are never
|
|
480
549
|
judged (they are partial by construction).
|
|
550
|
+
|
|
551
|
+
``offered_hz`` — one rate per entry of ``bins``, from the sending peer's own
|
|
552
|
+
records (``build_offered_timelines``) — names each event's culprit as
|
|
553
|
+
``cause``: ``source`` when every collapsed bin was offered below the same
|
|
554
|
+
threshold (the collapse is fully explained by the source pausing or slowing;
|
|
555
|
+
the link delivered what it was given), ``link`` when at least one collapsed
|
|
556
|
+
bin was offered at/above threshold and the link still delivered below it,
|
|
557
|
+
and ``unknown`` without sender-side records. The receiver alone cannot tell
|
|
558
|
+
these apart: on the 2026-08-17 CCNG drive, 0 Hz "collapses" on the planner
|
|
559
|
+
visualization were the source pausing during manual interventions, not the
|
|
560
|
+
link.
|
|
481
561
|
"""
|
|
482
562
|
nominal_hz = stream.nominal_hz
|
|
483
563
|
if nominal_hz is None or len(bins) < 3:
|
|
484
564
|
return []
|
|
565
|
+
threshold_hz = config.rate_collapse_fraction * nominal_hz
|
|
485
566
|
events: list[DegradationEvent] = []
|
|
486
|
-
run: list[dict[str, Any]] = []
|
|
567
|
+
run: list[tuple[int, dict[str, Any]]] = []
|
|
568
|
+
|
|
569
|
+
def cause_details() -> dict[str, Any]:
|
|
570
|
+
if offered_hz is None or len(offered_hz) != len(bins):
|
|
571
|
+
return {"cause": "unknown"}
|
|
572
|
+
rates = [float(offered_hz[index]) for index, _entry in run]
|
|
573
|
+
return {
|
|
574
|
+
"cause": "source" if max(rates) < threshold_hz else "link",
|
|
575
|
+
"min_offered_hz": round(min(rates), 3),
|
|
576
|
+
"max_offered_hz": round(max(rates), 3),
|
|
577
|
+
}
|
|
487
578
|
|
|
488
579
|
def flush() -> None:
|
|
489
580
|
if len(run) >= config.rate_collapse_min_bins:
|
|
581
|
+
entries = [entry for _index, entry in run]
|
|
490
582
|
events.append(
|
|
491
583
|
DegradationEvent(
|
|
492
584
|
kind=RATE_COLLAPSE,
|
|
493
585
|
stream=stream.label,
|
|
494
|
-
start_epoch=float(
|
|
495
|
-
end_epoch=float(
|
|
496
|
-
count=len(
|
|
586
|
+
start_epoch=float(entries[0]["bin_start_epoch"]),
|
|
587
|
+
end_epoch=float(entries[-1]["bin_start_epoch"]) + config.bin_s,
|
|
588
|
+
count=len(entries),
|
|
497
589
|
details={
|
|
498
590
|
"nominal_hz": round(nominal_hz, 3),
|
|
499
|
-
"min_delivered_hz": min(float(entry["delivered_hz"]) for entry in
|
|
500
|
-
"threshold_hz": round(
|
|
591
|
+
"min_delivered_hz": min(float(entry["delivered_hz"]) for entry in entries),
|
|
592
|
+
"threshold_hz": round(threshold_hz, 3),
|
|
593
|
+
**cause_details(),
|
|
501
594
|
},
|
|
502
595
|
)
|
|
503
596
|
)
|
|
504
597
|
run.clear()
|
|
505
598
|
|
|
506
|
-
for entry in bins[1:-1]:
|
|
507
|
-
if float(entry["delivered_hz"]) <
|
|
508
|
-
run.append(entry)
|
|
599
|
+
for index, entry in enumerate(bins[1:-1], start=1):
|
|
600
|
+
if float(entry["delivered_hz"]) < threshold_hz:
|
|
601
|
+
run.append((index, entry))
|
|
509
602
|
else:
|
|
510
603
|
flush()
|
|
511
604
|
flush()
|
|
512
605
|
return events
|
|
513
606
|
|
|
514
607
|
|
|
515
|
-
def detect_events(
|
|
608
|
+
def detect_events(
|
|
609
|
+
stream: Stream,
|
|
610
|
+
bins: list[dict[str, Any]],
|
|
611
|
+
config: DetectionConfig,
|
|
612
|
+
*,
|
|
613
|
+
offered_hz: list[float] | None = None,
|
|
614
|
+
) -> list[DegradationEvent]:
|
|
516
615
|
events = [
|
|
517
616
|
*detect_loss_bursts(stream, config),
|
|
518
617
|
*detect_latency_excursions(stream, config),
|
|
519
|
-
*detect_rate_collapses(stream, bins, config),
|
|
618
|
+
*detect_rate_collapses(stream, bins, config, offered_hz=offered_hz),
|
|
520
619
|
]
|
|
521
620
|
events.sort(key=lambda event: (event.start_epoch, event.end_epoch, event.kind, event.stream))
|
|
522
621
|
return events
|
|
@@ -843,8 +942,15 @@ def build_report(
|
|
|
843
942
|
config = config or DetectionConfig()
|
|
844
943
|
inputs = discover_inputs(instance_dir, peers=peers)
|
|
845
944
|
records = load_transit_records(inputs.events_paths)
|
|
945
|
+
# The sending peer's own rows (direction=outbound, stage com_out) carry the
|
|
946
|
+
# offered rate. They must stay out of the delivered view: the cross-peer
|
|
947
|
+
# join keys on (source, topic, epoch, seq), so a sender row would upgrade
|
|
948
|
+
# the receiver's lost row to delivered and flatten delivered_hz to the
|
|
949
|
+
# offered rate.
|
|
950
|
+
offered_records = [record for record in records if record.get("direction") == "outbound"]
|
|
951
|
+
delivered_records = [record for record in records if record.get("direction") != "outbound"]
|
|
846
952
|
declared = load_declared_ffmpeg_topics(inputs.status_paths)
|
|
847
|
-
streams = build_streams(
|
|
953
|
+
streams = build_streams(delivered_records, declared_ffmpeg_topics=declared)
|
|
848
954
|
|
|
849
955
|
send_spans = [(stream.send_time(stream.records[0]), stream.send_time(stream.records[-1])) for stream in streams]
|
|
850
956
|
run_start = min((span[0] for span in send_spans), default=0.0)
|
|
@@ -859,9 +965,10 @@ def build_report(
|
|
|
859
965
|
)
|
|
860
966
|
|
|
861
967
|
timelines = {stream.label: build_stream_bins(stream, run_start=run_start, bin_s=config.bin_s) for stream in streams}
|
|
968
|
+
offered = build_offered_timelines(offered_records, streams, timelines, run_start=run_start, bin_s=config.bin_s)
|
|
862
969
|
events: list[DegradationEvent] = []
|
|
863
970
|
for stream in streams:
|
|
864
|
-
events.extend(detect_events(stream, timelines[stream.label], config))
|
|
971
|
+
events.extend(detect_events(stream, timelines[stream.label], config, offered_hz=offered.get(stream.label)))
|
|
865
972
|
events.sort(key=lambda event: (event.start_epoch, event.end_epoch, event.kind, event.stream))
|
|
866
973
|
|
|
867
974
|
incidents = group_incidents(events, merge_gap_s=config.merge_gap_s)
|
|
@@ -881,7 +988,7 @@ def build_report(
|
|
|
881
988
|
|
|
882
989
|
from .transit import summarize_transit_records
|
|
883
990
|
|
|
884
|
-
summary = summarize_transit_records(
|
|
991
|
+
summary = summarize_transit_records(delivered_records)["topics"]
|
|
885
992
|
stream_entries: dict[str, Any] = {}
|
|
886
993
|
for stream in streams:
|
|
887
994
|
entry = dict(summary.get(stream.label, {}))
|
|
@@ -984,9 +1091,21 @@ def _event_line(event: dict[str, Any]) -> str:
|
|
|
984
1091
|
f"baseline {_format_ms(details.get('baseline_ms'))} (threshold {_format_ms(details.get('threshold_ms'))}), "
|
|
985
1092
|
f"{window}"
|
|
986
1093
|
)
|
|
1094
|
+
cause = details.get("cause")
|
|
1095
|
+
if cause == "source":
|
|
1096
|
+
name = (
|
|
1097
|
+
"rate collapse (source paused)" if details.get("max_offered_hz") == 0.0 else "rate collapse (source slowed)"
|
|
1098
|
+
)
|
|
1099
|
+
elif cause == "link":
|
|
1100
|
+
name = "rate collapse (link)"
|
|
1101
|
+
else:
|
|
1102
|
+
name = "rate collapse"
|
|
1103
|
+
low, high = details.get("min_offered_hz"), details.get("max_offered_hz")
|
|
1104
|
+
offered = "" if low is None else (f", offered {low}Hz" if low == high else f", offered {low}-{high}Hz")
|
|
1105
|
+
suffix = "" if cause in ("source", "link") else "; cause unknown (no sender-side records)"
|
|
987
1106
|
return (
|
|
988
|
-
f"**
|
|
989
|
-
f"{details.get('min_delivered_hz')}Hz vs nominal {details.get('nominal_hz')}Hz, {window}"
|
|
1107
|
+
f"**{name}** on `{event['stream']}` — {event['count']} bins, delivered rate down to "
|
|
1108
|
+
f"{details.get('min_delivered_hz')}Hz vs nominal {details.get('nominal_hz')}Hz{offered}, {window}{suffix}"
|
|
990
1109
|
)
|
|
991
1110
|
|
|
992
1111
|
|