Melatonin Perfetto

Sounds like an ice cream flavor (albeit a sleepy one).

However, it's just a way to use the amazing Perfetto performance tracing in JUCE.

Perfetto lets you accurately measure different parts of your code and visualize it over time. It's the successor to chrome://tracing.

image-1024x396

Why would I use Perfetto instead of the profiler?

Read my blog post!

Installing with CMake

We worked hard so you don't have to.

Not only do we handle building Perfetto (which is fairly annoying on Windows), but we've made it very easy to add this module into your CMake projects.

You have several options. In all cases, the exported target you should link against is: Melatonin::Perfetto.

Option #1: FetchContent

Example usage:

include (FetchContent)

FetchContent_Declare (melatonin_perfetto
  GIT_REPOSITORY https://github.com/sudara/melatonin_perfetto.git
  GIT_TAG origin/main)

FetchContent_MakeAvailable (melatonin_perfetto)

target_link_libraries (yourTarget PRIVATE Melatonin::Perfetto)

Option #2 submodules and add_subdirectory

If you are a git submodule aficionado, add this repository as a git submodule to your project:

git submodule add -b main https://github.com/sudara/melatonin_perfetto.git modules/melatonin_perfetto

and then simply call add_subdirectory in your CMakeLists.txt:

add_subdirectory (modules/melatonin_perfetto)

target_link_libraries (yourTarget PRIVATE Melatonin::Perfetto)

Option #3 Installing and using find_package

Install the module to your system by cloning the code and then running the following commands:

cmake -B Builds
cmake --build Builds
cmake --install Builds

The --install command will write to system directories, so it may require sudo.

Once this module is installed to your system, you can simply add to your CMake project:

find_package (MelatoninPerfetto)

target_link_libraries (yourTarget PRIVATE Melatonin::Perfetto)

How to use

Step 1: Add a few pieces of guarded code to your plugin

Add a member of the plugin processor:

#if PERFETTO
    std::unique_ptr<perfetto::TracingSession> tracingSession;
#endif

Put this in PluginProcessor's constructor:

#if PERFETTO
    MelatoninPerfetto::get().beginSession();
#endif

and in the destructor:

#if PERFETTO
    MelatoninPerfetto::get().endSession();
#endif

Step 2: Pepper around some sweet sweet macros

Perfetto will only measure functions you specifically tell it to.

Pepper around TRACE_DSP() or TRACE_COMPONENT() macros at the start of the functions you want to measure.

For example, in your PluginProcessor::processBlock:

void SineMachineAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    TRACE_DSP();
    
    // your dsp code here
}

or a component's paint method:

void paint (juce::Graphics& g) override
{
    TRACE_COMPONENT();
    
    // your paint code here
}

By default, perfetto is disabled (PERFETTO=0) and the macros will evaporate on compile, so feel free to leave them in your code, especially if you plan on profiling again in the future.

Step 3: Enable profiling

When you are ready to profile, you'll need to set PERFETTO=1

This module offers a CMake option, PERFETTO, that when ON, adds the PERFETTO symbol to the module's exported compile definitions. This CMake option is an easy way for you to turn tracing on and off from the command line when building your project:

cmake -B build -D PERFETTO=ON

The value of PERFETTO will be saved in the CMake cache, so you don't need to re-specify this every time you re-run CMake configure.

To turn it off again, you can do:

cmake -B build -D PERFETTO=OFF

You can add this as a CMake option in your IDE build settings, for example in CLion:

CLion - 2023-01-07 06@2x

Aaaaand if you are lazy like Sudara sometimes is, you can just edit melatonin_perfetto and change the default to PERFETTO=1...

That'll actually include the google lib and will profile the code.

Step 4: Run your app

Reminder: do you want to profile Release? Probably! I love profiling Debug builds too, but if you are looking for real-world numbers, you'll want to use Release.

Start your app and perform the actions you want traced.

When you quit your app, a trace file will be dumped

(Note: don't just terminate it via your IDE, the file will be only dumped on a graceful quit).

Step 5: Drag the trace into Perfetto

Find the trace file and drag it into https://ui.perfetto.dev

You can keep the macros peppered around in your app during normal dev/release.

Just remember to set PERFETTO back to 0 or OFF so everything gets turned into a no-op.

Customizing

By default, there are two perfetto "categories" defined, dsp and components.

The current function name is passed as the name.

You can add custom parameters that will show up in perfetto's UI:

TRACE_DSP("startSample", startSample, "numSamples", numSamples);

You can also use the built in TRACE_EVENT which takes a name if you don't want it to derive a name based on the function.

This is handy if you want to do things like have multiple traces in a function, for example in a loop.

    TRACE_DSP(); // start the trace, use the function name
    
    if (someCondition)
    {
        TRACE_EVENT("dsp", "someCondition");
        // do something expensive,  traced separately
    }

    moreFuctionCode(); // included in the main trace

You can also wrap code in TRACE_EVENT_BEGIN and TRACE_EVENT_END:

TRACE_EVENT_BEGIN ("dsp", "memset");
// CLEAR the temp buffer
temp.clear();
TRACE_EVENT_END ("dsp");

Go wild!

Assumptions / Caveats

  • On Mac, the trace is dumped to your Downloads folder. On Windows, it's dumped to your Desktop (sorry not sorry).
  • Traces are set to in memory, 80MB by default.

Troubleshooting

I don't see a trace file

Did you quit your app gracefully, such as with cmd-Q? If you instead just hit STOP on your IDE, you won't get a trace file.

My traces appear empty

You probably went over the memory size that Perfetto is set to use by default (80MB).

If you are doing intensive profiling with lots of functions being called many times, you'll probably want to increase this limit. You can do this by passing the number of kb you want to beginSession:

MelatoninPerfetto::get().beginSession(300000); # 300MB

I keep forgetting to turn perfetto off!

Get warned by your tests if someone left PERFETTO=1 on with a test like this (this example is Catch2):

TEST_CASE ("Perfetto not accidentally left enabled", "[perfetto]")
{
#if defined(PERFETTO) && PERFETTO
    FAIL_CHECK ("PERFETTO IS ENABLED");
#else
    SUCCEED ("PERFETTO DISABLED");
#endif
}

If you use perfetto regularly, you can also do what I do and check for PERFETTO in your plugin editor and display something in the UI:

AudioPluginHost - 2023-01-06 44@2x

Running Melatonin::Perfetto's tests

melatonin_perfetto includes a test suite using CTest. To run the tests, clone the code and run these commands:

cmake -B Builds
cmake --build Builds --config Debug
cd Builds
ctest -C Debug

The tests attempt to build two minimal CMake projects that depend on the melatonin_perfetto module; one tests finding an install tree using find_package() and one tests calling add_subdirectory(). These tests serve to verify that this module's packaging and installation scripts are correct, and that it can be successfully imported to other projects using the methods advertised above. Another test case verifies that attempting to configure a project that adds melatonin_perfetto before JUCE will fail with the proper error message.