rosotacom-dev 2.5.dev69__py3-none-any.whl → 2.5.dev71__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rosotacom/resources/ws/ros2src/com_py/com_py/ota_pcap.py +42 -5
- rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py +15 -1
- {rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/METADATA +1 -1
- {rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/RECORD +8 -8
- {rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/WHEEL +0 -0
- {rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/entry_points.txt +0 -0
- {rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/licenses/LICENSE.txt +0 -0
- {rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/top_level.txt +0 -0
|
@@ -79,6 +79,15 @@ A capture that quietly drops packets is worse than none: the analysis would
|
|
|
79
79
|
read the gap as radio loss. So `PACKET_STATISTICS` is read from the kernel and
|
|
80
80
|
the sidecar `.json` reports `kernel_drops` next to `packets`. A non-zero drop
|
|
81
81
|
count means this process fell behind, not that the link did.
|
|
82
|
+
|
|
83
|
+
Neither file depends on a clean shutdown, and that is deliberate. A session
|
|
84
|
+
usually ends by the container being stopped, which does not reach this process
|
|
85
|
+
as a signal it can act on — the first bench run ended exactly that way and left
|
|
86
|
+
a pcap truncated at a buffer boundary and no sidecar at all. So the pcap is
|
|
87
|
+
flushed on an interval and the sidecar is written at the *start* and rewritten
|
|
88
|
+
on the same interval, with `stopped_because: "running"` until something stops
|
|
89
|
+
it. A killed capture therefore still leaves a readable pcap and a sidecar that
|
|
90
|
+
says what it was and how much it had seen, rather than an unexplained file.
|
|
82
91
|
"""
|
|
83
92
|
|
|
84
93
|
from __future__ import annotations
|
|
@@ -135,6 +144,12 @@ DEFAULT_MAX_MB = 2000
|
|
|
135
144
|
#: is where `kernel_drops` comes from on a burst.
|
|
136
145
|
RCVBUF_BYTES = 4 << 20
|
|
137
146
|
|
|
147
|
+
#: How often the pcap is flushed and the sidecar rewritten. A session that ends
|
|
148
|
+
#: by having its container stopped never reaches this process, so what is on
|
|
149
|
+
#: disk at any moment is what the run will be read from. One second costs a
|
|
150
|
+
#: ~300-byte write and bounds the loss to the packets of that second.
|
|
151
|
+
CHECKPOINT_S = 1.0
|
|
152
|
+
|
|
138
153
|
|
|
139
154
|
def log(message: str) -> None:
|
|
140
155
|
print(f"[ota_pcap] {message}", flush=True)
|
|
@@ -326,7 +341,7 @@ class Capture:
|
|
|
326
341
|
self.kernel_seen = 0
|
|
327
342
|
self.first_ts: float | None = None
|
|
328
343
|
self.last_ts: float | None = None
|
|
329
|
-
self.stopped_because = "
|
|
344
|
+
self.stopped_because = "running"
|
|
330
345
|
|
|
331
346
|
def sidecar(self) -> dict:
|
|
332
347
|
"""What the file is, and what it is missing. Written next to the pcap."""
|
|
@@ -348,6 +363,14 @@ class Capture:
|
|
|
348
363
|
"stopped_because": self.stopped_because,
|
|
349
364
|
}
|
|
350
365
|
|
|
366
|
+
def write_sidecar(self) -> None:
|
|
367
|
+
"""The sidecar as of now. Cheap enough to call on every checkpoint."""
|
|
368
|
+
try:
|
|
369
|
+
self.out.with_suffix(".json").write_text(
|
|
370
|
+
json.dumps(self.sidecar(), indent=2) + "\n", encoding="utf-8")
|
|
371
|
+
except OSError as error:
|
|
372
|
+
log(f"WARN could not write the sidecar: {error}")
|
|
373
|
+
|
|
351
374
|
def run(self, stop: list) -> None:
|
|
352
375
|
sock = open_socket(self.iface)
|
|
353
376
|
# Root was needed for that one call and for nothing after it.
|
|
@@ -360,8 +383,22 @@ class Capture:
|
|
|
360
383
|
with open(self.out, "wb", buffering=1 << 20) as handle:
|
|
361
384
|
handle.write(pcap_header(self.snaplen))
|
|
362
385
|
self.bytes_written = 24
|
|
386
|
+
handle.flush()
|
|
387
|
+
# Written before a single packet arrives, so a capture that is
|
|
388
|
+
# killed with its container still explains itself.
|
|
389
|
+
self.write_sidecar()
|
|
390
|
+
next_checkpoint = time.monotonic() + CHECKPOINT_S
|
|
363
391
|
sock.settimeout(0.5)
|
|
364
392
|
while not stop:
|
|
393
|
+
# `tick`, not `now`: the timestamp fallback below rebinds `now`
|
|
394
|
+
# to wall-clock time, and comparing that against a monotonic
|
|
395
|
+
# deadline would checkpoint on every single packet.
|
|
396
|
+
tick = time.monotonic()
|
|
397
|
+
if tick >= next_checkpoint:
|
|
398
|
+
next_checkpoint = tick + CHECKPOINT_S
|
|
399
|
+
self._collect(sock)
|
|
400
|
+
handle.flush()
|
|
401
|
+
self.write_sidecar()
|
|
365
402
|
try:
|
|
366
403
|
packet, ancdata, _flags, address = sock.recvmsg(65535, ancillary)
|
|
367
404
|
except socket.timeout:
|
|
@@ -395,8 +432,9 @@ class Capture:
|
|
|
395
432
|
f"after {self.packets} packets — the recording is untouched")
|
|
396
433
|
self.stopped_because = "size ceiling"
|
|
397
434
|
break
|
|
398
|
-
|
|
399
|
-
|
|
435
|
+
handle.flush()
|
|
436
|
+
if self.stopped_because == "running":
|
|
437
|
+
self.stopped_because = "signal"
|
|
400
438
|
self._collect(sock)
|
|
401
439
|
sock.close()
|
|
402
440
|
|
|
@@ -445,9 +483,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
445
483
|
log(f"FAIL {error}")
|
|
446
484
|
return 3
|
|
447
485
|
|
|
486
|
+
capture.write_sidecar()
|
|
448
487
|
sidecar = capture.sidecar()
|
|
449
|
-
args.out.with_suffix(".json").write_text(
|
|
450
|
-
json.dumps(sidecar, indent=2) + "\n", encoding="utf-8")
|
|
451
488
|
log(f"{sidecar['packets']} packets, {sidecar['bytes'] / 1e6:.1f} MB, "
|
|
452
489
|
f"{sidecar['kernel_drops']} dropped by the kernel")
|
|
453
490
|
if sidecar["kernel_drops"]:
|
|
@@ -58,6 +58,7 @@ import signal
|
|
|
58
58
|
import subprocess
|
|
59
59
|
import sys
|
|
60
60
|
import threading
|
|
61
|
+
from pathlib import Path
|
|
61
62
|
from typing import Dict, Optional
|
|
62
63
|
|
|
63
64
|
import yaml
|
|
@@ -558,8 +559,21 @@ class StatusOverview(Node):
|
|
|
558
559
|
return
|
|
559
560
|
|
|
560
561
|
path = os.path.join(output_dir, "ota.pcap")
|
|
562
|
+
# By file path, not `-m com_py.ota_pcap`. sudo resets the environment,
|
|
563
|
+
# so PYTHONPATH does not survive and the package is not importable on
|
|
564
|
+
# the other side of it — a two-host run answered exactly that,
|
|
565
|
+
# "No module named 'com_py'", after the permission problem was fixed.
|
|
566
|
+
# `ota_pcap.py` imports nothing but the standard library (a test pins
|
|
567
|
+
# that), so a path needs no package at all.
|
|
568
|
+
script = Path(__file__).resolve().with_name("ota_pcap.py")
|
|
569
|
+
if not script.is_file():
|
|
570
|
+
self.get_logger().warning(
|
|
571
|
+
f"status_overview: ota_pcap is enabled but {script} is not here; "
|
|
572
|
+
"capturing nothing"
|
|
573
|
+
)
|
|
574
|
+
return
|
|
561
575
|
argv = [
|
|
562
|
-
sys.executable,
|
|
576
|
+
sys.executable, str(script),
|
|
563
577
|
"--iface", interface,
|
|
564
578
|
"--out", path,
|
|
565
579
|
"--snaplen", str(int(self.get_parameter("ota_pcap_snaplen").value)),
|
|
@@ -163,7 +163,7 @@ rosotacom/resources/ws/ros2src/com_py/com_py/link_trace.py,sha256=elVggfIe4YXCnq
|
|
|
163
163
|
rosotacom/resources/ws/ros2src/com_py/com_py/local_global_frame_bridge.py,sha256=BCuq_njJ_sXgdSGJ1J9FtH6sxqvELTU0UdXAw13XqhA,18724
|
|
164
164
|
rosotacom/resources/ws/ros2src/com_py/com_py/localization_map_split_reduce_saver.py,sha256=m6lMb51q5pUYTyHVWiss8vPlsbYRpKMDwHG9gG_fY4E,9088
|
|
165
165
|
rosotacom/resources/ws/ros2src/com_py/com_py/msg_transform.py,sha256=gdSYrvMUKuBOG5UePm6EGxkHz-QNfZ00PIR9IYEr8ao,9629
|
|
166
|
-
rosotacom/resources/ws/ros2src/com_py/com_py/ota_pcap.py,sha256=
|
|
166
|
+
rosotacom/resources/ws/ros2src/com_py/com_py/ota_pcap.py,sha256=2bgy6XIEtWugoB32GbHFVIX6CFre0pTtqv2Q1G8s3eE,21277
|
|
167
167
|
rosotacom/resources/ws/ros2src/com_py/com_py/pair_management.py,sha256=DfNNsX09GEKwIFolXFKj0blYQTNhoHQTj09Cat3zKZ8,3765
|
|
168
168
|
rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer.py,sha256=Kr5T_pBPjJP4DgEaDSINv5TkaiBrs6HF-J6GhlxzEYs,4813
|
|
169
169
|
rosotacom/resources/ws/ros2src/com_py/com_py/playout_pacer_core.py,sha256=d9omGcz9a7rT1smA2ROIsOiL0rqXHZTAA0NRj81V9UA,4675
|
|
@@ -175,7 +175,7 @@ rosotacom/resources/ws/ros2src/com_py/com_py/restamp.py,sha256=S8LjtZs_WBdnvL5oN
|
|
|
175
175
|
rosotacom/resources/ws/ros2src/com_py/com_py/sized_publisher.py,sha256=prpmQf_cXp3KMYw3FlBZfNhq0Rrz56YUjWOunVF6xL8,9259
|
|
176
176
|
rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency.py,sha256=su_oWQBeDeLHdCy08YzzsASPgYoWID-syqqVkrjSg4g,2768
|
|
177
177
|
rosotacom/resources/ws/ros2src/com_py/com_py/stage_latency_core.py,sha256=VXR0EN8EdExxLSb8UbY4mTv94khLZi6weqJpQtRMbd8,2600
|
|
178
|
-
rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py,sha256=
|
|
178
|
+
rosotacom/resources/ws/ros2src/com_py/com_py/status_overview.py,sha256=V4iDhUn9RsRuTBvp82-j4HhzlU9VBPQXtsucm8ZmXEE,31672
|
|
179
179
|
rosotacom/resources/ws/ros2src/com_py/com_py/status_overview_core.py,sha256=1g_wULJGfcBL9lIkkxWVfYDGnGuiuTVnWEo3ZdvnZ_Q,64146
|
|
180
180
|
rosotacom/resources/ws/ros2src/com_py/com_py/tf_map_to_cart_saver.py,sha256=eWq0D-pVDadj186e2ZC4kZpxTBkXMzi24BfBj3NjGXc,3777
|
|
181
181
|
rosotacom/resources/ws/ros2src/com_py/com_py/topic_monitor.py,sha256=5iNoHU3SiiDwzsOj7tudh5d801ELVxaHiGKmTCP3JFw,16320
|
|
@@ -205,9 +205,9 @@ rosotacom/resources/ws/session/creation/generate_session_files.py,sha256=eosnUYS
|
|
|
205
205
|
rosotacom/resources/ws/session/creation/run_session.py,sha256=G2hJhrmLp11gm3JZ6_C-zZcqu_reXlE_wmobB7BiCfU,16849
|
|
206
206
|
rosotacom/resources/ws/session/creation/run_session_in_external_terminal.py,sha256=Ub_5f-ttUUc-rAPrZmg_Op34s8Z4C91hTyEn8fgyt-E,2633
|
|
207
207
|
rosotacom/resources/ws/session/creation/strip_ansi.py,sha256=vNjpemcq4xHr0VG4x0bZqvgwEBEfOgN-b4PzbrM5nfM,851
|
|
208
|
-
rosotacom_dev-2.5.
|
|
209
|
-
rosotacom_dev-2.5.
|
|
210
|
-
rosotacom_dev-2.5.
|
|
211
|
-
rosotacom_dev-2.5.
|
|
212
|
-
rosotacom_dev-2.5.
|
|
213
|
-
rosotacom_dev-2.5.
|
|
208
|
+
rosotacom_dev-2.5.dev71.dist-info/licenses/LICENSE.txt,sha256=FDB3-fao3lqZ94wsJHKAH7U88kAnfvZhZC8Hm5rN__g,1459
|
|
209
|
+
rosotacom_dev-2.5.dev71.dist-info/METADATA,sha256=B8G12zJU_JgUUz3rhkwFvDZUy4GMVR0pd5nSX6bB9fw,58884
|
|
210
|
+
rosotacom_dev-2.5.dev71.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
211
|
+
rosotacom_dev-2.5.dev71.dist-info/entry_points.txt,sha256=im11ZWqBbgjQUBz2Ik2RNfMu54wlMwgiVpsBKeI7dCI,49
|
|
212
|
+
rosotacom_dev-2.5.dev71.dist-info/top_level.txt,sha256=BZrtQcIVDWn_pQZR3Mlssqy5SaRXHjOa6B-1D9Fy4Ao,10
|
|
213
|
+
rosotacom_dev-2.5.dev71.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{rosotacom_dev-2.5.dev69.dist-info → rosotacom_dev-2.5.dev71.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|