/indra/newview/llviewerassetstats.cpp
C++ | 612 lines | 381 code | 93 blank | 138 comment | 39 complexity | 85a587662f5bc0fc2d76f0b71b5a05b1 MD5 | raw file
Possible License(s): LGPL-2.1
- /**
- * @file llviewerassetstats.cpp
- * @brief
- *
- * $LicenseInfo:firstyear=2010&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, 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 "llviewerprecompiledheaders.h"
- #include "llviewerassetstats.h"
- #include "llregionhandle.h"
- #include "stdtypes.h"
- /*
- * Classes and utility functions for per-thread and per-region
- * asset and experiential metrics to be aggregated grid-wide.
- *
- * The basic metrics grouping is LLViewerAssetStats::PerRegionStats.
- * This provides various counters and simple statistics for asset
- * fetches binned into a few categories. One of these is maintained
- * for each region encountered and the 'current' region is available
- * as a simple reference. Each thread (presently two) interested
- * in participating in these stats gets an instance of the
- * LLViewerAssetStats class so that threads are completely
- * independent.
- *
- * The idea of a current region is used for simplicity and speed
- * of categorization. Each metrics event could have taken a
- * region uuid argument resulting in a suitable lookup. Arguments
- * against this design include:
- *
- * - Region uuid not trivially available to caller.
- * - Cost (cpu, disruption in real work flow) too high.
- * - Additional precision not really meaningful.
- *
- * By itself, the LLViewerAssetStats class is thread- and
- * viewer-agnostic and can be used anywhere without assumptions
- * of global pointers and other context. For the viewer,
- * a set of free functions are provided in the namespace
- * LLViewerAssetStatsFF which *do* implement viewer-native
- * policies about per-thread globals and will do correct
- * defensive tests of same.
- *
- * References
- *
- * Project:
- * <TBD>
- *
- * Test Plan:
- * <TBD>
- *
- * Jiras:
- * <TBD>
- *
- * Unit Tests:
- * indra/newview/tests/llviewerassetstats_test.cpp
- *
- */
- // ------------------------------------------------------
- // Global data definitions
- // ------------------------------------------------------
- LLViewerAssetStats * gViewerAssetStatsMain(0);
- LLViewerAssetStats * gViewerAssetStatsThread1(0);
- // ------------------------------------------------------
- // Local declarations
- // ------------------------------------------------------
- namespace
- {
- static LLViewerAssetStats::EViewerAssetCategories
- asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool is_temp);
- }
- // ------------------------------------------------------
- // LLViewerAssetStats::PerRegionStats struct definition
- // ------------------------------------------------------
- void
- LLViewerAssetStats::PerRegionStats::reset()
- {
- for (int i(0); i < LL_ARRAY_SIZE(mRequests); ++i)
- {
- mRequests[i].mEnqueued.reset();
- mRequests[i].mDequeued.reset();
- mRequests[i].mResponse.reset();
- }
- mFPS.reset();
-
- mTotalTime = 0;
- mStartTimestamp = LLViewerAssetStatsFF::get_timestamp();
- }
- void
- LLViewerAssetStats::PerRegionStats::merge(const LLViewerAssetStats::PerRegionStats & src)
- {
- // mRegionHandle, mTotalTime, mStartTimestamp are left alone.
-
- // mFPS
- if (src.mFPS.getCount() && mFPS.getCount())
- {
- mFPS.merge(src.mFPS);
- }
- // Requests
- for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i)
- {
- mRequests[i].mEnqueued.merge(src.mRequests[i].mEnqueued);
- mRequests[i].mDequeued.merge(src.mRequests[i].mDequeued);
- mRequests[i].mResponse.merge(src.mRequests[i].mResponse);
- }
- }
- void
- LLViewerAssetStats::PerRegionStats::accumulateTime(duration_t now)
- {
- mTotalTime += (now - mStartTimestamp);
- mStartTimestamp = now;
- }
- // ------------------------------------------------------
- // LLViewerAssetStats class definition
- // ------------------------------------------------------
- LLViewerAssetStats::LLViewerAssetStats()
- : mRegionHandle(U64(0))
- {
- reset();
- }
- LLViewerAssetStats::LLViewerAssetStats(const LLViewerAssetStats & src)
- : mRegionHandle(src.mRegionHandle),
- mResetTimestamp(src.mResetTimestamp)
- {
- const PerRegionContainer::const_iterator it_end(src.mRegionStats.end());
- for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it)
- {
- mRegionStats[it->first] = new PerRegionStats(*it->second);
- }
- mCurRegionStats = mRegionStats[mRegionHandle];
- }
- void
- LLViewerAssetStats::reset()
- {
- // Empty the map of all region stats
- mRegionStats.clear();
- // If we have a current stats, reset it, otherwise, as at construction,
- // create a new one as we must always have a current stats block.
- if (mCurRegionStats)
- {
- mCurRegionStats->reset();
- }
- else
- {
- mCurRegionStats = new PerRegionStats(mRegionHandle);
- }
- // And add reference to map
- mRegionStats[mRegionHandle] = mCurRegionStats;
- // Start timestamp consistent with per-region collector
- mResetTimestamp = mCurRegionStats->mStartTimestamp;
- }
- void
- LLViewerAssetStats::setRegion(region_handle_t region_handle)
- {
- if (region_handle == mRegionHandle)
- {
- // Already active, ignore.
- return;
- }
- // Get duration for current set
- const duration_t now = LLViewerAssetStatsFF::get_timestamp();
- mCurRegionStats->accumulateTime(now);
- // Prepare new set
- PerRegionContainer::iterator new_stats = mRegionStats.find(region_handle);
- if (mRegionStats.end() == new_stats)
- {
- // Haven't seen this region_id before, create a new block and make it current.
- mCurRegionStats = new PerRegionStats(region_handle);
- mRegionStats[region_handle] = mCurRegionStats;
- }
- else
- {
- mCurRegionStats = new_stats->second;
- }
- mCurRegionStats->mStartTimestamp = now;
- mRegionHandle = region_handle;
- }
- void
- LLViewerAssetStats::recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
-
- ++(mCurRegionStats->mRequests[int(eac)].mEnqueued);
- }
-
- void
- LLViewerAssetStats::recordGetDequeued(LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
- ++(mCurRegionStats->mRequests[int(eac)].mDequeued);
- }
- void
- LLViewerAssetStats::recordGetServiced(LLViewerAssetType::EType at, bool with_http, bool is_temp, duration_t duration)
- {
- const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
- mCurRegionStats->mRequests[int(eac)].mResponse.record(duration);
- }
- void
- LLViewerAssetStats::recordFPS(F32 fps)
- {
- mCurRegionStats->mFPS.record(fps);
- }
- LLSD
- LLViewerAssetStats::asLLSD(bool compact_output)
- {
- // Top-level tags
- static const LLSD::String tags[EVACCount] =
- {
- LLSD::String("get_texture_temp_http"),
- LLSD::String("get_texture_temp_udp"),
- LLSD::String("get_texture_non_temp_http"),
- LLSD::String("get_texture_non_temp_udp"),
- LLSD::String("get_wearable_udp"),
- LLSD::String("get_sound_udp"),
- LLSD::String("get_gesture_udp"),
- LLSD::String("get_other")
- };
- // Stats Group Sub-tags.
- static const LLSD::String enq_tag("enqueued");
- static const LLSD::String deq_tag("dequeued");
- static const LLSD::String rcnt_tag("resp_count");
- static const LLSD::String rmin_tag("resp_min");
- static const LLSD::String rmax_tag("resp_max");
- static const LLSD::String rmean_tag("resp_mean");
- // MMM Group Sub-tags.
- static const LLSD::String cnt_tag("count");
- static const LLSD::String min_tag("min");
- static const LLSD::String max_tag("max");
- static const LLSD::String mean_tag("mean");
- const duration_t now = LLViewerAssetStatsFF::get_timestamp();
- mCurRegionStats->accumulateTime(now);
- LLSD regions = LLSD::emptyArray();
- for (PerRegionContainer::iterator it = mRegionStats.begin();
- mRegionStats.end() != it;
- ++it)
- {
- if (0 == it->first)
- {
- // Never emit NULL UUID/handle in results.
- continue;
- }
- PerRegionStats & stats = *it->second;
-
- LLSD reg_stat = LLSD::emptyMap();
-
- for (int i = 0; i < LL_ARRAY_SIZE(tags); ++i)
- {
- PerRegionStats::prs_group & group(stats.mRequests[i]);
-
- if ((! compact_output) ||
- group.mEnqueued.getCount() ||
- group.mDequeued.getCount() ||
- group.mResponse.getCount())
- {
- LLSD & slot = reg_stat[tags[i]];
- slot = LLSD::emptyMap();
- slot[enq_tag] = LLSD(S32(stats.mRequests[i].mEnqueued.getCount()));
- slot[deq_tag] = LLSD(S32(stats.mRequests[i].mDequeued.getCount()));
- slot[rcnt_tag] = LLSD(S32(stats.mRequests[i].mResponse.getCount()));
- slot[rmin_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMin() * 1.0e-6));
- slot[rmax_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMax() * 1.0e-6));
- slot[rmean_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMean() * 1.0e-6));
- }
- }
- if ((! compact_output) || stats.mFPS.getCount())
- {
- LLSD & slot = reg_stat["fps"];
- slot = LLSD::emptyMap();
- slot[cnt_tag] = LLSD(S32(stats.mFPS.getCount()));
- slot[min_tag] = LLSD(F64(stats.mFPS.getMin()));
- slot[max_tag] = LLSD(F64(stats.mFPS.getMax()));
- slot[mean_tag] = LLSD(F64(stats.mFPS.getMean()));
- }
- U32 grid_x(0), grid_y(0);
- grid_from_region_handle(it->first, &grid_x, &grid_y);
- reg_stat["grid_x"] = LLSD::Integer(grid_x);
- reg_stat["grid_y"] = LLSD::Integer(grid_y);
- reg_stat["duration"] = LLSD::Real(stats.mTotalTime * 1.0e-6);
- regions.append(reg_stat);
- }
- LLSD ret = LLSD::emptyMap();
- ret["regions"] = regions;
- ret["duration"] = LLSD::Real((now - mResetTimestamp) * 1.0e-6);
-
- return ret;
- }
- void
- LLViewerAssetStats::merge(const LLViewerAssetStats & src)
- {
- // mRegionHandle, mCurRegionStats and mResetTimestamp are left untouched.
- // Just merge the stats bodies
- const PerRegionContainer::const_iterator it_end(src.mRegionStats.end());
- for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it)
- {
- PerRegionContainer::iterator dst(mRegionStats.find(it->first));
- if (mRegionStats.end() == dst)
- {
- // Destination is missing data, just make a private copy
- mRegionStats[it->first] = new PerRegionStats(*it->second);
- }
- else
- {
- dst->second->merge(*it->second);
- }
- }
- }
- // ------------------------------------------------------
- // Global free-function definitions (LLViewerAssetStatsFF namespace)
- // ------------------------------------------------------
- namespace LLViewerAssetStatsFF
- {
- //
- // Target thread is elaborated in the function name. This could
- // have been something 'templatey' like specializations iterated
- // over a set of constants but with so few, this is clearer I think.
- //
- // As for the threads themselves... rather than do fine-grained
- // locking as we gather statistics, this code creates a collector
- // for each thread, allocated and run independently. Logging
- // happens at relatively infrequent intervals and at that time
- // the data is sent to a single thread to be aggregated into
- // a single entity with locks, thread safety and other niceties.
- //
- // A particularly fussy implementation would distribute the
- // per-thread pointers across separate cache lines. But that should
- // be beyond current requirements.
- //
- // 'main' thread - initial program thread
- void
- set_region_main(LLViewerAssetStats::region_handle_t region_handle)
- {
- if (! gViewerAssetStatsMain)
- return;
- gViewerAssetStatsMain->setRegion(region_handle);
- }
- void
- record_enqueue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- if (! gViewerAssetStatsMain)
- return;
- gViewerAssetStatsMain->recordGetEnqueued(at, with_http, is_temp);
- }
- void
- record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- if (! gViewerAssetStatsMain)
- return;
- gViewerAssetStatsMain->recordGetDequeued(at, with_http, is_temp);
- }
- void
- record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp, LLViewerAssetStats::duration_t duration)
- {
- if (! gViewerAssetStatsMain)
- return;
- gViewerAssetStatsMain->recordGetServiced(at, with_http, is_temp, duration);
- }
- void
- record_fps_main(F32 fps)
- {
- if (! gViewerAssetStatsMain)
- return;
- gViewerAssetStatsMain->recordFPS(fps);
- }
- // 'thread1' - should be for TextureFetch thread
- void
- set_region_thread1(LLViewerAssetStats::region_handle_t region_handle)
- {
- if (! gViewerAssetStatsThread1)
- return;
- gViewerAssetStatsThread1->setRegion(region_handle);
- }
- void
- record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- if (! gViewerAssetStatsThread1)
- return;
- gViewerAssetStatsThread1->recordGetEnqueued(at, with_http, is_temp);
- }
- void
- record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- if (! gViewerAssetStatsThread1)
- return;
- gViewerAssetStatsThread1->recordGetDequeued(at, with_http, is_temp);
- }
- void
- record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp, LLViewerAssetStats::duration_t duration)
- {
- if (! gViewerAssetStatsThread1)
- return;
- gViewerAssetStatsThread1->recordGetServiced(at, with_http, is_temp, duration);
- }
- void
- init()
- {
- if (! gViewerAssetStatsMain)
- {
- gViewerAssetStatsMain = new LLViewerAssetStats();
- }
- if (! gViewerAssetStatsThread1)
- {
- gViewerAssetStatsThread1 = new LLViewerAssetStats();
- }
- }
- void
- cleanup()
- {
- delete gViewerAssetStatsMain;
- gViewerAssetStatsMain = 0;
- delete gViewerAssetStatsThread1;
- gViewerAssetStatsThread1 = 0;
- }
- } // namespace LLViewerAssetStatsFF
- // ------------------------------------------------------
- // Local function definitions
- // ------------------------------------------------------
- namespace
- {
- LLViewerAssetStats::EViewerAssetCategories
- asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool is_temp)
- {
- // For statistical purposes, we divide GETs into several
- // populations of asset fetches:
- // - textures which are de-prioritized in the asset system
- // - wearables (clothing, bodyparts) which directly affect
- // user experiences when they log in
- // - sounds
- // - gestures
- // - everything else.
- //
- llassert_always(50 == LLViewerAssetType::AT_COUNT);
- // Multiple asset definitions are floating around so this requires some
- // maintenance and attention.
- static const LLViewerAssetStats::EViewerAssetCategories asset_to_bin_map[LLViewerAssetType::AT_COUNT] =
- {
- LLViewerAssetStats::EVACTextureTempHTTPGet, // (0) AT_TEXTURE
- LLViewerAssetStats::EVACSoundUDPGet, // AT_SOUND
- LLViewerAssetStats::EVACOtherGet, // AT_CALLINGCARD
- LLViewerAssetStats::EVACOtherGet, // AT_LANDMARK
- LLViewerAssetStats::EVACOtherGet, // AT_SCRIPT
- LLViewerAssetStats::EVACWearableUDPGet, // AT_CLOTHING
- LLViewerAssetStats::EVACOtherGet, // AT_OBJECT
- LLViewerAssetStats::EVACOtherGet, // AT_NOTECARD
- LLViewerAssetStats::EVACOtherGet, // AT_CATEGORY
- LLViewerAssetStats::EVACOtherGet, // AT_ROOT_CATEGORY
- LLViewerAssetStats::EVACOtherGet, // (10) AT_LSL_TEXT
- LLViewerAssetStats::EVACOtherGet, // AT_LSL_BYTECODE
- LLViewerAssetStats::EVACOtherGet, // AT_TEXTURE_TGA
- LLViewerAssetStats::EVACWearableUDPGet, // AT_BODYPART
- LLViewerAssetStats::EVACOtherGet, // AT_TRASH
- LLViewerAssetStats::EVACOtherGet, // AT_SNAPSHOT_CATEGORY
- LLViewerAssetStats::EVACOtherGet, // AT_LOST_AND_FOUND
- LLViewerAssetStats::EVACSoundUDPGet, // AT_SOUND_WAV
- LLViewerAssetStats::EVACOtherGet, // AT_IMAGE_TGA
- LLViewerAssetStats::EVACOtherGet, // AT_IMAGE_JPEG
- LLViewerAssetStats::EVACGestureUDPGet, // (20) AT_ANIMATION
- LLViewerAssetStats::EVACGestureUDPGet, // AT_GESTURE
- LLViewerAssetStats::EVACOtherGet, // AT_SIMSTATE
- LLViewerAssetStats::EVACOtherGet, // AT_FAVORITE
- LLViewerAssetStats::EVACOtherGet, // AT_LINK
- LLViewerAssetStats::EVACOtherGet, // AT_LINK_FOLDER
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, // (30)
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, // (40)
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, //
- LLViewerAssetStats::EVACOtherGet, // AT_MESH
- // (50)
- };
-
- if (at < 0 || at >= LLViewerAssetType::AT_COUNT)
- {
- return LLViewerAssetStats::EVACOtherGet;
- }
- LLViewerAssetStats::EViewerAssetCategories ret(asset_to_bin_map[at]);
- if (LLViewerAssetStats::EVACTextureTempHTTPGet == ret)
- {
- // Indexed with [is_temp][with_http]
- static const LLViewerAssetStats::EViewerAssetCategories texture_bin_map[2][2] =
- {
- {
- LLViewerAssetStats::EVACTextureNonTempUDPGet,
- LLViewerAssetStats::EVACTextureNonTempHTTPGet,
- },
- {
- LLViewerAssetStats::EVACTextureTempUDPGet,
- LLViewerAssetStats::EVACTextureTempHTTPGet,
- }
- };
- ret = texture_bin_map[is_temp][with_http];
- }
- return ret;
- }
- } // anonymous namespace