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.
@@ -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 = "signal"
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
- if self.packets % 20000 == 0:
399
- self._collect(sock)
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, "-m", "com_py.ota_pcap",
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)),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rosotacom-dev
3
- Version: 2.5.dev69
3
+ Version: 2.5.dev71
4
4
  Summary: Host CLI for ROS OTA communication sessions.
5
5
  Author: develNor
6
6
  License-Expression: BSD-3-Clause
@@ -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=dL9vTwR2PEDdZrhkgkJdRL5zmLjFH0eHv2sCnLcSApQ,19277
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=xbYhkTTKbEHEBAMMmPMxI_gesFNRPFjxpYkFSjS5-uc,30930
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.dev69.dist-info/licenses/LICENSE.txt,sha256=FDB3-fao3lqZ94wsJHKAH7U88kAnfvZhZC8Hm5rN__g,1459
209
- rosotacom_dev-2.5.dev69.dist-info/METADATA,sha256=DTigcF5y6MBXPW9Zc0jV3iJ6Z5hDMi48ztYyWR5fmZI,58884
210
- rosotacom_dev-2.5.dev69.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
211
- rosotacom_dev-2.5.dev69.dist-info/entry_points.txt,sha256=im11ZWqBbgjQUBz2Ik2RNfMu54wlMwgiVpsBKeI7dCI,49
212
- rosotacom_dev-2.5.dev69.dist-info/top_level.txt,sha256=BZrtQcIVDWn_pQZR3Mlssqy5SaRXHjOa6B-1D9Fy4Ao,10
213
- rosotacom_dev-2.5.dev69.dist-info/RECORD,,
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,,