/** * @file llviewerstatsrecorder.cpp * @brief record info about viewer events to a metrics log file * * $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 "llviewerstatsrecorder.h" #include "llcontrol.h" #include "llfile.h" #include "llviewerregion.h" #include "llviewerobject.h" #include "llworld.h" extern LLControlGroup gSavedSettings; LLViewerStatsRecorder* LLViewerStatsRecorder::sInstance = NULL; LLViewerStatsRecorder::LLViewerStatsRecorder() : mStatsFile(NULL), mTimer(), mFileOpenTime(0.0), mLastSnapshotTime(0.0), mEnableStatsRecording(false), mEnableStatsLogging(false), mInterval(0.2), mMaxDuration(300.f), mSkipSaveIfZeros(false) { if (NULL != sInstance) { LL_ERRS() << "Attempted to create multiple instances of LLViewerStatsRecorder!" << LL_ENDL; } sInstance = this; clearStats(); } LLViewerStatsRecorder::~LLViewerStatsRecorder() { if (mStatsFile) { writeToLog(0.f); // Save last data closeStatsFile(); } } void LLViewerStatsRecorder::clearStats() { mObjectCacheHitCount = 0; mObjectCacheMissFullCount = 0; mObjectCacheMissCrcCount = 0; mObjectFullUpdates = 0; mObjectTerseUpdates = 0; mObjectCacheMissRequests = 0; mObjectCacheMissResponses = 0; mObjectCacheUpdateDupes = 0; mObjectCacheUpdateChanges = 0; mObjectCacheUpdateAdds = 0; mObjectCacheUpdateReplacements = 0; mObjectUpdateFailures = 0; mObjectUpdateFailuresSize = 0; mTextureFetchCount = 0; mMeshLoadedCount = 0; } void LLViewerStatsRecorder::enableObjectStatsRecording(bool enable, bool logging /* false */) { mEnableStatsRecording = enable; // if logging is stopping, close the file if (mStatsFile && !logging) { writeToLog(0.f); // Save last data closeStatsFile(); } mEnableStatsLogging = logging; } void LLViewerStatsRecorder::recordObjectUpdateFailure(S32 msg_size) { mObjectUpdateFailures++; mObjectUpdateFailuresSize += msg_size; } void LLViewerStatsRecorder::recordCacheMissEvent(U8 cache_miss_type) { if (LLViewerRegion::CACHE_MISS_TYPE_TOTAL == cache_miss_type) { mObjectCacheMissFullCount++; } else { mObjectCacheMissCrcCount++; } } void LLViewerStatsRecorder::recordCacheHitEvent() { mObjectCacheHitCount++; } void LLViewerStatsRecorder::recordObjectUpdateEvent(const EObjectUpdateType update_type) { switch (update_type) { case OUT_FULL: mObjectFullUpdates++; break; case OUT_TERSE_IMPROVED: mObjectTerseUpdates++; break; case OUT_FULL_COMPRESSED: mObjectCacheMissResponses++; break; default: LL_WARNS() << "Unknown update_type" << LL_ENDL; break; }; } void LLViewerStatsRecorder::recordCacheFullUpdate(LLViewerRegion::eCacheUpdateResult update_result) { switch (update_result) { case LLViewerRegion::CACHE_UPDATE_DUPE: mObjectCacheUpdateDupes++; break; case LLViewerRegion::CACHE_UPDATE_CHANGED: mObjectCacheUpdateChanges++; break; case LLViewerRegion::CACHE_UPDATE_ADDED: mObjectCacheUpdateAdds++; break; case LLViewerRegion::CACHE_UPDATE_REPLACED: mObjectCacheUpdateReplacements++; break; default: LL_WARNS() << "Unknown update_result type " << (S32) update_result << LL_ENDL; break; }; } void LLViewerStatsRecorder::recordRequestCacheMissesEvent(S32 count) { mObjectCacheMissRequests += count; } void LLViewerStatsRecorder::writeToLog( F32 interval ) { if (!mEnableStatsLogging || !mEnableStatsRecording) { return; } size_t data_size = 0; F64 delta_time = LLFrameTimer::getTotalSeconds() - mLastSnapshotTime; if (delta_time < interval) return; if (mSkipSaveIfZeros) { S32 total_events = mObjectCacheHitCount + mObjectCacheMissCrcCount + mObjectCacheMissFullCount + mObjectFullUpdates + mObjectTerseUpdates + mObjectCacheMissRequests + mObjectCacheMissResponses + mObjectCacheUpdateDupes + mObjectCacheUpdateChanges + mObjectCacheUpdateAdds + mObjectCacheUpdateReplacements + mObjectUpdateFailures; if (total_events == 0) { LL_DEBUGS("ILXZeroData") << "ILX: not saving zero data" << LL_ENDL; return; } } mLastSnapshotTime = LLFrameTimer::getTotalSeconds(); LL_DEBUGS("ILX") << "ILX: " << mObjectCacheHitCount << " hits, " << mObjectCacheMissFullCount << " full misses, " << mObjectCacheMissCrcCount << " crc misses, " << mObjectFullUpdates << " full updates, " << mObjectTerseUpdates << " terse updates, " << mObjectCacheMissRequests << " cache miss requests, " << mObjectCacheMissResponses << " cache miss responses, " << mObjectCacheUpdateDupes << " cache update dupes, " << mObjectCacheUpdateChanges << " cache update changes, " << mObjectCacheUpdateAdds << " cache update adds, " << mObjectCacheUpdateReplacements << " cache update replacements, " << mObjectUpdateFailures << " update failures" << LL_ENDL; if (mStatsFile == NULL) { // Refresh settings mInterval = gSavedSettings.getF32("StatsReportFileInterval"); mSkipSaveIfZeros = gSavedSettings.getBOOL("StatsReportSkipZeroDataSaves"); mMaxDuration = gSavedSettings.getF32("StatsReportMaxDuration"); // Open the data file makeStatsFileName(); mStatsFile = LLFile::fopen(mStatsFileName, "wb"); if (mStatsFile) { LL_INFOS("ILX") << "ILX: Writing update information to " << mStatsFileName << LL_ENDL; mFileOpenTime = LLFrameTimer::getTotalSeconds(); // Write column headers std::ostringstream col_headers; col_headers << "Time (sec)," << "Regions," << "Active Cached Objects," << "Cache Hits," << "Cache Full Misses," << "Cache Crc Misses," << "Full Updates," << "Terse Updates," << "Cache Miss Requests," << "Cache Miss Responses," << "Cache Update Dupes," << "Cache Update Changes," << "Cache Update Adds," << "Cache Update Replacements," << "Update Failures," << "Texture Count," << "Mesh Load Count," << "\n"; data_size = col_headers.str().size(); if (fwrite(col_headers.str().c_str(), 1, data_size, mStatsFile ) != data_size) { LL_WARNS() << "failed to write full headers to " << mStatsFileName << LL_ENDL; } } else { // Failed to open file LL_WARNS() << "Couldn't open " << mStatsFileName << " for logging, turning off stats recording." << LL_ENDL; mEnableStatsLogging = false; return; } } std::ostringstream stats_data; stats_data << getTimeSinceStart() << "," << LLWorld::getInstance()->getRegionList().size() << "," << LLWorld::getInstance()->getNumOfActiveCachedObjects() << "," << mObjectCacheHitCount << "," << mObjectCacheMissFullCount << "," << mObjectCacheMissCrcCount << "," << mObjectFullUpdates << "," << mObjectTerseUpdates << "," << mObjectCacheMissRequests << "," << mObjectCacheMissResponses << "," << mObjectCacheUpdateDupes << "," << mObjectCacheUpdateChanges << "," << mObjectCacheUpdateAdds << "," << mObjectCacheUpdateReplacements << "," << mObjectUpdateFailures << "," << mTextureFetchCount << "," << mMeshLoadedCount << "\n"; data_size = stats_data.str().size(); if ( data_size != fwrite(stats_data.str().c_str(), 1, data_size, mStatsFile )) { LL_WARNS() << "Unable to write complete column data to " << mStatsFileName << LL_ENDL; closeStatsFile(); } clearStats(); if (getTimeSinceStart() >= mMaxDuration) { // If file recording has been running for too long, stop it. closeStatsFile(); } } void LLViewerStatsRecorder::closeStatsFile() { if (mStatsFile) { LL_INFOS("ILX") << "ILX: Stopped writing update information to " << mStatsFileName << " after " << getTimeSinceStart() << " seconds." << LL_ENDL; LLFile::close(mStatsFile); mStatsFile = NULL; } mEnableStatsLogging = false; } void LLViewerStatsRecorder::makeStatsFileName() { // Create filename - tbd: use pid? #if LL_WINDOWS std::string stats_file_name("SLViewerStats-"); #else std::string stats_file_name("slviewerstats-"); #endif F64 now = LLFrameTimer::getTotalSeconds(); std::string date_str = LLDate(now).asString(); std::replace(date_str.begin(), date_str.end(), ':', '-'); // Make it valid for a filename stats_file_name.append(date_str); stats_file_name.append(".csv"); mStatsFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, stats_file_name); } F32 LLViewerStatsRecorder::getTimeSinceStart() { return (F32) (LLFrameTimer::getTotalSeconds() - mFileOpenTime); } void LLViewerStatsRecorder::recordTextureFetch() { mTextureFetchCount += 1; } void LLViewerStatsRecorder::recordMeshLoaded() { mMeshLoadedCount += 1; }