Control Groups (cgroups) for the Web?

You’ve optimized every aspect of your page—it’s fast, and you can prove it. However, for better or worse, you also need to include a resource that you do not control (e.g. owned by a different subteam or a third-party), and by doing so you lose most, if not all, guarantees about the runtime performance of your page - e.g. an included script resource can execute any code it wants, at any point in your carefully optimized rendering loop, and for any lengths of time; it can fetch and inject other resources; all of the scheduling and execution is on par with your carefully crafted code.

We’re missing primitives that enable control over how and where CPU, GPU, and network resources are allocated by the browser. To the browser, all scripts look the same. To the developer, some are more important than others. Today, the web platform lack the tools to bridge this gap, and that’s at least one reason why delivering reliable performance is often an elusive goal for many.

We can learn from those before us…

Conceptually, the above problem is nothing new. For example, Linux control groups (cgroups) address the very same issues “higher up” in the stack: multiple processes compete for a finite number of available resources on the device, and cgroups provide a mechanism by which resource allocation (CPU, GPU, memory, network, etc) can be specified and enforced at a per-process level - e.g. this process is allowed to use at most 10% of the CPU, 128MB of RAM, is rate-limited to 500Kbps of peak bandwidth, and is only allowed to download 10Mb in total.

The problem is that we, as site developers, have no way to communicate and specify similar policies for resources that run on our sites. Today, including a script or an iframe gives it the keys to the kingdom: these resources execute with the same priority and with unrestricted access to the CPU, GPU, memory, and the network. As a result, the best we can do is cross our fingers and hope for the best.

Arguably, Content-Security-Policy offers a functional subset of the larger "cgroups for the web" problem: it allows the developer to control which origins the browser is allowed to access, and new embedded enforcement proposal extends this to subresources! However, this only controls the initial fetch, it does not address the resource footprint (CPU, GPU, memory, network, etc.) once it is executed by the browser.

Would cgroups for the web help?

As a thought experiment, it may be worth considering how a cgroups-like policy could look like in the browser, and what we would want to control. What follows is a handwavy sketch, based on the frequent performance failure cases found in the wild, and conversations with teams that have found themselves in these types of predicaments:

<!-- "background" group should receive low CPU and network priority and consume at most 5% of the available CPU and network resources --> <meta http-equiv= "cgroup" name= "background" content= "cpu-share 0.05; cpu-priority low; net-share 0.05; net-priority low;" > <!-- "app" group should receive high CPU priority and be allowed to consume up to 80% of available CPU resources (don't hog all of CPU), but be allowed to consume all of the available network resources --> <meta http-equiv= "cgroup" name= "app" content= "cpu-share 0.8; cpu-priority high; net-share 1.0; net-priority high" > <!-- "ads" group should receive at most 20% of the cpu and have lower scheduling and network priority then "app" content. --> <meta http-equiv= "cgroup" name= "ads" content= "cpu-share 0.2; cpu-priority medium; net-share 0.8; net-priority medium" > ... <!-- assign followng resources to "app" group --> <link cgroup= "app" rel= "stylesheet" href= "/style.css" > <script cgroup= "app" src= "/app.js" async ></script> <!-- assign followng resources to "ads" group --> <script cgroup= "ads" src= "/ads-manager.js" async ></script> <iframe cgroup= "ads" src= "//3rdparty.com/widget" ></iframe> <!-- assign followng resources to "background" group --> <script cgroup= "background" src= "analytics.js" async ></script>

The above is not an exhaustive list of plausible directives; don’t fixate on the syntax. The key point, and question, is whether it would be useful—both to site developers and browser developers—to have such annotations communicate the preferred priorities and resource allocation strategy on their page - e.g. some scripts are more important than others, some network fetches should have lower relative priority, and so on.

Bonus: control groups are hierarchical. For example, if an iframe is allocated 30% of the available CPU cycles, then subresources executing within that iframe are sub-dividing the 30% allocated to the parent.

How does the browser enforce such policies?

Well, it may not be able to, in the strict sense of that word. For example, if a “background” script is scheduled and decides to monopolize the renderer thread and run for 20 frames, there isn’t much that the runtime can do—today, at least. However, the runtime can use the provided information to decide which callback or function to schedule next, or how to prioritize loading of resources. Some browsers may be able to do a better job of enforcing such policies, but even small scheduling optimizations can yield significant user-visible wins. Today, the browser is running blind.

Further, once the browser knows the “desired allocation”, it can flag and warn the developer when there is a mismatch at runtime - e.g. it can fire events via PerformanceObserver to notify the app of violations, allowing the developer to gather and act on this data. In effect, this could be the first step towards enabling attribution and visibility into the real-world runtime performance and impact of various resources.

Perhaps an idea worth exploring?