# orca.usage

> A module of morca's API.

What a run's processes use: each attempt's peak memory and CPU.

Whoever starts a run's process measures it: the scheduler for a local or
control-placed run (`orca.scheduler`), `orca worker` for a remote worker's
(`orca.worker`). Each tick, at most every quarter of a second, it reads
the process's tree in /proc (`orca.sample.tree_usage`: the driver and what
it started, dista's local workers among them) and keeps a `Meter` of it:
the resident bytes, the CPU seconds so far (utime + stime, with what the
tree's processes waited for), and the cores that kept busy since the
sample before. When the process is reaped, `wait4` adds its own word:
`ru_maxrss`, the largest single process (a lower bound on the tree's), and
its CPU seconds, which count descendants that ended after the last sample.

A process runs every attempt of a run's start, so its samples are shared
out among those attempts by time (`attribute`): an attempt's peak is the
largest sample taken while it ran (or, for one shorter than a tick, the
sample just after it), its CPU the meter's CPU at its end less at its
start, its peak cores the busiest interval it overlapped. `ru_maxrss`
raises the peak of a process's only attempt. A meter keeps at most
`MAX_MARKS` samples, merging neighbours as it fills (keeping their maximum
and the later total), so a long run's samples are coarser, not dropped.

On macOS, which has no /proc, there are no samples: a process's only
attempt gets `wait4`'s numbers, and none gets its peak cores; a process
of several attempts records none. Runs on EC2 or Kubernetes, and runs no
scheduler or worker started (`<binary> run` by hand), record nothing:
their columns stay NULL.

The numbers are `orca.attempts`' `peak_rss_bytes`, `cpu_seconds` and
`peak_cpus` (migration 35), written once, when the process ends: by the
scheduler over Postgres, and for a worker's run by `orca ui`, which the
worker sends its meter with `/api/worker/exited` (`marks_json`, ages in
seconds before the request, so neither clock needs to agree with the
database's).

## Structs

### `Mark`

```mojo
struct Mark
```

A sample of a process tree. `at` is seconds on the monotonic clock in a `Meter`, and seconds before now once `aged`.

Implements: `Copyable`, `ImplicitlyCopyable`, `Movable`

Fields:

- `at: Float64`
- `rss: Int`: The most resident bytes sampled up to here since the mark before; -1 for none (the start, and the end).
- `cpu: Float64`: The CPU seconds so far.
- `cpus: Float64`: The most cores busy over a sample's interval since the mark before; -1 for none.

### `Meter`

```mojo
struct Meter
```

One run process's samples, from its start to its end.

Implements: `Copyable`, `Movable`

Fields:

- `marks: List[Mark]`
- `max_rss: Int`: `wait4`'s `ru_maxrss` in bytes once it has ended, else -1.

#### `__init__`

```mojo
def __init__(out self, now: Float64 = Float64("-1"))
```

#### `sample`

```mojo
def sample(mut self, rss: Int, cpu: Float64, now_in: Float64 = Float64("-1"))
```

Adds a reading of the tree: its resident bytes and CPU seconds.

#### `end`

```mojo
def end(mut self, max_rss: Int, cpu: Float64, now_in: Float64 = Float64("-1"))
```

The process ended, `wait4` says having used `cpu` seconds with `max_rss` bytes its largest process.

#### `aged`

```mojo
def aged(self, now_in: Float64 = Float64("-1")) -> List[Mark]
```

The marks with `at` seconds before now, oldest first.

### `AttemptSpan`

```mojo
struct AttemptSpan
```

An attempt the process made, in seconds before now.

Implements: `Copyable`, `ImplicitlyCopyable`, `Movable`

Fields:

- `id: Int`
- `started: Float64`
- `ended: Float64`: -1 if it hadn't ended when the process did.

## Functions

### `attribute`

```mojo
def attribute(marks: List[Mark], max_rss: Int, attempts: List[AttemptSpan]) -> List[Usage]
```

Each attempt's share of a process's `marks` (aged, oldest first, the start first and the end last), as described above.

### `record`

```mojo
def record(mut db: State, run: Int, pid: Int, marks: List[Mark], max_rss: Int, worker: Int = Int(0), start: Int = Int(0)) -> Int
```

Writes each attempt's share of the process `pid`'s `marks` (aged at about the database's now) to the attempts of `run` that it made, and returns how many. With `worker`, only while the run's start `start` is that worker's.

### `marks_json`

```mojo
def marks_json(marks: List[Mark]) -> String
```

Aged marks as `[[age, rss, cpu, cpus], ...]`.

### `marks_of`

```mojo
def marks_of(v: Value) -> List[Mark]
```

`marks_json`'s marks back.

### `bytes_text`

```mojo
def bytes_text(n: Int) -> String
```

Resident bytes as MB (MiB, as `ORCA_CONTROL_MEMORY_MB` counts them), or GB from 10 GB.

### `cpu_text`

```mojo
def cpu_text(s: Float64) -> String
```

CPU seconds, as seconds, minutes or hours.

### `usage_text`

```mojo
def usage_text(u: Usage) -> String
```

`peak 812 MB, CPU 41.2 s on up to 3.9 cores`, as much as is known; empty for nothing.

### `usage_of`

```mojo
def usage_of(v: Value) -> Usage
```

`usage_json`'s usage back; -1 for what's null or missing.

### `usage_json`

```mojo
def usage_json(u: Usage) -> String
```

`{"peak_rss_bytes": ..., "cpu_seconds": ..., "peak_cpus": ...}`, null for what isn't known.

## Aliases

### `MAX_MARKS`

```mojo
comptime MAX_MARKS = 1024
```

The most samples a meter keeps; past it, neighbours are merged.

### `MIN_SAMPLE_SECONDS`

```mojo
comptime MIN_SAMPLE_SECONDS = 0.25
```

The least time between two samples of one process.
