manta-node 0.6b5.dev707__py3-none-any.whl → 0.6b5.dev742__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.
@@ -4,7 +4,7 @@ This file is auto-generated at build time by scripts/generate_version.py.
4
4
  Do not edit manually.
5
5
  """
6
6
 
7
- __version__ = "2026.09.25.d14aac9a"
8
- __build_date__ = "2026.09.25"
9
- __commit_id__ = "d14aac9a"
7
+ __version__ = "2026.09.29.6a0ccdc7"
8
+ __build_date__ = "2026.09.29"
9
+ __commit_id__ = "6a0ccdc7"
10
10
  __package_version__ = "0.6b5"
@@ -132,16 +132,27 @@ class MetricsCollectorBase(ABC):
132
132
  cpu_metrics = await self.collect_cpu_metrics()
133
133
  memory_metrics = await self.collect_memory_metrics()
134
134
  # gpu_metrics = await self.collect_gpu_metrics()
135
- # additional_metrics = await self.collect_additional_metrics()
136
135
  # platform_info = self.get_platform_info()
137
136
 
137
+ # Disk/network is best-effort: the manager is waiting on this snapshot
138
+ # for its heartbeat, so a failure collecting it must not cost the
139
+ # cpu/memory reading. `additional=None` and an unset field serialize
140
+ # identically (proto3 explicit presence), which is also what a build
141
+ # too old to collect it sends -- both cases correctly report "no
142
+ # disk/network reading this cycle", never a stale or partial one.
143
+ try:
144
+ additional_metrics = await self.collect_additional_metrics()
145
+ except Exception as e:
146
+ self.tracer.error(f"Error collecting additional metrics: {e}")
147
+ additional_metrics = None
148
+
138
149
  # Combine all metrics into a snapshot
139
150
  metrics = MetricsSnapshot(
140
151
  timestamp=datetime.now(timezone.utc),
141
152
  cpu=cpu_metrics,
142
153
  memory=memory_metrics,
143
154
  # gpu=gpu_metrics,
144
- # additional=additional_metrics,
155
+ additional=additional_metrics,
145
156
  # platform=platform_info,
146
157
  )
147
158
 
@@ -322,40 +333,60 @@ class GenericMetricsCollector(MetricsCollectorBase):
322
333
  """
323
334
  # psutil is now always available
324
335
 
