rosotacom-dev 2.5.dev49__tar.gz → 2.5.dev51__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.dev49 → rosotacom_dev-2.5.dev51}/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/README.md +2 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/delay-alone-no-loss.md +8 -1
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/head-of-line-irregularity.md +11 -1
- rosotacom_dev-2.5.dev51/docs/findings/keyframe-loss-kills-the-next-deltas.md +69 -0
- rosotacom_dev-2.5.dev51/docs/findings/oversubscription-queues-not-losses.md +81 -0
- rosotacom_dev-2.5.dev51/docs/findings/repro/decode_chain_repro.py +126 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0004-network-profiles.md +18 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/cli.py +155 -8
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/cli_benchmark.py +30 -5
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/SOURCES.txt +4 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/scm_file_list.json +4 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
- rosotacom_dev-2.5.dev51/tests/unit/test_container_shaping.py +67 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.devcontainer/devcontainer.json.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.devcontainer/re_generate_devcontainer_json.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/CODEOWNERS +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/actions/setup-just/action.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/dependabot.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/benchmark-calibrate.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/benchmark-gate.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/dev-release.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/image-scan.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/nightly-e2e.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/pr-lightweight.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/pr-merge-gate.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.github/workflows/release.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/.pre-commit-config.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/AGENTS.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/CONCURRENCY.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/CONTRIBUTING.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/DEVELOPMENT_PRINCIPLES.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/MANIFEST.in +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/README.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/budgets.jsonl +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/README.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/capacity-size-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/probe-loss-nominal-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/probe-loss-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/probe-loss-tight-fastdds.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/calibration/probe-loss-tight-zenoh.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/codecov.yml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/deployment-configuration.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/benchmark-ab.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/benchmark-bag-as-load.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/ci.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/ffmpeg-keyframes.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/lighter-message-loss.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/reorder-becomes-reader-loss.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/repro/reorder_drop_repro.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/forensics-report.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/geo-link-map.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/github-repository-settings.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/link-bandwidth.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/link-trace.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/owner-runbook.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/performance-bands.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/playbooks/README.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/quality-model.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/release-notes/TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/release-notes/v2.1.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/release-notes/v2.2.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/release-notes/v2.3.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/release-notes/v2.4.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/release.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0002-expectation-concepts.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0003-metric-backbone.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0006-dynamic-qos.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/0007-regression-gate.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/rfcs/README.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/testing.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/work-items.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/install.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/justfile +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/media/control_center.png +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/media/echo_test.png +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/media/forensics-report-example.png +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/media/host.png +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/media/vehicle.png +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/pyproject.toml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/requirements.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/session-configuration.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/__init__.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/__main__.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/anonymize.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/benched_set.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/benchmark.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/deployment.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/forensics.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/geomap.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/image_cache.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/plots.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/py.typed +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/benched-set.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/.gitignore +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/README.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/__init__.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/dist/field-20260817.dist +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/profiles.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ros2docker.json.example +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/status_eval.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/transit.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom/video_quality.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/requires.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/terminology.md +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_benched_set_registry.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_findings.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_markdown_links.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_ota_source_bundle.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_package_metadata.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_packaged_resources.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_profile_covers_generated_commands.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_pytest_policy.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_readme_examples.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_security_maintenance_config.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_session_configuration_doc.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_session_template_substitution.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/contract/test_workflow_contracts.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/conftest.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_anonymize_e2e.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_benchmark_ab.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_benchmark_capacity.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_benchmark_replay.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_parallel_smoke.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_smoke.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_timeline_stepping.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/e2e/test_video_quality_e2e.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/sessions/generate_rmw_matrix.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_anonymize.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_benched_set.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_catmux_pane_logging.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_cli.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_concurrency.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_deployment.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_ffmpeg_flags.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_field_profiles.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_forensics.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_geomap.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_image_cache.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_link_trace.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_netem_dist.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_network_profiles.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_playout_pacer.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_plots.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_qos_fallback_logging.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_relay_optional_parameters.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_session_pipeline.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_status_eval.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_status_overview.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_status_overview_observer.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_transit.py +0 -0
- {rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/tests/unit/test_video_quality.py +0 -0
|
@@ -28,6 +28,8 @@ living verification hook.
|
|
|
28
28
|
|
|
29
29
|
## Findings
|
|
30
30
|
|
|
31
|
+
- [A lost keyframe silences exactly the next gop_size−2 deltas](keyframe-loss-kills-the-next-deltas.md) - libavcodec emits nothing for them, the last delta decodes with artifacts, the next keyframe restores clean decode; a lost delta never blocks decode — so the display hole is gop_size × frame period, deterministically.
|
|
32
|
+
- [Oversubscription queues — it does not lose until the queue itself dies](oversubscription-queues-not-losses.md) - 16% over an emulated cap climbed one-way delay 340 → 3250 ms across twelve bins with zero loss in every one; the losses arrived only when the queue died, all at once.
|
|
31
33
|
- [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.
|
|
32
34
|
- [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.
|
|
33
35
|
- [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.
|
|
@@ -54,13 +54,20 @@ rosotacom benchmark capacity --project src/rosotacom/resources/examples/rosotaco
|
|
|
54
54
|
Migrated summary: 400 expected, 400 delivered, 0 lost, max p95 latency
|
|
55
55
|
306.927 ms.
|
|
56
56
|
|
|
57
|
+
Field recurrence (2026-08-17 CCNG drive): the negative control held on a real
|
|
58
|
+
cellular link — five jam episodes ran one-way delay up to 401–459 ms with zero
|
|
59
|
+
losses, and the drive's soggy last minute held p90 ≈ 138 ms at 0.04 losses/s.
|
|
60
|
+
The positive counterpart (where the delay actually comes from when a link is
|
|
61
|
+
too slow rather than too far) is
|
|
62
|
+
[oversubscription-queues-not-losses.md](oversubscription-queues-not-losses.md).
|
|
63
|
+
|
|
57
64
|
Verification: manual: run the commands above from a source checkout with Docker
|
|
58
65
|
and `tc` privileges; this is a no-loss negative-control finding rather than a
|
|
59
66
|
must-fail boundary row, so it remains outside the RFC 0007 boundary gate.
|
|
60
67
|
|
|
61
68
|
## Status
|
|
62
69
|
|
|
63
|
-
confirmed, 2026-
|
|
70
|
+
confirmed, 2026-08-18.
|
|
64
71
|
|
|
65
72
|
## Publication notes
|
|
66
73
|
|
{rosotacom_dev-2.5.dev49 → rosotacom_dev-2.5.dev51}/docs/findings/head-of-line-irregularity.md
RENAMED
|
@@ -45,13 +45,23 @@ The mechanism is visible only per message: the stream carrying alternating
|
|
|
45
45
|
heavy/light samples can lose more than the steady stream despite lower mean
|
|
46
46
|
payload bandwidth.
|
|
47
47
|
|
|
48
|
+
Field recurrence (2026-08-17 CCNG drive): the DELAY half of the mechanism
|
|
49
|
+
recurred and was measured per message — a small message with more than 30 KB
|
|
50
|
+
in flight ahead of it paid +17.4 ms p50 / +67 ms p90, a small message sent
|
|
51
|
+
within 40 ms after a video keyframe paid p50 42.1 vs 28.4 ms, and 18.4% of
|
|
52
|
+
cross-topic pairs sent within 2 ms flipped order after a >20 KB first message.
|
|
53
|
+
The LOSS half (delayed-then-overwritten) did not trigger there: it needs a
|
|
54
|
+
queue tight against the offered load, and that link had ~14 Mbit/s
|
|
55
|
+
serialization headroom at 2.5 Mbit/s offered. Both halves stay reproducible
|
|
56
|
+
with the tight profile above.
|
|
57
|
+
|
|
48
58
|
Verification: manual: run the two commands above from a source checkout with
|
|
49
59
|
Docker and `tc` privileges; automation waits for the RFC 0007 regression matrix
|
|
50
60
|
row for patterned loads because the public row registry does not exist yet.
|
|
51
61
|
|
|
52
62
|
## Status
|
|
53
63
|
|
|
54
|
-
confirmed, 2026-
|
|
64
|
+
confirmed, 2026-08-18.
|
|
55
65
|
|
|
56
66
|
## Publication notes
|
|
57
67
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# A Lost Keyframe Silences Exactly The Next gop_size−2 Deltas
|
|
2
|
+
|
|
3
|
+
## Claim
|
|
4
|
+
|
|
5
|
+
In a best-effort libx264 stream (ABR, `tune: zerolatency`, fixed GOP), losing
|
|
6
|
+
a KEYFRAME makes exactly the next `gop_size − 2` arrived deltas undecodable —
|
|
7
|
+
libavcodec emits **no frame at all** for them — the last delta before the next
|
|
8
|
+
keyframe decodes with concealment artifacts, and the next keyframe restores
|
|
9
|
+
clean decode. Losing a DELTA never blocks decode: every following access unit
|
|
10
|
+
still produces a frame (artifacts until the next keyframe, never a gap). The
|
|
11
|
+
behaviour is deterministic — not timing, not decoder state noise — so the
|
|
12
|
+
display hole a keyframe loss tears is `gop_size × frame_period`, which is what
|
|
13
|
+
makes the GOP length the knob that decides whether one lost packet crosses an
|
|
14
|
+
operator-visible threshold.
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
- Host pair / topology: single host, single process, below rosotacom at the
|
|
19
|
+
codec layer — encode with PyAV/libx264 exactly as the ffmpeg transport does,
|
|
20
|
+
delete single access units explicitly, feed the survivors to a fresh
|
|
21
|
+
libavcodec h264 decoder (what a best-effort receiver does after a loss).
|
|
22
|
+
- rosotacom SHA: measured at `73fa1de`; re-runs record theirs.
|
|
23
|
+
- Profile: none — no network is involved; the deletion IS the loss, which is
|
|
24
|
+
what makes the rule attributable to the codec rather than the transport.
|
|
25
|
+
- Seed policy: deterministic synthetic motion clip, `n=1` (the assertions are
|
|
26
|
+
exact-set comparisons, not statistics).
|
|
27
|
+
|
|
28
|
+
## Evidence
|
|
29
|
+
|
|
30
|
+
Evidence grade: scripted single-host reproduction, committed next to this file.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python3 docs/findings/repro/decode_chain_repro.py --gop 5 # needs: pip install av numpy
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2026-08-18 runs (synthetic clip, libx264 1 Mbit/s ABR, zerolatency, 10 Hz):
|
|
37
|
+
|
|
38
|
+
| gop | deleted | silent AUs after it | first output after the gap |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| 5 | keyframe | exactly 3 (= gop−2) | last delta of the GOP, with artifacts |
|
|
41
|
+
| 3 | keyframe | exactly 1 (= gop−2) | last delta of the GOP, with artifacts |
|
|
42
|
+
| 2 | keyframe | none (= gop−2 = 0) | the following delta, with artifacts |
|
|
43
|
+
| any | one delta | none | every AU keeps decoding, artifacts to next KF |
|
|
44
|
+
|
|
45
|
+
The same rule was first measured on real field material (2026-08-17 CCNG
|
|
46
|
+
drive, camera FFMPEGPacket stream at gop 5: every lost keyframe silenced the
|
|
47
|
+
next 3 arrived deltas, ~400 ms holes at 10 Hz; a `refs=1` encode does NOT
|
|
48
|
+
shorten the chain — tested and rejected). The synthetic clip reproduces it
|
|
49
|
+
exactly, so the public repro carries the claim without any private bag.
|
|
50
|
+
|
|
51
|
+
Verification: manual: run the command above (per `--gop 5`, `--gop 3`,
|
|
52
|
+
`--gop 2`); the script asserts the exact silent set and exits non-zero on any
|
|
53
|
+
deviation.
|
|
54
|
+
|
|
55
|
+
## Status
|
|
56
|
+
|
|
57
|
+
confirmed, 2026-08-18.
|
|
58
|
+
|
|
59
|
+
## Publication notes
|
|
60
|
+
|
|
61
|
+
This is the quantitative bridge from "loss percent" to "operator-visible
|
|
62
|
+
hole": camera loss numbers mean nothing next to costmap loss numbers until
|
|
63
|
+
multiplied by the GOP geometry. It drove the 2026-08-17 field configuration
|
|
64
|
+
change gop 5 → 3 (hole ≤ 300 ms at equal measured bitrate). For papers: plot
|
|
65
|
+
hole duration vs GOP at fixed measured bitrate, and state that intra-refresh
|
|
66
|
+
trades the hole for a several-frame artifact window instead (no silent AUs,
|
|
67
|
+
lower clean-frame share). See also
|
|
68
|
+
[docs/ffmpeg-keyframes.md](../ffmpeg-keyframes.md) for reading keyframe flags
|
|
69
|
+
out of recorded transit records.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Oversubscription Queues — It Does Not Lose Until The Queue Itself Dies
|
|
2
|
+
|
|
3
|
+
## Claim
|
|
4
|
+
|
|
5
|
+
Offered load slightly above a rate cap turns entirely into queueing delay, not
|
|
6
|
+
loss: one-way delay climbs by hundreds of milliseconds per second of
|
|
7
|
+
oversubscription — into the seconds — while per-bin loss stays exactly zero.
|
|
8
|
+
Loss appears only when the queue itself is destroyed (in emulation: a
|
|
9
|
+
shape-changing timeline step; in the field: a bearer/modem reset), and then it
|
|
10
|
+
is the whole backlog at once. Consequences: loss statistics alone certify a
|
|
11
|
+
badly broken link as healthy, delay-based failure precedes loss-based failure,
|
|
12
|
+
and a recovering link replays a burst of stale messages unless a lifespan
|
|
13
|
+
bounds them.
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
- Host pair / topology: rosotacom local benchmark mode using the packaged
|
|
18
|
+
example project, synthetic `bench_1_1_capacity` session, one
|
|
19
|
+
`a->b:/bench_capacity` stream, uplink shaping only (tc inside the peer
|
|
20
|
+
containers' own netns — no host privileges).
|
|
21
|
+
- rosotacom SHA: measured at `73fa1de`; public re-runs record the current
|
|
22
|
+
checkout SHA in `result.json.context`.
|
|
23
|
+
- Profile: `field-20260817-case3-delay-jam` in
|
|
24
|
+
[`src/rosotacom/resources/examples/profiles.yaml`](../../src/rosotacom/resources/examples/profiles.yaml)
|
|
25
|
+
— 10 s clean, 15 s `rate: 900kbit` (the crunch), 20 s clean; fitted to the
|
|
26
|
+
2026-08-17 CCNG drive's case3 window (in-flight queueing to 1.4 s at almost
|
|
27
|
+
no loss).
|
|
28
|
+
- Seed policy: no stochastic netem elements (rate/delay only), `n=1`.
|
|
29
|
+
|
|
30
|
+
## Evidence
|
|
31
|
+
|
|
32
|
+
Evidence grade: packaged-profile benchmark run plus per-bin timeline.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
rosotacom benchmark probe --project src/rosotacom/resources/examples/rosotacom.yaml --profile field-20260817-case3-delay-jam --size-pattern 1x13KB --rate-hz 10 --duration 45 --repeats 1 --no-plot
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2026-08-18 run (offered 1.04 Mbit/s against the 15 s 900 kbit crunch — 16%
|
|
39
|
+
oversubscription): twelve consecutive 1 s bins during the crunch show p95
|
|
40
|
+
one-way latency climbing 340 → 601 → 880 → 1117 → 1420 → 1684 → 1944 → 2208 →
|
|
41
|
+
2469 → 2704 → 3009 → **3250 ms with `lost = 0` in every single bin** — the
|
|
42
|
+
queue (netem child, default limit 1000 packets) absorbs the entire excess.
|
|
43
|
+
All 27 lost messages (5.9% of the run) sit in the three bins at the
|
|
44
|
+
crunch→clean boundary, where the timeline step replaces a tbf+netem tree with
|
|
45
|
+
a netem-only tree and the queued ~2.7 s backlog dies with the old queue.
|
|
46
|
+
|
|
47
|
+
Two readings beyond the headline: (a) a loss-based health verdict over the
|
|
48
|
+
crunch window reads "0% loss" while the operator's picture is three seconds
|
|
49
|
+
old and aging — delay-based failure criteria fire long before loss-based
|
|
50
|
+
ones; (b) the backlog-drop at the boundary is the emulated twin of a bearer
|
|
51
|
+
reset, and it is also the caveat's edge: `tc qdisc replace` steps are seamless
|
|
52
|
+
only between same-shape trees — a step that adds or removes `rate` swaps the
|
|
53
|
+
tree and drops its queue.
|
|
54
|
+
|
|
55
|
+
Field recurrence (2026-08-17 CCNG drive): five jam episodes with one-way
|
|
56
|
+
delay 401–459 ms at zero losses; the case3 window queued to 1.4 s in-flight
|
|
57
|
+
with almost no loss; and the drive's last minute ran p90 ≈ 138 ms delay at
|
|
58
|
+
0.04 losses/s — the same regime at lower intensity. A 2026-08-18 container
|
|
59
|
+
replay of a 9% oversubscription reached 3.66 s delay at exactly 0% loss.
|
|
60
|
+
|
|
61
|
+
Verification: manual: run the command above from a source checkout with
|
|
62
|
+
Docker; read the per-bin `time-bins.jsonl` next to `result.json` — the crunch
|
|
63
|
+
bins must show monotonically climbing latency at zero loss, with the losses
|
|
64
|
+
(if any) confined to the boundary bins.
|
|
65
|
+
|
|
66
|
+
## Status
|
|
67
|
+
|
|
68
|
+
confirmed, 2026-08-18.
|
|
69
|
+
|
|
70
|
+
## Publication notes
|
|
71
|
+
|
|
72
|
+
This is the mechanism behind
|
|
73
|
+
[delay-alone-no-loss.md](delay-alone-no-loss.md)'s negative control seen from
|
|
74
|
+
the other side: delay alone does not lose, and oversubscription alone does not
|
|
75
|
+
lose either — it defers. Pair with
|
|
76
|
+
[reorder-becomes-reader-loss.md](reorder-becomes-reader-loss.md) for the two
|
|
77
|
+
ways "the network was fine, loss says so" misleads. For papers: plot per-bin
|
|
78
|
+
delay and loss on one timeline; the visual is a delay ramp under a flat zero
|
|
79
|
+
loss line, ending in a loss spike exactly when the queue dies. Lifespan QoS
|
|
80
|
+
(and its enforcement gap — writer-side only in CycloneDDS) decides what the
|
|
81
|
+
recovery burst does to the application.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Reproduce the keyframe decode-chain rule of a best-effort libx264 stream.
|
|
3
|
+
|
|
4
|
+
Encodes a deterministic synthetic motion clip the way the ffmpeg transport
|
|
5
|
+
does (libx264, ABR, ``tune: zerolatency``, fixed GOP), then deletes single
|
|
6
|
+
access units and feeds the survivors to a fresh libavcodec h264 decoder --
|
|
7
|
+
which is exactly what a best-effort receiver does after a network loss.
|
|
8
|
+
|
|
9
|
+
Measured rule (docs/findings/keyframe-loss-kills-the-next-deltas.md):
|
|
10
|
+
|
|
11
|
+
* delete a KEYFRAME -> the next ``gop_size - 2`` arrived deltas produce NO
|
|
12
|
+
output at all, the last delta before the next keyframe decodes with
|
|
13
|
+
concealment artifacts, the next keyframe restores clean decode;
|
|
14
|
+
* delete a DELTA -> every following access unit still decodes (artifacts
|
|
15
|
+
until the next keyframe, never a gap).
|
|
16
|
+
|
|
17
|
+
Usage (needs ``pip install av numpy``; no network, no privileges)::
|
|
18
|
+
|
|
19
|
+
python3 docs/findings/repro/decode_chain_repro.py [--gop 5] [--frames 40]
|
|
20
|
+
|
|
21
|
+
Prints the per-AU decode table for both deletions and exits non-zero if the
|
|
22
|
+
rule above does not hold.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import sys
|
|
29
|
+
from fractions import Fraction
|
|
30
|
+
|
|
31
|
+
import av
|
|
32
|
+
import numpy as np
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def synthetic_frames(count: int, width: int = 320, height: int = 240):
|
|
36
|
+
"""Deterministic motion: drifting background plus a moving block."""
|
|
37
|
+
for index in range(count):
|
|
38
|
+
image = np.full((height, width, 3), (index * 3) % 200, dtype=np.uint8)
|
|
39
|
+
x = (index * 7) % (width - 40)
|
|
40
|
+
image[60:180, x : x + 40] = 255
|
|
41
|
+
frame = av.VideoFrame.from_ndarray(image, format="rgb24")
|
|
42
|
+
yield frame.reformat(format="yuv420p")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def encode(gop: int, count: int) -> list[tuple[bytes, bool]]:
|
|
46
|
+
codec = av.CodecContext.create("libx264", "w")
|
|
47
|
+
codec.width, codec.height = 320, 240
|
|
48
|
+
codec.pix_fmt = "yuv420p"
|
|
49
|
+
codec.framerate = Fraction(10, 1)
|
|
50
|
+
codec.bit_rate = 1_000_000
|
|
51
|
+
codec.options = {"tune": "zerolatency", "g": str(gop)}
|
|
52
|
+
packets: list[tuple[bytes, bool]] = []
|
|
53
|
+
for frame in synthetic_frames(count):
|
|
54
|
+
for packet in codec.encode(frame):
|
|
55
|
+
packets.append((bytes(packet), bool(packet.is_keyframe)))
|
|
56
|
+
for packet in codec.encode(None):
|
|
57
|
+
packets.append((bytes(packet), bool(packet.is_keyframe)))
|
|
58
|
+
return packets
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def decode_survivors(packets: list[tuple[bytes, bool]], deleted: set[int]) -> list[tuple[int, int]]:
|
|
62
|
+
"""Feed every surviving AU to a fresh decoder; return (au_index, frames_out)."""
|
|
63
|
+
decoder = av.CodecContext.create("h264", "r")
|
|
64
|
+
out: list[tuple[int, int]] = []
|
|
65
|
+
for index, (payload, _kf) in enumerate(packets):
|
|
66
|
+
if index in deleted:
|
|
67
|
+
continue
|
|
68
|
+
try:
|
|
69
|
+
frames = decoder.decode(av.Packet(payload))
|
|
70
|
+
except av.error.InvalidDataError:
|
|
71
|
+
frames = []
|
|
72
|
+
out.append((index, len(frames)))
|
|
73
|
+
return out
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main() -> int:
|
|
77
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
78
|
+
parser.add_argument("--gop", type=int, default=5)
|
|
79
|
+
parser.add_argument("--frames", type=int, default=40)
|
|
80
|
+
args = parser.parse_args()
|
|
81
|
+
|
|
82
|
+
packets = encode(args.gop, args.frames)
|
|
83
|
+
kf_indices = [i for i, (_p, kf) in enumerate(packets) if kf]
|
|
84
|
+
if len(kf_indices) < 3:
|
|
85
|
+
print("clip too short to delete a mid-stream keyframe", file=sys.stderr)
|
|
86
|
+
return 2
|
|
87
|
+
|
|
88
|
+
baseline = decode_survivors(packets, set())
|
|
89
|
+
silent_baseline = [i for i, n in baseline if n == 0]
|
|
90
|
+
|
|
91
|
+
failures: list[str] = []
|
|
92
|
+
|
|
93
|
+
# --- delete the second keyframe -------------------------------------
|
|
94
|
+
kf = kf_indices[1]
|
|
95
|
+
next_kf = kf_indices[2]
|
|
96
|
+
rows = decode_survivors(packets, {kf})
|
|
97
|
+
silent = [i for i, n in rows if n == 0 and i not in silent_baseline]
|
|
98
|
+
deltas_in_gap = [i for i in range(kf + 1, next_kf)]
|
|
99
|
+
expected_silent = deltas_in_gap[: max(0, args.gop - 2)]
|
|
100
|
+
print(f"deleted keyframe AU {kf} (gop {args.gop}, next keyframe {next_kf})")
|
|
101
|
+
print(f" silent AUs: {silent}")
|
|
102
|
+
print(f" expected : {expected_silent} (the next gop_size-2 deltas)")
|
|
103
|
+
if silent != expected_silent:
|
|
104
|
+
failures.append(f"keyframe deletion: silent AUs {silent} != expected {expected_silent}")
|
|
105
|
+
resumed = [i for i, n in rows if i >= next_kf and n == 0]
|
|
106
|
+
if resumed:
|
|
107
|
+
failures.append(f"decode did not resume at the next keyframe: silent {resumed}")
|
|
108
|
+
|
|
109
|
+
# --- delete one delta ------------------------------------------------
|
|
110
|
+
delta = kf_indices[1] + 1
|
|
111
|
+
rows = decode_survivors(packets, {delta})
|
|
112
|
+
silent = [i for i, n in rows if n == 0 and i not in silent_baseline]
|
|
113
|
+
print(f"deleted delta AU {delta}")
|
|
114
|
+
print(f" silent AUs: {silent} (must be empty -- artifacts, never a gap)")
|
|
115
|
+
if silent:
|
|
116
|
+
failures.append(f"delta deletion silenced AUs {silent}; expected none")
|
|
117
|
+
|
|
118
|
+
if failures:
|
|
119
|
+
print("FINDING VIOLATED:", *failures, sep="\n ", file=sys.stderr)
|
|
120
|
+
return 1
|
|
121
|
+
print("ok: keyframe loss silences exactly the next gop_size-2 deltas; delta loss silences nothing")
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
sys.exit(main())
|
|
@@ -266,6 +266,17 @@ risk — a stuck `qdisc` silently corrupts every later result on the machine.
|
|
|
266
266
|
and outage steps, plus static distillation from a full trace or window
|
|
267
267
|
(`trace_profiles.convert_trace_to_profile_yaml`). Passive throughput is omitted
|
|
268
268
|
as `rate` unless the sample is marked saturated/probed/capacity-like.
|
|
269
|
+
- [x] Shape without host sudo (issue #279): `--sudo-mode container` runs every
|
|
270
|
+
tc/ip argv in a short-lived `docker run --rm --network host --cap-add
|
|
271
|
+
NET_ADMIN` container on the peer, so docker-group membership is the whole
|
|
272
|
+
privilege. `--network host` keeps the target identical to the sudo modes
|
|
273
|
+
(the host netns is the netns the OTA com containers use); the safety
|
|
274
|
+
watchdog detaches as `docker run -d` under a fixed name and replaces a
|
|
275
|
+
stale predecessor at arm time; the image is `--shaping-image` or resolved
|
|
276
|
+
on the peer (running `rosotacom_` container's image, else the newest local
|
|
277
|
+
`ros-communication*` image, else an actionable exit-69). The lab benchmark
|
|
278
|
+
path needed none of this — it has always armed tc inside the peer
|
|
279
|
+
containers' own netns (`--cap-add NET_ADMIN` on the isolated network).
|
|
269
280
|
- [ ] Confirm the `/proc/net/dev` link sampler (RFC 0003 / `link_bytes.py`) reports
|
|
270
281
|
post-shaping wire bytes so link-overhead stays meaningful under a profile. *(Bench
|
|
271
282
|
check.)*
|
|
@@ -342,6 +353,13 @@ off during implementation). Notes whether automation is feasible; privileged
|
|
|
342
353
|
load generated YAML through `load_profiles_file`, and feed the resulting
|
|
343
354
|
timeline into dry-run `ProfileShaper` command generation
|
|
344
355
|
(`tests/unit/test_trace_profiles.py`). Done.
|
|
356
|
+
- [x] **Sudo-free container shaping** (`--sudo-mode container`) — host unit tests
|
|
357
|
+
pin the generated shell: host-netns + NET_ADMIN on every tc/ip run, the
|
|
358
|
+
watchdog's `docker run -d` detachment and stale-predecessor replacement, the
|
|
359
|
+
on-peer image resolution with its actionable failure, and that the watchdog
|
|
360
|
+
name stays out of the `rosotacom_` conflict scan
|
|
361
|
+
(`tests/unit/test_container_shaping.py`). Arming it against a live remote pair
|
|
362
|
+
stays a bench check (same standing as the sudo modes' live half).
|
|
345
363
|
- [ ] **Emulated-profile gate (rung 2)** — an example session run under one
|
|
346
364
|
canonical profile in the per-promotion CI smoke, asserting a conditional bound
|
|
347
365
|
that differs from the unshaped run. Automatable in the smoke matrix.
|
|
@@ -121,7 +121,15 @@ EXTERNAL_SESSION_CONTAINER_DIR = "/session/current"
|
|
|
121
121
|
# `anonymize_bag.py` two thousand lines below already does it this way.
|
|
122
122
|
RUN_SESSION_CONTAINER_ARGV = ("python3", "/ws/session/creation/run_session.py")
|
|
123
123
|
DEFAULT_SMOKE_SESSION = "1_heartbeat"
|
|
124
|
-
|
|
124
|
+
# How profile shaping obtains tc/ip privileges on a peer. `passwordless` and
|
|
125
|
+
# `askpass` are the two sudo shapes. `container` needs no sudo at all: every
|
|
126
|
+
# tc/ip argv runs in a short-lived `docker run --rm --network host --cap-add
|
|
127
|
+
# NET_ADMIN` container on the peer, so docker-group membership is the whole
|
|
128
|
+
# privilege — the mode that makes profiles runnable on a machine that has
|
|
129
|
+
# Docker but no passwordless sudo (issue #279). `--network host` puts tc in
|
|
130
|
+
# the host netns, which is the netns the OTA com containers use, so it shapes
|
|
131
|
+
# exactly the interface sudo would.
|
|
132
|
+
OTA_SUDO_MODES = ("passwordless", "askpass", "container")
|
|
125
133
|
|
|
126
134
|
# How ota-smoke puts rosotacom on each peer.
|
|
127
135
|
#
|
|
@@ -2663,6 +2671,59 @@ def _sudo_command(argv: Sequence[str], *, askpass: bool = False) -> str:
|
|
|
2663
2671
|
return f"{mode} {shlex.join(list(argv))}"
|
|
2664
2672
|
|
|
2665
2673
|
|
|
2674
|
+
#: Deterministic name for the container-mode safety watchdog on a peer. Fixed
|
|
2675
|
+
#: (not per-run) on purpose: arming replaces a stale predecessor with
|
|
2676
|
+
#: `docker rm -f`, so a crashed run's watchdog cannot accumulate — and the
|
|
2677
|
+
#: dash spelling keeps it out of the `rosotacom_` conflict scan, whose signal
|
|
2678
|
+
#: for a live run is the qdisc itself, not the sleeping watchdog.
|
|
2679
|
+
_SHAPING_WATCHDOG_NAME = "rosotacom-shaping-watchdog"
|
|
2680
|
+
|
|
2681
|
+
|
|
2682
|
+
def _shaping_image_resolution(image: str | None) -> str:
|
|
2683
|
+
"""Shell preamble that leaves the shaping image in ``$img`` on the peer.
|
|
2684
|
+
|
|
2685
|
+
An explicit image wins. Otherwise resolve on the peer itself, because the
|
|
2686
|
+
scoped image name (`<base>-<install_id>`) is a property of the peer's own
|
|
2687
|
+
install that the orchestrator cannot know before that install ran: prefer
|
|
2688
|
+
the image of a running `rosotacom_` container, fall back to the newest
|
|
2689
|
+
local `ros-communication*` image. Exit 69 with an actionable message when
|
|
2690
|
+
neither exists (a machine that never ran rosotacom): shaping needs *an*
|
|
2691
|
+
image with iproute2, and the com image is the one guaranteed relevant."""
|
|
2692
|
+
if image:
|
|
2693
|
+
return f"img={shlex.quote(image)}; "
|
|
2694
|
+
return (
|
|
2695
|
+
"img=\"$(docker ps --filter name=rosotacom_ --format '{{.Image}}' | head -n1)\"; "
|
|
2696
|
+
'[ -n "$img" ] || img="$(docker image ls --format \'{{.Repository}}:{{.Tag}}\''
|
|
2697
|
+
" | grep -m1 '^ros-communication' || true)\"; "
|
|
2698
|
+
'[ -n "$img" ] || { echo "no shaping image on this peer: run rosotacom here once'
|
|
2699
|
+
' (any smoke/start builds the com image) or pass --shaping-image" >&2; exit 69; }; '
|
|
2700
|
+
)
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
def _container_shaping_command(argv: Sequence[str], *, image: str | None, watchdog: bool = False) -> str:
|
|
2704
|
+
"""One privileged tc/ip argv as a short-lived NET_ADMIN container run.
|
|
2705
|
+
|
|
2706
|
+
``--network host`` is the point: the ephemeral container shares the host
|
|
2707
|
+
netns, so ``tc`` acts on the same interface sudo would — but the privilege
|
|
2708
|
+
comes from docker-group membership instead of a sudoers entry. A watchdog
|
|
2709
|
+
argv (``sh -c 'sleep …; tc …'``) runs detached (``-d``) under the fixed
|
|
2710
|
+
name above, replacing any stale predecessor; docker itself keeps it alive
|
|
2711
|
+
across an orchestrator crash, which is the fail-safe RFC 0004 demands."""
|
|
2712
|
+
argv = list(argv)
|
|
2713
|
+
resolve = _shaping_image_resolution(image)
|
|
2714
|
+
run = "docker run --rm --network host --cap-add NET_ADMIN"
|
|
2715
|
+
if watchdog:
|
|
2716
|
+
run = (
|
|
2717
|
+
f"docker rm -f {_SHAPING_WATCHDOG_NAME} >/dev/null 2>&1 || true; "
|
|
2718
|
+
f"docker run -d --rm --name {_SHAPING_WATCHDOG_NAME} --network host --cap-add NET_ADMIN"
|
|
2719
|
+
)
|
|
2720
|
+
if len(argv) >= 3 and argv[0] == "sh" and argv[1] == "-c":
|
|
2721
|
+
tail = f'--entrypoint sh "$img" -c {shlex.quote(str(argv[2]))}'
|
|
2722
|
+
else:
|
|
2723
|
+
tail = f'--entrypoint {shlex.quote(argv[0])} "$img" {shlex.join(argv[1:])}'
|
|
2724
|
+
return f"{resolve}{run} {tail}"
|
|
2725
|
+
|
|
2726
|
+
|
|
2666
2727
|
def _passwordless_watchdog_command(argv: Sequence[str]) -> str:
|
|
2667
2728
|
argv = list(argv)
|
|
2668
2729
|
if len(argv) >= 3 and argv[0] == "sh" and argv[1] == "-c":
|
|
@@ -2744,6 +2805,7 @@ def _ota_preflight(
|
|
|
2744
2805
|
require_network_shaping_sudo: bool = False,
|
|
2745
2806
|
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
2746
2807
|
sudo_passwords: Mapping[str, str] | None = None,
|
|
2808
|
+
shaping_image: str | None = None,
|
|
2747
2809
|
check_conflicts: bool = True,
|
|
2748
2810
|
) -> None:
|
|
2749
2811
|
sudo_mode = _validate_ota_sudo_mode(sudo_mode)
|
|
@@ -2771,6 +2833,27 @@ def _ota_preflight(
|
|
|
2771
2833
|
)
|
|
2772
2834
|
_ota_run(peer, "docker ps >/dev/null", label=f"{peer.name}: docker access", dry_run=dry_run, batch=True)
|
|
2773
2835
|
if require_network_shaping_sudo:
|
|
2836
|
+
if sudo_mode == "container":
|
|
2837
|
+
# tc/ip live in the shaping image, not on the host, and no sudo
|
|
2838
|
+
# is involved: the one thing to prove is that a NET_ADMIN
|
|
2839
|
+
# host-netns container can run and sees the tools. This also
|
|
2840
|
+
# fails early — with the actionable exit-69 message — on a
|
|
2841
|
+
# machine that has no candidate image yet.
|
|
2842
|
+
_ota_run(
|
|
2843
|
+
peer,
|
|
2844
|
+
_container_shaping_command(
|
|
2845
|
+
[
|
|
2846
|
+
"sh",
|
|
2847
|
+
"-c",
|
|
2848
|
+
"command -v tc >/dev/null && command -v ip >/dev/null && tc qdisc show >/dev/null",
|
|
2849
|
+
],
|
|
2850
|
+
image=shaping_image,
|
|
2851
|
+
),
|
|
2852
|
+
label=f"{peer.name}: containerized network shaping (docker run, NET_ADMIN, tc in image)",
|
|
2853
|
+
dry_run=dry_run,
|
|
2854
|
+
batch=True,
|
|
2855
|
+
)
|
|
2856
|
+
continue
|
|
2774
2857
|
_ota_run(
|
|
2775
2858
|
peer,
|
|
2776
2859
|
"command -v tc >/dev/null 2>&1 && command -v ip >/dev/null 2>&1",
|
|
@@ -3241,13 +3324,21 @@ def _ota_resolve_interfaces(peer: OtaSmokePeer, peer_addr: str, *, dry_run: bool
|
|
|
3241
3324
|
|
|
3242
3325
|
|
|
3243
3326
|
def _peer_command_runner(
|
|
3244
|
-
peer: OtaSmokePeer,
|
|
3327
|
+
peer: OtaSmokePeer,
|
|
3328
|
+
*,
|
|
3329
|
+
dry_run: bool,
|
|
3330
|
+
sudo_password: str | None = None,
|
|
3331
|
+
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
3332
|
+
shaping_image: str | None = None,
|
|
3245
3333
|
) -> Callable[[Sequence[str]], None]:
|
|
3246
3334
|
"""A CommandRunner that runs one privileged argv on ``peer`` via the SSH path."""
|
|
3247
3335
|
|
|
3248
3336
|
def run(argv: Sequence[str]) -> None:
|
|
3249
3337
|
label = f"{peer.name}: tc/netem"
|
|
3250
|
-
if
|
|
3338
|
+
if sudo_mode == "container":
|
|
3339
|
+
script = _container_shaping_command(argv, image=shaping_image)
|
|
3340
|
+
_ota_run(peer, script, label=label, dry_run=dry_run)
|
|
3341
|
+
elif sudo_password is None:
|
|
3251
3342
|
script = _sudo_command(argv)
|
|
3252
3343
|
_ota_run(peer, script, label=label, dry_run=dry_run)
|
|
3253
3344
|
else:
|
|
@@ -3265,12 +3356,23 @@ def _peer_command_runner(
|
|
|
3265
3356
|
|
|
3266
3357
|
|
|
3267
3358
|
def _peer_watchdog_launcher(
|
|
3268
|
-
peer: OtaSmokePeer,
|
|
3359
|
+
peer: OtaSmokePeer,
|
|
3360
|
+
*,
|
|
3361
|
+
dry_run: bool,
|
|
3362
|
+
sudo_password: str | None = None,
|
|
3363
|
+
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
3364
|
+
shaping_image: str | None = None,
|
|
3269
3365
|
) -> Callable[[Sequence[str]], None]:
|
|
3270
3366
|
"""Launch the safety-watchdog argv detached on ``peer`` so it survives a crash."""
|
|
3271
3367
|
|
|
3272
3368
|
def launch(argv: Sequence[str]) -> None:
|
|
3273
|
-
if
|
|
3369
|
+
if sudo_mode == "container":
|
|
3370
|
+
# `docker run -d` is the detachment: the daemon owns the sleeping
|
|
3371
|
+
# watchdog, so it survives the SSH session, the orchestrator, and
|
|
3372
|
+
# every rosotacom container on the machine.
|
|
3373
|
+
detached = _container_shaping_command(argv, image=shaping_image, watchdog=True)
|
|
3374
|
+
_ota_run(peer, detached, label=f"{peer.name}: profile safety watchdog", dry_run=dry_run, check=False)
|
|
3375
|
+
elif sudo_password is None:
|
|
3274
3376
|
detached = _passwordless_watchdog_command(argv)
|
|
3275
3377
|
_ota_run(peer, detached, label=f"{peer.name}: profile safety watchdog", dry_run=dry_run, check=False)
|
|
3276
3378
|
else:
|
|
@@ -3297,6 +3399,8 @@ def _ota_arm_profile(
|
|
|
3297
3399
|
*,
|
|
3298
3400
|
dry_run: bool,
|
|
3299
3401
|
sudo_passwords: Mapping[str, str] | None = None,
|
|
3402
|
+
sudo_mode: str = OTA_DEFAULT_SUDO_MODE,
|
|
3403
|
+
shaping_image: str | None = None,
|
|
3300
3404
|
) -> list[Any]:
|
|
3301
3405
|
"""Arm the static profile per direction on every peer's OTA egress (RFC 0004),
|
|
3302
3406
|
returning the ``ProfileShaper`` handles to revert in the run's ``finally``."""
|
|
@@ -3320,10 +3424,22 @@ def _ota_arm_profile(
|
|
|
3320
3424
|
sudo_password = (sudo_passwords or {}).get(peer_name)
|
|
3321
3425
|
shaper = ProfileShaper(
|
|
3322
3426
|
ota_iface,
|
|
3323
|
-
_peer_command_runner(
|
|
3427
|
+
_peer_command_runner(
|
|
3428
|
+
peer,
|
|
3429
|
+
dry_run=dry_run,
|
|
3430
|
+
sudo_password=sudo_password,
|
|
3431
|
+
sudo_mode=sudo_mode,
|
|
3432
|
+
shaping_image=shaping_image,
|
|
3433
|
+
),
|
|
3324
3434
|
control_interface=control_iface,
|
|
3325
3435
|
safety_max_duration_s=_PROFILE_SAFETY_MAX_S,
|
|
3326
|
-
watchdog_launcher=_peer_watchdog_launcher(
|
|
3436
|
+
watchdog_launcher=_peer_watchdog_launcher(
|
|
3437
|
+
peer,
|
|
3438
|
+
dry_run=dry_run,
|
|
3439
|
+
sudo_password=sudo_password,
|
|
3440
|
+
sudo_mode=sudo_mode,
|
|
3441
|
+
shaping_image=shaping_image,
|
|
3442
|
+
),
|
|
3327
3443
|
)
|
|
3328
3444
|
# Register before arming so a mid-arm failure is still reverted in `finally`.
|
|
3329
3445
|
shapers.append(shaper)
|
|
@@ -4085,7 +4201,20 @@ def _start_noninteractive_ota_smoke(args: argparse.Namespace) -> int:
|
|
|
4085
4201
|
errors: list[str] = []
|
|
4086
4202
|
try:
|
|
4087
4203
|
if profile is not None:
|
|
4088
|
-
shapers = _ota_arm_profile(
|
|
4204
|
+
shapers = _ota_arm_profile(
|
|
4205
|
+
plan,
|
|
4206
|
+
profile,
|
|
4207
|
+
directions,
|
|
4208
|
+
dry_run=dry_run,
|
|
4209
|
+
sudo_passwords=_ota_network_sudo_passwords(
|
|
4210
|
+
plan,
|
|
4211
|
+
sudo_mode=getattr(args, "sudo_mode", OTA_DEFAULT_SUDO_MODE),
|
|
4212
|
+
require_network_shaping_sudo=True,
|
|
4213
|
+
dry_run=dry_run,
|
|
4214
|
+
),
|
|
4215
|
+
sudo_mode=getattr(args, "sudo_mode", OTA_DEFAULT_SUDO_MODE),
|
|
4216
|
+
shaping_image=getattr(args, "shaping_image", None),
|
|
4217
|
+
)
|
|
4089
4218
|
_ota_start_peers(
|
|
4090
4219
|
target,
|
|
4091
4220
|
plan,
|
|
@@ -9039,6 +9168,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
9039
9168
|
action="store_true",
|
|
9040
9169
|
help="Keep staged source and project files after cleanup.",
|
|
9041
9170
|
)
|
|
9171
|
+
ota_smoke_parser.add_argument(
|
|
9172
|
+
"--sudo-mode",
|
|
9173
|
+
choices=OTA_SUDO_MODES,
|
|
9174
|
+
default=OTA_DEFAULT_SUDO_MODE,
|
|
9175
|
+
help=(
|
|
9176
|
+
"How profile shaping obtains tc/ip privileges on the peers "
|
|
9177
|
+
"(passwordless: require sudo -n; askpass: prompt locally per peer and feed sudo -S; "
|
|
9178
|
+
"container: no sudo -- run each tc/ip in a short-lived NET_ADMIN host-netns container, "
|
|
9179
|
+
"for peers with Docker but no passwordless sudo)."
|
|
9180
|
+
),
|
|
9181
|
+
)
|
|
9182
|
+
ota_smoke_parser.add_argument(
|
|
9183
|
+
"--shaping-image",
|
|
9184
|
+
help=(
|
|
9185
|
+
"Image for --sudo-mode container (must contain tc/ip). Default: resolve on the peer -- "
|
|
9186
|
+
"the image of a running rosotacom_ container, else the newest local ros-communication* image."
|
|
9187
|
+
),
|
|
9188
|
+
)
|
|
9042
9189
|
ota_smoke_parser.add_argument("--skip-preflight", action="store_true", help="Skip SSH/Docker readiness checks.")
|
|
9043
9190
|
ota_smoke_parser.add_argument(
|
|
9044
9191
|
"--skip-conflict-check",
|