[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"note-how-a-plugins-memory-is-judged-en":3},{"slug":4,"order":5,"title":6,"summary":7,"measured":8,"topic":9,"toc":10,"html":26},"how-a-plugins-memory-is-judged",10,"How a plugin's memory is judged","A plugin's memory is measured per instance, watched for growth, bounded by a hard limit and judged against each host's budget, so that no plugin is qualified as good on a host it would hurt.","plugin-qualify, October 2026. The Raspberry Pi budgets are provisional until read on a Pi.","qualify",[11,14,17,20,23],{"id":12,"title":13},"footprint-per-instance","Footprint per instance",{"id":15,"title":16},"growth-over-time","Growth over time",{"id":18,"title":19},"a-hard-limit","A hard limit",{"id":21,"title":22},"a-verdict-per-host-profile","A verdict per host profile",{"id":24,"title":25},"what-is-not-measured-yet","What is not measured yet","\u003Cp>A plugin's memory is judged, never merely recorded: a plugin must not be qualified as good on a host it would hurt,\na Raspberry Pi included. Four pieces do it: a footprint per instance, a check for growth over time, a hard limit, and a\nverdict per host profile.\u003C\u002Fp>\n\u003Ch2 id=\"footprint-per-instance\">Footprint per instance\u003C\u002Fh2>\n\u003Cp>Beside the cost of each rate, the record carries how far the resident set grew, in KB: after instantiate, after\nactivate, at rest after processing, and with notes held for an instrument (at the same count as the cost), and what a\nsecond instance added.\u003C\u002Fp>\n\u003Cp>Each rate's footprint is taken by a child process forked before any instance exists, so no earlier instance has grown\nthe process, and a plugin that dies there costs that rate its figure and nothing else. For CLAP, what loading the\n\u003Ccode>.clap\u003C\u002Fcode> took is added to the first instance; an LV2 plugin is loaded by its first instantiate. A figure not taken is\nabsent, never 0, and the record says why.\u003C\u002Fp>\n\u003Cp>Two figures come out of it, written beside the raw ones so that a client adds up the same numbers the verdict read:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>firstInstanceKb\u003C\u002Fcode>: the most one instance held at any step, with everything its instances share.\u003C\u002Fli>\n\u003Cli>\u003Ccode>perInstanceKb\u003C\u002Fcode>: what one more instance costs: the second instance's own part, plus the voices (notes held less at\nrest). With no second instance measured nothing separates what is shared, so it is the whole of the first.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The second instance is measured to the end of its first blocks, not only to instantiate: a plugin that allocates at\nactivate would otherwise read as free.\u003C\u002Fp>\n\u003Cp>The child does not inherit the probe's memory lock. A footprint read under the lock would count every page the plugin\nmapped, not what it touched.\u003C\u002Fp>\n\u003Ch2 id=\"growth-over-time\">Growth over time\u003C\u002Fh2>\n\u003Cp>A rate whose memory keeps climbing past a ceiling fails its soak with the reason \u003Ccode>leaks\u003C\u002Fcode>. The prober reads the resident\nset after a warm-up and again halfway from there to the end; the growth is worked out from the halfway reading to the\nhighest, per minute of audio. Measuring only the second half is what tells a library that finished loading late from a\nleak.\u003C\u002Fp>\n\u003Cp>The ceiling is 10 MB per minute. It ships provisional, is tightened from measurements, and the verdict's reason says\n\u003Ccode>provisional\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"a-hard-limit\">A hard limit\u003C\u002Fh2>\n\u003Cp>Two limits, each with its own job:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>plugin-hostd enforces \u003Ccode>memory.max\u003C\u002Fcode>\u003C\u002Fstrong> (cgroup v2) on the supervised probe child. The kernel counts what the plugin\nreally holds and kills its tree at the limit; the outcome is \u003Ccode>probe.memory-limit\u003C\u002Fcode>, read from \u003Ccode>memory.events\u003C\u002Fcode>. A machine\nwith no delegated controller falls back to \u003Ccode>RLIMIT_DATA\u003C\u002Fcode> and says so in the record.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>plugin-qualify enforces \u003Ccode>RLIMIT_AS\u003C\u002Fcode>\u003C\u002Fstrong> on the processes it starts itself. It is set to all the machine's RAM: the far\nbound of a run rather than its budget, since address space is what a plugin reserves, not what it touches. It is off\nfor a sanitizer build, which reserves far more than it uses. The first refused allocation ends the child with the\noutcome \u003Ccode>memory-limit-exceeded\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Either way the cell carries its outcome and the limit, the record is written, and the other cells are judged. A host\nwhose own cell hit the limit is \u003Ccode>unknown\u003C\u002Fcode> there, in the memory dimension.\u003C\u002Fp>\n\u003Ch2 id=\"a-verdict-per-host-profile\">A verdict per host profile\u003C\u002Fh2>\n\u003Cp>A host profile's budget holds a per-instance share and a whole-console figure. A profile with no budget declared reads\nnothing here, which is not a budget of 0: \u003Ccode>jalv\u003C\u002Fcode>, \u003Ccode>mod-host\u003C\u002Fcode> and \u003Ccode>omx-clap-host\u003C\u002Fcode> are general hosts on no particular\nmachine. Memory is a dimension of the verdict with its own codes (\u003Ccode>hosting.memory.*\u003C\u002Fcode>) and can decide on its own.\u003C\u002Fp>\n\u003Cp>\u003Ccode>perInstanceKb\u003C\u002Fcode> is read against the instance's share and \u003Ccode>firstInstanceKb\u003C\u002Fcode> against the whole console's, so a sampler with\na large shared library can fit a small host once and is still read correctly for two. Per cell, the worst standing wins:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>what was read\u003C\u002Fth>\n\u003Cth>rating\u003C\u002Fth>\n\u003Cth>code\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>a figure above a ceiling\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>unsuitable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>hosting.memory.above-ceiling\u003C\u002Fcode> (with \u003Ccode>provisional\u003C\u002Fcode> when the ceiling is)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>no figure, or a reason beside the figures\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>unknown\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>hosting.memory.unmeasured\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>under a measured budget\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>suitable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>hosting.memory.within-ceiling\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>under a provisional budget\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>conditional\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>hosting.memory.within-provisional-ceiling\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>A plugin under a provisional budget is therefore not qualified there: the figure and the ceiling are stated, and the\nverdict does not say the plugin fits. Only a measured budget does that.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The console\u003C\u002Fstrong> profile is sized for a 30 GiB machine: 16 GiB for the plugins of a console, the rest for the engine and\nthe system, and 4 GiB for one instance, which is also what the probe bounds a run by.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>A Raspberry Pi\u003C\u002Fstrong>: the 4 GB and 8 GB profiles have the shape of \u003Ccode>omx-clap-host\u003C\u002Fcode>, which is what runs a plugin on a Pi. Their\ncells are what Zynthian sets a Pi to: 48000 Hz and 128 frames on a Pi 5 with the V5 kit, 256 frames on a Pi 4 and the\nother kits. Wider sweeps (44.1 to 192 kHz, 128 to 1024 frames) are benchmark cells for comparing hosts, and no plugin is\njudged at them. The budget is all the board's RAM but 1 GiB for the system and Zynthian's interface, and an instance's\nshare an eighth of that: 3 GiB and 384 MiB on 4 GB, 7 GiB and 896 MiB on 8 GB. These are provisional until read on a Pi.\u003C\u002Fp>\n\u003Cp>Since a missing figure reads as \u003Ccode>unknown\u003C\u002Fcode>, a console that upgrades reads every plugin as \u003Ccode>unknown\u003C\u002Fcode> until it has been\nmeasured again. That is the rule at work: nothing says the old figures still fit.\u003C\u002Fp>\n\u003Ch2 id=\"what-is-not-measured-yet\">What is not measured yet\u003C\u002Fh2>\n\u003Cp>A CLAP plugin's growth over time: the CLAP prober has no continuous render yet, so \u003Ccode>leaks\u003C\u002Fcode> applies to LV2 plugins only and a\nCLAP plugin reads \u003Ccode>unsoaked\u003C\u002Fcode>. The footprint itself is measured for both formats.\u003C\u002Fp>\n",1793954995762]