From a119910371b7dfed3d0b3c1095bffce73e09cc8c Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Apr 2026 13:39:50 +0100 Subject: [PATCH 1/9] Set UDP limits higher on Mac OS X when possible --- .../service/system_configurator/base.py | 2 +- .../service/system_configurator/lcm.py | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/dimos/protocol/service/system_configurator/base.py b/dimos/protocol/service/system_configurator/base.py index 347699536a..51413ea658 100644 --- a/dimos/protocol/service/system_configurator/base.py +++ b/dimos/protocol/service/system_configurator/base.py @@ -45,7 +45,7 @@ def _read_sysctl_int(name: str) -> int | None: def _write_sysctl_int(name: str, value: int) -> None: - prompt.sudo_run("sysctl", "-w", f"{name}={value}", check=True, text=True, capture_output=False) + prompt.sudo_run("sysctl", "-w", f"{name}={value}", check=True, text=True, capture_output=True) # base class for system config checks/requirements diff --git a/dimos/protocol/service/system_configurator/lcm.py b/dimos/protocol/service/system_configurator/lcm.py index 360d1c988a..355f5e9489 100644 --- a/dimos/protocol/service/system_configurator/lcm.py +++ b/dimos/protocol/service/system_configurator/lcm.py @@ -219,39 +219,41 @@ def fix(self) -> None: class BufferConfiguratorMacOS(SystemConfigurator): critical = False - MAX_POSSIBLE_RECVSPACE = 2_097_152 - MAX_POSSIBLE_BUFFER_SIZE = 8_388_608 - MAX_POSSIBLE_DGRAM_SIZE = 65_535 - # these values are based on macos 26 - TARGET_BUFFER_SIZE = MAX_POSSIBLE_BUFFER_SIZE - TARGET_RECVSPACE = MAX_POSSIBLE_RECVSPACE # we want this to be IDEAL_RMEM_SIZE but MacOS 26 (and probably in general) doesn't support it - TARGET_DGRAM_SIZE = MAX_POSSIBLE_DGRAM_SIZE + TARGET = IDEAL_RMEM_SIZE + + KEYS = ( + "kern.ipc.maxsockbuf", + "net.inet.udp.recvspace", + "net.inet.udp.maxdgram", + ) def __init__(self) -> None: - self.needs: list[tuple[str, int]] = [] + self.needs: list[tuple[str, int, int]] = [] # (key, target, current) def check(self) -> bool: self.needs.clear() - for key, target in [ - ("kern.ipc.maxsockbuf", self.TARGET_BUFFER_SIZE), - ("net.inet.udp.recvspace", self.TARGET_RECVSPACE), - ("net.inet.udp.maxdgram", self.TARGET_DGRAM_SIZE), - ]: - current = _read_sysctl_int(key) - if current is None or current < target: - self.needs.append((key, target)) + for key in self.KEYS: + current = _read_sysctl_int(key) or 0 + if current < self.TARGET: + self.needs.append((key, self.TARGET, current)) return not self.needs def explanation(self) -> str | None: lines = [] - for key, target in self.needs: + for key, target, _ in self.needs: lines.append(f"- socket buffer optimization for LCM: sudo sysctl -w {key}={target}") return "\n".join(lines) def fix(self) -> None: - for key, target in self.needs: - _write_sysctl_int(key, target) + for key, target, current in self.needs: + while target > current: + try: + _write_sysctl_int(key, target) + except subprocess.CalledProcessError: + target //= 2 + else: + break # specific checks: ulimit From 6deb28325a95bc6be1bb23f705cbdf742ba790f8 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Apr 2026 13:42:03 +0100 Subject: [PATCH 2/9] Revert SHM transport on Mac --- .../go2/blueprints/basic/unitree_go2_basic.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py index ab55b7dbb6..f42efaf675 100644 --- a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py +++ b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py @@ -14,31 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import platform from typing import Any -from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE from dimos.core.coordination.blueprints import autoconnect from dimos.core.global_config import global_config -from dimos.core.transport import pSHMTransport -from dimos.msgs.sensor_msgs.Image import Image from dimos.protocol.pubsub.impl.lcmpubsub import LCM from dimos.protocol.service.system_configurator.clock_sync import ClockSyncConfigurator from dimos.robot.unitree.go2.connection import GO2Connection from dimos.web.websocket_vis.websocket_vis_module import WebsocketVisModule -# Mac has some issue with high bandwidth UDP, so we use pSHMTransport for color_image -# actually we can use pSHMTransport for all platforms, and for all streams -# TODO need a global transport toggle on blueprints/global config -_mac_transports: dict[tuple[str, type], pSHMTransport[Image]] = { - ("color_image", Image): pSHMTransport( - "color_image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE - ), -} - -_transports_base = ( - autoconnect() if platform.system() == "Linux" else autoconnect().transports(_mac_transports) -) +_transports_base = autoconnect() def _convert_camera_info(camera_info: Any) -> Any: From fe82fe29443bf468828fc4cf1888ae6a32e40528 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Apr 2026 15:25:14 +0100 Subject: [PATCH 3/9] Update test_system_configurator.py --- dimos/protocol/service/test_system_configurator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dimos/protocol/service/test_system_configurator.py b/dimos/protocol/service/test_system_configurator.py index 8b2400aab6..4726096cce 100644 --- a/dimos/protocol/service/test_system_configurator.py +++ b/dimos/protocol/service/test_system_configurator.py @@ -96,7 +96,7 @@ def test_calls_sudo_run_with_correct_args(self) -> None: ["sudo", "sysctl", "-w", "net.core.rmem_max=67108864"], check=True, text=True, - capture_output=False, + capture_output=True, ) @@ -338,9 +338,9 @@ def test_check_returns_true_when_buffers_sufficient(self) -> None: configurator = BufferConfiguratorMacOS() with patch("dimos.protocol.service.system_configurator.lcm._read_sysctl_int") as mock_read: mock_read.side_effect = [ - BufferConfiguratorMacOS.TARGET_BUFFER_SIZE, - BufferConfiguratorMacOS.TARGET_RECVSPACE, - BufferConfiguratorMacOS.TARGET_DGRAM_SIZE, + BufferConfiguratorMacOS.TARGET, + BufferConfiguratorMacOS.TARGET, + BufferConfiguratorMacOS.TARGET, ] assert configurator.check() is True assert configurator.needs == [] @@ -355,7 +355,7 @@ def test_check_returns_false_when_values_low(self) -> None: def test_explanation_lists_needed_changes(self) -> None: configurator = BufferConfiguratorMacOS() configurator.needs = [ - ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET_BUFFER_SIZE), + ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET), ] explanation = configurator.explanation() assert "kern.ipc.maxsockbuf" in explanation @@ -363,14 +363,14 @@ def test_explanation_lists_needed_changes(self) -> None: def test_fix_writes_needed_values(self) -> None: configurator = BufferConfiguratorMacOS() configurator.needs = [ - ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET_BUFFER_SIZE), + ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET), ] with patch( "dimos.protocol.service.system_configurator.lcm._write_sysctl_int" ) as mock_write: configurator.fix() mock_write.assert_called_once_with( - "kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET_BUFFER_SIZE + "kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET ) From 2d68af8966de94a4c253a1b07ef60bbcd0dc529e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Apr 2026 15:56:43 +0100 Subject: [PATCH 4/9] Update test_system_configurator.py --- dimos/protocol/service/test_system_configurator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dimos/protocol/service/test_system_configurator.py b/dimos/protocol/service/test_system_configurator.py index 4726096cce..99f20f76a2 100644 --- a/dimos/protocol/service/test_system_configurator.py +++ b/dimos/protocol/service/test_system_configurator.py @@ -355,7 +355,7 @@ def test_check_returns_false_when_values_low(self) -> None: def test_explanation_lists_needed_changes(self) -> None: configurator = BufferConfiguratorMacOS() configurator.needs = [ - ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET), + ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET, BufferConfiguratorMacOS.TARGET), ] explanation = configurator.explanation() assert "kern.ipc.maxsockbuf" in explanation @@ -363,7 +363,7 @@ def test_explanation_lists_needed_changes(self) -> None: def test_fix_writes_needed_values(self) -> None: configurator = BufferConfiguratorMacOS() configurator.needs = [ - ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET), + ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET, BufferConfiguratorMacOS.TARGET), ] with patch( "dimos.protocol.service.system_configurator.lcm._write_sysctl_int" From f348e72733821916ecd77b791d9df4f30937feee Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Apr 2026 17:32:28 +0100 Subject: [PATCH 5/9] Revert "Revert SHM transport on Mac" This reverts commit 6deb28325a95bc6be1bb23f705cbdf742ba790f8. --- .../go2/blueprints/basic/unitree_go2_basic.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py index f42efaf675..ab55b7dbb6 100644 --- a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py +++ b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py @@ -14,16 +14,31 @@ # See the License for the specific language governing permissions and # limitations under the License. +import platform from typing import Any +from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE from dimos.core.coordination.blueprints import autoconnect from dimos.core.global_config import global_config +from dimos.core.transport import pSHMTransport +from dimos.msgs.sensor_msgs.Image import Image from dimos.protocol.pubsub.impl.lcmpubsub import LCM from dimos.protocol.service.system_configurator.clock_sync import ClockSyncConfigurator from dimos.robot.unitree.go2.connection import GO2Connection from dimos.web.websocket_vis.websocket_vis_module import WebsocketVisModule -_transports_base = autoconnect() +# Mac has some issue with high bandwidth UDP, so we use pSHMTransport for color_image +# actually we can use pSHMTransport for all platforms, and for all streams +# TODO need a global transport toggle on blueprints/global config +_mac_transports: dict[tuple[str, type], pSHMTransport[Image]] = { + ("color_image", Image): pSHMTransport( + "color_image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE + ), +} + +_transports_base = ( + autoconnect() if platform.system() == "Linux" else autoconnect().transports(_mac_transports) +) def _convert_camera_info(camera_info: Any) -> Any: From e6ea9b5ba8401e0e31767792a41430e576befc34 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 16 Apr 2026 15:04:40 +0100 Subject: [PATCH 6/9] Save config to avoid repeated requests --- .../service/system_configurator/lcm.py | 27 +++++++- .../service/test_system_configurator.py | 61 +++++++++++++++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/dimos/protocol/service/system_configurator/lcm.py b/dimos/protocol/service/system_configurator/lcm.py index 355f5e9489..fd4c973d0f 100644 --- a/dimos/protocol/service/system_configurator/lcm.py +++ b/dimos/protocol/service/system_configurator/lcm.py @@ -14,10 +14,12 @@ from __future__ import annotations +import json import re import resource import subprocess +from dimos.constants import STATE_DIR from dimos.protocol.service.system_configurator.base import ( SystemConfigurator, _read_sysctl_int, @@ -25,6 +27,21 @@ ) from dimos.utils import prompt +_SYSCTL_CONF = STATE_DIR / "sysctl.json" + + +def _load_sysctl_conf() -> dict[str, int]: + try: + return json.loads(_SYSCTL_CONF.read_text()) + except (OSError, json.JSONDecodeError): + return {} + + +def _save_sysctl_conf(data: dict[str, int]) -> None: + _SYSCTL_CONF.parent.mkdir(parents=True, exist_ok=True) + _SYSCTL_CONF.write_text(json.dumps(data)) + + # specific checks: multicast @@ -233,10 +250,12 @@ def __init__(self) -> None: def check(self) -> bool: self.needs.clear() + saved = _load_sysctl_conf() for key in self.KEYS: + target = saved.get(key, self.TARGET) current = _read_sysctl_int(key) or 0 - if current < self.TARGET: - self.needs.append((key, self.TARGET, current)) + if current < target: + self.needs.append((key, target, current)) return not self.needs def explanation(self) -> str | None: @@ -246,6 +265,7 @@ def explanation(self) -> str | None: return "\n".join(lines) def fix(self) -> None: + saved = _load_sysctl_conf() for key, target, current in self.needs: while target > current: try: @@ -253,7 +273,10 @@ def fix(self) -> None: except subprocess.CalledProcessError: target //= 2 else: + saved[key] = target break + # Write current amounts to config to avoid requesting TARGET every startup. + _save_sysctl_conf(saved) # specific checks: ulimit diff --git a/dimos/protocol/service/test_system_configurator.py b/dimos/protocol/service/test_system_configurator.py index 99f20f76a2..11a3e28e07 100644 --- a/dimos/protocol/service/test_system_configurator.py +++ b/dimos/protocol/service/test_system_configurator.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import resource import struct +import subprocess from unittest.mock import MagicMock, patch import pytest @@ -352,6 +354,22 @@ def test_check_returns_false_when_values_low(self) -> None: assert configurator.check() is False assert len(configurator.needs) == 3 + def test_check_uses_saved_config_as_target(self, tmp_path) -> None: + conf = tmp_path / "sysctl.json" + conf.write_text('{"kern.ipc.maxsockbuf": 32000000}') + configurator = BufferConfiguratorMacOS() + with ( + patch("dimos.protocol.service.system_configurator.lcm._SYSCTL_CONF", conf), + patch("dimos.protocol.service.system_configurator.lcm._read_sysctl_int") as mock_read, + ): + # maxsockbuf: saved target is 32M, current is 32M → ok + # recvspace/maxdgram: no saved value → uses IDEAL (64M) → needs fix + mock_read.side_effect = [32000000] * 3 + assert configurator.check() is False + assert len(configurator.needs) == 2 + # maxsockbuf should not be in needs + assert all(k != "kern.ipc.maxsockbuf" for k, _, _ in configurator.needs) + def test_explanation_lists_needed_changes(self) -> None: configurator = BufferConfiguratorMacOS() configurator.needs = [ @@ -363,16 +381,51 @@ def test_explanation_lists_needed_changes(self) -> None: def test_fix_writes_needed_values(self) -> None: configurator = BufferConfiguratorMacOS() configurator.needs = [ - ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET, BufferConfiguratorMacOS.TARGET), + ("kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET, 0), ] - with patch( - "dimos.protocol.service.system_configurator.lcm._write_sysctl_int" - ) as mock_write: + with ( + patch("dimos.protocol.service.system_configurator.lcm._write_sysctl_int") as mock_write, + patch("dimos.protocol.service.system_configurator.lcm._save_sysctl_conf"), + ): configurator.fix() mock_write.assert_called_once_with( "kern.ipc.maxsockbuf", BufferConfiguratorMacOS.TARGET ) + def test_fix_halves_on_failure_and_saves(self, tmp_path) -> None: + conf = tmp_path / "sysctl.json" + configurator = BufferConfiguratorMacOS() + configurator.needs = [("kern.ipc.maxsockbuf", 64000000, 0)] + with ( + patch("dimos.protocol.service.system_configurator.lcm._SYSCTL_CONF", conf), + patch("dimos.protocol.service.system_configurator.lcm._write_sysctl_int") as mock_write, + ): + # Fail at 64M, fail at 32M, succeed at 16M + mock_write.side_effect = [ + subprocess.CalledProcessError(1, "sysctl"), + subprocess.CalledProcessError(1, "sysctl"), + None, + ] + configurator.fix() + assert mock_write.call_count == 3 + mock_write.assert_called_with("kern.ipc.maxsockbuf", 16000000) + # Saved value should be 16M + saved = json.loads(conf.read_text()) + assert saved["kern.ipc.maxsockbuf"] == 16000000 + + def test_fix_stops_at_current_value(self) -> None: + configurator = BufferConfiguratorMacOS() + configurator.needs = [("kern.ipc.maxsockbuf", 64000000, 32000000)] + with ( + patch("dimos.protocol.service.system_configurator.lcm._write_sysctl_int") as mock_write, + patch("dimos.protocol.service.system_configurator.lcm._save_sysctl_conf"), + ): + # All attempts fail — should stop when halved below current (32M) + mock_write.side_effect = subprocess.CalledProcessError(1, "sysctl") + configurator.fix() + # 64M fails, 32M == current → stops + assert mock_write.call_count == 1 + # MaxFileConfiguratorMacOS tests From 3226e2cdc065e9c3d8887e3636702a94cdd7d26a Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 16 Apr 2026 22:20:02 +0100 Subject: [PATCH 7/9] Update lcm.py --- dimos/protocol/service/system_configurator/lcm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/protocol/service/system_configurator/lcm.py b/dimos/protocol/service/system_configurator/lcm.py index fd4c973d0f..13538c5419 100644 --- a/dimos/protocol/service/system_configurator/lcm.py +++ b/dimos/protocol/service/system_configurator/lcm.py @@ -32,7 +32,7 @@ def _load_sysctl_conf() -> dict[str, int]: try: - return json.loads(_SYSCTL_CONF.read_text()) + return json.loads(_SYSCTL_CONF.read_text()) # type: ignore[no-any-return] except (OSError, json.JSONDecodeError): return {} From 241996feaf79a16272b0418342cfa40a379d9ae6 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Fri, 17 Apr 2026 01:34:58 +0100 Subject: [PATCH 8/9] Update test_system_configurator.py --- dimos/protocol/service/test_system_configurator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dimos/protocol/service/test_system_configurator.py b/dimos/protocol/service/test_system_configurator.py index 11a3e28e07..4703469478 100644 --- a/dimos/protocol/service/test_system_configurator.py +++ b/dimos/protocol/service/test_system_configurator.py @@ -356,7 +356,8 @@ def test_check_returns_false_when_values_low(self) -> None: def test_check_uses_saved_config_as_target(self, tmp_path) -> None: conf = tmp_path / "sysctl.json" - conf.write_text('{"kern.ipc.maxsockbuf": 32000000}') + user_limit = 32 * 2**20 # 32 MiB + conf.write_text(f'{"kern.ipc.maxsockbuf": {user_limit}}') configurator = BufferConfiguratorMacOS() with ( patch("dimos.protocol.service.system_configurator.lcm._SYSCTL_CONF", conf), @@ -364,7 +365,7 @@ def test_check_uses_saved_config_as_target(self, tmp_path) -> None: ): # maxsockbuf: saved target is 32M, current is 32M → ok # recvspace/maxdgram: no saved value → uses IDEAL (64M) → needs fix - mock_read.side_effect = [32000000] * 3 + mock_read.side_effect = [user_limit] * 3 assert configurator.check() is False assert len(configurator.needs) == 2 # maxsockbuf should not be in needs From f95f9215e69bad7cd01751c96352536af721f589 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Fri, 17 Apr 2026 17:23:25 +0100 Subject: [PATCH 9/9] Fix --- dimos/protocol/service/test_system_configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/protocol/service/test_system_configurator.py b/dimos/protocol/service/test_system_configurator.py index 4703469478..ff41d3f66c 100644 --- a/dimos/protocol/service/test_system_configurator.py +++ b/dimos/protocol/service/test_system_configurator.py @@ -357,7 +357,7 @@ def test_check_returns_false_when_values_low(self) -> None: def test_check_uses_saved_config_as_target(self, tmp_path) -> None: conf = tmp_path / "sysctl.json" user_limit = 32 * 2**20 # 32 MiB - conf.write_text(f'{"kern.ipc.maxsockbuf": {user_limit}}') + conf.write_text(json.dumps({"kern.ipc.maxsockbuf": user_limit})) configurator = BufferConfiguratorMacOS() with ( patch("dimos.protocol.service.system_configurator.lcm._SYSCTL_CONF", conf),