Hello eBPF: A scheduler controlled by sound (20)

Welcome back to my hello-ebpf series. Last week, I was an attendee at a scheduling and power management summit (and to bring someone a sched-ext-themed birthday cake), and the Chemnitz Linux Days, so this blog post will be a bit shorter but cover a small scheduler that I wrote for Chemnitz.

In all of my eBPF presentations, I quote Brendan Gregg with his statement, “eBPF is a crazy technology, it’s like putting JavaScript into the Linux kernel.” and a picture of him shouting at hard drives. I took this picture from the following video where he demonstrates that his disk-read-latency measurement tool can detect if someone shouts at the hard drives:

Andrea Righi came up with the idea of writing a tiny scheduler that reacts to sound. The great thing about sched-ext and eBPF is that we can write experimental schedulers without much effort, especially if you use my hello-ebpf library and access the vast Java ecosystem.

For this scheduler, we scale the number of cores any task can use by the loudness level. So shouting at your computer makes your application run faster:

In this scenario, I ran the slow roads game while my system was exhausted by stress-ng, so more cores mean a less overcommitted system and, thereby, a more fluent gaming experience.

Implementation

I implemented the scheduler, of course, in Java; you can find its code on GitHub. As a base scheduler, I chose a simple FIFO scheduler (Hello eBPF: Writing a Linux scheduler in Java with eBPF (15)) and extended it so that the slice size and number of cores is configurable using global variables:

@BPF(license = "GPL")
@Property(name = "sched_name", value = "loudness_scheduler")
public abstract class FIFOScheduler extends BPFProgram implements Scheduler {

    private static final int SHARED_DSQ_ID = 0;

    final GlobalVariable<Integer> cores = new GlobalVariable<>(
         Runtime.getRuntime().availableProcessors());
    final GlobalVariable<Integer> sliceNs = new GlobalVariable<>(-1);

The default number of cores is just the number of available cores. The init method creates a global scheduling queue, as seen countless times before, but the selectCPU method is different:

    @Override
    public int init() {
        return scx_bpf_create_dsq(SHARED_DSQ_ID, -1);
    }

    @Override
    public int selectCPU(Ptr<task_struct> p, int prev_cpu, 
                         long wake_flags) {
        if (!bpf_cpumask_full(p.val().cpus_ptr)) {
            return bpf_cpumask_first(p.val().cpus_ptr);
        }
        return (@Unsigned int) (prev_cpu + 1) % cores.get();
    }

It restricts the number of usable cores to a set number and considers that some tasks cannot run on all cores. For these core-restricted tasks, it schedules them onto the first one on their allow list. This, of course, means that these tasks don’t adhere to the restriction placed on the number of used cores, but this is inevitable.

Now, on to the enqueue method that is called whenever a task is ready to be considered for scheduling again:

    @Override
    public void enqueue(Ptr<task_struct> p, long enq_flags) {
        var slice = sliceNs.get() == -1 ? 5_000_000 : sliceNs.get();

        var scaledSlice = (@Unsigned int) slice /
                scx_bpf_dsq_nr_queued(SHARED_DSQ_ID);

        if (!bpf_cpumask_full(p.val().cpus_ptr)) {
            scx_bpf_dispatch(p, SCX_DSQ_LOCAL.value(), 
                             scaledSlice, 0);
            return;
        }

        scx_bpf_dispatch(p, SHARED_DSQ_ID, scaledSlice, enq_flags);
    }

In this method, we must also handle the core-restricted tasks and schedule them directly onto the selected core’s local queue. We assume these core-restricted tasks are mostly kernel tasks, so prioritizing them is not too problematic.

We then implement the dispatch method as you might expect:

    @Override
    public void dispatch(int cpu, Ptr<task_struct> prev) {
        if (cpu < cores.get()) { // just to be sure
            scx_bpf_consume(SHARED_DSQ_ID);
        }
    }

And that’s all. It’s not the most complicated scheduler I’ve ever written, but good enough for the demo.

Now, we just need to use the scheduler. I spare you the command line parsing code of the Main class and implementing the sound processing, but instead focus on the main loop:

    void schedule() {
        try (var program = BPFProgram.load(FIFOScheduler.class)) {
            program.attachScheduler();
            System.out.println("Scheduler attached");
            while (program.isSchedulerAttachedProperly()) {
                Thread.sleep(100);
                var loudness = sound.getLoudness();
                var frequencies = sound.getFrequencies();
                printInfo(loudness, frequencies);
                program.setCores(computeCores(loudness));
                if (frequency != -1) {
                    program.setSlice((int) Math.round(
                        computeTimeSliceMillis(frequencies)
                        * 1_000_000));
                }
            a
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

We obtain the loudness levels and frequency every 100 milliseconds and use them to update the time slice and the number of cores.

The closer the frequency is to a set frequency (like 440Hz), the smaller the time slice is. So, the system becomes more responsive if you sing a note closer to the configured tone. It’s just to be able to say that I’m using frequency scaling in my scheduler…

Chemnitz Linux Days

As mentioned in the beginning, I created this scheduler to have some fun during my talk “Writing a Minimal Scheduler with eBPF, sched_ext, and C“. I like to interact with my audience, and having them all shout at my computer was truly awesome.

If you want to see me having fun with schedulers on stage, I have two presentations planned in the following two months:

  • JUG Karlsruhe with Sound of Scheduling on May the 7th
  • JCon Europe (in Cologne) with a talk on hello-ebpf on May the 15th

Conclusion

I conclude this blog post as I concluded my last one on eBPF (Hello eBPF: Concurrency Testing using Custom Linux Schedulers (19)): hello-ebpf and sched-ext allow us to easily create stunning new schedulers and thereby gather interest from a far wider range of people than just scheduler folks. It’s important for me to have fun when exploring new technologies, learning a lot along the way.

Thank you for joining me on this journey to bring eBPF and sched-ext to the Java world. I hope to see you in my next blog post, which will probably be about implementing a CPU-time profiler in JFR.

P.S.: There is another excellent scheduler experiment/demo that I want you to take a look at. It’s the taskclicker, “An interactive, fcfs (“first clicked, first served”), gaming scheduler“, written by a third-year student at KIT:

Taskclicker let’s you experience scheduling first hand, literally. It’s a clicker-game scheduler GUI where you have to click on tasks to schedule them. Running tasks earns you syscalls that you can spend on upgrades to make your life easier!

Can you make you system fully interactive before the 30 seconds on a task run out? (Spoiler: This is written with Taskclicker running)

Author

  • Johannes Bechberger

    Johannes Bechberger is a JVM developer working on profilers and their underlying technology in the SapMachine team at SAP. This includes improvements to async-profiler and its ecosystem, a website to view the different JFR event types, and improvements to the FirefoxProfiler, making it usable in the Java world. He started at SAP in 2022 after two years of research studies at the KIT in the field of Java security analyses. His work today is comprised of many open-source contributions and his blog, where he writes regularly on in-depth profiling and debugging topics, and of working on his JEP Candidate 435 to add a new profiling API to the OpenJDK.

    View all posts

New posts like these come out at least every two weeks, to get notified about new posts, follow me on BlueSky, Twitter, Mastodon, or LinkedIn, or join the newsletter:

Leave a Reply

Your email address will not be published. Required fields are marked *