From 2545ffd25a0452b22aac211c9786231650b44fa7 Mon Sep 17 00:00:00 2001 From: Beq Date: Wed, 11 Dec 2024 01:58:54 +0000 Subject: [PATCH] Add searchable/filterable attributes to BugSplat New metadata file method. Like the previous method that only aplied to windows this is so far only available on PC and XBox BugSplat APIS. Unmlike the deprecated version, it is expected to be added to MacOS and crashpad "soon". --- indra/newview/CMakeLists.txt | 9 ++ indra/newview/bugsplatattributes.cpp | 132 +++++++++++++++++++++++++++ indra/newview/bugsplatattributes.h | 71 ++++++++++++++ indra/newview/llappviewer.cpp | 35 +++++-- indra/newview/llappviewer.h | 1 + indra/newview/llappviewerwin32.cpp | 101 ++++++++++++++++++-- indra/newview/llappviewerwin32.h | 2 +- 7 files changed, 337 insertions(+), 14 deletions(-) create mode 100644 indra/newview/bugsplatattributes.cpp create mode 100644 indra/newview/bugsplatattributes.h diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 04cf9f7140..0256c7c78d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1691,6 +1691,15 @@ set(viewer_HEADER_FILES list(APPEND viewer_SOURCE_FILES llperfstats.cpp) list(APPEND viewer_HEADER_FILES llperfstats.h) +if (USE_BUGSPLAT) + list(APPEND viewer_SOURCE_FILES + bugsplatattributes.cpp + ) + list(APPEND viewer_HEADER_FILES + bugsplatattributes.h + ) +endif (USE_BUGSPLAT) + # Flickr / Discord keys and fsversionvalues headers are generated in here include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/indra/newview/bugsplatattributes.cpp b/indra/newview/bugsplatattributes.cpp new file mode 100644 index 0000000000..46febab638 --- /dev/null +++ b/indra/newview/bugsplatattributes.cpp @@ -0,0 +1,132 @@ +#include "bugsplatattributes.h" +#include +#include + +std::wstring BugSplatAttributes::mCrashContextFileName; + +BugSplatAttributes& BugSplatAttributes::instance() +{ + static BugSplatAttributes sInstance; + return sInstance; +} + + +#include +#include + +std::wstring BugSplatAttributes::to_xml_token(const std::wstring& input) +{ + // Example usage: + // std::wstring token = to_xml_token(L"Bandwidth (kbit/s)"); + // The result should be: L"Bandwidth_kbit_per_s" + std::wstring result; + result.reserve(input.size() * 2); // Reserve some space, since we might insert more chars for "/" + + for (wchar_t ch : input) + { + if (ch == L'/') + { + // Replace '/' with "_per_" + result.append(L"_per_"); + } + else if (std::iswalnum(ch) || ch == L'_' || ch == L'-') + { + // Letters, digits, underscore, and hyphen are okay + result.push_back(ch); + } + else if (ch == L' ' || ch == L'(' || ch == L')') + { + // Replace spaces and parentheses with underscore + result.push_back(L'_'); + } + else + { + // Other characters map to underscore + result.push_back(L'_'); + } + } + + // Ensure the first character is a letter or underscore: + // If not, prepend underscore. + if (result.empty() || !(std::iswalpha(result.front()) || result.front() == L'_')) + { + result.insert(result.begin(), L'_'); + } + // trim trailing underscores + while (!result.empty() && result.back() == L'_') + { + result.pop_back(); + } + return result; +} + + + +bool BugSplatAttributes::writeToFile(const std::wstring& file_path) +{ + std::lock_guard lock(mMutex); + + // Write to a temporary file first + std::wstring tmp_file = file_path + L".tmp"; + + { + std::wofstream ofs(tmp_file, std::ios::out | std::ios::trunc); + if (!ofs.good()) + { + return false; + } + + ofs << L"\n"; + ofs << L"\n"; + + // First, write out attributes that have an empty category (top-level) + auto empty_category_it = mAttributes.find(L""); + if (empty_category_it != mAttributes.end()) + { + for (const auto& kv : empty_category_it->second) + { + const std::wstring& key = kv.first; + const std::wstring& val = kv.second; + ofs << L" <" << key << L">" << val << L"\n"; + } + } + + // Write out all other categories + for (const auto& cat_pair : mAttributes) + { + const std::wstring& category = cat_pair.first; + + // Skip empty category since we already handled it above + if (category.empty()) + { + continue; + } + + ofs << L" <" << category << L">\n"; + for (const auto& kv : cat_pair.second) + { + const std::wstring& key = kv.first; + const std::wstring& val = kv.second; + ofs << L" <" << key << L">" << val << L"\n"; + } + ofs << L" \n"; + } + + ofs << L"\n"; + + // Close the file before renaming + ofs.close(); + } + + // Rename the temp file to the final file. If rename fails, leave the old file intact. + std::error_code ec; + std::filesystem::rename(tmp_file, file_path, ec); + if (ec) + { + // Rename failed, remove the temp file and return false + std::filesystem::remove(tmp_file, ec); + return false; + } + + return true; +} diff --git a/indra/newview/bugsplatattributes.h b/indra/newview/bugsplatattributes.h new file mode 100644 index 0000000000..8155225ac3 --- /dev/null +++ b/indra/newview/bugsplatattributes.h @@ -0,0 +1,71 @@ +#ifndef BUGSPLATATTRIBUTES_INCLUDED +#define BUGSPLATATTRIBUTES_INCLUDED +#ifdef LL_BUGSPLAT +// Currently on Windows BugSplat supports the new attributes file, but it is expected to be added to Mac and Linux soon. + +#include +#include +#include + +class BugSplatAttributes +{ +public: + // Obtain the singleton instance + static BugSplatAttributes& instance(); + + template + void setAttribute(const std::wstring& key, const T& value, const std::wstring& category = L"FS") + { + std::lock_guard lock(mMutex); + const auto& xml_key = to_xml_token(key); + if constexpr (std::is_same_v) + { + // Already wide string + mAttributes[category][xml_key] = value; + } + else if constexpr (std::is_same_v || std::is_array_v) + { + // Handle wide string literals and arrays (which includes string literals) + mAttributes[category][xml_key] = std::wstring(value); + } + else if constexpr (std::is_same_v) + { + // Convert narrow to wide + std::wstring wide_value(value.begin(), value.end()); + mAttributes[category][xml_key] = wide_value; + } + else if constexpr (std::is_same_v) + { + // Convert boolean to "true"/"false" + mAttributes[category][xml_key] = value ? L"true" : L"false"; + } + else if constexpr (std::is_arithmetic_v) + { + // Convert arithmetic types (int, float, double, etc.) to wstring + mAttributes[category][xml_key] = std::to_wstring(value); + } + else + { + static_assert(!sizeof(T*), "No known conversion for this type to std::wstring."); + } + } + // Returns true on success, false on failure. + bool writeToFile(const std::wstring& file_path); + const static std::wstring& getCrashContextFileName() { return mCrashContextFileName; } + static void setCrashContextFileName(const std::wstring& file_name) { mCrashContextFileName = file_name; } +private: + BugSplatAttributes() = default; + ~BugSplatAttributes() = default; + BugSplatAttributes(const BugSplatAttributes&) = delete; + BugSplatAttributes& operator=(const BugSplatAttributes&) = delete; + std::wstring to_xml_token(const std::wstring& input); + // Internal structure to hold attributes by category + // For example: + // attributes_["RuntimeProperties"]["CrashGUID"] = L"649F57B7EE6E92A0_0000" + std::unordered_map> mAttributes; + std::mutex mMutex; + static std::wstring mCrashContextFileName; +}; + +#endif // LL_BUGSPLAT +#endif // BUGSPLATATTRIBUTES_INCLUDED \ No newline at end of file diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2b9d642aa7..522ae59736 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -289,7 +289,7 @@ using namespace LL; #include "fsradar.h" #include "fsassetblacklist.h" - +#include "bugsplatattributes.h" // #include "fstelemetry.h" // Tracy profiler support #if LL_LINUX && LL_GTK @@ -767,6 +767,10 @@ LLAppViewer::LLAppViewer() // MAINT-8917: don't create a dump directory just for the // static_debug_info.log file std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); + // Improve Bugsplat tracking by using attribu + std::wstring wlogdir(logdir.begin(), logdir.end()); + BugSplatAttributes::setCrashContextFileName(wlogdir + L"crash-context.xml"); + // # else // ! LL_BUGSPLAT // write Google Breakpad minidump files to a per-run dump directory to avoid multiple viewer issues. std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); @@ -3762,6 +3766,10 @@ bool LLAppViewer::waitForUpdater() void LLAppViewer::writeDebugInfo(bool isStatic) { #if LL_WINDOWS && LL_BUGSPLAT + // Improve Bugsplat tracking by using attributes for certain static data items. + const LLSD& info = getViewerInfo(); + bugsplatAddStaticAttributes(info); + // // bugsplat does not create dump folder and debug logs are written directly // to logs folder, so it conflicts with main instance if (mSecondInstance) @@ -3970,10 +3978,17 @@ LLSD LLAppViewer::getViewerInfo() const info["OPENGL_VERSION"] = ll_safe_string((const char*)(glGetString(GL_VERSION))); info["LIBCURL_VERSION"] = LLCore::LLHttp::getCURLVersion(); // Settings - - LLRect window_rect = gViewerWindow->getWindowRectRaw(); - info["WINDOW_WIDTH"] = window_rect.getWidth(); - info["WINDOW_HEIGHT"] = window_rect.getHeight(); + // gViewerWindow can be null on shutdown. Crashes if bugsplatt uses the info + // LLRect window_rect = gViewerWindow->getWindowRectRaw(); + // info["WINDOW_WIDTH"] = window_rect.getWidth(); + // info["WINDOW_HEIGHT"] = window_rect.getHeight(); + if(gViewerWindow) + { + LLRect window_rect = gViewerWindow->getWindowRectRaw(); + info["WINDOW_WIDTH"] = window_rect.getWidth(); + info["WINDOW_HEIGHT"] = window_rect.getHeight(); + } + // // Custom sysinfo //info["FONT_SIZE_ADJUSTMENT"] = gSavedSettings.getF32("FontScreenDPI"); @@ -3994,7 +4009,7 @@ LLSD LLAppViewer::getViewerInfo() const info["J2C_VERSION"] = LLImageJ2C::getEngineInfo(); bool want_fullname = true; info["AUDIO_DRIVER_VERSION"] = gAudiop ? LLSD(gAudiop->getDriverName(want_fullname)) : "Undefined"; - if(LLVoiceClient::getInstance()->voiceEnabled()) + if(LLStartUp::getStartupState() == STATE_STARTED && LLVoiceClient::getInstance()->voiceEnabled()) // Calling this too earli leads to a nasty crash loop { LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion(); const std::string build_version = version.mBuildVersion; @@ -4086,7 +4101,13 @@ LLSD LLAppViewer::getViewerInfo() const } // populate field for new local disk cache with some details - info["DISK_CACHE_INFO"] = LLDiskCache::getInstance()->getCacheInfo(); + // only populate if the cache is available + // info["DISK_CACHE_INFO"] = LLDiskCache::getInstance()->getCacheInfo(); + if (auto cache = LLDiskCache::getInstance(); cache) + { + info["DISK_CACHE_INFO"] = cache->getCacheInfo(); + } + // // FIRE-4785: Current render quality setting in sysinfo / about floater switch (gSavedSettings.getU32("RenderQualityPerformance")) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 1183ab7f6c..40b686c094 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -253,6 +253,7 @@ protected: virtual void overrideDetectedHardware(); // Override VRAM (and others in future?) consistently. virtual bool initSLURLHandler(); virtual bool sendURLToOtherInstance(const std::string& url); + virtual void bugsplatAddStaticAttributes(const LLSD& info) {/*empty*/}; // create a NOOP base impl virtual bool initParseCommandLine(LLCommandLineParser& clp) { return true; } // Allow platforms to specify the command line args. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 4361107589..643b1ab321 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -75,6 +75,7 @@ // Bugsplat (http://bugsplat.com) crash reporting tool #ifdef LL_BUGSPLAT +#include "bugsplatattributes.h" #include "BugSplat.h" #include "boost/json.hpp" // Boost.Json #include "llagent.h" // for agent location @@ -164,6 +165,7 @@ namespace flavor = "oss"; #endif sBugSplatSender->setDefaultUserEmail( WCSTR(STRINGIZE(LLOSInfo::instance().getOSStringSimple() << " (" << ADDRESS_SIZE << "-bit, flavor " << flavor <<")"))); + // BugSplatAttributes::instance().setAttribute(L"Flavor", flavor); // // Clear out username first, as we get some crashes that has the OS set as username, let's see if this fixes it. Use Crash.Linden as a usr can never have a "Linden" @@ -195,7 +197,6 @@ namespace // LL_ERRS message, when there is one sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); - sBugSplatSender->setAttribute(WCSTR(L"OS"), WCSTR(LLOSInfo::instance().getOSStringSimple())); // In case we ever stop using email for this sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString())); sBugSplatSender->setAttribute(WCSTR(L"GL Vendor"), WCSTR(gGLManager.mGLVendor)); @@ -203,18 +204,41 @@ namespace sBugSplatSender->setAttribute(WCSTR(L"GPU Version"), WCSTR(gGLManager.mDriverVersionVendorString)); sBugSplatSender->setAttribute(WCSTR(L"GL Renderer"), WCSTR(gGLManager.mGLRenderer)); sBugSplatSender->setAttribute(WCSTR(L"VRAM"), WCSTR(STRINGIZE(gGLManager.mVRAM))); - sBugSplatSender->setAttribute(WCSTR(L"RAM"), WCSTR(STRINGIZE(gSysMemory.getPhysicalMemoryKB().value()))); + // sBugSplatSender->setAttribute(WCSTR(L"RAM"), WCSTR(STRINGIZE(gSysMemory.getPhysicalMemoryKB().value()))); + // Improve bugsplpat reporting with attributes + // sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + // sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString())); + auto fatal_message = LLError::getFatalMessage(); + sBugSplatSender->setDefaultUserDescription(WCSTR(fatal_message)); + BugSplatAttributes::instance().setAttribute(L"FatalMessage", fatal_message); // Store this additionally as an attribute in case user overwrites. + // App state + BugSplatAttributes::instance().setAttribute(L"AppState", LLStartUp::getStartupStateString()); + // Location + // if (gAgent.getRegion()) { // region location, when we have it - LLVector3 loc = gAgent.getPositionAgent(); - sBugSplatSender->resetAppIdentifier( - WCSTR(STRINGIZE(gAgent.getRegion()->getName() + // Improve bugsplat reporting with attributes + // LLVector3 loc = gAgent.getPositionAgent(); + // sBugSplatSender->resetAppIdentifier( + // WCSTR(STRINGIZE(gAgent.getRegion()->getName() + // << '/' << loc.mV[0] + // << '/' << loc.mV[1] + // << '/' << loc.mV[2]))); + const LLVector3 loc = gAgent.getPositionAgent(); + const auto & fullLocation = STRINGIZE(gAgent.getRegion()->getName() << '/' << loc.mV[0] << '/' << loc.mV[1] - << '/' << loc.mV[2]))); + << '/' << loc.mV[2]); + sBugSplatSender->resetAppIdentifier(WCSTR(fullLocation)); + BugSplatAttributes::instance().setAttribute(L"Location", std::string(fullLocation)); + // } + // Improve bugsplat reporting with attributes + LLAppViewer::instance()->writeDebugInfo(); + sBugSplatSender->sendAdditionalFile(WCSTR(BugSplatAttributes::getCrashContextFileName())); // Add the new attributes file + // } // MDSCB_EXCEPTIONCODE return false; @@ -619,6 +643,71 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } } #endif +// Use the Attributes API on Windows to enhance crash metadata +void LLAppViewerWin32::bugsplatAddStaticAttributes(const LLSD& info) +{ +#ifdef LL_BUGSPLAT + static bool write_statics = true; + + auto& bugSplatMap = BugSplatAttributes::instance(); + + if (write_statics) + { + write_statics = false; + auto multipleInstances = gDebugInfo["FoundOtherInstanceAtStartup"].asBoolean(); + bugSplatMap.setAttribute(L"MultipleInstance", multipleInstances); + + bugSplatMap.setAttribute(L"GPU", info["GRAPHICS_CARD"].asString()); + bugSplatMap.setAttribute(L"GPU VRAM (MB)", info["GRAPHICS_CARD_MEMORY"].asInteger()); + bugSplatMap.setAttribute(L"GPU VRAM Detected (MB)", info["GRAPHICS_CARD_MEMORY_DETECTED"].asInteger()); + bugSplatMap.setAttribute(L"GPU VRAM (Budget)", info["VRAM_BUDGET_ENGLISH"].asInteger()); + + bugSplatMap.setAttribute(L"CPU", info["CPU"].asString()); + bugSplatMap.setAttribute(L"Graphics Driver", info["GRAPHICS_DRIVER_VERSION"].asString()); + bugSplatMap.setAttribute(L"CPU MHz", (S32)gSysCPU.getMHz()); // +#ifdef USE_AVX2_OPTIMIZATION + bugSplatMap.setAttribute(L"SIMD", L"AVX2"); +#elif USE_AVX_OPTIMIZATION + bugSplatMap.setAttribute(L"SIMD", L"AVX"); +#else + bugSplatMap.setAttribute(L"SIMD", L"SSE2"); +#endif + // set physical ram integer as a string attribute + bugSplatMap.setAttribute(L"Physical RAM (KB)", LLMemory::getMaxMemKB().value()); + bugSplatMap.setAttribute(L"OpenGL Version", info["OPENGL_VERSION"].asString()); + bugSplatMap.setAttribute(L"libcurl Version", info["LIBCURL_VERSION"].asString()); + bugSplatMap.setAttribute(L"J2C Decoder Version", info["J2C_VERSION"].asString()); + bugSplatMap.setAttribute(L"Audio Driver Version", info["AUDIO_DRIVER_VERSION"].asString()); + // bugSplatMap.setAttribute(L"CEF Info", info["LIBCEF_VERSION"].asString()); + bugSplatMap.setAttribute(L"LibVLC Version", info["LIBVLC_VERSION"].asString()); + bugSplatMap.setAttribute(L"Vivox Version", info["VOICE_VERSION"].asString()); + bugSplatMap.setAttribute(L"RLVa", info["RLV_VERSION"].asString()); + bugSplatMap.setAttribute(L"Mode", info["MODE"].asString()); + bugSplatMap.setAttribute(L"Skin", llformat("%s (%s)", info["SKIN"].asString().c_str(), info["THEME"].asString().c_str())); + #if LL_DARWIN + bugSplatMap.setAttribute(L"HiDPI", info["HIDPI"].asBoolean() ? L"Enabled" : L"Disabled"); + #endif + } + // These attributes are potentially dynamic + bugSplatMap.setAttribute(L"Packets Lost", llformat("%.0f/%.0f (%.1f%%)", info["PACKETS_LOST"].asReal(), info["PACKETS_IN"].asReal(), info["PACKETS_PCT"].asReal())); + bugSplatMap.setAttribute(L"Window Size", llformat("%sx%s px", info["WINDOW_WIDTH"].asString().c_str(), info["WINDOW_HEIGHT"].asString().c_str())); + bugSplatMap.setAttribute(L"Draw Distance (m)", info["DRAW_DISTANCE"].asInteger()); + bugSplatMap.setAttribute(L"Bandwidth (kbit/s)", info["BANDWIDTH"].asInteger()); + bugSplatMap.setAttribute(L"LOD Factor", info["LOD"].asReal()); + bugSplatMap.setAttribute(L"Render quality", info["RENDERQUALITY_FSDATA_ENGLISH"].asString()); + bugSplatMap.setAttribute(L"Disk Cache", info["DISK_CACHE_INFO"].asString()); + + bugSplatMap.setAttribute(L"GridName", gDebugInfo["GridName"].asString()); + bugSplatMap.setAttribute(L"Available RAM (KB)", LLMemory::getAvailableMemKB().value()); + bugSplatMap.setAttribute(L"Allocated RAM (KB)", LLMemory::getAllocatedMemKB().value()); + + if (bugSplatMap.writeToFile(BugSplatAttributes::getCrashContextFileName())) + { + LL_INFOS() << "Crash context saved to " << WCSTR(BugSplatAttributes::getCrashContextFileName()) << LL_ENDL; + } +#endif +} +// void LLAppViewerWin32::disableWinErrorReporting() { diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 1be4c62ee5..90ed468bc1 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -62,7 +62,7 @@ protected: private: void disableWinErrorReporting(); - + void bugsplatAddStaticAttributes(const LLSD& info) override; // override for windows attributes std::string mCmdLine; bool mIsConsoleAllocated;