diff --git a/.vscode/launch.json b/.vscode/launch.json index cc7a005..0ef6f21 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -172,6 +172,38 @@ } ] }, + { + "name": "(gdb) profilePlugin Launch", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + + "args": [ "Nam_Profile", "--no-profile","-w" ], + + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + }, + { + "name": "OTHER_VALUE", + "value": "Something something" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { diff --git a/docs/BuildPrerequisites.md b/docs/BuildPrerequisites.md index 0bc656f..131dc4c 100644 --- a/docs/BuildPrerequisites.md +++ b/docs/BuildPrerequisites.md @@ -27,7 +27,7 @@ Run the following commands to install dependent libraries required by the PiPeda sudo apt install -y authbind sudo apt install -y libavahi-client-dev sudo apt install -y libnm-dev libicu-dev - sudo apt install -y libsdbus-c++-dev libzip-dev + sudo apt install -y libsdbus-c++-dev libzip-dev google-perftools ### Installing Sources diff --git a/profiler_tools/ToobNam_profile b/profiler_tools/ToobNam_profile new file mode 100755 index 0000000..7c9fef7 --- /dev/null +++ b/profiler_tools/ToobNam_profile @@ -0,0 +1,3 @@ +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +${SCRIPT_DIR}/../build/src/profilePlugin -w ToobNam_Profile -o /tmp/ToobNam.perf &&\ +google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt \ No newline at end of file diff --git a/profiler_tools/nam_profile b/profiler_tools/nam_profile new file mode 100755 index 0000000..9654887 --- /dev/null +++ b/profiler_tools/nam_profile @@ -0,0 +1,3 @@ +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +${SCRIPT_DIR}/../build/src/profilePlugin -w Nam_Profile -o /tmp/nam.perf &&\ +google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 416e77d..13df939 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -310,6 +310,14 @@ target_link_libraries(pipedaltest PRIVATE PiPedalCommon) target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES} ) +add_executable(profilePlugin + profilePluginMain.cpp + ) +target_link_libraries(profilePlugin PRIVATE profiler ${PIPEDAL_LIBS}) + +target_include_directories(profilePlugin PRIVATE ${PIPEDAL_INCLUDES} + ) + add_executable(jsonTest testMain.cpp jsonTest.cpp diff --git a/src/CommandLineParser.hpp b/src/CommandLineParser.hpp index 6ce4276..d1a08ed 100644 --- a/src/CommandLineParser.hpp +++ b/src/CommandLineParser.hpp @@ -162,7 +162,7 @@ namespace pipedal { if (shortOption.length() != 0) { - auto option = "-" + longOption; + auto option = "-" + shortOption; options.push_back(new BooleanOption(option, pResult, true)); options.push_back(new BooleanOption(option + "+", pResult, true)); options.push_back(new BooleanOption(option + "-", pResult, false)); diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 92eadcc..da2147d 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -492,7 +492,7 @@ void PiPedalModel::FirePedalboardChanged(int64_t clientId, bool loadAudioThread) if (loadAudioThread) { // notify the audio thread. - if (audioHost->IsOpen()) + if (audioHost && audioHost->IsOpen()) { LoadCurrentPedalboard(); @@ -2070,6 +2070,21 @@ void PiPedalModel::CheckForResourceInitialization(Pedalboard &pedalboard) } } } +Pedalboard &PiPedalModel::GetPedalboard() +{ + return this->pedalboard; +} +std::shared_ptr PiPedalModel::GetLv2Pedalboard() +{ + // test only. + Lv2PedalboardErrorList errorMessages; + std::shared_ptr lv2Pedalboard{this->pluginHost.CreateLv2Pedalboard(this->pedalboard, errorMessages)}; + if (errorMessages.size() != 0) + { + throw std::runtime_error(errorMessages[0].message); + } + return lv2Pedalboard; +} bool PiPedalModel::LoadCurrentPedalboard() { Lv2PedalboardErrorList errorMessages; @@ -2174,8 +2189,8 @@ UpdateStatus PiPedalModel::GetUpdateStatus() void PiPedalModel::UpdateNow(const std::string &updateUrl) { std::lock_guard lock(mutex); - std::filesystem::path fileName,signatureName; - updater.DownloadUpdate(updateUrl,&fileName,&signatureName); + std::filesystem::path fileName, signatureName; + updater.DownloadUpdate(updateUrl, &fileName, &signatureName); adminClient.InstallUpdate(fileName); } diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 06c97b1..8fc4104 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -251,6 +251,9 @@ namespace pipedal void SetPedalboard(int64_t clientId, Pedalboard &pedalboard); + Pedalboard&GetPedalboard(); + std::shared_ptr GetLv2Pedalboard(); + void UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard); void GetPresets(PresetIndex *pResult); diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index 9ffa5ef..0e41275 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -740,9 +740,9 @@ namespace pipedal size_t maxBufferSize = 1024; size_t maxAtomBufferSize = 16 * 1024; bool hasMidiInputChannel; - int numberOfAudioInputChannels = 2; - int numberOfAudioOutputChannels = 2; - double sampleRate = 0; + int numberOfAudioInputChannels = 1; + int numberOfAudioOutputChannels = 1; + double sampleRate = 48000; std::string vst3CachePath; diff --git a/src/profilePluginMain.cpp b/src/profilePluginMain.cpp new file mode 100644 index 0000000..d34acdc --- /dev/null +++ b/src/profilePluginMain.cpp @@ -0,0 +1,255 @@ +#include "PiPedalModel.hpp" +#include "PiPedalConfiguration.hpp" +#include "Pedalboard.hpp" +#include "Lv2Log.hpp" +#include "RingBufferReader.hpp" +#include "CommandLineParser.hpp" +#include +#include + +// apt install google-perftools +#include + +using namespace pipedal; +using namespace std; +namespace fs = std::filesystem; + +// discards data from the ring buffer without discarding it. + +using WriterRingbuffer = RingBuffer; + +class RingBufferSink +{ +public: + RingBufferSink(WriterRingbuffer &writerRingBuffer) + : writerRingbuffer(writerRingBuffer) + { + thread = std::make_unique( + [this]() + { + ThreadProc(); + }); + } + void Close() + { + if (!closed) + { + closed = true; + writerRingbuffer.close(); + terminateThread = true; + thread->join(); + thread = nullptr; + } + } + ~RingBufferSink() + { + Close(); + } + +private: + void ThreadProc() + { + std::vector dataVector(1024); + uint8_t *data = dataVector.data(); + while (true) + { + RingBufferStatus status = writerRingbuffer.readWait_for(std::chrono::milliseconds(10)); + if (status == RingBufferStatus::Closed) + { + break; + } + if (status == RingBufferStatus::Ready) + { + size_t available = writerRingbuffer.readSpace(); + while (available != 0) + { + size_t thisTime = std::min(dataVector.size(), available); + writerRingbuffer.read(thisTime, data); + available -= thisTime; + } + } + } + } + bool closed = false; + std::atomic terminateThread{false}; + + std::unique_ptr thread; + WriterRingbuffer &writerRingbuffer; +}; +struct ProfileOptions +{ + std::string presetName; + float benchmark_seconds = 20; + std::string outputFilename = "/tmp/profilePlugin.perf"; + bool noProfile = false; + bool waitForWork = false; + size_t frameSize = 64; +}; + +void profilePlugin(const ProfileOptions &profileOptions) +{ + size_t nFrames = profileOptions.frameSize; + Lv2Log::log_level(LogLevel::Info); + + /*** Initialize the model */ + PiPedalModel model; + fs::path doc_root = "/etc/pipedal/config"; + PiPedalConfiguration configuration; + try + { + configuration.Load(doc_root, ""); + } + catch (const std::exception &e) + { + std::stringstream s; + s << "Unable to read configuration from '" << (doc_root / "config.json") << "'. (" << e.what() << ")"; + throw std::runtime_error(s.str()); + } + + model.Init(configuration); + + model.LoadLv2PluginInfo(); + + // model.Load(); don't start audio. + + /* *** Load the preset. */ + PresetIndex presetIndex; + model.GetPresets(&presetIndex); + + int64_t presetIndexId = 0; + bool presetIndexFound = false; + for (const auto &preset : presetIndex.presets()) + { + if (preset.name() == profileOptions.presetName) + { + presetIndexId = preset.instanceId(); + presetIndexFound = true; + break; + } + } + if (!presetIndexFound) + { + throw std::runtime_error("Preset not found."); + } + model.LoadPreset(-1, presetIndexId); + + auto pedalboard = model.GetCurrentPedalboardCopy(); + cout << "uri: " << pedalboard.items()[0].uri() << endl; + /* *** Get and prepare the audio thread pedalboard. */ + auto lv2Pedalboard = model.GetLv2Pedalboard(); + + lv2Pedalboard->Activate(); + + std::vector inputBufferVector; + std::vector outputBufferVector; + inputBufferVector.resize(nFrames); + outputBufferVector.resize(nFrames); + + float *inputBuffers[2]{inputBufferVector.data(), nullptr}; + float *outputBuffers[2] = {inputBufferVector.data(), nullptr}; + + WriterRingbuffer writerRingbuffer; + RealtimeRingBufferWriter ringBufferWriter(&writerRingbuffer); + + RingBufferSink ringBufferSink(writerRingbuffer); + + /* *** Pump the plugin for a bit if it is expected to do work on the scheduler thread when initializing */ + if (profileOptions.waitForWork) + { + using clock = std::chrono::steady_clock; + + // idle, pumping the plugin occasionally to allow inital scheduler work to complete. + auto waitStart = clock::now(); + auto waitDuration = std::chrono::duration_cast(std::chrono::seconds(3)); + + while (true) + { + lv2Pedalboard->Run(inputBuffers, outputBuffers, nFrames, &ringBufferWriter); + + auto elapsed = clock::now() - waitStart; + if (elapsed >= waitDuration) + { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // the scheduler thread runs. + } + } + + /* *** Run the benchmark */ + if (!profileOptions.noProfile) + { + ProfilerStart(profileOptions.outputFilename.c_str()); + } + auto startTime = std::chrono::system_clock::now(); + + size_t repetitions = static_cast(48000 * profileOptions.benchmark_seconds / nFrames); + for (size_t i = 0; i < repetitions; ++i) + { + lv2Pedalboard->Run(inputBuffers, outputBuffers, nFrames, &ringBufferWriter); + } + + auto elapsedTime = std::chrono::high_resolution_clock::now() - startTime; + if (!profileOptions.noProfile) + { + ProfilerStop(); + } + std::chrono::milliseconds us = std::chrono::duration_cast(elapsedTime); + std::cout << "Ellapsed time: " << (us.count() / 1000.0) << "s" << endl; + + ringBufferSink.Close(); + + lv2Pedalboard->Deactivate(); +} + +int main(int argc, char **argv) +{ + try + { + ProfileOptions profileOptions; + bool help = false; + CommandLineParser commandLineParser; + commandLineParser.AddOption("w", "wait-for-work", &profileOptions.waitForWork); + commandLineParser.AddOption("", "no-profile", &profileOptions.noProfile); + commandLineParser.AddOption("o", "output", &profileOptions.outputFilename); + commandLineParser.AddOption("s", "seconds", &profileOptions.benchmark_seconds); + commandLineParser.AddOption("h", "help", &help); + + commandLineParser.Parse(argc, (const char **)argv); + + if (commandLineParser.Arguments().size() != 1 || help) + { + cout << "profilePlugin - Generate profiling data for LV2 Plugins" << endl; + cout << "Copyright (c) 2024 Robin E. R. Davies" << endl; + cout << endl; + cout << "Syntax: profilePlugin preset_name [options...]" << endl; + cout << " where preset_name is the name of a PiPedal preset." << endl; + cout << endl; + cout << " A google-perf profile capture will be written to " << endl; + cout << " /tmp/profilePlugin.perf" << endl; + cout << endl; + cout << "Options:" << endl; + cout << " --no-profile:" << endl; + cout << " do NOT generate a perf file." << endl; + cout << " -o, --output filename:" << endl; + cout << " The file to which profiler data is written. " << endl; + cout << " Defaults to /tmp/profilePlugin.perf" << endl; + cout << " -w, --wait-for-work: " << endl; + cout << " Assume that the plugin will load data on the LV2 scheduler thread." << endl; + cout << " -s, --seconds time_in_seconds: " << endl; + cout << " The number of seconds of audio to process." << endl; + cout << " -h, --help: display this message." << endl; + cout << endl; + return help ? EXIT_SUCCESS : EXIT_FAILURE; + } + + profileOptions.presetName = commandLineParser.Arguments()[0]; + + profilePlugin(profileOptions); + } + catch (const std::exception &e) + { + cerr << "Error: " << e.what() << endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} \ No newline at end of file