325
- # Get disk usage
326
- disk_usage = []
327
- for partition in psutil.disk_partitions(all=False):
328
- try:
329
- usage = psutil.disk_usage(partition.mountpoint)
330
- disk_usage.append(
331
- DiskUsage(
332
- mount_point=partition.mountpoint,
333
- total_bytes=usage.total,
334
- used_bytes=usage.used,
335
- free_bytes=usage.free,
336
- percent_used=usage.percent,
336
+ def read_additional_metrics() -> AdditionalMetrics:
337
+ # Get disk usage. `all=False` already drops pseudo/virtual
338
+ # filesystems (overlay, tmpfs, proc, sysfs, ...): on Linux it
339
+ # keeps only fstypes absent from /proc/filesystems' `nodev` list,
340
+ # which excludes a container's own overlay root along with the
341
+ # rest. What it does NOT dedupe: Docker bind-mounts
342
+ # /etc/hostname, /etc/hosts and /etc/resolv.conf from one
343
+ # host-side file each, and psutil lists each bind mount as its
344
+ # own partition even though all three share one underlying
345
+ # device -- so every container reports that one disk three times
346
+ # over unless a device already seen is skipped.
347
+ disk_usage = []
348
+ seen_devices: set[str] = set()
349
+ for partition in psutil.disk_partitions(all=False):
350
+ if partition.device in seen_devices:
351
+ continue
352
+ seen_devices.add(partition.device)
353
+ try:
354
+ usage = psutil.disk_usage(partition.mountpoint)
355
+ disk_usage.append(
356
+ DiskUsage(
357
+ mount_point=partition.mountpoint,
358
+ total_bytes=usage.total,
359
+ used_bytes=usage.used,
360
+ free_bytes=usage.free,
361
+ percent_used=usage.percent,
362
+ )
337
363
  )
338
- )
339
- except (PermissionError, OSError):
340
- # Some mountpoints may not be accessible
341
- pass
364
+ except (PermissionError, OSError):
365
+ # Some mountpoints may not be accessible
366
+ pass
367
+
368
+ # Get basic network info
369
+ net_io = psutil.net_io_counters()
370
+ network = NetworkUsage(
371
+ bytes_sent=net_io.bytes_sent,
372
+ bytes_recv=net_io.bytes_recv,
373
+ packets_sent=net_io.packets_sent,
374
+ packets_recv=net_io.packets_recv,
375
+ )
342
376
 
343
- # Get basic network info
344
- net_io = psutil.net_io_counters()
345
- network = NetworkUsage(
346
- bytes_sent=net_io.bytes_sent,
347
- bytes_recv=net_io.bytes_recv,
348
- packets_sent=net_io.packets_sent,
349
- packets_recv=net_io.packets_recv,
350
- )
377
+ # Get system uptime
378
+ boot_time = psutil.boot_time()
379
+ uptime_seconds = time.time() - boot_time
351
380
 
352
- # Get system uptime
353
- boot_time = psutil.boot_time()
354
- uptime_seconds = time.time() - boot_time
381
+ return AdditionalMetrics(
382
+ disk_usage=disk_usage,
383
+ network=network,
384
+ uptime_seconds=int(uptime_seconds),
385
+ )
355
386
 
356
- # Combine all additional metrics
357
- return AdditionalMetrics(
358
- disk_usage=disk_usage,
359
- network=network,
360
- uptime_seconds=int(uptime_seconds),
361
- )
387
+ # A stat per surviving partition plus two /proc reads, all blocking
388
+ # syscalls; this now runs on the ~1s production heartbeat
389
+ # (ping_interval, infrastructure/mqtt/command_handler.py), so it must
390
+ # not block the shared event loop -- the same reason
391
+ # collect_memory_metrics reads psutil off-thread.
392
+ return await asyncio.to_thread(read_additional_metrics)
manta_node/seam.py CHANGED
@@ -51,6 +51,7 @@ class SeamState(str, Enum):
51
51
  REVERTING = "reverting"
52
52
  REPORTING = "reporting"
53
53
  REVERTED = "reverted"
54
+ SKIPPED = "skipped"
54
55
  FAILED = "failed"
55
56
  DOWN = "down"
56
57
 
@@ -99,7 +99,8 @@ def run_switch(
99
99
  """Run the lifecycle for one request and return its terminal outcome.
100
100
 
101
101
  Terminal states: ``REPORTING`` (came back on the new version), ``REVERTED``
102
- (no return within the window, reverted to the last good), ``FAILED`` (a
102
+ (no return within the window, reverted to the last good), ``SKIPPED`` (the
103
+ target is already current; nothing to do, no restart), ``FAILED`` (a
103
104
  pre-switch step failed; nothing changed), ``DOWN`` (the switch happened and
104
105
  the revert itself failed — the node is not running, surfaced honestly).
105
106
 
@@ -107,6 +108,15 @@ def run_switch(
107
108
  lifecycle resolves, so a supervisor restart mid-window can re-evaluate it.
108
109
  """
109
110
  bounds = bounds or Bounds()
111
+
112
+ # NO-OP — applying the pair already current changes nothing (NF-5). Return
113
+ # before fetch/verify/stage/commit, so the child is NOT restarted: under B1 a
114
+ # re-apply would otherwise be a full switch and restart the node for no change
115
+ # (the daily loop that never converges). `force` does not bypass this: it is
116
+ # the not-newer override, and a deliberate bounce is a unit restart.
117
+ if version_tree.is_current_pair(request.target):
118
+ return _outcome(request, SeamState.SKIPPED, "target is already current")
119
+
110
120
  last_good_dir = version_tree.selected()
111
121
  last_good = last_good_dir.name if last_good_dir else None
112
122
  candidate_dir = request.target
@@ -215,6 +215,26 @@ def is_pointer_target(target: Path) -> bool:
215
215
  return link.is_symlink() and Path(os.path.realpath(link)) == target
216
216
 
217
217
 
218
+ def is_current_pair(pair: str) -> bool:
219
+ """True when the pair named by ``pair`` is already the current version.
220
+
221
+ A request to apply the code already running is a no-op (NF-5): under B1 a
222
+ re-apply would otherwise fetch, verify, stage, commit and **restart** the
223
+ child, restarting the node for no change. The check is by pair identity, so it
224
+ holds whether the target names the version dir directly or a set id that
225
+ resolves to it. A node with no valid ``current`` is never "already current".
226
+ """
227
+ version_dir = selected()
228
+ if version_dir is None:
229
+ return False
230
+ # The dir name and the manifest's pair are the same identity; either naming it
231
+ # is an exact match on what is running.
232
+ if version_dir.name == pair:
233
+ return True
234
+ manifest = read_manifest(version_dir)
235
+ return manifest is not None and manifest.pair == pair
236
+
237
+
218
238
  def list_versions() -> list[Path]:
219
239
  """The version dirs, oldest first.
220
240
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: manta_node
3
- Version: 0.6b5.dev707
3
+ Version: 0.6b5.dev742
4
4
  Summary: Node side software of the Manta platform. It is responsible for the communication with the Manta Core and the execution of the tasks.
5
5
  Author-email: Hugo Miralles <hugo.miralles@manta-tech.io>, Matthew Thompson <matthew.thompson@manta-tech.io>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -28,7 +28,7 @@ Requires-Dist: pyyaml
28
28
  Requires-Dist: psutil
29
29
  Requires-Dist: msgpack
30
30
  Requires-Dist: aiomqtt<3.0,>=2.5.1
31
- Requires-Dist: manta-common-core<=0.6b5,>=0.6b5.dev668
31
+ Requires-Dist: manta-common-core<=0.6b5,>=0.6b5.dev738
32
32
  Requires-Dist: toml
33
33
  Requires-Dist: tomli; python_version < "3.11"
34
34
  Requires-Dist: tomli-w
@@ -1,12 +1,12 @@
1
1
  manta_node/__init__.py,sha256=bgGWXH3yQVhNf0v1ZGUBAy5Y51fqb0Y-EaLnocbcino,153
2
2
  manta_node/__main__.py,sha256=SjrJyuMg_ZFKV6WOVe3MrtWM8Qmu065dkKZYl4n2YLw,1665
3
3
  manta_node/node_orchestrator.py,sha256=nfpNCM8wgnihKhZZ36GIizmyyaOdEOBhdfn7PqqTCrQ,29591
4
- manta_node/seam.py,sha256=wAI3uL1iuV6pmDOaBW7WZi7_V7-NsHQDP6C_gFIjCiA,4747
4
+ manta_node/seam.py,sha256=npu2QdIqnybiz0h4Wb7aE4vPwXJIWTZW3z31FSk6sFk,4771
5
5
  manta_node/task_manager.py,sha256=xRE-yKdUW0FFR8vtX1jDwl1AxTbqVD_JS7QVR62GA00,38002
6
6
  manta_node/tasks.py,sha256=o837Hi-KFVJg9LF5iOtE8oD6UuTOfrEPWR-JOdhRGeY,6983
7
7
  manta_node/utils.py,sha256=Hw4Iw2jYQYsfVLVJuBMt-_rMFBeml3nCDE2jiiXc8YM,1155
8
8
  manta_node/cli/__init__.py,sha256=arzO3mXDJ-E2yeeytbZfN7mCswwxdQ43_QjyeL2E5yE,134
9
- manta_node/cli/_version_generated.py,sha256=oq-vrYUSYqZEeiOos3Fqzj9GAI7ShLAmLb-7xD8MTio,275
9
+ manta_node/cli/_version_generated.py,sha256=SEyf_9vyzjLestxXRcjIeAys-zf8ukAgt9rRCNp9zpM,275
10
10
  manta_node/cli/config_manager.py,sha256=BCju9zqzraviL3KrzRnB-vZQUY__Q5kuEqb7EdB8YdI,4409
11
11
  manta_node/cli/main.py,sha256=dZa6zrypWBvpN8hd4qpxevvlyNFhxKINCg1537NJ36w,4318
12
12
  manta_node/cli/version.py,sha256=3lrnIWpOosszBN-VsCBVLxDgpvCC7IB0aDIKx_3e7hI,3115
@@ -39,7 +39,7 @@ manta_node/infrastructure/grpc/local_servicer.py,sha256=9PFAwpKKFGy3P7HZfx2BHwhO
39
39
  manta_node/infrastructure/grpc/world_servicer.py,sha256=nmyrMzYxzGDlZ1-oOeyALgd76mbeOmAOsjzoVN6f0SA,8222
40
40
  manta_node/infrastructure/metrics/__init__.py,sha256=ODGAj9s63F5jba2c3dQRMfNK6I4WvMdcJUS_xApUN5Y,272
41
41
  manta_node/infrastructure/metrics/collector.py,sha256=9ESw__Pc2tV6ldG5yhos2IS7T5Td59KxTIwDRZc_G4w,3067
42
- manta_node/infrastructure/metrics/metrics_collector.py,sha256=TI_9tRoIUunGx_BstVavK6VElig7Wc5mJ0kB4bFumVs,11048
42
+ manta_node/infrastructure/metrics/metrics_collector.py,sha256=osIYrNzU3g9WCAxVQ14isM9YEQ3eJ7F3QgKBbpgeMJs,13077
43
43
  manta_node/infrastructure/mqtt/__init__.py,sha256=riXo1sXgO4YV6DHCmWRqK61BAmuGLdHiVdQFunAJadA,140
44
44
  manta_node/infrastructure/mqtt/command_handler.py,sha256=aGJMPF8uAdeEw5xfE_WlL3-ROiSV_LCUXf5EpmZrbtk,29728
45
45
  manta_node/infrastructure/network/__init__.py,sha256=On3XLgycFQoc-fBCa5hFuFDfEJjmFy7Wl2CK3D2LkdI,478
@@ -56,14 +56,14 @@ manta_node/supervisor/__init__.py,sha256=kgdoNce1tM4e7jvdxkdmnNJV1L92-NI2cRfYSdm
56
56
  manta_node/supervisor/bounds.py,sha256=MlUfj0tYY-US348JsxYA-Ph5pSpgoOPlRdVTwmrPtZA,3181
57
57
  manta_node/supervisor/executor.py,sha256=a0JafVoTmtbIu87nwUtj08KAC-Q-u0Ajdn_ptyu9yoc,6833
58
58
  manta_node/supervisor/inflight.py,sha256=JOqwXYM928fpcQtYdoK7596eAg28KM70bh85cyQgX3Q,2663
59
- manta_node/supervisor/lifecycle.py,sha256=SA-xJqlxwbLa37fvp0W_YbUZbrRz9vQJDpg6vvuokpI,10303
59
+ manta_node/supervisor/lifecycle.py,sha256=HARQv3wYyK-ZjcBac-ixQMVlkjHvNtIpRdSMm2lNHCQ,10915
60
60
  manta_node/supervisor/process.py,sha256=N5WvNCJnJR3c6I4uqBPPFqgEdCI9rFulHyGem9Shc-I,19155
61
61
  manta_node/supervisor/records.py,sha256=fUlL1SfmWpGnCbJ9956cuy0H3a7-aEF-B-naqbJiYoU,6405
62
62
  manta_node/supervisor/seam.py,sha256=HomMFTDbc0dZQ9VrfGVhQxjiU7E23-zwyjZnLsnq6dc,8031
63
- manta_node/supervisor/switch.py,sha256=iWLukvuxynK3NU26iIk0qhkwrRPUyJuutV67QA3aLO8,16732
64
- manta_node-0.6b5.dev707.dist-info/licenses/LICENSE,sha256=9mBgLhiB9goQPQjg9XQJ6FjgVxYw9Q8lyOZYkPGwF_c,34484
65
- manta_node-0.6b5.dev707.dist-info/METADATA,sha256=uOmOaIVFbJllDFQMjCOr4zfNlYc-Qq9V4VaBk6uHYDg,4231
66
- manta_node-0.6b5.dev707.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
67
- manta_node-0.6b5.dev707.dist-info/entry_points.txt,sha256=uCwggRApalLEsYs1YxwTBN0-e9oMJAkMoBp01HaSdoY,110
68
- manta_node-0.6b5.dev707.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
69
- manta_node-0.6b5.dev707.dist-info/RECORD,,
63
+ manta_node/supervisor/switch.py,sha256=hFCWhZDoWN9KN0zkNepsQfkypDhhlkBKmCOltCqlb3w,17614
64
+ manta_node-0.6b5.dev742.dist-info/licenses/LICENSE,sha256=9mBgLhiB9goQPQjg9XQJ6FjgVxYw9Q8lyOZYkPGwF_c,34484
65
+ manta_node-0.6b5.dev742.dist-info/METADATA,sha256=kD0dvyj-vlf8iKCY3_M1Juc2IFWE_kqUsyk7IIDB930,4231
66
+ manta_node-0.6b5.dev742.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
67
+ manta_node-0.6b5.dev742.dist-info/entry_points.txt,sha256=uCwggRApalLEsYs1YxwTBN0-e9oMJAkMoBp01HaSdoY,110
68
+ manta_node-0.6b5.dev742.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
69
+ manta_node-0.6b5.dev742.dist-info/RECORD,,