rosotacom-dev 2.5.dev41__tar.gz → 2.5.dev42__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.dev41 → rosotacom_dev-2.5.dev42}/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0004-network-profiles.md +44 -7
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/cli_benchmark.py +24 -0
- rosotacom_dev-2.5.dev42/src/rosotacom/netem_dist.py +104 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/network_profiles.py +162 -17
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/PKG-INFO +1 -1
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/SOURCES.txt +2 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/scm_file_list.json +2 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/scm_version.json +2 -2
- rosotacom_dev-2.5.dev42/tests/unit/test_netem_dist.py +62 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_network_profiles.py +87 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.devcontainer/devcontainer.json.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.devcontainer/re_generate_devcontainer_json.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/CODEOWNERS +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/documentation-audit.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/implementation-cleanup.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/quality-workflow.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/redesign.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/release-process.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/repository-diagnosis.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/test-ci-audit.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/ISSUE_TEMPLATE/work-item.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/actions/setup-just/action.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/dependabot.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/benchmark-calibrate.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/benchmark-gate.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/dev-release.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/image-scan.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/nightly-e2e.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/pr-lightweight.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/pr-merge-gate.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.github/workflows/release.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/.pre-commit-config.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/AGENTS.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/CONCURRENCY.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/CONTRIBUTING.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/DEVELOPMENT_PRINCIPLES.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/MANIFEST.in +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/README.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/budgets.jsonl +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/README.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/capacity-size-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-gop-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-nominal-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-tight-cyclone.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-tight-fastdds.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/calibration/probe-loss-tight-zenoh.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/codecov.yml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/deployment-configuration.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/benchmark-ab.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/benchmark-bag-as-load.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/ci.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/ffmpeg-keyframes.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/README.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/cyclone-spdp-discovery-bursts.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/delay-alone-no-loss.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/head-of-line-irregularity.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/jitter-loss-bandwidth-latency.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/lighter-message-loss.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/findings/zero-loss-boundaries-18kb-20hz.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/forensics-report.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/geo-link-map.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/github-repository-settings.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/link-bandwidth.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/link-trace.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/owner-runbook.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/performance-bands.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/playbooks/README.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/quality-model.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/TEMPLATE.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.1.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.2.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.3.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release-notes/v2.4.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/release.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0001-expectation-driven-test-suite.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0002-expectation-concepts.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0003-metric-backbone.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0005-benchmark-genres-and-ci.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0006-dynamic-qos.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/0007-regression-gate.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/rfcs/README.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/testing.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/docs/work-items.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/install.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/justfile +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/control_center.png +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/echo_test.png +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/forensics-report-example.png +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/host.png +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/media/vehicle.png +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/pyproject.toml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/requirements.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/session-configuration.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/__init__.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/__main__.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/anonymize.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/benched_set.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/benchmark.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/cli.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/deployment.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/forensics.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/geomap.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/image_cache.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/plots.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/py.typed +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/benched-set.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/.gitignore +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/README.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/__init__.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/bags/8_drop_reference/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/deployment.example.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/profiles.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/rosotacom.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scenarios/2_native_chatter/scenario-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_a.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/1_heartbeat/run_machine_b.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_a/wait_for_echo_topic.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/2_native_chatter/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/3_comp_occ_grid/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/foxglove.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/costmap_1second_0.mcap +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/costmap_1second/metadata.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/4_comp_occ_grid_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/5_sized_payload/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_a/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external.ros2docker.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/external_session.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_communication.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/scripts/6_sized_payload_zen/machine_b/run_external.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/10_restamp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/11_trickle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/12_content_integrity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/13_link_latency/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/14_remote_assist_anonymized/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/15_remote_assist_anonymized_costmap/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/16_remote_assist_anonymized_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/17_synthetic_camera_quality/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/1_heartbeat/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/1_heartbeat_status/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/2_native_chatter/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/3_comp_occ_grid/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/4_comp_occ_grid_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/5_sized_payload/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/6_sized_payload_zen/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/7_latched_static/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/8_drop/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/9_throttle/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_1_capacity/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_2_load_sweep/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_3_ramp/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_4_recovery/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/examples/sessions/bench_1_5_camera/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ros2docker.json.example +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/.vscode/c_cpp_properties.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/.vscode/settings.json +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_easy_mode.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_initial_peer_list.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v2.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v3.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v4.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/archive/fastdds_v5.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/create_zenoh_json5.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/cyclonedds_local_participants.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/cyclonedds_minimal.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/cyclonedds_tuned.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/fastdds_unicast.xml.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/get_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/utils.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ota_configs/zenoh.json5.template +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/.gitkeep +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/COLCON_IGNORE +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/package.xml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/src/hz_monitor.cpp +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_compressor.cpp +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_cpp/src/universal_decompressor.cpp +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/CMakeLists.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/CompressedData.msg +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/EchoHeartbeat.msg +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/OtaStamped.msg +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/msg/SizedPayload.msg +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_msgs/package.xml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/__init__.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/base_bridge_relay.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_in.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/bridge_out.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/heartbeat_echo.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/hz_monitor.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_bandwidth_reducer.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/image_pixel_capper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/lanelet_saver.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/latch_relay.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/costmap_remapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/marker_remapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/speed_test.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/tf_remapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/traffic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/legacy/wifi_network_analysis_1.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/pub_sub_pair.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/qos.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_in.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/relay_out.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/topic_resolution.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/trickle.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_compressor.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_decompressor.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/com_py/universal_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/package.xml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/resource/com_py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/setup.cfg +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/setup.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/test/test_copyright.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/test/test_flake8.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/ros2src/com_py/test/test_pep257.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/content/base/session_plugin_base.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/content/network/up_connectivity.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/content/network/wifi_network_analysis_2.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/anonymize_bag.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/catmux_log_setup.sh +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/create_session_yaml.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/generate_session_files.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/run_session.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/resources/ws/session/creation/strip_ansi.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/status_eval.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/transit.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom/video_quality.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/dependency_links.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/requires.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/top_level.txt +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/terminology.md +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_benched_set_registry.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_findings.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_markdown_links.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_ota_source_bundle.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_package_metadata.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_packaged_resources.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_profile_covers_generated_commands.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_pytest_policy.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_readme_examples.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_security_maintenance_config.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_session_configuration_doc.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_session_template_substitution.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/contract/test_workflow_contracts.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/conftest.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_anonymize_e2e.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_benchmark_ab.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_benchmark_capacity.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_benchmark_replay.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_parallel_smoke.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_smoke.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_timeline_stepping.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/e2e/test_video_quality_e2e.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/generate_rmw_matrix.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-local_zenoh-ros2dds-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_cyclone-ota-tuned/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_fastdds/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/sessions/rmw_matrix/1_heartbeat_zen-endpoints/session-definition.yaml +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_anonymize.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_bag_ground_truth.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_benched_set.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_benchmark_delta.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_bundle_check.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_catmux_pane_logging.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_cli.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_cli_benchmark.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_concurrency.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_deployment.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ffmpeg_packet.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_forensics.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_geomap.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_image_cache.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_link_bytes.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_link_trace.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_network_shaper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ota_unwrapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ota_wrapper.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_ota_xml.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_plots.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_qos_fallback_logging.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_relay_optional_parameters.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_session_pipeline.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_stage_latency.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_status_eval.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_status_overview.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_status_overview_observer.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_stream_stats.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_trace_profiles.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_transit.py +0 -0
- {rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/tests/unit/test_video_quality.py +0 -0
|
@@ -91,11 +91,24 @@ profiles:
|
|
|
91
91
|
- { for: 40s, uplink: { rate: 2.5mbit, delay: 90ms, loss: 1% } }
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
- **static** — constant `{rate, delay, jitter, distribution,
|
|
95
|
-
loss_correlation, reorder, duplicate, seed}` per
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
- **static** — constant `{rate, delay, jitter, distribution, distribution_file,
|
|
95
|
+
loss, loss_correlation, loss_gemodel, reorder, duplicate, seed}` per
|
|
96
|
+
direction. Two loss models, mutually exclusive per direction:
|
|
97
|
+
`loss` (+ optional `loss_correlation`) is netem's independent/correlated
|
|
98
|
+
per-packet loss; `loss_gemodel: {p, r, loss_bad, loss_good}` is the
|
|
99
|
+
Gilbert–Elliott state model (`tc netem loss gemodel`), the vocabulary a real
|
|
100
|
+
cellular link needs — rare transitions into a short intense bad state
|
|
101
|
+
(`p`/`r` per packet) with distinct loss probabilities inside (`loss_bad`)
|
|
102
|
+
and outside (`loss_good`) of it. The 2026-08-17 CCNG field fit is the
|
|
103
|
+
reference: `p 0.12%, r 2.9%, loss_bad 2.7%, loss_good 0.098%` reproduces
|
|
104
|
+
113 ms bad states every ~2.8 s. `distribution` accepts the iproute2
|
|
105
|
+
builtins (`normal|pareto|paretonormal`) or a custom table name whose
|
|
106
|
+
`distribution_file` (a `.dist` built by `python -m rosotacom.netem_dist`
|
|
107
|
+
from measured delay samples) the runner installs into the shaping netns as
|
|
108
|
+
`/usr/lib/tc/<name>.dist` — because a field link's delay is
|
|
109
|
+
lognormal-tailed and `normal` under-tails p99 by ~2x. `seed` maps to
|
|
110
|
+
`tc netem seed SEED` when supported, making the generated random
|
|
111
|
+
delay/loss draw replayable.
|
|
99
112
|
- **timeline** — an ordered list of `{ for: <dur>, <static-params> | outage }`
|
|
100
113
|
segments. A recovery benchmark (RFC 0005) *is* a timeline profile; `outage` is
|
|
101
114
|
the step whose recovery is measured.
|
|
@@ -189,8 +202,11 @@ RFC 0002's `calibrate` / `--suggest` machinery, **per profile**:
|
|
|
189
202
|
the fluctuation `netem` does not reproduce by default. Emulation buys
|
|
190
203
|
**reproducibility, not truth** — rung 4 (real cellular) calibrates the
|
|
191
204
|
*shape* of the emulated profile; the profile then gives repeatable gating.
|
|
192
|
-
- **Bursty loss needs the state model.** Default `netem loss` is independent;
|
|
193
|
-
`
|
|
205
|
+
- **Bursty loss needs the state model.** Default `netem loss` is independent; the
|
|
206
|
+
2-arg `loss p% corr%` cannot express short-bad/long-good either. `loss_gemodel`
|
|
207
|
+
carries the Gilbert–Elliott parameters directly (see the schema section); fit
|
|
208
|
+
`p`/`r` from the measured bad/good run lengths (per packet) and the two loss
|
|
209
|
+
probabilities from the loss rate inside/outside bad states.
|
|
194
210
|
- **Outage semantics are a choice.** `netem loss 100%` keeps the interface up (DDS
|
|
195
211
|
endpoints survive, recovery is "catch up") vs link-down (forces RMW
|
|
196
212
|
re-discovery, a harsher recovery). They test different recoveries — see Open
|
|
@@ -215,6 +231,12 @@ risk — a stuck `qdisc` silently corrupts every later result on the machine.
|
|
|
215
231
|
`profiles.yaml` referenced from `rosotacom.yaml`), and resolve selection via
|
|
216
232
|
`--profile <name>` / `shared.profile` / `none` (`resolve_profile_selection`,
|
|
217
233
|
`cli._resolve_active_profile`).
|
|
234
|
+
- [x] Extend the loss/delay vocabulary to what field links actually need
|
|
235
|
+
(issue #278): `loss_gemodel` (Gilbert–Elliott state loss, exclusive with
|
|
236
|
+
`loss`) and custom delay-distribution tables (`distribution` beyond the
|
|
237
|
+
builtins + `distribution_file`, generated from measured samples by
|
|
238
|
+
`rosotacom.netem_dist`, installed into the shaping netns by the lab runner
|
|
239
|
+
before arming).
|
|
218
240
|
- [x] Arm a named static profile on the OTA-interface egress with fail-safe
|
|
219
241
|
teardown (revert on stop, on error, and via a safety max-duration watchdog);
|
|
220
242
|
target the data interface only, never the SSH/control interface
|
|
@@ -268,6 +290,21 @@ off during implementation). Notes whether automation is feasible; privileged
|
|
|
268
290
|
for a given profile/direction (rate/delay/jitter/loss/correlation → `tbf`+`netem`
|
|
269
291
|
string), without touching a real interface (`test_shaping_commands_*`,
|
|
270
292
|
`test_netem_arg_ordering_is_valid`). Done.
|
|
293
|
+
- [x] **Gilbert–Elliott loss (`loss_gemodel`)** — host unit tests on the schema
|
|
294
|
+
(required/unknown keys, exclusivity against `loss`) and on the argv (all four
|
|
295
|
+
probabilities always emitted): `test_gemodel_parse_and_exclusivity`,
|
|
296
|
+
`test_gemodel_argv_emits_all_four_probabilities`. The *statistical* fidelity of
|
|
297
|
+
the emitted process (bad-run length, in-state loss rates) stays a bench check
|
|
298
|
+
against a field fit — the 2026-08-17 CCNG fragment replay is the reference.
|
|
299
|
+
- [x] **Custom delay-distribution tables** — host unit tests: a non-builtin
|
|
300
|
+
`distribution` requires `distribution_file` and resolves relative to the
|
|
301
|
+
profiles file (`test_custom_distribution_requires_a_file_and_builtin_does_not`,
|
|
302
|
+
`test_load_profiles_file_resolves_distribution_files_and_lists_installs`);
|
|
303
|
+
table generation is normalized, monotonic, tail-preserving
|
|
304
|
+
(`test_netem_dist.py`). The lab runner installs required tables into the
|
|
305
|
+
shaping netns before arming (`required_dist_installs` consumed in
|
|
306
|
+
`cli_benchmark`); that a netem referencing the installed name actually loads
|
|
307
|
+
it is part of the same bench check as arming itself.
|
|
271
308
|
- [~] **Fail-safe teardown** (revert on stop, on error, on safety max-duration; the
|
|
272
309
|
idempotent `tc qdisc del … root` always runs; data-interface-only, never the
|
|
273
310
|
SSH/control interface) — host unit test on the teardown/targeting logic done
|
|
@@ -5514,6 +5514,30 @@ def _make_live_run_point(args: argparse.Namespace, session_name: str) -> RunPoin
|
|
|
5514
5514
|
|
|
5515
5515
|
# 3. Arm the network shapers (after discovery is complete)
|
|
5516
5516
|
if profile_obj is not None:
|
|
5517
|
+
from .network_profiles import required_dist_installs
|
|
5518
|
+
|
|
5519
|
+
dist_installs = required_dist_installs(profile_obj)
|
|
5520
|
+
if dist_installs:
|
|
5521
|
+
# Custom delay-distribution tables must exist inside the
|
|
5522
|
+
# shaping netns before netem references them by name.
|
|
5523
|
+
for dist_container in (a_container, b_container):
|
|
5524
|
+
if not dist_container:
|
|
5525
|
+
continue
|
|
5526
|
+
for table_name, file_path in dist_installs:
|
|
5527
|
+
dist_target = f"/usr/lib/tc/{table_name}.dist"
|
|
5528
|
+
for install_cmd in (
|
|
5529
|
+
["docker", "exec", "-u", "root", dist_container, "mkdir", "-p", "/usr/lib/tc"],
|
|
5530
|
+
["docker", "cp", file_path, f"{dist_container}:{dist_target}"],
|
|
5531
|
+
):
|
|
5532
|
+
res = subprocess.run(install_cmd, capture_output=True, text=True, check=False)
|
|
5533
|
+
if res.returncode != 0:
|
|
5534
|
+
raise RuntimeError(
|
|
5535
|
+
f"failed to install netem distribution table {table_name!r} in "
|
|
5536
|
+
f"{dist_container}: {' '.join(install_cmd)} -> {res.stderr or res.stdout}"
|
|
5537
|
+
)
|
|
5538
|
+
print(
|
|
5539
|
+
f" netem distribution {table_name!r} installed in {dist_container}: {dist_target}"
|
|
5540
|
+
)
|
|
5517
5541
|
if profile_obj.is_timeline:
|
|
5518
5542
|
# Peer A shapes uplink (egress to B)
|
|
5519
5543
|
shaper_a = ProfileShaper("eth0", make_container_runner(a_container))
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Build netem delay-distribution tables (``/usr/lib/tc/*.dist``) from samples.
|
|
2
|
+
|
|
3
|
+
netem draws jitter as ``delay + jitter * table[uniform] / NETEM_DIST_SCALE``:
|
|
4
|
+
the ``distribution`` argument names a table of signed 16-bit values that encode
|
|
5
|
+
the *shape* (the inverse CDF of the normalized distribution, scaled by 8192),
|
|
6
|
+
while ``delay``/``jitter`` supply location and scale. iproute2 ships only
|
|
7
|
+
``normal``, ``pareto`` and ``paretonormal``; a field-measured delay
|
|
8
|
+
distribution (e.g. the lognormal-tailed one-way delay of a cellular uplink,
|
|
9
|
+
which ``normal`` badly under-tails) becomes reproducible by generating its own
|
|
10
|
+
table from samples and installing it next to the builtin ones.
|
|
11
|
+
|
|
12
|
+
This is the same construction as iproute2's ``maketable``: sort the samples,
|
|
13
|
+
normalize to mean 0 / stddev 1, take :data:`TABLE_SIZE` inverse-CDF points,
|
|
14
|
+
scale by :data:`NETEM_DIST_SCALE` and clamp to int16.
|
|
15
|
+
|
|
16
|
+
Usage (module CLI)::
|
|
17
|
+
|
|
18
|
+
python -m rosotacom.netem_dist samples.csv --column delay_ms --out field.dist
|
|
19
|
+
|
|
20
|
+
The output installs as ``/usr/lib/tc/field.dist`` and is then addressable as
|
|
21
|
+
``distribution: field`` (with ``distribution_file`` pointing at the file, so
|
|
22
|
+
profile runners install it into the shaping netns themselves).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import csv
|
|
29
|
+
import math
|
|
30
|
+
from collections.abc import Sequence
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
NETEM_DIST_SCALE = 8192 # kernel: delay = mu + sigma * table[r] / 8192
|
|
34
|
+
TABLE_SIZE = 4096 # entries in the shipped iproute2 tables
|
|
35
|
+
_INT16_MIN, _INT16_MAX = -32768, 32767
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_dist_table(samples: Sequence[float], *, table_size: int = TABLE_SIZE) -> list[int]:
|
|
39
|
+
"""Inverse-CDF table of the normalized ``samples`` (int16, netem scaling)."""
|
|
40
|
+
values = sorted(float(x) for x in samples if math.isfinite(float(x)))
|
|
41
|
+
if len(values) < 100:
|
|
42
|
+
raise ValueError(f"need at least 100 finite samples for a stable table, got {len(values)}")
|
|
43
|
+
mean = sum(values) / len(values)
|
|
44
|
+
var = sum((x - mean) ** 2 for x in values) / len(values)
|
|
45
|
+
sigma = math.sqrt(var)
|
|
46
|
+
if sigma <= 0:
|
|
47
|
+
raise ValueError("samples have zero spread; a distribution table is meaningless")
|
|
48
|
+
table: list[int] = []
|
|
49
|
+
n = len(values)
|
|
50
|
+
for i in range(table_size):
|
|
51
|
+
# midpoint quantile of bucket i, linearly interpolated in the sorted samples
|
|
52
|
+
q = (i + 0.5) / table_size
|
|
53
|
+
pos = q * (n - 1)
|
|
54
|
+
lo = int(pos)
|
|
55
|
+
frac = pos - lo
|
|
56
|
+
x = values[lo] if lo + 1 >= n else values[lo] * (1 - frac) + values[lo + 1] * frac
|
|
57
|
+
z = (x - mean) / sigma
|
|
58
|
+
table.append(max(_INT16_MIN, min(_INT16_MAX, round(z * NETEM_DIST_SCALE))))
|
|
59
|
+
return table
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def format_dist_file(table: Sequence[int], *, comment: str | None = None) -> str:
|
|
63
|
+
"""Render a table in the ``/usr/lib/tc/*.dist`` text format (8 values/line)."""
|
|
64
|
+
lines = [f"# {line}" for line in (comment or "").splitlines() if line]
|
|
65
|
+
for i in range(0, len(table), 8):
|
|
66
|
+
lines.append(" ".join(f"{v:d}" for v in table[i : i + 8]))
|
|
67
|
+
return "\n".join(lines) + "\n"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def read_samples_csv(path: str | Path, column: str) -> list[float]:
|
|
71
|
+
out: list[float] = []
|
|
72
|
+
with open(path, newline="", encoding="utf-8") as fp:
|
|
73
|
+
reader = csv.DictReader(fp)
|
|
74
|
+
if reader.fieldnames is None or column not in reader.fieldnames:
|
|
75
|
+
raise ValueError(f"{path}: no column {column!r}; columns: {reader.fieldnames}")
|
|
76
|
+
for row in reader:
|
|
77
|
+
text = (row.get(column) or "").strip()
|
|
78
|
+
if text:
|
|
79
|
+
try:
|
|
80
|
+
out.append(float(text))
|
|
81
|
+
except ValueError:
|
|
82
|
+
continue
|
|
83
|
+
return out
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
87
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
88
|
+
parser.add_argument("samples_csv", help="CSV with one numeric column of delay samples")
|
|
89
|
+
parser.add_argument("--column", default="delay_ms", help="column to read (default: delay_ms)")
|
|
90
|
+
parser.add_argument("--out", required=True, help="output .dist path (basename = table name)")
|
|
91
|
+
parser.add_argument("--comment", default=None, help="provenance line(s) for the file header")
|
|
92
|
+
args = parser.parse_args(argv)
|
|
93
|
+
samples = read_samples_csv(args.samples_csv, args.column)
|
|
94
|
+
table = build_dist_table(samples)
|
|
95
|
+
comment = args.comment or (
|
|
96
|
+
f"generated by rosotacom.netem_dist from {Path(args.samples_csv).name} ({len(samples)} samples)"
|
|
97
|
+
)
|
|
98
|
+
Path(args.out).write_text(format_dist_file(table, comment=comment), encoding="utf-8")
|
|
99
|
+
print(f"wrote {args.out}: {len(table)} entries from {len(samples)} samples")
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
raise SystemExit(main())
|
|
@@ -136,6 +136,38 @@ def parse_seed(value: Any, what: str = "seed") -> int:
|
|
|
136
136
|
# Schema
|
|
137
137
|
# --------------------------------------------------------------------------- #
|
|
138
138
|
|
|
139
|
+
# Distribution tables iproute2 ships; anything else needs a ``distribution_file``
|
|
140
|
+
# that the runner installs into the shaping netns as ``/usr/lib/tc/<name>.dist``.
|
|
141
|
+
_BUILTIN_DISTRIBUTIONS = ("normal", "pareto", "paretonormal")
|
|
142
|
+
_DISTRIBUTION_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True)
|
|
146
|
+
class GemodelLoss:
|
|
147
|
+
"""netem ``loss gemodel`` — the Gilbert(-Elliott) two-state loss process.
|
|
148
|
+
|
|
149
|
+
Per-*packet* state transitions (netem evaluates the chain once per packet):
|
|
150
|
+
``p_pct`` = P(good→bad), ``r_pct`` = P(bad→good), ``loss_bad_pct`` = loss
|
|
151
|
+
probability while bad (netem's ``1-h`` argument), ``loss_good_pct`` = loss
|
|
152
|
+
probability while good (netem's ``1-k``). All four are always emitted, so the
|
|
153
|
+
argv never depends on iproute2 defaults. This is the vocabulary the plain
|
|
154
|
+
2-arg ``loss p% corr%`` cannot express: short intense bad states inside long
|
|
155
|
+
clean good states, as measured on real cellular links.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
p_pct: float
|
|
159
|
+
r_pct: float
|
|
160
|
+
loss_bad_pct: float = 100.0
|
|
161
|
+
loss_good_pct: float = 0.0
|
|
162
|
+
|
|
163
|
+
def __post_init__(self) -> None:
|
|
164
|
+
for name in ("p_pct", "r_pct", "loss_bad_pct", "loss_good_pct"):
|
|
165
|
+
value = getattr(self, name)
|
|
166
|
+
if not 0.0 <= value <= 100.0:
|
|
167
|
+
raise ValueError(f"gemodel {name} must be within [0, 100]%, got {value!r}")
|
|
168
|
+
if self.p_pct == 0.0 and self.loss_good_pct == 0.0:
|
|
169
|
+
raise ValueError("gemodel with p=0 and loss_good=0 never loses anything; drop the block instead")
|
|
170
|
+
|
|
139
171
|
|
|
140
172
|
@dataclass(frozen=True)
|
|
141
173
|
class DirectionShaping:
|
|
@@ -145,9 +177,11 @@ class DirectionShaping:
|
|
|
145
177
|
rate_bps: float | None = None
|
|
146
178
|
delay_ms: float | None = None
|
|
147
179
|
jitter_ms: float | None = None
|
|
148
|
-
distribution: str | None = None # normal
|
|
180
|
+
distribution: str | None = None # builtin (normal|pareto|paretonormal) or a custom table name
|
|
181
|
+
distribution_file: str | None = None # .dist file for a custom table (installed as /usr/lib/tc/<name>.dist)
|
|
149
182
|
loss_pct: float | None = None
|
|
150
183
|
loss_correlation_pct: float | None = None # netem correlated loss (needs loss)
|
|
184
|
+
loss_gemodel: GemodelLoss | None = None # netem Gilbert-Elliott loss (exclusive with loss)
|
|
151
185
|
reorder_pct: float | None = None # netem reorder (needs delay)
|
|
152
186
|
duplicate_pct: float | None = None
|
|
153
187
|
seed: int | None = None
|
|
@@ -156,23 +190,29 @@ class DirectionShaping:
|
|
|
156
190
|
|
|
157
191
|
@property
|
|
158
192
|
def is_empty(self) -> bool:
|
|
159
|
-
return
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
193
|
+
return (
|
|
194
|
+
all(
|
|
195
|
+
getattr(self, name) is None
|
|
196
|
+
for name in (
|
|
197
|
+
"rate_bps",
|
|
198
|
+
"delay_ms",
|
|
199
|
+
"jitter_ms",
|
|
200
|
+
"loss_pct",
|
|
201
|
+
"reorder_pct",
|
|
202
|
+
"duplicate_pct",
|
|
203
|
+
)
|
|
168
204
|
)
|
|
205
|
+
and self.loss_gemodel is None
|
|
169
206
|
)
|
|
170
207
|
|
|
171
208
|
@property
|
|
172
209
|
def has_netem(self) -> bool:
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
210
|
+
return (
|
|
211
|
+
any(
|
|
212
|
+
getattr(self, name) is not None
|
|
213
|
+
for name in ("delay_ms", "jitter_ms", "loss_pct", "reorder_pct", "duplicate_pct")
|
|
214
|
+
)
|
|
215
|
+
or self.loss_gemodel is not None
|
|
176
216
|
)
|
|
177
217
|
|
|
178
218
|
def __post_init__(self) -> None:
|
|
@@ -182,10 +222,21 @@ class DirectionShaping:
|
|
|
182
222
|
raise ValueError("distribution requires a jitter")
|
|
183
223
|
if self.loss_correlation_pct is not None and self.loss_pct is None:
|
|
184
224
|
raise ValueError("loss_correlation requires a loss")
|
|
225
|
+
if self.loss_gemodel is not None and self.loss_pct is not None:
|
|
226
|
+
raise ValueError("loss_gemodel and loss are exclusive: one loss model per direction")
|
|
185
227
|
if self.reorder_pct is not None and self.delay_ms is None:
|
|
186
228
|
raise ValueError("reorder requires a delay (netem reorders within the delay window)")
|
|
187
|
-
if self.distribution is not None
|
|
188
|
-
|
|
229
|
+
if self.distribution is not None:
|
|
230
|
+
if not _DISTRIBUTION_NAME_RE.fullmatch(self.distribution):
|
|
231
|
+
raise ValueError(f"unsupported distribution name {self.distribution!r}")
|
|
232
|
+
if self.distribution not in _BUILTIN_DISTRIBUTIONS and self.distribution_file is None:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"distribution {self.distribution!r} is not built into iproute2 "
|
|
235
|
+
f"({', '.join(_BUILTIN_DISTRIBUTIONS)}); give distribution_file so the "
|
|
236
|
+
"runner can install /usr/lib/tc/" + self.distribution + ".dist"
|
|
237
|
+
)
|
|
238
|
+
if self.distribution_file is not None and self.distribution is None:
|
|
239
|
+
raise ValueError("distribution_file requires a distribution name")
|
|
189
240
|
|
|
190
241
|
|
|
191
242
|
@dataclass(frozen=True)
|
|
@@ -235,8 +286,10 @@ _DIRECTION_KEYS = frozenset(
|
|
|
235
286
|
"delay",
|
|
236
287
|
"jitter",
|
|
237
288
|
"distribution",
|
|
289
|
+
"distribution_file",
|
|
238
290
|
"loss",
|
|
239
291
|
"loss_correlation",
|
|
292
|
+
"loss_gemodel",
|
|
240
293
|
"reorder",
|
|
241
294
|
"duplicate",
|
|
242
295
|
"seed",
|
|
@@ -245,6 +298,26 @@ _DIRECTION_KEYS = frozenset(
|
|
|
245
298
|
}
|
|
246
299
|
)
|
|
247
300
|
|
|
301
|
+
_GEMODEL_KEYS = frozenset({"p", "r", "loss_bad", "loss_good"})
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def parse_gemodel(spec: Mapping[str, Any]) -> GemodelLoss:
|
|
305
|
+
"""Parse a ``loss_gemodel`` block (``p``/``r`` required, loss_bad/loss_good optional)."""
|
|
306
|
+
if not isinstance(spec, Mapping):
|
|
307
|
+
raise ValueError(f"loss_gemodel: expected a mapping with p/r[/loss_bad/loss_good], got {spec!r}")
|
|
308
|
+
unknown = sorted(set(spec) - _GEMODEL_KEYS)
|
|
309
|
+
if unknown:
|
|
310
|
+
raise ValueError(f"unsupported loss_gemodel keys {unknown}; allowed: {sorted(_GEMODEL_KEYS)}")
|
|
311
|
+
missing = sorted({"p", "r"} - set(spec))
|
|
312
|
+
if missing:
|
|
313
|
+
raise ValueError(f"loss_gemodel requires {missing}")
|
|
314
|
+
return GemodelLoss(
|
|
315
|
+
p_pct=parse_pct(spec["p"], "loss_gemodel p"),
|
|
316
|
+
r_pct=parse_pct(spec["r"], "loss_gemodel r"),
|
|
317
|
+
loss_bad_pct=parse_pct(spec["loss_bad"], "loss_gemodel loss_bad") if "loss_bad" in spec else 100.0,
|
|
318
|
+
loss_good_pct=parse_pct(spec["loss_good"], "loss_gemodel loss_good") if "loss_good" in spec else 0.0,
|
|
319
|
+
)
|
|
320
|
+
|
|
248
321
|
|
|
249
322
|
def parse_direction(spec: Mapping[str, Any] | None) -> DirectionShaping | None:
|
|
250
323
|
"""Parse one ``uplink``/``downlink`` block into a :class:`DirectionShaping`."""
|
|
@@ -258,10 +331,12 @@ def parse_direction(spec: Mapping[str, Any] | None) -> DirectionShaping | None:
|
|
|
258
331
|
delay_ms=parse_ms(spec["delay"], "delay") if "delay" in spec else None,
|
|
259
332
|
jitter_ms=parse_ms(spec["jitter"], "jitter") if "jitter" in spec else None,
|
|
260
333
|
distribution=str(spec["distribution"]) if "distribution" in spec else None,
|
|
334
|
+
distribution_file=str(spec["distribution_file"]) if "distribution_file" in spec else None,
|
|
261
335
|
loss_pct=parse_pct(spec["loss"], "loss") if "loss" in spec else None,
|
|
262
336
|
loss_correlation_pct=(
|
|
263
337
|
parse_pct(spec["loss_correlation"], "loss_correlation") if "loss_correlation" in spec else None
|
|
264
338
|
),
|
|
339
|
+
loss_gemodel=parse_gemodel(spec["loss_gemodel"]) if "loss_gemodel" in spec else None,
|
|
265
340
|
reorder_pct=parse_pct(spec["reorder"], "reorder") if "reorder" in spec else None,
|
|
266
341
|
duplicate_pct=parse_pct(spec["duplicate"], "duplicate") if "duplicate" in spec else None,
|
|
267
342
|
seed=parse_seed(spec["seed"], "seed") if "seed" in spec else None,
|
|
@@ -330,13 +405,71 @@ def parse_profiles(doc: Mapping[str, Any]) -> dict[str, Profile]:
|
|
|
330
405
|
|
|
331
406
|
|
|
332
407
|
def load_profiles_file(path: str | Path) -> dict[str, Profile]:
|
|
333
|
-
"""Load a project-scoped ``profiles.yaml`` into ``{name: Profile}``.
|
|
408
|
+
"""Load a project-scoped ``profiles.yaml`` into ``{name: Profile}``.
|
|
409
|
+
|
|
410
|
+
Relative ``distribution_file`` entries are resolved against the profiles
|
|
411
|
+
file's own directory, so a profiles file stays self-contained wherever the
|
|
412
|
+
project is checked out.
|
|
413
|
+
"""
|
|
334
414
|
import yaml
|
|
335
415
|
|
|
336
416
|
doc = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
|
|
337
417
|
if not isinstance(doc, Mapping):
|
|
338
418
|
raise ValueError(f"{path}: profiles file must be a mapping (with a top-level 'profiles:' block)")
|
|
339
|
-
|
|
419
|
+
profiles = parse_profiles(doc)
|
|
420
|
+
base = Path(path).resolve().parent
|
|
421
|
+
return {name: _resolve_distribution_files(profile, base) for name, profile in profiles.items()}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _resolve_distribution_files(profile: Profile, base: Path) -> Profile:
|
|
425
|
+
from dataclasses import replace
|
|
426
|
+
|
|
427
|
+
def fix(shaping: DirectionShaping | None) -> DirectionShaping | None:
|
|
428
|
+
if shaping is None or shaping.distribution_file is None:
|
|
429
|
+
return shaping
|
|
430
|
+
file = Path(shaping.distribution_file)
|
|
431
|
+
if not file.is_absolute():
|
|
432
|
+
file = base / file
|
|
433
|
+
return replace(shaping, distribution_file=str(file))
|
|
434
|
+
|
|
435
|
+
if profile.is_timeline:
|
|
436
|
+
segments = tuple(
|
|
437
|
+
replace(segment, uplink=fix(segment.uplink), downlink=fix(segment.downlink)) for segment in profile.timeline
|
|
438
|
+
)
|
|
439
|
+
return replace(profile, timeline=segments)
|
|
440
|
+
return replace(profile, uplink=fix(profile.uplink), downlink=fix(profile.downlink))
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def required_dist_installs(profile: Profile) -> list[tuple[str, str]]:
|
|
444
|
+
"""``(table_name, file_path)`` pairs a runner must install as
|
|
445
|
+
``/usr/lib/tc/<table_name>.dist`` inside the shaping netns before arming.
|
|
446
|
+
|
|
447
|
+
Builtin distributions need nothing; duplicates collapse; conflicting files
|
|
448
|
+
for one name are an error (two segments must mean the same table).
|
|
449
|
+
"""
|
|
450
|
+
found: dict[str, str] = {}
|
|
451
|
+
|
|
452
|
+
def visit(shaping: DirectionShaping | None) -> None:
|
|
453
|
+
if shaping is None or shaping.distribution is None:
|
|
454
|
+
return
|
|
455
|
+
if shaping.distribution in _BUILTIN_DISTRIBUTIONS or shaping.distribution_file is None:
|
|
456
|
+
return
|
|
457
|
+
existing = found.get(shaping.distribution)
|
|
458
|
+
if existing is not None and existing != shaping.distribution_file:
|
|
459
|
+
raise ValueError(
|
|
460
|
+
f"distribution {shaping.distribution!r} maps to two files: {existing!r} and "
|
|
461
|
+
f"{shaping.distribution_file!r}"
|
|
462
|
+
)
|
|
463
|
+
found[shaping.distribution] = shaping.distribution_file
|
|
464
|
+
|
|
465
|
+
if profile.is_timeline:
|
|
466
|
+
for segment in profile.timeline:
|
|
467
|
+
visit(segment.uplink)
|
|
468
|
+
visit(segment.downlink)
|
|
469
|
+
else:
|
|
470
|
+
visit(profile.uplink)
|
|
471
|
+
visit(profile.downlink)
|
|
472
|
+
return sorted(found.items())
|
|
340
473
|
|
|
341
474
|
|
|
342
475
|
# Selecting nothing — the unshaped rung (0/1) or the "no emulation" sentinel.
|
|
@@ -400,6 +533,18 @@ def _netem_args(shaping: DirectionShaping) -> list[str]:
|
|
|
400
533
|
args += ["loss", _pct(shaping.loss_pct)]
|
|
401
534
|
if shaping.loss_correlation_pct is not None:
|
|
402
535
|
args.append(_pct(shaping.loss_correlation_pct))
|
|
536
|
+
if shaping.loss_gemodel is not None:
|
|
537
|
+
gem = shaping.loss_gemodel
|
|
538
|
+
# netem's positional names are p, r, 1-h, 1-k: the 3rd/4th arguments ARE
|
|
539
|
+
# the loss probabilities in bad/good state. All four always emitted.
|
|
540
|
+
args += [
|
|
541
|
+
"loss",
|
|
542
|
+
"gemodel",
|
|
543
|
+
_pct(gem.p_pct),
|
|
544
|
+
_pct(gem.r_pct),
|
|
545
|
+
_pct(gem.loss_bad_pct),
|
|
546
|
+
_pct(gem.loss_good_pct),
|
|
547
|
+
]
|
|
403
548
|
if shaping.duplicate_pct is not None:
|
|
404
549
|
args += ["duplicate", _pct(shaping.duplicate_pct)]
|
|
405
550
|
if shaping.reorder_pct is not None:
|
|
@@ -101,6 +101,7 @@ src/rosotacom/ffmpeg_packet.py
|
|
|
101
101
|
src/rosotacom/forensics.py
|
|
102
102
|
src/rosotacom/geomap.py
|
|
103
103
|
src/rosotacom/image_cache.py
|
|
104
|
+
src/rosotacom/netem_dist.py
|
|
104
105
|
src/rosotacom/network_profiles.py
|
|
105
106
|
src/rosotacom/network_shaper.py
|
|
106
107
|
src/rosotacom/plots.py
|
|
@@ -331,6 +332,7 @@ tests/unit/test_geomap.py
|
|
|
331
332
|
tests/unit/test_image_cache.py
|
|
332
333
|
tests/unit/test_link_bytes.py
|
|
333
334
|
tests/unit/test_link_trace.py
|
|
335
|
+
tests/unit/test_netem_dist.py
|
|
334
336
|
tests/unit/test_network_profiles.py
|
|
335
337
|
tests/unit/test_network_shaper.py
|
|
336
338
|
tests/unit/test_ota_unwrapper.py
|
{rosotacom_dev-2.5.dev41 → rosotacom_dev-2.5.dev42}/src/rosotacom_dev.egg-info/scm_file_list.json
RENAMED
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"src/rosotacom/forensics.py",
|
|
104
104
|
"src/rosotacom/geomap.py",
|
|
105
105
|
"src/rosotacom/image_cache.py",
|
|
106
|
+
"src/rosotacom/netem_dist.py",
|
|
106
107
|
"src/rosotacom/network_profiles.py",
|
|
107
108
|
"src/rosotacom/network_shaper.py",
|
|
108
109
|
"src/rosotacom/plots.py",
|
|
@@ -326,6 +327,7 @@
|
|
|
326
327
|
"tests/unit/test_image_cache.py",
|
|
327
328
|
"tests/unit/test_link_bytes.py",
|
|
328
329
|
"tests/unit/test_link_trace.py",
|
|
330
|
+
"tests/unit/test_netem_dist.py",
|
|
329
331
|
"tests/unit/test_network_profiles.py",
|
|
330
332
|
"tests/unit/test_network_shaper.py",
|
|
331
333
|
"tests/unit/test_ota_unwrapper.py",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""netem distribution-table generation (rosotacom.netem_dist) — host tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from rosotacom.netem_dist import (
|
|
11
|
+
NETEM_DIST_SCALE,
|
|
12
|
+
TABLE_SIZE,
|
|
13
|
+
build_dist_table,
|
|
14
|
+
format_dist_file,
|
|
15
|
+
read_samples_csv,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_table_shape_and_normalization() -> None:
|
|
20
|
+
rng = random.Random(1)
|
|
21
|
+
samples = [rng.gauss(30.0, 10.0) for _ in range(20000)]
|
|
22
|
+
table = build_dist_table(samples)
|
|
23
|
+
assert len(table) == TABLE_SIZE
|
|
24
|
+
assert table == sorted(table) # inverse CDF is nondecreasing
|
|
25
|
+
mean = sum(table) / len(table)
|
|
26
|
+
std = math.sqrt(sum((v - mean) ** 2 for v in table) / len(table))
|
|
27
|
+
# normalized to mean 0 / std 1 in units of NETEM_DIST_SCALE
|
|
28
|
+
assert abs(mean) < 0.05 * NETEM_DIST_SCALE
|
|
29
|
+
assert abs(std - NETEM_DIST_SCALE) < 0.05 * NETEM_DIST_SCALE
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_table_preserves_a_heavy_tail() -> None:
|
|
33
|
+
# A lognormal delay (field cellular links) must keep its asymmetry: the
|
|
34
|
+
# upper tail reaches much further from the median than the lower tail.
|
|
35
|
+
rng = random.Random(2)
|
|
36
|
+
samples = [math.exp(rng.gauss(3.3, 0.6)) for _ in range(20000)]
|
|
37
|
+
table = build_dist_table(samples)
|
|
38
|
+
p50 = table[TABLE_SIZE // 2]
|
|
39
|
+
upper = table[int(TABLE_SIZE * 0.999)] - p50
|
|
40
|
+
lower = p50 - table[int(TABLE_SIZE * 0.001)]
|
|
41
|
+
assert upper > 1.8 * lower
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_rejects_degenerate_input() -> None:
|
|
45
|
+
with pytest.raises(ValueError):
|
|
46
|
+
build_dist_table([1.0] * 500) # zero spread
|
|
47
|
+
with pytest.raises(ValueError):
|
|
48
|
+
build_dist_table([1.0, 2.0]) # too few samples
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_format_and_csv_roundtrip(tmp_path) -> None:
|
|
52
|
+
csv_path = tmp_path / "samples.csv"
|
|
53
|
+
csv_path.write_text("delay_ms\n" + "\n".join(str(10 + (i % 50)) for i in range(500)) + "\n", encoding="utf-8")
|
|
54
|
+
samples = read_samples_csv(csv_path, "delay_ms")
|
|
55
|
+
assert len(samples) == 500
|
|
56
|
+
text = format_dist_file(build_dist_table(samples), comment="prov line")
|
|
57
|
+
lines = text.strip().splitlines()
|
|
58
|
+
assert lines[0] == "# prov line"
|
|
59
|
+
values = [int(v) for line in lines[1:] for v in line.split()]
|
|
60
|
+
assert len(values) == TABLE_SIZE
|
|
61
|
+
with pytest.raises(ValueError):
|
|
62
|
+
read_samples_csv(csv_path, "missing_column")
|