alissa-tools-github-revloop 0.16.15__tar.gz → 0.16.16__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.
Files changed (32) hide show
  1. {alissa_tools_github_revloop-0.16.15/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.16.16}/PKG-INFO +1 -1
  2. alissa_tools_github_revloop-0.16.16/src/main/alissa/tools/github/revloop/version +1 -0
  3. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/__init__.py +7 -5
  4. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/page.py +17 -2
  5. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/sources.py +6 -0
  6. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +34 -6
  7. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
  8. alissa_tools_github_revloop-0.16.15/src/main/alissa/tools/github/revloop/version +0 -1
  9. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/LICENSE +0 -0
  10. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/MANIFEST.in +0 -0
  11. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/NOTICE +0 -0
  12. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/README.md +0 -0
  13. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/requirements.txt +0 -0
  14. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/setup.cfg +0 -0
  15. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/setup.py +0 -0
  16. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
  17. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
  18. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/alissa.py +0 -0
  19. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/config.py +0 -0
  20. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
  21. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/loop.py +0 -0
  22. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/proc.py +0 -0
  23. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
  24. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/state.py +0 -0
  25. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/version.py +0 -0
  26. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
  27. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
  28. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
  29. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
  30. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
  31. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
  32. {alissa_tools_github_revloop-0.16.15 → alissa_tools_github_revloop-0.16.16}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-revloop
