├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── tests ├── FindPackage │ ├── main.cpp │ ├── CMakeLists.txt │ └── AddTests.cmake ├── AddSubdirectory │ ├── main.cpp │ ├── CMakeLists.txt │ └── AddTests.cmake ├── PerfettoValue │ ├── should_be_off.cpp │ ├── should_be_on.cpp │ └── CMakeLists.txt ├── DumpFiles │ ├── CMakeLists.txt │ └── main.cpp ├── MissingJUCE │ ├── CMakeLists.txt │ └── AddTests.cmake └── CMakeLists.txt ├── melatonin_perfetto ├── melatonin_perfetto.cpp └── melatonin_perfetto.h ├── .gitignore ├── cmake ├── package_config.cmake └── CPM.cmake ├── scripts └── RunTests.py ├── CMakeLists.txt └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: sudara 4 | -------------------------------------------------------------------------------- /tests/FindPackage/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main (int, char**) 4 | { 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/AddSubdirectory/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main (int, char**) 4 | { 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /melatonin_perfetto/melatonin_perfetto.cpp: -------------------------------------------------------------------------------- 1 | #include "melatonin_perfetto.h" 2 | 3 | #if PERFETTO 4 | PERFETTO_TRACK_EVENT_STATIC_STORAGE(); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tests/PerfettoValue/should_be_off.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef PERFETTO 4 | #error The PERFETTO symbol is not defined! 5 | #endif 6 | 7 | #if PERFETTO 8 | #error The PERFETTO symbol should be defined to 0, but it's not! 9 | #endif 10 | -------------------------------------------------------------------------------- /tests/PerfettoValue/should_be_on.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef PERFETTO 4 | #error The PERFETTO symbol is not defined! 5 | #endif 6 | 7 | #if ! PERFETTO 8 | #error The PERFETTO symbol should be defined to 1, but it's not! 9 | #endif 10 | -------------------------------------------------------------------------------- /tests/DumpFiles/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (MelatoninDumpFileTest) 2 | 3 | target_sources (MelatoninDumpFileTest PRIVATE main.cpp) 4 | 5 | target_link_libraries (MelatoninDumpFileTest PRIVATE Melatonin::Perfetto) 6 | 7 | add_test (NAME melatonin.perfetto.dump_file_creation 8 | COMMAND MelatoninDumpFileTest) 9 | -------------------------------------------------------------------------------- /tests/MissingJUCE/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.24 FATAL_ERROR) 2 | 3 | project (PerfettoTest) 4 | 5 | if(NOT DEFINED MELATONIN_PERFETTO_ROOT) 6 | message (FATAL_ERROR "MELATONIN_PERFETTO_ROOT must be defined!") 7 | endif() 8 | 9 | add_subdirectory ("${MELATONIN_PERFETTO_ROOT}" "${CMAKE_CURRENT_BINARY_DIR}/melatonin_perfetto") 10 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_property (DIRECTORY APPEND PROPERTY LABELS melatonin_perfetto) 2 | 3 | include ("${CMAKE_CURRENT_LIST_DIR}/FindPackage/AddTests.cmake") 4 | include ("${CMAKE_CURRENT_LIST_DIR}/AddSubdirectory/AddTests.cmake") 5 | include ("${CMAKE_CURRENT_LIST_DIR}/MissingJUCE/AddTests.cmake") 6 | 7 | add_subdirectory (PerfettoValue) 8 | 9 | if(PERFETTO) 10 | add_subdirectory (DumpFiles) 11 | endif() 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # specific files 2 | .DS_Store 3 | CMakeUserPresets.json 4 | 5 | # build directories 6 | [Bb]uilds/ 7 | [Bb]uild/ 8 | 9 | # other directories generated by various tools 10 | Testing/ 11 | [Cc]ache/ 12 | .cache/ 13 | [Dd]eploy/ 14 | 15 | # IDE-specific files and directories 16 | xcuserdata/ 17 | .idea/ 18 | cmake-build-*/ 19 | [Oo]ut/ 20 | .vs/ 21 | .vscode/ 22 | .history/ 23 | *.vim 24 | [._]*.un~ 25 | *.sublime-workspace 26 | 27 | # other files 28 | .[Tt]rash* 29 | .nfs* 30 | *.bak 31 | *.tmp 32 | -------------------------------------------------------------------------------- /tests/PerfettoValue/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (PerfettoValueTest STATIC EXCLUDE_FROM_ALL) 2 | 3 | if(PERFETTO) 4 | target_sources (PerfettoValueTest PRIVATE should_be_on.cpp) 5 | else() 6 | target_sources (PerfettoValueTest PRIVATE should_be_off.cpp) 7 | endif() 8 | 9 | target_link_libraries (PerfettoValueTest PRIVATE Melatonin::Perfetto) 10 | 11 | add_test (NAME melatonin.perfetto.PERFETTO_symbol_value 12 | COMMAND "${CMAKE_COMMAND}" 13 | --build "${CMAKE_BINARY_DIR}" 14 | --target PerfettoValueTest 15 | --config "$") 16 | -------------------------------------------------------------------------------- /tests/MissingJUCE/AddTests.cmake: -------------------------------------------------------------------------------- 1 | set (test_name melatonin.perfetto.missing_juce) 2 | 3 | add_test (NAME "${test_name}" 4 | COMMAND "${CMAKE_COMMAND}" 5 | -S "${CMAKE_CURRENT_LIST_DIR}" 6 | -B "${CMAKE_CURRENT_BINARY_DIR}/MissingJUCETest" 7 | -D "MELATONIN_PERFETTO_ROOT=${MelatoninPerfetto_SOURCE_DIR}" 8 | -D "FETCHCONTENT_SOURCE_DIR_JUCE=${juce_SOURCE_DIR}" 9 | -D "FETCHCONTENT_SOURCE_DIR_PERFETTO=${perfetto_SOURCE_DIR}") 10 | 11 | set_property (TEST "${test_name}" APPEND PROPERTY LABELS missing_juce) 12 | 13 | set_tests_properties ("${test_name}" PROPERTIES PASS_REGULAR_EXPRESSION "${missing_juce_error_message}") 14 | -------------------------------------------------------------------------------- /tests/FindPackage/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.24 FATAL_ERROR) 2 | 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 5 | 6 | project (PerfettoTest) 7 | 8 | include (FetchContent) 9 | 10 | FetchContent_Declare (JUCE 11 | GIT_REPOSITORY https://github.com/juce-framework/JUCE.git 12 | GIT_TAG origin/master 13 | GIT_SHALLOW TRUE 14 | GIT_PROGRESS TRUE 15 | FIND_PACKAGE_ARGS 7.0.3) 16 | 17 | FetchContent_MakeAvailable (JUCE) 18 | 19 | find_package (MelatoninPerfetto REQUIRED) 20 | 21 | juce_add_console_app (test VERSION 1.0.0) 22 | 23 | target_sources (test PRIVATE main.cpp) 24 | 25 | target_link_libraries (test PRIVATE Melatonin::Perfetto) 26 | -------------------------------------------------------------------------------- /cmake/package_config.cmake: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include (CMakeFindDependencyMacro) 4 | 5 | if(NOT COMMAND juce_add_module) 6 | message (FATAL_ERROR "JUCE must be added to your project before you call find_package(MelatoninPerfetto)!") 7 | endif() 8 | 9 | include ("${CMAKE_CURRENT_LIST_DIR}/PerfettoTargets.cmake") 10 | 11 | juce_add_module ("${CMAKE_CURRENT_LIST_DIR}/melatonin_perfetto") 12 | 13 | target_link_libraries (melatonin_perfetto INTERFACE perfetto::perfetto) 14 | 15 | option (PERFETTO "Enable Perfetto tracing using the melatonin_perfetto module" OFF) 16 | 17 | if(PERFETTO) 18 | target_compile_definitions (melatonin_perfetto INTERFACE PERFETTO=1) 19 | endif() 20 | 21 | add_library (Melatonin::Perfetto ALIAS melatonin_perfetto) 22 | 23 | check_required_components ("@PROJECT_NAME@") 24 | -------------------------------------------------------------------------------- /tests/AddSubdirectory/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.24 FATAL_ERROR) 2 | 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 5 | 6 | project (PerfettoTest) 7 | 8 | if(NOT DEFINED MELATONIN_PERFETTO_ROOT) 9 | message (FATAL_ERROR "MELATONIN_PERFETTO_ROOT must be defined!") 10 | endif() 11 | 12 | include (FetchContent) 13 | 14 | FetchContent_Declare (JUCE 15 | GIT_REPOSITORY https://github.com/juce-framework/JUCE.git 16 | GIT_TAG origin/master 17 | GIT_SHALLOW TRUE 18 | GIT_PROGRESS TRUE 19 | FIND_PACKAGE_ARGS 7.0.3) 20 | 21 | FetchContent_MakeAvailable (JUCE) 22 | 23 | add_subdirectory ("${MELATONIN_PERFETTO_ROOT}" "${CMAKE_CURRENT_BINARY_DIR}/melatonin_perfetto") 24 | 25 | juce_add_console_app (test VERSION 1.0.0) 26 | 27 | target_sources (test PRIVATE main.cpp) 28 | 29 | target_link_libraries (test PRIVATE Melatonin::Perfetto) 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | 7 | env: 8 | CMAKE_BUILD_PARALLEL_LEVEL: 3 # Use up to 3 cpus to build juceaide, etc 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}.${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | defaults: 15 | run: 16 | shell: bash 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | 23 | BuildAndTest: 24 | 25 | name: ${{ matrix.name }} 26 | 27 | runs-on: ${{ matrix.os }} 28 | 29 | timeout-minutes: 30 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | include: 35 | - name: macOS 36 | os: macos-latest 37 | - name: Windows 38 | os: windows-latest 39 | 40 | steps: 41 | 42 | - name: Checkout code 43 | uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3 44 | with: 45 | fetch-depth: 1 46 | 47 | - name: Build and test 48 | run: python3 scripts/RunTests.py 49 | -------------------------------------------------------------------------------- /tests/DumpFiles/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #if ! PERFETTO 8 | #error PERFETTO must be 1 for this test! 9 | #endif 10 | 11 | int szudzikPair (int a, int b) 12 | { 13 | TRACE_DSP(); 14 | 15 | const auto A = a >= 0 ? 2 * a : -2 * a - 1; 16 | const auto B = b >= 0 ? 2 * b : -2 * b - 1; 17 | 18 | if (A >= B) 19 | return A * A + A + B; 20 | 21 | return A + B * B; 22 | } 23 | 24 | int main (int, char**) 25 | { 26 | MelatoninPerfetto tracingSession; 27 | 28 | auto& rand = juce::Random::getSystemRandom(); 29 | 30 | // we want to make sure these function calls don't get optimized away 31 | std::vector values; 32 | 33 | for (auto i = 0; i < 100; ++i) 34 | values.push_back (szudzikPair (rand.nextInt(), rand.nextInt())); 35 | 36 | const auto dumpFile = tracingSession.endSession(); 37 | 38 | if (dumpFile.existsAsFile()) 39 | return EXIT_SUCCESS; 40 | 41 | return EXIT_FAILURE; 42 | } 43 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.2) 6 | set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /tests/AddSubdirectory/AddTests.cmake: -------------------------------------------------------------------------------- 1 | set (base_name melatonin.perfetto.add_subdirectory) 2 | 3 | set (binary_dir "${CMAKE_CURRENT_BINARY_DIR}/AddSubdirectoryTest") 4 | 5 | add_test (NAME "${base_name}.configure" 6 | COMMAND "${CMAKE_COMMAND}" 7 | -S "${CMAKE_CURRENT_LIST_DIR}" 8 | -B "${binary_dir}" 9 | -D "MELATONIN_PERFETTO_ROOT=${MelatoninPerfetto_SOURCE_DIR}" 10 | -D "FETCHCONTENT_SOURCE_DIR_JUCE=${juce_SOURCE_DIR}" 11 | -D "FETCHCONTENT_SOURCE_DIR_PERFETTO=${perfetto_SOURCE_DIR}") 12 | 13 | set_tests_properties ("${base_name}.configure" 14 | PROPERTIES 15 | FIXTURES_SETUP MelatoninPerfettoAddSubdirectoryConfigure 16 | ENVIRONMENT_MODIFICATION "MP_PERFETTO_SHOULD_BE_ON=unset:") 17 | 18 | add_test (NAME "${base_name}.build" 19 | COMMAND "${CMAKE_COMMAND}" 20 | --build "${binary_dir}" 21 | --config "$") 22 | 23 | set_tests_properties ("${base_name}.build" PROPERTIES FIXTURES_REQUIRED MelatoninPerfettoAddSubdirectoryConfigure) 24 | 25 | set_property (TEST "${base_name}.configure" "${base_name}.build" 26 | APPEND PROPERTY LABELS add_subdirectory) 27 | -------------------------------------------------------------------------------- /tests/FindPackage/AddTests.cmake: -------------------------------------------------------------------------------- 1 | set (base_name melatonin.perfetto.find_package) 2 | 3 | set (prefix "${CMAKE_CURRENT_BINARY_DIR}/deploy/$") 4 | 5 | add_test (NAME "${base_name}.install" 6 | COMMAND "${CMAKE_COMMAND}" 7 | --install "${MelatoninPerfetto_BINARY_DIR}" 8 | --config "$" 9 | --prefix "${prefix}") 10 | 11 | set_tests_properties ("${base_name}.install" PROPERTIES FIXTURES_SETUP MelatoninPerfettoFindPackageInstall) 12 | 13 | set (binary_dir "${CMAKE_CURRENT_BINARY_DIR}/FindPackageTest") 14 | 15 | add_test (NAME "${base_name}.configure" 16 | COMMAND "${CMAKE_COMMAND}" 17 | -S "${CMAKE_CURRENT_LIST_DIR}" 18 | -B "${binary_dir}" 19 | -G "${CMAKE_GENERATOR}" 20 | -D "MelatoninPerfetto_DIR=${prefix}/${CMAKE_INSTALL_LIBDIR}/cmake/melatonin_perfetto" 21 | -D "FETCHCONTENT_SOURCE_DIR_JUCE=${juce_SOURCE_DIR}" 22 | -D "FETCHCONTENT_SOURCE_DIR_PERFETTO=${perfetto_SOURCE_DIR}") 23 | 24 | set_tests_properties ("${base_name}.configure" PROPERTIES 25 | FIXTURES_SETUP MelatoninPerfettoFindPackageConfigure 26 | FIXTURES_REQUIRED MelatoninPerfettoFindPackageInstall 27 | ENVIRONMENT_MODIFICATION "MP_PERFETTO_SHOULD_BE_ON=unset:") 28 | 29 | add_test (NAME "${base_name}.build" 30 | COMMAND "${CMAKE_COMMAND}" 31 | --build "${binary_dir}" 32 | --config "$") 33 | 34 | set_tests_properties ("${base_name}.build" PROPERTIES FIXTURES_REQUIRED MelatoninPerfettoFindPackageConfigure) 35 | 36 | set_property (TEST "${base_name}.install" "${base_name}.configure" "${base_name}.build" 37 | APPEND PROPERTY LABELS find_package) 38 | -------------------------------------------------------------------------------- /scripts/RunTests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This script runs CMake configure, build, and test twice in sequence -- once with no options specified, and once with PERFETTO=ON. 4 | This is to ensure that the default has Perfetto off, and that toggling it on actually has an effect. 5 | An environment variable is used to communicate what the correct value of PERFETTO should be to the child CMake process. 6 | """ 7 | 8 | import os.path as path 9 | from os import chdir, environ 10 | from shutil import rmtree 11 | import subprocess 12 | 13 | # 14 | 15 | def run_command(command, workingDir): 16 | 17 | print (f"Running command {command}") 18 | 19 | try: 20 | cp = subprocess.run (command, 21 | cwd=workingDir, 22 | stderr=subprocess.PIPE, 23 | stdout=subprocess.PIPE, 24 | text=True, 25 | shell=True, 26 | check=True) 27 | 28 | print(cp.stdout) 29 | print(cp.stderr) 30 | 31 | except subprocess.CalledProcessError as error: 32 | print (error.output) 33 | print (f"Command {error.cmd} failed with exit code {error.returncode}") 34 | exit (1) 35 | 36 | # 37 | 38 | REPO_ROOT = path.dirname(path.dirname(path.realpath(__file__))) 39 | 40 | BUILD_DIR = path.join(REPO_ROOT, "Builds") 41 | 42 | if path.isdir(BUILD_DIR): 43 | rmtree (BUILD_DIR) 44 | 45 | environ["MP_PERFETTO_SHOULD_BE_ON"] = "FALSE" 46 | 47 | run_command (command=f"cmake -B {BUILD_DIR}", 48 | workingDir=REPO_ROOT) 49 | 50 | run_command (command=f"cmake --build {BUILD_DIR}", 51 | workingDir=REPO_ROOT) 52 | 53 | run_command (command="ctest -C Debug", 54 | workingDir=BUILD_DIR) 55 | 56 | environ["MP_PERFETTO_SHOULD_BE_ON"] = "TRUE" 57 | 58 | run_command (command=f"cmake -B {BUILD_DIR} -D PERFETTO=ON", 59 | workingDir=REPO_ROOT) 60 | 61 | run_command (command=f"cmake --build {BUILD_DIR}", 62 | workingDir=REPO_ROOT) 63 | 64 | run_command (command="ctest -C Debug", 65 | workingDir=BUILD_DIR) 66 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24 FATAL_ERROR) 2 | 3 | project(MelatoninPerfetto 4 | VERSION 1.3.0 5 | LANGUAGES CXX 6 | DESCRIPTION "JUCE module for profiling with Perfetto" 7 | HOMEPAGE_URL "https://github.com/sudara/melatonin_perfetto") 8 | 9 | set(missing_juce_error_message "JUCE must be added to your project before melatonin_perfetto!") 10 | 11 | include(cmake/CPM.cmake) 12 | 13 | if (MelatoninPerfetto_IS_TOP_LEVEL) 14 | message (STATUS "Grabbing JUCE...") 15 | CPMAddPackage("gh:juce-framework/JUCE#master") 16 | endif () 17 | 18 | if (NOT COMMAND juce_add_module) 19 | message(FATAL_ERROR "${missing_juce_error_message}") 20 | endif () 21 | 22 | include(GNUInstallDirs) 23 | 24 | set(MP_INSTALL_DEST "${CMAKE_INSTALL_LIBDIR}/cmake/melatonin_perfetto" 25 | CACHE STRING 26 | "Path below the install prefix where melatonin_perfetto package files will be installed to") 27 | 28 | 29 | message (STATUS "Grabbing Perfetto...") 30 | CPMAddPackage(gh:google/perfetto@51.2) 31 | 32 | # we need to manually set up a target for Perfetto 33 | add_library(perfetto STATIC) 34 | 35 | # set a *minimum* of C++17, but allow higher 36 | target_compile_features(perfetto PUBLIC cxx_std_17) 37 | 38 | target_sources(perfetto 39 | PRIVATE 40 | "$" 41 | PUBLIC 42 | "$" 43 | "$" 44 | ) 45 | 46 | target_include_directories(perfetto PUBLIC 47 | "$" 48 | "$") 49 | 50 | set_target_properties(perfetto PROPERTIES POSITION_INDEPENDENT_CODE TRUE) 51 | 52 | if (WIN32) 53 | target_compile_definitions(perfetto PUBLIC NOMINMAX=1 WIN32_LEAN_AND_MEAN=1) 54 | endif() 55 | 56 | if (MSVC) 57 | target_compile_options(perfetto 58 | PRIVATE 59 | /bigobj # only needed for compilation of perfetto itself 60 | PUBLIC 61 | /Zc:__cplusplus # we need the correct value of the __cplusplus macro 62 | /permissive- # see https://github.com/google/perfetto/issues/214 63 | ) 64 | endif () 65 | 66 | add_library(perfetto::perfetto ALIAS perfetto) 67 | 68 | install(FILES "${perfetto_SOURCE_DIR}/sdk/perfetto.h" 69 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/perfetto" 70 | COMPONENT perfetto) 71 | 72 | install(TARGETS perfetto 73 | EXPORT PerfettoTargets 74 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT perfetto 75 | NAMELINK_COMPONENT perfetto 76 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT perfetto 77 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT perfetto 78 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/perfetto") 79 | 80 | install(EXPORT PerfettoTargets 81 | NAMESPACE perfetto:: 82 | DESTINATION "${MP_INSTALL_DEST}" 83 | EXPORT_LINK_INTERFACE_LIBRARIES 84 | COMPONENT perfetto) 85 | 86 | include(CPackComponent) 87 | 88 | cpack_add_component(perfetto 89 | GROUP MelatoninPerfetto 90 | INSTALL_TYPES Developer 91 | DISPLAY_NAME "Perfetto" 92 | DESCRIPTION "The Perfetto profiling library") 93 | 94 | juce_add_module("${CMAKE_CURRENT_LIST_DIR}/melatonin_perfetto") 95 | 96 | target_link_libraries(melatonin_perfetto INTERFACE perfetto::perfetto) 97 | 98 | option(PERFETTO "Enable Perfetto tracing using the melatonin_perfetto module" OFF) 99 | 100 | if (PERFETTO) 101 | target_compile_definitions(melatonin_perfetto INTERFACE PERFETTO=1) 102 | endif () 103 | 104 | # this is an internal check related to the RunTests.py script 105 | # users should never worry about this environment variable! 106 | if(DEFINED ENV{MP_PERFETTO_SHOULD_BE_ON}) 107 | if($ENV{MP_PERFETTO_SHOULD_BE_ON}) 108 | if(NOT PERFETTO) 109 | message (FATAL_ERROR "PERFETTO should be on, but is not!") 110 | endif() 111 | else() 112 | if(PERFETTO) 113 | message (FATAL_ERROR "PERFETTO should be off, but is on!") 114 | endif() 115 | endif() 116 | endif() 117 | 118 | add_library(Melatonin::Perfetto ALIAS melatonin_perfetto) 119 | 120 | install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/melatonin_perfetto" 121 | DESTINATION "${MP_INSTALL_DEST}" 122 | COMPONENT melatonin_perfetto) 123 | 124 | cpack_add_component(melatonin_perfetto 125 | GROUP MelatoninPerfetto 126 | INSTALL_TYPES Developer 127 | DISPLAY_NAME "melatonin_perfetto" 128 | DESCRIPTION "The melatonin_perfetto JUCE module" 129 | DEPENDS perfetto) 130 | 131 | cpack_add_component_group(MelatoninPerfetto 132 | DISPLAY_NAME "melatonin_perfetto" 133 | DESCRIPTION "The melatonin_perfetto JUCE module and the Perfetto library itself" 134 | BOLD_TITLE) 135 | 136 | include(CMakePackageConfigHelpers) 137 | 138 | write_basic_package_version_file(MelatoninPerfettoConfigVersion.cmake 139 | VERSION "${MelatoninPerfetto_VERSION}" 140 | COMPATIBILITY SameMajorVersion) 141 | 142 | configure_package_config_file(cmake/package_config.cmake MelatoninPerfettoConfig.cmake 143 | INSTALL_DESTINATION "${MP_INSTALL_DEST}" 144 | NO_SET_AND_CHECK_MACRO) 145 | 146 | install(FILES 147 | "${CMAKE_CURRENT_BINARY_DIR}/MelatoninPerfettoConfigVersion.cmake" 148 | "${CMAKE_CURRENT_BINARY_DIR}/MelatoninPerfettoConfig.cmake" 149 | DESTINATION "${MP_INSTALL_DEST}" 150 | COMPONENT melatonin_perfetto) 151 | 152 | option(MP_TESTS "Build the melatonin_perfetto tests" "${MelatoninPerfetto_IS_TOP_LEVEL}") 153 | 154 | if (MP_TESTS) 155 | enable_testing() 156 | 157 | add_subdirectory(tests) 158 | 159 | if (MelatoninPerfetto_IS_TOP_LEVEL) 160 | include(CTest) 161 | endif () 162 | endif () 163 | -------------------------------------------------------------------------------- /melatonin_perfetto/melatonin_perfetto.h: -------------------------------------------------------------------------------- 1 | /* 2 | BEGIN_JUCE_MODULE_DECLARATION 3 | 4 | ID: melatonin_perfetto 5 | vendor: Sudara 6 | version: 1.4.0 7 | name: Melatonin Perfetto 8 | description: Perfetto module for JUCE 9 | license: MIT 10 | dependencies: juce_core 11 | minimumCppStandard: 17 12 | 13 | END_JUCE_MODULE_DECLARATION 14 | */ 15 | 16 | #pragma once 17 | 18 | // I'm lazy and just toggle perfetto right here 19 | // But you can define it in your build system or in the file that includes this header 20 | #ifndef PERFETTO 21 | #define PERFETTO 0 22 | #endif 23 | 24 | #if PERFETTO 25 | 26 | // allow granular toggles to be defined before this file 27 | #ifndef PERFETTO_ENABLE_TRACE_DSP 28 | #define PERFETTO_ENABLE_TRACE_DSP 1 29 | #endif 30 | 31 | #ifndef PERFETTO_ENABLE_TRACE_COMPONENT 32 | #define PERFETTO_ENABLE_TRACE_COMPONENT 1 33 | #endif 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | PERFETTO_DEFINE_CATEGORIES ( 42 | perfetto::Category ("component") 43 | .SetDescription ("Component"), 44 | perfetto::Category ("dsp") 45 | .SetDescription ("dsp")); 46 | 47 | class MelatoninPerfetto 48 | { 49 | public: 50 | explicit MelatoninPerfetto (const bool startTrace = true) : startTraceAutomatically (startTrace) 51 | { 52 | perfetto::TracingInitArgs args; 53 | // The backends determine where trace events are recorded. For this example we 54 | // are going to use the in-process tracing service, which only includes in-app events. 55 | args.backends = perfetto::kInProcessBackend; 56 | perfetto::Tracing::Initialize (args); 57 | perfetto::TrackEvent::Register(); 58 | 59 | if (startTraceAutomatically) 60 | beginSession(); 61 | } 62 | 63 | ~MelatoninPerfetto() 64 | { 65 | if (started) 66 | endSession(); 67 | } 68 | 69 | void beginSession (const uint32_t buffer_size_kb = 80000) 70 | { 71 | perfetto::TraceConfig cfg; 72 | cfg.add_buffers()->set_size_kb (buffer_size_kb); // 80MB is the default 73 | auto* ds_cfg = cfg.add_data_sources()->mutable_config(); 74 | ds_cfg->set_name ("track_event"); 75 | session = perfetto::Tracing::NewTrace(); 76 | session->Setup (cfg); 77 | session->StartBlocking(); 78 | started = true; 79 | } 80 | 81 | // Returns the file where the dump was written to (or a null file if an error occurred) 82 | // the return value can be ignored if you don't need this information 83 | juce::File endSession() 84 | { 85 | // Make sure the last event is closed for this example. 86 | perfetto::TrackEvent::Flush(); 87 | 88 | // Stop tracing 89 | session->StopBlocking(); 90 | started = false; 91 | return writeFile(); 92 | } 93 | 94 | static juce::File getDumpFileDirectory() 95 | { 96 | #if JUCE_WINDOWS 97 | return juce::File::getSpecialLocation (juce::File::SpecialLocationType::userDesktopDirectory); 98 | #else 99 | return juce::File::getSpecialLocation (juce::File::SpecialLocationType::userHomeDirectory).getChildFile ("Downloads"); 100 | #endif 101 | } 102 | 103 | private: 104 | bool startTraceAutomatically; 105 | bool started = false; 106 | juce::File writeFile() 107 | { 108 | // Read trace data 109 | std::vector trace_data (session->ReadTraceBlocking()); 110 | 111 | const auto file = getDumpFileDirectory(); 112 | 113 | #if JUCE_DEBUG 114 | auto mode = juce::String ("-DEBUG-"); 115 | #else 116 | auto mode = juce::String ("-RELEASE-"); 117 | #endif 118 | 119 | const auto currentTime = juce::Time::getCurrentTime().formatted ("%Y-%m-%d_%H%M"); 120 | const auto childFile = file.getChildFile ("perfetto" + mode + currentTime + ".pftrace"); 121 | 122 | if (auto output = childFile.createOutputStream()) 123 | { 124 | output->setPosition (0); 125 | output->write (&trace_data[0], trace_data.size() * sizeof (char)); 126 | 127 | DBG ("Wrote perfetto trace to: " + childFile.getFullPathName()); 128 | 129 | return childFile; 130 | } 131 | 132 | DBG ("Failed to write perfetto trace file. Check for missing permissions."); 133 | jassertfalse; 134 | return juce::File {}; 135 | } 136 | 137 | std::unique_ptr session; 138 | }; 139 | 140 | /* 141 | 142 | There be dragons here. Serious C++ constexpr dragons. 143 | 144 | Deriving the function name produces an ugly string: 145 | auto AudioProcessor::processBlock(juce::AudioBuffer &, juce::MidiBuffer &)::(anonymous class)::operator()()::(anonymous class)::operator()(uint32_t) const 146 | 147 | Without dragons, trying to trim the string results in it happening at runtime. 148 | 149 | With dragons, we get a nice compile time string: 150 | AudioProcessor::processBlock 151 | 152 | */ 153 | 154 | namespace melatonin 155 | { 156 | // This wraps our compile time strings (coming from PRETTY_FUNCTION, etc) in lambdas 157 | // so that they can be used as template parameters 158 | // This lets us trim the strings while still using them at compile time 159 | // https://accu.org/journals/overload/30/172/wu/#_idTextAnchor004 160 | #define WRAP_COMPILE_TIME_STRING(x) [] { return (x); } 161 | #define UNWRAP_COMPILE_TIME_STRING(x) (x)() 162 | 163 | template 164 | constexpr auto compileTimePrettierFunction (CompileTimeLambdaWrappedString wrappedSrc) 165 | { 166 | // if we're C++20 or higher, ensure we're compile-time 167 | #if __cplusplus >= 202002L 168 | // This should never assert, but if so, report it on this issue: 169 | // https://github.com/sudara/melatonin_perfetto/issues/13#issue-1558171132 170 | if (!std::is_constant_evaluated()) 171 | jassertfalse; 172 | #endif 173 | 174 | constexpr auto src = UNWRAP_COMPILE_TIME_STRING (wrappedSrc); 175 | constexpr auto size = std::string_view (src).size(); // -1 to ignore the /0 176 | std::array result {}; 177 | 178 | // loop through the source, building a new truncated array 179 | // see: https://stackoverflow.com/a/72627251 180 | for (size_t i = 0; i < size; ++i) 181 | { 182 | // wait until after the return type (first space in the string) 183 | if (src[i] == ' ') 184 | { 185 | ++i; // skip the space 186 | 187 | // MSVC has an additional identifier after the return type: __cdecl 188 | if (src[i + 1] == '_') 189 | i += 8; // skip __cdecl and the space afterwards 190 | 191 | size_t j = 0; 192 | 193 | // build result, stop when we hit the arguments 194 | // clang and gcc use (, MSVC uses < 195 | while ((src[i] != '(' && src[i] != '<') && i < size && j < size) 196 | { 197 | result[j] = src[i]; 198 | ++i; // increment character in source 199 | ++j; // increment character in result 200 | } 201 | 202 | // really ugly clean up after msvc, remove the extra :: before 203 | if (src[i] == '<') 204 | { 205 | result[j - 2] = '\0'; 206 | } 207 | return result; 208 | } 209 | } 210 | return result; 211 | } 212 | 213 | namespace test 214 | { 215 | // a lil test helper so we don't go crazy 216 | template 217 | constexpr bool strings_equal (const std::array& result, const char (&test)[sizeTest]) 218 | { 219 | // sanity check 220 | static_assert (sizeTest > 1); 221 | static_assert (sizeResult + 1 >= sizeTest); // +1 for the /0 222 | 223 | std::string_view resultView (result.data(), sizeTest); 224 | std::string_view testView (test, sizeTest); 225 | return testView == resultView; 226 | } 227 | 228 | // testing at compile time isn't fun (no debugging) so dumb things like this help: 229 | constexpr std::array main { "main" }; 230 | static_assert (strings_equal (main, "main")); 231 | 232 | // ensure the return type is removed 233 | static_assert (strings_equal (compileTimePrettierFunction (WRAP_COMPILE_TIME_STRING ("int main")), "main")); 234 | 235 | // clang example 236 | static_assert (strings_equal (compileTimePrettierFunction (WRAP_COMPILE_TIME_STRING ("void AudioProcessor::processBlock(juce::AudioBuffer &, juce::MidiBuffer &)::(anonymous class)::operator()()::(anonymous class)::operator()(uint32_t) const")), "AudioProcessor::processBlock")); 237 | 238 | // msvc example 239 | static_assert (strings_equal (compileTimePrettierFunction (WRAP_COMPILE_TIME_STRING ("void __cdecl AudioProcessor::processBlock::::operator")), "AudioProcessor::processBlock")); 240 | } 241 | } 242 | 243 | #else 244 | // allow people to keep perfetto helpers in-place even when disabled 245 | #define TRACE_EVENT_BEGIN(category, ...) 246 | #define TRACE_EVENT_END(category) 247 | #define TRACE_EVENT(category, ...) 248 | #endif 249 | 250 | // Et voilà! Our nicer macros. 251 | // This took > 20 hours, hope the DX is worth it... 252 | // The separate constexpr calls are required for `compileTimePrettierFunction` to remain constexpr 253 | // in other words, they can't be inline with perfetto::StaticString, otherwise it will go runtime 254 | 255 | // we also can toggle dsp/component on/off individually to help clean up traces 256 | #if PERFETTO_ENABLE_TRACE_DSP 257 | #define TRACE_DSP(...) \ 258 | static constexpr auto pf = melatonin::compileTimePrettierFunction (WRAP_COMPILE_TIME_STRING (PERFETTO_DEBUG_FUNCTION_IDENTIFIER())); \ 259 | TRACE_EVENT ("dsp", perfetto::StaticString (pf.data()), ##__VA_ARGS__) 260 | 261 | #define TRACE_DSP_BEGIN(name) TRACE_EVENT_BEGIN ("dsp", perfetto::StaticString (name)) 262 | #define TRACE_DSP_END() TRACE_EVENT_END ("dsp") 263 | #else 264 | #define TRACE_DSP(...) 265 | #define TRACE_DSP_BEGIN(name) 266 | #define TRACE_DSP_END() 267 | #endif 268 | 269 | #if PERFETTO_ENABLE_TRACE_COMPONENT 270 | #define TRACE_COMPONENT(...) \ 271 | static constexpr auto pf = melatonin::compileTimePrettierFunction (WRAP_COMPILE_TIME_STRING (PERFETTO_DEBUG_FUNCTION_IDENTIFIER())); \ 272 | TRACE_EVENT ("component", perfetto::StaticString (pf.data()), ##__VA_ARGS__) 273 | 274 | #define TRACE_COMPONENT_BEGIN(name) TRACE_EVENT_BEGIN ("component", perfetto::StaticString (name)) 275 | #define TRACE_COMPONENT_END() TRACE_EVENT_END ("component") 276 | #else 277 | #define TRACE_COMPONENT(...) 278 | #define TRACE_COMPONENT_BEGIN(name) 279 | #define TRACE_COMPONENT_END() 280 | #endif 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Melatonin Perfetto 2 | 3 | [![](https://github.com/sudara/melatonin_perfetto/workflows/CI/badge.svg)](https://github.com/sudara/melatonin_perfetto/actions) 4 | 5 | Sounds like an ice cream flavor (albeit a sleepy one). 6 | 7 | However, it's just a way to use the amazing [Perfetto](http://perfetto.dev) performance tracing in JUCE. 8 | 9 | [Perfetto](https://perfetto.dev) lets you accurately measure different parts of your code and visualize it over time. It's the successor to [`chrome://tracing`](https://slack.engineering/chrome-tracing-for-fun-and-profit/). 10 | 11 | ![image-1024x396](https://user-images.githubusercontent.com/472/180338251-ce3c5814-ff9c-4fbb-a8c0-9caefc2f34dc.png) 12 | 13 | ## Why would I use Perfetto instead of the profiler? 14 | 15 | ✨ 16 | ✨✨ 17 | ✨✨✨ 18 | ### [Read the blog post!](https://melatonin.dev/blog/using-perfetto-with-juce-for-dsp-and-ui-performance-tuning) 19 | ✨✨✨ 20 | ✨✨ 21 | ✨ 22 | 23 | ## Requirements 24 | 25 | * JUCE, any version after JUCE 5 should be happy 26 | * C++17 (a Perfetto requirement since v31.0) 27 | 28 | ## Installing with CMake 29 | 30 | We worked hard so you don't have to. 31 | 32 | 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. 33 | 34 | You have several options. In all cases, the exported target you should link against is: `Melatonin::Perfetto`. 35 | 36 | ### Option #1: `FetchContent` 37 | 38 | Example usage: 39 | ```cmake 40 | include (FetchContent) 41 | 42 | FetchContent_Declare (melatonin_perfetto 43 | GIT_REPOSITORY https://github.com/sudara/melatonin_perfetto.git 44 | GIT_TAG origin/main) 45 | 46 | FetchContent_MakeAvailable (melatonin_perfetto) 47 | 48 | target_link_libraries (yourTarget PRIVATE Melatonin::Perfetto) 49 | ``` 50 | 51 | ### Option #2: submodules and `add_subdirectory` 52 | 53 | If you are a git submodule aficionado, add this repository as a git submodule to your project: 54 | ```sh 55 | git submodule add -b main https://github.com/sudara/melatonin_perfetto.git modules/melatonin_perfetto 56 | ``` 57 | and then simply call `add_subdirectory` in your CMakeLists.txt: 58 | ```cmake 59 | add_subdirectory (modules/melatonin_perfetto) 60 | 61 | target_link_libraries (yourTarget PRIVATE Melatonin::Perfetto) 62 | ``` 63 | 64 | ### Option #3: Installing and using `find_package` 65 | 66 | Install the module to your system by cloning the code and then running the following commands: 67 | ```sh 68 | cmake -B Builds 69 | cmake --build Builds 70 | cmake --install Builds 71 | ``` 72 | 73 | The `--install` command will write to system directories, so it may require `sudo`. 74 | 75 | Once this module is installed to your system, you can simply add to your CMake project: 76 | ```cmake 77 | find_package (MelatoninPerfetto) 78 | 79 | target_link_libraries (yourTarget PRIVATE Melatonin::Perfetto) 80 | ``` 81 | 82 | ## Installing with Projucer 83 | 84 | ### Step 1: Download the Perfetto SDK 85 | 86 | It can go anywhere. You'll actually need to use git to grab it though, there's no way to download it otherwise. Paste this into your macOS terminal (or download git on windows and use git bash): 87 | 88 | ``` 89 | git clone https://android.googlesource.com/platform/external/perfetto -b v31.0 90 | ``` 91 | 92 | ### Step 2: Download this module and add to your project 93 | 94 | Use git to add it as a submodule if you'd like stay up to date with any changes: 95 | 96 | ``` 97 | git submodule add -b main https://github.com/sudara/melatonin_perfetto.git modules/melatonin_perfetto 98 | ``` 99 | 100 | Or just download it and stick it somewhere. 101 | 102 | Be sure to add it in Projucer under "Modules". 103 | 104 | 105 | ### Step 3: Add the perfetto headers in File Explorer 106 | 107 | This is necessary to actually compile the perfetto tracing sdk from source. 108 | 109 | In the File Explorer, hit the `+`, `Add Existing Files` and make sure the following two are added: 110 | 111 | ``` 112 | sdk/perfetto.h 113 | sdk/perfetto.cc 114 | ``` 115 | 116 | ### Step 4: Add to your project's Header Search Paths 117 | 118 | In the Project Settings (gear at the top right of the sidebar), tell the Projucer where to find `perfetto/sdk` folder. 119 | 120 | For example, if you downloaded it as a sibling folder to the project, you would add the following to `Header Search Paths`: 121 | 122 | ``` 123 | ../perfetto/sdk 124 | ``` 125 | 126 | ### Step 5: Add read/write permissions where necessary (macOS) 127 | 128 | If you have `App Sandbox` enabled, you'll have to enable the following: 129 | 130 | ``` 131 | `File Access: Read/Write: Download Folder (Read/Write)` 132 | ``` 133 | 134 | This lets perfetto write out the trace files. 135 | 136 | 137 | 138 | 139 | 140 | ### Step 5: Add compile flags and preprocessor definitions (Windows) 141 | 142 | Windows Projucer builds [require some extra love](https://forum.juce.com/t/new-module-profile-your-juce-ui-dsp-performance-with-perfetto/54589/43?u=sudara). 143 | 144 | Go to the Settings page for the Visual Studio Exporter and add these to "Extra Compile Flags": 145 | 146 | ``` 147 | /bigobj 148 | /Zc:__cplusplus 149 | /permissive- 150 | /Zc:externC- 151 | ``` 152 | 153 | In addition, you'll need the following "Extra Preprocessor Definitions" set on that same page: 154 | 155 | ``` 156 | NOMINMAX=1 157 | WIN32_LEAN_AND_MEAN=1 158 | ``` 159 | 160 | ### Step 6: Enable/Disable via the Projucer 161 | 162 | As you'll see in "How to use", you can toggle perfetto traces on/off by adding the following in the exporter's preprocessor definitions: 163 | 164 | ``` 165 | PERFETTO=1 166 | ``` 167 | 168 | If that's too clunky, you can also just toggle in the source (read more below). 169 | 170 | ## How to use 171 | 172 | ### Step 1: Add a few pieces of guarded code to your plugin 173 | 174 | Include library: 175 | ```cpp 176 | #include 177 | ``` 178 | 179 | Add a member of the plugin processor, guarded by a preprocessor definition: 180 | ```cpp 181 | #if PERFETTO 182 | MelatoninPerfetto tracingSession; 183 | #endif 184 | ``` 185 | 186 | That's it! Earlier versions (1.2 and before) had MelatoninPerfetto as a singleton that you'd have to setup in the constructor, but now it's just a regular class. 187 | 188 | ### Step 2: Pepper around some sweet sweet trace macros 189 | 190 | Perfetto will *only* measure functions you specifically tell it to. 191 | 192 | Pepper around `TRACE_DSP()` or `TRACE_COMPONENT()` macros at the start of the functions you want to measure. 193 | 194 | For example, in your `PluginProcessor::processBlock`: 195 | 196 | ```cpp 197 | void SineMachineAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) 198 | { 199 | TRACE_DSP(); 200 | 201 | // your dsp code here 202 | } 203 | ``` 204 | 205 | or a component's `paint` method: 206 | 207 | ```cpp 208 | void paint (juce::Graphics& g) override 209 | { 210 | TRACE_COMPONENT(); 211 | 212 | // your paint code here 213 | } 214 | ``` 215 | 216 | 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. 217 | 218 | ### Step 3: Enable profiling 219 | 220 | When you are ready to profile, you'll need to set `PERFETTO=1` 221 | 222 | This module offers a CMake option, `PERFETTO`, that when `ON`, adds the `PERFETTO` symbol to the module's exported 223 | compile definitions. This CMake option 224 | is an easy way for you to turn tracing on and off from the command line when building your project: 225 | 226 | ```sh 227 | cmake -B build -D PERFETTO=ON 228 | ``` 229 | 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 230 | CMake configure. 231 | 232 | To turn it off again, you can do: 233 | ```sh 234 | cmake -B build -D PERFETTO=OFF 235 | ``` 236 | 237 | You can add this as a CMake option in your IDE build settings, for example in CLion: 238 | 239 | CLion - 2023-01-07 06@2x 240 | 241 | 242 | Aaaaand if you are lazy like Sudara sometimes is, you can just edit melatonin_perfetto and change the default to `PERFETTO=1`... 243 | 244 | That'll actually include the google lib and will profile the code. 245 | 246 | ### Step 4: Run your app 247 | 248 | 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. 249 | 250 | Start your app and perform the actions you want traced. 251 | 252 | When you quit your app, a trace file will be dumped 253 | 254 | **(Note: don't just terminate it via your IDE, the file will be only dumped on a graceful quit)**. 255 | 256 | ### Step 5: Drag the trace into Perfetto 257 | 258 | Find the trace file and drag it into https://ui.perfetto.dev 259 | 260 | You can keep the macros peppered around in your app during normal dev/release. 261 | 262 | Just remember to set `PERFETTO` back to `0` or `OFF` so everything gets turned into a no-op. 263 | 264 | ## Customizing 265 | 266 | By default, there are two perfetto "categories" defined, `dsp` and `components`. 267 | 268 | The current function name is passed as the name. 269 | 270 | 271 | You can also add custom parameters that will show up in perfetto's UI: 272 | 273 | ```cpp 274 | TRACE_DSP("startSample", startSample, "numSamples", numSamples); 275 | ``` 276 | 277 | ### TRACE_EVENT 278 | 279 | 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. 280 | 281 | This is also if you want to do things like have multiple traces in a function, for example in a loop. 282 | 283 | ```cpp 284 | TRACE_DSP(); // start the trace, use the function name 285 | 286 | if (someCondition) 287 | { 288 | TRACE_EVENT("dsp", "someCondition"); 289 | // do something expensive, traced separately 290 | } 291 | 292 | moreFuctionCode(); // included in the main trace 293 | ``` 294 | 295 | 296 | ### TRACE_EVENT_BEGIN and TRACE_EVENT_END 297 | 298 | Sometimes you want to go full granular and not just depend on scoping. 299 | 300 | To do this, use `TRACE_EVENT_BEGIN` and `TRACE_EVENT_END`: 301 | 302 | ```cpp 303 | TRACE_EVENT_BEGIN ("dsp", "memset"); 304 | // CLEAR the temp buffer 305 | temp.clear(); 306 | TRACE_EVENT_END ("dsp"); 307 | 308 | ``` 309 | 310 | ### Dynamic Names 311 | 312 | Perfetto is optimized to be low overhead, shipping in RELEASE production builds. By default usage is via compile-time strings. 313 | 314 | Working with strings dynamically introduces runtime overhead — but hey, sometimes you just want to look at something real quick. For those cases, Perfetto provides a helper: 315 | 316 | ``` 317 | TRACE_EVENT ("dsp", perfetto::DynamicString{my_dynamic_string}); 318 | ``` 319 | 320 | 321 | ### "Solo" the message or audio thread 322 | 323 | If you are focusing on UI and want to temporarily rid of the audio thread in the trace, set `PERFETTO_ENABLE_TRACE_DSP=0` in your preprocessor definitions (or just modify the header like I do) and it will be a no-op. 324 | 325 | You can do the reverse and disable all UI component tracing on the message thread with `PERFETTO_ENABLE_TRACE_COMPONENT=0`. 326 | 327 | Go wild! 328 | 329 | 330 | ## Assumptions / Caveats 331 | 332 | * On Mac, the trace is dumped to your Downloads folder. On Windows, it's dumped to your Desktop (sorry not sorry). 333 | * Traces are set to in memory, 80MB by default. 334 | 335 | ## Troubleshooting 336 | 337 | ### Perfetto gives me an error about C++17 on Windows 338 | 339 | Make sure you are passing the `/Zc:__cplusplus` flag so MSVC's version detection actually works, see [this link](https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/) 340 | 341 | In CMake: 342 | 343 | ``` 344 | target_compile_options("${PROJECT_NAME}" PUBLIC /Zc:__cplusplus) 345 | 346 | ``` 347 | 348 | ### I don't see a trace file 349 | 350 | 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. 351 | 352 | ### My traces appear empty 353 | 354 | You probably went over the memory size that Perfetto is set to use by default (80MB). 355 | 356 | 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`: 357 | 358 | ``` 359 | MelatoninPerfetto::get().beginSession(300000); # 300MB 360 | ``` 361 | 362 | ### I keep forgetting to turn perfetto off! 363 | 364 | Get warned by your tests if someone left `PERFETTO=1` on with a test like this (this example is Catch2): 365 | 366 | ```c++ 367 | TEST_CASE ("Perfetto not accidentally left enabled", "[perfetto]") 368 | { 369 | #if defined(PERFETTO) && PERFETTO 370 | FAIL_CHECK ("PERFETTO IS ENABLED"); 371 | #else 372 | SUCCEED ("PERFETTO DISABLED"); 373 | #endif 374 | } 375 | ``` 376 | 377 | 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: 378 | 379 | AudioPluginHost - 2023-01-06 44@2x 380 | 381 | ## Running Perfetto in your tests 382 | 383 | It can be really nice to run a few test cases through perfetto. 384 | 385 | To do so with Catch2, for example, you'll need to first link against `Catch2::Catch2` instead of `Catch2::Catch2WithMain`: 386 | 387 | ``` 388 | target_link_libraries(Tests PRIVATE SharedCode Catch2::Catch2WithMain) 389 | ``` 390 | 391 | And then define your own `main` function. 392 | 393 | ```cpp 394 | #include "melatonin_perfetto/melatonin_perfetto.h" 395 | int main (int argc, char* argv[]) 396 | { 397 | #if PERFETTO 398 | MelatoninPerfetto perfetto; 399 | #endif 400 | 401 | const int result = Catch::Session().run (argc, argv); 402 | 403 | return result; 404 | } 405 | ``` 406 | 407 | ## Running Melatonin::Perfetto's tests 408 | 409 | `melatonin_perfetto` includes a test suite using CTest. To run the tests, clone the code and run these commands: 410 | ```sh 411 | cmake -B Builds 412 | cmake --build Builds --config Debug 413 | cd Builds 414 | ctest -C Debug 415 | ``` 416 | 417 | The tests attempt to build two minimal CMake projects that depend on the `melatonin_perfetto` module; one tests 418 | finding an install tree using `find_package()` and one tests calling `add_subdirectory()`. These tests serve to 419 | verify that this module's packaging and installation scripts are correct, and that it can be successfully imported 420 | to other projects using the methods advertised above. Another test case verifies that attempting to configure a 421 | project that adds `melatonin_perfetto` before JUCE will fail with the proper error message. 422 | 423 | ## Acknowledgements 424 | 425 | * Thanks to [@benthevining](https://github.com/benthevining) for extra CMake love and tests! 426 | * Thanks to [@dikadk](https://github.com/dikadk) for getting a Projucer version running on MacOS 427 | * Thanks to stephenk for putting in the effort to [getting the Projucer version working on Windows](https://forum.juce.com/t/new-module-profile-your-juce-ui-dsp-performance-with-perfetto/54589/43?u=sudara). 428 | --------------------------------------------------------------------------------