diff options
Diffstat (limited to 'indra/newview')
63 files changed, 5792 insertions, 486 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 2dd32b7aa4..8eb350da34 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -50,6 +50,7 @@ include_directories( ${LLCHARACTER_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} + ${LLKDU_INCLUDE_DIRS} ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} @@ -222,6 +223,7 @@ set(viewer_SOURCE_FILES llfloaterurlentry.cpp llfloatervoiceeffect.cpp llfloaterwater.cpp + llfloaterwebcontent.cpp llfloaterwhitelistentry.cpp llfloaterwindlight.cpp llfloaterwindowsize.cpp @@ -481,6 +483,7 @@ set(viewer_SOURCE_FILES llvectorperfoptions.cpp llversioninfo.cpp llviewchildren.cpp + llviewerassetstats.cpp llviewerassetstorage.cpp llviewerassettype.cpp llviewerattachmenu.cpp @@ -757,6 +760,7 @@ set(viewer_HEADER_FILES llfloaterurlentry.h llfloatervoiceeffect.h llfloaterwater.h + llfloaterwebcontent.h llfloaterwhitelistentry.h llfloaterwindlight.h llfloaterwindowsize.h @@ -1014,6 +1018,7 @@ set(viewer_HEADER_FILES llvectorperfoptions.h llversioninfo.h llviewchildren.h + llviewerassetstats.h llviewerassetstorage.h llviewerassettype.h llviewerattachmenu.h @@ -1453,11 +1458,6 @@ if (WINDOWS) # In the meantime, if you have any ideas on how to easily maintain one list, either here or in viewer_manifest.py # and have the build deps get tracked *please* tell me about it. - if(LLKDU_LIBRARY) - # Configure a var for llkdu which may not exist for all builds. - set(LLKDU_DLL_SOURCE ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/llkdu.dll) - endif(LLKDU_LIBRARY) - if(USE_GOOGLE_PERFTOOLS) # Configure a var for tcmalloc location, if used. # Note the need to specify multiple names explicitly. @@ -1474,7 +1474,6 @@ if (WINDOWS) #${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libtcmalloc_minimal.dll => None ... Skipping libtcmalloc_minimal.dll ${CMAKE_SOURCE_DIR}/../etc/message.xml ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg - ${LLKDU_DLL_SOURCE} ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/llcommon.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libapr-1.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libaprutil-1.dll @@ -1660,7 +1659,6 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLAUDIO_LIBRARIES} ${LLCHARACTER_LIBRARIES} ${LLIMAGE_LIBRARIES} - ${LLIMAGEJ2COJ_LIBRARIES} ${LLINVENTORY_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLPLUGIN_LIBRARIES} @@ -1696,6 +1694,17 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${GOOGLE_PERFTOOLS_LIBRARIES} ) +if (USE_KDU) + target_link_libraries(${VIEWER_BINARY_NAME} + ${LLKDU_LIBRARIES} + ${KDU_LIBRARY} + ) +else (USE_KDU) + target_link_libraries(${VIEWER_BINARY_NAME} + ${LLIMAGEJ2COJ_LIBRARIES} + ) +endif (USE_KDU) + build_version(viewer) set(ARTWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH @@ -1843,29 +1852,31 @@ if (PACKAGE) set(VIEWER_COPY_MANIFEST copy_l_viewer_manifest) endif (LINUX) - if(CMAKE_CFG_INTDIR STREQUAL ".") + if(RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) + if(CMAKE_CFG_INTDIR STREQUAL ".") set(LLBUILD_CONFIG ${CMAKE_BUILD_TYPE}) - else(CMAKE_CFG_INTDIR STREQUAL ".") + else(CMAKE_CFG_INTDIR STREQUAL ".") # set LLBUILD_CONFIG to be a shell variable evaluated at build time # reflecting the configuration we are currently building. set(LLBUILD_CONFIG ${CMAKE_CFG_INTDIR}) - endif(CMAKE_CFG_INTDIR STREQUAL ".") - add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" - COMMAND "${PYTHON_EXECUTABLE}" - ARGS - "${CMAKE_CURRENT_SOURCE_DIR}/generate_breakpad_symbols.py" - "${LLBUILD_CONFIG}" - "${VIEWER_DIST_DIR}" - "${VIEWER_EXE_GLOBS}" - "${VIEWER_LIB_GLOB}" - "${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/bin/dump_syms" - "${VIEWER_SYMBOL_FILE}" - DEPENDS generate_breakpad_symbols.py - VERBATIM - ) - add_custom_target(generate_breakpad_symbols DEPENDS "${VIEWER_SYMBOL_FILE}") - add_dependencies(generate_breakpad_symbols "${VIEWER_BINARY_NAME}" "${VIEWER_COPY_MANIFEST}") - add_dependencies(package generate_breakpad_symbols) + endif(CMAKE_CFG_INTDIR STREQUAL ".") + add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" + COMMAND "${PYTHON_EXECUTABLE}" + ARGS + "${CMAKE_CURRENT_SOURCE_DIR}/generate_breakpad_symbols.py" + "${LLBUILD_CONFIG}" + "${VIEWER_DIST_DIR}" + "${VIEWER_EXE_GLOBS}" + "${VIEWER_LIB_GLOB}" + "${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/bin/dump_syms" + "${VIEWER_SYMBOL_FILE}" + DEPENDS generate_breakpad_symbols.py + VERBATIM) + + add_custom_target(generate_breakpad_symbols DEPENDS "${VIEWER_SYMBOL_FILE}") + add_dependencies(generate_breakpad_symbols "${VIEWER_BINARY_NAME}" "${VIEWER_COPY_MANIFEST}") + add_dependencies(package generate_breakpad_symbols) + endif(RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) endif (PACKAGE) if (LL_TESTS) @@ -1955,6 +1966,16 @@ if (LL_TESTS) "${test_libs}" ) + LL_ADD_INTEGRATION_TEST(llsimplestat + "" + "${test_libs}" + ) + + LL_ADD_INTEGRATION_TEST(llviewerassetstats + llviewerassetstats.cpp + "${test_libs}" + ) + #ADD_VIEWER_BUILD_TEST(llmemoryview viewer) #ADD_VIEWER_BUILD_TEST(llagentaccess viewer) #ADD_VIEWER_BUILD_TEST(llworldmap viewer) diff --git a/indra/newview/app_settings/lindenlab.pem b/indra/newview/app_settings/lindenlab.pem new file mode 100644 index 0000000000..cf88d0e047 --- /dev/null +++ b/indra/newview/app_settings/lindenlab.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEUDCCA7mgAwIBAgIJAN4ppNGwj6yIMA0GCSqGSIb3DQEBBAUAMIHMMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j +aXNjbzEZMBcGA1UEChMQTGluZGVuIExhYiwgSW5jLjEpMCcGA1UECxMgTGluZGVu +IExhYiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxKTAnBgNVBAMTIExpbmRlbiBMYWIg +Q2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYJKoZIhvcNAQkBFhBjYUBsaW5kZW5s +YWIuY29tMB4XDTA1MDQyMTAyNDAzMVoXDTI1MDQxNjAyNDAzMVowgcwxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp +c2NvMRkwFwYDVQQKExBMaW5kZW4gTGFiLCBJbmMuMSkwJwYDVQQLEyBMaW5kZW4g +TGFiIENlcnRpZmljYXRlIEF1dGhvcml0eTEpMCcGA1UEAxMgTGluZGVuIExhYiBD +ZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgkqhkiG9w0BCQEWEGNhQGxpbmRlbmxh +Yi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKXh1MThucdTbMg9bYBO +rAm8yWns32YojB0PRfbq8rUjepEhTm3/13s0u399Uc202v4ejcGhkIDWJZd2NZMF +oKrhmRfxGHSKPCuFaXC3jh0lRECj7k8FoPkcmaPjSyodrDFDUUuv+C06oYJoI+rk +8REyal9NwgHvqCzOrZtiTXAdAgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUO1zK2e1f +1wO1fHAjq6DTJobKDrcwggEBBgNVHSMEgfkwgfaAFDtcytntX9cDtXxwI6ug0yaG +yg63oYHSpIHPMIHMMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW +MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQTGluZGVuIExhYiwgSW5j +LjEpMCcGA1UECxMgTGluZGVuIExhYiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxKTAn +BgNVBAMTIExpbmRlbiBMYWIgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYJKoZI +hvcNAQkBFhBjYUBsaW5kZW5sYWIuY29tggkA3imk0bCPrIgwDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQQFAAOBgQA/ZkgfvwHYqk1UIAKZS3kMCxz0HvYuEQtviwnu +xA39CIJ65Zozs28Eg1aV9/Y+Of7TnWhW+U3J3/wD/GghaAGiKK6vMn9gJBIdBX/9 +e6ef37VGyiOEFFjnUIbuk0RWty0orN76q/lI/xjCi15XSA/VSq2j4vmnwfZcPTDu +glmQ1A== +-----END CERTIFICATE----- + diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 06992d2b52..b3324ea6c6 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -686,6 +686,39 @@ <key>Value</key> <string>http://www.secondlife.com</string> </map> + <key>BrowserIgnoreSSLCertErrors</key> + <map> + <key>Comment</key> + <string>FOR TESTING ONLY: Tell the built-in web browser to ignore SSL cert errors.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>BrowserUseDefaultCAFile</key> + <map> + <key>Comment</key> + <string>Tell the built-in web browser to use the CA.pem file shipped with the client.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>BrowserCAFilePath</key> + <map> + <key>Comment</key> + <string>Tell the built-in web browser the path to an alternative CA.pem file (only used if BrowserUseDefaultCAFile is false).</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string></string> + </map> <key>BlockAvatarAppearanceMessages</key> <map> <key>Comment</key> @@ -6572,7 +6605,18 @@ <key>MediaBrowserWindowLimit</key> <map> <key>Comment</key> - <string>Maximum number of media brower windows that can be open at once (0 for no limit)</string> + <string>Maximum number of media brower windows that can be open at once in the media browser floater (0 for no limit)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> + <integer>5</integer> + </map> + <key>WebContentWindowLimit</key> + <map> + <key>Comment</key> + <string>Maximum number of web brower windows that can be open at once in the Web content floater (0 for no limit)</string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -9980,6 +10024,17 @@ <key>Value</key> <real>500.0</real> </map> + <key>UpdaterMaximumBandwidth</key> + <map> + <key>Comment</key> + <string>Maximum allowable downstream bandwidth for updater service (kilo bits per second)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>500.0</real> + </map> <key>ToolTipDelay</key> <map> <key>Comment</key> @@ -11080,16 +11135,16 @@ <key>Value</key> <integer>15</integer> </map> - <key>UpdaterServiceActive</key> + <key>UpdaterServiceSetting</key> <map> <key>Comment</key> - <string>Enable or disable the updater service.</string> + <string>Configure updater service.</string> <key>Persist</key> <integer>1</integer> <key>Type</key> - <string>Boolean</string> + <string>U32</string> <key>Value</key> - <integer>1</integer> + <integer>3</integer> </map> <key>UpdaterServiceCheckPeriod</key> <map> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 001a6a8851..ea3c2eb312 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -637,6 +637,9 @@ void LLAgent::setRegion(LLViewerRegion *regionp) // Update all of the regions. LLWorld::getInstance()->updateAgentOffset(mAgentOriginGlobal); } + + // Pass new region along to metrics components that care about this level of detail. + LLAppViewer::metricsUpdateRegion(regionp->getHandle()); } mRegionp = regionp; diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 15f8e7bf4d..f01d5ff1f5 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -2695,6 +2695,9 @@ void LLAgentCamera::lookAtLastChat() new_camera_pos -= delta_pos * 0.4f; new_camera_pos += left * 0.3f; new_camera_pos += up * 0.2f; + + setFocusOnAvatar(FALSE, FALSE); + if (chatter_av->mHeadp) { setFocusGlobal(gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()), gAgent.getLastChatter()); @@ -2705,7 +2708,6 @@ void LLAgentCamera::lookAtLastChat() setFocusGlobal(chatter->getPositionGlobal(), gAgent.getLastChatter()); mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); } - setFocusOnAvatar(FALSE, TRUE); } else { @@ -2725,9 +2727,10 @@ void LLAgentCamera::lookAtLastChat() new_camera_pos += left * 0.3f; new_camera_pos += up * 0.2f; + setFocusOnAvatar(FALSE, FALSE); + setFocusGlobal(chatter->getPositionGlobal(), gAgent.getLastChatter()); mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); - setFocusOnAvatar(FALSE, TRUE); } } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b460885a53..3a98c23e05 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -80,6 +80,8 @@ #include "llfeaturemanager.h" #include "llurlmatch.h" #include "lltextutil.h" +#include "lllogininstance.h" +#include "llprogressview.h" #include "llweb.h" #include "llsecondlifeurls.h" @@ -192,6 +194,7 @@ #include "llparcel.h" #include "llavatariconctrl.h" #include "llgroupiconctrl.h" +#include "llviewerassetstats.h" // Include for security api initialization #include "llsecapi.h" @@ -336,6 +339,14 @@ static std::string gWindowTitle; LLAppViewer::LLUpdaterInfo *LLAppViewer::sUpdaterInfo = NULL ; +//---------------------------------------------------------------------------- +// Metrics logging control constants +//---------------------------------------------------------------------------- +static const F32 METRICS_INTERVAL_DEFAULT = 600.0; +static const F32 METRICS_INTERVAL_QA = 30.0; +static F32 app_metrics_interval = METRICS_INTERVAL_DEFAULT; +static bool app_metrics_qa_mode = false; + void idle_afk_check() { // check idle timers @@ -593,10 +604,14 @@ LLAppViewer::LLAppViewer() : setupErrorHandling(); sInstance = this; gLoggedInTime.stop(); + + LLLoginInstance::instance().setUpdaterService(mUpdater.get()); } LLAppViewer::~LLAppViewer() { + LLLoginInstance::instance().setUpdaterService(0); + destroyMainloopTimeout(); // If we got to this destructor somehow, the app didn't hang. @@ -655,6 +670,21 @@ bool LLAppViewer::init() // Called before threads are created. LLCurl::initClass(); LLMachineID::init(); + + { + // Viewer metrics initialization + static LLCachedControl<bool> metrics_submode(gSavedSettings, + "QAModeMetrics", + false, + "Enables QA features (logging, faster cycling) for metrics collector"); + + if (metrics_submode) + { + app_metrics_qa_mode = true; + app_metrics_interval = METRICS_INTERVAL_QA; + } + LLViewerAssetStatsFF::init(); + } initThreads(); writeSystemInfo(); @@ -1717,6 +1747,8 @@ bool LLAppViewer::cleanup() LLWatchdog::getInstance()->cleanup(); + LLViewerAssetStatsFF::cleanup(); + llinfos << "Shutting down message system" << llendflush; end_messaging_system(); @@ -1783,7 +1815,10 @@ bool LLAppViewer::initThreads() // Image decoding LLAppViewer::sImageDecodeThread = new LLImageDecodeThread(enable_threads && true); LLAppViewer::sTextureCache = new LLTextureCache(enable_threads && true); - LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), sImageDecodeThread, enable_threads && true); + LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), + sImageDecodeThread, + enable_threads && true, + app_metrics_qa_mode); LLImage::initClass(); if (LLFastTimer::sLog || LLFastTimer::sMetricLog) @@ -2403,26 +2438,120 @@ bool LLAppViewer::initConfiguration() } namespace { - // *TODO - decide if there's a better place for this function. + // *TODO - decide if there's a better place for these functions. // do we need a file llupdaterui.cpp or something? -brad + + void apply_update_callback(LLSD const & notification, LLSD const & response) + { + lldebugs << "LLUpdate user response: " << response << llendl; + if(response["OK_okcancelbuttons"].asBoolean()) + { + llinfos << "LLUpdate restarting viewer" << llendl; + static const bool install_if_ready = true; + // *HACK - this lets us launch the installer immediately for now + LLUpdaterService().startChecking(install_if_ready); + } + } + + void apply_update_ok_callback(LLSD const & notification, LLSD const & response) + { + llinfos << "LLUpdate restarting viewer" << llendl; + static const bool install_if_ready = true; + // *HACK - this lets us launch the installer immediately for now + LLUpdaterService().startChecking(install_if_ready); + } + + void on_update_downloaded(LLSD const & data) + { + std::string notification_name; + void (*apply_callback)(LLSD const &, LLSD const &) = NULL; + + if(data["required"].asBoolean()) + { + apply_callback = &apply_update_ok_callback; + if(LLStartUp::getStartupState() <= STATE_LOGIN_WAIT) + { + // The user never saw the progress bar. + notification_name = "RequiredUpdateDownloadedVerboseDialog"; + } + else + { + notification_name = "RequiredUpdateDownloadedDialog"; + } + } + else + { + apply_callback = &apply_update_callback; + if(LLStartUp::getStartupState() < STATE_STARTED) + { + // CHOP-262 we need to use a different notification + // method prior to login. + notification_name = "DownloadBackgroundDialog"; + } + else + { + notification_name = "DownloadBackgroundTip"; + } + } + + LLSD substitutions; + substitutions["VERSION"] = data["version"]; + + // truncate version at the rightmost '.' + std::string version_short(data["version"]); + size_t short_length = version_short.rfind('.'); + if (short_length != std::string::npos) + { + version_short.resize(short_length); + } + + LLUIString relnotes_url("[RELEASE_NOTES_BASE_URL][CHANNEL_URL]/[VERSION_SHORT]"); + relnotes_url.setArg("[VERSION_SHORT]", version_short); + + // *TODO thread the update service's response through to this point + std::string const & channel = LLVersionInfo::getChannel(); + boost::shared_ptr<char> channel_escaped(curl_escape(channel.c_str(), channel.size()), &curl_free); + + relnotes_url.setArg("[CHANNEL_URL]", channel_escaped.get()); + relnotes_url.setArg("[RELEASE_NOTES_BASE_URL]", LLTrans::getString("RELEASE_NOTES_BASE_URL")); + substitutions["RELEASE_NOTES_FULL_URL"] = relnotes_url.getString(); + + LLNotificationsUtil::add(notification_name, substitutions, LLSD(), apply_callback); + } + + void install_error_callback(LLSD const & notification, LLSD const & response) + { + LLAppViewer::instance()->forceQuit(); + } + bool notify_update(LLSD const & evt) { + std::string notification_name; switch (evt["type"].asInteger()) { case LLUpdaterService::DOWNLOAD_COMPLETE: - LLNotificationsUtil::add("DownloadBackground"); + on_update_downloaded(evt); break; case LLUpdaterService::INSTALL_ERROR: - LLNotificationsUtil::add("FailedUpdateInstall"); + if(evt["required"].asBoolean()) { + LLNotificationsUtil::add("FailedRequiredUpdateInstall", LLSD(), LLSD(), &install_error_callback); + } else { + LLNotificationsUtil::add("FailedUpdateInstall"); + } break; default: - llinfos << "unhandled update event " << evt << llendl; break; } // let others also handle this event by default return false; } + + bool on_bandwidth_throttle(LLUpdaterService * updater, LLSD const & evt) + { + updater->setBandwidthLimit(evt.asInteger() * (1024/8)); + return false; // Let others receive this event. + }; }; void LLAppViewer::initUpdater() @@ -2445,7 +2574,10 @@ void LLAppViewer::initUpdater() channel, version); mUpdater->setCheckPeriod(check_period); - if(gSavedSettings.getBOOL("UpdaterServiceActive")) + mUpdater->setBandwidthLimit((int)gSavedSettings.getF32("UpdaterMaximumBandwidth") * (1024/8)); + gSavedSettings.getControl("UpdaterMaximumBandwidth")->getSignal()-> + connect(boost::bind(&on_bandwidth_throttle, mUpdater.get(), _2)); + if(gSavedSettings.getU32("UpdaterServiceSetting")) { bool install_if_ready = true; mUpdater->startChecking(install_if_ready); @@ -2872,8 +3004,10 @@ void LLAppViewer::handleViewerCrash() pApp->removeMarkerFile(false); } +#if LL_SEND_CRASH_REPORTS // Call to pure virtual, handled by platform specific llappviewer instance. pApp->handleCrashReporting(); +#endif return; } @@ -3045,6 +3179,9 @@ void LLAppViewer::requestQuit() return; } + // Try to send metrics back to the grid + metricsSend(!gDisconnected); + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, TRUE); effectp->setPositionGlobal(gAgent.getPositionGlobal()); effectp->setColor(LLColor4U(gAgent.getEffectColor())); @@ -3081,7 +3218,7 @@ static LLNotificationFunctorRegistration finish_quit_reg("ConfirmQuit", finish_q void LLAppViewer::userQuit() { - if (gDisconnected) + if (gDisconnected || gViewerWindow->getProgressView()->getVisible()) { requestQuit(); } @@ -3822,6 +3959,11 @@ void LLAppViewer::idle() llinfos << "Unknown object updates: " << gObjectList.mNumUnknownUpdates << llendl; gObjectList.mNumUnknownUpdates = 0; } + + // ViewerMetrics FPS piggy-backing on the debug timer. + // The 5-second interval is nice for this purpose. If the object debug + // bit moves or is disabled, please give this a suitable home. + LLViewerAssetStatsFF::record_fps_main(gFPSClamped); } } @@ -3864,6 +4006,18 @@ void LLAppViewer::idle() gInventory.idleNotifyObservers(); } + // Metrics logging (LLViewerAssetStats, etc.) + { + static LLTimer report_interval; + + // *TODO: Add configuration controls for this + if (report_interval.getElapsedTimeF32() >= app_metrics_interval) + { + metricsSend(! gDisconnected); + report_interval.reset(); + } + } + if (gDisconnected) { return; @@ -4580,6 +4734,35 @@ void LLAppViewer::loadEventHostModule(S32 listen_port) return; } + LL_INFOS("eventhost") << "Found lleventhost at '" << dso_path << "'" << LL_ENDL; +#if ! defined(LL_WINDOWS) + { + std::string outfile("/tmp/lleventhost.file.out"); + std::string command("file '" + dso_path + "' > '" + outfile + "' 2>&1"); + int rc = system(command.c_str()); + if (rc != 0) + { + LL_WARNS("eventhost") << command << " ==> " << rc << ':' << LL_ENDL; + } + else + { + LL_INFOS("eventhost") << command << ':' << LL_ENDL; + } + { + std::ifstream reader(outfile.c_str()); + std::string line; + while (std::getline(reader, line)) + { + size_t len = line.length(); + if (len && line[len-1] == '\n') + line.erase(len-1); + LL_INFOS("eventhost") << line << LL_ENDL; + } + } + remove(outfile.c_str()); + } +#endif // LL_WINDOWS + apr_dso_handle_t * eventhost_dso_handle = NULL; apr_pool_t * eventhost_dso_memory_pool = NULL; @@ -4588,13 +4771,13 @@ void LLAppViewer::loadEventHostModule(S32 listen_port) apr_status_t rv = apr_dso_load(&eventhost_dso_handle, dso_path.c_str(), eventhost_dso_memory_pool); - ll_apr_assert_status(rv); + llassert_always(! ll_apr_warn_status(rv, eventhost_dso_handle)); llassert_always(eventhost_dso_handle != NULL); int (*ll_plugin_start_func)(LLSD const &) = NULL; rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll_plugin_start_func, eventhost_dso_handle, "ll_plugin_start"); - ll_apr_assert_status(rv); + llassert_always(! ll_apr_warn_status(rv, eventhost_dso_handle)); llassert_always(ll_plugin_start_func != NULL); LLSD args; @@ -4766,3 +4949,75 @@ bool LLAppViewer::getMasterSystemAudioMute() { return gSavedSettings.getBOOL("MuteAudio"); } + +//---------------------------------------------------------------------------- +// Metrics-related methods (static and otherwise) +//---------------------------------------------------------------------------- + +/** + * LLViewerAssetStats collects data on a per-region (as defined by the agent's + * location) so we need to tell it about region changes which become a kind of + * hidden variable/global state in the collectors. For collectors not running + * on the main thread, we need to send a message to move the data over safely + * and cheaply (amortized over a run). + */ +void LLAppViewer::metricsUpdateRegion(U64 region_handle) +{ + if (0 != region_handle) + { + LLViewerAssetStatsFF::set_region_main(region_handle); + if (LLAppViewer::sTextureFetch) + { + // Send a region update message into 'thread1' to get the new region. + LLAppViewer::sTextureFetch->commandSetRegion(region_handle); + } + else + { + // No 'thread1', a.k.a. TextureFetch, so update directly + LLViewerAssetStatsFF::set_region_thread1(region_handle); + } + } +} + + +/** + * Attempts to start a multi-threaded metrics report to be sent back to + * the grid for consumption. + */ +void LLAppViewer::metricsSend(bool enable_reporting) +{ + if (! gViewerAssetStatsMain) + return; + + if (LLAppViewer::sTextureFetch) + { + LLViewerRegion * regionp = gAgent.getRegion(); + + if (enable_reporting && regionp) + { + std::string caps_url = regionp->getCapability("ViewerMetrics"); + + // Make a copy of the main stats to send into another thread. + // Receiving thread takes ownership. + LLViewerAssetStats * main_stats(new LLViewerAssetStats(*gViewerAssetStatsMain)); + + // Send a report request into 'thread1' to get the rest of the data + // and provide some additional parameters while here. + LLAppViewer::sTextureFetch->commandSendMetrics(caps_url, + gAgentSessionID, + gAgentID, + main_stats); + main_stats = 0; // Ownership transferred + } + else + { + LLAppViewer::sTextureFetch->commandDataBreak(); + } + } + + // Reset even if we can't report. Rather than gather up a huge chunk of + // data, we'll keep to our sampling interval and retain the data + // resolution in time. + gViewerAssetStatsMain->reset(); +} + diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 7c946b04a5..a18e6cbb02 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -169,6 +169,10 @@ public: // mute/unmute the system's master audio virtual void setMasterSystemAudioMute(bool mute); virtual bool getMasterSystemAudioMute(); + + // Metrics policy helper statics. + static void metricsUpdateRegion(U64 region_handle); + static void metricsSend(bool enable_reporting); protected: virtual bool initWindow(); // Initialize the viewer's window. diff --git a/indra/newview/llbrowsernotification.cpp b/indra/newview/llbrowsernotification.cpp index d6a813d608..6e77d1e336 100644 --- a/indra/newview/llbrowsernotification.cpp +++ b/indra/newview/llbrowsernotification.cpp @@ -29,8 +29,9 @@ #include "llnotificationhandler.h" #include "llnotifications.h" -#include "llfloaterreg.h" #include "llmediactrl.h" +#include "llviewermedia.h" +#include "llviewermediafocus.h" using namespace LLNotificationsUI; @@ -39,10 +40,19 @@ bool LLBrowserNotification::processNotification(const LLSD& notify) LLNotificationPtr notification = LLNotifications::instance().find(notify["id"].asUUID()); if (!notification) return false; - LLMediaCtrl* media_instance = LLMediaCtrl::getInstance(notification->getPayload()["media_id"].asUUID()); + LLUUID media_id = notification->getPayload()["media_id"].asUUID(); + LLMediaCtrl* media_instance = LLMediaCtrl::getInstance(media_id); if (media_instance) { media_instance->showNotification(notification); } + else if (LLViewerMediaFocus::instance().getControlsMediaID() == media_id) + { + LLViewerMediaImpl* impl = LLViewerMedia::getMediaImplFromTextureID(media_id); + if (impl) + { + impl->showNotification(notification); + } + } return false; } diff --git a/indra/newview/llbuycurrencyhtml.cpp b/indra/newview/llbuycurrencyhtml.cpp index d35c9ed853..e5a9be0203 100644 --- a/indra/newview/llbuycurrencyhtml.cpp +++ b/indra/newview/llbuycurrencyhtml.cpp @@ -33,6 +33,7 @@ #include "llfloaterreg.h" #include "llcommandhandler.h" #include "llviewercontrol.h" +#include "llstatusbar.h" // support for secondlife:///app/buycurrencyhtml/{ACTION}/{NEXT_ACTION}/{RETURN_CODE} SLapps class LLBuyCurrencyHTMLHandler : @@ -156,4 +157,7 @@ void LLBuyCurrencyHTML::closeDialog() { buy_currency_floater->closeFloater(); }; + + // Update L$ balance in the status bar in case L$ were purchased + LLStatusBar::sendMoneyBalanceRequest(); } diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 6e778de2d8..c98bcbda45 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -586,7 +586,7 @@ void LLChatHistory::initFromParams(const LLChatHistory::Params& p) LLLayoutStack::Params layout_p; layout_p.rect = stack_rect; layout_p.follows.flags = FOLLOWS_ALL; - layout_p.orientation = "vertical"; + layout_p.orientation = LLLayoutStack::VERTICAL; layout_p.mouse_opaque = false; LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p, this); diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp index 8ae3ccbae3..2873bc0059 100644 --- a/indra/newview/llfloaterabout.cpp +++ b/indra/newview/llfloaterabout.cpp @@ -272,7 +272,7 @@ LLSD LLFloaterAbout::getInfo() } // TODO: Implement media plugin version query - info["QT_WEBKIT_VERSION"] = "4.6 (version number hard-coded)"; + info["QT_WEBKIT_VERSION"] = "4.7.1 (version number hard-coded)"; if (gPacketsIn > 0) { diff --git a/indra/newview/llfloaterbuycurrency.cpp b/indra/newview/llfloaterbuycurrency.cpp index 58c79fdf15..e21a8594bc 100644 --- a/indra/newview/llfloaterbuycurrency.cpp +++ b/indra/newview/llfloaterbuycurrency.cpp @@ -267,17 +267,23 @@ void LLFloaterBuyCurrencyUI::onClickBuy() { mManager.buy(getString("buy_currency")); updateUI(); + // Update L$ balance + LLStatusBar::sendMoneyBalanceRequest(); } void LLFloaterBuyCurrencyUI::onClickCancel() { closeFloater(); + // Update L$ balance + LLStatusBar::sendMoneyBalanceRequest(); } void LLFloaterBuyCurrencyUI::onClickErrorWeb() { LLWeb::loadURLExternal(mManager.errorURI()); closeFloater(); + // Update L$ balance + LLStatusBar::sendMoneyBalanceRequest(); } // static diff --git a/indra/newview/llfloaterbuycurrencyhtml.cpp b/indra/newview/llfloaterbuycurrencyhtml.cpp index bde620d965..013cf74c7b 100644 --- a/indra/newview/llfloaterbuycurrencyhtml.cpp +++ b/indra/newview/llfloaterbuycurrencyhtml.cpp @@ -82,7 +82,7 @@ void LLFloaterBuyCurrencyHTML::navigateToFinalURL() LLStringUtil::format( buy_currency_url, replace ); // write final URL to debug console - llinfos << "Buy currency HTML prased URL is " << buy_currency_url << llendl; + llinfos << "Buy currency HTML parsed URL is " << buy_currency_url << llendl; // kick off the navigation mBrowser->navigateTo( buy_currency_url, "text/html" ); @@ -105,7 +105,7 @@ void LLFloaterBuyCurrencyHTML::handleMediaEvent( LLPluginClassMedia* self, EMedi // void LLFloaterBuyCurrencyHTML::onClose( bool app_quitting ) { - // update L$ balanace one more time + // Update L$ balance one more time LLStatusBar::sendMoneyBalanceRequest(); destroy(); diff --git a/indra/newview/llfloaterhelpbrowser.cpp b/indra/newview/llfloaterhelpbrowser.cpp index cec98e9992..a650886d89 100644 --- a/indra/newview/llfloaterhelpbrowser.cpp +++ b/indra/newview/llfloaterhelpbrowser.cpp @@ -132,9 +132,10 @@ void LLFloaterHelpBrowser::onClickOpenWebBrowser(void* user_data) void LLFloaterHelpBrowser::openMedia(const std::string& media_url) { - mBrowser->setHomePageUrl(media_url); - //mBrowser->navigateTo("data:text/html;charset=utf-8,I'd really love to be going to:<br><b>" + media_url + "</b>"); // tofu HACK for debugging =:) - mBrowser->navigateTo(media_url); + // explicitly make the media mime type for this floater since it will + // only ever display one type of content (Web). + mBrowser->setHomePageUrl(media_url, "text/html"); + mBrowser->navigateTo(media_url, "text/html"); setCurrentURL(media_url); } diff --git a/indra/newview/llfloatermediabrowser.cpp b/indra/newview/llfloatermediabrowser.cpp index d20092e344..7a670dd90c 100644 --- a/indra/newview/llfloatermediabrowser.cpp +++ b/indra/newview/llfloatermediabrowser.cpp @@ -306,17 +306,14 @@ void LLFloaterMediaBrowser::setCurrentURL(const std::string& url) { mCurrentURL = url; - // redirects will navigate momentarily to about:blank, don't add to history - if (mCurrentURL != "about:blank") - { - mAddressCombo->remove(mCurrentURL); - mAddressCombo->add(mCurrentURL); - mAddressCombo->selectByValue(mCurrentURL); + mAddressCombo->remove(mCurrentURL); + mAddressCombo->add(mCurrentURL); + mAddressCombo->selectByValue(mCurrentURL); + + // Serialize url history + LLURLHistory::removeURL("browser", mCurrentURL); + LLURLHistory::addURL("browser", mCurrentURL); - // Serialize url history - LLURLHistory::removeURL("browser", mCurrentURL); - LLURLHistory::addURL("browser", mCurrentURL); - } getChildView("back")->setEnabled(mBrowser->canNavigateBack()); getChildView("forward")->setEnabled(mBrowser->canNavigateForward()); getChildView("reload")->setEnabled(TRUE); @@ -334,8 +331,15 @@ void LLFloaterMediaBrowser::onClickRefresh(void* user_data) { LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; - self->mAddressCombo->remove(0); - self->mBrowser->navigateTo(self->mCurrentURL); + if( self->mBrowser->getMediaPlugin() && self->mBrowser->getMediaPlugin()->pluginSupportsMediaBrowser()) + { + bool ignore_cache = true; + self->mBrowser->getMediaPlugin()->browse_reload( ignore_cache ); + } + else + { + self->mBrowser->navigateTo(self->mCurrentURL); + } } //static diff --git a/indra/newview/llfloatersearch.cpp b/indra/newview/llfloatersearch.cpp index 3ed4aec89a..2041fac8d8 100644 --- a/indra/newview/llfloatersearch.cpp +++ b/indra/newview/llfloatersearch.cpp @@ -200,5 +200,5 @@ void LLFloaterSearch::search(const LLSD &key) url = LLWeb::expandURLSubstitutions(url, subs); // and load the URL in the web view - mBrowser->navigateTo(url); + mBrowser->navigateTo(url, "text/html"); } diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp new file mode 100644 index 0000000000..51726112a0 --- /dev/null +++ b/indra/newview/llfloaterwebcontent.cpp @@ -0,0 +1,402 @@ +/**
+ * @file llfloaterwebcontent.cpp
+ * @brief floater for displaying web content - e.g. profiles and search (eventually)
+ *
+ * $LicenseInfo:firstyear=2006&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 "llcombobox.h"
+#include "lliconctrl.h"
+#include "llfloaterreg.h"
+#include "lllayoutstack.h"
+#include "llpluginclassmedia.h"
+#include "llprogressbar.h"
+#include "lltextbox.h"
+#include "llurlhistory.h"
+#include "llviewercontrol.h"
+#include "llweb.h"
+#include "llwindow.h"
+
+#include "llfloaterwebcontent.h"
+
+LLFloaterWebContent::LLFloaterWebContent( const LLSD& key )
+ : LLFloater( key )
+{
+ mCommitCallbackRegistrar.add( "WebContent.Back", boost::bind( &LLFloaterWebContent::onClickBack, this ));
+ mCommitCallbackRegistrar.add( "WebContent.Forward", boost::bind( &LLFloaterWebContent::onClickForward, this ));
+ mCommitCallbackRegistrar.add( "WebContent.Reload", boost::bind( &LLFloaterWebContent::onClickReload, this ));
+ mCommitCallbackRegistrar.add( "WebContent.Stop", boost::bind( &LLFloaterWebContent::onClickStop, this ));
+ mCommitCallbackRegistrar.add( "WebContent.EnterAddress", boost::bind( &LLFloaterWebContent::onEnterAddress, this ));
+ mCommitCallbackRegistrar.add( "WebContent.PopExternal", boost::bind( &LLFloaterWebContent::onPopExternal, this ));
+}
+
+BOOL LLFloaterWebContent::postBuild()
+{
+ // these are used in a bunch of places so cache them
+ mWebBrowser = getChild< LLMediaCtrl >( "webbrowser" );
+ mAddressCombo = getChild< LLComboBox >( "address" );
+ mStatusBarText = getChild< LLTextBox >( "statusbartext" );
+ mStatusBarProgress = getChild<LLProgressBar>("statusbarprogress" );
+
+ // observe browser events
+ mWebBrowser->addObserver( this );
+
+ // these buttons are always enabled
+ getChildView("reload")->setEnabled( true );
+ getChildView("popexternal")->setEnabled( true );
+
+ // cache image for secure browsing
+ mSecureLockIcon = getChild< LLIconCtrl >("media_secure_lock_flag");
+
+ // initialize the URL history using the system URL History manager
+ initializeURLHistory();
+
+ return TRUE;
+}
+
+void LLFloaterWebContent::initializeURLHistory()
+{
+ // start with an empty list
+ LLCtrlListInterface* url_list = childGetListInterface("address");
+ if (url_list)
+ {
+ url_list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ }
+
+ // Get all of the entries in the "browser" collection
+ LLSD browser_history = LLURLHistory::getURLHistory("browser");
+ LLSD::array_iterator iter_history =
+ browser_history.beginArray();
+ LLSD::array_iterator end_history =
+ browser_history.endArray();
+ for(; iter_history != end_history; ++iter_history)
+ {
+ std::string url = (*iter_history).asString();
+ if(! url.empty())
+ url_list->addSimpleElement(url);
+ }
+}
+
+//static
+void LLFloaterWebContent::create( const std::string &url, const std::string& target, const std::string& uuid )
+{
+ lldebugs << "url = " << url << ", target = " << target << ", uuid = " << uuid << llendl;
+
+ std::string tag = target;
+
+ if(target.empty() || target == "_blank")
+ {
+ if(!uuid.empty())
+ {
+ tag = uuid;
+ }
+ else
+ {
+ // create a unique tag for this instance
+ LLUUID id;
+ id.generate();
+ tag = id.asString();
+ }
+ }
+
+ S32 browser_window_limit = gSavedSettings.getS32("WebContentWindowLimit");
+
+ if(LLFloaterReg::findInstance("web_content", tag) != NULL)
+ {
+ // There's already a web browser for this tag, so we won't be opening a new window.
+ }
+ else if(browser_window_limit != 0)
+ {
+ // showInstance will open a new window. Figure out how many web browsers are already open,
+ // and close the least recently opened one if this will put us over the limit.
+
+ LLFloaterReg::const_instance_list_t &instances = LLFloaterReg::getFloaterList("web_content");
+ lldebugs << "total instance count is " << instances.size() << llendl;
+
+ for(LLFloaterReg::const_instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); iter++)
+ {
+ lldebugs << " " << (*iter)->getKey() << llendl;
+ }
+
+ if(instances.size() >= (size_t)browser_window_limit)
+ {
+ // Destroy the least recently opened instance
+ (*instances.begin())->closeFloater();
+ }
+ }
+
+ LLFloaterWebContent *browser = dynamic_cast<LLFloaterWebContent*> (LLFloaterReg::showInstance("web_content", tag));
+ llassert(browser);
+ if(browser)
+ {
+ browser->mUUID = uuid;
+
+ // tell the browser instance to load the specified URL
+ browser->open_media(url, target);
+ LLViewerMedia::proxyWindowOpened(target, uuid);
+ }
+}
+
+//static
+void LLFloaterWebContent::closeRequest(const std::string &uuid)
+{
+ LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("web_content");
+ lldebugs << "instance list size is " << inst_list.size() << ", incoming uuid is " << uuid << llendl;
+ for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter)
+ {
+ LLFloaterWebContent* i = dynamic_cast<LLFloaterWebContent*>(*iter);
+ lldebugs << " " << i->mUUID << llendl;
+ if (i && i->mUUID == uuid)
+ {
+ i->closeFloater(false);
+ return;
+ }
+ }
+}
+
+//static
+void LLFloaterWebContent::geometryChanged(const std::string &uuid, S32 x, S32 y, S32 width, S32 height)
+{
+ LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("web_content");
+ lldebugs << "instance list size is " << inst_list.size() << ", incoming uuid is " << uuid << llendl;
+ for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter)
+ {
+ LLFloaterWebContent* i = dynamic_cast<LLFloaterWebContent*>(*iter);
+ lldebugs << " " << i->mUUID << llendl;
+ if (i && i->mUUID == uuid)
+ {
+ i->geometryChanged(x, y, width, height);
+ return;
+ }
+ }
+}
+
+void LLFloaterWebContent::geometryChanged(S32 x, S32 y, S32 width, S32 height)
+{
+ // Make sure the layout of the browser control is updated, so this calculation is correct.
+ LLLayoutStack::updateClass();
+
+ // TODO: need to adjust size and constrain position to make sure floaters aren't moved outside the window view, etc.
+ LLCoordWindow window_size;
+ getWindow()->getSize(&window_size);
+
+ // Adjust width and height for the size of the chrome on the web Browser window.
+ width += getRect().getWidth() - mWebBrowser->getRect().getWidth();
+ height += getRect().getHeight() - mWebBrowser->getRect().getHeight();
+
+ LLRect geom;
+ geom.setOriginAndSize(x, window_size.mY - (y + height), width, height);
+
+ lldebugs << "geometry change: " << geom << llendl;
+
+ handleReshape(geom,false);
+}
+
+void LLFloaterWebContent::open_media(const std::string& web_url, const std::string& target)
+{
+ // Specifying a mime type of text/html here causes the plugin system to skip the MIME type probe and just open a browser plugin.
+ mWebBrowser->setHomePageUrl(web_url, "text/html");
+ mWebBrowser->setTarget(target);
+ mWebBrowser->navigateTo(web_url, "text/html");
+ set_current_url(web_url);
+}
+
+//virtual
+void LLFloaterWebContent::onClose(bool app_quitting)
+{
+ LLViewerMedia::proxyWindowClosed(mUUID);
+ destroy();
+}
+
+// virtual
+void LLFloaterWebContent::draw()
+{
+ // this is asychronous so we need to keep checking
+ getChildView( "back" )->setEnabled( mWebBrowser->canNavigateBack() );
+ getChildView( "forward" )->setEnabled( mWebBrowser->canNavigateForward() );
+
+ LLFloater::draw();
+}
+
+// virtual
+void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event)
+{
+ if(event == MEDIA_EVENT_LOCATION_CHANGED)
+ {
+ const std::string url = self->getLocation();
+
+ if ( url.length() )
+ mStatusBarText->setText( url );
+
+ set_current_url( url );
+ }
+ else if(event == MEDIA_EVENT_NAVIGATE_BEGIN)
+ {
+ // flags are sent with this event
+ getChildView("back")->setEnabled( self->getHistoryBackAvailable() );
+ getChildView("forward")->setEnabled( self->getHistoryForwardAvailable() );
+
+ // toggle visibility of these buttons based on browser state
+ getChildView("reload")->setVisible( false );
+ getChildView("stop")->setVisible( true );
+
+ // turn "on" progress bar now we're about to start loading
+ mStatusBarProgress->setVisible( true );
+ }
+ else if(event == MEDIA_EVENT_NAVIGATE_COMPLETE)
+ {
+ // flags are sent with this event
+ getChildView("back")->setEnabled( self->getHistoryBackAvailable() );
+ getChildView("forward")->setEnabled( self->getHistoryForwardAvailable() );
+
+ // toggle visibility of these buttons based on browser state
+ getChildView("reload")->setVisible( true );
+ getChildView("stop")->setVisible( false );
+
+ // turn "off" progress bar now we're loaded
+ mStatusBarProgress->setVisible( false );
+
+ // we populate the status bar with URLs as they change so clear it now we're done
+ const std::string end_str = "";
+ mStatusBarText->setText( end_str );
+
+ // decide if secure browsing icon should be displayed
+ std::string prefix = std::string("https://");
+ std::string test_prefix = mCurrentURL.substr(0, prefix.length());
+ LLStringUtil::toLower(test_prefix);
+ if(test_prefix == prefix)
+ {
+ mSecureLockIcon->setVisible(true);
+ }
+ else
+ {
+ mSecureLockIcon->setVisible(false);
+ }
+ }
+ else if(event == MEDIA_EVENT_CLOSE_REQUEST)
+ {
+ // The browser instance wants its window closed.
+ closeFloater();
+ }
+ else if(event == MEDIA_EVENT_GEOMETRY_CHANGE)
+ {
+ geometryChanged(self->getGeometryX(), self->getGeometryY(), self->getGeometryWidth(), self->getGeometryHeight());
+ }
+ else if(event == MEDIA_EVENT_STATUS_TEXT_CHANGED )
+ {
+ const std::string text = self->getStatusText();
+ if ( text.length() )
+ mStatusBarText->setText( text );
+ }
+ else if(event == MEDIA_EVENT_PROGRESS_UPDATED )
+ {
+ int percent = (int)self->getProgressPercent();
+ mStatusBarProgress->setValue( percent );
+ }
+ else if(event == MEDIA_EVENT_NAME_CHANGED )
+ {
+ std::string page_title = self->getMediaName();
+ // simulate browser behavior - title is empty, use the current URL
+ if ( page_title.length() > 0 )
+ setTitle( page_title );
+ else
+ setTitle( mCurrentURL );
+ }
+ else if(event == MEDIA_EVENT_LINK_HOVERED )
+ {
+ const std::string link = self->getHoverLink();
+ mStatusBarText->setText( link );
+ }
+}
+
+void LLFloaterWebContent::set_current_url(const std::string& url)
+{
+ mCurrentURL = url;
+
+ // serialize url history into the system URL History manager
+ LLURLHistory::removeURL("browser", mCurrentURL);
+ LLURLHistory::addURL("browser", mCurrentURL);
+
+ mAddressCombo->remove( mCurrentURL );
+ mAddressCombo->add( mCurrentURL );
+ mAddressCombo->selectByValue( mCurrentURL );
+}
+
+void LLFloaterWebContent::onClickForward()
+{
+ mWebBrowser->navigateForward();
+}
+
+void LLFloaterWebContent::onClickBack()
+{
+ mWebBrowser->navigateBack();
+}
+
+void LLFloaterWebContent::onClickReload()
+{
+
+ if( mWebBrowser->getMediaPlugin() )
+ {
+ bool ignore_cache = true;
+ mWebBrowser->getMediaPlugin()->browse_reload( ignore_cache );
+ }
+ else
+ {
+ mWebBrowser->navigateTo(mCurrentURL);
+ }
+}
+
+void LLFloaterWebContent::onClickStop()
+{
+ if( mWebBrowser->getMediaPlugin() )
+ mWebBrowser->getMediaPlugin()->browse_stop();
+
+ // still should happen when we catch the navigate complete event
+ // but sometimes (don't know why) that event isn't sent from Qt
+ // and we getto a point where the stop button stays active.
+ getChildView("reload")->setVisible( true );
+ getChildView("stop")->setVisible( false );
+}
+
+void LLFloaterWebContent::onEnterAddress()
+{
+ // make sure there is at least something there.
+ // (perhaps this test should be for minimum length of a URL)
+ std::string url = mAddressCombo->getValue().asString();
+ if ( url.length() > 0 )
+ {
+ mWebBrowser->navigateTo( url, "text/html");
+ };
+}
+
+void LLFloaterWebContent::onPopExternal()
+{
+ // make sure there is at least something there.
+ // (perhaps this test should be for minimum length of a URL)
+ std::string url = mAddressCombo->getValue().asString();
+ if ( url.length() > 0 )
+ {
+ LLWeb::loadURLExternal( url );
+ };
+}
diff --git a/indra/newview/llfloaterwebcontent.h b/indra/newview/llfloaterwebcontent.h new file mode 100644 index 0000000000..001d822ada --- /dev/null +++ b/indra/newview/llfloaterwebcontent.h @@ -0,0 +1,82 @@ +/**
+ * @file llfloaterwebcontent.h
+ * @brief floater for displaying web content - e.g. profiles and search (eventually)
+ *
+ * $LicenseInfo:firstyear=2006&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$
+ */
+
+#ifndef LL_LLFLOATERWEBCONTENT_H
+#define LL_LLFLOATERWEBCONTENT_H
+
+#include "llfloater.h"
+#include "llmediactrl.h"
+
+class LLMediaCtrl;
+class LLComboBox;
+class LLTextBox;
+class LLProgressBar;
+class LLIconCtrl;
+
+class LLFloaterWebContent :
+ public LLFloater,
+ public LLViewerMediaObserver
+{
+public:
+ LOG_CLASS(LLFloaterWebContent);
+ LLFloaterWebContent(const LLSD& key);
+
+ void initializeURLHistory(); +
+ static void create(const std::string &url, const std::string& target, const std::string& uuid = LLStringUtil::null);
+
+ static void closeRequest(const std::string &uuid);
+ static void geometryChanged(const std::string &uuid, S32 x, S32 y, S32 width, S32 height);
+ void geometryChanged(S32 x, S32 y, S32 width, S32 height);
+
+ /* virtual */ BOOL postBuild();
+ /* virtual */ void onClose(bool app_quitting);
+ /* virtual */ void draw();
+
+ // inherited from LLViewerMediaObserver
+ /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event);
+
+ void onClickBack();
+ void onClickForward();
+ void onClickReload();
+ void onClickStop();
+ void onEnterAddress();
+ void onPopExternal();
+
+private:
+ void open_media(const std::string& media_url, const std::string& target);
+ void set_current_url(const std::string& url);
+
+ LLMediaCtrl* mWebBrowser;
+ LLComboBox* mAddressCombo;
+ LLIconCtrl *mSecureLockIcon;
+ LLTextBox* mStatusBarText;
+ LLProgressBar* mStatusBarProgress;
+ std::string mCurrentURL;
+ std::string mUUID;
+};
+
+#endif // LL_LLFLOATERWEBCONTENT_H
diff --git a/indra/newview/llinspecttoast.cpp b/indra/newview/llinspecttoast.cpp index 58b3f0309f..d7b82667d1 100644 --- a/indra/newview/llinspecttoast.cpp +++ b/indra/newview/llinspecttoast.cpp @@ -46,6 +46,7 @@ public: virtual ~LLInspectToast(); /*virtual*/ void onOpen(const LLSD& notification_id); + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask); private: void onToastDestroy(LLToast * toast); @@ -73,6 +74,7 @@ LLInspectToast::~LLInspectToast() LLTransientFloaterMgr::getInstance()->removeControlView(this); } +// virtual void LLInspectToast::onOpen(const LLSD& notification_id) { LLInspect::onOpen(notification_id); @@ -103,6 +105,15 @@ void LLInspectToast::onOpen(const LLSD& notification_id) LLUI::positionViewNearMouse(this); } +// virtual +BOOL LLInspectToast::handleToolTip(S32 x, S32 y, MASK mask) +{ + // We don't like the way LLInspect handles tooltips + // (black tooltips look weird), + // so force using the default implementation (STORM-511). + return LLFloater::handleToolTip(x, y, mask); +} + void LLInspectToast::onToastDestroy(LLToast * toast) { closeFloater(false); diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index 0546803784..d866db1829 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -49,18 +49,421 @@ #include "llnotifications.h" #include "llwindow.h" #include "llviewerwindow.h" +#include "llprogressview.h" #if LL_LINUX || LL_SOLARIS #include "lltrans.h" #endif #include "llsecapi.h" #include "llstartup.h" #include "llmachineid.h" +#include "llupdaterservice.h" +#include "llevents.h" +#include "llnotificationsutil.h" +#include "llappviewer.h" + +#include <boost/scoped_ptr.hpp> +#include <sstream> + +class LLLoginInstance::Disposable { +public: + virtual ~Disposable() {} +}; + +namespace { + class MandatoryUpdateMachine: + public LLLoginInstance::Disposable + { + public: + MandatoryUpdateMachine(LLLoginInstance & loginInstance, LLUpdaterService & updaterService); + + void start(void); + + private: + class State; + class CheckingForUpdate; + class Error; + class ReadyToInstall; + class StartingUpdaterService; + class WaitingForDownload; + + LLLoginInstance & mLoginInstance; + boost::scoped_ptr<State> mState; + LLUpdaterService & mUpdaterService; + + void setCurrentState(State * newState); + }; + + + class MandatoryUpdateMachine::State { + public: + virtual ~State() {} + virtual void enter(void) {} + virtual void exit(void) {} + }; + + + class MandatoryUpdateMachine::CheckingForUpdate: + public MandatoryUpdateMachine::State + { + public: + CheckingForUpdate(MandatoryUpdateMachine & machine); + + virtual void enter(void); + virtual void exit(void); + + private: + LLTempBoundListener mConnection; + MandatoryUpdateMachine & mMachine; + LLProgressView * mProgressView; + + bool onEvent(LLSD const & event); + }; + + + class MandatoryUpdateMachine::Error: + public MandatoryUpdateMachine::State + { + public: + Error(MandatoryUpdateMachine & machine); + + virtual void enter(void); + virtual void exit(void); + void onButtonClicked(const LLSD &, const LLSD &); + + private: + MandatoryUpdateMachine & mMachine; + }; + + + class MandatoryUpdateMachine::ReadyToInstall: + public MandatoryUpdateMachine::State + { + public: + ReadyToInstall(MandatoryUpdateMachine & machine); + + virtual void enter(void); + virtual void exit(void); + + private: + MandatoryUpdateMachine & mMachine; + }; + + + class MandatoryUpdateMachine::StartingUpdaterService: + public MandatoryUpdateMachine::State + { + public: + StartingUpdaterService(MandatoryUpdateMachine & machine); + + virtual void enter(void); + virtual void exit(void); + void onButtonClicked(const LLSD & uiform, const LLSD & result); + private: + MandatoryUpdateMachine & mMachine; + }; + + + class MandatoryUpdateMachine::WaitingForDownload: + public MandatoryUpdateMachine::State + { + public: + WaitingForDownload(MandatoryUpdateMachine & machine); + + virtual void enter(void); + virtual void exit(void); + + private: + LLTempBoundListener mConnection; + MandatoryUpdateMachine & mMachine; + LLProgressView * mProgressView; + + bool onEvent(LLSD const & event); + }; +} static const char * const TOS_REPLY_PUMP = "lllogininstance_tos_callback"; static const char * const TOS_LISTENER_NAME = "lllogininstance_tos"; std::string construct_start_string(); + + +// MandatoryUpdateMachine +//----------------------------------------------------------------------------- + + +MandatoryUpdateMachine::MandatoryUpdateMachine(LLLoginInstance & loginInstance, LLUpdaterService & updaterService): + mLoginInstance(loginInstance), + mUpdaterService(updaterService) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::start(void) +{ + llinfos << "starting manditory update machine" << llendl; + + if(mUpdaterService.isChecking()) { + switch(mUpdaterService.getState()) { + case LLUpdaterService::UP_TO_DATE: + mUpdaterService.stopChecking(); + mUpdaterService.startChecking(); + // Fall through. + case LLUpdaterService::INITIAL: + case LLUpdaterService::CHECKING_FOR_UPDATE: + setCurrentState(new CheckingForUpdate(*this)); + break; + case LLUpdaterService::DOWNLOADING: + setCurrentState(new WaitingForDownload(*this)); + break; + case LLUpdaterService::TERMINAL: + if(LLUpdaterService::updateReadyToInstall()) { + setCurrentState(new ReadyToInstall(*this)); + } else { + setCurrentState(new Error(*this)); + } + break; + case LLUpdaterService::FAILURE: + setCurrentState(new Error(*this)); + break; + default: + llassert(!"unpossible case"); + break; + } + } else { + setCurrentState(new StartingUpdaterService(*this)); + } +} + + +void MandatoryUpdateMachine::setCurrentState(State * newStatePointer) +{ + { + boost::scoped_ptr<State> newState(newStatePointer); + if(mState != 0) mState->exit(); + mState.swap(newState); + + // Old state will be deleted on exit from this block before the new state + // is entered. + } + if(mState != 0) mState->enter(); +} + + + +// MandatoryUpdateMachine::CheckingForUpdate +//----------------------------------------------------------------------------- + + +MandatoryUpdateMachine::CheckingForUpdate::CheckingForUpdate(MandatoryUpdateMachine & machine): + mMachine(machine) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::CheckingForUpdate::enter(void) +{ + llinfos << "entering checking for update" << llendl; + + mProgressView = gViewerWindow->getProgressView(); + mProgressView->setMessage("Looking for update..."); + mProgressView->setText("There is a required update for your Second Life installation."); + mProgressView->setPercent(0); + mProgressView->setVisible(true); + mConnection = LLEventPumps::instance().obtain(LLUpdaterService::pumpName()). + listen("MandatoryUpdateMachine::CheckingForUpdate", boost::bind(&MandatoryUpdateMachine::CheckingForUpdate::onEvent, this, _1)); +} + + +void MandatoryUpdateMachine::CheckingForUpdate::exit(void) +{ +} + + +bool MandatoryUpdateMachine::CheckingForUpdate::onEvent(LLSD const & event) +{ + if(event["type"].asInteger() == LLUpdaterService::STATE_CHANGE) { + switch(event["state"].asInteger()) { + case LLUpdaterService::DOWNLOADING: + mMachine.setCurrentState(new WaitingForDownload(mMachine)); + break; + case LLUpdaterService::UP_TO_DATE: + case LLUpdaterService::TERMINAL: + case LLUpdaterService::FAILURE: + mProgressView->setVisible(false); + mMachine.setCurrentState(new Error(mMachine)); + break; + case LLUpdaterService::INSTALLING: + llassert(!"can't possibly be installing"); + break; + default: + break; + } + } else { + ; // Ignore. + } + + return false; +} + + + +// MandatoryUpdateMachine::Error +//----------------------------------------------------------------------------- + + +MandatoryUpdateMachine::Error::Error(MandatoryUpdateMachine & machine): + mMachine(machine) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::Error::enter(void) +{ + llinfos << "entering error" << llendl; + LLNotificationsUtil::add("FailedUpdateInstall", LLSD(), LLSD(), boost::bind(&MandatoryUpdateMachine::Error::onButtonClicked, this, _1, _2)); +} + + +void MandatoryUpdateMachine::Error::exit(void) +{ + LLAppViewer::instance()->forceQuit(); +} + + +void MandatoryUpdateMachine::Error::onButtonClicked(const LLSD &, const LLSD &) +{ + mMachine.setCurrentState(0); +} + + + +// MandatoryUpdateMachine::ReadyToInstall +//----------------------------------------------------------------------------- + + +MandatoryUpdateMachine::ReadyToInstall::ReadyToInstall(MandatoryUpdateMachine & machine): + mMachine(machine) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::ReadyToInstall::enter(void) +{ + llinfos << "entering ready to install" << llendl; + // Open update ready dialog. +} + + +void MandatoryUpdateMachine::ReadyToInstall::exit(void) +{ + // Restart viewer. +} + + + +// MandatoryUpdateMachine::StartingUpdaterService +//----------------------------------------------------------------------------- + + +MandatoryUpdateMachine::StartingUpdaterService::StartingUpdaterService(MandatoryUpdateMachine & machine): + mMachine(machine) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::StartingUpdaterService::enter(void) +{ + llinfos << "entering start update service" << llendl; + LLNotificationsUtil::add("UpdaterServiceNotRunning", LLSD(), LLSD(), boost::bind(&MandatoryUpdateMachine::StartingUpdaterService::onButtonClicked, this, _1, _2)); +} + + +void MandatoryUpdateMachine::StartingUpdaterService::exit(void) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::StartingUpdaterService::onButtonClicked(const LLSD & uiform, const LLSD & result) +{ + if(result["OK_okcancelbuttons"].asBoolean()) { + mMachine.mUpdaterService.startChecking(false); + mMachine.setCurrentState(new CheckingForUpdate(mMachine)); + } else { + LLAppViewer::instance()->forceQuit(); + } +} + + + +// MandatoryUpdateMachine::WaitingForDownload +//----------------------------------------------------------------------------- + + +MandatoryUpdateMachine::WaitingForDownload::WaitingForDownload(MandatoryUpdateMachine & machine): + mMachine(machine), + mProgressView(0) +{ + ; // No op. +} + + +void MandatoryUpdateMachine::WaitingForDownload::enter(void) +{ + llinfos << "entering waiting for download" << llendl; + mProgressView = gViewerWindow->getProgressView(); + mProgressView->setMessage("Downloading update..."); + std::ostringstream stream; + stream << "There is a required update for your Second Life installation." << std::endl << + "Version " << mMachine.mUpdaterService.updatedVersion(); + mProgressView->setText(stream.str()); + mProgressView->setPercent(0); + mProgressView->setVisible(true); + mConnection = LLEventPumps::instance().obtain(LLUpdaterService::pumpName()). + listen("MandatoryUpdateMachine::CheckingForUpdate", boost::bind(&MandatoryUpdateMachine::WaitingForDownload::onEvent, this, _1)); +} + + +void MandatoryUpdateMachine::WaitingForDownload::exit(void) +{ + mProgressView->setVisible(false); +} + + +bool MandatoryUpdateMachine::WaitingForDownload::onEvent(LLSD const & event) +{ + switch(event["type"].asInteger()) { + case LLUpdaterService::DOWNLOAD_COMPLETE: + mMachine.setCurrentState(new ReadyToInstall(mMachine)); + break; + case LLUpdaterService::DOWNLOAD_ERROR: + mMachine.setCurrentState(new Error(mMachine)); + break; + case LLUpdaterService::PROGRESS: { + double downloadSize = event["download_size"].asReal(); + double bytesDownloaded = event["bytes_downloaded"].asReal(); + mProgressView->setPercent(100. * bytesDownloaded / downloadSize); + break; + } + default: + break; + } + + return false; +} + + + +// LLLoginInstance +//----------------------------------------------------------------------------- + + LLLoginInstance::LLLoginInstance() : mLoginModule(new LLLogin()), mNotifications(NULL), @@ -69,7 +472,8 @@ LLLoginInstance::LLLoginInstance() : mSkipOptionalUpdate(false), mAttemptComplete(false), mTransferRate(0.0f), - mDispatcher("LLLoginInstance", "change") + mDispatcher("LLLoginInstance", "change"), + mUpdaterService(0) { mLoginModule->getEventPump().listen("lllogininstance", boost::bind(&LLLoginInstance::handleLoginEvent, this, _1)); @@ -354,6 +758,15 @@ bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) void LLLoginInstance::updateApp(bool mandatory, const std::string& auth_msg) { + if(mandatory) + { + gViewerWindow->setShowProgress(false); + MandatoryUpdateMachine * machine = new MandatoryUpdateMachine(*this, *mUpdaterService); + mUpdateStateMachine.reset(machine); + machine->start(); + return; + } + // store off config state, as we might quit soon gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), TRUE); LLUIColorTable::instance().saveUserSettings(); diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index 159e05046c..b872d7d1b1 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -34,12 +34,15 @@ class LLLogin; class LLEventStream; class LLNotificationsInterface; +class LLUpdaterService; // This class hosts the login module and is used to // negotiate user authentication attempts. class LLLoginInstance : public LLSingleton<LLLoginInstance> { public: + class Disposable; + LLLoginInstance(); ~LLLoginInstance(); @@ -75,6 +78,7 @@ public: typedef boost::function<void()> UpdaterLauncherCallback; void setUpdaterLauncher(const UpdaterLauncherCallback& ulc) { mUpdaterLauncher = ulc; } + void setUpdaterService(LLUpdaterService * updaterService) { mUpdaterService = updaterService; } private: void constructAuthParams(LLPointer<LLCredential> user_credentials); void updateApp(bool mandatory, const std::string& message); @@ -104,6 +108,8 @@ private: int mLastExecEvent; UpdaterLauncherCallback mUpdaterLauncher; LLEventDispatcher mDispatcher; + LLUpdaterService * mUpdaterService; + boost::scoped_ptr<Disposable> mUpdateStateMachine; }; #endif diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 0f66713ab0..9493fddf50 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -25,7 +25,7 @@ */ #include "llviewerprecompiledheaders.h" - +#include "lltooltip.h" #include "llmediactrl.h" @@ -54,6 +54,10 @@ #include "llbutton.h" #include "llcheckboxctrl.h" #include "llnotifications.h" +#include "lllineeditor.h" +#include "llfloatermediabrowser.h" +#include "llfloaterwebcontent.h" +#include "llwindowshade.h" extern BOOL gRestoreGL; @@ -98,7 +102,9 @@ LLMediaCtrl::LLMediaCtrl( const Params& p) : mTextureHeight ( 1024 ), mClearCache(false), mHomePageMimeType(p.initial_mime_type), - mTrusted(p.trusted_content) + mTrusted(p.trusted_content), + mWindowShade(NULL), + mHoverTextChanged(false) { { LLColor4 color = p.caret_color().get(); @@ -127,7 +133,7 @@ LLMediaCtrl::LLMediaCtrl( const Params& p) : setTextureSize(screen_width, screen_height); } - mMediaTextureID.generate(); + mMediaTextureID = getKey(); // We don't need to create the media source up front anymore unless we have a non-empty home URL to navigate to. if(!mHomePageUrl.empty()) @@ -141,8 +147,6 @@ LLMediaCtrl::LLMediaCtrl( const Params& p) : // addChild( mBorder ); } -//////////////////////////////////////////////////////////////////////////////// -// note: this is now a singleton and destruction happens via initClass() now LLMediaCtrl::~LLMediaCtrl() { @@ -182,6 +186,13 @@ BOOL LLMediaCtrl::handleHover( S32 x, S32 y, MASK mask ) mMediaSource->mouseMove(x, y, mask); gViewerWindow->setCursor(mMediaSource->getLastSetCursor()); } + + // TODO: Is this the right way to handle hover text changes driven by the plugin? + if(mHoverTextChanged) + { + mHoverTextChanged = false; + handleToolTip(x, y, mask); + } return TRUE; } @@ -198,6 +209,35 @@ BOOL LLMediaCtrl::handleScrollWheel( S32 x, S32 y, S32 clicks ) } //////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL LLMediaCtrl::handleToolTip(S32 x, S32 y, MASK mask) +{ + std::string hover_text; + + if (mMediaSource && mMediaSource->hasMedia()) + hover_text = mMediaSource->getMediaPlugin()->getHoverText(); + + if(hover_text.empty()) + { + return FALSE; + } + else + { + S32 screen_x, screen_y; + + localPointToScreen(x, y, &screen_x, &screen_y); + LLRect sticky_rect_screen; + sticky_rect_screen.setCenterAndSize(screen_x, screen_y, 20, 20); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hover_text) + .sticky_rect(sticky_rect_screen)); + } + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// // BOOL LLMediaCtrl::handleMouseUp( S32 x, S32 y, MASK mask ) { @@ -338,85 +378,6 @@ void LLMediaCtrl::onFocusLost() // BOOL LLMediaCtrl::postBuild () { - LLLayoutStack::Params layout_p; - layout_p.name = "notification_stack"; - layout_p.rect = LLRect(0,getLocalRect().mTop,getLocalRect().mRight, 30); - layout_p.follows.flags = FOLLOWS_ALL; - layout_p.mouse_opaque = false; - layout_p.orientation = "vertical"; - - LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p); - addChild(stackp); - - LLLayoutPanel::Params panel_p; - panel_p.rect = LLRect(0, 30, 800, 0); - panel_p.min_height = 30; - panel_p.name = "notification_area"; - panel_p.visible = false; - panel_p.user_resize = false; - panel_p.background_visible = true; - panel_p.bg_alpha_image.name = "Yellow_Gradient"; - panel_p.auto_resize = false; - LLLayoutPanel* notification_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); - stackp->addChild(notification_panel); - - panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); - panel_p.auto_resize = true; - panel_p.mouse_opaque = false; - LLLayoutPanel* dummy_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); - stackp->addChild(dummy_panel); - - layout_p = LLUICtrlFactory::getDefaultParams<LLLayoutStack>(); - layout_p.rect = LLRect(0, 30, 800, 0); - layout_p.follows.flags = FOLLOWS_ALL; - layout_p.orientation = "horizontal"; - stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p); - notification_panel->addChild(stackp); - - panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); - panel_p.rect.height = 30; - LLLayoutPanel* panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); - stackp->addChild(panel); - - LLIconCtrl::Params icon_p; - icon_p.name = "notification_icon"; - icon_p.rect = LLRect(5, 23, 21, 8); - panel->addChild(LLUICtrlFactory::create<LLIconCtrl>(icon_p)); - - LLTextBox::Params text_p; - text_p.rect = LLRect(31, 20, 430, 0); - text_p.text_color = LLColor4::black; - text_p.font = LLFontGL::getFontSansSerif(); - text_p.font.style = "BOLD"; - text_p.name = "notification_text"; - text_p.use_ellipses = true; - panel->addChild(LLUICtrlFactory::create<LLTextBox>(text_p)); - - panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); - panel_p.auto_resize = false; - panel_p.user_resize = false; - panel_p.name="form_elements"; - panel_p.rect = LLRect(0, 30, 130, 0); - LLLayoutPanel* form_elements_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); - stackp->addChild(form_elements_panel); - - panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); - panel_p.auto_resize = false; - panel_p.user_resize = false; - panel_p.rect = LLRect(0, 30, 25, 0); - LLLayoutPanel* close_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); - stackp->addChild(close_panel); - - LLButton::Params button_p; - button_p.name = "close_notification"; - button_p.rect = LLRect(5, 23, 21, 7); - button_p.image_color=LLUIColorTable::instance().getColor("DkGray_66"); - button_p.image_unselected.name="Icon_Close_Foreground"; - button_p.image_selected.name="Icon_Close_Press"; - button_p.click_callback.function = boost::bind(&LLMediaCtrl::onCloseNotification, this); - - close_panel->addChild(LLUICtrlFactory::create<LLButton>(button_p)); - setVisibleCallback(boost::bind(&LLMediaCtrl::onVisibilityChange, this, _2)); return TRUE; } @@ -425,13 +386,15 @@ BOOL LLMediaCtrl::postBuild () // BOOL LLMediaCtrl::handleKeyHere( KEY key, MASK mask ) { - if (LLPanel::handleKeyHere(key, mask)) return TRUE; BOOL result = FALSE; if (mMediaSource) { result = mMediaSource->handleKeyHere(key, mask); } + + if ( ! result ) + result = LLPanel::handleKeyHere(key, mask); return result; } @@ -451,7 +414,6 @@ void LLMediaCtrl::handleVisibilityChange ( BOOL new_visibility ) // BOOL LLMediaCtrl::handleUnicodeCharHere(llwchar uni_char) { - if (LLPanel::handleUnicodeCharHere(uni_char)) return TRUE; BOOL result = FALSE; if (mMediaSource) @@ -459,6 +421,9 @@ BOOL LLMediaCtrl::handleUnicodeCharHere(llwchar uni_char) result = mMediaSource->handleUnicodeCharHere(uni_char); } + if ( ! result ) + result = LLPanel::handleUnicodeCharHere(uni_char); + return result; } @@ -914,11 +879,6 @@ void LLMediaCtrl::draw() if ( mBorder && mBorder->getVisible() ) mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus( this ) ); - if (mCurNotification && !mCurNotification->isActive()) - { - hideNotification(); - } - LLPanel::draw(); // Restore the previous values @@ -1026,7 +986,7 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) LLNotification::Params notify_params; notify_params.name = "PopupAttempt"; - notify_params.payload = LLSD().with("target", target).with("url", url).with("uuid", uuid).with("media_id", getKey()); + notify_params.payload = LLSD().with("target", target).with("url", url).with("uuid", uuid).with("media_id", mMediaTextureID); notify_params.functor.function = boost::bind(&LLMediaCtrl::onPopup, this, _1, _2); if (mTrusted) @@ -1081,6 +1041,31 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() << LL_ENDL; } break; + + case MEDIA_EVENT_AUTH_REQUEST: + { + LLNotification::Params auth_request_params; + auth_request_params.name = "AuthRequest"; + + // pass in host name and realm for site (may be zero length but will always exist) + LLSD args; + LLURL raw_url( self->getAuthURL().c_str() ); + args["HOST_NAME"] = raw_url.getAuthority(); + args["REALM"] = self->getAuthRealm(); + auth_request_params.substitutions = args; + + auth_request_params.payload = LLSD().with("media_id", mMediaTextureID); + auth_request_params.functor.function = boost::bind(&LLViewerMedia::onAuthSubmit, _1, _2); + LLNotifications::instance().add(auth_request_params); + }; + break; + + case MEDIA_EVENT_LINK_HOVERED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << LL_ENDL; + mHoverTextChanged = true; + }; + break; }; // chain all events to any potential observers of this object. @@ -1098,109 +1083,85 @@ void LLMediaCtrl::onPopup(const LLSD& notification, const LLSD& response) { if (response["open"]) { - LLWeb::loadURL(notification["payload"]["url"], notification["payload"]["target"], notification["payload"]["uuid"]); + // name of default floater to open + std::string floater_name = "media_browser"; + + // look for parent floater name + if ( gFloaterView ) + { + if ( gFloaterView->getParentFloater(this) ) + { + floater_name = gFloaterView->getParentFloater(this)->getInstanceName(); + } + else + { + lldebugs << "No gFloaterView->getParentFloater(this) for onPopuup()" << llendl; + }; + } + else + { + lldebugs << "No gFloaterView for onPopuup()" << llendl; + }; + + // (for now) open web content floater if that's our parent, otherwise, open the current media floater + // (this will change soon) + if ( floater_name == "web_content" ) + { + LLWeb::loadWebURL(notification["payload"]["url"], notification["payload"]["target"], notification["payload"]["uuid"]); + } + else + { + LLWeb::loadURL(notification["payload"]["url"], notification["payload"]["target"], notification["payload"]["uuid"]); + } } else { // Make sure the opening instance knows its window open request was denied, so it can clean things up. LLViewerMedia::proxyWindowClosed(notification["payload"]["uuid"]); } - } -void LLMediaCtrl::onCloseNotification() -{ - LLNotifications::instance().cancel(mCurNotification); -} - -void LLMediaCtrl::onClickIgnore(LLUICtrl* ctrl) +void LLMediaCtrl::showNotification(LLNotificationPtr notify) { - bool check = ctrl->getValue().asBoolean(); - if (mCurNotification && mCurNotification->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN) + delete mWindowShade; + + LLWindowShade::Params params; + params.name = "notification_shade"; + params.rect = getLocalRect(); + params.follows.flags = FOLLOWS_ALL; + params.notification = notify; + params.modal = true; + //HACK: don't hardcode this + if (notify->getIcon() == "Popup_Caution") { - // question was "show again" so invert value to get "ignore" - check = !check; + params.bg_image.name = "Yellow_Gradient"; + params.text_color = LLColor4::black; } - mCurNotification->setIgnored(check); -} - -void LLMediaCtrl::onClickNotificationButton(const std::string& name) -{ - if (!mCurNotification) return; - - LLSD response = mCurNotification->getResponseTemplate(); - response[name] = true; - - mCurNotification->respond(response); -} - -void LLMediaCtrl::showNotification(LLNotificationPtr notify) -{ - mCurNotification = notify; - - // add popup here - LLSD payload = notify->getPayload(); - - LLNotificationFormPtr formp = notify->getForm(); - LLLayoutPanel& panel = getChildRef<LLLayoutPanel>("notification_area"); - panel.setVisible(true); - panel.getChild<LLUICtrl>("notification_icon")->setValue(notify->getIcon()); - panel.getChild<LLUICtrl>("notification_text")->setValue(notify->getMessage()); - panel.getChild<LLUICtrl>("notification_text")->setToolTip(notify->getMessage()); - LLNotificationForm::EIgnoreType ignore_type = formp->getIgnoreType(); - LLLayoutPanel& form_elements = panel.getChildRef<LLLayoutPanel>("form_elements"); - form_elements.deleteAllChildren(); - - const S32 FORM_PADDING_HORIZONTAL = 10; - const S32 FORM_PADDING_VERTICAL = 3; - S32 cur_x = FORM_PADDING_HORIZONTAL; - - if (ignore_type != LLNotificationForm::IGNORE_NO) + else + //HACK: another one since XUI doesn't support what we need right now + if (notify->getName() == "AuthRequest") { - LLCheckBoxCtrl::Params checkbox_p; - checkbox_p.name = "ignore_check"; - checkbox_p.rect = LLRect(cur_x, form_elements.getRect().getHeight() - FORM_PADDING_VERTICAL, cur_x, FORM_PADDING_VERTICAL); - checkbox_p.label = formp->getIgnoreMessage(); - checkbox_p.label_text.text_color = LLColor4::black; - checkbox_p.commit_callback.function = boost::bind(&LLMediaCtrl::onClickIgnore, this, _1); - checkbox_p.initial_value = formp->getIgnored(); - - LLCheckBoxCtrl* check = LLUICtrlFactory::create<LLCheckBoxCtrl>(checkbox_p); - check->setRect(check->getBoundingRect()); - form_elements.addChild(check); - cur_x = check->getRect().mRight + FORM_PADDING_HORIZONTAL; + params.bg_image.name = "Yellow_Gradient"; + params.text_color = LLColor4::black; + params.can_close = false; } - - for (S32 i = 0; i < formp->getNumElements(); i++) + else { - LLSD form_element = formp->getElement(i); - if (form_element["type"].asString() == "button") - { - LLButton::Params button_p; - button_p.name = form_element["name"]; - button_p.label = form_element["text"]; - button_p.rect = LLRect(cur_x, form_elements.getRect().getHeight() - FORM_PADDING_VERTICAL, cur_x, FORM_PADDING_VERTICAL); - button_p.click_callback.function = boost::bind(&LLMediaCtrl::onClickNotificationButton, this, form_element["name"].asString()); - button_p.auto_resize = true; - - LLButton* button = LLUICtrlFactory::create<LLButton>(button_p); - button->autoResize(); - form_elements.addChild(button); - - cur_x = button->getRect().mRight + FORM_PADDING_HORIZONTAL; - } + //HACK: make this a property of the notification itself, "cancellable" + params.can_close = false; + params.text_color.control = "LabelTextColor"; } + mWindowShade = LLUICtrlFactory::create<LLWindowShade>(params); - form_elements.reshape(cur_x, form_elements.getRect().getHeight()); - - //LLWeb::loadURL(payload["url"], payload["target"]); + addChild(mWindowShade); + mWindowShade->show(); } void LLMediaCtrl::hideNotification() { - LLLayoutPanel& panel = getChildRef<LLLayoutPanel>("notification_area"); - panel.setVisible(FALSE); - - mCurNotification.reset(); + if (mWindowShade) + { + mWindowShade->hide(); + } } diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h index 96bb0c1df5..38a74f90d3 100644 --- a/indra/newview/llmediactrl.h +++ b/indra/newview/llmediactrl.h @@ -90,6 +90,7 @@ public: virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); virtual BOOL handleDoubleClick( S32 x, S32 y, MASK mask ); virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); + virtual BOOL handleToolTip(S32 x, S32 y, MASK mask); // navigation void navigateTo( std::string url_in, std::string mime_type = ""); @@ -168,9 +169,6 @@ public: private: void onVisibilityChange ( const LLSD& new_visibility ); void onPopup(const LLSD& notification, const LLSD& response); - void onCloseNotification(); - void onClickNotificationButton(const std::string& name); - void onClickIgnore(LLUICtrl* ctrl); const S32 mTextureDepthBytes; LLUUID mMediaTextureID; @@ -194,7 +192,8 @@ public: S32 mTextureWidth; S32 mTextureHeight; bool mClearCache; - boost::shared_ptr<class LLNotification> mCurNotification; + class LLWindowShade* mWindowShade; + bool mHoverTextChanged; }; #endif // LL_LLMediaCtrl_H diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 2e4be78be1..5f89097c17 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -73,7 +73,6 @@ #endif // LL_WINDOWS #include "llsdserialize.h" -#define USE_VIEWER_AUTH 0 const S32 BLACK_BORDER_HEIGHT = 160; const S32 MAX_PASSWORD = 16; @@ -190,10 +189,6 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, buildFromFile( "panel_login.xml"); -#if USE_VIEWER_AUTH - //leave room for the login menu bar - setRect(LLRect(0, rect.getHeight()-18, rect.getWidth(), 0)); -#endif // Legacy login web page is hidden under the menu bar. // Adjust reg-in-client web browser widget to not be hidden. if (gSavedSettings.getBOOL("RegInClient")) @@ -205,16 +200,12 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, reshape(rect.getWidth(), rect.getHeight()); } -#if !USE_VIEWER_AUTH getChild<LLLineEditor>("password_edit")->setKeystrokeCallback(onPassKey, this); // change z sort of clickable text to be behind buttons //sendChildToBack(getChildView("channel_text")); sendChildToBack(getChildView("forgot_password_text")); - LLLineEditor* edit = getChild<LLLineEditor>("password_edit"); - if (edit) edit->setDrawAsterixes(TRUE); - if(LLStartUp::getStartSLURL().getType() != LLSLURL::LOCATION) { LLSLURL slurl(gSavedSettings.getString("LoginLocation")); @@ -248,7 +239,6 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, LLTextBox* need_help_text = getChild<LLTextBox>("login_help"); need_help_text->setClickedCallback(onClickHelp, NULL); -#endif // get the web browser control LLMediaCtrl* web_browser = getChild<LLMediaCtrl>("login_html"); @@ -275,15 +265,9 @@ void LLPanelLogin::reshapeBrowser() LLMediaCtrl* web_browser = getChild<LLMediaCtrl>("login_html"); LLRect rect = gViewerWindow->getWindowRectScaled(); LLRect html_rect; -#if USE_VIEWER_AUTH - html_rect.setCenterAndSize( - rect.getCenterX() - 2, rect.getCenterY(), - rect.getWidth() + 6, rect.getHeight()); -#else html_rect.setCenterAndSize( rect.getCenterX() - 2, rect.getCenterY() + 40, rect.getWidth() + 6, rect.getHeight() - 78 ); -#endif web_browser->setRect( html_rect ); web_browser->reshape( html_rect.getWidth(), html_rect.getHeight(), TRUE ); reshape( rect.getWidth(), rect.getHeight(), 1 ); @@ -306,7 +290,6 @@ void LLPanelLogin::setSiteIsAlive( bool alive ) else // the site is not available (missing page, server down, other badness) { -#if !USE_VIEWER_AUTH if ( web_browser ) { // hide browser control (revealing default one) @@ -315,16 +298,6 @@ void LLPanelLogin::setSiteIsAlive( bool alive ) // mark as unavailable mHtmlAvailable = FALSE; } -#else - - if ( web_browser ) - { - web_browser->navigateToLocalPage( "loading-error" , "index.html" ); - - // mark as available - mHtmlAvailable = TRUE; - } -#endif } } @@ -364,7 +337,6 @@ void LLPanelLogin::draw() if ( mHtmlAvailable ) { -#if !USE_VIEWER_AUTH if (getChild<LLView>("login_widgets")->getVisible()) { // draw a background box in black @@ -373,7 +345,6 @@ void LLPanelLogin::draw() // just the blue background to the native client UI mLogoImage->draw(0, -264, width + 8, mLogoImage->getHeight()); } -#endif } else { @@ -419,12 +390,6 @@ void LLPanelLogin::setFocus(BOOL b) // static void LLPanelLogin::giveFocus() { -#if USE_VIEWER_AUTH - if (sInstance) - { - sInstance->setFocus(TRUE); - } -#else if( sInstance ) { // Grab focus and move cursor to first blank input field @@ -453,17 +418,18 @@ void LLPanelLogin::giveFocus() edit->selectAll(); } } -#endif } // static void LLPanelLogin::showLoginWidgets() { + // *NOTE: Mani - This may or may not be obselete code. + // It seems to be part of the defunct? reg-in-client project. sInstance->getChildView("login_widgets")->setVisible( true); LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html"); sInstance->reshapeBrowser(); // *TODO: Append all the usual login parameters, like first_login=Y etc. - std::string splash_screen_url = sInstance->getString("real_url"); + std::string splash_screen_url = LLGridManager::getInstance()->getLoginPage(); web_browser->navigateTo( splash_screen_url, "text/html" ); LLUICtrl* username_edit = sInstance->getChild<LLUICtrl>("username_edit"); username_edit->setFocus(TRUE); @@ -833,73 +799,6 @@ void LLPanelLogin::loadLoginPage() curl_free(curl_grid); gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); gLoginMenuBarView->setBackgroundColor(gMenuBarView->getBackgroundColor()); - - -#if USE_VIEWER_AUTH - LLURLSimString::sInstance.parse(); - - std::string location; - std::string region; - std::string password; - - if (LLURLSimString::parse()) - { - std::ostringstream oRegionStr; - location = "specify"; - oRegionStr << LLURLSimString::sInstance.mSimName << "/" << LLURLSimString::sInstance.mX << "/" - << LLURLSimString::sInstance.mY << "/" - << LLURLSimString::sInstance.mZ; - region = oRegionStr.str(); - } - else - { - location = gSavedSettings.getString("LoginLocation"); - } - - std::string username; - - if(gSavedSettings.getLLSD("UserLoginInfo").size() == 3) - { - LLSD cmd_line_login = gSavedSettings.getLLSD("UserLoginInfo"); - username = cmd_line_login[0].asString() + " " + cmd_line_login[1]; - password = cmd_line_login[2].asString(); - } - - - char* curl_region = curl_escape(region.c_str(), 0); - - oStr <<"username=" << username << - "&location=" << location << "®ion=" << curl_region; - - curl_free(curl_region); - - if (!password.empty()) - { - oStr << "&password=" << password; - } - else if (!(password = load_password_from_disk()).empty()) - { - oStr << "&password=$1$" << password; - } - if (gAutoLogin) - { - oStr << "&auto_login=TRUE"; - } - if (gSavedSettings.getBOOL("ShowStartLocation")) - { - oStr << "&show_start_location=TRUE"; - } - if (gSavedSettings.getBOOL("RememberPassword")) - { - oStr << "&remember_password=TRUE"; - } -#ifndef LL_RELEASE_FOR_DOWNLOAD - oStr << "&show_grid=TRUE"; -#else - if (gSavedSettings.getBOOL("ForceShowGrid")) - oStr << "&show_grid=TRUE"; -#endif -#endif LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html"); diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index 614700fb0a..8ae3553857 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -59,6 +59,7 @@ #include "llvovolume.h" #include "llweb.h" #include "llwindow.h" +#include "llwindowshade.h" #include "llfloatertools.h" // to enable hide if build tools are up // Functions pulled from pipeline.cpp @@ -90,7 +91,8 @@ LLPanelPrimMediaControls::LLPanelPrimMediaControls() : mTargetObjectNormal(LLVector3::zero), mZoomObjectID(LLUUID::null), mZoomObjectFace(0), - mVolumeSliderVisible(0) + mVolumeSliderVisible(0), + mWindowShade(NULL) { mCommitCallbackRegistrar.add("MediaCtrl.Close", boost::bind(&LLPanelPrimMediaControls::onClickClose, this)); mCommitCallbackRegistrar.add("MediaCtrl.Back", boost::bind(&LLPanelPrimMediaControls::onClickBack, this)); @@ -205,6 +207,9 @@ BOOL LLPanelPrimMediaControls::postBuild() mMediaAddress->setFocusReceivedCallback(boost::bind(&LLPanelPrimMediaControls::onInputURL, _1, this )); + LLWindowShade::Params window_shade_params; + window_shade_params.name = "window_shade"; + mCurrentZoom = ZOOM_NONE; // clicks on buttons do not remove keyboard focus from media setIsChrome(TRUE); @@ -698,6 +703,24 @@ void LLPanelPrimMediaControls::updateShape() /*virtual*/ void LLPanelPrimMediaControls::draw() { + LLViewerMediaImpl* impl = getTargetMediaImpl(); + if (impl) + { + LLNotificationPtr notification = impl->getCurrentNotification(); + if (notification != mActiveNotification) + { + mActiveNotification = notification; + if (notification) + { + showNotification(notification); + } + else + { + hideNotification(); + } + } + } + F32 alpha = getDrawContext().mAlpha; if(mFadeTimer.getStarted()) { @@ -1295,3 +1318,38 @@ bool LLPanelPrimMediaControls::shouldVolumeSliderBeVisible() { return mVolumeSliderVisible > 0; } + +void LLPanelPrimMediaControls::showNotification(LLNotificationPtr notify) +{ + delete mWindowShade; + LLWindowShade::Params params; + params.rect = mMediaRegion->getLocalRect(); + params.follows.flags = FOLLOWS_ALL; + params.notification = notify; + + //HACK: don't hardcode this + if (notify->getIcon() == "Popup_Caution") + { + params.bg_image.name = "Yellow_Gradient"; + params.text_color = LLColor4::black; + } + else + { + //HACK: make this a property of the notification itself, "cancellable" + params.can_close = false; + params.text_color.control = "LabelTextColor"; + } + + mWindowShade = LLUICtrlFactory::create<LLWindowShade>(params); + + mMediaRegion->addChild(mWindowShade); + mWindowShade->show(); +} + +void LLPanelPrimMediaControls::hideNotification() +{ + if (mWindowShade) + { + mWindowShade->hide(); + } +} diff --git a/indra/newview/llpanelprimmediacontrols.h b/indra/newview/llpanelprimmediacontrols.h index 3ec24f0e24..0b9664359c 100644 --- a/indra/newview/llpanelprimmediacontrols.h +++ b/indra/newview/llpanelprimmediacontrols.h @@ -29,6 +29,7 @@ #include "llpanel.h" #include "llviewermedia.h" +#include "llnotificationptr.h" class LLButton; class LLCoordWindow; @@ -37,6 +38,7 @@ class LLLayoutStack; class LLProgressBar; class LLSliderCtrl; class LLViewerMediaImpl; +class LLWindowShade; class LLPanelPrimMediaControls : public LLPanel { @@ -54,6 +56,9 @@ public: void updateShape(); bool isMouseOver(); + void showNotification(LLNotificationPtr notify); + void hideNotification(); + enum EZoomLevel { ZOOM_NONE = 0, @@ -162,6 +167,7 @@ private: LLUICtrl *mRightBookend; LLUIImage* mBackgroundImage; LLUIImage* mVolumeSliderBackgroundImage; + LLWindowShade* mWindowShade; F32 mSkipStep; S32 mMinWidth; S32 mMinHeight; @@ -204,6 +210,8 @@ private: S32 mZoomObjectFace; S32 mVolumeSliderVisible; + + LLNotificationPtr mActiveNotification; }; #endif // LL_PANELPRIMMEDIACONTROLS_H diff --git a/indra/newview/llprogressview.cpp b/indra/newview/llprogressview.cpp index db02d76139..31fde5d58a 100644 --- a/indra/newview/llprogressview.cpp +++ b/indra/newview/llprogressview.cpp @@ -133,13 +133,13 @@ void LLProgressView::setVisible(BOOL visible) mFadeTimer.start(); } // showing progress view - else if (!getVisible() && visible) + else if (visible && (!getVisible() || mFadeTimer.getStarted())) { setFocus(TRUE); mFadeTimer.stop(); mProgressTimer.start(); LLPanel::setVisible(TRUE); - } + } } diff --git a/indra/newview/llsimplestat.h b/indra/newview/llsimplestat.h new file mode 100644 index 0000000000..a90e503adb --- /dev/null +++ b/indra/newview/llsimplestat.h @@ -0,0 +1,158 @@ +/** + * @file llsimplestat.h + * @brief Runtime statistics accumulation. + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_SIMPLESTAT_H +#define LL_SIMPLESTAT_H + +// History +// +// The original source for this code is the server repositories' +// llcommon/llstat.h file. This particular code was added after the +// viewer/server code schism but before the effort to convert common +// code to libraries was complete. Rather than add to merge issues, +// the needed code was cut'n'pasted into this new header as it isn't +// too awful a burden. Post-modularization, we can look at removing +// this redundancy. + + +/** + * @class LLSimpleStatCounter + * @brief Just counts events. + * + * Really not needed but have a pattern in mind in the future. + * Interface limits what can be done at that's just fine. + * + * *TODO: Update/transfer unit tests + * Unit tests: indra/test/llcommon_llstat_tut.cpp + */ +class LLSimpleStatCounter +{ +public: + inline LLSimpleStatCounter() { reset(); } + // Default destructor and assignment operator are valid + + inline void reset() { mCount = 0; } + + inline void merge(const LLSimpleStatCounter & src) + { mCount += src.mCount; } + + inline U32 operator++() { return ++mCount; } + + inline U32 getCount() const { return mCount; } + +protected: + U32 mCount; +}; + + +/** + * @class LLSimpleStatMMM + * @brief Templated collector of min, max and mean data for stats. + * + * Fed a stream of data samples, keeps a running account of the + * min, max and mean seen since construction or the last reset() + * call. A freshly-constructed or reset instance returns counts + * and values of zero. + * + * Overflows and underflows (integer, inf or -inf) and NaN's + * are the caller's problem. As is loss of precision when + * the running sum's exponent (when parameterized by a floating + * point of some type) differs from a given data sample's. + * + * Unit tests: indra/test/llcommon_llstat_tut.cpp + */ +template <typename VALUE_T = F32> +class LLSimpleStatMMM +{ +public: + typedef VALUE_T Value; + +public: + LLSimpleStatMMM() { reset(); } + // Default destructor and assignment operator are valid + + /** + * Resets the object returning all counts and derived + * values back to zero. + */ + void reset() + { + mCount = 0; + mMin = Value(0); + mMax = Value(0); + mTotal = Value(0); + } + + void record(Value v) + { + if (mCount) + { + mMin = llmin(mMin, v); + mMax = llmax(mMax, v); + } + else + { + mMin = v; + mMax = v; + } + mTotal += v; + ++mCount; + } + + void merge(const LLSimpleStatMMM<VALUE_T> & src) + { + if (! mCount) + { + *this = src; + } + else if (src.mCount) + { + mMin = llmin(mMin, src.mMin); + mMax = llmax(mMax, src.mMax); + mCount += src.mCount; + mTotal += src.mTotal; + } + } + + inline U32 getCount() const { return mCount; } + inline Value getMin() const { return mMin; } + inline Value getMax() const { return mMax; } + inline Value getMean() const { return mCount ? mTotal / mCount : mTotal; } + +protected: + U32 mCount; + Value mMin; + Value mMax; + Value mTotal; +}; + +#endif // LL_SIMPLESTAT_H diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index e9fc25404a..1b8be7a5b2 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -115,6 +115,7 @@ LLStatusBar::LLStatusBar(const LLRect& rect) mSGBandwidth(NULL), mSGPacketLoss(NULL), mBtnVolume(NULL), + mBoxBalance(NULL), mBalance(0), mHealth(100), mSquareMetersCredit(0), @@ -168,6 +169,9 @@ BOOL LLStatusBar::postBuild() getChild<LLUICtrl>("buyL")->setCommitCallback( boost::bind(&LLStatusBar::onClickBuyCurrency, this)); + mBoxBalance = getChild<LLTextBox>("balance"); + mBoxBalance->setClickedCallback( &LLStatusBar::onClickBalance, this ); + mBtnVolume = getChild<LLButton>( "volume_btn" ); mBtnVolume->setClickedCallback( onClickVolume, this ); mBtnVolume->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterVolume, this)); @@ -304,6 +308,7 @@ void LLStatusBar::setVisibleForMouselook(bool visible) { mTextTime->setVisible(visible); getChild<LLUICtrl>("balance_bg")->setVisible(visible); + mBoxBalance->setVisible(visible); mBtnVolume->setVisible(visible); mMediaToggle->setVisible(visible); mSGBandwidth->setVisible(visible); @@ -330,16 +335,15 @@ void LLStatusBar::setBalance(S32 balance) std::string money_str = LLResMgr::getInstance()->getMonetaryString( balance ); - LLTextBox* balance_box = getChild<LLTextBox>("balance"); LLStringUtil::format_map_t string_args; string_args["[AMT]"] = llformat("%s", money_str.c_str()); std::string label_str = getString("buycurrencylabel", string_args); - balance_box->setValue(label_str); + mBoxBalance->setValue(label_str); // Resize the L$ balance background to be wide enough for your balance plus the buy button { const S32 HPAD = 24; - LLRect balance_rect = balance_box->getTextBoundingRect(); + LLRect balance_rect = mBoxBalance->getTextBoundingRect(); LLRect buy_rect = getChildView("buyL")->getRect(); LLView* balance_bg_view = getChildView("balance_bg"); LLRect balance_bg_rect = balance_bg_view->getRect(); @@ -506,6 +510,14 @@ static void onClickVolume(void* data) } //static +void LLStatusBar::onClickBalance(void* ) +{ + // Force a balance request message: + LLStatusBar::sendMoneyBalanceRequest(); + // The refresh of the display (call to setBalance()) will be done by process_money_balance_reply() +} + +//static void LLStatusBar::onClickMediaToggle(void* data) { LLStatusBar *status_bar = (LLStatusBar*)data; diff --git a/indra/newview/llstatusbar.h b/indra/newview/llstatusbar.h index 2388aeb0c8..4ea3183d18 100644 --- a/indra/newview/llstatusbar.h +++ b/indra/newview/llstatusbar.h @@ -94,6 +94,7 @@ private: void onClickScreen(S32 x, S32 y); static void onClickMediaToggle(void* data); + static void onClickBalance(void* data); private: LLTextBox *mTextTime; @@ -102,6 +103,7 @@ private: LLStatGraph *mSGPacketLoss; LLButton *mBtnVolume; + LLTextBox *mBoxBalance; LLButton *mMediaToggle; LLView* mScriptOut; LLFrameTimer mClockUpdateTimer; diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 13fd51f473..4f63abb152 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include <iostream> +#include <map> #include "llstl.h" @@ -49,6 +50,7 @@ #include "llviewertexture.h" #include "llviewerregion.h" #include "llviewerstats.h" +#include "llviewerassetstats.h" #include "llworld.h" ////////////////////////////////////////////////////////////////////////////// @@ -143,7 +145,7 @@ public: /*virtual*/ bool deleteOK(); // called from update() (WORK THREAD) ~LLTextureFetchWorker(); - void relese() { --mActiveCount; } + // void relese() { --mActiveCount; } S32 callbackHttpGet(const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer, @@ -161,9 +163,11 @@ public: mGetReason = reason; } - void setCanUseHTTP(bool can_use_http) {mCanUseHTTP = can_use_http;} - bool getCanUseHTTP()const {return mCanUseHTTP ;} + void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; } + bool getCanUseHTTP() const { return mCanUseHTTP; } + LLTextureFetch & getFetcher() { return *mFetcher; } + protected: LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, S32 discard, S32 size); @@ -277,6 +281,8 @@ private: S32 mLastPacket; U16 mTotalPackets; U8 mImageCodec; + + LLViewerAssetStats::duration_t mMetricsStartTime; }; ////////////////////////////////////////////////////////////////////////////// @@ -344,6 +350,18 @@ public: } mFetcher->removeFromHTTPQueue(mID, data_size); + + if (worker->mMetricsStartTime) + { + LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, + true, + LLImageBase::TYPE_AVATAR_BAKE == worker->mType, + LLViewerAssetStatsFF::get_timestamp() - worker->mMetricsStartTime); + worker->mMetricsStartTime = 0; + } + LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, + true, + LLImageBase::TYPE_AVATAR_BAKE == worker->mType); } else { @@ -368,6 +386,229 @@ private: ////////////////////////////////////////////////////////////////////////////// +// Cross-thread messaging for asset metrics. + +/** + * @brief Base class for cross-thread requests made of the fetcher + * + * I believe the intent of the LLQueuedThread class was to + * have these operations derived from LLQueuedThread::QueuedRequest + * but the texture fetcher has elected to manage the queue + * in its own manner. So these are free-standing objects which are + * managed in simple FIFO order on the mCommands queue of the + * LLTextureFetch object. + * + * What each represents is a simple command sent from an + * outside thread into the TextureFetch thread to be processed + * in order and in a timely fashion (though not an absolute + * higher priority than other operations of the thread). + * Each operation derives a new class from the base customizing + * members, constructors and the doWork() method to effect + * the command. + * + * The flow is one-directional. There are two global instances + * of the LLViewerAssetStats collector, one for the main program's + * thread pointed to by gViewerAssetStatsMain and one for the + * TextureFetch thread pointed to by gViewerAssetStatsThread1. + * Common operations has each thread recording metrics events + * into the respective collector unconcerned with locking and + * the state of any other thread. But when the agent moves into + * a different region or the metrics timer expires and a report + * needs to be sent back to the grid, messaging across threads + * is required to distribute data and perform global actions. + * In pseudo-UML, it looks like: + * + * Main Thread1 + * . . + * . . + * +-----+ . + * | AM | . + * +--+--+ . + * +-------+ | . + * | Main | +--+--+ . + * | | | SRE |---. . + * | Stats | +-----+ \ . + * | | | \ (uuid) +-----+ + * | Coll. | +--+--+ `-------->| SR | + * +-------+ | MSC | +--+--+ + * | ^ +-----+ | + * | | (uuid) / . +-----+ (uuid) + * | `--------' . | MSC |---------. + * | . +-----+ | + * | +-----+ . v + * | | TE | . +-------+ + * | +--+--+ . | Thd1 | + * | | . | | + * | +-----+ . | Stats | + * `--------->| RSC | . | | + * +--+--+ . | Coll. | + * | . +-------+ + * +--+--+ . | + * | SME |---. . | + * +-----+ \ . | + * . \ (clone) +-----+ | + * . `-------->| SM | | + * . +--+--+ | + * . | | + * . +-----+ | + * . | RSC |<--------' + * . +-----+ + * . | + * . +-----+ + * . | CP |--> HTTP POST + * . +-----+ + * . . + * . . + * + * + * Key: + * + * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in + * the other thread providing the new UUID of the region. + * TFReqSetRegion carries the data. + * SR - Set Region. New region UUID is sent to the thread-local + * collector. + * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command + * including an ownership transfer of a cloned LLViewerAssetStats. + * TFReqSendMetrics carries the data. + * SM - Send Metrics. Global metrics reporting operation. Takes + * the cloned stats from the command, merges it with the + * thread's local stats, converts to LLSD and sends it on + * to the grid. + * AM - Agent Moved. Agent has completed some sort of move to a + * new region. + * TE - Timer Expired. Metrics timer has expired (on the order + * of 10 minutes). + * CP - CURL Post + * MSC - Modify Stats Collector. State change in the thread-local + * collector. Typically a region change which affects the + * global pointers used to find the 'current stats'. + * RSC - Read Stats Collector. Extract collector data cloning it + * (i.e. deep copy) when necessary. + * + */ +class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest +{ +public: + // Default ctors and assignment operator are correct. + + virtual ~TFRequest() + {} + + // Patterned after QueuedRequest's method but expected behavior + // is different. Always expected to complete on the first call + // and work dispatcher will assume the same and delete the + // request after invocation. + virtual bool doWork(LLTextureFetch * fetcher) = 0; +}; + +namespace +{ + +/** + * @brief Implements a 'Set Region' cross-thread command. + * + * When an agent moves to a new region, subsequent metrics need + * to be binned into a new or existing stats collection in 1:1 + * relationship with the region. We communicate this region + * change across the threads involved in the communication with + * this message. + * + * Corresponds to LLTextureFetch::commandSetRegion() + */ +class TFReqSetRegion : public LLTextureFetch::TFRequest +{ +public: + TFReqSetRegion(U64 region_handle) + : LLTextureFetch::TFRequest(), + mRegionHandle(region_handle) + {} + TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined + + virtual ~TFReqSetRegion() + {} + + virtual bool doWork(LLTextureFetch * fetcher); + +public: + const U64 mRegionHandle; +}; + + +/** + * @brief Implements a 'Send Metrics' cross-thread command. + * + * This is the big operation. The main thread gathers metrics + * for a period of minutes into LLViewerAssetStats and other + * objects then makes a snapshot of the data by cloning the + * collector. This command transfers the clone, along with a few + * additional arguments (UUIDs), handing ownership to the + * TextureFetch thread. It then merges its own data into the + * cloned copy, converts to LLSD and kicks off an HTTP POST of + * the resulting data to the currently active metrics collector. + * + * Corresponds to LLTextureFetch::commandSendMetrics() + */ +class TFReqSendMetrics : public LLTextureFetch::TFRequest +{ +public: + /** + * Construct the 'Send Metrics' command to have the TextureFetch + * thread add and log metrics data. + * + * @param caps_url URL of a "ViewerMetrics" Caps target + * to receive the data. Does not have to + * be associated with a particular region. + * + * @param session_id UUID of the agent's session. + * + * @param agent_id UUID of the agent. (Being pure here...) + * + * @param main_stats Pointer to a clone of the main thread's + * LLViewerAssetStats data. Thread1 takes + * ownership of the copy and disposes of it + * when done. + */ + TFReqSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLViewerAssetStats * main_stats) + : LLTextureFetch::TFRequest(), + mCapsURL(caps_url), + mSessionID(session_id), + mAgentID(agent_id), + mMainStats(main_stats) + {} + TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined + + virtual ~TFReqSendMetrics(); + + virtual bool doWork(LLTextureFetch * fetcher); + +public: + const std::string mCapsURL; + const LLUUID mSessionID; + const LLUUID mAgentID; + LLViewerAssetStats * mMainStats; +}; + +/* + * Examines the merged viewer metrics report and if found to be too long, + * will attempt to truncate it in some reasonable fashion. + * + * @param max_regions Limit of regions allowed in report. + * + * @param metrics Full, merged viewer metrics report. + * + * @returns If data was truncated, returns true. + */ +bool truncate_viewer_metrics(int max_regions, LLSD & metrics); + +} // end of anonymous namespace + + +////////////////////////////////////////////////////////////////////////////// + //static const char* LLTextureFetchWorker::sStateDescs[] = { "INVALID", @@ -385,6 +626,9 @@ const char* LLTextureFetchWorker::sStateDescs[] = { "DONE", }; +// static +volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break + // called from MAIN THREAD LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, @@ -434,7 +678,8 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, mFirstPacket(0), mLastPacket(-1), mTotalPackets(0), - mImageCodec(IMG_CODEC_INVALID) + mImageCodec(IMG_CODEC_INVALID), + mMetricsStartTime(0) { mCanUseNET = mUrl.empty() ; @@ -602,6 +847,7 @@ bool LLTextureFetchWorker::doWork(S32 param) return true; // abort } } + if(mImagePriority < F_ALMOST_ZERO) { if (mState == INIT || mState == LOAD_FROM_NETWORK || mState == LOAD_FROM_SIMULATOR) @@ -811,7 +1057,15 @@ bool LLTextureFetchWorker::doWork(S32 param) mRequestedDiscard = mDesiredDiscard; mSentRequest = QUEUED; mFetcher->addToNetworkQueue(this); + if (! mMetricsStartTime) + { + mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, + false, + LLImageBase::TYPE_AVATAR_BAKE == mType); setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + return false; } else @@ -820,6 +1074,12 @@ bool LLTextureFetchWorker::doWork(S32 param) //llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end()); // Make certain this is in the network queue //mFetcher->addToNetworkQueue(this); + //if (! mMetricsStartTime) + //{ + // mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + //} + //LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, false, + // LLImageBase::TYPE_AVATAR_BAKE == mType); //setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); return false; } @@ -843,11 +1103,30 @@ bool LLTextureFetchWorker::doWork(S32 param) } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); mState = DECODE_IMAGE; - mWriteToCacheState = SHOULD_WRITE ; + mWriteToCacheState = SHOULD_WRITE; + + if (mMetricsStartTime) + { + LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE, + false, + LLImageBase::TYPE_AVATAR_BAKE == mType, + LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime); + mMetricsStartTime = 0; + } + LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE, + false, + LLImageBase::TYPE_AVATAR_BAKE == mType); } else { mFetcher->addToNetworkQueue(this); // failsafe + if (! mMetricsStartTime) + { + mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, + false, + LLImageBase::TYPE_AVATAR_BAKE == mType); setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); } return false; @@ -909,6 +1188,14 @@ bool LLTextureFetchWorker::doWork(S32 param) mState = WAIT_HTTP_REQ; mFetcher->addToHTTPQueue(mID); + if (! mMetricsStartTime) + { + mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, + true, + LLImageBase::TYPE_AVATAR_BAKE == mType); + // Will call callbackHttpGet when curl request completes std::vector<std::string> headers; headers.push_back("Accept: image/x-j2c"); @@ -1523,7 +1810,7 @@ bool LLTextureFetchWorker::writeToCacheComplete() ////////////////////////////////////////////////////////////////////////////// // public -LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded) +LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode) : LLWorkerThread("TextureFetch", threaded), mDebugCount(0), mDebugPause(FALSE), @@ -1535,8 +1822,10 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mImageDecodeThread(imagedecodethread), mTextureBandwidth(0), mHTTPTextureBits(0), - mCurlGetRequest(NULL) + mCurlGetRequest(NULL), + mQAMode(qa_mode) { + mCurlPOSTRequestCount = 0; mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold")); } @@ -1545,6 +1834,13 @@ LLTextureFetch::~LLTextureFetch() { clearDeleteList() ; + while (! mCommands.empty()) + { + TFRequest * req(mCommands.front()); + mCommands.erase(mCommands.begin()); + delete req; + } + // ~LLQueuedThread() called here } @@ -1825,8 +2121,76 @@ bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority) return res; } +// Replicates and expands upon the base class's +// getPending() implementation. getPending() and +// runCondition() replicate one another's logic to +// an extent and are sometimes used for the same +// function (deciding whether or not to sleep/pause +// a thread). So the implementations need to stay +// in step, at least until this can be refactored and +// the redundancy eliminated. +// +// May be called from any thread + +//virtual +S32 LLTextureFetch::getPending() +{ + S32 res; + lockData(); + { + LLMutexLock lock(&mQueueMutex); + + res = mRequestQueue.size(); + res += mCurlPOSTRequestCount; + res += mCommands.size(); + } + unlockData(); + return res; +} + +// virtual +bool LLTextureFetch::runCondition() +{ + // Caller is holding the lock on LLThread's condition variable. + + // LLQueuedThread, unlike its base class LLThread, makes this a + // private method which is unfortunate. I want to use it directly + // but I'm going to have to re-implement the logic here (or change + // declarations, which I don't want to do right now). + // + // Changes here may need to be reflected in getPending(). + + bool have_no_commands(false); + { + LLMutexLock lock(&mQueueMutex); + + have_no_commands = mCommands.empty(); + } + + bool have_no_curl_requests(0 == mCurlPOSTRequestCount); + + return ! (have_no_commands + && have_no_curl_requests + && (mRequestQueue.empty() && mIdleThread)); // From base class +} + ////////////////////////////////////////////////////////////////////////////// +// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs) +void LLTextureFetch::commonUpdate() +{ + // Run a cross-thread command, if any. + cmdDoWork(); + + // Update Curl on same thread as mCurlGetRequest was constructed + S32 processed = mCurlGetRequest->process(); + if (processed > 0) + { + lldebugs << "processed: " << processed << " messages." << llendl; + } +} + + // MAIN THREAD //virtual S32 LLTextureFetch::update(U32 max_time_ms) @@ -1852,12 +2216,7 @@ S32 LLTextureFetch::update(U32 max_time_ms) if (!mThreaded) { - // Update Curl on same thread as mCurlGetRequest was constructed - S32 processed = mCurlGetRequest->process(); - if (processed > 0) - { - lldebugs << "processed: " << processed << " messages." << llendl; - } + commonUpdate(); } return res; @@ -1912,12 +2271,7 @@ void LLTextureFetch::threadedUpdate() } process_timer.reset(); - // Update Curl on same thread as mCurlGetRequest was constructed - S32 processed = mCurlGetRequest->process(); - if (processed > 0) - { - lldebugs << "processed: " << processed << " messages." << llendl; - } + commonUpdate(); #if 0 const F32 INFO_TIME = 1.0f; @@ -2367,3 +2721,280 @@ void LLTextureFetch::dump() } } +////////////////////////////////////////////////////////////////////////////// + +// cross-thread command methods + +void LLTextureFetch::commandSetRegion(U64 region_handle) +{ + TFReqSetRegion * req = new TFReqSetRegion(region_handle); + + cmdEnqueue(req); +} + +void LLTextureFetch::commandSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLViewerAssetStats * main_stats) +{ + TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats); + + cmdEnqueue(req); +} + +void LLTextureFetch::commandDataBreak() +{ + // The pedantically correct way to implement this is to create a command + // request object in the above fashion and enqueue it. However, this is + // simple data of an advisorial not operational nature and this case + // of shared-write access is tolerable. + + LLTextureFetch::svMetricsDataBreak = true; +} + +void LLTextureFetch::cmdEnqueue(TFRequest * req) +{ + lockQueue(); + mCommands.push_back(req); + unlockQueue(); + + unpause(); +} + +LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue() +{ + TFRequest * ret = 0; + + lockQueue(); + if (! mCommands.empty()) + { + ret = mCommands.front(); + mCommands.erase(mCommands.begin()); + } + unlockQueue(); + + return ret; +} + +void LLTextureFetch::cmdDoWork() +{ + if (mDebugPause) + { + return; // debug: don't do any work + } + + TFRequest * req = cmdDequeue(); + if (req) + { + // One request per pass should really be enough for this. + req->doWork(this); + delete req; + } +} + + +////////////////////////////////////////////////////////////////////////////// + +// Private (anonymous) class methods implementing the command scheme. + +namespace +{ + +/** + * Implements the 'Set Region' command. + * + * Thread: Thread1 (TextureFetch) + */ +bool +TFReqSetRegion::doWork(LLTextureFetch *) +{ + LLViewerAssetStatsFF::set_region_thread1(mRegionHandle); + + return true; +} + + +TFReqSendMetrics::~TFReqSendMetrics() +{ + delete mMainStats; + mMainStats = 0; +} + + +/** + * Implements the 'Send Metrics' command. Takes over + * ownership of the passed LLViewerAssetStats pointer. + * + * Thread: Thread1 (TextureFetch) + */ +bool +TFReqSendMetrics::doWork(LLTextureFetch * fetcher) +{ + /* + * HTTP POST responder. Doesn't do much but tries to + * detect simple breaks in recording the metrics stream. + * + * The 'volatile' modifiers don't indicate signals, + * mmap'd memory or threads, really. They indicate that + * the referenced data is part of a pseudo-closure for + * this responder rather than being required for correct + * operation. + * + * We don't try very hard with the POST request. We give + * it one shot and that's more-or-less it. With a proper + * refactoring of the LLQueuedThread usage, these POSTs + * could be put in a request object and made more reliable. + */ + class lcl_responder : public LLCurl::Responder + { + public: + lcl_responder(LLTextureFetch * fetcher, + S32 expected_sequence, + volatile const S32 & live_sequence, + volatile bool & reporting_break, + volatile bool & reporting_started) + : LLCurl::Responder(), + mFetcher(fetcher), + mExpectedSequence(expected_sequence), + mLiveSequence(live_sequence), + mReportingBreak(reporting_break), + mReportingStarted(reporting_started) + { + mFetcher->incrCurlPOSTCount(); + } + + ~lcl_responder() + { + mFetcher->decrCurlPOSTCount(); + } + + // virtual + void error(U32 status_num, const std::string & reason) + { + if (mLiveSequence == mExpectedSequence) + { + mReportingBreak = true; + } + LL_WARNS("Texture") << "Break in metrics stream due to POST failure to metrics collection service. Reason: " + << reason << LL_ENDL; + } + + // virtual + void result(const LLSD & content) + { + if (mLiveSequence == mExpectedSequence) + { + mReportingBreak = false; + mReportingStarted = true; + } + } + + private: + LLTextureFetch * mFetcher; + S32 mExpectedSequence; + volatile const S32 & mLiveSequence; + volatile bool & mReportingBreak; + volatile bool & mReportingStarted; + + }; // class lcl_responder + + if (! gViewerAssetStatsThread1) + return true; + + static volatile bool reporting_started(false); + static volatile S32 report_sequence(0); + + // We've taken over ownership of the stats copy at this + // point. Get a working reference to it for merging here + // but leave it in 'this'. Destructor will rid us of it. + LLViewerAssetStats & main_stats = *mMainStats; + + // Merge existing stats into those from main, convert to LLSD + main_stats.merge(*gViewerAssetStatsThread1); + LLSD merged_llsd = main_stats.asLLSD(true); + + // Add some additional meta fields to the content + merged_llsd["session_id"] = mSessionID; + merged_llsd["agent_id"] = mAgentID; + merged_llsd["message"] = "ViewerAssetMetrics"; // Identifies the type of metrics + merged_llsd["sequence"] = report_sequence; // Sequence number + merged_llsd["initial"] = ! reporting_started; // Initial data from viewer + merged_llsd["break"] = LLTextureFetch::svMetricsDataBreak; // Break in data prior to this report + + // Update sequence number + if (S32_MAX == ++report_sequence) + report_sequence = 0; + + // Limit the size of the stats report if necessary. + merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd); + + if (! mCapsURL.empty()) + { + LLCurlRequest::headers_t headers; + fetcher->getCurlRequest().post(mCapsURL, + headers, + merged_llsd, + new lcl_responder(fetcher, + report_sequence, + report_sequence, + LLTextureFetch::svMetricsDataBreak, + reporting_started)); + } + else + { + LLTextureFetch::svMetricsDataBreak = true; + } + + // In QA mode, Metrics submode, log the result for ease of testing + if (fetcher->isQAMode()) + { + LL_INFOS("Textures") << merged_llsd << LL_ENDL; + } + + gViewerAssetStatsThread1->reset(); + + return true; +} + + +bool +truncate_viewer_metrics(int max_regions, LLSD & metrics) +{ + static const LLSD::String reg_tag("regions"); + static const LLSD::String duration_tag("duration"); + + LLSD & reg_map(metrics[reg_tag]); + if (reg_map.size() <= max_regions) + { + return false; + } + + // Build map of region hashes ordered by duration + typedef std::multimap<LLSD::Real, int> reg_ordered_list_t; + reg_ordered_list_t regions_by_duration; + + int ind(0); + LLSD::array_const_iterator it_end(reg_map.endArray()); + for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind) + { + LLSD::Real duration = (*it)[duration_tag].asReal(); + regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind)); + } + + // Build a replacement regions array with the longest-persistence regions + LLSD new_region(LLSD::emptyArray()); + reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend()); + reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin()); + for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2) + { + new_region.append(reg_map[it2->second]); + } + reg_map = new_region; + + return true; +} + +} // end of anonymous namespace + + + diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 796109df06..ad00a7ea36 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -33,6 +33,7 @@ #include "llworkerthread.h" #include "llcurl.h" #include "lltextureinfo.h" +#include "llapr.h" class LLViewerTexture; class LLTextureFetchWorker; @@ -40,6 +41,7 @@ class HTTPGetResponder; class LLTextureCache; class LLImageDecodeThread; class LLHost; +class LLViewerAssetStats; // Interface class class LLTextureFetch : public LLWorkerThread @@ -48,9 +50,11 @@ class LLTextureFetch : public LLWorkerThread friend class HTTPGetResponder; public: - LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded); + LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode); ~LLTextureFetch(); + class TFRequest; + /*virtual*/ S32 update(U32 max_time_ms); void shutDownTextureCacheThread() ; //called in the main thread after the TextureCacheThread shuts down. void shutDownImageDecodeThread() ; //called in the main thread after the ImageDecodeThread shuts down. @@ -77,28 +81,77 @@ public: S32 getNumHTTPRequests() ; // Public for access by callbacks + S32 getPending(); void lockQueue() { mQueueMutex.lock(); } void unlockQueue() { mQueueMutex.unlock(); } LLTextureFetchWorker* getWorker(const LLUUID& id); LLTextureFetchWorker* getWorkerAfterLock(const LLUUID& id); LLTextureInfo* getTextureInfo() { return &mTextureInfo; } - + + // Commands available to other threads to control metrics gathering operations. + void commandSetRegion(U64 region_handle); + void commandSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLViewerAssetStats * main_stats); + void commandDataBreak(); + + LLCurlRequest & getCurlRequest() { return *mCurlGetRequest; } + + bool isQAMode() const { return mQAMode; } + + // Curl POST counter maintenance + inline void incrCurlPOSTCount() { mCurlPOSTRequestCount++; } + inline void decrCurlPOSTCount() { mCurlPOSTRequestCount--; } + protected: void addToNetworkQueue(LLTextureFetchWorker* worker); void removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel); void addToHTTPQueue(const LLUUID& id); void removeFromHTTPQueue(const LLUUID& id, S32 received_size = 0); void removeRequest(LLTextureFetchWorker* worker, bool cancel); - // Called from worker thread (during doWork) - void processCurlRequests(); + + // Overrides from the LLThread tree + bool runCondition(); private: void sendRequestListToSimulators(); /*virtual*/ void startThread(void); /*virtual*/ void endThread(void); /*virtual*/ void threadedUpdate(void); - + void commonUpdate(); + + // Metrics command helpers + /** + * Enqueues a command request at the end of the command queue + * and wakes up the thread as needed. + * + * Takes ownership of the TFRequest object. + * + * Method locks the command queue. + */ + void cmdEnqueue(TFRequest *); + + /** + * Returns the first TFRequest object in the command queue or + * NULL if none is present. + * + * Caller acquires ownership of the object and must dispose of it. + * + * Method locks the command queue. + */ + TFRequest * cmdDequeue(); + + /** + * Processes the first command in the queue disposing of the + * request on completion. Successive calls are needed to perform + * additional commands. + * + * Method locks the command queue. + */ + void cmdDoWork(); + public: LLUUID mDebugID; S32 mDebugCount; @@ -107,7 +160,7 @@ public: S32 mBadPacketCount; private: - LLMutex mQueueMutex; //to protect mRequestMap only + LLMutex mQueueMutex; //to protect mRequestMap and mCommands only LLMutex mNetworkQueueMutex; //to protect mNetworkQueue, mHTTPTextureQueue and mCancelQueue. LLTextureCache* mTextureCache; @@ -129,6 +182,29 @@ private: LLTextureInfo mTextureInfo; U32 mHTTPTextureBits; + + // Out-of-band cross-thread command queue. This command queue + // is logically tied to LLQueuedThread's list of + // QueuedRequest instances and so must be covered by the + // same locks. + typedef std::vector<TFRequest *> command_queue_t; + command_queue_t mCommands; + + // If true, modifies some behaviors that help with QA tasks. + const bool mQAMode; + + // Count of POST requests outstanding. We maintain the count + // indirectly in the CURL request responder's ctor and dtor and + // use it when determining whether or not to sleep the thread. Can't + // use the LLCurl module's request counter as it isn't thread compatible. + // *NOTE: Don't mix Atomic and static, apr_initialize must be called first. + LLAtomic32<S32> mCurlPOSTRequestCount; + +public: + // A probabilistically-correct indicator that the current + // attempt to log metrics follows a break in the metrics stream + // reporting due to either startup or a problem POSTing data. + static volatile bool svMetricsDataBreak; }; #endif // LL_LLTEXTUREFETCH_H diff --git a/indra/newview/lltoastgroupnotifypanel.cpp b/indra/newview/lltoastgroupnotifypanel.cpp index 563c27c4d7..75178a6ef8 100644 --- a/indra/newview/lltoastgroupnotifypanel.cpp +++ b/indra/newview/lltoastgroupnotifypanel.cpp @@ -77,6 +77,7 @@ LLToastGroupNotifyPanel::LLToastGroupNotifyPanel(LLNotificationPtr& notification from << from_name << "/" << groupData.mName; LLTextBox* pTitleText = getChild<LLTextBox>("title"); pTitleText->setValue(from.str()); + pTitleText->setToolTip(from.str()); //message subject const std::string& subject = payload["subject"].asString(); diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp new file mode 100644 index 0000000000..5ad7725b3e --- /dev/null +++ b/indra/newview/llviewerassetstats.cpp @@ -0,0 +1,619 @@ +/** + * @file llviewerassetstats.cpp + * @brief + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/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(26 == 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 +#if 0 + // When LLViewerAssetType::AT_COUNT == 49 + LLViewerAssetStats::EVACOtherGet, // AT_FOLDER_ENSEMBLE_START + 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, // AT_FOLDER_ENSEMBLE_END + LLViewerAssetStats::EVACOtherGet, // AT_CURRENT_OUTFIT + LLViewerAssetStats::EVACOtherGet, // AT_OUTFIT + LLViewerAssetStats::EVACOtherGet // AT_MY_OUTFITS +#endif + }; + + 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 diff --git a/indra/newview/llviewerassetstats.h b/indra/newview/llviewerassetstats.h new file mode 100644 index 0000000000..905ceefad5 --- /dev/null +++ b/indra/newview/llviewerassetstats.h @@ -0,0 +1,334 @@ +/** + * @file llviewerassetstats.h + * @brief Client-side collection of asset request statistics + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERASSETSTATUS_H +#define LL_LLVIEWERASSETSTATUS_H + + +#include "linden_common.h" + +#include "llpointer.h" +#include "llrefcount.h" +#include "llviewerassettype.h" +#include "llviewerassetstorage.h" +#include "llsimplestat.h" +#include "llsd.h" + +/** + * @class LLViewerAssetStats + * @brief Records performance aspects of asset access operations. + * + * This facility is derived from a very similar simulator-based + * one, LLSimAssetStats. It's function is to count asset access + * operations and characterize response times. Collected data + * are binned in several dimensions: + * + * - Asset types collapsed into a few aggregated categories + * - By simulator UUID + * - By transport mechanism (HTTP vs MessageSystem) + * - By persistence (temp vs non-temp) + * + * Statistics collected are fairly basic at this point: + * + * - Counts of enqueue and dequeue operations + * - Min/Max/Mean of asset transfer operations + * + * This collector differs from the simulator-based on in a + * number of ways: + * + * - The front-end/back-end distinction doesn't exist in viewer + * code + * - Multiple threads must be safely accomodated in the viewer + * + * Access to results is by conversion to an LLSD with some standardized + * key names. The intent of this structure is that it be emitted as + * standard syslog-based metrics formatting where it can be picked + * up by interested parties. + * + * For convenience, a set of free functions in namespace + * LLViewerAssetStatsFF is provided for conditional test-and-call + * operations. + */ +class LLViewerAssetStats +{ +public: + enum EViewerAssetCategories + { + EVACTextureTempHTTPGet, //< Texture GETs - temp/baked, HTTP + EVACTextureTempUDPGet, //< Texture GETs - temp/baked, UDP + EVACTextureNonTempHTTPGet, //< Texture GETs - perm, HTTP + EVACTextureNonTempUDPGet, //< Texture GETs - perm, UDP + EVACWearableUDPGet, //< Wearable GETs + EVACSoundUDPGet, //< Sound GETs + EVACGestureUDPGet, //< Gesture GETs + EVACOtherGet, //< Other GETs + + EVACCount // Must be last + }; + + /** + * Type for duration and other time values in the metrics. Selected + * for compatibility with the pre-existing timestamp on the texture + * fetcher class, LLTextureFetch. + */ + typedef U64 duration_t; + + /** + * Type for the region identifier used in stats. Currently uses + * the region handle's type (a U64) rather than the regions's LLUUID + * as the latter isn't available immediately. + */ + typedef U64 region_handle_t; + + /** + * @brief Collected data for a single region visited by the avatar. + * + * Fairly simple, for each asset bin enumerated above a count + * of enqueue and dequeue operations and simple stats on response + * times for completed requests. + */ + class PerRegionStats : public LLRefCount + { + public: + PerRegionStats(const region_handle_t region_handle) + : LLRefCount(), + mRegionHandle(region_handle) + { + reset(); + } + + PerRegionStats(const PerRegionStats & src) + : LLRefCount(), + mRegionHandle(src.mRegionHandle), + mTotalTime(src.mTotalTime), + mStartTimestamp(src.mStartTimestamp), + mFPS(src.mFPS) + { + for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i) + { + mRequests[i] = src.mRequests[i]; + } + } + + // Default assignment and destructor are correct. + + void reset(); + + void merge(const PerRegionStats & src); + + // Apply current running time to total and reset start point. + // Return current timestamp as a convenience. + void accumulateTime(duration_t now); + + public: + region_handle_t mRegionHandle; + duration_t mTotalTime; + duration_t mStartTimestamp; + LLSimpleStatMMM<> mFPS; + + struct prs_group + { + LLSimpleStatCounter mEnqueued; + LLSimpleStatCounter mDequeued; + LLSimpleStatMMM<duration_t> mResponse; + } + mRequests [EVACCount]; + }; + +public: + LLViewerAssetStats(); + LLViewerAssetStats(const LLViewerAssetStats &); + // Default destructor is correct. + LLViewerAssetStats & operator=(const LLViewerAssetStats &); // Not defined + + // Clear all metrics data. This leaves the currently-active region + // in place but with zero'd data for all metrics. All other regions + // are removed from the collection map. + void reset(); + + // Set hidden region argument and establish context for subsequent + // collection calls. + void setRegion(region_handle_t region_handle); + + // Asset GET Requests + void recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp); + void recordGetDequeued(LLViewerAssetType::EType at, bool with_http, bool is_temp); + void recordGetServiced(LLViewerAssetType::EType at, bool with_http, bool is_temp, duration_t duration); + + // Frames-Per-Second Samples + void recordFPS(F32 fps); + + // Merge a source instance into a destination instance. This is + // conceptually an 'operator+=()' method: + // - counts are added + // - minimums are min'd + // - maximums are max'd + // - other scalars are ignored ('this' wins) + // + void merge(const LLViewerAssetStats & src); + + // Retrieve current metrics for all visited regions (NULL region UUID/handle excluded) + // Returned LLSD is structured as follows: + // + // &stats_group = { + // enqueued : int, + // dequeued : int, + // resp_count : int, + // resp_min : float, + // resp_max : float, + // resp_mean : float + // } + // + // &mmm_group = { + // count : int, + // min : float, + // max : float, + // mean : float + // } + // + // { + // duration: int + // regions: { + // $: { // Keys are strings of the region's handle in hex + // duration: : int, + // fps: : &mmm_group, + // get_texture_temp_http : &stats_group, + // get_texture_temp_udp : &stats_group, + // get_texture_non_temp_http : &stats_group, + // get_texture_non_temp_udp : &stats_group, + // get_wearable_udp : &stats_group, + // get_sound_udp : &stats_group, + // get_gesture_udp : &stats_group, + // get_other : &stats_group + // } + // } + // } + // + // @param compact_output If true, omits from conversion any mmm_block + // or stats_block that would contain all zero data. + // Useful for transmission when the receiver knows + // what is expected and will assume zero for missing + // blocks. + LLSD asLLSD(bool compact_output); + +protected: + typedef std::map<region_handle_t, LLPointer<PerRegionStats> > PerRegionContainer; + + // Region of the currently-active region. Always valid but may + // be zero after construction or when explicitly set. Unchanged + // by a reset() call. + region_handle_t mRegionHandle; + + // Pointer to metrics collection for currently-active region. Always + // valid and unchanged after reset() though contents will be changed. + // Always points to a collection contained in mRegionStats. + LLPointer<PerRegionStats> mCurRegionStats; + + // Metrics data for all regions during one collection cycle + PerRegionContainer mRegionStats; + + // Time of last reset + duration_t mResetTimestamp; +}; + + +/** + * Global stats collectors one for each independent thread where + * assets and other statistics are gathered. The globals are + * expected to be created at startup time and then picked up by + * their respective threads afterwards. A set of free functions + * are provided to access methods behind the globals while both + * minimally disrupting visual flow and supplying a description + * of intent. + * + * Expected thread assignments: + * + * - Main: main() program execution thread + * - Thread1: TextureFetch worker thread + */ +extern LLViewerAssetStats * gViewerAssetStatsMain; + +extern LLViewerAssetStats * gViewerAssetStatsThread1; + +namespace LLViewerAssetStatsFF +{ +/** + * @brief Allocation and deallocation of globals. + * + * init() should be called before threads are started that will access it though + * you'll likely get away with calling it afterwards. cleanup() should only be + * called after threads are shutdown to prevent races on the global pointers. + */ +void init(); + +void cleanup(); + +/** + * We have many timers, clocks etc. in the runtime. This is the + * canonical timestamp for these metrics which is compatible with + * the pre-existing timestamping in the texture fetcher. + */ +inline LLViewerAssetStats::duration_t get_timestamp() +{ + return LLTimer::getTotalTime(); +} + +/** + * Region context, event and duration loggers for the Main thread. + */ +void set_region_main(LLViewerAssetStats::region_handle_t region_handle); + +void record_enqueue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp); + +void record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp); + +void record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp, + LLViewerAssetStats::duration_t duration); + +void record_fps_main(F32 fps); + + +/** + * Region context, event and duration loggers for Thread 1. + */ +void set_region_thread1(LLViewerAssetStats::region_handle_t region_handle); + +void record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp); + +void record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp); + +void record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp, + LLViewerAssetStats::duration_t duration); + +} // namespace LLViewerAssetStatsFF + +#endif // LL_LLVIEWERASSETSTATUS_H diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 2e7ef0fec3..36c8b42a52 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -33,6 +33,61 @@ #include "message.h" #include "llagent.h" +#include "lltransfersourceasset.h" +#include "lltransfertargetvfile.h" +#include "llviewerassetstats.h" + +///---------------------------------------------------------------------------- +/// LLViewerAssetRequest +///---------------------------------------------------------------------------- + +/** + * @brief Local class to encapsulate asset fetch requests with a timestamp. + * + * Derived from the common LLAssetRequest class, this is currently used + * only for fetch/get operations and its only function is to wrap remote + * asset fetch requests so that they can be timed. + */ +class LLViewerAssetRequest : public LLAssetRequest +{ +public: + LLViewerAssetRequest(const LLUUID &uuid, const LLAssetType::EType type) + : LLAssetRequest(uuid, type), + mMetricsStartTime(0) + { + } + + LLViewerAssetRequest & operator=(const LLViewerAssetRequest &); // Not defined + // Default assignment operator valid + + // virtual + ~LLViewerAssetRequest() + { + recordMetrics(); + } + +protected: + void recordMetrics() + { + if (mMetricsStartTime) + { + // Okay, it appears this request was used for useful things. Record + // the expected dequeue and duration of request processing. + LLViewerAssetStatsFF::record_dequeue_main(mType, false, false); + LLViewerAssetStatsFF::record_response_main(mType, false, false, + (LLViewerAssetStatsFF::get_timestamp() + - mMetricsStartTime)); + mMetricsStartTime = 0; + } + } + +public: + LLViewerAssetStats::duration_t mMetricsStartTime; +}; + +///---------------------------------------------------------------------------- +/// LLViewerAssetStorage +///---------------------------------------------------------------------------- LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, LLVFS *static_vfs, @@ -258,3 +313,77 @@ void LLViewerAssetStorage::storeAssetData( } } } + + +/** + * @brief Allocate and queue an asset fetch request for the viewer + * + * This is a nearly-verbatim copy of the base class's implementation + * with the following changes: + * - Use a locally-derived request class + * - Start timing for metrics when request is queued + * + * This is an unfortunate implementation choice but it's forced by + * current conditions. A refactoring that might clean up the layers + * of responsibility or introduce factories or more virtualization + * of methods would enable a more attractive solution. + * + * If LLAssetStorage::_queueDataRequest changes, this must change + * as well. + */ + +// virtual +void LLViewerAssetStorage::_queueDataRequest( + const LLUUID& uuid, + LLAssetType::EType atype, + LLGetAssetCallback callback, + void *user_data, + BOOL duplicate, + BOOL is_priority) +{ + if (mUpstreamHost.isOk()) + { + // stash the callback info so we can find it after we get the response message + LLViewerAssetRequest *req = new LLViewerAssetRequest(uuid, atype); + req->mDownCallback = callback; + req->mUserData = user_data; + req->mIsPriority = is_priority; + if (!duplicate) + { + // Only collect metrics for non-duplicate requests. Others + // are piggy-backing and will artificially lower averages. + req->mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + + mPendingDownloads.push_back(req); + + if (!duplicate) + { + // send request message to our upstream data provider + // Create a new asset transfer. + LLTransferSourceParamsAsset spa; + spa.setAsset(uuid, atype); + + // Set our destination file, and the completion callback. + LLTransferTargetParamsVFile tpvf; + tpvf.setAsset(uuid, atype); + tpvf.setCallback(downloadCompleteCallback, req); + + llinfos << "Starting transfer for " << uuid << llendl; + LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(mUpstreamHost, LLTCT_ASSET); + ttcp->requestTransfer(spa, tpvf, 100.f + (is_priority ? 1.f : 0.f)); + + LLViewerAssetStatsFF::record_enqueue_main(atype, false, false); + } + } + else + { + // uh-oh, we shouldn't have gotten here + llwarns << "Attempt to move asset data request upstream w/o valid upstream provider" << llendl; + if (callback) + { + callback(mVFS, uuid, atype, user_data, LL_ERR_CIRCUIT_GONE, LL_EXSTAT_NO_UPSTREAM); + } + } +} + diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h index 6346b79f03..ca9b9943fa 100644 --- a/indra/newview/llviewerassetstorage.h +++ b/indra/newview/llviewerassetstorage.h @@ -63,6 +63,17 @@ public: bool is_priority = false, bool user_waiting=FALSE, F64 timeout=LL_ASSET_STORAGE_TIMEOUT); + +protected: + using LLAssetStorage::_queueDataRequest; + + // virtual + void _queueDataRequest(const LLUUID& uuid, + LLAssetType::EType type, + void (*callback) (LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), + void *user_data, + BOOL duplicate, + BOOL is_priority); }; #endif diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 622d09c600..8c5a52c187 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -504,9 +504,10 @@ bool toggle_show_object_render_cost(const LLSD& newvalue) void toggle_updater_service_active(LLControlVariable* control, const LLSD& new_value) { - if(new_value.asBoolean()) + if(new_value.asInteger()) { - LLUpdaterService().startChecking(); + LLUpdaterService update_service; + if(!update_service.isChecking()) update_service.startChecking(); } else { @@ -661,7 +662,7 @@ void settings_setup_listeners() gSavedSettings.getControl("ShowNavbarFavoritesPanel")->getSignal()->connect(boost::bind(&toggle_show_favorites_panel, _2)); gSavedSettings.getControl("ShowMiniLocationPanel")->getSignal()->connect(boost::bind(&toggle_show_mini_location_panel, _2)); gSavedSettings.getControl("ShowObjectRenderingCost")->getSignal()->connect(boost::bind(&toggle_show_object_render_cost, _2)); - gSavedSettings.getControl("UpdaterServiceActive")->getSignal()->connect(&toggle_updater_service_active); + gSavedSettings.getControl("UpdaterServiceSetting")->getSignal()->connect(&toggle_updater_service_active); gSavedSettings.getControl("ForceShowGrid")->getSignal()->connect(boost::bind(&handleForceShowGrid, _2)); gSavedSettings.getControl("RenderTransparentWater")->getSignal()->connect(boost::bind(&handleRenderTransparentWaterChanged, _2)); } diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index f573f25efe..dca1e33e60 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -60,6 +60,7 @@ #include "llfloaterhardwaresettings.h" #include "llfloaterhelpbrowser.h" #include "llfloatermediabrowser.h" +#include "llfloaterwebcontent.h" #include "llfloatermediasettings.h" #include "llfloaterhud.h" #include "llfloaterimagepreview.h" @@ -252,6 +253,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("voice_controls", "floater_voice_controls.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLCallFloater>); LLFloaterReg::add("voice_effect", "floater_voice_effect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterVoiceEffect>); + LLFloaterReg::add("web_content", "floater_web_content.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWebContent>); LLFloaterReg::add("whitelist_entry", "floater_whitelist_entry.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWhiteListEntry>); LLFloaterWindowSizeUtil::registerFloater(); LLFloaterReg::add("world_map", "floater_world_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWorldMap>); diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index fae4eb3c05..d3b6dcd86f 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -52,6 +52,7 @@ #include "llviewerregion.h" #include "llwebsharing.h" // For LLWebSharing::setOpenIDCookie(), *TODO: find a better way to do this! #include "llfilepicker.h" +#include "llnotifications.h" #include "llevent.h" // LLSimpleListener #include "llnotificationsutil.h" @@ -62,6 +63,7 @@ #include "llwindow.h" #include "llfloatermediabrowser.h" // for handling window close requests and geometry change requests in media browser windows. +#include "llfloaterwebcontent.h" // for handling window close requests and geometry change requests in media browser windows. #include <boost/bind.hpp> // for SkinFolder listener #include <boost/signals2.hpp> @@ -293,6 +295,7 @@ public: LLPluginCookieStore *LLViewerMedia::sCookieStore = NULL; LLURL LLViewerMedia::sOpenIDURL; std::string LLViewerMedia::sOpenIDCookie; +LLPluginClassMedia* LLViewerMedia::sSpareBrowserMediaSource = NULL; static LLViewerMedia::impl_list sViewerMediaImplList; static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap; static LLTimer sMediaCreateTimer; @@ -742,6 +745,9 @@ void LLViewerMedia::updateMedia(void *dummy_arg) // Enable/disable the plugin read thread LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread")); + // HACK: we always try to keep a spare running webkit plugin around to improve launch times. + createSpareBrowserMediaSource(); + sAnyMediaShowing = false; sUpdatedCookies = getCookieStore()->getChangedCookies(); if(!sUpdatedCookies.empty()) @@ -759,6 +765,12 @@ void LLViewerMedia::updateMedia(void *dummy_arg) pimpl->update(); pimpl->calculateInterest(); } + + // Let the spare media source actually launch + if(sSpareBrowserMediaSource) + { + sSpareBrowserMediaSource->idle(); + } // Sort the static instance list using our interest criteria sViewerMediaImplList.sort(priorityComparitor); @@ -1034,6 +1046,26 @@ bool LLViewerMedia::isParcelAudioPlaying() return (LLViewerMedia::hasParcelAudio() && gAudiop && LLAudioEngine::AUDIO_PLAYING == gAudiop->isInternetStreamPlaying()); } +void LLViewerMedia::onAuthSubmit(const LLSD& notification, const LLSD& response) +{ + LLViewerMediaImpl *impl = LLViewerMedia::getMediaImplFromTextureID(notification["payload"]["media_id"]); + if(impl) + { + LLPluginClassMedia* media = impl->getMediaPlugin(); + if(media) + { + if (response["ok"]) + { + media->sendAuthResponse(true, response["username"], response["password"]); + } + else + { + media->sendAuthResponse(false, "", ""); + } + } + } +} + ///////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::clearAllCookies() @@ -1400,6 +1432,29 @@ void LLViewerMedia::proxyWindowClosed(const std::string &uuid) } } +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::createSpareBrowserMediaSource() +{ + if(!sSpareBrowserMediaSource) + { + // If we don't have a spare browser media source, create one. + // The null owner will keep the browser plugin from fully initializing + // (specifically, it keeps LLPluginClassMedia from negotiating a size change, + // which keeps MediaPluginWebkit::initBrowserWindow from doing anything until we have some necessary data, like the background color) + sSpareBrowserMediaSource = LLViewerMediaImpl::newSourceFromMediaType("text/html", NULL, 0, 0); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +LLPluginClassMedia* LLViewerMedia::getSpareBrowserMediaSource() +{ + LLPluginClassMedia* result = sSpareBrowserMediaSource; + sSpareBrowserMediaSource = NULL; + return result; +}; + bool LLViewerMedia::hasInWorldMedia() { if (sInWorldMediaDisabled) return false; @@ -1636,6 +1691,21 @@ void LLViewerMediaImpl::setMediaType(const std::string& media_type) LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, const std::string target) { std::string plugin_basename = LLMIMETypes::implType(media_type); + LLPluginClassMedia* media_source = NULL; + + // HACK: we always try to keep a spare running webkit plugin around to improve launch times. + if(plugin_basename == "media_plugin_webkit") + { + media_source = LLViewerMedia::getSpareBrowserMediaSource(); + if(media_source) + { + media_source->setOwner(owner); + media_source->setTarget(target); + media_source->setSize(default_width, default_height); + + return media_source; + } + } if(plugin_basename.empty()) { @@ -1673,7 +1743,7 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ } else { - LLPluginClassMedia* media_source = new LLPluginClassMedia(owner); + media_source = new LLPluginClassMedia(owner); media_source->setSize(default_width, default_height); media_source->setUserDataPath(user_data_path); media_source->setLanguageCode(LLUI::getLanguage()); @@ -1753,6 +1823,22 @@ bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) media_source->focus(mHasFocus); media_source->setBackgroundColor(mBackgroundColor); + if(gSavedSettings.getBOOL("BrowserIgnoreSSLCertErrors")) + { + media_source->ignore_ssl_cert_errors(true); + } + + // start by assuming the default CA file will be used + std::string ca_path = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "lindenlab.pem" ); + + // default turned off so pick up the user specified path + if( ! gSavedSettings.getBOOL("BrowserUseDefaultCAFile")) + { + ca_path = gSavedSettings.getString("BrowserCAFilePath"); + } + // set the path to the CA.pem file + media_source->addCertificateFilePath( ca_path ); + media_source->proxy_setup(gSavedSettings.getBOOL("BrowserProxyEnabled"), gSavedSettings.getString("BrowserProxyAddress"), gSavedSettings.getS32("BrowserProxyPort")); if(mClearCache) @@ -1849,6 +1935,18 @@ void LLViewerMediaImpl::setSize(int width, int height) } ////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::showNotification(LLNotificationPtr notify) +{ + mNotification = notify; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::hideNotification() +{ + mNotification.reset(); +} + +////////////////////////////////////////////////////////////////////////////////////////// void LLViewerMediaImpl::play() { // If the media source isn't there, try to initialize it and load an URL. @@ -2850,7 +2948,6 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla LL_DEBUGS("Media") << "MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is: " << plugin->getClickURL() << LL_ENDL; std::string url = plugin->getClickURL(); LLURLDispatcher::dispatch(url, NULL, mTrustedBrowser); - } break; case MEDIA_EVENT_CLICK_LINK_HREF: @@ -2913,6 +3010,7 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_BEGIN: { LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_BEGIN, uri is: " << plugin->getNavigateURI() << LL_ENDL; + hideNotification(); if(getNavState() == MEDIANAVSTATE_SERVER_SENT) { @@ -3003,7 +3101,26 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla plugin->sendPickFileResponse(response); } break; - + + + case LLViewerMediaObserver::MEDIA_EVENT_AUTH_REQUEST: + { + LLNotification::Params auth_request_params; + auth_request_params.name = "AuthRequest"; + + // pass in host name and realm for site (may be zero length but will always exist) + LLSD args; + LLURL raw_url( plugin->getAuthURL().c_str() ); + args["HOST_NAME"] = raw_url.getAuthority(); + args["REALM"] = plugin->getAuthRealm(); + auth_request_params.substitutions = args; + + auth_request_params.payload = LLSD().with("media_id", mTextureId); + auth_request_params.functor.function = boost::bind(&LLViewerMedia::onAuthSubmit, _1, _2); + LLNotifications::instance().add(auth_request_params); + }; + break; + case LLViewerMediaObserver::MEDIA_EVENT_CLOSE_REQUEST: { std::string uuid = plugin->getClickUUID(); @@ -3019,6 +3136,7 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla // This close request is directed at another instance pass_through = false; LLFloaterMediaBrowser::closeRequest(uuid); + LLFloaterWebContent::closeRequest(uuid); } } break; @@ -3038,6 +3156,7 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla // This request is directed at another instance pass_through = false; LLFloaterMediaBrowser::geometryChanged(uuid, plugin->getGeometryX(), plugin->getGeometryY(), plugin->getGeometryWidth(), plugin->getGeometryHeight()); + LLFloaterWebContent::geometryChanged(uuid, plugin->getGeometryX(), plugin->getGeometryY(), plugin->getGeometryWidth(), plugin->getGeometryHeight()); } } break; @@ -3521,6 +3640,11 @@ bool LLViewerMediaImpl::isInAgentParcel() const return result; } +LLNotificationPtr LLViewerMediaImpl::getCurrentNotification() const +{ + return mNotification; +} + ////////////////////////////////////////////////////////////////////////////////////////// // // static diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index 4025a4484f..e2e342cc45 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -37,6 +37,7 @@ #include "llpluginclassmedia.h" #include "v4color.h" +#include "llnotificationptr.h" #include "llurl.h" @@ -130,6 +131,8 @@ public: static bool isParcelMediaPlaying(); static bool isParcelAudioPlaying(); + static void onAuthSubmit(const LLSD& notification, const LLSD& response); + // Clear all cookies for all plugins static void clearAllCookies(); @@ -155,6 +158,9 @@ public: static void proxyWindowOpened(const std::string &target, const std::string &uuid); static void proxyWindowClosed(const std::string &uuid); + static void createSpareBrowserMediaSource(); + static LLPluginClassMedia* getSpareBrowserMediaSource(); + private: static void setOpenIDCookie(); static void onTeleportFinished(); @@ -162,6 +168,7 @@ private: static LLPluginCookieStore *sCookieStore; static LLURL sOpenIDURL; static std::string sOpenIDCookie; + static LLPluginClassMedia* sSpareBrowserMediaSource; }; // Implementation functions not exported into header file @@ -195,6 +202,9 @@ public: LLPluginClassMedia* getMediaPlugin() { return mMediaSource; } void setSize(int width, int height); + void showNotification(LLNotificationPtr notify); + void hideNotification(); + void play(); void stop(); void pause(); @@ -387,6 +397,9 @@ public: // Is this media in the agent's parcel? bool isInAgentParcel() const; + // get currently active notification associated with this media instance + LLNotificationPtr getCurrentNotification() const; + private: bool isAutoPlayable() const; bool shouldShowBasedOnClass() const; @@ -444,7 +457,8 @@ private: bool mNavigateSuspendedDeferred; bool mTrustedBrowser; std::string mTarget; - + LLNotificationPtr mNotification; + private: BOOL mIsUpdated ; std::list< LLVOVolume* > mObjectList ; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index e18b4ec414..9e16bf2fbb 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -7223,6 +7223,12 @@ void handle_web_browser_test(const LLSD& param) LLWeb::loadURLInternal(url); } +void handle_web_content_test(const LLSD& param) +{ + std::string url = param.asString(); + LLWeb::loadWebURLInternal(url); +} + void handle_buy_currency_test(void*) { std::string url = @@ -7973,7 +7979,8 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedDumpRegionObjectCache(), "Advanced.DumpRegionObjectCache"); // Advanced > UI - commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); + commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser + commit.add("Advanced.WebContentTest", boost::bind(&handle_web_content_test, _2)); // this one opens the Web Content floater view_listener_t::addMenu(new LLAdvancedBuyCurrencyTest(), "Advanced.BuyCurrencyTest"); view_listener_t::addMenu(new LLAdvancedDumpSelectMgr(), "Advanced.DumpSelectMgr"); view_listener_t::addMenu(new LLAdvancedDumpInventory(), "Advanced.DumpInventory"); diff --git a/indra/newview/llviewerparcelmedia.cpp b/indra/newview/llviewerparcelmedia.cpp index 99e869dafc..40f0b43313 100644 --- a/indra/newview/llviewerparcelmedia.cpp +++ b/indra/newview/llviewerparcelmedia.cpp @@ -586,6 +586,18 @@ void LLViewerParcelMedia::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() << LL_ENDL; } break; + + case MEDIA_EVENT_AUTH_REQUEST: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_AUTH_REQUEST, url " << self->getAuthURL() << ", realm " << self->getAuthRealm() << LL_ENDL; + } + break; + + case MEDIA_EVENT_LINK_HOVERED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << LL_ENDL; + }; + break; }; } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index ca07e7c4cf..551ba18dd5 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1414,6 +1414,7 @@ void LLViewerRegion::setSeedCapability(const std::string& url) capabilityNames.append("UpdateNotecardTaskInventory"); capabilityNames.append("UpdateScriptTask"); capabilityNames.append("UploadBakedTexture"); + capabilityNames.append("ViewerMetrics"); capabilityNames.append("ViewerStartAuction"); capabilityNames.append("ViewerStats"); capabilityNames.append("WebFetchInventoryDescendents"); diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp index 6028a8fbea..b73017a51a 100644 --- a/indra/newview/llweb.cpp +++ b/indra/newview/llweb.cpp @@ -35,6 +35,7 @@ #include "llagent.h" #include "llappviewer.h" #include "llfloatermediabrowser.h" +#include "llfloaterwebcontent.h" #include "llfloaterreg.h" #include "lllogininstance.h" #include "llparcel.h" @@ -95,6 +96,23 @@ void LLWeb::loadURL(const std::string& url, const std::string& target, const std } } +// static +void LLWeb::loadWebURL(const std::string& url, const std::string& target, const std::string& uuid) +{ + if(target == "_internal") + { + // Force load in the internal browser, as if with a blank target. + loadWebURLInternal(url, "", uuid); + } + else if (gSavedSettings.getBOOL("UseExternalBrowser") || (target == "_external")) + { + loadURLExternal(url); + } + else + { + loadWebURLInternal(url, target, uuid); + } +} // static void LLWeb::loadURLInternal(const std::string &url, const std::string& target, const std::string& uuid) @@ -102,6 +120,13 @@ void LLWeb::loadURLInternal(const std::string &url, const std::string& target, c LLFloaterMediaBrowser::create(url, target, uuid); } +// static +// Explicitly open a Web URL using the Web content floater +void LLWeb::loadWebURLInternal(const std::string &url, const std::string& target, const std::string& uuid) +{ + LLFloaterWebContent::create(url, target, uuid); +} + // static void LLWeb::loadURLExternal(const std::string& url, const std::string& uuid) diff --git a/indra/newview/llweb.h b/indra/newview/llweb.h index 2915376583..dc5958e57f 100644 --- a/indra/newview/llweb.h +++ b/indra/newview/llweb.h @@ -57,6 +57,11 @@ public: static void loadURLExternal(const std::string& url, const std::string& uuid); static void loadURLExternal(const std::string& url, bool async, const std::string& uuid = LLStringUtil::null); + // Explicitly open a Web URL using the Web content floater vs. the more general media browser + static void loadWebURL(const std::string& url, const std::string& target, const std::string& uuid); + static void loadWebURLInternal(const std::string &url, const std::string& target, const std::string& uuid); + static void loadWebURLInternal(const std::string &url) { loadWebURLInternal(url, LLStringUtil::null, LLStringUtil::null); } + /// Returns escaped url (eg, " " to "%20") - used by all loadURL methods static std::string escapeURL(const std::string& url); /// Expands various strings like [LANG], [VERSION], etc. in a URL diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index a51a096482..89611d8899 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -392,7 +392,7 @@ with the same filename but different name <texture name="RadioButton_On_Disabled" file_name="widgets/RadioButton_On_Disabled.png" preload="true" /> - <texture name="Refresh_Off" file_name="icons/Refresh_Off.png" preload="false" /> + <texture name="Refresh_Off" file_name="icons/Refresh_Off.png" preload="true" /> <texture name="Resize_Corner" file_name="windows/Resize_Corner.png" preload="true" /> @@ -468,7 +468,7 @@ with the same filename but different name <texture name="Stepper_Up_Off" file_name="widgets/Stepper_Up_Off.png" preload="false" /> <texture name="Stepper_Up_Press" file_name="widgets/Stepper_Up_Press.png" preload="false" /> - <texture name="Stop_Off" file_name="icons/Stop_Off.png" preload="false" /> + <texture name="Stop_Off" file_name="icons/Stop_Off.png" preload="true" /> <texture name="StopReload_Off" file_name="icons/StopReload_Off.png" preload="false" /> <texture name="StopReload_Over" file_name="icons/StopReload_Over.png" preload="false" /> diff --git a/indra/newview/skins/default/xui/en/floater_help_browser.xml b/indra/newview/skins/default/xui/en/floater_help_browser.xml index 837923bcf6..02e50ee584 100644 --- a/indra/newview/skins/default/xui/en/floater_help_browser.xml +++ b/indra/newview/skins/default/xui/en/floater_help_browser.xml @@ -36,7 +36,8 @@ user_resize="false" width="620"> <web_browser - trusted_content="true" + trusted_content="true" + initial_mime_type="text/html" bottom="-25" follows="left|right|top|bottom" layout="topleft" diff --git a/indra/newview/skins/default/xui/en/floater_media_browser.xml b/indra/newview/skins/default/xui/en/floater_media_browser.xml index 49e835cce4..43729d7c9f 100644 --- a/indra/newview/skins/default/xui/en/floater_media_browser.xml +++ b/indra/newview/skins/default/xui/en/floater_media_browser.xml @@ -101,7 +101,7 @@ left_pad="5" name="go" top_delta="0" - width="55"> + width="50"> <button.commit_callback function="MediaBrowser.Go" /> </button> diff --git a/indra/newview/skins/default/xui/en/floater_web_content.xml b/indra/newview/skins/default/xui/en/floater_web_content.xml new file mode 100644 index 0000000000..2ad46824c2 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_web_content.xml @@ -0,0 +1,190 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ can_resize="true"
+ height="440"
+ layout="topleft"
+ min_height="140"
+ min_width="467"
+ name="floater_web_content"
+ help_topic="floater_web_content"
+ save_rect="true"
+ auto_tile="true"
+ title=""
+ initial_mime_type="text/html"
+ width="820">
+ <layout_stack
+ bottom="440"
+ follows="left|right|top|bottom"
+ layout="topleft"
+ left="5"
+ name="stack1"
+ orientation="vertical"
+ top="20"
+ width="810">
+ <layout_panel
+ auto_resize="false"
+ default_tab_group="1"
+ height="22"
+ layout="topleft"
+ left="0"
+ min_height="20"
+ name="nav_controls"
+ top="400"
+ user_resize="false"
+ width="800">
+ <button
+ image_overlay="Arrow_Left_Off"
+ image_disabled="PushButton_Disabled"
+ image_disabled_selected="PushButton_Disabled"
+ image_selected="PushButton_Selected"
+ image_unselected="PushButton_Off"
+ hover_glow_amount="0.15"
+ tool_tip="Navigate back"
+ follows="left|top"
+ height="22"
+ layout="topleft"
+ left="1"
+ name="back"
+ top="0"
+ width="22">
+ <button.commit_callback
+ function="WebContent.Back" />
+ </button>
+ <button
+ image_overlay="Arrow_Right_Off"
+ image_disabled="PushButton_Disabled"
+ image_disabled_selected="PushButton_Disabled"
+ image_selected="PushButton_Selected"
+ image_unselected="PushButton_Off"
+ tool_tip="Navigate forward"
+ follows="left|top"
+ height="22"
+ layout="topleft"
+ left="27"
+ name="forward"
+ top_delta="0"
+ width="22">
+ <button.commit_callback
+ function="WebContent.Forward" />
+ </button>
+ <button
+ image_overlay="Stop_Off"
+ image_disabled="PushButton_Disabled"
+ image_disabled_selected="PushButton_Disabled"
+ image_selected="PushButton_Selected"
+ image_unselected="PushButton_Off"
+ tool_tip="Stop navigation"
+ enabled="true"
+ follows="left|top"
+ height="22"
+ layout="topleft"
+ left="51"
+ name="stop"
+ top_delta="0"
+ width="22">
+ <button.commit_callback
+ function="WebContent.Stop" />
+ </button>
+ <button
+ image_overlay="Refresh_Off"
+ image_disabled="PushButton_Disabled"
+ image_disabled_selected="PushButton_Disabled"
+ image_selected="PushButton_Selected"
+ image_unselected="PushButton_Off"
+ tool_tip="Reload page"
+ follows="left|top"
+ height="22"
+ layout="topleft"
+ left="51"
+ name="reload"
+ top_delta="0"
+ width="22">
+ <button.commit_callback
+ function="WebContent.Reload" />
+ </button>
+ <combo_box
+ allow_text_entry="true"
+ follows="left|top|right"
+ tab_group="1"
+ height="22"
+ layout="topleft"
+ left_pad="4"
+ max_chars="1024"
+ name="address"
+ combo_editor.select_on_focus="true"
+ tool_tip="Enter URL here"
+ top_delta="0"
+ width="702">
+ <combo_box.commit_callback
+ function="WebContent.EnterAddress" />
+ </combo_box>
+ <icon
+ name="media_secure_lock_flag"
+ height="16"
+ follows="top|right"
+ image_name="Lock2"
+ layout="topleft"
+ left_delta="656"
+ top_delta="2"
+ visible="false"
+ tool_tip="Secured Browsing"
+ width="16" />
+ <button
+ image_overlay="ExternalBrowser_Off"
+ image_disabled="PushButton_Disabled"
+ image_disabled_selected="PushButton_Disabled"
+ image_selected="PushButton_Selected"
+ image_unselected="PushButton_Off"
+ tool_tip="Open current URL in your desktop browser"
+ follows="right|top"
+ enabled="true"
+ height="22"
+ layout="topleft"
+ name="popexternal"
+ right="800"
+ top_delta="-2"
+ width="22">
+ <button.commit_callback
+ function="WebContent.PopExternal" />
+ </button>
+ </layout_panel>
+ <layout_panel
+ height="40"
+ layout="topleft"
+ left_delta="0"
+ name="external_controls"
+ top_delta="0"
+ user_resize="false"
+ width="540">
+ <web_browser
+ bottom="-22"
+ follows="all"
+ layout="topleft"
+ left="0"
+ name="webbrowser"
+ top="0"/>
+ <text
+ type="string"
+ length="100"
+ follows="bottom|left"
+ height="20"
+ layout="topleft"
+ left_delta="0"
+ name="statusbartext"
+ parse_urls="false"
+ text_color="0.4 0.4 0.4 1"
+ top_pad="5"
+ width="452"/>
+ <progress_bar
+ color_bar="0.3 1.0 0.3 1"
+ follows="bottom|right"
+ height="16"
+ top_delta="-1"
+ left_pad="24"
+ layout="topleft"
+ name="statusbarprogress"
+ width="64"/>
+ </layout_panel>
+ </layout_stack>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml index 4f982cc8e9..0d4a095e14 100644 --- a/indra/newview/skins/default/xui/en/menu_login.xml +++ b/indra/newview/skins/default/xui/en/menu_login.xml @@ -176,13 +176,19 @@ parameter="message_critical" /> </menu_item_call> <menu_item_call - label="Web Browser Test" + label="Media Browser Test" name="Web Browser Test"> <menu_item_call.on_click function="Advanced.WebBrowserTest" parameter="http://join.secondlife.com/"/> </menu_item_call> - <menu_item_separator/> + <menu_item_call + label="Web Content Floater Test" + name="Web Content Floater Test"> + <menu_item_call.on_click + function="Advanced.WebContentTest" + parameter="http://www.google.com"/> + </menu_item_call> <menu_item_check label="Show Grid Picker" name="Show Grid Picker" diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 3b1ebc64ab..d997a262a8 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2646,13 +2646,21 @@ parameter="BottomPanelNew" /> </menu_item_check>--> <menu_item_call - label="Web Browser Test" + label="Media Browser Test" name="Web Browser Test"> <menu_item_call.on_click function="Advanced.WebBrowserTest" parameter="http://secondlife.com/app/search/slurls.html"/> </menu_item_call> - <menu_item_call + <menu_item_call + label="Web Content Browser" + name="Web Content Browser" + shortcut="control|alt|W"> + <menu_item_call.on_click + function="Advanced.WebContentTest" + parameter="http://google.com"/> + </menu_item_call> + <menu_item_call label="Dump SelectMgr" name="Dump SelectMgr"> <menu_item_call.on_click diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index b1fd579c6f..63c030b068 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -2901,12 +2901,80 @@ http://secondlife.com/download. name="okbutton" yestext="OK"/> </notification> + <notification - icon="notifytip.tga" - name="DownloadBackground" - type="notifytip"> -An updated version of [APP_NAME] has been downloaded. -It will be applied the next time you restart [APP_NAME] + icon="alertmodal.tga" + name="FailedRequiredUpdateInstall" + type="alertmodal"> +We were unable to install a required update. +You will be unable to log in until [APP_NAME] has been updated. + +Please download and install the latest viewer from +http://secondlife.com/download. + <usetemplate + name="okbutton" + yestext="Quit"/> + </notification> + + <notification + icon="alertmodal.tga" + name="UpdaterServiceNotRunning" + type="alertmodal"> +There is a required update for your Second Life Installation. + +You may download this update from http://www.secondlife.com/downloads +or you can install it now. + <usetemplate + name="okcancelbuttons" + notext="Quit Second Life" + yestext="Download and install now"/> + </notification> + + <notification + icon="notify.tga" + name="DownloadBackgroundTip" + type="notify"> +We have downloaded an update to your [APP_NAME] installation. +Version [VERSION] [[RELEASE_NOTES_FULL_URL] Information about this update] + <usetemplate + name="okcancelbuttons" + notext="Later..." + yestext="Install now and restart [APP_NAME]"/> + </notification> + + <notification + icon="alertmodal.tga" + name="DownloadBackgroundDialog" + type="alertmodal"> +We have downloaded an update to your [APP_NAME] installation. +Version [VERSION] [[RELEASE_NOTES_FULL_URL] Information about this update] + <usetemplate + name="okcancelbuttons" + notext="Later..." + yestext="Install now and restart [APP_NAME]"/> + </notification> + + <notification + icon="alertmodal.tga" + name="RequiredUpdateDownloadedVerboseDialog" + type="alertmodal"> +We have downloaded a required software update. +Version [VERSION] + +We must restart [APP_NAME] to install the update. + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="RequiredUpdateDownloadedDialog" + type="alertmodal"> +We must restart [APP_NAME] to install the update. + <usetemplate + name="okbutton" + yestext="OK"/> </notification> <notification @@ -5014,7 +5082,7 @@ If you want to view streaming media on parcels that support it you should go to type="notify"> No Media Plugin was found to handle the "[MIME_TYPE]" mime type. Media of this type will be unavailable. <unique> - <context key="[MIME_TYPE]"/> + <context>MIME_TYPE</context> </unique> </notification> @@ -5943,7 +6011,7 @@ You may only select up to [MAX_SELECT] items from this list. [NAME] is inviting you to a Voice Chat call. Click Accept to join the call or Decline to decline the invitation. Click Block to block this caller. <unique> - <context key="NAME"/> + <context>NAME</context> </unique> <form name="form"> <button @@ -5992,8 +6060,8 @@ Click Accept to join the call or Decline to decline the invitation. Click Block [NAME] has joined a Voice Chat call with the group [GROUP]. Click Accept to join the call or Decline to decline the invitation. Click Block to block this caller. <unique> - <context key="NAME"/> - <context key="GROUP"/> + <context>NAME</context> + <context>GROUP</context> </unique> <form name="form"> <button @@ -6018,7 +6086,7 @@ Click Accept to join the call or Decline to decline the invitation. Click Block [NAME] has joined a voice chat call with a conference chat. Click Accept to join the call or Decline to decline the invitation. Click Block to block this caller. <unique> - <context key="NAME"/> + <context>NAME</context> </unique> <form name="form"> <button @@ -6043,7 +6111,7 @@ Click Accept to join the call or Decline to decline the invitation. Click Block [NAME] is inviting you to a conference chat. Click Accept to join the chat or Decline to decline the invitation. Click Block to block this caller. <unique> - <context key="NAME"/> + <context>NAME</context> </unique> <form name="form"> <button @@ -6067,7 +6135,7 @@ Click Accept to join the chat or Decline to decline the invitation. Click Block type="notifytip"> The voice call you are trying to join, [VOICE_CHANNEL_NAME], has reached maximum capacity. Please try again later. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6085,7 +6153,7 @@ We're sorry. This area has reached maximum capacity for voice conversation type="notifytip"> You have been disconnected from [VOICE_CHANNEL_NAME]. You will now be reconnected to Nearby Voice Chat. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6095,7 +6163,7 @@ You have been disconnected from [VOICE_CHANNEL_NAME]. You will now be reconnect type="notifytip"> [VOICE_CHANNEL_NAME] has ended the call. You will now be reconnected to Nearby Voice Chat. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6105,7 +6173,7 @@ You have been disconnected from [VOICE_CHANNEL_NAME]. You will now be reconnect type="notifytip"> [VOICE_CHANNEL_NAME] has declined your call. You will now be reconnected to Nearby Voice Chat. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6115,7 +6183,7 @@ You have been disconnected from [VOICE_CHANNEL_NAME]. You will now be reconnect type="notifytip"> [VOICE_CHANNEL_NAME] is not available to take your call. You will now be reconnected to Nearby Voice Chat. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6125,7 +6193,7 @@ You have been disconnected from [VOICE_CHANNEL_NAME]. You will now be reconnect type="notifytip"> Failed to connect to [VOICE_CHANNEL_NAME], please try again later. You will now be reconnected to Nearby Voice Chat. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6211,7 +6279,7 @@ Cannot enter parcel, you are not on the access list. type="notifytip"> You do not have permission to connect to voice chat for [VOICE_CHANNEL_NAME]. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6221,7 +6289,7 @@ You do not have permission to connect to voice chat for [VOICE_CHANNEL_NAME]. type="notifytip"> An error has occurred while trying to connect to voice chat for [VOICE_CHANNEL_NAME]. Please try again later. <unique> - <context key="VOICE_CHANNEL_NAME"/> + <context>VOICE_CHANNEL_NAME</context> </unique> </notification> @@ -6628,6 +6696,23 @@ Mute everyone? </form> </notification> + <notification + name="AuthRequest" + type="browser"> +The site at '<nolink>[HOST_NAME]</nolink>' in realm '[REALM]' requires a user name and password. + <form name="form"> + <input name="username" type="text" text="User Name"/> + <input name="password" type="password" text="Password "/> + <button default="true" + index="0" + name="ok" + text="Submit"/> + <button index="1" + name="cancel" + text="Cancel"/> + </form> + </notification> + <global name="UnsupportedCPU"> - Your CPU speed does not meet the minimum requirements. diff --git a/indra/newview/skins/default/xui/en/panel_login.xml b/indra/newview/skins/default/xui/en/panel_login.xml index e3cd61c5aa..257ed799da 100644 --- a/indra/newview/skins/default/xui/en/panel_login.xml +++ b/indra/newview/skins/default/xui/en/panel_login.xml @@ -12,11 +12,7 @@ top="600" name="create_account_url"> http://join.secondlife.com/ </panel.string> -<panel.string - name="real_url" translate="false"> - http://secondlife.com/app/login/ -</panel.string> - <string name="reg_in_client_url" translate="false"> +<string name="reg_in_client_url" translate="false"> http://secondlife.eniac15.lindenlab.com/reg-in-client/ </string> <panel.string @@ -92,6 +88,7 @@ follows="left|bottom" height="22" max_length_bytes="16" name="password_edit" +is_password="true" select_on_focus="true" top_pad="0" width="135" /> diff --git a/indra/newview/skins/default/xui/en/panel_my_profile.xml b/indra/newview/skins/default/xui/en/panel_my_profile.xml index 1b41f602cd..5b8abaca6f 100644 --- a/indra/newview/skins/default/xui/en/panel_my_profile.xml +++ b/indra/newview/skins/default/xui/en/panel_my_profile.xml @@ -185,7 +185,7 @@ </expandable_text> </panel> <text - follows="left|top" + follows="left|top|right" height="15" font.style="BOLD" font="SansSerifMedium" @@ -200,7 +200,7 @@ use_ellipses="true" /> <text - follows="left|top" + follows="left|top|right" font.style="BOLD" height="10" layout="topleft" @@ -213,7 +213,7 @@ <text_editor allow_scroll="false" bg_visible="false" - follows="left|top" + follows="left|top|right" h_pad="0" height="15" layout="topleft" @@ -226,7 +226,7 @@ width="300" word_wrap="true" /> <text - follows="left|top" + follows="left|top|right" font.style="BOLD" height="15" layout="topleft" @@ -250,7 +250,7 @@ <text_editor allow_scroll="false" bg_visible="false" - follows="left|top" + follows="left|top|right" h_pad="0" height="28" layout="topleft" @@ -266,7 +266,7 @@ Linden. </text_editor> <text - follows="left|top" + follows="left|top|right" font.style="BOLD" height="15" layout="topleft" @@ -277,7 +277,7 @@ value="Partner:" width="300" /> <panel - follows="left|top" + follows="left|top|right" height="15" layout="topleft" left="10" @@ -285,7 +285,7 @@ top_pad="0" width="300"> <text - follows="left|top" + follows="left|top|right" height="10" initial_value="(retrieving)" layout="topleft" @@ -297,7 +297,7 @@ width="300" /> </panel> <text - follows="left|top" + follows="left|top|right" font.style="BOLD" height="13" layout="topleft" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml index d6e4c56113..37aab059a9 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml @@ -82,7 +82,7 @@ control_name="AllowMultipleViewers" follows="top|left" height="15" - label="Allow Multiple Viewer" + label="Allow Multiple Viewers" layout="topleft" left="30" name="allow_multiple_viewer_check" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_setup.xml b/indra/newview/skins/default/xui/en/panel_preferences_setup.xml index 584bd1ea9d..901a1257e0 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_setup.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_setup.xml @@ -142,7 +142,7 @@ layout="topleft" left="80" name="Cache location" - top_delta="40" + top_delta="20" width="300"> Cache location: </text> @@ -341,20 +341,41 @@ name="web_proxy_port" top_delta="0" width="145" /> - - <check_box - top_delta="2" - enabled="true" - follows="left|top" - height="18" - initial_value="true" - control_name="UpdaterServiceActive" - label="Automatically download and install [APP_NAME] updates" - left="30" - mouse_opaque="true" - name="updater_service_active" - radio_style="false" - width="400" - top_pad="10"/> - + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left="30" + name="Software updates:" + mouse_opaque="false" + top_pad="5" + width="300"> + Software updates: + </text> + <combo_box + control_name="UpdaterServiceSetting" + follows="left|top" + height="23" + layout="topleft" + left_delta="50" + top_pad="5" + name="updater_service_combobox" + width="300"> + <combo_box.item + label="Install automatically" + name="Install_automatically" + value="3" /> + <!-- + <combo_box.item + label="Ask before installing" + name="Install_ask" + value="1" /> + --> + <combo_box.item + label="Download and install updates manually" + name="Install_manual" + value="0" /> + </combo_box> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_status_bar.xml b/indra/newview/skins/default/xui/en/panel_status_bar.xml index 2f52ca660b..d756dfb7de 100644 --- a/indra/newview/skins/default/xui/en/panel_status_bar.xml +++ b/indra/newview/skins/default/xui/en/panel_status_bar.xml @@ -51,7 +51,7 @@ height="18" left="0" name="balance" - tool_tip="My Balance" + tool_tip="Click to refresh your L$ balance" v_pad="4" top="0" wrap="false" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 51fba470cb..752bb6ed3a 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -1716,8 +1716,8 @@ integer llGetRegionAgentCount() Returns the number of avatars in the region </string> <string name="LSLTipText_llTextBox" translate="false"> -llTextBox(key avatar, string message, integer chat_channel -Shows a dialog box on the avatar's screen with the message. +llTextBox(key avatar, string message, integer chat_channel) +Shows a window on the avatar's screen with the message. It contains a text box for input, and if entered that text is chatted on chat_channel. </string> <string name="LSLTipText_llGetAgentLanguage" translate="false"> diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp index 309e9e9ee3..9e321db889 100644 --- a/indra/newview/tests/lllogininstance_test.cpp +++ b/indra/newview/tests/lllogininstance_test.cpp @@ -40,6 +40,7 @@ #if defined(LL_WINDOWS) #pragma warning(disable: 4355) // using 'this' in base-class ctor initializer expr +#pragma warning(disable: 4702) // disable 'unreachable code' so we can safely use skip(). #endif // Constants @@ -68,6 +69,7 @@ static bool gDisconnectCalled = false; #include "../llviewerwindow.h" void LLViewerWindow::setShowProgress(BOOL show) {} +LLProgressView * LLViewerWindow::getProgressView(void) const { return 0; } LLViewerWindow* gViewerWindow; @@ -185,6 +187,41 @@ const std::string &LLVersionInfo::getChannelAndVersion() { return VIEWERLOGIN_VE const std::string &LLVersionInfo::getChannel() { return VIEWERLOGIN_CHANNEL; } //----------------------------------------------------------------------------- +#include "../llappviewer.h" +void LLAppViewer::forceQuit(void) {} +LLAppViewer * LLAppViewer::sInstance = 0; + +//----------------------------------------------------------------------------- +#include "llnotificationsutil.h" +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + boost::function<void (const LLSD&, const LLSD&)> functor) { return LLNotificationPtr((LLNotification*)0); } + + +//----------------------------------------------------------------------------- +#include "llupdaterservice.h" + +std::string const & LLUpdaterService::pumpName(void) +{ + static std::string wakka = "wakka wakka wakka"; + return wakka; +} +bool LLUpdaterService::updateReadyToInstall(void) { return false; } +void LLUpdaterService::initialize(const std::string& protocol_version, + const std::string& url, + const std::string& path, + const std::string& channel, + const std::string& version) {} + +void LLUpdaterService::setCheckPeriod(unsigned int seconds) {} +void LLUpdaterService::startChecking(bool install_if_ready) {} +void LLUpdaterService::stopChecking() {} +bool LLUpdaterService::isChecking() { return false; } +LLUpdaterService::eUpdaterState LLUpdaterService::getState() { return INITIAL; } +std::string LLUpdaterService::updatedVersion() { return ""; } + +//----------------------------------------------------------------------------- #include "llnotifications.h" #include "llfloaterreg.h" static std::string gTOSType; @@ -198,6 +235,12 @@ LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, return NULL; } +//---------------------------------------------------------------------------- +#include "../llprogressview.h" +void LLProgressView::setText(std::string const &){} +void LLProgressView::setPercent(float){} +void LLProgressView::setMessage(std::string const &){} + //----------------------------------------------------------------------------- // LLNotifications class MockNotifications : public LLNotificationsInterface @@ -435,6 +478,8 @@ namespace tut template<> template<> void lllogininstance_object::test<3>() { + skip(); + set_test_name("Test Mandatory Update User Accepts"); // Part 1 - Mandatory Update, with User accepts response. @@ -462,6 +507,8 @@ namespace tut template<> template<> void lllogininstance_object::test<4>() { + skip(); + set_test_name("Test Mandatory Update User Decline"); // Test connect with update needed. diff --git a/indra/newview/tests/llsimplestat_test.cpp b/indra/newview/tests/llsimplestat_test.cpp new file mode 100644 index 0000000000..60a8cac995 --- /dev/null +++ b/indra/newview/tests/llsimplestat_test.cpp @@ -0,0 +1,586 @@ +/** + * @file llsimplestats_test.cpp + * @date 2010-10-22 + * @brief Test cases for some of llsimplestat.h + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include <tut/tut.hpp> + +#include "lltut.h" +#include "../llsimplestat.h" +#include "llsd.h" +#include "llmath.h" + +// @brief Used as a pointer cast type to get access to LLSimpleStatCounter +class TutStatCounter: public LLSimpleStatCounter +{ +public: + TutStatCounter(); // Not defined + ~TutStatCounter(); // Not defined + void operator=(const TutStatCounter &); // Not defined + + void setRawCount(U32 c) { mCount = c; } + U32 getRawCount() const { return mCount; } +}; + + +namespace tut +{ + struct stat_counter_index + {}; + typedef test_group<stat_counter_index> stat_counter_index_t; + typedef stat_counter_index_t::object stat_counter_index_object_t; + tut::stat_counter_index_t tut_stat_counter_index("stat_counter_test"); + + // Testing LLSimpleStatCounter's external interface + template<> template<> + void stat_counter_index_object_t::test<1>() + { + LLSimpleStatCounter c1; + ensure("Initialized counter is zero", (0 == c1.getCount())); + + ensure("Counter increment return is 1", (1 == ++c1)); + ensure("Counter increment return is 2", (2 == ++c1)); + + ensure("Current counter is 2", (2 == c1.getCount())); + + c1.reset(); + ensure("Counter is 0 after reset", (0 == c1.getCount())); + + ensure("Counter increment return is 1", (1 == ++c1)); + } + + // Testing LLSimpleStatCounter's internal state + template<> template<> + void stat_counter_index_object_t::test<2>() + { + LLSimpleStatCounter c1; + TutStatCounter * tc1 = (TutStatCounter *) &c1; + + ensure("Initialized private counter is zero", (0 == tc1->getRawCount())); + + ++c1; + ++c1; + + ensure("Current private counter is 2", (2 == tc1->getRawCount())); + + c1.reset(); + ensure("Raw counter is 0 after reset", (0 == tc1->getRawCount())); + } + + // Testing LLSimpleStatCounter's wrapping behavior + template<> template<> + void stat_counter_index_object_t::test<3>() + { + LLSimpleStatCounter c1; + TutStatCounter * tc1 = (TutStatCounter *) &c1; + + tc1->setRawCount(U32_MAX); + ensure("Initialized private counter is zero", (U32_MAX == c1.getCount())); + + ensure("Increment of max value wraps to 0", (0 == ++c1)); + } + + // Testing LLSimpleStatMMM's external behavior + template<> template<> + void stat_counter_index_object_t::test<4>() + { + LLSimpleStatMMM<> m1; + typedef LLSimpleStatMMM<>::Value lcl_float; + lcl_float zero(0); + + // Freshly-constructed + ensure("Constructed MMM<> has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM<> has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM<> has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM<> has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1.0); + ensure("Single insert MMM<> has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("Single insert MMM<> has 1.0 max", (1.0 == m1.getMax())); + ensure("Single insert MMM<> has 1.0 mean", (1.0 == m1.getMean())); + + // Second insert + m1.record(3.0); + ensure("2nd insert MMM<> has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("2nd insert MMM<> has 3.0 max", (3.0 == m1.getMax())); + ensure_approximately_equals("2nd insert MMM<> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + + // Third insert + m1.record(5.0); + ensure("3rd insert MMM<> has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("3rd insert MMM<> has 5.0 max", (5.0 == m1.getMax())); + ensure_approximately_equals("3rd insert MMM<> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + + // Fourth insert + m1.record(1000000.0); + ensure("4th insert MMM<> has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM<> has 1.0 min", (1.0 == m1.getMin())); + ensure("4th insert MMM<> has 100000.0 max", (1000000.0 == m1.getMax())); + ensure_approximately_equals("4th insert MMM<> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + + // Reset + m1.reset(); + ensure("Reset MMM<> has 0 count", (0 == m1.getCount())); + ensure("Reset MMM<> has 0 min", (zero == m1.getMin())); + ensure("Reset MMM<> has 0 max", (zero == m1.getMax())); + ensure("Reset MMM<> has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<5>() + { + LLSimpleStatMMM<> m1; + typedef LLSimpleStatMMM<>::Value lcl_float; + lcl_float zero(0); + + // Insert overflowing values + const lcl_float bignum(F32_MAX / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM<> has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM<> has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM<> has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM<> has fetchable mean", (1.0 == m1.getMean() || true)); + // We should be infinte but not interested in proving the IEEE standard here. + LLSD sd1(m1.getMean()); + // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl; + ensure("Overflowed MMM<> produces LLSDable Real", (sd1.isReal())); + } + + // Testing LLSimpleStatMMM<F32>'s external behavior + template<> template<> + void stat_counter_index_object_t::test<6>() + { + LLSimpleStatMMM<F32> m1; + typedef LLSimpleStatMMM<F32>::Value lcl_float; + lcl_float zero(0); + + // Freshly-constructed + ensure("Constructed MMM<F32> has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM<F32> has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM<F32> has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM<F32> has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1.0); + ensure("Single insert MMM<F32> has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); + ensure("Single insert MMM<F32> has 1.0 max", (1.0 == m1.getMax())); + ensure("Single insert MMM<F32> has 1.0 mean", (1.0 == m1.getMean())); + + // Second insert + m1.record(3.0); + ensure("2nd insert MMM<F32> has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); + ensure("2nd insert MMM<F32> has 3.0 max", (3.0 == m1.getMax())); + ensure_approximately_equals("2nd insert MMM<F32> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + + // Third insert + m1.record(5.0); + ensure("3rd insert MMM<F32> has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); + ensure("3rd insert MMM<F32> has 5.0 max", (5.0 == m1.getMax())); + ensure_approximately_equals("3rd insert MMM<F32> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + + // Fourth insert + m1.record(1000000.0); + ensure("4th insert MMM<F32> has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM<F32> has 1.0 min", (1.0 == m1.getMin())); + ensure("4th insert MMM<F32> has 1000000.0 max", (1000000.0 == m1.getMax())); + ensure_approximately_equals("4th insert MMM<F32> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + + // Reset + m1.reset(); + ensure("Reset MMM<F32> has 0 count", (0 == m1.getCount())); + ensure("Reset MMM<F32> has 0 min", (zero == m1.getMin())); + ensure("Reset MMM<F32> has 0 max", (zero == m1.getMax())); + ensure("Reset MMM<F32> has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<7>() + { + LLSimpleStatMMM<F32> m1; + typedef LLSimpleStatMMM<F32>::Value lcl_float; + lcl_float zero(0); + + // Insert overflowing values + const lcl_float bignum(F32_MAX / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM<F32> has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM<F32> has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM<F32> has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM<F32> has fetchable mean", (1.0 == m1.getMean() || true)); + // We should be infinte but not interested in proving the IEEE standard here. + LLSD sd1(m1.getMean()); + // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl; + ensure("Overflowed MMM<F32> produces LLSDable Real", (sd1.isReal())); + } + + // Testing LLSimpleStatMMM<F64>'s external behavior + template<> template<> + void stat_counter_index_object_t::test<8>() + { + LLSimpleStatMMM<F64> m1; + typedef LLSimpleStatMMM<F64>::Value lcl_float; + lcl_float zero(0); + + // Freshly-constructed + ensure("Constructed MMM<F64> has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM<F64> has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM<F64> has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM<F64> has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1.0); + ensure("Single insert MMM<F64> has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); + ensure("Single insert MMM<F64> has 1.0 max", (1.0 == m1.getMax())); + ensure("Single insert MMM<F64> has 1.0 mean", (1.0 == m1.getMean())); + + // Second insert + m1.record(3.0); + ensure("2nd insert MMM<F64> has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); + ensure("2nd insert MMM<F64> has 3.0 max", (3.0 == m1.getMax())); + ensure_approximately_equals("2nd insert MMM<F64> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1); + + // Third insert + m1.record(5.0); + ensure("3rd insert MMM<F64> has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); + ensure("3rd insert MMM<F64> has 5.0 max", (5.0 == m1.getMax())); + ensure_approximately_equals("3rd insert MMM<F64> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1); + + // Fourth insert + m1.record(1000000.0); + ensure("4th insert MMM<F64> has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM<F64> has 1.0 min", (1.0 == m1.getMin())); + ensure("4th insert MMM<F64> has 1000000.0 max", (1000000.0 == m1.getMax())); + ensure_approximately_equals("4th insert MMM<F64> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1); + + // Reset + m1.reset(); + ensure("Reset MMM<F64> has 0 count", (0 == m1.getCount())); + ensure("Reset MMM<F64> has 0 min", (zero == m1.getMin())); + ensure("Reset MMM<F64> has 0 max", (zero == m1.getMax())); + ensure("Reset MMM<F64> has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<9>() + { + LLSimpleStatMMM<F64> m1; + typedef LLSimpleStatMMM<F64>::Value lcl_float; + lcl_float zero(0); + + // Insert overflowing values + const lcl_float bignum(F64_MAX / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM<F64> has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM<F64> has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM<F64> has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM<F64> has fetchable mean", (1.0 == m1.getMean() || true)); + // We should be infinte but not interested in proving the IEEE standard here. + LLSD sd1(m1.getMean()); + // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl; + ensure("Overflowed MMM<F64> produces LLSDable Real", (sd1.isReal())); + } + + // Testing LLSimpleStatMMM<U64>'s external behavior + template<> template<> + void stat_counter_index_object_t::test<10>() + { + LLSimpleStatMMM<U64> m1; + typedef LLSimpleStatMMM<U64>::Value lcl_int; + lcl_int zero(0); + + // Freshly-constructed + ensure("Constructed MMM<U64> has 0 count", (0 == m1.getCount())); + ensure("Constructed MMM<U64> has 0 min", (zero == m1.getMin())); + ensure("Constructed MMM<U64> has 0 max", (zero == m1.getMax())); + ensure("Constructed MMM<U64> has 0 mean no div-by-zero", (zero == m1.getMean())); + + // Single insert + m1.record(1); + ensure("Single insert MMM<U64> has 1 count", (1 == m1.getCount())); + ensure("Single insert MMM<U64> has 1 min", (1 == m1.getMin())); + ensure("Single insert MMM<U64> has 1 max", (1 == m1.getMax())); + ensure("Single insert MMM<U64> has 1 mean", (1 == m1.getMean())); + + // Second insert + m1.record(3); + ensure("2nd insert MMM<U64> has 2 count", (2 == m1.getCount())); + ensure("2nd insert MMM<U64> has 1 min", (1 == m1.getMin())); + ensure("2nd insert MMM<U64> has 3 max", (3 == m1.getMax())); + ensure("2nd insert MMM<U64> has 2 mean", (2 == m1.getMean())); + + // Third insert + m1.record(5); + ensure("3rd insert MMM<U64> has 3 count", (3 == m1.getCount())); + ensure("3rd insert MMM<U64> has 1 min", (1 == m1.getMin())); + ensure("3rd insert MMM<U64> has 5 max", (5 == m1.getMax())); + ensure("3rd insert MMM<U64> has 3 mean", (3 == m1.getMean())); + + // Fourth insert + m1.record(U64L(1000000000000)); + ensure("4th insert MMM<U64> has 4 count", (4 == m1.getCount())); + ensure("4th insert MMM<U64> has 1 min", (1 == m1.getMin())); + ensure("4th insert MMM<U64> has 1000000000000ULL max", (U64L(1000000000000) == m1.getMax())); + ensure("4th insert MMM<U64> has 250000000002ULL mean", (U64L( 250000000002) == m1.getMean())); + + // Reset + m1.reset(); + ensure("Reset MMM<U64> has 0 count", (0 == m1.getCount())); + ensure("Reset MMM<U64> has 0 min", (zero == m1.getMin())); + ensure("Reset MMM<U64> has 0 max", (zero == m1.getMax())); + ensure("Reset MMM<U64> has 0 mean no div-by-zero", (zero == m1.getMean())); + } + + // Testing LLSimpleStatMMM's response to large values + template<> template<> + void stat_counter_index_object_t::test<11>() + { + LLSimpleStatMMM<U64> m1; + typedef LLSimpleStatMMM<U64>::Value lcl_int; + lcl_int zero(0); + + // Insert overflowing values + const lcl_int bignum(U64L(0xffffffffffffffff) / 2); + + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(bignum); + m1.record(zero); + + ensure("Overflowed MMM<U64> has 8 count", (8 == m1.getCount())); + ensure("Overflowed MMM<U64> has 0 min", (zero == m1.getMin())); + ensure("Overflowed MMM<U64> has huge max", (bignum == m1.getMax())); + ensure("Overflowed MMM<U64> has fetchable mean", (zero == m1.getMean() || true)); + } + + // Testing LLSimpleStatCounter's merge() method + template<> template<> + void stat_counter_index_object_t::test<12>() + { + LLSimpleStatCounter c1; + LLSimpleStatCounter c2; + + ++c1; + ++c1; + ++c1; + ++c1; + + ++c2; + ++c2; + c2.merge(c1); + + ensure_equals("4 merged into 2 results in 6", 6, c2.getCount()); + + ensure_equals("Source of merge is undamaged", 4, c1.getCount()); + } + + // Testing LLSimpleStatMMM's merge() method + template<> template<> + void stat_counter_index_object_t::test<13>() + { + LLSimpleStatMMM<> m1; + LLSimpleStatMMM<> m2; + + m1.record(3.5); + m1.record(4.5); + m1.record(5.5); + m1.record(6.5); + + m2.record(5.0); + m2.record(7.0); + m2.record(9.0); + + m2.merge(m1); + + ensure_equals("Count after merge (p1)", 7, m2.getCount()); + ensure_approximately_equals("Min after merge (p1)", F32(3.5), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p1)", F32(41.000/7.000), m2.getMean(), 22); + + + ensure_equals("Source count of merge is undamaged (p1)", 4, m1.getCount()); + ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(3.5), m1.getMin(), 22); + ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(6.5), m1.getMax(), 22); + ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(5.0), m1.getMean(), 22); + + m2.reset(); + + m2.record(-22.0); + m2.record(-1.0); + m2.record(30.0); + + m2.merge(m1); + + ensure_equals("Count after merge (p2)", 7, m2.getCount()); + ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p2)", F32(30.0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p2)", F32(27.000/7.000), m2.getMean(), 22); + + } + + // Testing LLSimpleStatMMM's merge() method when src contributes nothing + template<> template<> + void stat_counter_index_object_t::test<14>() + { + LLSimpleStatMMM<> m1; + LLSimpleStatMMM<> m2; + + m2.record(5.0); + m2.record(7.0); + m2.record(9.0); + + m2.merge(m1); + + ensure_equals("Count after merge (p1)", 3, m2.getCount()); + ensure_approximately_equals("Min after merge (p1)", F32(5.0), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p1)", F32(7.000), m2.getMean(), 22); + + ensure_equals("Source count of merge is undamaged (p1)", 0, m1.getCount()); + ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(0), m1.getMin(), 22); + ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(0), m1.getMax(), 22); + ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(0), m1.getMean(), 22); + + m2.reset(); + + m2.record(-22.0); + m2.record(-1.0); + + m2.merge(m1); + + ensure_equals("Count after merge (p2)", 2, m2.getCount()); + ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p2)", F32(-1.0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p2)", F32(-11.5), m2.getMean(), 22); + } + + // Testing LLSimpleStatMMM's merge() method when dst contributes nothing + template<> template<> + void stat_counter_index_object_t::test<15>() + { + LLSimpleStatMMM<> m1; + LLSimpleStatMMM<> m2; + + m1.record(5.0); + m1.record(7.0); + m1.record(9.0); + + m2.merge(m1); + + ensure_equals("Count after merge (p1)", 3, m2.getCount()); + ensure_approximately_equals("Min after merge (p1)", F32(5.0), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p1)", F32(7.000), m2.getMean(), 22); + + ensure_equals("Source count of merge is undamaged (p1)", 3, m1.getCount()); + ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(5.0), m1.getMin(), 22); + ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(9.0), m1.getMax(), 22); + ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(7.0), m1.getMean(), 22); + + m1.reset(); + m2.reset(); + + m1.record(-22.0); + m1.record(-1.0); + + m2.merge(m1); + + ensure_equals("Count after merge (p2)", 2, m2.getCount()); + ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p2)", F32(-1.0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p2)", F32(-11.5), m2.getMean(), 22); + } + + // Testing LLSimpleStatMMM's merge() method when neither dst nor src contributes + template<> template<> + void stat_counter_index_object_t::test<16>() + { + LLSimpleStatMMM<> m1; + LLSimpleStatMMM<> m2; + + m2.merge(m1); + + ensure_equals("Count after merge (p1)", 0, m2.getCount()); + ensure_approximately_equals("Min after merge (p1)", F32(0), m2.getMin(), 22); + ensure_approximately_equals("Max after merge (p1)", F32(0), m2.getMax(), 22); + ensure_approximately_equals("Mean after merge (p1)", F32(0), m2.getMean(), 22); + + ensure_equals("Source count of merge is undamaged (p1)", 0, m1.getCount()); + ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(0), m1.getMin(), 22); + ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(0), m1.getMax(), 22); + ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(0), m1.getMean(), 22); + } +} diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp new file mode 100644 index 0000000000..1bb4fb7c0c --- /dev/null +++ b/indra/newview/tests/llviewerassetstats_test.cpp @@ -0,0 +1,990 @@ +/** + * @file llviewerassetstats_tut.cpp + * @date 2010-10-28 + * @brief Test cases for some of newview/llviewerassetstats.cpp + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include <tut/tut.hpp> +#include <iostream> + +#include "lltut.h" +#include "../llviewerassetstats.h" +#include "lluuid.h" +#include "llsdutil.h" +#include "llregionhandle.h" + +static const char * all_keys[] = +{ + "duration", + "fps", + "get_other", + "get_texture_temp_http", + "get_texture_temp_udp", + "get_texture_non_temp_http", + "get_texture_non_temp_udp", + "get_wearable_udp", + "get_sound_udp", + "get_gesture_udp" +}; + +static const char * resp_keys[] = +{ + "get_other", + "get_texture_temp_http", + "get_texture_temp_udp", + "get_texture_non_temp_http", + "get_texture_non_temp_udp", + "get_wearable_udp", + "get_sound_udp", + "get_gesture_udp" +}; + +static const char * sub_keys[] = +{ + "dequeued", + "enqueued", + "resp_count", + "resp_max", + "resp_min", + "resp_mean" +}; + +static const char * mmm_resp_keys[] = +{ + "fps" +}; + +static const char * mmm_sub_keys[] = +{ + "count", + "max", + "min", + "mean" +}; + +static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8"); +static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a"); +static const U64 region1_handle(0x0000040000003f00ULL); +static const U64 region2_handle(0x0000030000004200ULL); +static const std::string region1_handle_str("0000040000003f00"); +static const std::string region2_handle_str("0000030000004200"); + +#if 0 +static bool +is_empty_map(const LLSD & sd) +{ + return sd.isMap() && 0 == sd.size(); +} + +static bool +is_single_key_map(const LLSD & sd, const std::string & key) +{ + return sd.isMap() && 1 == sd.size() && sd.has(key); +} +#endif + +static bool +is_double_key_map(const LLSD & sd, const std::string & key1, const std::string & key2) +{ + return sd.isMap() && 2 == sd.size() && sd.has(key1) && sd.has(key2); +} + +static bool +is_no_stats_map(const LLSD & sd) +{ + return is_double_key_map(sd, "duration", "regions"); +} + +static bool +is_single_slot_array(const LLSD & sd, U64 region_handle) +{ + U32 grid_x(0), grid_y(0); + grid_from_region_handle(region_handle, &grid_x, &grid_y); + + return (sd.isArray() && + 1 == sd.size() && + sd[0].has("grid_x") && + sd[0].has("grid_y") && + sd[0]["grid_x"].isInteger() && + sd[0]["grid_y"].isInteger() && + grid_x == sd[0]["grid_x"].asInteger() && + grid_y == sd[0]["grid_y"].asInteger()); +} + +static bool +is_double_slot_array(const LLSD & sd, U64 region_handle1, U64 region_handle2) +{ + U32 grid_x1(0), grid_y1(0); + U32 grid_x2(0), grid_y2(0); + grid_from_region_handle(region_handle1, &grid_x1, &grid_y1); + grid_from_region_handle(region_handle2, &grid_x2, &grid_y2); + + return (sd.isArray() && + 2 == sd.size() && + sd[0].has("grid_x") && + sd[0].has("grid_y") && + sd[0]["grid_x"].isInteger() && + sd[0]["grid_y"].isInteger() && + sd[1].has("grid_x") && + sd[1].has("grid_y") && + sd[1]["grid_x"].isInteger() && + sd[1]["grid_y"].isInteger() && + ((grid_x1 == sd[0]["grid_x"].asInteger() && + grid_y1 == sd[0]["grid_y"].asInteger() && + grid_x2 == sd[1]["grid_x"].asInteger() && + grid_y2 == sd[1]["grid_y"].asInteger()) || + (grid_x1 == sd[1]["grid_x"].asInteger() && + grid_y1 == sd[1]["grid_y"].asInteger() && + grid_x2 == sd[0]["grid_x"].asInteger() && + grid_y2 == sd[0]["grid_y"].asInteger()))); +} + +static LLSD +get_region(const LLSD & sd, U64 region_handle1) +{ + U32 grid_x(0), grid_y(0); + grid_from_region_handle(region_handle1, &grid_x, &grid_y); + + for (LLSD::array_const_iterator it(sd["regions"].beginArray()); + sd["regions"].endArray() != it; + ++it) + { + if ((*it).has("grid_x") && + (*it).has("grid_y") && + (*it)["grid_x"].isInteger() && + (*it)["grid_y"].isInteger() && + (*it)["grid_x"].asInteger() == grid_x && + (*it)["grid_y"].asInteger() == grid_y) + { + return *it; + } + } + return LLSD(); +} + +namespace tut +{ + struct tst_viewerassetstats_index + {}; + typedef test_group<tst_viewerassetstats_index> tst_viewerassetstats_index_t; + typedef tst_viewerassetstats_index_t::object tst_viewerassetstats_index_object_t; + tut::tst_viewerassetstats_index_t tut_tst_viewerassetstats_index("tst_viewerassetstats_test"); + + // Testing free functions without global stats allocated + template<> template<> + void tst_viewerassetstats_index_object_t::test<1>() + { + // Check that helpers aren't bothered by missing global stats + ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain)); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_response_main(LLViewerAssetType::AT_GESTURE, false, false, 12300000ULL); + } + + // Create a non-global instance and check the structure + template<> template<> + void tst_viewerassetstats_index_object_t::test<2>() + { + ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain)); + + LLViewerAssetStats * it = new LLViewerAssetStats(); + + ensure("Global gViewerAssetStatsMain should still be NULL", (NULL == gViewerAssetStatsMain)); + + LLSD sd_full = it->asLLSD(false); + + // Default (NULL) region ID doesn't produce LLSD results so should + // get an empty map back from output + ensure("Stat-less LLSD initially", is_no_stats_map(sd_full)); + + // Once the region is set, we will get a response even with no data collection + it->setRegion(region1_handle); + sd_full = it->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd_full, "duration", "regions")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd_full["regions"], region1_handle)); + + LLSD sd = sd_full["regions"][0]; + + delete it; + + // Check the structure of the LLSD + for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i) + { + std::string line = llformat("Has '%s' key", all_keys[i]); + ensure(line, sd.has(all_keys[i])); + } + + for (int i = 0; i < LL_ARRAY_SIZE(resp_keys); ++i) + { + for (int j = 0; j < LL_ARRAY_SIZE(sub_keys); ++j) + { + std::string line = llformat("Key '%s' has '%s' key", resp_keys[i], sub_keys[j]); + ensure(line, sd[resp_keys[i]].has(sub_keys[j])); + } + } + + for (int i = 0; i < LL_ARRAY_SIZE(mmm_resp_keys); ++i) + { + for (int j = 0; j < LL_ARRAY_SIZE(mmm_sub_keys); ++j) + { + std::string line = llformat("Key '%s' has '%s' key", mmm_resp_keys[i], mmm_sub_keys[j]); + ensure(line, sd[mmm_resp_keys[i]].has(mmm_sub_keys[j])); + } + } + } + + // Create a non-global instance and check some content + template<> template<> + void tst_viewerassetstats_index_object_t::test<3>() + { + LLViewerAssetStats * it = new LLViewerAssetStats(); + it->setRegion(region1_handle); + + LLSD sd = it->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = sd[0]; + + delete it; + + // Check a few points on the tree for content + ensure("sd[get_texture_temp_http][dequeued] is 0", (0 == sd["get_texture_temp_http"]["dequeued"].asInteger())); + ensure("sd[get_sound_udp][resp_min] is 0", (0.0 == sd["get_sound_udp"]["resp_min"].asReal())); + } + + // Create a global instance and verify free functions do something useful + template<> template<> + void tst_viewerassetstats_index_object_t::test<4>() + { + gViewerAssetStatsMain = new LLViewerAssetStats(); + LLViewerAssetStatsFF::set_region_main(region1_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + + LLSD sd = gViewerAssetStatsMain->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = sd["regions"][0]; + + // Check a few points on the tree for content + ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); + ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStatsMain->reset(); + sd = gViewerAssetStatsMain->asLLSD(false)["regions"][region1_handle_str]; + + delete gViewerAssetStatsMain; + gViewerAssetStatsMain = NULL; + + ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + } + + // Create two global instances and verify no interactions + template<> template<> + void tst_viewerassetstats_index_object_t::test<5>() + { + gViewerAssetStatsThread1 = new LLViewerAssetStats(); + gViewerAssetStatsMain = new LLViewerAssetStats(); + LLViewerAssetStatsFF::set_region_main(region1_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + + LLSD sd = gViewerAssetStatsThread1->asLLSD(false); + ensure("Other collector is empty", is_no_stats_map(sd)); + sd = gViewerAssetStatsMain->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = sd["regions"][0]; + + // Check a few points on the tree for content + ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); + ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStatsMain->reset(); + sd = gViewerAssetStatsMain->asLLSD(false)["regions"][0]; + + delete gViewerAssetStatsMain; + gViewerAssetStatsMain = NULL; + delete gViewerAssetStatsThread1; + gViewerAssetStatsThread1 = NULL; + + ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + } + + // Check multiple region collection + template<> template<> + void tst_viewerassetstats_index_object_t::test<6>() + { + gViewerAssetStatsMain = new LLViewerAssetStats(); + + LLViewerAssetStatsFF::set_region_main(region1_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::set_region_main(region2_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + + LLSD sd = gViewerAssetStatsMain->asLLSD(false); + + // std::cout << sd << std::endl; + + ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); + ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); + LLSD sd1 = get_region(sd, region1_handle); + LLSD sd2 = get_region(sd, region2_handle); + ensure("Region1 is present in results", sd1.isMap()); + ensure("Region2 is present in results", sd2.isMap()); + + // Check a few points on the tree for content + ensure_equals("sd1[get_texture_non_temp_udp][enqueued] is 1", sd1["get_texture_non_temp_udp"]["enqueued"].asInteger(), 1); + ensure_equals("sd1[get_texture_temp_udp][enqueued] is 0", sd1["get_texture_temp_udp"]["enqueued"].asInteger(), 0); + ensure_equals("sd1[get_texture_non_temp_http][enqueued] is 0", sd1["get_texture_non_temp_http"]["enqueued"].asInteger(), 0); + ensure_equals("sd1[get_texture_temp_http][enqueued] is 0", sd1["get_texture_temp_http"]["enqueued"].asInteger(), 0); + ensure_equals("sd1[get_gesture_udp][dequeued] is 0", sd1["get_gesture_udp"]["dequeued"].asInteger(), 0); + + // Check a few points on the tree for content + ensure("sd2[get_gesture_udp][enqueued] is 4", (4 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); + ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStatsMain->reset(); + sd = gViewerAssetStatsMain->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); + sd2 = sd["regions"][0]; + + delete gViewerAssetStatsMain; + gViewerAssetStatsMain = NULL; + + ensure("sd2[get_texture_non_temp_udp][enqueued] is reset", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd2[get_gesture_udp][enqueued] is reset", (0 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + } + + // Check multiple region collection jumping back-and-forth between regions + template<> template<> + void tst_viewerassetstats_index_object_t::test<7>() + { + gViewerAssetStatsMain = new LLViewerAssetStats(); + + LLViewerAssetStatsFF::set_region_main(region1_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::set_region_main(region2_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + + LLViewerAssetStatsFF::set_region_main(region1_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, true, true); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, true, true); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::set_region_main(region2_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false); + + LLSD sd = gViewerAssetStatsMain->asLLSD(false); + + ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); + ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); + LLSD sd1 = get_region(sd, region1_handle); + LLSD sd2 = get_region(sd, region2_handle); + ensure("Region1 is present in results", sd1.isMap()); + ensure("Region2 is present in results", sd2.isMap()); + + // Check a few points on the tree for content + ensure("sd1[get_texture_non_temp_udp][enqueued] is 1", (1 == sd1["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd1[get_texture_temp_udp][enqueued] is 0", (0 == sd1["get_texture_temp_udp"]["enqueued"].asInteger())); + ensure("sd1[get_texture_non_temp_http][enqueued] is 0", (0 == sd1["get_texture_non_temp_http"]["enqueued"].asInteger())); + ensure("sd1[get_texture_temp_http][enqueued] is 1", (1 == sd1["get_texture_temp_http"]["enqueued"].asInteger())); + ensure("sd1[get_gesture_udp][dequeued] is 0", (0 == sd1["get_gesture_udp"]["dequeued"].asInteger())); + + // Check a few points on the tree for content + ensure("sd2[get_gesture_udp][enqueued] is 8", (8 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); + ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStatsMain->reset(); + sd = gViewerAssetStatsMain->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "duration", "regions")); + ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); + sd2 = get_region(sd, region2_handle); + ensure("Region2 is present in results", sd2.isMap()); + + delete gViewerAssetStatsMain; + gViewerAssetStatsMain = NULL; + + ensure_equals("sd2[get_texture_non_temp_udp][enqueued] is reset", sd2["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); + ensure_equals("sd2[get_gesture_udp][enqueued] is reset", sd2["get_gesture_udp"]["enqueued"].asInteger(), 0); + } + + // Non-texture assets ignore transport and persistence flags + template<> template<> + void tst_viewerassetstats_index_object_t::test<8>() + { + gViewerAssetStatsThread1 = new LLViewerAssetStats(); + gViewerAssetStatsMain = new LLViewerAssetStats(); + LLViewerAssetStatsFF::set_region_main(region1_handle); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, true); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, true); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, true, false); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, true, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, true, true); + LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, true, true); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, false, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, false, true); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, true, false); + + LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + LLSD sd = gViewerAssetStatsThread1->asLLSD(false); + ensure("Other collector is empty", is_no_stats_map(sd)); + sd = gViewerAssetStatsMain->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = get_region(sd, region1_handle); + ensure("Region1 is present in results", sd.isMap()); + + // Check a few points on the tree for content + ensure("sd[get_gesture_udp][enqueued] is 0", (0 == sd["get_gesture_udp"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + + ensure("sd[get_wearable_udp][enqueued] is 4", (4 == sd["get_wearable_udp"]["enqueued"].asInteger())); + ensure("sd[get_wearable_udp][dequeued] is 4", (4 == sd["get_wearable_udp"]["dequeued"].asInteger())); + + ensure("sd[get_other][enqueued] is 4", (4 == sd["get_other"]["enqueued"].asInteger())); + ensure("sd[get_other][dequeued] is 0", (0 == sd["get_other"]["dequeued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStatsMain->reset(); + sd = get_region(gViewerAssetStatsMain->asLLSD(false), region1_handle); + ensure("Region1 is present in results", sd.isMap()); + + delete gViewerAssetStatsMain; + gViewerAssetStatsMain = NULL; + delete gViewerAssetStatsThread1; + gViewerAssetStatsThread1 = NULL; + + ensure_equals("sd[get_texture_non_temp_udp][enqueued] is reset", sd["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); + ensure_equals("sd[get_gesture_udp][dequeued] is reset", sd["get_gesture_udp"]["dequeued"].asInteger(), 0); + } + + + // LLViewerAssetStats::merge() basic functions work + template<> template<> + void tst_viewerassetstats_index_object_t::test<9>() + { + LLViewerAssetStats s1; + LLViewerAssetStats s2; + + s1.setRegion(region1_handle); + s2.setRegion(region1_handle); + + s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 5000000); + s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 6000000); + s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 8000000); + s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 7000000); + s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 9000000); + + s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 2000000); + s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 3000000); + s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 4000000); + + s2.merge(s1); + + LLSD s2_llsd = get_region(s2.asLLSD(false), region1_handle); + ensure("Region1 is present in results", s2_llsd.isMap()); + + ensure_equals("count after merge", s2_llsd["get_texture_temp_http"]["resp_count"].asInteger(), 8); + ensure_approximately_equals("min after merge", s2_llsd["get_texture_temp_http"]["resp_min"].asReal(), 2.0, 22); + ensure_approximately_equals("max after merge", s2_llsd["get_texture_temp_http"]["resp_max"].asReal(), 9.0, 22); + ensure_approximately_equals("max after merge", s2_llsd["get_texture_temp_http"]["resp_mean"].asReal(), 5.5, 22); + } + + // LLViewerAssetStats::merge() basic functions work without corrupting source data + template<> template<> + void tst_viewerassetstats_index_object_t::test<10>() + { + LLViewerAssetStats s1; + LLViewerAssetStats s2; + + s1.setRegion(region1_handle); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900); + + + s2.setRegion(region2_handle); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 6500000); + s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 10000); + + { + s2.merge(s1); + + LLSD src = s1.asLLSD(false); + LLSD dst = s2.asLLSD(false); + + ensure_equals("merge src has single region", src["regions"].size(), 1); + ensure_equals("merge dst has dual regions", dst["regions"].size(), 2); + + // Remove time stamps, they're a problem + src.erase("duration"); + src["regions"][0].erase("duration"); + dst.erase("duration"); + dst["regions"][0].erase("duration"); + dst["regions"][1].erase("duration"); + + LLSD s1_llsd = get_region(src, region1_handle); + ensure("Region1 is present in src", s1_llsd.isMap()); + LLSD s2_llsd = get_region(dst, region1_handle); + ensure("Region1 is present in dst", s2_llsd.isMap()); + + ensure("result from src is in dst", llsd_equals(s1_llsd, s2_llsd)); + } + + s1.setRegion(region1_handle); + s2.setRegion(region1_handle); + s1.reset(); + s2.reset(); + + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900); + + + s2.setRegion(region1_handle); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 6500000); + s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 10000); + + { + s2.merge(s1); + + LLSD src = s1.asLLSD(false); + LLSD dst = s2.asLLSD(false); + + ensure_equals("merge src has single region (p2)", src["regions"].size(), 1); + ensure_equals("merge dst has single region (p2)", dst["regions"].size(), 1); + + // Remove time stamps, they're a problem + src.erase("duration"); + src["regions"][0].erase("duration"); + dst.erase("duration"); + dst["regions"][0].erase("duration"); + + LLSD s1_llsd = get_region(src, region1_handle); + ensure("Region1 is present in src", s1_llsd.isMap()); + LLSD s2_llsd = get_region(dst, region1_handle); + ensure("Region1 is present in dst", s2_llsd.isMap()); + + ensure_equals("src counts okay (enq)", s1_llsd["get_other"]["enqueued"].asInteger(), 4); + ensure_equals("src counts okay (deq)", s1_llsd["get_other"]["dequeued"].asInteger(), 4); + ensure_equals("src resp counts okay", s1_llsd["get_other"]["resp_count"].asInteger(), 2); + ensure_approximately_equals("src respmin okay", s1_llsd["get_other"]["resp_min"].asReal(), 0.2829, 20); + ensure_approximately_equals("src respmax okay", s1_llsd["get_other"]["resp_max"].asReal(), 23.2892, 20); + + ensure_equals("dst counts okay (enq)", s2_llsd["get_other"]["enqueued"].asInteger(), 12); + ensure_equals("src counts okay (deq)", s2_llsd["get_other"]["dequeued"].asInteger(), 11); + ensure_equals("dst resp counts okay", s2_llsd["get_other"]["resp_count"].asInteger(), 4); + ensure_approximately_equals("dst respmin okay", s2_llsd["get_other"]["resp_min"].asReal(), 0.010, 20); + ensure_approximately_equals("dst respmax okay", s2_llsd["get_other"]["resp_max"].asReal(), 23.2892, 20); + } + } + + + // Maximum merges are interesting when one side contributes nothing + template<> template<> + void tst_viewerassetstats_index_object_t::test<11>() + { + LLViewerAssetStats s1; + LLViewerAssetStats s2; + + s1.setRegion(region1_handle); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + // Want to test negative numbers here but have to work in U64 + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + + s2.setRegion(region1_handle); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + { + s2.merge(s1); + + LLSD src = s1.asLLSD(false); + LLSD dst = s2.asLLSD(false); + + ensure_equals("merge src has single region", src["regions"].size(), 1); + ensure_equals("merge dst has single region", dst["regions"].size(), 1); + + // Remove time stamps, they're a problem + src.erase("duration"); + src["regions"][0].erase("duration"); + dst.erase("duration"); + dst["regions"][0].erase("duration"); + + LLSD s2_llsd = get_region(dst, region1_handle); + ensure("Region1 is present in dst", s2_llsd.isMap()); + + ensure_equals("dst counts come from src only", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + + ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum", + s2_llsd["get_other"]["resp_max"].asReal(), F64(0.0), 20); + } + + // Other way around + s1.setRegion(region1_handle); + s2.setRegion(region1_handle); + s1.reset(); + s2.reset(); + + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + // Want to test negative numbers here but have to work in U64 + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0); + + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + { + s1.merge(s2); + + LLSD src = s2.asLLSD(false); + LLSD dst = s1.asLLSD(false); + + ensure_equals("merge src has single region", src["regions"].size(), 1); + ensure_equals("merge dst has single region", dst["regions"].size(), 1); + + // Remove time stamps, they're a problem + src.erase("duration"); + src["regions"][0].erase("duration"); + dst.erase("duration"); + dst["regions"][0].erase("duration"); + + LLSD s2_llsd = get_region(dst, region1_handle); + ensure("Region1 is present in dst", s2_llsd.isMap()); + + ensure_equals("dst counts come from src only (flipped)", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + + ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum (flipped)", + s2_llsd["get_other"]["resp_max"].asReal(), F64(0.0), 20); + } + } + + // Minimum merges are interesting when one side contributes nothing + template<> template<> + void tst_viewerassetstats_index_object_t::test<12>() + { + LLViewerAssetStats s1; + LLViewerAssetStats s2; + + s1.setRegion(region1_handle); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 3800000); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2700000); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2900000); + + s2.setRegion(region1_handle); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + { + s2.merge(s1); + + LLSD src = s1.asLLSD(false); + LLSD dst = s2.asLLSD(false); + + ensure_equals("merge src has single region", src["regions"].size(), 1); + ensure_equals("merge dst has single region", dst["regions"].size(), 1); + + // Remove time stamps, they're a problem + src.erase("duration"); + src["regions"][0].erase("duration"); + dst.erase("duration"); + dst["regions"][0].erase("duration"); + + LLSD s2_llsd = get_region(dst, region1_handle); + ensure("Region1 is present in dst", s2_llsd.isMap()); + + ensure_equals("dst counts come from src only", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + + ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum", + s2_llsd["get_other"]["resp_min"].asReal(), F64(2.7), 20); + } + + // Other way around + s1.setRegion(region1_handle); + s2.setRegion(region1_handle); + s1.reset(); + s2.reset(); + + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 3800000); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2700000); + s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2900000); + + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + { + s1.merge(s2); + + LLSD src = s2.asLLSD(false); + LLSD dst = s1.asLLSD(false); + + ensure_equals("merge src has single region", src["regions"].size(), 1); + ensure_equals("merge dst has single region", dst["regions"].size(), 1); + + // Remove time stamps, they're a problem + src.erase("duration"); + src["regions"][0].erase("duration"); + dst.erase("duration"); + dst["regions"][0].erase("duration"); + + LLSD s2_llsd = get_region(dst, region1_handle); + ensure("Region1 is present in dst", s2_llsd.isMap()); + + ensure_equals("dst counts come from src only (flipped)", s2_llsd["get_other"]["resp_count"].asInteger(), 3); + + ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum (flipped)", + s2_llsd["get_other"]["resp_min"].asReal(), F64(2.7), 20); + } + } + +} diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 6c77f8ec38..338c62b9fb 100644 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -255,12 +255,6 @@ class WindowsManifest(ViewerManifest): self.enable_crt_manifest_check() - # Get kdu dll, continue if missing. - try: - self.path('llkdu.dll', dst='llkdu.dll') - except RuntimeError: - print "Skipping llkdu.dll" - # Get llcommon and deps. If missing assume static linkage and continue. try: self.path('llcommon.dll') @@ -625,21 +619,21 @@ class DarwinManifest(ViewerManifest): libdir = "../../libraries/universal-darwin/lib_release" dylibs = {} - # need to get the kdu dll from any of the build directories as well - for lib in "llkdu", "llcommon": - libfile = "lib%s.dylib" % lib - try: - self.path(self.find_existing_file(os.path.join(os.pardir, - lib, - self.args['configuration'], - libfile), - os.path.join(libdir, libfile)), - dst=libfile) - except RuntimeError: - print "Skipping %s" % libfile - dylibs[lib] = False - else: - dylibs[lib] = True + # Need to get the llcommon dll from any of the build directories as well + lib = "llcommon" + libfile = "lib%s.dylib" % lib + try: + self.path(self.find_existing_file(os.path.join(os.pardir, + lib, + self.args['configuration'], + libfile), + os.path.join(libdir, libfile)), + dst=libfile) + except RuntimeError: + print "Skipping %s" % libfile + dylibs[lib] = False + else: + dylibs[lib] = True if dylibs["llcommon"]: for libfile in ("libapr-1.0.3.7.dylib", @@ -931,15 +925,6 @@ class Linux_i686Manifest(LinuxManifest): def construct(self): super(Linux_i686Manifest, self).construct() - # install either the libllkdu we just built, or a prebuilt one, in - # decreasing order of preference. for linux package, this goes to bin/ - try: - self.path(self.find_existing_file('../llkdu/libllkdu.so', - '../../libraries/i686-linux/lib_release_client/libllkdu.so'), - dst='bin/libllkdu.so') - except: - print "Skipping libllkdu.so - not found" - if self.prefix("../../libraries/i686-linux/lib_release_client", dst="lib"): self.path("libapr-1.so.0") self.path("libaprutil-1.so.0") @@ -956,12 +941,6 @@ class Linux_i686Manifest(LinuxManifest): self.path("libopenal.so", "libopenal.so.1") self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname try: - self.path("libkdu.so") - pass - except: - print "Skipping libkdu.so - not found" - pass - try: self.path("libfmod-3.75.so") pass except: |