Repository navigation
View Arbor
The process tree drawn as a tree. PID 1 is the trunk and every other process is a branch on its parent; a branch is as thick as the memory of the whole subtree it carries (Leonardo's rule), leaves are threads, green when idle and shading through yellow to red as they work, blossoms open on processes that have just started, leaves drift down when threads exit, the canopy sways in a wind set by CPU pressure, and the kernel threads grow as roots under a cut-away slab of soil. Where a branch leaves its parent is fixed by a phyllotaxis slot, so nothing in the geometry depends on CPU, memory or how many processes there are.

The README image of the view (docs/media/arbor.webp), rendered from the demo workload.
The process tree. Every Unix process except the first has a parent, so the relation is a tree rooted at PID 1 (init or systemd; launchd on macOS). A process is made by fork, may replace its program with exec, and ends with exit; its parent collects the exit status with wait, and until it does the dead child is a zombie (state Z). If a parent dies first, its children are re-parented to PID 1 or to the nearest ancestor that declared itself a subreaper. Linux has a second tree under PID 2, kthreadd, the parent of the kernel threads. Threads hang on each process as a flat set, so the tree is a tree of processes, with the threads as a canopy on each twig. /proc/<pid>/stat carries the parent's pid; on macOS the parent comes from libproc.
Why a tree is a good picture of it. A plant is the same kind of object: a structure built by repeated budding, in which what was made first stays where it is and new parts attach to old ones. Two properties transfer almost unchanged. New growth does not move old growth (a branch never relocates when a twig appears), which is the project's stable-layout invariant (Theory Stable Layout). And a plant has a conservation law of its own, the one that ties the thickness of a branch to what it supports (see Theory below).
The longer account lives in the repository: docs/theory/arbor.md.
Trees are a habit, not a family. A tree is a growth form, a plant that stands on its own wood, and plants invented it several separate times. The first tall plants were small: Cooksonia and its relatives, a few centimetres of bare forked stem, about 425 million years ago. Wattieza, whose stumps were found in the Gilboa quarry in New York State, is the oldest tree known in the fossil record, about 385 million years old: a hollow-trunked cladoxylopsid with a crown of frond-like branches and a shallow root system. Reports differ on how much older it is than the next tree, by roughly 15 to 25 million years.
Archaeopteris followed, and it is the ancestor of what the word usually means: true wood from secondary growth, branching in all directions, flat leaves and deep roots, forming forests of trees some tens of metres tall by the end of the Devonian. One hypothesis is that its roots, breaking rock and drawing down carbon dioxide, helped cool the Late Devonian climate. It remains a hypothesis. The Carboniferous coal forests (about 360 to 300 million years ago) were giant club-mosses and horsetail trees; a popular explanation of why they became coal, that nothing yet could rot lignin, was proposed in 2012 and has since been disputed.
Branching rules. Leonardo da Vinci's notebooks state that when a tree divides, the thickness of the daughters taken together equals the thickness of the parent. In 1964 Shinozaki and colleagues proposed the pipe model: a tree is a bundle of unit pipes, one per leaf-bearing twig, so a branch's cross-section tracks the leaf area it carries. In 2011 Christophe Eloy showed that the same rule follows from a different principle, equal risk of wind damage at every scale. The spiral arrangement of leaves and branches, phyllotaxis, was a puzzle for centuries: Karl Schimper and Alexander Braun found its Fibonacci regularities in the 1830s, the Bravais brothers measured the divergence angle near 137.5 degrees in 1837, and in 1992 Stephane Douady and Yves Couder reproduced the pattern with magnetized droplets, showing that it emerges when each new bud is placed where the earlier ones leave the most room.
The tree as an idea. Darwin sketched a branching tree in a notebook in 1837 beside the words "I think", and Haeckel drew his oaks of life in 1866. The process tree is the same figure with one difference: a process can die before its children, which are then adopted by PID 1, a sort of grafting. The only dated cultivation record on this page is the 1994 discovery of Wollemia, a conifer known only from fossils, alive in a gorge outside Sydney; no process has been reported to do that.
The standard way to draw a rooted tree is the Reingold-Tilford algorithm (1981), which places subtrees as compact contours with parents centred over their children; Walker (1990) made it linear for general trees. These layouts optimise compactness, and an optimum moves when the input changes: insert a child and its siblings, cousins and whole subtrees shift. isotop requires stable layouts, so Arbor lays the tree out by growth rules instead of optimisation: each parent hands its children slots on a fixed pattern in the order it first sees them, and a placed child never moves.
Place the
Vogel (1979) generated sunflower heads with
Points at three fixed angles, radius proportional to the square root of the index. A rational angle leaves spokes. From the repository's theory page.
The code uses the literal 137.5 degrees (SLOT_ANGLE, whose doc comment calls it the golden angle), not layout::GOLDEN_ANGLE (137.50783 degrees). With 137.5 exactly, attachment), so they do not coincide; but a parent with more than 144 children, such as kthreadd on a real machine, has azimuths that repeat.
At a branching, parent and child radii obey
the conservation of cross-sectional area (Leonardo's rule). The botanical explanation is the pipe model (Shinozaki, Yoda, Hozumi and Kira, 1964): each unit of foliage is served by a unit pipe of constant cross-section, so the stem area at any point is the number of pipes it carries. A branch that splits into two equal halves has daughters of radius
Each branch is a damped oscillator pulled toward the lean the gust asks for:
Here
with branch radius
Left: Leonardo (areas add) against Murray (cubes add). Right: step responses; $\zeta = 0.12$ is DAMPING in src/arbor.rs. From the repository's theory page.
| Visual element | Measured quantity (source) | Transform |
|---|---|---|
| Trunk | PID 1, as a process or as a pale shadow (Snapshot.shadows, launchd on macOS without privilege); without PID 1, the top-level node whose tree holds the most nodes (ties to the lowest key) |
a node whose parent is not drawn, or that sits in a cycle, hangs on the trunk |
| Branch cross-section | subtree memory: Process.memory summed over the subtree (the memory field, resident bytes) |
radius R_TRUNK), floor 0.05 (RADIUS_FLOOR); along a branch the area steps down at each junction (Tree::levels), so Leonardo's rule holds at every junction |
| Where a branch leaves its parent | order in which the parent first saw its children (a counter that only rises, ties by Identity.start then pid) |
slot |
| Branch length and angle | depth in the tree | length ELEVATION_TRUNK, ELEVATION) |
| Roots | processes of Kind::Kernel that are top-level or direct children of the trunk, with their subtrees |
length ROOT_COLLAR, 0.55 of the trunk radius) |
| Root glow |
Process.cpu of the root process |
bounded(cpu, 6), a saturating |
| Leaf | one per thread: Process.thread_list (Source::Threads), else Process.threads leaves |
at most 32 per twig (LEAVES_PER_TWIG) and 6,000 in all (LEAF_CAP); threads past the cap share a leaf by tid bucket, showing the bucket's mean CPU |
| Leaf colour | thread CPU as a fraction of one core; a thread with no reading shows the process's cpu / threads
|
stops at 0 (74, 150, 72), 0.08, 0.3 (214, 196, 60), 0.6, 1.0 (220, 52, 44), linear between (leaf_color) |
| Blossom | a process the scene first saw less than 5 s ago (Stage::age, BLOSSOM_SECONDS) |
up to 6 per process, opening and closing with a smoothstep; processes present on the first frame never blossom; roots do not |
| Falling leaf | a thread of the previous sample that is gone, or every leaf of a process that exited or became a zombie | falls 4 s (FALL_SECONDS), rests on the soil 30 s (REST_SECONDS), at most 400 at once (FALLEN_CAP) |
| Grey twig | zombie (state == 'Z') with no children |
colour (128, 128, 128) |
| Pale branch | a parent isotop cannot read (Snapshot.shadows) |
colour (214, 218, 238), not pickable; the legend adds | pale branch = a parent isotop can't read (run with sudo)
|
| Sway |
Snapshot.pressure[0], CPU pressure (10 s average) |
bounded(pressure, 20) eased with a 1.5 s time constant (WIND_EASING) into the oscillators of the section above; roots do not sway |
| Soil slab, grass, sky, bark texture, leaf shapes, curve of each branch | none | decorative |
Module and registry row. src/arbor.rs holds Arbor (a #[derive(Default)] struct implementing views::ViewModule) and one row at the end of views::REGISTRY: Backdrop::Dusk, pitch: Some(0.48) (the camera looks across the canopy), flash: false, link_strokes: true, sources: Sources::of(&[Source::Threads]) (threads are sampled only while the view is shown), no keys, no digit (digits pick only the first ten rows). Its demo options are churn, threads, thread_churn and unreadable_parent, and its headless warm-up plays 8 s of demo at a 0.05 s step, so blossoms, falling leaves and sway are steady in a PNG. There is no plugin system: the view is compiled in.
The tree (Tree::build). Nodes are built from parent links; the trunk is chosen as in the Mapping table; kernel processes under the trunk go to the Side::Root side. Slots::seat gives each fresh child the next number of its parent's counter in start-time order; a child already seated there keeps its slot, and the counter never falls when a child leaves. Tree::grow then fixes every branch's rest geometry from slot, parent and a per-node seed (Rng::for_identity with SALT): the direction from the elevation and the azimuth, a bend toward the sky (roots: toward the ground) of 0.18 to 0.48 plus a small wobble, so point(s) = base + length (direction s + bend s^2). Topology and rest geometry are rebuilt when the sample changes; each frame runs only the sway and the projection.
Thickness (Tree::weigh, Weights::radius, Tree::stops). On the crown a node's area is its resident memory; among the roots it is 1. Subtree sums run leaves-first. A branch's radius at its base is RAMP), and children whose junctions lie within 2 % of each other share one step (JUNCTION_MERGE), so a parent of hundreds of children is a handful of pieces.
Drawing. A branch wider than 2 px is a Frame::tube with 4, 6 or 8 sides (by width: over 5 px, over 14 px), shaded by render::strokes::lit; thinner ones are Frame::ribbons with a floor of 1.2 px, so the CPU and GPU backends rasterize the same triangles. Leaves are two-triangle diamonds picking the owning process; blossoms are spheres with a halo Item::Puff. The soil is a checkered grass top and a four-layer cut-away side (SOIL_HALF 20, SOIL_DEPTH 10). The camera is fitted to a fixed cylinder (FIT_RADIUS 22, FIT_TOP 32), not to the moving canopy.
Leaf budget (twig_cap, sampled_twigs). The per-twig cap starts at 32 and is lowered for every twig alike until the scene holds at most 6,000 leaves; when even one leaf per process would pass 6,000, the 6,000 processes with the lowest hash of identity keep theirs, which is stable from sample to sample.
Wind and time. The oscillators step on simulation::FixedStep (STEP 0.05 s, MAX_STEPS 10, MAX_GAP 0.5 s). Each gust is a steady push along a heading of 0.35 rad plus two incommensurate sines whose phase is seeded per branch, so no two branches move alike and the motion does not repeat; a child's base moves with its parent's displacement, weighted by the square of its attachment fraction (Sway::bases). A full gust leans a branch by 9 % of its length (LEAN).
Stable layout. Arbor::anchors (test only) returns each twig's tip at rest, which depends on the tree alone; the view harness checks that no process moves when CPU and memory scale or a tenth of the population turns over.
Picking, text and tour. Twig tips go to stage.positions; stage.places names the trunk with its total memory, up to four roots with their kernel-thread counts and the six heaviest branches (LABELLED_BRANCHES); stage.notes for a lit process holds the subtree memory, process and thread counts, leaves drawn, the slot and the three busiest threads as tid name cpu%, and all names pass terminal::clean_text. The tour visits the 16 heaviest branches and 3 heaviest roots (TOUR_BRANCHES, TOUR_ROOTS).
Legend. Trunk = PID 1 | branch area = subtree memory | leaves = threads: green idle, red busy | blossoms = started < 5 s | falling leaves = exited threads | roots = kernel threads | wind = CPU pressure N%, with suffixes appended while they apply: the pale-branch note, | no kernel threads reported: no roots, the status::missing_note wording for CPU pressure ("still air") and threads ("leaves use process CPU"), and the count of processes whose per-thread CPU was unreadable.
Platforms. Linux: full; without root, other users' processes may be unreadable and appear as pale branches. macOS: the tree, areas, thread-count leaves, blossoms and falls work from libproc; without privilege launchd is the pale trunk. Where a platform reports no kernel threads or no CPU pressure, the legend says no kernel threads reported: no roots or still air; which of these applies on a given Mac was not run here (unverified).
- Measured: the process tree (who spawned whom, from the parent pid), memory per process, thread counts and per-thread CPU where the source is read, which processes are new (first seen), zombies, and CPU pressure.
- Derived: branch thickness (a sum over the subtree, so it is the subtree's memory, not the branch's own), slot positions (a deterministic function of the order in which children were first seen, not of anything about the processes), and the sway phase (seeded).
- Decorative: the soil, sky, grass, the exact curve of each branch, leaf shapes.
- A thick branch is a heavy subtree, not a heavy process. A thin shell that spawned a huge compile is thick below it. The inspector's subtree memory and the twig's own figures separate the two.
-
Cannot infer: a process's age from its position (slot order is the order the scene saw children, apart from ties); the order of
exec; CPU from branch colour (the colour is on the leaves, that is on threads). - Roots are counts, not bytes. Kernel threads have no resident memory, so root thickness is the number of kernel processes in the subtree, in a different unit from the canopy.
- Resolution. With 192 processes most leaves are a few pixels. Above the leaf cap leaves stop being threads one to one, and the inspector's "threads, leaves" line shows the difference.
Commands run with the release binary on Linux (x86_64, CPU renderer), repository root:
./target/release/isotop --list-views
./target/release/isotop --demo --view arbor --time 12 --width 1600 --output /tmp/arbor.png --renderer cpuOutput of the second command here: Wrote /tmp/arbor.png (1600x900, 216 visible). The first lists eighteen views, arbor last (the order of views::REGISTRY). Live: ./target/release/isotop --view arbor, or Tab and the v picker. c cycles the link mode, which draws sockets as hairlines between twig tips. Space visits processes in turn. pstree -p 1 shows the real tree the demo imitates (not run here).
| Item | Path and symbol |
|---|---|
| The view |
src/arbor.rs: Arbor, Tree::build, Tree::grow, Slots, attachment, Weights, Tree::levels, leaf_specs, twig_cap, leaf_color, Sway, Oscillator, natural_frequency
|
| Registry row |
src/views.rs: REGISTRY row arbor
|
| Strokes |
src/render/strokes.rs: Frame::tube, Frame::ribbon
|
| Shared helpers |
simulation::FixedStep, Rng::for_identity, layout::GOLDEN_ANGLE (not used for the slots), Stage::age, model::bounded
|
| Unreadable parents |
Snapshot.shadows (platform::Shadow) in src/platform/mod.rs
|
| Repository theory page | docs/theory/arbor.md |
| Prototype (reference only) | docs/mockups/code/arbor.py |
- The design in the roadmap used lengths of
$14 \times 0.78^{d}$ and elevations of 50 and 40 degrees; the implementation uses 0.84 and 66 and 52 degrees, which gives a broader, more open crown. The figure below was computed for the earlier numbers: its panel (a) (azimuth gaps) applies unchanged, panel (b) does not. - The 145th child of a parent has the azimuth of the first, because
SLOT_ANGLEis 137.5 degrees exactly. Usinglayout::GOLDEN_ANGLEremoves the repeat; whether it changes the look ofkthreadd's roots is untried. - Replace the slot order "first seen" by the true start order when the whole tree is present, and see whether stability survives churn.
- A Murray-style root system: radius by exponent 3 for the roots (kernel threads as a fluid network) and 2 for the canopy; the inequality above predicts thinner roots.
- Use
Snapshot.lifeevents for blossoms instead of first-seen age, so fork andexecstorms show with exact times. - Replace the convention frequency by the cantilever scaling
$r/L^{2}$ and compare how the sway reads. - A leaf that falls is not a grave. A process that exits drops all its leaves here and is buried, on its own terms, in View Necropolis; the two views disagree only about which part of it was worth keeping.
docs/figures/arbor-skeleton.py. (a) Largest azimuth gap against slots used for four turn angles. (b) A seeded synthetic tree with the design's earlier branch geometry (not the constants above). (c) Child to parent radius ratios. The tree is a stand-in, not a machine.
- E. M. Reingold and J. S. Tilford, "Tidier drawings of trees", IEEE Transactions on Software Engineering SE-7(2) (1981) 223-228; J. Q. Walker II, "A node-positioning algorithm for general trees", Software: Practice and Experience 20 (1990) 685-705.
- H. Vogel, "A better way to construct the sunflower head", Mathematical Biosciences 44 (1979) 179-182; S. Douady and Y. Couder, "Phyllotaxis as a physical self-organized growth process", Physical Review Letters 68 (1992) 2098-2101.
- K. Shinozaki, K. Yoda, K. Hozumi and T. Kira, "A quantitative analysis of plant form: the pipe model theory", Japanese Journal of Ecology 14 (1964) 97-105 and 133-139.
- C. Eloy, "Leonardo's rule, self-similarity, and wind-induced stresses in trees", Physical Review Letters 107 (2011) 258101.
- C. D. Murray, "The physiological principle of minimum work: I. The vascular system and the cost of blood volume", Proceedings of the National Academy of Sciences 12 (1926) 207-214.
- W. E. Stein et al., "Giant cladoxylopsid trees resolve the enigma of the Earth's earliest forest stumps at Gilboa", Nature 446 (2007) 904; B. Meyer-Berthaud, S. E. Scheckler and J. Wendt, "Archaeopteris is the earliest known modern tree", Nature 398 (1999) 700.
- P. Prusinkiewicz and A. Lindenmayer, The Algorithmic Beauty of Plants (Springer, 1990).
- See also Theory Stable Layout, Theory Life Events, Theory Text Safety, Math Spatial Hashing.
isotop — maximalism for its own sake. Source: tfpickard/isotop. This wiki is published from wiki/ in the repository; edit it there. This footer is decorative.
Philosophy · Gallery · Glossary
Theory
Overview
Sampling: Linux
Sampling: macOS
Background sampler
Model and rates
Life events
History
Journal
Scene and display list
Camera and projection
Rasterization
GPU and parity
Picking
Terminal graphics
Stable layout
Determinism
Simulation toolkit
Colour
Text safety
Mathematics
Fourier epicycles
Wave equation
Flocking
Spatial hashing
Stellar photometry
Lorentz and Schlegel
Reaction-diffusion
Spiking neurons
Schwarzschild
Views
City · Orbit · Ripple
Flow · Cores · Cells
Strata · Globe · Reef
Coop · Matrix
Ptolemy · Tesseract
Necropolis · Cortex
Horizon · Murmuration
Arbor · HR
Hive · Switchboard
Armorial · Lights
Planned
Mold · Chamber
Anthill · Plumbing
Morph · Ouija
Canyon · Die
Guides
Writing a view
Recipes
Headless renders
Contributing to the wiki