New Module: Profile your JUCE UI/dsp performance with Perfetto

The trace shows nested events in this way. Since components in JUCE are in a hierarchy, if you trace a component’s paint as well as its children (or other function calls or wrapped code chunks in the code path) the nesting will be visualized.

I just have one question at the moment: If you have a slice selected, is there any way to advance to the next slice?

When slices are more than a few milliseconds apart, trying to move to the next one using scroll left/right is extremely tedious. Constantly scrolling in, out, panning left, right - can’t you just advance to the next slice?

I might be missing something, but so far I can’t find an answer…

I’ve never had that need. I just zoom to the level I care about and point and click.

It does seem like [ and ] would be good candidates even if the context is not a ‘flow’. You could raise a GitHub issue with them, they’ve been responsive to me in the past.

One other cool feature I should mention are their “pivot tables”.

You can enable this under “Flags > Pivot tables V2”

This lets you highlight chunks and quickly get aggregates like min/max as well as see a data table of the individual events selected (by event name).

Thanks. Yes, I saw those. After playing with this a bit, I can say: this is really deep. And fascinating.

I took the tip of your blog post and put a trace in Component::paintComponentAndChildren():

// at top of juce_Component.cpp, outside of namespace:
#include <melatonin_perfetto/melatonin_perfetto.h>

