summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMnikolenko Productengine <mnikolenko@productengine.com>2026-05-26 18:34:37 +0300
committerMnikolenko Productengine <mnikolenko@productengine.com>2026-05-27 14:50:41 +0300
commitf27285d02b0f9ef4ba18d37b2d1c743bcfe4fdec (patch)
treefb43b56095aa756dd29465aac5d01ef4277719ba
parent412509181814baad066425bd1bfdfe3d50b62324 (diff)
#5368 add Leap API to query statistics
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/llinventorymodel.cpp12
-rw-r--r--indra/newview/llinventorymodel.h4
-rw-r--r--indra/newview/llinventorymodelbackgroundfetch.cpp26
-rw-r--r--indra/newview/llinventorymodelbackgroundfetch.h6
-rw-r--r--indra/newview/llstatslistener.cpp197
-rw-r--r--indra/newview/llstatslistener.h41
-rw-r--r--indra/newview/llviewerstats.h37
-rw-r--r--indra/newview/llviewerwindow.cpp2
-rw-r--r--indra/newview/llviewerwindow.h2
10 files changed, 317 insertions, 12 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 8e5e146c03..e4db686776 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -739,6 +739,7 @@ set(viewer_SOURCE_FILES
llwebprofile.cpp
llwind.cpp
llwindowlistener.cpp
+ llstatslistener.cpp
llwlhandlers.cpp
llworld.cpp
llworldmap.cpp
@@ -1412,6 +1413,7 @@ set(viewer_HEADER_FILES
llwebprofile.h
llwind.h
llwindowlistener.h
+ llstatslistener.h
llwlhandlers.h
llworld.h
llworldmap.h
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 8b9aad4641..604a66c241 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -433,6 +433,8 @@ LLInventoryModel::LLInventoryModel()
: // These are now ordered, keep them that way.
mBacklinkMMap(),
mIsAgentInvUsable(false),
+ mLibrarySkeletonLoadTime(0.f),
+ mAgentSkeletonLoadTime(0.f),
mRootFolderID(),
mLibraryRootFolderID(),
mLibraryOwnerID(),
@@ -3001,6 +3003,16 @@ bool LLInventoryModel::loadSkeleton(
<< " after " << timer.getElapsedTimeF32() << " seconds."
<< LL_ENDL;
+ const F32 elapsed = timer.getElapsedTimeF32();
+ if (owner_id == mLibraryOwnerID)
+ {
+ mLibrarySkeletonLoadTime = elapsed;
+ }
+ else
+ {
+ mAgentSkeletonLoadTime = elapsed;
+ }
+
return rv;
}
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index 05ada9121a..3aaba69a60 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -158,8 +158,12 @@ protected:
public:
// The inventory model usage is sensitive to the initial construction of the model
bool isInventoryUsable() const;
+ F32 getLibrarySkeletonLoadTime() const { return mLibrarySkeletonLoadTime; }
+ F32 getAgentSkeletonLoadTime() const { return mAgentSkeletonLoadTime; }
private:
bool mIsAgentInvUsable; // used to handle an invalid inventory state
+ F32 mLibrarySkeletonLoadTime;
+ F32 mAgentSkeletonLoadTime;
// One-time initialization of HTTP system.
void initHttpRequest();
diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp
index 82eefb50ac..f68eb7f4a5 100644
--- a/indra/newview/llinventorymodelbackgroundfetch.cpp
+++ b/indra/newview/llinventorymodelbackgroundfetch.cpp
@@ -192,6 +192,8 @@ LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch():
mFetchCount(0),
mLastFetchCount(0),
mFetchFolderCount(0),
+ mInitialFetchDuration(0.f),
+ mInitialFetchDurationCaptured(false),
mAllRecursiveFoldersFetched(false),
mRecursiveInventoryFetchStarted(false),
mRecursiveLibraryFetchStarted(false),
@@ -199,6 +201,14 @@ LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch():
mMinTimeBetweenFetches(0.3f)
{}
+void LLInventoryModelBackgroundFetch::markFetchStarted()
+{
+ if (!mBackgroundFetchActive)
+ {
+ mFetchStartTimer.reset();
+ }
+}
+
LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch()
{
gIdleCallbacks.deleteFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
@@ -289,6 +299,7 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive)
// it's a folder, do a bulk fetch
LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL;
+ markFetchStarted();
mBackgroundFetchActive = true;
mFolderFetchActive = true;
EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT;
@@ -376,6 +387,7 @@ void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id,
{
if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id)
{
+ markFetchStarted();
mBackgroundFetchActive = true;
mFolderFetchActive = true;
@@ -403,6 +415,7 @@ void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, b
{
if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id)
{
+ markFetchStarted();
mBackgroundFetchActive = true;
if (forced)
{
@@ -447,6 +460,7 @@ void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id,
});
// start idle loop to track completion
+ markFetchStarted();
mBackgroundFetchActive = true;
mFolderFetchActive = true;
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
@@ -489,6 +503,7 @@ void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback)
});
// start idle loop to track completion
+ markFetchStarted();
mBackgroundFetchActive = true;
mFolderFetchActive = true;
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr);
@@ -496,6 +511,7 @@ void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback)
void LLInventoryModelBackgroundFetch::findLostItems()
{
+ markFetchStarted();
mBackgroundFetchActive = true;
mFolderFetchActive = true;
mFetchFolderQueue.emplace_back(LLUUID::null, FT_RECURSIVE);
@@ -515,6 +531,11 @@ void LLInventoryModelBackgroundFetch::setAllFoldersFetched()
mFolderFetchActive = false;
if (isBulkFetchProcessingComplete())
{
+ if (mAllRecursiveFoldersFetched && !mInitialFetchDurationCaptured)
+ {
+ mInitialFetchDuration = mFetchStartTimer.getElapsedTimeF32();
+ mInitialFetchDurationCaptured = true;
+ }
mBackgroundFetchActive = false;
}
@@ -843,6 +864,11 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis()
if (isBulkFetchProcessingComplete())
{
+ if (mAllRecursiveFoldersFetched && !mInitialFetchDurationCaptured)
+ {
+ mInitialFetchDuration = mFetchStartTimer.getElapsedTimeF32();
+ mInitialFetchDurationCaptured = true;
+ }
mBackgroundFetchActive = false;
}
}
diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h
index ef6fa06e9f..6a5e0f66ff 100644
--- a/indra/newview/llinventorymodelbackgroundfetch.h
+++ b/indra/newview/llinventorymodelbackgroundfetch.h
@@ -70,6 +70,7 @@ public:
bool inventoryFetchStarted() const;
bool inventoryFetchCompleted() const;
bool inventoryFetchInProgress() const;
+ F32 getInitialFetchDuration() const { return mInitialFetchDuration; }
void findLostItems();
void incrFetchCount(S32 fetching);
@@ -120,6 +121,8 @@ protected:
bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const;
private:
+ void markFetchStarted();
+
bool mRecursiveInventoryFetchStarted;
bool mRecursiveLibraryFetchStarted;
bool mRecursiveMarketplaceFetchStarted; // AIS3 specific
@@ -133,6 +136,9 @@ private:
S32 mLastFetchCount; // for debug
S32 mFetchFolderCount;
+ LLFrameTimer mFetchStartTimer;
+ F32 mInitialFetchDuration;
+ bool mInitialFetchDurationCaptured;
LLFrameTimer mFetchTimer;
F32 mMinTimeBetweenFetches;
fetch_queue_t mFetchFolderQueue;
diff --git a/indra/newview/llstatslistener.cpp b/indra/newview/llstatslistener.cpp
new file mode 100644
index 0000000000..47b025cd8b
--- /dev/null
+++ b/indra/newview/llstatslistener.cpp
@@ -0,0 +1,197 @@
+/**
+ * @file llstatslistener.cpp
+ * @brief EventAPI interface for querying performance statistics
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2026, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llstatslistener.h"
+
+#include "llinventorymodel.h"
+#include "llinventorymodelbackgroundfetch.h"
+#include "llimagegl.h"
+#include "llviewerstats.h"
+
+namespace
+{
+template <typename STAT, typename EXTRACTOR>
+LLSD collectPeriodArray(LLTrace::PeriodicRecording& recording,
+ size_t num_periods,
+ const STAT& stat,
+ EXTRACTOR extractor)
+{
+ LLSD values = LLSD::emptyArray();
+ for (size_t i = 1; i <= num_periods; ++i)
+ {
+ LLTrace::Recording& period = recording.getPrevRecording(i);
+ if (period.hasValue(stat))
+ {
+ values.append((F64)extractor(period, stat));
+ }
+ }
+ return values;
+}
+
+template <typename STAT, typename EXTRACTOR>
+void setPeriodArray(LLSD& out,
+ const char* key,
+ LLTrace::Recording& last,
+ LLTrace::PeriodicRecording& recording,
+ size_t num_periods,
+ const STAT& stat,
+ EXTRACTOR extractor)
+{
+ if (last.hasValue(stat))
+ {
+ out[key] = collectPeriodArray(recording, num_periods, stat, extractor);
+ }
+}
+}
+
+LLStatsListener::LLStatsListener()
+ : LLEventAPI("LLStats", "Query performance statistics")
+{
+ add("getPerfData",
+ "Get performance data from the frame recording buffer, plus texture memory\n"
+ "and inventory loading timing information.\n"
+ "Reply contains [\"stats\"] with nested group maps.",
+ &LLStatsListener::getPerfData,
+ llsd::map("reply", LLSD()));
+}
+
+void LLStatsListener::getPerfData(LLSD const & evt)
+{
+ LLEventAPI::Response response(LLSD(), evt);
+
+ // get_frame_recording() is a PeriodicRecording with 200 periods
+ LLTrace::PeriodicRecording& recording = LLTrace::get_frame_recording();
+ LLTrace::Recording& last = recording.getLastRecording();
+
+ size_t num_periods = recording.getNumRecordedPeriods();
+ F64 total_duration = recording.getDuration().value();
+
+ LLSD stats;
+ stats["total_periods_duration"] = total_duration;
+ stats["num_periods"] = (LLSD::Integer)num_periods;
+
+ LLSD frametime;
+
+ setPeriodArray(frametime, "fps", last, recording, num_periods, LLStatViewer::FPS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getPerSec(stat); });
+
+ setPeriodArray(frametime, "frame_time_ms", last, recording, num_periods, LLStatViewer::FRAMETIME,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ setPeriodArray(frametime, "frame_time_jitter_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Normalized jitter - scalar samples updated per frame
+ // report the current (last) value since they are session/period rolling stats
+ if (last.hasValue(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION))
+ {
+ frametime["normalized_jitter_session"] = last.getLastValue(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION);
+ }
+ if (last.hasValue(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD))
+ {
+ frametime["normalized_jitter_period"] = last.getLastValue(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD);
+ }
+ if (last.hasValue(LLStatViewer::NFTV))
+ {
+ frametime["normalized_frametime_variation"] = last.getLastValue(LLStatViewer::NFTV);
+ }
+
+ // Jitter event minute counters: running avg per minute and count in last completed minute
+ // These are already minute-window aggregates, so sending single value, not arrays
+ if (last.hasValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE))
+ {
+ frametime["frame_time_jitter_events_per_minute"] = (F64)last.getLastValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE);
+ }
+ if (last.hasValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE))
+ {
+ frametime["frame_time_jitter_events_last_minute"] = (F64)last.getLastValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE);
+ }
+
+ // Jitter percentiles / cumulative
+ setPeriodArray(frametime, "jitter_cumulative_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_CUMULATIVE,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "jitter_99th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_99TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "jitter_95th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_95TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "jitter_stddev_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_STDDEV,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Frame time percentiles
+ setPeriodArray(frametime, "frametime_99th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_99TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "frametime_95th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_95TH,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(frametime, "frametime_stddev_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_STDDEV,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Frametime jitter event count
+ setPeriodArray(frametime, "frametime_jitter_events", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_EVENTS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ stats["frametime"] = frametime;
+
+ LLSD other;
+
+ // Packet loss
+ setPeriodArray(other, "packet_loss_percent", last, recording, num_periods, LLStatViewer::PACKETS_LOST_PERCENT,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Sim ping
+ setPeriodArray(other, "sim_ping_ms", last, recording, num_periods, LLStatViewer::SIM_PING,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ // Triangle rendering
+ setPeriodArray(other, "ktris_per_frame", last, recording, num_periods, LLStatViewer::TRIANGLES_DRAWN,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getSum(stat); });
+ setPeriodArray(other, "ktris_per_sec", last, recording, num_periods, LLStatViewer::TRIANGLES_DRAWN,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getPerSec(stat); });
+
+ // Object counts
+ setPeriodArray(other, "num_objects", last, recording, num_periods, LLStatViewer::NUM_OBJECTS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+ setPeriodArray(other, "num_active_objects", last, recording, num_periods, LLStatViewer::NUM_ACTIVE_OBJECTS,
+ [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); });
+
+ stats["other"] = other;
+
+ // texture memory usage
+ LLSD memory;
+ memory["texture_bytes_alloc_mb"] = (F64)LLImageGL::getTextureBytesAllocated() / 1024.0 / 512.0;
+ stats["memory"] = memory;
+
+ LLSD inventory;
+ // library skeleton cache load duration
+ inventory["skeleton_load_time_library_seconds"] = gInventory.getLibrarySkeletonLoadTime();
+ // agent skeleton cache load duration
+ inventory["skeleton_load_time_agent_seconds"] = gInventory.getAgentSkeletonLoadTime();
+ // initial recursive inventory/library background fetch duration
+ inventory["initial_fetch_time_seconds"] = LLInventoryModelBackgroundFetch::instance().getInitialFetchDuration();
+ inventory["fetch_completed"] = LLInventoryModelBackgroundFetch::instance().isEverythingFetched();
+ stats["inventory"] = inventory;
+
+ response["stats"] = stats;
+}
diff --git a/indra/newview/llstatslistener.h b/indra/newview/llstatslistener.h
new file mode 100644
index 0000000000..77777034c7
--- /dev/null
+++ b/indra/newview/llstatslistener.h
@@ -0,0 +1,41 @@
+/**
+ * @file llstatslistener.h
+ * @brief EventAPI interface for querying performance statistics
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2026, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLSTATSLISTENER_H
+#define LL_LLSTATSLISTENER_H
+
+#include "lleventapi.h"
+
+class LLStatsListener : public LLEventAPI
+{
+public:
+ LLStatsListener();
+
+private:
+ void getPerfData(LLSD const & evt);
+};
+
+#endif // LL_LLSTATSLISTENER_H
diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h
index 8ec0dd0024..f670dadc27 100644
--- a/indra/newview/llviewerstats.h
+++ b/indra/newview/llviewerstats.h
@@ -209,29 +209,42 @@ extern SimMeasurement<F64Megabytes > SIM_PHYSICS_MEM;
extern LLTrace::SampleStatHandle<F64Milliseconds > FRAMETIME_JITTER,
- SIM_PING;
+ FRAMETIME,
+ SIM_PING,
+ FRAMETIME_JITTER_99TH,
+ FRAMETIME_JITTER_95TH,
+ FRAMETIME_99TH,
+ FRAMETIME_95TH,
+ FRAMETIME_JITTER_CUMULATIVE,
+ FRAMETIME_JITTER_STDDEV,
+ FRAMETIME_STDDEV;
+
+extern LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS,
+ FRAMETIME_JITTER_EVENTS_PER_MINUTE,
+ FRAMETIME_JITTER_EVENTS_LAST_MINUTE;
+
+extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION;
+extern LLTrace::SampleStatHandle<F64> NFTV;
+extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD;
extern LLTrace::EventStatHandle<LLUnit<F64, LLUnits::Meters> > AGENT_POSITION_SNAP;
extern LLTrace::EventStatHandle<> LOADING_WEARABLES_LONG_DELAY;
extern LLTrace::EventStatHandle<F64Milliseconds > REGION_CROSSING_TIME,
- FRAME_STACKTIME,
- UPDATE_STACKTIME,
- NETWORK_STACKTIME,
- IMAGE_STACKTIME,
- REBUILD_STACKTIME,
- RENDER_STACKTIME;
+ FRAME_STACKTIME,
+ UPDATE_STACKTIME,
+ NETWORK_STACKTIME,
+ IMAGE_STACKTIME,
+ REBUILD_STACKTIME,
+ RENDER_STACKTIME;
extern LLTrace::EventStatHandle<F64Seconds > AVATAR_EDIT_TIME,
- TOOLBOX_TIME,
- MOUSELOOK_TIME;
+ TOOLBOX_TIME,
+ MOUSELOOK_TIME;
extern LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT_RATE;
-extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION;
-extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD;
-
extern LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST, WEBRTC_PACKETS_IN_RECEIVED, WEBRTC_PACKETS_OUT_SENT, WEBRTC_PACKETS_OUT_LOST;
extern LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT, WEBRTC_JITTER_IN, WEBRTC_LATENCY, WEBRTC_UPLOAD_BANDWIDTH, WEBRTC_JITTER_BUFFER;
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 0f3f24d1af..dea96e2012 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -203,6 +203,7 @@
#include "llwindowlistener.h"
#include "llviewerwindowlistener.h"
+#include "llstatslistener.h"
#include "llcleanup.h"
#if LL_WINDOWS
@@ -1886,6 +1887,7 @@ LLViewerWindow::LLViewerWindow(const Params& p)
LLWindowListener::KeyboardGetter getter = [](){ return gKeyboard; };
mWindowListener = std::make_unique<LLWindowListener>(this, getter);
mViewerWindowListener = std::make_unique<LLViewerWindowListener>(this);
+ mStatsListener = std::make_unique<LLStatsListener>();
mSystemChannel.reset(new LLNotificationChannel("System", "Visible", LLNotificationFilters::includeEverything));
mCommunicationChannel.reset(new LLCommunicationChannel("Communication", "Visible"));
diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h
index ec28a3fc4a..5f1afe2cbe 100644
--- a/indra/newview/llviewerwindow.h
+++ b/indra/newview/llviewerwindow.h
@@ -66,6 +66,7 @@ class LLWindow;
class LLRootView;
class LLWindowListener;
class LLViewerWindowListener;
+class LLStatsListener;
class LLVOPartGroup;
class LLPopupView;
class LLCubeMap;
@@ -563,6 +564,7 @@ private:
std::unique_ptr<LLWindowListener> mWindowListener;
std::unique_ptr<LLViewerWindowListener> mViewerWindowListener;
+ std::unique_ptr<LLStatsListener> mStatsListener;
// Object temporarily hovered over while dragging
LLPointer<LLViewerObject> mDragHoveredObject;