diff --git a/Makefile b/Makefile index 240d89a..f6389ec 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ CXXFLAGS += -DDEBUG -DVERBOSE_DEBUG -g CFLAGS += -DDEBUG -DVERBOSE_DEBUG -g endif -LIBS := -lwums -lwups -lwut -lfunctionpatcher -lmappedmemory -lz -lnotifications -lbuttoncombo +LIBS := -lwums -lwups -lwut -lfunctionpatcher -lmappedmemory -lz -lnotifications -lbuttoncombo -liopshell #------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level diff --git a/source/ShellCommands.cpp b/source/ShellCommands.cpp new file mode 100644 index 0000000..80af048 --- /dev/null +++ b/source/ShellCommands.cpp @@ -0,0 +1,174 @@ +#include "ShellCommands.h" + +#include "globals.h" +#include "plugin/PluginContainer.h" +#include "plugin/PluginData.h" + +#include + +#include + +#include +#include +#include + +namespace ShellCommands { + namespace { + class ConsoleTable { + public: + enum Alignment { LEFT, + RIGHT }; + + void AddColumn(const std::string &title, const Alignment align = LEFT) { + mCols.push_back({title, align, title.length()}); + } + + void AddRow(const std::vector &rowData) { + if (rowData.size() != mCols.size()) return; + mRows.push_back(rowData); + updateWidths(rowData); + } + + void AddFooter(const std::vector &footerData) { + if (footerData.size() != mCols.size()) return; + mFooter = footerData; + updateWidths(footerData); + } + + void Print() { + PrintSeparator(); + + OSReport("|"); + for (const auto &[title, align, width] : mCols) printCell(title, width, align); + OSReport("\n"); + + PrintSeparator(); + + for (const auto &row : mRows) { + OSReport("|"); + for (size_t i = 0; i < row.size(); ++i) printCell(row[i], mCols[i].width, mCols[i].align); + OSReport("\n"); + } + + if (!mFooter.empty()) { + PrintSeparator(); + OSReport("|"); + for (size_t i = 0; i < mFooter.size(); ++i) printCell(mFooter[i], mCols[i].width, mCols[i].align); + OSReport("\n"); + } + + size_t totalWidth = 1; + for (const auto &col : mCols) { + totalWidth += col.width + 3; + } + + std::string border(totalWidth, '-'); + OSReport("%s\n", border.c_str()); + } + + private: + void updateWidths(const std::vector &data) { + for (size_t i = 0; i < mCols.size(); ++i) { + if (data[i].length() > mCols[i].width) { + mCols[i].width = data[i].length(); + } + } + } + + void PrintSeparator() const { + OSReport("|"); + for (const auto &col : mCols) { + // Separator is width + 2 spaces padding + std::string line(col.width + 2, '-'); + OSReport("%s|", line.c_str()); + } + OSReport("\n"); + } + + static void printCell(const std::string &text, const size_t width, const Alignment align) { + if (align == LEFT) OSReport(" %-*s |", static_cast(width), text.c_str()); + else + OSReport(" %*s |", static_cast(width), text.c_str()); + } + + struct Column { + std::string title; + Alignment align; + size_t width; + }; + + std::vector mCols; + std::vector> mRows; + std::vector mFooter; + }; + } // namespace + + std::unique_ptr sPluginGroup; + std::optional sPluginCmdHandle; + + std::string BytesToHumanReadable(uint32_t bytes) { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.2f KiB", bytes / 1024.0f); + return std::string(buffer); + } + + void PrintHeapUsage() { + ConsoleTable table; + + table.AddColumn("Plugin Name", ConsoleTable::LEFT); + table.AddColumn("Current usage", ConsoleTable::RIGHT); + table.AddColumn("Peak usage", ConsoleTable::RIGHT); + table.AddColumn("Currently allocated", ConsoleTable::RIGHT); + table.AddColumn("Total allocated", ConsoleTable::RIGHT); + table.AddColumn("Total freed", ConsoleTable::RIGHT); + + uint32_t totalCurrentBytes = 0; + uint32_t totalPeakBytes = 0; + uint32_t totalChunks = 0; + + for (const auto &plugin : gLoadedPlugins) { + if (!plugin.isLinkedAndLoaded() || !plugin.isUsingTrackingPluginHeapMemoryAllocator()) { + continue; + } + + const auto tracking = plugin.getTrackingMemoryAllocator(); + if (!tracking) { continue; } + + const auto stats = tracking->GetHeapMemoryUsageSnapshot(); + if (!stats) { continue; } + + auto activeChunks = stats->allocationMap.size(); + + table.AddRow({stats->pluginName, + BytesToHumanReadable(stats->currentAllocated), + BytesToHumanReadable(stats->peakAllocated), + std::to_string(activeChunks), + std::to_string(stats->allocCount), + std::to_string(stats->freeCount)}); + + // Accumulate Totals + totalCurrentBytes += stats->currentAllocated; + totalPeakBytes += stats->peakAllocated; + totalChunks += activeChunks; + } + + // Add the Footer Row + table.AddFooter({"TOTAL (tracked plugins)", + BytesToHumanReadable(totalCurrentBytes), + BytesToHumanReadable(totalPeakBytes), + std::to_string(totalChunks), + "-", + "-"}); + + table.Print(); + } + + + void Init() { + sPluginGroup = std::make_unique("plugins", "Manage aroma plugins"); + + sPluginGroup->AddCommand("heap_usage", PrintHeapUsage, "Show current heap usage for tracked plugins"); + + sPluginCmdHandle = sPluginGroup->Register(); + } +} // namespace ShellCommands \ No newline at end of file diff --git a/source/ShellCommands.h b/source/ShellCommands.h new file mode 100644 index 0000000..ed0d464 --- /dev/null +++ b/source/ShellCommands.h @@ -0,0 +1,5 @@ +#pragma once + +namespace ShellCommands { + void Init(); +} diff --git a/source/main.cpp b/source/main.cpp index 203e851..e9ca91a 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,4 +1,5 @@ #include "PluginManagement.h" +#include "ShellCommands.h" #include "globals.h" #include "hooks.h" #include "patcher/hooks_patcher_static.h" @@ -20,6 +21,7 @@ #include #include +#include #include #include @@ -39,6 +41,9 @@ WUMS_DEPENDS_ON(homebrew_memorymapping); WUMS_DEPENDS_ON(homebrew_notifications); WUMS_DEPENDS_ON(homebrew_buttoncombo); +// This should be a soft dependency +//WUMS_DEPENDS_ON(homebrew_iopshell); + using namespace std::chrono_literals; WUMS_INITIALIZE() { @@ -59,6 +64,12 @@ WUMS_INITIALIZE() { gNotificationModuleLoaded = true; } + if (const auto res = IOPShellModule::Init(); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to init IOPShellModule: %s (%d)", IOPShellModule::GetErrorString(res), res); + } else { + ShellCommands::Init(); + } + DEBUG_FUNCTION_LINE("Patching functions"); for (uint32_t i = 0; i < method_hooks_static_size; i++) { if (FunctionPatcher_AddFunctionPatch(&method_hooks_static[i], nullptr, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { diff --git a/source/plugin/PluginContainer.cpp b/source/plugin/PluginContainer.cpp index a036b15..4508dc3 100644 --- a/source/plugin/PluginContainer.cpp +++ b/source/plugin/PluginContainer.cpp @@ -171,4 +171,11 @@ const IPluginHeapMemoryAllocator &PluginContainer::getMemoryAllocator() const { return *mTrackingHeapAllocatorOpt; } return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator; -} \ No newline at end of file +} + +const TrackingPluginHeapMemoryAllocator *PluginContainer::getTrackingMemoryAllocator() const { + if (mTrackingHeapAllocatorOpt) { + return &(mTrackingHeapAllocatorOpt.value()); + } + return nullptr; +} diff --git a/source/plugin/PluginContainer.h b/source/plugin/PluginContainer.h index e205197..93b2a15 100644 --- a/source/plugin/PluginContainer.h +++ b/source/plugin/PluginContainer.h @@ -79,6 +79,8 @@ public: [[nodiscard]] const IPluginHeapMemoryAllocator &getMemoryAllocator() const; + [[nodiscard]] const TrackingPluginHeapMemoryAllocator *getTrackingMemoryAllocator() const; + private: PluginMetaInformation mMetaInformation; PluginLinkInformation mPluginLinkInformation;