Skip to content

meter

Measure an image's peak memory against recording length, rate and channels.

The platform claims memory for a run by :meth:ResourceRequirements.estimate_peak_system_memory_mib: a base the author sets, plus terms that scale with the recording's hours, its highest native sample rate and the number of selected channels. A claim that is too low fails the run out of memory, and only on the long recordings a short fixture never shows. The meter runs the image on recordings chosen so that length and padding effects appear, reads each run's peak, and compares it with the claim.

The points (:func:select_points):

  • every length at the middle rate with the largest channel selection;
  • the smallest selection, and the lowest and highest rates, at two lengths;
  • for each rate, the length just past a power-of-two sample count. Libraries that resample with an FFT pad each channel to the next power of two, so memory steps with length and roughly doubles just past a boundary.

Each run is the platform's run: the same confinement, the declared cores and thread variables, and the claim in the manifest. Only the container's memory limit is raised, so that the measurement is not cut short. With a runner that stages recordings (DockerRunner(stage_recording=True)), the recording is copied into a volume by a separate container first, so that its page cache is charged to that copy, as it is to whatever writes the file on the platform. The kernel reclaims that cache before it runs out of memory, so it is not memory the claim must cover.

:func:propose fits the measured peaks by least squares to the claim's terms and proposes terms that leave the required headroom at every point, with rates above the fitted ones so that the headroom holds beyond the measured range.

Fit dataclass

A least-squares fit of the peaks to some of the claim's terms.

describe()

Write the fit as a formula.

MeterPoint dataclass

One recording the image is measured on.

describe()

24.00 h, 3 ch, 512 Hz and the recording's name when it is real.

MeterReport dataclass

The outcome of a metering run.

ok property

Every point was measured and the claim holds at each.

growth()

Find the largest rise of the peak with length, at one rate and selection.

Returns:

Type Description
tuple[float, int, float, int] | None

(short hours, its peak, long hours, its peak) when some peak

tuple[float, int, float, int] | None

rises by more than :data:GROWTH_MIB and :data:GROWTH_FRACTION;

tuple[float, int, float, int] | None

None otherwise.

problems()

Every reason the claim does not hold; empty when it does.

to_dict()

Return a JSON-ready view of the report.

MeterRow dataclass

What one point measured.

failure property

Why the point measured nothing usable; empty when it did.

headroom property

How far the claim is above the peak, as a fraction of the peak.

peak_mib property

The measured peak in MiB.

Proposal dataclass

Memory terms that leave the required headroom at every measured point.

boundary_hours(rate_hz, shortest, longest)

Return the length just past the largest power-of-two sample count in a range.

Returns:

Type Description
float | None

Hours, in whole seconds, :data:PAST_BOUNDARY past 2**k samples

float | None

of one channel at rate_hz, for the largest k that lands

float | None

between shortest and longest; None when none does.

configured_terms(resources)

Return the length terms a configuration uses.

describe_claim(resources)

Write the claim as a formula, e.g. 800 + 450*h*kHz + 90*h*ch*kHz MiB.

field_name(term)

Name the resources field that holds a term: base or one of :data:TERMS.

fit_terms(samples, terms=TERMS)

Fit peaks to the base and terms by least squares, every coefficient at least 0.

Every subset of the terms is solved and the one with the smallest residual and no negative coefficient is kept, preferring fewer terms on a tie: a negative term would promise memory back for longer recordings.

Parameters:

Name Type Description Default
samples Sequence[tuple[float, int, float, float]]

(hours, channels, rate in Hz, peak MiB) per measurement.

required
terms Sequence[str]

The terms the claim may use, a subset of :data:TERMS.

TERMS

Raises:

Type Description
ValueError

If there is nothing to fit.

has_length_term(resources)

Whether any term of the claim scales with the recording's length.

measure_point(image, config, runner, point, workdir, *, limit_mib, timeout_seconds=900, seed=0, upstream_events=None)

Write one point's recording and manifest, run the image on it, and read the peak.

meter(*, image, config, runner, limit_mib=DEFAULT_LIMIT_MIB, hours=DEFAULT_HOURS, rates=DEFAULT_RATES, recordings=(), headroom=HEADROOM, timeout_seconds=900, seed=0, upstream_events=None, pull=False, keep=None, progress=None)

Measure an image's peak memory on each point and compare it with the claim.

Parameters:

Name Type Description Default
image str

The image reference to run.

required
config str | Path

The configuration file whose claim is checked.

required
runner Runner

The container runtime, normally a :class:DockerRunner.

required
limit_mib int

Each run's memory limit, high enough not to cut it short.

DEFAULT_LIMIT_MIB
hours Sequence[float]

The recording lengths.

DEFAULT_HOURS
rates Sequence[int]

The sample rates of synthetic recordings.

DEFAULT_RATES
recordings Sequence[str | Path]

Real EDF recordings to cut or extend instead.

()
headroom float

The fraction of the peak the claim must exceed it by.

HEADROOM
timeout_seconds float

A run's timeout, plus :data:TIMEOUT_PER_HOUR per hour of recording.

900
seed int

Makes the synthetic recordings reproducible.

0
upstream_events str | Path | None

An events document for a configuration that declares an events input.

None
pull bool

Pull the image before inspecting it.

False
keep str | Path | None

Keep each point's files in this directory.

None
progress Callable[[str], None] | None

Called with a line before and after each run.

None

Returns:

Type Description
MeterReport

The report. report.ok is the verdict; report.problems() says

MeterReport

why not.

propose(report, *, terms=None, margin=MARGIN)

Propose memory terms from the measured peaks.

The peaks are fitted to terms: by default the length terms the configuration uses, or every term when it uses none. The proposal keeps the fitted shape and scales it up:

  • the base is the report's headroom over the fitted base;
  • every fitted rate is multiplied by one factor, the smallest that gives each measured point the headroom, and at least margin so that the claim grows faster than the peaks beyond the longest recording measured. The points just past a power-of-two sample count usually set it, which carries their padding to the boundaries between the points;
  • rates are rounded up to a round number (105, 380, 1250), the base to a multiple of :data:BASE_STEP_MIB, and the base is raised if a point still lacks the headroom.

Raises:

Type Description
ValueError

If a point failed, or nothing was measured.

regressors(hours, n_channels, rate_hz)

Return what each of the claim's terms is multiplied by, for one recording.

render_proposal(proposal, report)

Render the resources: block for the configuration, measurements as comments.

render_table(report)

Render the measurements, one line per point, and the verdict.

select_points(config, *, hours=DEFAULT_HOURS, rates=DEFAULT_RATES, recordings=())

Choose the recordings to measure, so that length and padding effects show.

Synthetic recordings (no recordings) take every channel at the point's rate. With recordings the rate is each file's own, and every point is that file cut or extended.

Returns:

Type Description
tuple[list[MeterPoint], list[str]]

The points, without repeats, and notes on what was left out.

Raises:

Type Description
ValueError

If no point can be chosen: no length, no rate the configuration accepts, or a recording that cannot satisfy it.