diff options
| author | nat-goodspeed <nat@lindenlab.com> | 2024-09-19 14:48:14 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-19 14:48:14 -0400 | 
| commit | a7d5d4cf84b751f129264d2b24a7b1bb799fce9c (patch) | |
| tree | 34c4f8101d0bdbd1b944a16cdc93a15b2d0f3cc0 /indra | |
| parent | 25969b330e4dc69f6eb39a487b171ccc07a5df14 (diff) | |
| parent | be40936881a747893d03c5c003914efb3867ccd1 (diff) | |
Merge pull request #2610 from secondlife/frame-profile-json
Make Develop->Render Tests->Frame Profile dump JSON to a file too.
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | indra/llcommon/fsyspath.h | 79 | ||||
| -rwxr-xr-x | indra/llcommon/hexdump.h | 106 | ||||
| -rw-r--r-- | indra/llcommon/llsdjson.cpp | 15 | ||||
| -rw-r--r-- | indra/llrender/llglslshader.cpp | 108 | ||||
| -rw-r--r-- | indra/llrender/llglslshader.h | 10 | ||||
| -rw-r--r-- | indra/newview/llappviewer.cpp | 4 | ||||
| -rw-r--r-- | indra/newview/llfeaturemanager.cpp | 2 | ||||
| -rw-r--r-- | indra/newview/llglsandbox.cpp | 2 | ||||
| -rw-r--r-- | indra/newview/llviewerdisplay.cpp | 144 | 
10 files changed, 394 insertions, 77 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 60549d9d11..a504e71340 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -119,6 +119,7 @@ set(llcommon_HEADER_FILES      commoncontrol.h      ctype_workaround.h      fix_macros.h +    fsyspath.h      function_types.h      indra_constants.h      lazyeventapi.h diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h new file mode 100644 index 0000000000..1b4aec09b4 --- /dev/null +++ b/indra/llcommon/fsyspath.h @@ -0,0 +1,79 @@ +/** + * @file   fsyspath.h + * @author Nat Goodspeed + * @date   2024-04-03 + * @brief  Adapt our UTF-8 std::strings for std::filesystem::path + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_FSYSPATH_H) +#define LL_FSYSPATH_H + +#include <filesystem> + +// While std::filesystem::path can be directly constructed from std::string on +// both Posix and Windows, that's not what we want on Windows. Per +// https://en.cppreference.com/w/cpp/filesystem/path/path: + +// ... the method of conversion to the native character set depends on the +// character type used by source. +// +// * If the source character type is char, the encoding of the source is +//   assumed to be the native narrow encoding (so no conversion takes place on +//   POSIX systems). +// * If the source character type is char8_t, conversion from UTF-8 to native +//   filesystem encoding is used. (since C++20) +// * If the source character type is wchar_t, the input is assumed to be the +//   native wide encoding (so no conversion takes places on Windows). + +// The trouble is that on Windows, from std::string ("source character type is +// char"), the "native narrow encoding" isn't UTF-8, so file paths containing +// non-ASCII characters get mangled. +// +// Once we're building with C++20, we could pass a UTF-8 std::string through a +// vector<char8_t> to engage std::filesystem::path's own UTF-8 conversion. But +// sigh, as of 2024-04-03 we're not yet there. +// +// Anyway, encapsulating the important UTF-8 conversions in our own subclass +// allows us to migrate forward to C++20 conventions without changing +// referencing code. + +class fsyspath: public std::filesystem::path +{ +    using super = std::filesystem::path; + +public: +    // default +    fsyspath() {} +    // construct from UTF-8 encoded std::string +    fsyspath(const std::string& path): super(std::filesystem::u8path(path)) {} +    // construct from UTF-8 encoded const char* +    fsyspath(const char* path): super(std::filesystem::u8path(path)) {} +    // construct from existing path +    fsyspath(const super& path): super(path) {} + +    fsyspath& operator=(const super& p) { super::operator=(p); return *this; } +    fsyspath& operator=(const std::string& p) +    { +        super::operator=(std::filesystem::u8path(p)); +        return *this; +    } +    fsyspath& operator=(const char* p) +    { +        super::operator=(std::filesystem::u8path(p)); +        return *this; +    } + +    // shadow base-class string() method with UTF-8 aware method +    std::string string() const { return super::u8string(); } +    // On Posix systems, where value_type is already char, this operator +    // std::string() method shadows the base class operator string_type() +    // method. But on Windows, where value_type is wchar_t, the base class +    // doesn't have operator std::string(). Provide it. +    operator std::string() const { return string(); } +}; + +#endif /* ! defined(LL_FSYSPATH_H) */ diff --git a/indra/llcommon/hexdump.h b/indra/llcommon/hexdump.h new file mode 100755 index 0000000000..ab5ba2b16d --- /dev/null +++ b/indra/llcommon/hexdump.h @@ -0,0 +1,106 @@ +/** + * @file   hexdump.h + * @author Nat Goodspeed + * @date   2023-10-03 + * @brief  iostream manipulators to stream hex, or string with nonprinting chars + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_HEXDUMP_H) +#define LL_HEXDUMP_H + +#include <cctype> +#include <iomanip> +#include <iostream> +#include <string_view> + +namespace LL +{ + +// Format a given byte string as 2-digit hex values, no separators +// Usage: std::cout << hexdump(somestring) << ... +class hexdump +{ +public: +    hexdump(const std::string_view& data): +        hexdump(data.data(), data.length()) +    {} + +    hexdump(const char* data, size_t len): +        hexdump(reinterpret_cast<const unsigned char*>(data), len) +    {} + +    hexdump(const std::vector<unsigned char>& data): +        hexdump(data.data(), data.size()) +    {} + +    hexdump(const unsigned char* data, size_t len): +        mData(data, data + len) +    {} + +    friend std::ostream& operator<<(std::ostream& out, const hexdump& self) +    { +        auto oldfmt{ out.flags() }; +        auto oldfill{ out.fill() }; +        out.setf(std::ios_base::hex, std::ios_base::basefield); +        out.fill('0'); +        for (auto c : self.mData) +        { +            out << std::setw(2) << unsigned(c); +        } +        out.setf(oldfmt, std::ios_base::basefield); +        out.fill(oldfill); +        return out; +    } + +private: +    std::vector<unsigned char> mData; +}; + +// Format a given byte string as a mix of printable characters and, for each +// non-printable character, "\xnn" +// Usage: std::cout << hexmix(somestring) << ... +class hexmix +{ +public: +    hexmix(const std::string_view& data): +        mData(data) +    {} + +    hexmix(const char* data, size_t len): +        mData(data, len) +    {} + +    friend std::ostream& operator<<(std::ostream& out, const hexmix& self) +    { +        auto oldfmt{ out.flags() }; +        auto oldfill{ out.fill() }; +        out.setf(std::ios_base::hex, std::ios_base::basefield); +        out.fill('0'); +        for (auto c : self.mData) +        { +            // std::isprint() must be passed an unsigned char! +            if (std::isprint(static_cast<unsigned char>(c))) +            { +                out << c; +            } +            else +            { +                out << "\\x" << std::setw(2) << unsigned(c); +            } +        } +        out.setf(oldfmt, std::ios_base::basefield); +        out.fill(oldfill); +        return out; +    } + +private: +    std::string mData; +}; + +} // namespace LL + +#endif /* ! defined(LL_HEXDUMP_H) */ diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 5d38e55686..1df2a8f9eb 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -61,12 +61,20 @@ LLSD LlsdFromJson(const boost::json::value& val)          result = LLSD(val.as_bool());          break;      case boost::json::kind::array: +    {          result = LLSD::emptyArray(); -        for (const auto &element : val.as_array()) +        auto& array = val.as_array(); +        // allocate elements 0 .. (size() - 1) to avoid incremental allocation +        if (! array.empty()) +        { +            result[array.size() - 1] = LLSD(); +        } +        for (const auto &element : array)          {              result.append(LlsdFromJson(element));          }          break; +    }      case boost::json::kind::object:          result = LLSD::emptyMap();          for (const auto& element : val.as_object()) @@ -106,6 +114,7 @@ boost::json::value LlsdToJson(const LLSD &val)      case LLSD::TypeMap:      {          boost::json::object& obj = result.emplace_object(); +        obj.reserve(val.size());          for (const auto& llsd_dat : llsd::inMap(val))          {              obj[llsd_dat.first] = LlsdToJson(llsd_dat.second); @@ -115,6 +124,7 @@ boost::json::value LlsdToJson(const LLSD &val)      case LLSD::TypeArray:      {          boost::json::array& json_array = result.emplace_array(); +        json_array.reserve(val.size());          for (const auto& llsd_dat : llsd::inArray(val))          {              json_array.push_back(LlsdToJson(llsd_dat)); @@ -123,7 +133,8 @@ boost::json::value LlsdToJson(const LLSD &val)      }      case LLSD::TypeBinary:      default: -        LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL; +        LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" +                              << val.type() << ")." << LL_ENDL;          break;      } diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index a157bfee21..6ba5463acd 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -63,6 +63,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0;  U32 LLGLSLShader::sTotalTrianglesDrawn = 0;  U64 LLGLSLShader::sTotalSamplesDrawn = 0;  U32 LLGLSLShader::sTotalBinds = 0; +boost::json::value LLGLSLShader::sDefaultStats;  //UI shader -- declared here so llui_libtest will link properly  LLGLSLShader    gUIProgram; @@ -101,9 +102,9 @@ void LLGLSLShader::initProfile()      sTotalSamplesDrawn = 0;      sTotalBinds = 0; -    for (std::set<LLGLSLShader*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) +    for (auto ptr : sInstances)      { -        (*iter)->clearStats(); +        ptr->clearStats();      }  } @@ -117,45 +118,57 @@ struct LLGLSLShaderCompareTimeElapsed  };  //static -void LLGLSLShader::finishProfile(bool emit_report) +void LLGLSLShader::finishProfile(boost::json::value& statsv)  {      sProfileEnabled = false; -    if (emit_report) +    if (! statsv.is_null())      { -        std::vector<LLGLSLShader*> sorted; - -        for (std::set<LLGLSLShader*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) -        { -            sorted.push_back(*iter); -        } - +        std::vector<LLGLSLShader*> sorted(sInstances.begin(), sInstances.end());          std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed()); +        auto& stats = statsv.as_object(); +        auto shadersit = stats.emplace("shaders", boost::json::array_kind).first; +        auto& shaders = shadersit->value().as_array();          bool unbound = false; -        for (std::vector<LLGLSLShader*>::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) +        for (auto ptr : sorted)          { -            (*iter)->dumpStats(); -            if ((*iter)->mBinds == 0) +            if (ptr->mBinds == 0)              {                  unbound = true;              } +            else +            { +                auto& shaderit = shaders.emplace_back(boost::json::object_kind); +                ptr->dumpStats(shaderit.as_object()); +            }          } +        constexpr float mega = 1'000'000.f; +        float totalTimeMs = sTotalTimeElapsed / mega;          LL_INFOS() << "-----------------------------------" << LL_ENDL; -        LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", sTotalTimeElapsed / 1000000.f) << LL_ENDL; -        LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / 1000000.f) << LL_ENDL; -        LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / 1000000.f) << LL_ENDL; +        LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", totalTimeMs) << LL_ENDL; +        LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / mega) << LL_ENDL; +        LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / mega) << LL_ENDL;          LL_INFOS() << "-----------------------------------" << LL_ENDL; - +        auto totalsit = stats.emplace("totals", boost::json::object_kind).first; +        auto& totals = totalsit->value().as_object(); +        totals.emplace("time", totalTimeMs / 1000.0); +        totals.emplace("binds", sTotalBinds); +        totals.emplace("samples", sTotalSamplesDrawn); +        totals.emplace("triangles", sTotalTrianglesDrawn); + +        auto unusedit = stats.emplace("unused", boost::json::array_kind).first; +        auto& unused = unusedit->value().as_array();          if (unbound)          {              LL_INFOS() << "The following shaders were unused: " << LL_ENDL; -            for (std::vector<LLGLSLShader*>::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) +            for (auto ptr : sorted)              { -                if ((*iter)->mBinds == 0) +                if (ptr->mBinds == 0)                  { -                    LL_INFOS() << (*iter)->mName << LL_ENDL; +                    LL_INFOS() << ptr->mName << LL_ENDL; +                    unused.emplace_back(ptr->mName);                  }              }          } @@ -170,36 +183,43 @@ void LLGLSLShader::clearStats()      mBinds = 0;  } -void LLGLSLShader::dumpStats() +void LLGLSLShader::dumpStats(boost::json::object& stats)  { -    if (mBinds > 0) +    stats.emplace("name", mName); +    auto filesit = stats.emplace("files", boost::json::array_kind).first; +    auto& files = filesit->value().as_array(); +    LL_INFOS() << "=============================================" << LL_ENDL; +    LL_INFOS() << mName << LL_ENDL; +    for (U32 i = 0; i < mShaderFiles.size(); ++i)      { -        LL_INFOS() << "=============================================" << LL_ENDL; -        LL_INFOS() << mName << LL_ENDL; -        for (U32 i = 0; i < mShaderFiles.size(); ++i) -        { -            LL_INFOS() << mShaderFiles[i].first << LL_ENDL; -        } -        LL_INFOS() << "=============================================" << LL_ENDL; +        LL_INFOS() << mShaderFiles[i].first << LL_ENDL; +        files.emplace_back(mShaderFiles[i].first); +    } +    LL_INFOS() << "=============================================" << LL_ENDL; -        F32 ms = mTimeElapsed / 1000000.f; -        F32 seconds = ms / 1000.f; +    constexpr float  mega = 1'000'000.f; +    constexpr double giga = 1'000'000'000.0; +    F32 ms = mTimeElapsed / mega; +    F32 seconds = ms / 1000.f; -        F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; -        F32 tris_sec = (F32)(mTrianglesDrawn / 1000000.0); -        tris_sec /= seconds; +    F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; +    F32 tris_sec = (F32)(mTrianglesDrawn / mega); +    tris_sec /= seconds; -        F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; -        F32 samples_sec = (F32)(mSamplesDrawn / 1000000000.0); -        samples_sec /= seconds; +    F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; +    F32 samples_sec = (F32)(mSamplesDrawn / giga); +    samples_sec /= seconds; -        F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; +    F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; -        LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; -        LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; -        LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; -        LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; -    } +    LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; +    LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; +    LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; +    LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; +    stats.emplace("time", seconds); +    stats.emplace("binds", mBinds); +    stats.emplace("samples", mSamplesDrawn); +    stats.emplace("triangles", mTrianglesDrawn);  }  //static diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 27c8f0b7d0..2d669c70a9 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -30,6 +30,7 @@  #include "llgl.h"  #include "llrender.h"  #include "llstaticstringtable.h" +#include <boost/json.hpp>  #include <unordered_map>  class LLShaderFeatures @@ -169,14 +170,14 @@ public:      static U32 sMaxGLTFNodes;      static void initProfile(); -    static void finishProfile(bool emit_report = true); +    static void finishProfile(boost::json::value& stats=sDefaultStats);      static void startProfile();      static void stopProfile();      void unload();      void clearStats(); -    void dumpStats(); +    void dumpStats(boost::json::object& stats);      // place query objects for profiling if profiling is enabled      // if for_runtime is true, will place timer query only whether or not profiling is enabled @@ -363,6 +364,11 @@ public:  private:      void unloadInternal(); +    // This must be static because finishProfile() is called at least once +    // within a __try block. If we default its stats parameter to a temporary +    // json::value, that temporary must be destroyed when the stack is +    // unwound, which __try forbids. +    static boost::json::value sDefaultStats;  };  //UI shader (declared here so llui_libtest will link properly) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 764e52accb..093314a9f1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3285,10 +3285,10 @@ LLSD LLAppViewer::getViewerInfo() const          LLVector3d pos = gAgent.getPositionGlobal();          info["POSITION"] = ll_sd_from_vector3d(pos);          info["POSITION_LOCAL"] = ll_sd_from_vector3(gAgent.getPosAgentFromGlobal(pos)); -        info["REGION"] = gAgent.getRegion()->getName(); +        info["REGION"] = region->getName();          boost::regex regex("\\.(secondlife|lindenlab)\\..*"); -        info["HOSTNAME"] = boost::regex_replace(gAgent.getRegion()->getSimHostName(), regex, ""); +        info["HOSTNAME"] = boost::regex_replace(region->getSimHostName(), regex, "");          info["SERVER_VERSION"] = gLastVersionChannel;          LLSLURL slurl;          LLAgentUI::buildSLURL(slurl); diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 3259ea249b..b5d8f70c2e 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -393,7 +393,7 @@ F32 logExceptionBenchmark()      __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation()))      {          // HACK - ensure that profiling is disabled -        LLGLSLShader::finishProfile(false); +        LLGLSLShader::finishProfile();          // convert to C++ styled exception          char integer_string[32]; diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index af025d5879..112008172e 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -838,7 +838,7 @@ struct ShaderProfileHelper      }      ~ShaderProfileHelper()      { -        LLGLSLShader::finishProfile(false); +        LLGLSLShader::finishProfile();      }  }; diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 301ea5c5f6..f722d0bd1d 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -28,58 +28,70 @@  #include "llviewerdisplay.h" -#include "llgl.h" -#include "llrender.h" -#include "llglheaders.h" -#include "llgltfmateriallist.h" +#include "fsyspath.h" +#include "hexdump.h"  #include "llagent.h"  #include "llagentcamera.h" -#include "llviewercontrol.h" +#include "llappviewer.h"  #include "llcoord.h"  #include "llcriticaldamp.h" +#include "llcubemap.h"  #include "lldir.h" -#include "lldynamictexture.h"  #include "lldrawpoolalpha.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolwater.h" +#include "lldynamictexture.h" +#include "llenvironment.h" +#include "llfasttimer.h"  #include "llfeaturemanager.h" -//#include "llfirstuse.h" +#include "llfloatertools.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llgltfmateriallist.h"  #include "llhudmanager.h"  #include "llimagepng.h" +#include "llmachineid.h"  #include "llmemory.h" +#include "llparcel.h" +#include "llperfstats.h" +#include "llpostprocess.h" +#include "llrender.h" +#include "llscenemonitor.h" +#include "llsdjson.h"  #include "llselectmgr.h"  #include "llsky.h" +#include "llspatialpartition.h"  #include "llstartup.h" +#include "llstartup.h" +#include "lltooldraganddrop.h"  #include "lltoolfocus.h"  #include "lltoolmgr.h" -#include "lltooldraganddrop.h"  #include "lltoolpie.h"  #include "lltracker.h"  #include "lltrans.h"  #include "llui.h" +#include "lluuid.h" +#include "llversioninfo.h"  #include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h"  #include "llviewerobjectlist.h"  #include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h"  #include "llviewerwindow.h"  #include "llvoavatarself.h"  #include "llvograss.h"  #include "llworld.h"  #include "pipeline.h" -#include "llspatialpartition.h" -#include "llappviewer.h" -#include "llstartup.h" -#include "llviewershadermgr.h" -#include "llfasttimer.h" -#include "llfloatertools.h" -#include "llviewertexturelist.h" -#include "llfocusmgr.h" -#include "llcubemap.h" -#include "llviewerregion.h" -#include "lldrawpoolwater.h" -#include "lldrawpoolbump.h" -#include "llpostprocess.h" -#include "llscenemonitor.h" -#include "llenvironment.h" -#include "llperfstats.h" +#include <boost/json.hpp> + +#include <filesystem> +#include <iomanip> +#include <sstream>  #include <glm/glm.hpp>  #include <glm/gtc/matrix_transform.hpp> @@ -127,6 +139,9 @@ void render_ui_3d();  void render_ui_2d();  void render_disconnected_background(); +void getProfileStatsContext(boost::json::object& stats); +std::string getProfileStatsFilename(); +  void display_startup()  {      if (   !gViewerWindow @@ -1027,8 +1042,87 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)      if (gShaderProfileFrame)      {          gShaderProfileFrame = false; -        LLGLSLShader::finishProfile(); +        boost::json::value stats{ boost::json::object_kind }; +        getProfileStatsContext(stats.as_object()); +        LLGLSLShader::finishProfile(stats); + +        auto report_name = getProfileStatsFilename(); +        std::ofstream outf(report_name); +        if (! outf) +        { +            LL_WARNS() << "Couldn't write to " << std::quoted(report_name) << LL_ENDL; +        } +        else +        { +            outf << stats; +            LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; +        } +    } +} + +void getProfileStatsContext(boost::json::object& stats) +{ +    // populate the context with info from LLFloaterAbout +    auto contextit = stats.emplace("context", +                                   LlsdToJson(LLAppViewer::instance()->getViewerInfo())).first; +    auto& context = contextit->value().as_object(); + +    // then add a few more things +    unsigned char unique_id[MAC_ADDRESS_BYTES]{}; +    LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); +    context.emplace("machine", stringize(LL::hexdump(unique_id, sizeof(unique_id)))); +    context.emplace("grid", LLGridManager::instance().getGrid()); +    LLViewerRegion* region = gAgent.getRegion(); +    if (region) +    { +        context.emplace("regionid", stringize(region->getRegionID())); +    } +    LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); +    if (parcel) +    { +        context.emplace("parcel", parcel->getName()); +        context.emplace("parcelid", parcel->getLocalID());      } +    context.emplace("time", LLDate::now().toHTTPDateString("%Y-%m-%dT%H:%M:%S")); +} + +std::string getProfileStatsFilename() +{ +    std::ostringstream basebuff; +    // viewer build +    basebuff << "profile.v" << LLVersionInfo::instance().getBuild(); +    // machine ID: zero-initialize unique_id in case LLMachineID fails +    unsigned char unique_id[MAC_ADDRESS_BYTES]{}; +    LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); +    basebuff << ".m" << LL::hexdump(unique_id, sizeof(unique_id)); +    // region ID +    LLViewerRegion *region = gAgent.getRegion(); +    basebuff << ".r" << (region? region->getRegionID() : LLUUID()); +    // local parcel ID +    LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); +    basebuff << ".p" << (parcel? parcel->getLocalID() : 0); +    // date/time -- omit seconds for now +    auto now = LLDate::now(); +    basebuff << ".t" << LLDate::now().toHTTPDateString("%Y-%m-%dT%H-%M-"); +    // put this candidate file in our logs directory +    auto base = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, basebuff.str()); +    S32 sec; +    now.split(nullptr, nullptr, nullptr, nullptr, nullptr, &sec); +    // Loop over finished filename, incrementing sec until we find one that +    // doesn't yet exist. Should rarely loop (only if successive calls within +    // same second), may produce (e.g.) sec==61, but avoids collisions and +    // preserves chronological filename sort order. +    std::string name; +    std::error_code ec; +    do +    { +        // base + missing 2-digit seconds, append ".json" +        // post-increment sec in case we have to try again +        name = stringize(base, std::setw(2), std::setfill('0'), sec++, ".json"); +    } while (std::filesystem::exists(fsyspath(name), ec)); +    // Ignoring ec means we might potentially return a name that does already +    // exist -- but if we can't check its existence, what more can we do? +    return name;  }  // WIP simplified copy of display() that does minimal work | 