void Component::paintComponentAndChildren (Graphics& g)
{
    auto s = getName() + "::pCAC";
    TRACE_EVENT("component", perfetto::DynamicString{ s.getCharPointer() } );

If you name all your components (as I do), you can easily see all of the nested painting and so on. Really interesting. I suspect this will be very useful in UI optimization.

Edit: great work on this, and thanks!!!

1 Like

Nice! Glad you got the root component level stuff working too.

I’m hoping we can eventually get a per-component paint callback to hook into this stuff more easily/officially, see this Feature Request: FR: Callback or other mechanism for exposing Component debugging/timing

3 Likes

I voted for it. :wink:

1 Like

Voted for it too. Thanks @sudara for opening this interesting discussion.

Actually it got me interested into macOS’s Signposts :slight_smile: Really simple API, quite low overhead and very helpful. Building on @dave96’s class I came up with this simple wrapper, if anybody is interested:

#ifdef __APPLE__
#include <os/log.h>
#include <os/signpost.h>

struct Signpost {
  Signpost() : log_(os_log_create("com.company.myApp", OS_LOG_CATEGORY_POINTS_OF_INTEREST)) {}

  static os_log_t& getInstance() {
    static Signpost signpost;
    return signpost.log_;
  }

 private:
  os_log_t log_;
};

#define SIGNPOST_BEGIN(name, ...)                                                            \
  os_signpost_id_t _signpost_interval_id = os_signpost_id_generate(Signpost::getInstance()); \
  os_signpost_emit_with_type(Signpost::getInstance(), OS_SIGNPOST_INTERVAL_BEGIN,            \
                             _signpost_interval_id, name, __VA_ARGS__)

#define SIGNPOST_END(name, ...)                                                 \
  os_signpost_emit_with_type(Signpost::getInstance(), OS_SIGNPOST_INTERVAL_END, \
                             _signpost_interval_id, name, __VA_ARGS__)

#define SIGNPOST_EVENT(name, ...)                                                                  \
  os_signpost_emit_with_type(Signpost::getInstance(), OS_SIGNPOST_EVENT, OS_SIGNPOST_ID_EXCLUSIVE, \
                             name, __VA_ARGS__)

#else
#define SIGNPOST_BEGIN(...) ;
#define SIGNPOST_END(...) ;
#define SIGNPOST_EVENT(...) ;
#endif

Caveat: with the new (post-10.14) “named” signpost API I don’t think you can go RAII like @dave96, because the strings attached to the events must be litterals.

Then to e.g. profile all paint calls it’s a matter of temporarily modifying juce_Component.cpp:

diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp
index 9f8d9d742..5063604aa 100644
--- a/modules/juce_gui_basics/components/juce_Component.cpp
+++ b/modules/juce_gui_basics/components/juce_Component.cpp
@@ -23,6 +23,21 @@
   ==============================================================================
 */
 
+#include <cxxabi.h>
+
+std::string demangle(const char* name) {
+
+    int status = -1;
+    std::unique_ptr<char, void(*)(void*)> res {
+        abi::__cxa_demangle(name, nullptr, nullptr, &status),
+        std::free
+    };
+
+    return (status==0) ? res.get() : name ;
+}
+
 namespace juce
 {
 
@@ -1996,6 +2011,7 @@ void Component::paintWithinParentContext (Graphics& g)
 
 void Component::paintComponentAndChildren (Graphics& g)
 {
+    SIGNPOST_BEGIN("paint", "%s", demangle(typeid(*this).name()).c_str());
     auto clipBounds = g.getClipBounds();
 
     if (flags.dontClipGraphicsFlag && getNumChildComponents() == 0)
@@ -2057,6 +2073,7 @@ void Component::paintComponentAndChildren (Graphics& g)
 
     Graphics::ScopedSaveState ss (g);
     paintOverChildren (g);
+    SIGNPOST_END("paint");
 }
 
 void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)

… which gets me this beautiful waterfall on my app :heart_eyes:

@sudara hope we get a more permanent way of profiling Component!

4 Likes

Well, I managed it to create an RAII C++ wrapper class template that holds the event name string as template argument. In code it basically looks like

ScopedSignpost<"Name"> scopedSignpost;

It requires C++ 20 and directly accesses the internal _os_signpost_emit_with_name_impl function – but it seems to work fine. I’ll see if I can manage to brush it up a bit and then share it here.

3 Likes

Hey Sudara, awesome work! This is extremely helpful. I got it to work pretty fast in Clion with CMake. I do have one issue, though. While the TRACE_DSP and TRACE_COMPONENT work just fine, I am getting a compile error for TRACE_EVENT_BEGIN(“a”, “b”) :

constexpr variable ‘kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_62’ must be initialized by a constant expression

Any ideas? Thanks again!

1 Like

Glad it’s useful!

You can only use predefined categories as the first argument. So: dsp, component (unless you manually modify the module to add more)

1 Like

Understood, thanks for the quick reply!

1 Like

Hi again, Sudara - I have just ran this on Windows in CLion, and, while the Mac version runs just fine, on Windows it’s complaning that

cmake-build-debug/_deps/perfetto-src/sdk/perfetto.cc:69903:10: fatal error: afunix.h: No such file or directory

Any ideas? Thanks again!

1 Like

Interesting, are you on the latest commit from the main branch?

We have tests in CI running both windows and mac for both compiling and tracing (thanks again to @benvining) — they are green right now, maybe you could share what toolchain you are using?

Edit: just confirmed the latest main builds happily for me on CLion Windows w/ VS 2022 (17.0)

2 Likes

That header name sounds like it’s a Unix header… Are you using something like WSL, Cygwin, or MinGW?

1 Like

AFUnix comes to Windows (10):

Seems there is a missing (expected) SDK to install … a missing “git submodule update --init --recursive” somewhere, maybe?

1 Like

Thanks for all the replies and tips! Before these errors I did a full clean of my laptop and CLion defaulted to MinGW. Switching back to VS 2022 fixed it! Thanks!

2 Likes

I’ve just updated melatonin_perfetto to version 1.1. Please give it an update if you use it!

Before today, in some cases, TRACE_DSP and TRACE_COMPONENT macros did some string manipulation at run-time (only when profiling with PERFETTO=1, of course).

That’s now fixed, and everything happens at compile time.

We went on a ridiculous constexpr adventure and I wrote the hackiest C++ code I’ve ever written.

String manipulation at compile time is pretty sketchy, even in C++20 (melatonin_perfetto supports C++17) and debugging constexpr/eval stuff is like going on a treasure hunt. If you want to get nerd sniped: I’m wrapping string literals returned by PRETTY_FUNCTION and friends in lambdas so they are usable as template parameters, can therefore be “unique” and manipulated at compile time. I could find no other way to trim strings at compile time in a way that was compatible with perfetto. Happy to be proven wrong though!

Anyway, it passes tests, and I’ve manually confirmed compile-time Debug/Release behavior on Clang/MSVC.

4 Likes

I moved my project from Mac to Windows today (having not compiled with Perfetto on Windows up to now). When I try to compile on VS 2019, with my Projucer Settings specifically set to C++17, I am getting the following fatal error:

Error	C1189	#error:  Perfetto is exploring a switch to C++17 in v34 (Feb 2023). 
During this transitionary period, we are throwing an error when compiling Perfetto 
with a standard less than C++17. Please reach out to perfetto-dev@googlegroups.com
if you have objections or thoughts on this move. To continue compiling this release of 
Perfetto with C++11/14, specify the define PERFETTO_ALLOW_SUB_CPP17. 
*Note*: this define *will* stop working in v34 (Feb 2023). 
(compiling source file ..\..\..\..\modules\perfetto\sdk\perfetto.cc)	
XXXXX-apphost_App	C:\Users\sk\DocumentsXXXX JUCE (Win)\NO BU\XXXX JUCE 044 Win\modules\perfetto\sdk\perfetto.h	338	

According to this, it seems to think that I am using LESS THAN C++17. But I’m not:

Hey Stephen,

Yes, we ran into this (fun/ridiculous/baffling) problem as well. Basically, the version detection in is broken in MSVC unless you pass the /Zc:__cplusplus flag, see this link.

CMake doesn’t help out by default either, so in CMake one would need to do this in addition to setting the project as C++17/20:

target_compile_options("${PROJECT_NAME}" PUBLIC /Zc:__cplusplus)