3
- Version: 0.16.15
3
+ Version: 0.16.16
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -15,14 +15,16 @@ pass writes one `poll_snapshots` row (UI-1, PR #35) carrying the pass timing,
15
15
  the candidate count, the decision-summary counts, and the compact per-item
16
16
  stage list. The sidecar reads that table through `State.read_snapshots`, plus
17
17
  the spawn ledger, the escalation table and the ping ledger (the operator
18
- inbox), all read-only. Its only live signals are local (`alissa tmux ls`, a
19
- `/proc` walk of each session's pane-PID tree) or cached (`gh api rate_limit`,
20
- 60s; the PyPI version JSON, 10m) -- so a fleet of operators refreshing the
21
- dashboard never moves the daemon's rate budget.
18
+ inbox), all read-only. Its only live signals are local (`alissa tmux ls`, one
19
+ `/proc` walk serving both each session's pane-PID tree and the host-wide
20
+ top-by-RSS list, and the container's own cgroup v2 memory charge) or cached
21
+ (`gh api rate_limit`, 60s; the PyPI version JSON, 10m) -- so a fleet of
22
+ operators refreshing the dashboard never moves the daemon's rate budget.
22
23
 
23
24
  Layout:
24
25
  auth.py -- fail-closed passcode, HMAC-signed sessions, CSRF, login throttle
25
- sysinfo.py -- /proc process-tree CPU%/RSS (sample-free, vanished-PID tolerant)
26
+ sysinfo.py -- /proc process-tree CPU%/RSS + host-wide top-by-RSS and the
27
+ cgroup memory split (sample-free, vanished-PID tolerant)
26
28
  sources.py -- the read-only data layer + the retry-now UPDATE, cached checks
27
29
  page.py -- the single static HTML page (studio design system, both themes)
28
30
  server.py -- ThreadingHTTPServer wiring, routing, auth/CSRF gating, actions
@@ -437,14 +437,29 @@ _JS = r"""
437
437
  // The meter is the RESIDENT share of the charge, deliberately: it is the
438
438
  // only part of a rising charge that a limit can actually kill for, so the
439
439
  // shared warn/crit thresholds mean the same thing here as on the others.
440
+ // Gated on ANY of the three, not on `charged`: cgroup_memory reads
441
+ // memory.current and memory.stat through separate helpers so each degrades
442
+ // on its own, and a tile keyed to the headline alone would throw away a
443
+ // breakdown the reader deliberately preserved. The headline and the meter
444
+ // carry their own null handling, so a missing `charged` costs only itself.
440
445
  var mem = t.memory;
441
- if (mem && mem.charged != null) {
446
+ if (mem && (mem.charged != null || mem.resident != null || mem.reclaimable != null)) {
442
447
  var msub = (mem.resident == null ? '--' : bytes(mem.resident)) + ' resident · ' +
443
448
  (mem.reclaimable == null ? '--' : bytes(mem.reclaimable)) + ' reclaimable';
449
+ // shmem is charged, is NOT droppable (swap-backed), and is excluded from
450
+ // reclaimable -- so it has to be visible, or a tmpfs-heavy container
451
+ // shows a charge that neither of the other two numbers accounts for.
452
+ if (mem.shmem) msub += ' · ' + bytes(mem.shmem) + ' shmem';
444
453
  var mpct = (mem.resident == null || !mem.charged) ? null :
445
454
  Math.round(100 * mem.resident / mem.charged);
446
455
  out += tile('Container Memory', bytes(mem.charged), msub, mpct);
447
- } else { out += tile('Container Memory', '--', 'no cgroup v2'); }
456
+ // 'unavailable', not a cgroup-v2 verdict: this branch is also reached on a
457
+ // host-namespace deployment (the controller exists, its interface files
458
+ // are absent from the v2 root) and on a partial read. The console cannot
459
+ // tell those apart, and a wrong diagnosis sends an operator to check the
460
+ // wrong thing -- the neighbouring tiles say 'unavailable' for the same
461
+ // reason, and so does the origin task's acceptance detail.
462
+ } else { out += tile('Container Memory', '--', 'unavailable'); }
448
463
  out += tile('Review Queue', t.queue_depth, 'PRs awaiting me, last poll');
449
464
  el('tiles').innerHTML = out;
450
465
  }
@@ -501,6 +501,12 @@ class Sources:
501
501
  # trees out of it and the top-process list ranks the same snapshot, so
502
502
  # the two panels can never disagree about a process that exited between
503
503
  # them -- and the host pays for one walk per poll, not two.
504
+ #
505
+ # The cost on the other side of that trade, so nobody reorders this
506
+ # thinking it is free: the snapshot is now taken BEFORE `sessions()`
507
+ # shells out to tmux, so a pane that starts inside that window is
508
+ # missing from `stats` and its row shows "--" for CPU%/RSS for one
509
+ # poll. Self-healing in ~10s, and cheaper than walking /proc twice.
504
510
  proc_index = sysinfo.build_index(self._proc_root)
505
511
  sessions = self.sessions(index=proc_index)
506
512
  rate = self.rate_limit()
@@ -276,6 +276,15 @@ def _sum_or_none(*values: "int | None") -> "int | None":
276
276
  return sum(value for value in values if value is not None)
277
277
 
278
278
 
279
+ def _drop_shmem(file_bytes: "int | None", shmem: "int | None") -> "int | None":
280
+ """The droppable part of `file`. `shmem` unknown means the droppable part
281
+ is unknown -- publishing `file` whole would be the reassuring-direction
282
+ error the split exists to prevent."""
283
+ if file_bytes is None or shmem is None:
284
+ return None
285
+ return max(0, file_bytes - shmem)
286
+
287
+
279
288
  def cgroup_memory(
280
289
  cgroup_root: "str | os.PathLike[str]" = "/sys/fs/cgroup",
281
290
  ) -> dict:
@@ -287,13 +296,25 @@ def cgroup_memory(
287
296
  So we also report:
288
297
 
289
298
  * `resident` -- `anon`, the process memory that is really in use.
290
- * `reclaimable` -- `file` + `slab_reclaimable`, the page cache plus the
291
- shrinkable kernel caches: the "not really in use" bucket.
299
+ * `reclaimable` -- `(file - shmem) + slab_reclaimable`: the page cache plus
300
+ the shrinkable kernel caches, the "not really in use" bucket.
301
+ * `shmem` -- broken out of `file` rather than left inside it, because it is
302
+ not droppable: tmpfs, shm segments and shared anonymous mmaps are
303
+ swap-backed, so with swap disabled or capped (the normal container case)
304
+ the kernel cannot reclaim them under pressure -- they are what the
305
+ container gets OOM-killed for. Counting them as reclaimable is the one
306
+ way this reader can be wrong in the REASSURING direction, telling an
307
+ operator "5.9 GB of cache, no leak" about pages nothing can free. Issue
308
+ #74's scope text prescribes `file + slab_reclaimable` verbatim; the same
309
+ sentence calls the result the "not really in use" bucket, and where the
310
+ formula and the words disagree the words are the requirement. Surfaced as
311
+ its own magnitude instead of folded into `resident`, because "5 GB of
312
+ tmpfs" and "5 GB of process heap" are different operator problems even
313
+ though neither is droppable.
292
314
 
293
315
  The raw keys ride along for a spot check against an in-container
294
- `memory.stat`. Note the three summary numbers do NOT partition the charge
295
- (kernel stacks, pagetables, sockets are charged too, and `shmem` is counted
296
- inside `file` while behaving like anon memory) -- they are the three
316
+ `memory.stat`. Note the summary numbers do NOT partition the charge --
317
+ kernel stacks, pagetables and sockets are charged too -- they are the
297
318
  magnitudes an operator compares, not a balance sheet.
298
319
 
299
320
  Never raises. On a host without cgroup v2 -- a dev laptop, macOS, a v1
@@ -306,7 +327,14 @@ def cgroup_memory(
306
327
  out: "dict[str, int | None]" = {
307
328
  "charged": charged,
308
329
  "resident": stat["anon"],
309
- "reclaimable": _sum_or_none(stat["file"], stat["slab_reclaimable"]),
330
+ # `shmem` is a SUBSET of `file` in cgroup v2, so it is subtracted, not
331
+ # added. Clamped at zero: the two keys are sampled from one read of one
332
+ # file and cannot legitimately cross, but a negative "cache" number
333
+ # would be a worse thing to render than a zero if a kernel ever
334
+ # disagreed with that.
335
+ "reclaimable": _sum_or_none(
336
+ _drop_shmem(stat["file"], stat["shmem"]), stat["slab_reclaimable"]
337
+ ),
310
338
  }
311
339
  out.update(stat)
312
340
  return out
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-revloop
3
- Version: 0.16.15
3
+ Version: 0.16.16
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera