From 6c226ac96c8ba01e62ea1085177b2859b74b85d8 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Fri, 13 Sep 2024 16:01:05 -0400
Subject: Add context info to Develop->Render Tests->Frame Profile stats file.

Specifically, add the viewer version, the machine ID, the grid, the region
name and ID, the parcel name and ID and the timestamp. This is both richer and
less fragile than trying to extract that information from the generated
filename: e.g. we now have region and parcel names.

Instead of making `LLGLSLShader::finishProfile()` mess with file I/O, pass it
a reference to a `boost::json::value` to be filled in with statistics, if it's
a `boost::json::object`. Otherwise it's `boost::json::null`, meaning no report.

Make llviewerdisplay.cpp's `display()` function instantiate a `boost::json::value`
to pass to `finishProfile()`. That lets llviewerdisplay.cpp also set the
`"context"` entry, with a new `getProfileStatsContext()` function quite
similar to `getProfileStatsFilename()`.
---
 indra/llrender/llglslshader.cpp   | 21 ++++---------------
 indra/llrender/llglslshader.h     | 10 ++++-----
 indra/newview/llviewerdisplay.cpp | 44 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 56f1533708..6ba5463acd 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -41,8 +41,6 @@
 #include "OpenGL/OpenGL.h"
 #endif
 
-#include <fstream>
-
  // Print-print list of shader included source files that are linked together via glAttachShader()
  // i.e. On macOS / OSX the AMD GLSL linker will display an error if a varying is left in an undefined state.
 #define DEBUG_SHADER_INCLUDES 0
@@ -65,7 +63,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0;
 U32 LLGLSLShader::sTotalTrianglesDrawn = 0;
 U64 LLGLSLShader::sTotalSamplesDrawn = 0;
 U32 LLGLSLShader::sTotalBinds = 0;
-std::string LLGLSLShader::sDefaultReportName;
+boost::json::value LLGLSLShader::sDefaultStats;
 
 //UI shader -- declared here so llui_libtest will link properly
 LLGLSLShader    gUIProgram;
@@ -120,16 +118,16 @@ struct LLGLSLShaderCompareTimeElapsed
 };
 
 //static
-void LLGLSLShader::finishProfile(const std::string& report_name)
+void LLGLSLShader::finishProfile(boost::json::value& statsv)
 {
     sProfileEnabled = false;
 
-    if (! report_name.empty())
+    if (! statsv.is_null())
     {
         std::vector<LLGLSLShader*> sorted(sInstances.begin(), sInstances.end());
         std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed());
 
-        boost::json::object stats;
+        auto& stats = statsv.as_object();
         auto shadersit = stats.emplace("shaders", boost::json::array_kind).first;
         auto& shaders = shadersit->value().as_array();
         bool unbound = false;
@@ -174,17 +172,6 @@ void LLGLSLShader::finishProfile(const std::string& report_name)
                 }
             }
         }
-
-        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;
-        }
     }
 }
 
diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index a9b9bfafa8..2d669c70a9 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -170,7 +170,7 @@ public:
     static U32 sMaxGLTFNodes;
 
     static void initProfile();
-    static void finishProfile(const std::string& report_name=sDefaultReportName);
+    static void finishProfile(boost::json::value& stats=sDefaultStats);
 
     static void startProfile();
     static void stopProfile();
@@ -365,10 +365,10 @@ public:
 private:
     void unloadInternal();
     // This must be static because finishProfile() is called at least once
-    // within a __try block. If we default its report_name parameter to a
-    // temporary std::string, that temporary must be destroyed when the stack
-    // is unwound, which __try forbids.
-    static std::string sDefaultReportName;
+    // 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/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index fdfe477a6c..aad11d9372 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -138,6 +138,7 @@ void render_ui_3d();
 void render_ui_2d();
 void render_disconnected_background();
 
+void getProfileStatsContext(boost::json::object& stats);
 std::string getProfileStatsFilename();
 
 void display_startup()
@@ -1040,8 +1041,49 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)
     if (gShaderProfileFrame)
     {
         gShaderProfileFrame = false;
-        LLGLSLShader::finishProfile(getProfileStatsFilename());
+        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)
+{
+    auto contextit = stats.emplace("context", boost::json::object_kind).first;
+    auto& context = contextit->value().as_object();
+
+    auto& versionInfo = LLVersionInfo::instance();
+    context.emplace("channel", versionInfo.getChannel());
+    context.emplace("version", versionInfo.getVersion());
+    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("region", region->getName());
+        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()
-- 
cgit v1.2.3


From 439cfc97a81f221daaf8ba13aa5daa87e8511047 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Fri, 13 Sep 2024 16:32:04 -0400
Subject: Add script to compare a Frame Profile JSON stats file vs. baseline.

Extract `latest_file()` logic replicated in profile_pretty.py and
profile_csv.py out to logsdir.py, and use for new profile_cmp.py.
---
 scripts/perf/logsdir.py        |  46 ++++++++++++++++++
 scripts/perf/profile_cmp.py    | 104 +++++++++++++++++++++++++++++++++++++++++
 scripts/perf/profile_csv.py    |  24 +++-------
 scripts/perf/profile_pretty.py |  22 ++-------
 4 files changed, 160 insertions(+), 36 deletions(-)
 create mode 100644 scripts/perf/logsdir.py
 create mode 100644 scripts/perf/profile_cmp.py

diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py
new file mode 100644
index 0000000000..c8b498cf78
--- /dev/null
+++ b/scripts/perf/logsdir.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""\
+@file   logsdir.py
+@author Nat Goodspeed
+@date   2024-09-12
+@brief  Locate the Second Life logs directory for the current user on the
+        current platform.
+
+$LicenseInfo:firstyear=2024&license=viewerlgpl$
+Copyright (c) 2024, Linden Research, Inc.
+$/LicenseInfo$
+"""
+
+import os
+from pathlib import Path
+import platform
+
+class Error(Exception):
+    pass
+
+# logic used by SLVersionChecker
+def logsdir():
+    app = 'SecondLife'
+    system = platform.system()
+    if (system == 'Darwin'):
+        base_dir = os.path.join(os.path.expanduser('~'),
+                                'Library','Application Support',app)
+    elif (system == 'Linux'): 
+        base_dir = os.path.join(os.path.expanduser('~'),
+                                '.' + app.lower())
+    elif (system == 'Windows'):
+        appdata = os.getenv('APPDATA')
+        base_dir = os.path.join(appdata, app)
+    else:
+        raise ValueError("Unsupported platform '%s'" % system)
+
+    return os.path.join(base_dir, 'logs')
+
+def latest_file(dirpath, pattern):
+    files = Path(dirpath).glob(pattern)
+    sort = [(p.stat().st_mtime, p) for p in files if p.is_file()]
+    sort.sort(reverse=True)
+    try:
+        return sort[0][1]
+    except IndexError:
+        raise Error(f'No {pattern} files in {dirpath}')
diff --git a/scripts/perf/profile_cmp.py b/scripts/perf/profile_cmp.py
new file mode 100644
index 0000000000..9dbfa3145b
--- /dev/null
+++ b/scripts/perf/profile_cmp.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""\
+@file   profile_cmp.py
+@author Nat Goodspeed
+@date   2024-09-13
+@brief  Compare a frame profile stats file with a similar baseline file.
+
+$LicenseInfo:firstyear=2024&license=viewerlgpl$
+Copyright (c) 2024, Linden Research, Inc.
+$/LicenseInfo$
+"""
+
+from datetime import datetime
+import json
+from logsdir import Error, latest_file, logsdir
+from pathlib import Path
+import sys
+
+# variance that's ignorable
+DEFAULT_EPSILON = 0.03          # 3%
+
+def compare(baseline, test, epsilon=DEFAULT_EPSILON):
+    if Path(baseline).samefile(test):
+        print(f'{baseline} same as\n{test}\nAnalysis moot.')
+        return
+
+    with open(baseline) as inf:
+        bdata = json.load(inf)
+    with open(test) as inf:
+        tdata = json.load(inf)
+    print(f'baseline {baseline}\ntestfile {test}')
+
+    for k, tv in tdata['context'].items():
+        bv = bdata['context'].get(k)
+        if bv != tv:
+            print(f'baseline {k}={bv} vs.\ntestfile {k}={tv}')
+
+    btime = bdata['context'].get('time')
+    ttime = tdata['context'].get('time')
+    if btime and ttime:
+        print('testfile newer by',
+              datetime.fromisoformat(ttime) - datetime.fromisoformat(btime))
+
+    # The following ignores totals and unused shaders, except to the extent
+    # that some shaders were used in the baseline but not in the recent test
+    # or vice-versa. While the viewer considers that a shader has been used if
+    # 'binds' is nonzero, we exclude any whose 'time' is zero to avoid zero
+    # division.
+    bshaders = {s['name']: s for s in bdata['shaders'] if s['time'] and s['samples']}
+    tshaders = {s['name']: s for s in tdata['shaders'] if s['time']}
+
+    bothshaders = set(bshaders).intersection(tshaders)
+    deltas = []
+    for shader in bothshaders:
+        bshader = bshaders[shader]
+        tshader = tshaders[shader]
+        bthruput = bshader['samples']/bshader['time']
+        tthruput = tshader['samples']/tshader['time']
+        delta = (tthruput - bthruput)/bthruput
+        if abs(delta) > epsilon:
+            deltas.append((delta, shader, bthruput, tthruput))
+
+    # descending order of performance gain
+    deltas.sort(reverse=True)
+    print(f'{len(deltas)} shaders showed nontrivial performance differences '
+          '(millon samples/sec):')
+    namelen = max(len(s[1]) for s in deltas) if deltas else 0
+    for delta, shader, bthruput, tthruput in deltas:
+        print(f'  {shader.rjust(namelen)} {delta*100:6.1f}% '
+              f'{bthruput/1000000:8.2f} -> {tthruput/1000000:8.2f}')
+
+    tunused = set(bshaders).difference(tshaders)
+    print(f'{len(tunused)} baseline shaders not used in test:')
+    for s in tunused:
+        print(f'  {s}')
+    bunused = set(tshaders).difference(bshaders)
+    print(f'{len(bunused)} shaders newly used in test:')
+    for s in bunused:
+        print(f'  {s}')
+
+def main(*raw_args):
+    from argparse import ArgumentParser
+    parser = ArgumentParser(description="""
+%(prog)s compares a baseline JSON file from Develop -> Render Tests -> Frame
+Profile to another such file from a more recent test. It identifies shaders
+that have gained and lost in throughput.
+""")
+    parser.add_argument('-e', '--epsilon', type=float, default=int(DEFAULT_EPSILON*100),
+                        help="""percent variance considered ignorable (default %(default)s%%)""")
+    parser.add_argument('baseline',
+                        help="""baseline profile filename to compare against""")
+    parser.add_argument('test', nargs='?',
+                        help="""test profile filename to compare
+                        (default is most recent)""")
+    args = parser.parse_args(raw_args)
+    compare(args.baseline,
+            args.test or latest_file(logsdir(), 'profile.*.json'),
+            epsilon=(args.epsilon / 100.))
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main(*sys.argv[1:]))
+    except (Error, OSError, json.JSONDecodeError) as err:
+        sys.exit(str(err))
diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py
index 273e3b7434..7a6b2b338e 100644
--- a/scripts/perf/profile_csv.py
+++ b/scripts/perf/profile_csv.py
@@ -10,17 +10,16 @@ Copyright (c) 2024, Linden Research, Inc.
 $/LicenseInfo$
 """
 
-import logsdir
 import json
-from pathlib import Path
+from logsdir import Error, latest_file, logsdir
 import sys
 
-class Error(Exception):
-    pass
-
 def convert(path, totals=True, unused=True, file=sys.stdout):
     with open(path) as inf:
         data = json.load(inf)
+    # print path to sys.stderr in case user is redirecting stdout
+    print(path, file=sys.stderr)
+
     print('"name", "file1", "file2", "time", "binds", "samples", "triangles"', file=file)
 
     if totals:
@@ -51,19 +50,8 @@ shaders list to full shaders lines.
                         help="""profile filename to convert (default is most recent)""")
 
     args = parser.parse_args(raw_args)
-    if not args.path:
-        logs = logsdir.logsdir()
-        profiles = Path(logs).glob('profile.*.json')
-        sort = [(p.stat().st_mtime, p) for p in profiles]
-        sort.sort(reverse=True)
-        try:
-            args.path = sort[0][1]
-        except IndexError:
-            raise Error(f'No profile.*.json files in {logs}')
-        # print path to sys.stderr in case user is redirecting stdout
-        print(args.path, file=sys.stderr)
-
-    convert(args.path, totals=args.totals, unused=args.unused)
+    convert(args.path or latest_file(logsdir(), 'profile.*.json'),
+            totals=args.totals, unused=args.unused)
 
 if __name__ == "__main__":
     try:
diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py
index 15b6efd94d..405b14b373 100644
--- a/scripts/perf/profile_pretty.py
+++ b/scripts/perf/profile_pretty.py
@@ -10,17 +10,15 @@ Copyright (c) 2024, Linden Research, Inc.
 $/LicenseInfo$
 """
 
-import logsdir
 import json
-from pathlib import Path
+from logsdir import Error, latest_file, logsdir
 import sys
 
-class Error(Exception):
-    pass
-
 def pretty(path):
     with open(path) as inf:
         data = json.load(inf)
+    # print path to sys.stderr in case user is redirecting stdout
+    print(path, file=sys.stderr)
     json.dump(data, sys.stdout, indent=4)
 
 def main(*raw_args):
@@ -33,19 +31,7 @@ The file produced by the viewer is a single dense line of JSON.
                         help="""profile filename to pretty-print (default is most recent)""")
 
     args = parser.parse_args(raw_args)
-    if not args.path:
-        logs = logsdir.logsdir()
-        profiles = Path(logs).glob('profile.*.json')
-        sort = [(p.stat().st_mtime, p) for p in profiles]
-        sort.sort(reverse=True)
-        try:
-            args.path = sort[0][1]
-        except IndexError:
-            raise Error(f'No profile.*.json files in {logs}')
-        # print path to sys.stderr in case user is redirecting stdout
-        print(args.path, file=sys.stderr)
-
-    pretty(args.path)
+    pretty(args.path or latest_file(logsdir(), 'profile.*.json'))
 
 if __name__ == "__main__":
     try:
-- 
cgit v1.2.3


From 30238772354d4a99c2867f35b7e87c4f1a748222 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Fri, 13 Sep 2024 16:50:33 -0400
Subject: Zap stray trailing space.

---
 scripts/perf/logsdir.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py
index c8b498cf78..5ab45a28b6 100644
--- a/scripts/perf/logsdir.py
+++ b/scripts/perf/logsdir.py
@@ -25,7 +25,7 @@ def logsdir():
     if (system == 'Darwin'):
         base_dir = os.path.join(os.path.expanduser('~'),
                                 'Library','Application Support',app)
-    elif (system == 'Linux'): 
+    elif (system == 'Linux'):
         base_dir = os.path.join(os.path.expanduser('~'),
                                 '.' + app.lower())
     elif (system == 'Windows'):
-- 
cgit v1.2.3


From f4b65638879c10c832b3bb8448f82001106ffd11 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Mon, 16 Sep 2024 17:25:48 -0400
Subject: Add LLFloaterAbout info (esp. GPU info) to Frame Profile stats dump

With the About info added, `getProfileStatsContext()` need not redundantly add
`"channel"`, `"version"` or `"region"`.

Slightly improve the efficiency of `LlsdToJson()` and `LlsdFromJson()` by
preallocating the known size of the source array or map. (Unfortunately the C++
`LLSD` class offers us no way to preallocate a map.)

In `LLAppViewer::getViewerInfo()`, avoid immediate successive calls to
`gAgent.getRegion()`.
---
 indra/llcommon/llsdjson.cpp       | 15 +++++++++++++--
 indra/newview/llappviewer.cpp     |  4 ++--
 indra/newview/llviewerdisplay.cpp | 10 +++++-----
 3 files changed, 20 insertions(+), 9 deletions(-)

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/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 789aaab70d..77797ad5f0 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3351,10 +3351,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/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index aad11d9372..f722d0bd1d 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -58,6 +58,7 @@
 #include "llpostprocess.h"
 #include "llrender.h"
 #include "llscenemonitor.h"
+#include "llsdjson.h"
 #include "llselectmgr.h"
 #include "llsky.h"
 #include "llspatialpartition.h"
@@ -1061,12 +1062,12 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot)
 
 void getProfileStatsContext(boost::json::object& stats)
 {
-    auto contextit = stats.emplace("context", boost::json::object_kind).first;
+    // populate the context with info from LLFloaterAbout
+    auto contextit = stats.emplace("context",
+                                   LlsdToJson(LLAppViewer::instance()->getViewerInfo())).first;
     auto& context = contextit->value().as_object();
 
-    auto& versionInfo = LLVersionInfo::instance();
-    context.emplace("channel", versionInfo.getChannel());
-    context.emplace("version", versionInfo.getVersion());
+    // 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))));
@@ -1074,7 +1075,6 @@ void getProfileStatsContext(boost::json::object& stats)
     LLViewerRegion* region = gAgent.getRegion();
     if (region)
     {
-        context.emplace("region", region->getName());
         context.emplace("regionid", stringize(region->getRegionID()));
     }
     LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel();
-- 
cgit v1.2.3