summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/CMakeLists.txt5
-rw-r--r--indra/cmake/CURL.cmake6
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake103
-rw-r--r--indra/cmake/LLKDU.cmake17
-rw-r--r--indra/integration_tests/llui_libtest/CMakeLists.txt6
-rw-r--r--indra/llcommon/llqueuedthread.h2
-rw-r--r--indra/llimage/CMakeLists.txt1
-rw-r--r--indra/llimage/llimage.cpp2
-rw-r--r--indra/llimage/llimagej2c.cpp162
-rw-r--r--indra/llimage/llimagej2c.h2
-rw-r--r--indra/llinventory/llnotecard.cpp2
-rw-r--r--indra/llkdu/CMakeLists.txt45
-rw-r--r--indra/llkdu/llimagej2ckdu.cpp1084
-rw-r--r--indra/llkdu/llimagej2ckdu.h91
-rw-r--r--indra/llkdu/llkdumem.cpp196
-rw-r--r--indra/llkdu/llkdumem.h145
-rw-r--r--indra/llmessage/llassetstorage.cpp4
-rw-r--r--indra/mac_updater/CMakeLists.txt3
-rw-r--r--indra/newview/CMakeLists.txt31
-rw-r--r--indra/newview/llagent.cpp3
-rw-r--r--indra/newview/llappviewer.cpp123
-rw-r--r--indra/newview/llappviewer.h4
-rw-r--r--indra/newview/llbuycurrencyhtml.cpp4
-rw-r--r--indra/newview/llfloaterbuycurrency.cpp6
-rw-r--r--indra/newview/llfloaterbuycurrencyhtml.cpp4
-rw-r--r--indra/newview/llsimplestat.h158
-rw-r--r--indra/newview/llstatusbar.cpp18
-rw-r--r--indra/newview/llstatusbar.h2
-rw-r--r--indra/newview/lltexturefetch.cpp669
-rw-r--r--indra/newview/lltexturefetch.h88
-rw-r--r--indra/newview/llviewerassetstats.cpp619
-rw-r--r--indra/newview/llviewerassetstats.h334
-rw-r--r--indra/newview/llviewerassetstorage.cpp129
-rw-r--r--indra/newview/llviewerassetstorage.h11
-rw-r--r--indra/newview/llviewerregion.cpp1
-rw-r--r--indra/newview/skins/default/xui/en/floater_media_browser.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_status_bar.xml2
-rw-r--r--indra/newview/tests/llsimplestat_test.cpp586
-rw-r--r--indra/newview/tests/llviewerassetstats_test.cpp990
-rw-r--r--indra/newview/viewer_manifest.py51
40 files changed, 5384 insertions, 327 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index d01e1869b6..6d17a6f402 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -43,6 +43,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llaudio)
add_subdirectory(${LIBS_OPEN_PREFIX}llcharacter)
add_subdirectory(${LIBS_OPEN_PREFIX}llcommon)
add_subdirectory(${LIBS_OPEN_PREFIX}llimage)
+add_subdirectory(${LIBS_OPEN_PREFIX}llkdu)
add_subdirectory(${LIBS_OPEN_PREFIX}llimagej2coj)
add_subdirectory(${LIBS_OPEN_PREFIX}llinventory)
add_subdirectory(${LIBS_OPEN_PREFIX}llmath)
@@ -53,10 +54,6 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llvfs)
add_subdirectory(${LIBS_OPEN_PREFIX}llwindow)
add_subdirectory(${LIBS_OPEN_PREFIX}llxml)
-if (EXISTS ${LIBS_CLOSED_DIR}llkdu)
- add_subdirectory(${LIBS_CLOSED_PREFIX}llkdu)
-endif (EXISTS ${LIBS_CLOSED_DIR}llkdu)
-
add_subdirectory(${LIBS_OPEN_PREFIX}lscript)
if (WINDOWS AND EXISTS ${LIBS_CLOSED_DIR}copy_win_scripts)
diff --git a/indra/cmake/CURL.cmake b/indra/cmake/CURL.cmake
index 6e5fed4d52..9aba08e573 100644
--- a/indra/cmake/CURL.cmake
+++ b/indra/cmake/CURL.cmake
@@ -10,10 +10,10 @@ else (STANDALONE)
use_prebuilt_binary(curl)
if (WINDOWS)
set(CURL_LIBRARIES
- debug libcurld
- optimized libcurl)
+ debug libcurld.lib
+ optimized libcurl.lib)
else (WINDOWS)
- set(CURL_LIBRARIES curl)
+ set(CURL_LIBRARIES libcurl.a)
endif (WINDOWS)
set(CURL_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include)
endif (STANDALONE)
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index e852cf463c..176ae9787e 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -60,22 +60,6 @@ if(WINDOWS)
set(release_files ${release_files} fmod.dll)
endif (FMOD)
- #*******************************
- # LLKDU
- set(internal_llkdu_path "${CMAKE_SOURCE_DIR}/llkdu")
- if(NOT EXISTS ${internal_llkdu_path})
- if (EXISTS "${debug_src_dir}/llkdu.dll")
- set(debug_llkdu_src "${debug_src_dir}/llkdu.dll")
- set(debug_llkdu_dst "${SHARED_LIB_STAGING_DIR_DEBUG}/llkdu.dll")
- endif (EXISTS "${debug_src_dir}/llkdu.dll")
-
- if (EXISTS "${release_src_dir}/llkdu.dll")
- set(release_llkdu_src "${release_src_dir}/llkdu.dll")
- set(release_llkdu_dst "${SHARED_LIB_STAGING_DIR_RELEASE}/llkdu.dll")
- set(relwithdebinfo_llkdu_dst "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}/llkdu.dll")
- endif (EXISTS "${release_src_dir}/llkdu.dll")
- endif (NOT EXISTS ${internal_llkdu_path})
-
#*******************************
# Copy MS C runtime dlls, required for packaging.
# *TODO - Adapt this to support VC9
@@ -174,21 +158,6 @@ elseif(DARWIN)
# fmod is statically linked on darwin
set(fmod_files "")
- #*******************************
- # LLKDU
- set(internal_llkdu_path "${CMAKE_SOURCE_DIR}/llkdu")
- if(NOT EXISTS ${internal_llkdu_path})
- if (EXISTS "${debug_src_dir}/libllkdu.dylib")
- set(debug_llkdu_src "${debug_src_dir}/libllkdu.dylib")
- set(debug_llkdu_dst "${SHARED_LIB_STAGING_DIR_DEBUG}/libllkdu.dylib")
- endif (EXISTS "${debug_src_dir}/libllkdu.dylib")
-
- if (EXISTS "${release_src_dir}/libllkdu.dylib")
- set(release_llkdu_src "${release_src_dir}/libllkdu.dylib")
- set(release_llkdu_dst "${SHARED_LIB_STAGING_DIR_RELEASE}/libllkdu.dylib")
- set(relwithdebinfo_llkdu_dst "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}/libllkdu.dylib")
- endif (EXISTS "${release_src_dir}/libllkdu.dylib")
- endif (NOT EXISTS ${internal_llkdu_path})
elseif(LINUX)
# linux is weird, multiple side by side configurations aren't supported
# and we don't seem to have any debug shared libs built yet anyways...
@@ -242,21 +211,6 @@ elseif(LINUX)
set(release_files ${release_files} "libfmod-3.75.so")
endif (FMOD)
- #*******************************
- # LLKDU
- set(internal_llkdu_path "${CMAKE_SOURCE_DIR}/llkdu")
- if(NOT EXISTS ${internal_llkdu_path})
- if (EXISTS "${debug_src_dir}/libllkdu.so")
- set(debug_llkdu_src "${debug_src_dir}/libllkdu.so")
- set(debug_llkdu_dst "${SHARED_LIB_STAGING_DIR_DEBUG}/libllkdu.so")
- endif (EXISTS "${debug_src_dir}/libllkdu.so")
-
- if (EXISTS "${release_src_dir}/libllkdu.so")
- set(release_llkdu_src "${release_src_dir}/libllkdu.so")
- set(release_llkdu_dst "${SHARED_LIB_STAGING_DIR_RELEASE}/libllkdu.so")
- set(relwithdebinfo_llkdu_dst "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}/libllkdu.so")
- endif (EXISTS "${release_src_dir}/libllkdu.so")
- endif(NOT EXISTS ${internal_llkdu_path})
else(WINDOWS)
message(STATUS "WARNING: unrecognized platform for staging 3rd party libs, skipping...")
set(vivox_src_dir "${CMAKE_SOURCE_DIR}/newview/vivox-runtime/i686-linux")
@@ -334,40 +288,29 @@ copy_if_different(
)
set(third_party_targets ${third_party_targets} ${out_targets})
-#*******************************
-# LLKDU
-set(internal_llkdu_path "${CMAKE_SOURCE_DIR}/llkdu")
-if(NOT EXISTS ${internal_llkdu_path})
- if (EXISTS "${debug_llkdu_src}")
- ADD_CUSTOM_COMMAND(
- OUTPUT ${debug_llkdu_dst}
- COMMAND ${CMAKE_COMMAND} -E copy_if_different ${debug_llkdu_src} ${debug_llkdu_dst}
- DEPENDS ${debug_llkdu_src}
- COMMENT "Copying llkdu.dll ${SHARED_LIB_STAGING_DIR_DEBUG}"
- )
- set(third_party_targets ${third_party_targets} $} ${debug_llkdu_dst})
- endif (EXISTS "${debug_llkdu_src}")
-
- if (EXISTS "${release_llkdu_src}")
- ADD_CUSTOM_COMMAND(
- OUTPUT ${release_llkdu_dst}
- COMMAND ${CMAKE_COMMAND} -E copy_if_different ${release_llkdu_src} ${release_llkdu_dst}
- DEPENDS ${release_llkdu_src}
- COMMENT "Copying llkdu.dll ${SHARED_LIB_STAGING_DIR_RELEASE}"
- )
- set(third_party_targets ${third_party_targets} ${release_llkdu_dst})
-
- ADD_CUSTOM_COMMAND(
- OUTPUT ${relwithdebinfo_llkdu_dst}
- COMMAND ${CMAKE_COMMAND} -E copy_if_different ${release_llkdu_src} ${relwithdebinfo_llkdu_dst}
- DEPENDS ${release_llkdu_src}
- COMMENT "Copying llkdu.dll ${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- )
- set(third_party_targets ${third_party_targets} ${relwithdebinfo_llkdu_dst})
- endif (EXISTS "${release_llkdu_src}")
-
-endif (NOT EXISTS ${internal_llkdu_path})
-
+if (FMOD_SDK_DIR)
+ copy_if_different(
+ ${FMOD_SDK_DIR}
+ "${CMAKE_CURRENT_BINARY_DIR}/Debug"
+ out_targets
+ ${fmod_files}
+ )
+ set(all_targets ${all_targets} ${out_targets})
+ copy_if_different(
+ ${FMOD_SDK_DIR}
+ "${CMAKE_CURRENT_BINARY_DIR}/Release"
+ out_targets
+ ${fmod_files}
+ )
+ set(all_targets ${all_targets} ${out_targets})
+ copy_if_different(
+ ${FMOD_SDK_DIR}
+ "${CMAKE_CURRENT_BINARY_DIR}/RelWithDbgInfo"
+ out_targets
+ ${fmod_files}
+ )
+ set(all_targets ${all_targets} ${out_targets})
+endif (FMOD_SDK_DIR)
if(NOT STANDALONE)
add_custom_target(
diff --git a/indra/cmake/LLKDU.cmake b/indra/cmake/LLKDU.cmake
index 27c8ada686..f5cbad03a6 100644
--- a/indra/cmake/LLKDU.cmake
+++ b/indra/cmake/LLKDU.cmake
@@ -1,7 +1,20 @@
# -*- cmake -*-
include(Prebuilt)
+# USE_KDU can be set when launching cmake or develop.py as an option using the argument -DUSE_KDU:BOOL=ON
+# When building using proprietary binaries though (i.e. having access to LL private servers), we always build with KDU
if (INSTALL_PROPRIETARY AND NOT STANDALONE)
- use_prebuilt_binary(kdu)
- set(LLKDU_LIBRARY llkdu)
+ set(USE_KDU ON)
endif (INSTALL_PROPRIETARY AND NOT STANDALONE)
+
+if (USE_KDU)
+ use_prebuilt_binary(kdu)
+ if (WINDOWS)
+ set(KDU_LIBRARY kdu.lib)
+ else (WINDOWS)
+ set(KDU_LIBRARY libkdu.a)
+ endif (WINDOWS)
+ set(KDU_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/kdu)
+ set(LLKDU_INCLUDE_DIRS ${LIBS_OPEN_DIR}/llkdu)
+ set(LLKDU_LIBRARIES llkdu)
+endif (USE_KDU)
diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt
index 452d37d3be..e0772e55ca 100644
--- a/indra/integration_tests/llui_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llui_libtest/CMakeLists.txt
@@ -10,6 +10,7 @@ include(00-Common)
include(LLCommon)
include(LLImage)
include(LLImageJ2COJ) # ugh, needed for images
+include(LLKDU)
include(LLMath)
include(LLMessage)
include(LLRender)
@@ -71,6 +72,11 @@ endif (DARWIN)
target_link_libraries(llui_libtest
llui
llmessage
+ ${LLRENDER_LIBRARIES}
+ ${LLIMAGE_LIBRARIES}
+ ${LLKDU_LIBRARIES}
+ ${KDU_LIBRARY}
+ ${LLIMAGEJ2COJ_LIBRARIES}
${OS_LIBRARIES}
${GOOGLE_PERFTOOLS_LIBRARIES}
)
diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h
index c75e0e2bbf..a53b22f6fc 100644
--- a/indra/llcommon/llqueuedthread.h
+++ b/indra/llcommon/llqueuedthread.h
@@ -179,7 +179,7 @@ public:
void waitOnPending();
void printQueueStats();
- S32 getPending();
+ virtual S32 getPending();
bool getThreaded() { return mThreaded ? true : false; }
// Request accessors
diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt
index a69621a57b..6834267d4b 100644
--- a/indra/llimage/CMakeLists.txt
+++ b/indra/llimage/CMakeLists.txt
@@ -57,7 +57,6 @@ add_library (llimage ${llimage_SOURCE_FILES})
# Sort by high-level to low-level
target_link_libraries(llimage
llcommon
- llimagej2coj # *HACK: In theory a noop for KDU builds?
${JPEG_LIBRARIES}
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index 5c33b675ca..b46a99e030 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -52,13 +52,11 @@ LLMutex* LLImage::sMutex = NULL;
void LLImage::initClass()
{
sMutex = new LLMutex(NULL);
- LLImageJ2C::openDSO();
}
//static
void LLImage::cleanupClass()
{
- LLImageJ2C::closeDSO();
delete sMutex;
sMutex = NULL;
}
diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp
index d005aaf29f..cb2a85fa91 100644
--- a/indra/llimage/llimagej2c.cpp
+++ b/indra/llimage/llimagej2c.cpp
@@ -24,9 +24,6 @@
*/
#include "linden_common.h"
-#include "apr_pools.h"
-#include "apr_dso.h"
-
#include "lldir.h"
#include "llimagej2c.h"
#include "llmemtype.h"
@@ -37,18 +34,10 @@ typedef LLImageJ2CImpl* (*CreateLLImageJ2CFunction)();
typedef void (*DestroyLLImageJ2CFunction)(LLImageJ2CImpl*);
typedef const char* (*EngineInfoLLImageJ2CFunction)();
-//some "private static" variables so we only attempt to load
-//dynamic libaries once
-CreateLLImageJ2CFunction j2cimpl_create_func;
-DestroyLLImageJ2CFunction j2cimpl_destroy_func;
-EngineInfoLLImageJ2CFunction j2cimpl_engineinfo_func;
-apr_pool_t *j2cimpl_dso_memory_pool;
-apr_dso_handle_t *j2cimpl_dso_handle;
-
-//Declare the prototype for theses functions here, their functionality
-//will be implemented in other files which define a derived LLImageJ2CImpl
-//but only ONE static library which has the implementation for this
-//function should ever be included
+// Declare the prototype for theses functions here. Their functionality
+// will be implemented in other files which define a derived LLImageJ2CImpl
+// but only ONE static library which has the implementation for these
+// functions should ever be included.
LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl();
void fallbackDestroyLLImageJ2CImpl(LLImageJ2CImpl* impl);
const char* fallbackEngineInfoLLImageJ2CImpl();
@@ -58,120 +47,9 @@ LLImageCompressionTester* LLImageJ2C::sTesterp = NULL ;
const std::string sTesterName("ImageCompressionTester");
//static
-//Loads the required "create", "destroy" and "engineinfo" functions needed
-void LLImageJ2C::openDSO()
-{
- //attempt to load a DSO and get some functions from it
- std::string dso_name;
- std::string dso_path;
-
- bool all_functions_loaded = false;
- apr_status_t rv;
-
-#if LL_WINDOWS
- dso_name = "llkdu.dll";
-#elif LL_DARWIN
- dso_name = "libllkdu.dylib";
-#else
- dso_name = "libllkdu.so";
-#endif
-
- dso_path = gDirUtilp->findFile(dso_name,
- gDirUtilp->getAppRODataDir(),
- gDirUtilp->getExecutableDir());
-
- j2cimpl_dso_handle = NULL;
- j2cimpl_dso_memory_pool = NULL;
-
- //attempt to load the shared library
- apr_pool_create(&j2cimpl_dso_memory_pool, NULL);
- rv = apr_dso_load(&j2cimpl_dso_handle,
- dso_path.c_str(),
- j2cimpl_dso_memory_pool);
-
- //now, check for success
- if ( rv == APR_SUCCESS )
- {
- //found the dynamic library
- //now we want to load the functions we're interested in
- CreateLLImageJ2CFunction create_func = NULL;
- DestroyLLImageJ2CFunction dest_func = NULL;
- EngineInfoLLImageJ2CFunction engineinfo_func = NULL;
-
- rv = apr_dso_sym((apr_dso_handle_sym_t*)&create_func,
- j2cimpl_dso_handle,
- "createLLImageJ2CKDU");
- if ( rv == APR_SUCCESS )
- {
- //we've loaded the create function ok
- //we need to delete via the DSO too
- //so lets check for a destruction function
- rv = apr_dso_sym((apr_dso_handle_sym_t*)&dest_func,
- j2cimpl_dso_handle,
- "destroyLLImageJ2CKDU");
- if ( rv == APR_SUCCESS )
- {
- //we've loaded the destroy function ok
- rv = apr_dso_sym((apr_dso_handle_sym_t*)&engineinfo_func,
- j2cimpl_dso_handle,
- "engineInfoLLImageJ2CKDU");
- if ( rv == APR_SUCCESS )
- {
- //ok, everything is loaded alright
- j2cimpl_create_func = create_func;
- j2cimpl_destroy_func = dest_func;
- j2cimpl_engineinfo_func = engineinfo_func;
- all_functions_loaded = true;
- }
- }
- }
- }
-
- if ( !all_functions_loaded )
- {
- //something went wrong with the DSO or function loading..
- //fall back onto our satefy impl creation function
-
-#if 0
- // precious verbose debugging, sadly we can't use our
- // 'llinfos' stream etc. this early in the initialisation seq.
- char errbuf[256];
- fprintf(stderr, "failed to load syms from DSO %s (%s)\n",
- dso_name.c_str(), dso_path.c_str());
- apr_strerror(rv, errbuf, sizeof(errbuf));
- fprintf(stderr, "error: %d, %s\n", rv, errbuf);
- apr_dso_error(j2cimpl_dso_handle, errbuf, sizeof(errbuf));
- fprintf(stderr, "dso-error: %d, %s\n", rv, errbuf);
-#endif
-
- if ( j2cimpl_dso_handle )
- {
- apr_dso_unload(j2cimpl_dso_handle);
- j2cimpl_dso_handle = NULL;
- }
-
- if ( j2cimpl_dso_memory_pool )
- {
- apr_pool_destroy(j2cimpl_dso_memory_pool);
- j2cimpl_dso_memory_pool = NULL;
- }
- }
-}
-
-//static
-void LLImageJ2C::closeDSO()
-{
- if ( j2cimpl_dso_handle ) apr_dso_unload(j2cimpl_dso_handle);
- if (j2cimpl_dso_memory_pool) apr_pool_destroy(j2cimpl_dso_memory_pool);
-}
-
-//static
std::string LLImageJ2C::getEngineInfo()
{
- if (!j2cimpl_engineinfo_func)
- j2cimpl_engineinfo_func = fallbackEngineInfoLLImageJ2CImpl;
-
- return j2cimpl_engineinfo_func();
+ return fallbackEngineInfoLLImageJ2CImpl();
}
LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C),
@@ -181,20 +59,7 @@ LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C),
mReversible(FALSE),
mAreaUsedForDataSizeCalcs(0)
{
- //We assume here that if we wanted to create via
- //a dynamic library that the approriate open calls were made
- //before any calls to this constructor.
-
- //Therefore, a NULL creation function pointer here means
- //we either did not want to create using functions from the dynamic
- //library or there were issues loading it, either way
- //use our fall back
- if ( !j2cimpl_create_func )
- {
- j2cimpl_create_func = fallbackCreateLLImageJ2CImpl;
- }
-
- mImpl = j2cimpl_create_func();
+ mImpl = fallbackCreateLLImageJ2CImpl();
// Clear data size table
for( S32 i = 0; i <= MAX_DISCARD_LEVEL; i++)
@@ -217,22 +82,9 @@ LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C),
// virtual
LLImageJ2C::~LLImageJ2C()
{
- //We assume here that if we wanted to destroy via
- //a dynamic library that the approriate open calls were made
- //before any calls to this destructor.
-
- //Therefore, a NULL creation function pointer here means
- //we either did not want to destroy using functions from the dynamic
- //library or there were issues loading it, either way
- //use our fall back
- if ( !j2cimpl_destroy_func )
- {
- j2cimpl_destroy_func = fallbackDestroyLLImageJ2CImpl;
- }
-
if ( mImpl )
{
- j2cimpl_destroy_func(mImpl);
+ fallbackDestroyLLImageJ2CImpl(mImpl);
}
}
diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h
index cc3dabd7d8..dd5bec8b2e 100644
--- a/indra/llimage/llimagej2c.h
+++ b/indra/llimage/llimagej2c.h
@@ -72,8 +72,6 @@ public:
static S32 calcHeaderSizeJ2C();
static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = 0.f);
- static void openDSO();
- static void closeDSO();
static std::string getEngineInfo();
protected:
diff --git a/indra/llinventory/llnotecard.cpp b/indra/llinventory/llnotecard.cpp
index 62829c284f..69152cefe0 100644
--- a/indra/llinventory/llnotecard.cpp
+++ b/indra/llinventory/llnotecard.cpp
@@ -199,7 +199,7 @@ bool LLNotecard::importStream(std::istream& str)
return FALSE;
}
- if(text_len > mMaxText)
+ if(text_len > mMaxText || text_len < 0)
{
llwarns << "Invalid Linden text length: " << text_len << llendl;
return FALSE;
diff --git a/indra/llkdu/CMakeLists.txt b/indra/llkdu/CMakeLists.txt
new file mode 100644
index 0000000000..b8b44b44fc
--- /dev/null
+++ b/indra/llkdu/CMakeLists.txt
@@ -0,0 +1,45 @@
+# -*- cmake -*-
+
+project(llkdu)
+
+# Visual Studio 2005 has a dumb bug that causes it to fail compilation
+# of KDU if building with both optimisation and /WS (treat warnings as
+# errors), even when the specific warnings that make it croak are
+# disabled.
+
+#set(VS_DISABLE_FATAL_WARNINGS ON)
+
+include(00-Common)
+include(LLCommon)
+include(LLImage)
+include(LLKDU)
+include(LLMath)
+
+include_directories(
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLIMAGE_INCLUDE_DIRS}
+ ${KDU_INCLUDE_DIR}
+ ${LLMATH_INCLUDE_DIRS}
+ )
+
+set(llkdu_SOURCE_FILES
+ llimagej2ckdu.cpp
+ llkdumem.cpp
+ )
+
+set(llkdu_HEADER_FILES
+ CMakeLists.txt
+
+ llimagej2ckdu.h
+ llkdumem.h
+ )
+
+set_source_files_properties(${llkdu_HEADER_FILES}
+ PROPERTIES HEADER_FILE_ONLY TRUE)
+
+list(APPEND llkdu_SOURCE_FILES ${llkdu_HEADER_FILES})
+
+if (USE_KDU)
+ add_library (${LLKDU_LIBRARIES} ${llkdu_SOURCE_FILES})
+
+endif (USE_KDU)
diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp
new file mode 100644
index 0000000000..1a286d1406
--- /dev/null
+++ b/indra/llkdu/llimagej2ckdu.cpp
@@ -0,0 +1,1084 @@
+ /**
+ * @file llimagej2ckdu.cpp
+ * @brief This is an implementation of JPEG2000 encode/decode using Kakadu
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "llimagej2ckdu.h"
+
+#include "lltimer.h"
+#include "llpointer.h"
+#include "llkdumem.h"
+
+
+class kdc_flow_control {
+
+public: // Member functions
+ kdc_flow_control(kdu_image_in_base *img_in, kdu_codestream codestream);
+ ~kdc_flow_control();
+ bool advance_components();
+ void process_components();
+
+private: // Data
+
+ struct kdc_component_flow_control {
+ public: // Data
+ kdu_image_in_base *reader;
+ int vert_subsampling;
+ int ratio_counter; /* Initialized to 0, decremented by `count_delta';
+ when < 0, a new line must be processed, after
+ which it is incremented by `vert_subsampling'. */
+ int initial_lines;
+ int remaining_lines;
+ kdu_line_buf *line;
+ };
+
+ kdu_codestream codestream;
+ kdu_dims valid_tile_indices;
+ kdu_coords tile_idx;
+ kdu_tile tile;
+ int num_components;
+ kdc_component_flow_control *components;
+ int count_delta; // Holds the minimum of the `vert_subsampling' fields
+ kdu_multi_analysis engine;
+ kdu_long max_buffer_memory;
+};
+
+//
+// Kakadu specific implementation
+//
+void set_default_colour_weights(kdu_params *siz);
+
+const char* engineInfoLLImageJ2CKDU()
+{
+ return "KDU v6.4.1";
+}
+
+LLImageJ2CKDU* createLLImageJ2CKDU()
+{
+ return new LLImageJ2CKDU();
+}
+
+void destroyLLImageJ2CKDU(LLImageJ2CKDU* kdu)
+{
+ delete kdu;
+ kdu = NULL;
+}
+
+LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl()
+{
+ return new LLImageJ2CKDU();
+}
+
+void fallbackDestroyLLImageJ2CImpl(LLImageJ2CImpl* impl)
+{
+ delete impl;
+ impl = NULL;
+}
+
+const char* fallbackEngineInfoLLImageJ2CImpl()
+{
+ return engineInfoLLImageJ2CKDU();
+}
+
+class LLKDUDecodeState
+{
+public:
+
+ S32 mNumComponents;
+ BOOL mUseYCC;
+ kdu_dims mDims;
+ kdu_sample_allocator mAllocator;
+ kdu_tile_comp mComps[4];
+ kdu_line_buf mLines[4];
+ kdu_pull_ifc mEngines[4];
+ bool mReversible[4]; // Some components may be reversible and others not.
+ int mBitDepths[4]; // Original bit-depth may be quite different from 8.
+
+ kdu_tile mTile;
+ kdu_byte *mBuf;
+ S32 mRowGap;
+
+ LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap);
+ ~LLKDUDecodeState();
+ BOOL processTileDecode(F32 decode_time, BOOL limit_time = TRUE);
+
+public:
+ int *AssignLayerBytes(siz_params *siz, int &num_specs);
+
+ void setupCodeStream(BOOL keep_codestream, LLImageJ2CKDU::ECodeStreamMode mode);
+ BOOL initDecode(LLImageRaw &raw_image, F32 decode_time, LLImageJ2CKDU::ECodeStreamMode mode, S32 first_channel, S32 max_channel_count );
+};
+
+void ll_kdu_error( void )
+{
+ // *FIX: This exception is bad, bad, bad. It gets thrown from a
+ // destructor which can lead to immediate program termination!
+ throw "ll_kdu_error() throwing an exception";
+}
+
+// Stuff for new kdu error handling
+class LLKDUMessageWarning : public kdu_message
+{
+public:
+ /*virtual*/ void put_text(const char *s);
+ /*virtual*/ void put_text(const kdu_uint16 *s);
+
+ static LLKDUMessageWarning sDefaultMessage;
+};
+
+class LLKDUMessageError : public kdu_message
+{
+public:
+ /*virtual*/ void put_text(const char *s);
+ /*virtual*/ void put_text(const kdu_uint16 *s);
+ /*virtual*/ void flush(bool end_of_message=false);
+ static LLKDUMessageError sDefaultMessage;
+};
+
+void LLKDUMessageWarning::put_text(const char *s)
+{
+ llinfos << "KDU Warning: " << s << llendl;
+}
+
+void LLKDUMessageWarning::put_text(const kdu_uint16 *s)
+{
+ llinfos << "KDU Warning: " << s << llendl;
+}
+
+void LLKDUMessageError::put_text(const char *s)
+{
+ llinfos << "KDU Error: " << s << llendl;
+}
+
+void LLKDUMessageError::put_text(const kdu_uint16 *s)
+{
+ llinfos << "KDU Error: " << s << llendl;
+}
+
+void LLKDUMessageError::flush(bool end_of_message)
+{
+ if( end_of_message )
+ {
+ throw "KDU throwing an exception";
+ }
+}
+
+LLKDUMessageWarning LLKDUMessageWarning::sDefaultMessage;
+LLKDUMessageError LLKDUMessageError::sDefaultMessage;
+static bool kdu_message_initialized = false;
+
+LLImageJ2CKDU::LLImageJ2CKDU() : LLImageJ2CImpl(),
+mInputp(NULL),
+mCodeStreamp(NULL),
+mTPosp(NULL),
+mTileIndicesp(NULL),
+mRawImagep(NULL),
+mDecodeState(NULL)
+{
+}
+
+LLImageJ2CKDU::~LLImageJ2CKDU()
+{
+ cleanupCodeStream(); // in case destroyed before decode completed
+}
+
+// Stuff for new simple decode
+void transfer_bytes(kdu_byte *dest, kdu_line_buf &src, int gap, int precision);
+
+void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECodeStreamMode mode)
+{
+ S32 data_size = base.getDataSize();
+ S32 max_bytes = base.getMaxBytes() ? base.getMaxBytes() : data_size;
+
+ //
+ // Initialization
+ //
+ if (!kdu_message_initialized)
+ {
+ kdu_message_initialized = true;
+ kdu_customize_errors(&LLKDUMessageError::sDefaultMessage);
+ kdu_customize_warnings(&LLKDUMessageWarning::sDefaultMessage);
+ }
+
+ if (mCodeStreamp)
+ {
+ mCodeStreamp->destroy();
+ delete mCodeStreamp;
+ mCodeStreamp = NULL;
+ }
+
+ if (!mInputp)
+ {
+ llassert(base.getData());
+ // The compressed data has been loaded
+ // Setup the source for the codestrea
+ mInputp = new LLKDUMemSource(base.getData(), data_size);
+ }
+
+ llassert(mInputp);
+ mInputp->reset();
+ mCodeStreamp = new kdu_codestream;
+
+ mCodeStreamp->create(mInputp);
+
+ // Set the maximum number of bytes to use from the codestream
+ mCodeStreamp->set_max_bytes(max_bytes);
+
+ // If you want to flip or rotate the image for some reason, change
+ // the resolution, or identify a restricted region of interest, this is
+ // the place to do it. You may use "kdu_codestream::change_appearance"
+ // and "kdu_codestream::apply_input_restrictions" for this purpose.
+ // If you wish to truncate the code-stream prior to decompression, you
+ // may use "kdu_codestream::set_max_bytes".
+ // If you wish to retain all compressed data so that the material
+ // can be decompressed multiple times, possibly with different appearance
+ // parameters, you should call "kdu_codestream::set_persistent" here.
+ // There are a variety of other features which must be enabled at
+ // this point if you want to take advantage of them. See the
+ // descriptions appearing with the "kdu_codestream" interface functions
+ // in "kdu_compressed.h" for an itemized account of these capabilities.
+
+ switch( mode )
+ {
+ case MODE_FAST:
+ mCodeStreamp->set_fast();
+ break;
+ case MODE_RESILIENT:
+ mCodeStreamp->set_resilient();
+ break;
+ case MODE_FUSSY:
+ mCodeStreamp->set_fussy();
+ break;
+ default:
+ llassert(0);
+ mCodeStreamp->set_fast();
+ }
+
+ kdu_dims dims;
+ mCodeStreamp->get_dims(0,dims);
+
+ S32 components = mCodeStreamp->get_num_components();
+
+ if (components >= 3)
+ { // Check that components have consistent dimensions (for PPM file)
+ kdu_dims dims1; mCodeStreamp->get_dims(1,dims1);
+ kdu_dims dims2; mCodeStreamp->get_dims(2,dims2);
+ if ((dims1 != dims) || (dims2 != dims))
+ {
+ llerrs << "Components don't have matching dimensions!" << llendl;
+ }
+ }
+
+ base.setSize(dims.size.x, dims.size.y, components);
+
+ if (!keep_codestream)
+ {
+ mCodeStreamp->destroy();
+ delete mCodeStreamp;
+ mCodeStreamp = NULL;
+ delete mInputp;
+ mInputp = NULL;
+ }
+}
+
+void LLImageJ2CKDU::cleanupCodeStream()
+{
+ delete mInputp;
+ mInputp = NULL;
+
+ delete mDecodeState;
+ mDecodeState = NULL;
+
+ if (mCodeStreamp)
+ {
+ mCodeStreamp->destroy();
+ delete mCodeStreamp;
+ mCodeStreamp = NULL;
+ }
+
+ delete mTPosp;
+ mTPosp = NULL;
+
+ delete mTileIndicesp;
+ mTileIndicesp = NULL;
+}
+
+BOOL LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count )
+{
+ base.resetLastError();
+
+ // *FIX: kdu calls our callback function if there's an error, and then bombs.
+ // To regain control, we throw an exception, and catch it here.
+ try
+ {
+ base.updateRawDiscardLevel();
+ setupCodeStream(base, TRUE, mode);
+
+ mRawImagep = &raw_image;
+ mCodeStreamp->change_appearance(false, true, false);
+ mCodeStreamp->apply_input_restrictions(first_channel,max_channel_count,base.getRawDiscardLevel(),0,NULL);
+
+ kdu_dims dims; mCodeStreamp->get_dims(0,dims);
+ S32 channels = base.getComponents() - first_channel;
+ if( channels > max_channel_count )
+ {
+ channels = max_channel_count;
+ }
+ raw_image.resize(dims.size.x, dims.size.y, channels);
+
+ // llinfos << "Resizing to " << dims.size.x << ":" << dims.size.y << llendl;
+ if (!mTileIndicesp)
+ {
+ mTileIndicesp = new kdu_dims;
+ }
+ mCodeStreamp->get_valid_tiles(*mTileIndicesp);
+ if (!mTPosp)
+ {
+ mTPosp = new kdu_coords;
+ mTPosp->y = 0;
+ mTPosp->x = 0;
+ }
+ }
+ catch (const char* msg)
+ {
+ base.setLastError(ll_safe_string(msg));
+ return FALSE;
+ }
+ catch (...)
+ {
+ base.setLastError("Unknown J2C error");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+// Returns TRUE to mean done, whether successful or not.
+BOOL LLImageJ2CKDU::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count)
+{
+ ECodeStreamMode mode = MODE_FAST;
+
+ LLTimer decode_timer;
+
+ if (!mCodeStreamp)
+ {
+ if (!initDecode(base, raw_image, decode_time, mode, first_channel, max_channel_count))
+ {
+ // Initializing the J2C decode failed, bail out.
+ cleanupCodeStream();
+ return TRUE; // done
+ }
+ }
+
+ // These can probably be grabbed from what's saved in the class.
+ kdu_dims dims;
+ mCodeStreamp->get_dims(0,dims);
+
+ // Now we are ready to walk through the tiles processing them one-by-one.
+ kdu_byte *buffer = raw_image.getData();
+
+ while (mTPosp->y < mTileIndicesp->size.y)
+ {
+ while (mTPosp->x < mTileIndicesp->size.x)
+ {
+ try
+ {
+ if (!mDecodeState)
+ {
+ kdu_tile tile = mCodeStreamp->open_tile(*(mTPosp)+mTileIndicesp->pos);
+
+ // Find the region of the buffer occupied by this
+ // tile. Note that we have no control over
+ // sub-sampling factors which might have been used
+ // during compression and so it can happen that tiles
+ // (at the image component level) actually have
+ // different dimensions. For this reason, we cannot
+ // figure out the buffer region occupied by a tile
+ // directly from the tile indices. Instead, we query
+ // the highest resolution of the first tile-component
+ // concerning its location and size on the canvas --
+ // the `dims' object already holds the location and
+ // size of the entire image component on the same
+ // canvas coordinate system. Comparing the two tells
+ // us where the current tile is in the buffer.
+ S32 channels = base.getComponents() - first_channel;
+ if( channels > max_channel_count )
+ {
+ channels = max_channel_count;
+ }
+ kdu_resolution res = tile.access_component(0).access_resolution();
+ kdu_dims tile_dims; res.get_dims(tile_dims);
+ kdu_coords offset = tile_dims.pos - dims.pos;
+ int row_gap = channels*dims.size.x; // inter-row separation
+ kdu_byte *buf = buffer + offset.y*row_gap + offset.x*channels;
+ mDecodeState = new LLKDUDecodeState(tile, buf, row_gap);
+ }
+ // Do the actual processing
+ F32 remaining_time = decode_time - decode_timer.getElapsedTimeF32();
+ // This is where we do the actual decode. If we run out of time, return false.
+ if (mDecodeState->processTileDecode(remaining_time, (decode_time > 0.0f)))
+ {
+ delete mDecodeState;
+ mDecodeState = NULL;
+ }
+ else
+ {
+ // Not finished decoding yet.
+ // setLastError("Ran out of time while decoding");
+ return FALSE;
+ }
+ }
+ catch( const char* msg )
+ {
+ base.setLastError(ll_safe_string(msg));
+ base.decodeFailed();
+ cleanupCodeStream();
+ return TRUE; // done
+ }
+ catch( ... )
+ {
+ base.setLastError( "Unknown J2C error" );
+ base.decodeFailed();
+ cleanupCodeStream();
+ return TRUE; // done
+ }
+
+
+ mTPosp->x++;
+ }
+ mTPosp->y++;
+ mTPosp->x = 0;
+ }
+
+ cleanupCodeStream();
+
+ return TRUE;
+}
+
+
+BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, BOOL reversible)
+{
+ // Collect simple arguments.
+ bool transpose, vflip, hflip;
+ bool allow_rate_prediction, mem, quiet, no_weights;
+ int cpu_iterations;
+ std::ostream *record_stream;
+
+ transpose = false;
+ record_stream = NULL;
+ allow_rate_prediction = true;
+ no_weights = false;
+ cpu_iterations = -1;
+ mem = false;
+ quiet = false;
+ vflip = true;
+ hflip = false;
+
+ try
+ {
+ // Set up input image files.
+ siz_params siz;
+
+ // Should set rate someplace here.
+ LLKDUMemIn mem_in(raw_image.getData(),
+ raw_image.getDataSize(),
+ raw_image.getWidth(),
+ raw_image.getHeight(),
+ raw_image.getComponents(),
+ &siz);
+
+ base.setSize(raw_image.getWidth(), raw_image.getHeight(), raw_image.getComponents());
+
+ int num_components = raw_image.getComponents();
+
+ siz.set(Scomponents,0,0,num_components);
+ siz.set(Sdims,0,0,base.getHeight()); // Height of first image component
+ siz.set(Sdims,0,1,base.getWidth()); // Width of first image component
+ siz.set(Sprecision,0,0,8); // Image samples have original bit-depth of 8
+ siz.set(Ssigned,0,0,false); // Image samples are originally unsigned
+
+ kdu_params *siz_ref = &siz; siz_ref->finalize();
+ siz_params transformed_siz; // Use this one to construct code-strea
+ transformed_siz.copy_from(&siz,-1,-1,-1,0,transpose,false,false);
+
+ // Construct the `kdu_codestream' object and parse all remaining arguments.
+
+ U32 max_output_size = base.getWidth()*base.getHeight()*base.getComponents();
+ if (max_output_size < 1000)
+ {
+ max_output_size = 1000;
+ }
+ U8 *output_buffer = new U8[max_output_size];
+
+ U32 output_size = max_output_size; // gets modified
+ LLKDUMemTarget output(output_buffer, output_size, base.getWidth()*base.getHeight()*base.getComponents());
+ if (output_size > max_output_size)
+ {
+ llerrs << llformat("LLImageJ2C::encode output_size(%d) > max_output_size(%d)",
+ output_size,max_output_size) << llendl;
+ }
+
+ kdu_codestream codestream;
+ codestream.create(&transformed_siz,&output);
+
+ if (comment_text)
+ {
+ // Set the comments for the codestream
+ kdu_codestream_comment comment = codestream.add_comment();
+ comment.put_text(comment_text);
+ }
+
+ // Set codestream options
+ int num_layer_specs = 0;
+
+ kdu_long layer_bytes[64];
+ U32 max_bytes = 0;
+
+ if ((num_components >= 3) && !no_weights)
+ {
+ set_default_colour_weights(codestream.access_siz());
+ }
+
+ if (reversible)
+ {
+ // If we're doing reversible, assume we're not using quality layers.
+ // Yes, I know this is incorrect!
+ codestream.access_siz()->parse_string("Creversible=yes");
+ codestream.access_siz()->parse_string("Clayers=1");
+ num_layer_specs = 1;
+ layer_bytes[0] = 0;
+ }
+ else
+ {
+ // Rate is the argument passed into the LLImageJ2C which
+ // specifies the target compression rate. The default is 8:1.
+ // Possibly if max_bytes < 500, we should just use the default setting?
+ if (base.mRate != 0.f)
+ {
+ max_bytes = (U32)(base.mRate*base.getWidth()*base.getHeight()*base.getComponents());
+ }
+ else
+ {
+ max_bytes = (U32)(base.getWidth()*base.getHeight()*base.getComponents()*0.125);
+ }
+
+ const U32 min_bytes = FIRST_PACKET_SIZE;
+ if (max_bytes > min_bytes)
+ {
+ U32 i;
+ // This code is where we specify the target number of bytes for
+ // each layer. Not sure if we should do this for small images
+ // or not. The goal is to have this roughly align with
+ // different quality levels that we decode at.
+ for (i = min_bytes; i < max_bytes; i*=4)
+ {
+ if (i == min_bytes * 4)
+ {
+ i = 2000;
+ }
+ layer_bytes[num_layer_specs] = i;
+ num_layer_specs++;
+ }
+ layer_bytes[num_layer_specs] = max_bytes;
+ num_layer_specs++;
+
+ std::string layer_string = llformat("Clayers=%d",num_layer_specs);
+ codestream.access_siz()->parse_string(layer_string.c_str());
+ }
+ else
+ {
+ layer_bytes[0] = min_bytes;
+ num_layer_specs = 1;
+ std::string layer_string = llformat("Clayers=%d",num_layer_specs);
+ codestream.access_siz()->parse_string(layer_string.c_str());
+ }
+ }
+ codestream.access_siz()->finalize_all();
+ if (cpu_iterations >= 0)
+ {
+ codestream.collect_timing_stats(cpu_iterations);
+ }
+ codestream.change_appearance(transpose,vflip,hflip);
+
+ // Now we are ready for sample data processing.
+ kdc_flow_control *tile = new kdc_flow_control(&mem_in,codestream);
+ bool done = false;
+ while (!done)
+ {
+ // Process line by line
+ done = true;
+ if (tile->advance_components())
+ {
+ done = false;
+ tile->process_components();
+ }
+ }
+
+ // Produce the compressed output
+ codestream.flush(layer_bytes,num_layer_specs);
+
+ // Cleanup
+ delete tile;
+
+ codestream.destroy();
+ if (record_stream != NULL)
+ {
+ delete record_stream;
+ }
+
+ // Now that we're done encoding, create the new data buffer for the compressed
+ // image and stick it there.
+
+ base.copyData(output_buffer, output_size);
+ base.updateData(); // set width, height
+ delete[] output_buffer;
+ }
+ catch(const char* msg)
+ {
+ base.setLastError(ll_safe_string(msg));
+ return FALSE;
+ }
+ catch( ... )
+ {
+ base.setLastError( "Unknown J2C error" );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL LLImageJ2CKDU::getMetadata(LLImageJ2C &base)
+{
+ // *FIX: kdu calls our callback function if there's an error, and
+ // then bombs. To regain control, we throw an exception, and
+ // catch it here.
+ try
+ {
+ setupCodeStream(base, FALSE, MODE_FAST);
+ return TRUE;
+ }
+ catch( const char* msg )
+ {
+ base.setLastError(ll_safe_string(msg));
+ return FALSE;
+ }
+ catch( ... )
+ {
+ base.setLastError( "Unknown J2C error" );
+ return FALSE;
+ }
+}
+
+void set_default_colour_weights(kdu_params *siz)
+{
+ kdu_params *cod = siz->access_cluster(COD_params);
+ assert(cod != NULL);
+
+ bool can_use_ycc = true;
+ bool rev0=false;
+ int depth0=0, sub_x0=1, sub_y0=1;
+ for (int c=0; c < 3; c++)
+ {
+ int depth=0; siz->get(Sprecision,c,0,depth);
+ int sub_y=1; siz->get(Ssampling,c,0,sub_y);
+ int sub_x=1; siz->get(Ssampling,c,1,sub_x);
+ kdu_params *coc = cod->access_relation(-1,c);
+ bool rev=false; coc->get(Creversible,0,0,rev);
+ if (c == 0)
+ { rev0=rev; depth0=depth; sub_x0=sub_x; sub_y0=sub_y; }
+ else if ((rev != rev0) || (depth != depth0) ||
+ (sub_x != sub_x0) || (sub_y != sub_y0))
+ can_use_ycc = false;
+ }
+ if (!can_use_ycc)
+ return;
+
+ bool use_ycc;
+ if (!cod->get(Cycc,0,0,use_ycc))
+ cod->set(Cycc,0,0,use_ycc=true);
+ if (!use_ycc)
+ return;
+ float weight;
+ if (cod->get(Clev_weights,0,0,weight) ||
+ cod->get(Cband_weights,0,0,weight))
+ return; // Weights already specified explicitly.
+
+ /* These example weights are adapted from numbers generated by Marcus Nadenau
+ at EPFL, for a viewing distance of 15 cm and a display resolution of
+ 300 DPI. */
+
+ cod->parse_string("Cband_weights:C0="
+ "{0.0901},{0.2758},{0.2758},"
+ "{0.7018},{0.8378},{0.8378},{1}");
+ cod->parse_string("Cband_weights:C1="
+ "{0.0263},{0.0863},{0.0863},"
+ "{0.1362},{0.2564},{0.2564},"
+ "{0.3346},{0.4691},{0.4691},"
+ "{0.5444},{0.6523},{0.6523},"
+ "{0.7078},{0.7797},{0.7797},{1}");
+ cod->parse_string("Cband_weights:C2="
+ "{0.0773},{0.1835},{0.1835},"
+ "{0.2598},{0.4130},{0.4130},"
+ "{0.5040},{0.6464},{0.6464},"
+ "{0.7220},{0.8254},{0.8254},"
+ "{0.8769},{0.9424},{0.9424},{1}");
+}
+
+/******************************************************************************/
+/* transfer_bytes */
+/******************************************************************************/
+
+void transfer_bytes(kdu_byte *dest, kdu_line_buf &src, int gap, int precision)
+/* Transfers source samples from the supplied line buffer into the output
+byte buffer, spacing successive output samples apart by `gap' bytes
+(to allow for interleaving of colour components). The function performs
+all necessary level shifting, type conversion, rounding and truncation. */
+{
+ int width = src.get_width();
+ if (src.get_buf32() != NULL)
+ { // Decompressed samples have a 32-bit representation (integer or float)
+ assert(precision >= 8); // Else would have used 16 bit representation
+ kdu_sample32 *sp = src.get_buf32();
+ if (!src.is_absolute())
+ { // Transferring normalized floating point data.
+ float scale16 = (float)(1<<16);
+ kdu_int32 val;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = (kdu_int32)(sp->fval*scale16);
+ val = (val+128)>>8; // May be faster than true rounding
+ val += 128;
+ if (val & ((-1)<<8))
+ {
+ val = (val<0)?0:255;
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ else
+ { // Transferring 32-bit absolute integers.
+ kdu_int32 val;
+ kdu_int32 downshift = precision-8;
+ kdu_int32 offset = (1<<downshift)>>1;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val = (val+offset)>>downshift;
+ val += 128;
+ if (val & ((-1)<<8))
+ {
+ val = (val<0)?0:255;
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ }
+ else
+ { // Source data is 16 bits.
+ kdu_sample16 *sp = src.get_buf16();
+ if (!src.is_absolute())
+ { // Transferring 16-bit fixed point quantities
+ kdu_int16 val;
+
+ if (precision >= 8)
+ { // Can essentially ignore the bit-depth.
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val += (1<<(KDU_FIX_POINT-8))>>1;
+ val >>= (KDU_FIX_POINT-8);
+ val += 128;
+ if (val & ((-1)<<8))
+ {
+ val = (val<0)?0:255;
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ else
+ { // Need to force zeros into one or more least significant bits.
+ kdu_int16 downshift = KDU_FIX_POINT-precision;
+ kdu_int16 upshift = 8-precision;
+ kdu_int16 offset = 1<<(downshift-1);
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val = (val+offset)>>downshift;
+ val <<= upshift;
+ val += 128;
+ if (val & ((-1)<<8))
+ {
+ val = (val<0)?0:(256-(1<<upshift));
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ }
+ else
+ { // Transferring 16-bit absolute integers.
+ kdu_int16 val;
+
+ if (precision >= 8)
+ {
+ kdu_int16 downshift = precision-8;
+ kdu_int16 offset = (1<<downshift)>>1;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val = (val+offset)>>downshift;
+ val += 128;
+ if (val & ((-1)<<8))
+ {
+ val = (val<0)?0:255;
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ else
+ {
+ kdu_int16 upshift = 8-precision;
+
+ for (; width > 0; width--, sp++, dest+=gap)
+ {
+ val = sp->ival;
+ val <<= upshift;
+ val += 128;
+ if (val & ((-1)<<8))
+ {
+ val = (val<0)?0:(256-(1<<upshift));
+ }
+ *dest = (kdu_byte) val;
+ }
+ }
+ }
+ }
+}
+
+LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap)
+{
+ S32 c;
+
+ mTile = tile;
+ mBuf = buf;
+ mRowGap = row_gap;
+
+ mNumComponents = tile.get_num_components();
+
+ llassert(mNumComponents<=4);
+ mUseYCC = tile.get_ycc();
+
+ for (c=0; c<4; ++c)
+ {
+ mReversible[c] = false;
+ mBitDepths[c] = 0;
+ }
+
+ // Open tile-components and create processing engines and resources
+ for (c=0; c < mNumComponents; c++)
+ {
+ mComps[c] = mTile.access_component(c);
+ mReversible[c] = mComps[c].get_reversible();
+ mBitDepths[c] = mComps[c].get_bit_depth();
+ kdu_resolution res = mComps[c].access_resolution(); // Get top resolution
+ kdu_dims comp_dims; res.get_dims(comp_dims);
+ if (c == 0)
+ {
+ mDims = comp_dims;
+ }
+ else
+ {
+ llassert(mDims == comp_dims); // Safety check; the caller has ensured this
+ }
+ bool use_shorts = (mComps[c].get_bit_depth(true) <= 16);
+ mLines[c].pre_create(&mAllocator,mDims.size.x,mReversible[c],use_shorts);
+ if (res.which() == 0) // No DWT levels used
+ {
+ mEngines[c] = kdu_decoder(res.access_subband(LL_BAND),&mAllocator,use_shorts);
+ }
+ else
+ {
+ mEngines[c] = kdu_synthesis(res,&mAllocator,use_shorts);
+ }
+ }
+ mAllocator.finalize(); // Actually creates buffering resources
+ for (c=0; c < mNumComponents; c++)
+ {
+ mLines[c].create(); // Grabs resources from the allocator.
+ }
+}
+
+LLKDUDecodeState::~LLKDUDecodeState()
+{
+ S32 c;
+ // Cleanup
+ for (c=0; c < mNumComponents; c++)
+ {
+ mEngines[c].destroy(); // engines are interfaces; no default destructors
+ }
+
+ mTile.close();
+}
+
+BOOL LLKDUDecodeState::processTileDecode(F32 decode_time, BOOL limit_time)
+/* Decompresses a tile, writing the data into the supplied byte buffer.
+The buffer contains interleaved image components, if there are any.
+Although you may think of the buffer as belonging entirely to this tile,
+the `buf' pointer may actually point into a larger buffer representing
+multiple tiles. For this reason, `row_gap' is needed to identify the
+separation between consecutive rows in the real buffer. */
+{
+ S32 c;
+ // Now walk through the lines of the buffer, recovering them from the
+ // relevant tile-component processing engines.
+
+ LLTimer decode_timer;
+ while (mDims.size.y--)
+ {
+ for (c=0; c < mNumComponents; c++)
+ {
+ mEngines[c].pull(mLines[c],true);
+ }
+ if ((mNumComponents >= 3) && mUseYCC)
+ {
+ kdu_convert_ycc_to_rgb(mLines[0],mLines[1],mLines[2]);
+ }
+ for (c=0; c < mNumComponents; c++)
+ {
+ transfer_bytes(mBuf+c,mLines[c],mNumComponents,mBitDepths[c]);
+ }
+ mBuf += mRowGap;
+ if (mDims.size.y % 10)
+ {
+ if (limit_time && decode_timer.getElapsedTimeF32() > decode_time)
+ {
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+// kdc_flow_control
+
+kdc_flow_control::kdc_flow_control (kdu_image_in_base *img_in, kdu_codestream codestream)
+{
+ int n;
+
+ this->codestream = codestream;
+ codestream.get_valid_tiles(valid_tile_indices);
+ tile_idx = valid_tile_indices.pos;
+ tile = codestream.open_tile(tile_idx,NULL);
+
+ // Set up the individual components
+ num_components = codestream.get_num_components(true);
+ components = new kdc_component_flow_control[num_components];
+ count_delta = 0;
+ kdc_component_flow_control *comp = components;
+ for (n = 0; n < num_components; n++, comp++)
+ {
+ comp->line = NULL;
+ comp->reader = img_in;
+ kdu_coords subsampling;
+ codestream.get_subsampling(n,subsampling,true);
+ kdu_dims dims;
+ codestream.get_tile_dims(tile_idx,n,dims,true);
+ comp->vert_subsampling = subsampling.y;
+ if ((n == 0) || (comp->vert_subsampling < count_delta))
+ {
+ count_delta = comp->vert_subsampling;
+ }
+ comp->ratio_counter = 0;
+ comp->remaining_lines = comp->initial_lines = dims.size.y;
+ }
+ assert(num_components > 0);
+
+ tile.set_components_of_interest(num_components);
+ max_buffer_memory = engine.create(codestream,tile,false,NULL,false,1,NULL,NULL,false);
+}
+
+kdc_flow_control::~kdc_flow_control()
+{
+ if (components != NULL)
+ delete[] components;
+ if (engine.exists())
+ engine.destroy();
+}
+
+bool kdc_flow_control::advance_components()
+{
+ bool found_line = false;
+ while (!found_line)
+ {
+ bool all_done = true;
+ kdc_component_flow_control *comp = components;
+ for (int n = 0; n < num_components; n++, comp++)
+ {
+ assert(comp->ratio_counter >= 0);
+ if (comp->remaining_lines > 0)
+ {
+ all_done = false;
+ comp->ratio_counter -= count_delta;
+ if (comp->ratio_counter < 0)
+ {
+ found_line = true;
+ comp->line = engine.exchange_line(n,NULL,NULL);
+ assert(comp->line != NULL);
+ if (comp->line->get_width())
+ {
+ comp->reader->get(n,*(comp->line),0);
+ }
+ }
+ }
+ }
+ if (all_done)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+void kdc_flow_control::process_components()
+{
+ kdc_component_flow_control *comp = components;
+ for (int n = 0; n < num_components; n++, comp++)
+ {
+ if (comp->ratio_counter < 0)
+ {
+ comp->ratio_counter += comp->vert_subsampling;
+ assert(comp->ratio_counter >= 0);
+ assert(comp->remaining_lines > 0);
+ comp->remaining_lines--;
+ assert(comp->line != NULL);
+ engine.exchange_line(n,comp->line,NULL);
+ comp->line = NULL;
+ }
+ }
+}
diff --git a/indra/llkdu/llimagej2ckdu.h b/indra/llkdu/llimagej2ckdu.h
new file mode 100644
index 0000000000..03f289f8b1
--- /dev/null
+++ b/indra/llkdu/llimagej2ckdu.h
@@ -0,0 +1,91 @@
+/**
+ * @file llimagej2ckdu.h
+ * @brief This is an implementation of JPEG2000 encode/decode using Kakadu
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLIMAGEJ2CKDU_H
+#define LL_LLIMAGEJ2CKDU_H
+
+#include "llimagej2c.h"
+
+//
+// KDU core header files
+//
+#include "kdu_elementary.h"
+#include "kdu_messaging.h"
+#include "kdu_params.h"
+#include "kdu_compressed.h"
+#include "kdu_sample_processing.h"
+
+class LLKDUDecodeState;
+class LLKDUMemSource;
+
+class LLImageJ2CKDU : public LLImageJ2CImpl
+{
+public:
+ enum ECodeStreamMode
+ {
+ MODE_FAST = 0,
+ MODE_RESILIENT = 1,
+ MODE_FUSSY = 2
+ };
+
+public:
+ LLImageJ2CKDU();
+ virtual ~LLImageJ2CKDU();
+
+protected:
+ /*virtual*/ BOOL getMetadata(LLImageJ2C &base);
+ /*virtual*/ BOOL decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count);
+ /*virtual*/ BOOL encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0,
+ BOOL reversible=FALSE);
+
+ void setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECodeStreamMode mode);
+ void cleanupCodeStream();
+ BOOL initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count );
+
+ // Encode variable
+ LLKDUMemSource *mInputp;
+ kdu_codestream *mCodeStreamp;
+ kdu_coords *mTPosp; // tile position
+ kdu_dims *mTileIndicesp;
+
+ // Temporary variables for in-progress decodes...
+ LLImageRaw *mRawImagep;
+ LLKDUDecodeState *mDecodeState;
+};
+
+#if LL_WINDOWS
+# define LLSYMEXPORT __declspec(dllexport)
+#elif LL_LINUX
+# define LLSYMEXPORT __attribute__ ((visibility("default")))
+#else
+# define LLSYMEXPORT
+#endif
+
+extern "C" LLSYMEXPORT const char* engineInfoLLImageJ2CKDU();
+extern "C" LLSYMEXPORT LLImageJ2CKDU* createLLImageJ2CKDU();
+extern "C" LLSYMEXPORT void destroyLLImageJ2CKDU(LLImageJ2CKDU* kdu);
+
+#endif
diff --git a/indra/llkdu/llkdumem.cpp b/indra/llkdu/llkdumem.cpp
new file mode 100644
index 0000000000..1f549cbbe0
--- /dev/null
+++ b/indra/llkdu/llkdumem.cpp
@@ -0,0 +1,196 @@
+ /**
+ * @file llkdumem.cpp
+ * @brief Helper class for kdu memory management
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "llkdumem.h"
+#include "llerror.h"
+
+#if defined(LL_WINDOWS)
+# pragma warning(disable: 4702) // unreachable code
+#endif
+
+LLKDUMemIn::LLKDUMemIn(const U8 *data,
+ const U32 size,
+ const U16 width,
+ const U16 height,
+ const U8 in_num_components,
+ siz_params *siz)
+{
+ U8 n;
+
+ first_comp_idx = 0;
+ rows = height;
+ cols = width;
+ num_components = in_num_components;
+ alignment_bytes = 0;
+
+ for (n=0; n<3; ++n)
+ {
+ precision[n] = 0;
+ }
+
+ for (n=0; n < num_components; ++n)
+ {
+ siz->set(Sdims,n,0,rows);
+ siz->set(Sdims,n,1,cols);
+ siz->set(Ssigned,n,0,false);
+ siz->set(Sprecision,n,0,8);
+ }
+ incomplete_lines = NULL;
+ free_lines = NULL;
+ num_unread_rows = rows;
+
+ mData = data;
+ mDataSize = size;
+ mCurPos = 0;
+}
+
+LLKDUMemIn::~LLKDUMemIn()
+{
+ if ((num_unread_rows > 0) || (incomplete_lines != NULL))
+ {
+ kdu_warning w;
+ w << "Not all rows of image components "
+ << first_comp_idx << " through "
+ << first_comp_idx+num_components-1
+ << " were consumed!";
+ }
+ image_line_buf *tmp;
+ while ((tmp=incomplete_lines) != NULL)
+ {
+ incomplete_lines = tmp->next;
+ delete tmp;
+ }
+ while ((tmp=free_lines) != NULL)
+ {
+ free_lines = tmp->next;
+ delete tmp;
+ }
+}
+
+
+bool LLKDUMemIn::get(int comp_idx, kdu_line_buf &line, int x_tnum)
+{
+ int idx = comp_idx - this->first_comp_idx;
+ assert((idx >= 0) && (idx < num_components));
+ x_tnum = x_tnum*num_components+idx;
+ image_line_buf *scan, *prev=NULL;
+ for (scan=incomplete_lines; scan != NULL; prev=scan, scan=scan->next)
+ {
+ assert(scan->next_x_tnum >= x_tnum);
+ if (scan->next_x_tnum == x_tnum)
+ {
+ break;
+ }
+ }
+ if (scan == NULL)
+ { // Need to read a new image line.
+ assert(x_tnum == 0); // Must consume in very specific order.
+ if (num_unread_rows == 0)
+ {
+ return false;
+ }
+ if ((scan = free_lines) == NULL)
+ {
+ scan = new image_line_buf(cols+3,num_components);
+ }
+ free_lines = scan->next;
+ if (prev == NULL)
+ {
+ incomplete_lines = scan;
+ }
+ else
+ {
+ prev->next = scan;
+ }
+
+ // Copy from image buffer into scan.
+ memcpy(scan->buf, mData+mCurPos, cols*num_components);
+ mCurPos += cols*num_components;
+
+ num_unread_rows--;
+ scan->accessed_samples = 0;
+ scan->next_x_tnum = 0;
+ }
+
+ assert((cols-scan->accessed_samples) >= line.get_width());
+
+ int comp_offset = idx;
+ kdu_byte *sp = scan->buf+num_components*scan->accessed_samples + comp_offset;
+ int n=line.get_width();
+
+ if (line.get_buf32() != NULL)
+ {
+ kdu_sample32 *dp = line.get_buf32();
+ if (line.is_absolute())
+ { // 32-bit absolute integers
+ for (; n > 0; n--, sp+=num_components, dp++)
+ {
+ dp->ival = ((kdu_int32)(*sp)) - 128;
+ }
+ }
+ else
+ { // true 32-bit floats
+ for (; n > 0; n--, sp+=num_components, dp++)
+ {
+ dp->fval = (((float)(*sp)) / 256.0F) - 0.5F;
+ }
+ }
+ }
+ else
+ {
+ kdu_sample16 *dp = line.get_buf16();
+ if (line.is_absolute())
+ { // 16-bit absolute integers
+ for (; n > 0; n--, sp+=num_components, dp++)
+ {
+ dp->ival = ((kdu_int16)(*sp)) - 128;
+ }
+ }
+ else
+ { // 16-bit normalized representation.
+ for (; n > 0; n--, sp+=num_components, dp++)
+ {
+ dp->ival = (((kdu_int16)(*sp)) - 128) << (KDU_FIX_POINT-8);
+ }
+ }
+ }
+
+ scan->next_x_tnum++;
+ if (idx == (num_components-1))
+ {
+ scan->accessed_samples += line.get_width();
+ }
+ if (scan->accessed_samples == cols)
+ { // Send empty line to free list.
+ assert(scan == incomplete_lines);
+ incomplete_lines = scan->next;
+ scan->next = free_lines;
+ free_lines = scan;
+ }
+
+ return true;
+}
diff --git a/indra/llkdu/llkdumem.h b/indra/llkdu/llkdumem.h
new file mode 100644
index 0000000000..7064de4408
--- /dev/null
+++ b/indra/llkdu/llkdumem.h
@@ -0,0 +1,145 @@
+/**
+ * @file llkdumem.h
+ * @brief Helper class for kdu memory management
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLKDUMEM_H
+#define LL_LLKDUMEM_H
+
+// Support classes for reading and writing from memory buffers in KDU
+#include "kdu_image.h"
+#include "kdu_elementary.h"
+#include "kdu_messaging.h"
+#include "kdu_params.h"
+#include "kdu_compressed.h"
+#include "kdu_sample_processing.h"
+#include "image_local.h"
+#include "stdtypes.h"
+
+class LLKDUMemSource: public kdu_compressed_source
+{
+public: // Member functions
+ LLKDUMemSource(U8 *input_buffer, U32 size)
+ {
+ mData = input_buffer;
+ mSize = size;
+ mCurPos = 0;
+ }
+
+ ~LLKDUMemSource()
+ {
+ }
+
+ int read(kdu_byte *buf, int num_bytes)
+ {
+ U32 num_out;
+ num_out = num_bytes;
+
+ if ((mSize - mCurPos) < (U32)num_bytes)
+ {
+ num_out = mSize -mCurPos;
+ }
+ memcpy(buf, mData + mCurPos, num_out);
+ mCurPos += num_out;
+ return num_out;
+ }
+
+ void reset()
+ {
+ mCurPos = 0;
+ }
+
+private: // Data
+ U8 *mData;
+ U32 mSize;
+ U32 mCurPos;
+};
+
+class LLKDUMemTarget: public kdu_compressed_target
+{
+public: // Member functions
+ LLKDUMemTarget(U8 *output_buffer, U32 &output_size, const U32 buffer_size)
+ {
+ mData = output_buffer;
+ mSize = buffer_size;
+ mCurPos = 0;
+ mOutputSize = &output_size;
+ }
+
+ ~LLKDUMemTarget()
+ {
+ }
+
+ bool write(const kdu_byte *buf, int num_bytes)
+ {
+ U32 num_out;
+ num_out = num_bytes;
+
+ if ((mSize - mCurPos) < (U32)num_bytes)
+ {
+ num_out = mSize - mCurPos;
+ memcpy(mData + mCurPos, buf, num_out);
+ return false;
+ }
+ memcpy(mData + mCurPos, buf, num_out);
+ mCurPos += num_out;
+ *mOutputSize = mCurPos;
+ return true;
+ }
+
+private: // Data
+ U8 *mData;
+ U32 mSize;
+ U32 mCurPos;
+ U32 *mOutputSize;
+};
+
+class LLKDUMemIn : public kdu_image_in_base
+{
+public: // Member functions
+ LLKDUMemIn(const U8 *data,
+ const U32 size,
+ const U16 rows,
+ const U16 cols,
+ U8 in_num_components,
+ siz_params *siz);
+ ~LLKDUMemIn();
+
+ bool get(int comp_idx, kdu_line_buf &line, int x_tnum);
+
+private: // Data
+ const U8 *mData;
+ int first_comp_idx;
+ int num_components;
+ int rows, cols;
+ int alignment_bytes; // Number of 0's at end of each line.
+ int precision[3];
+ image_line_buf *incomplete_lines; // Each "sample" represents a full pixel
+ image_line_buf *free_lines;
+ int num_unread_rows;
+
+ U32 mCurPos;
+ U32 mDataSize;
+};
+#endif
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp
index b26d412e9f..27a368df3d 100644
--- a/indra/llmessage/llassetstorage.cpp
+++ b/indra/llmessage/llassetstorage.cpp
@@ -513,6 +513,10 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, LL
}
+//
+// *NOTE: Logic here is replicated in LLViewerAssetStorage::_queueDataRequest.
+// Changes here may need to be replicated in the viewer's derived class.
+//
void LLAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType atype,
LLGetAssetCallback callback,
void *user_data, BOOL duplicate,
diff --git a/indra/mac_updater/CMakeLists.txt b/indra/mac_updater/CMakeLists.txt
index 44f98e5e18..a4a6b50c6c 100644
--- a/indra/mac_updater/CMakeLists.txt
+++ b/indra/mac_updater/CMakeLists.txt
@@ -3,6 +3,7 @@
project(mac_updater)
include(00-Common)
+include(OpenSSL)
include(CURL)
include(LLCommon)
include(LLVFS)
@@ -49,6 +50,8 @@ set_target_properties(mac-updater
target_link_libraries(mac-updater
${LLVFS_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${CRYPTO_LIBRARIES}
${CURL_LIBRARIES}
${LLCOMMON_LIBRARIES}
)
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 3862ae5731..7975b181d3 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}
@@ -482,6 +483,7 @@ set(viewer_SOURCE_FILES
llvectorperfoptions.cpp
llversioninfo.cpp
llviewchildren.cpp
+ llviewerassetstats.cpp
llviewerassetstorage.cpp
llviewerassettype.cpp
llviewerattachmenu.cpp
@@ -1016,6 +1018,7 @@ set(viewer_HEADER_FILES
llvectorperfoptions.h
llversioninfo.h
llviewchildren.h
+ llviewerassetstats.h
llviewerassetstorage.h
llviewerassettype.h
llviewerattachmenu.h
@@ -1455,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.
@@ -1476,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
@@ -1662,7 +1659,6 @@ target_link_libraries(${VIEWER_BINARY_NAME}
${LLAUDIO_LIBRARIES}
${LLCHARACTER_LIBRARIES}
${LLIMAGE_LIBRARIES}
- ${LLIMAGEJ2COJ_LIBRARIES}
${LLINVENTORY_LIBRARIES}
${LLMESSAGE_LIBRARIES}
${LLPLUGIN_LIBRARIES}
@@ -1698,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
@@ -1957,6 +1964,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/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/llappviewer.cpp b/indra/newview/llappviewer.cpp
index b460885a53..5c0c5adabe 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -192,6 +192,7 @@
#include "llparcel.h"
#include "llavatariconctrl.h"
#include "llgroupiconctrl.h"
+#include "llviewerassetstats.h"
// Include for security api initialization
#include "llsecapi.h"
@@ -336,6 +337,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
@@ -655,6 +664,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 +1741,8 @@ bool LLAppViewer::cleanup()
LLWatchdog::getInstance()->cleanup();
+ LLViewerAssetStatsFF::cleanup();
+
llinfos << "Shutting down message system" << llendflush;
end_messaging_system();
@@ -1783,7 +1809,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)
@@ -3045,6 +3074,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()));
@@ -3822,6 +3854,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 +3901,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;
@@ -4766,3 +4815,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/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/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/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/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/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/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/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/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: