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

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!

3 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.

2 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