diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index 34d8f8b9ce969c034ef44bc6141b40a6a5b51569..187e29006635a4ac440eca37da13554267158c8b 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -52,8 +52,6 @@
 #include "llglcommonfunc.h"
 #include "llvoavatar.h"
 #include "llviewershadermgr.h"
-#include "llperfstats.h"
-
 
 S32 LLDrawPool::sNumDrawPools = 0;
 
@@ -387,22 +385,11 @@ void LLRenderPass::renderGroup(LLSpatialGroup* group, U32 type, bool texture)
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
 	LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];
 
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{}; // Perf stats 
 	for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
 	{
 		LLDrawInfo *pparams = *k;
 		if (pparams) 
         {
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-            if(pparams->mFace)
-            {
-                LLViewerObject* vobj = pparams->mFace->getViewerObject();
-                if(vobj->isAttachment())
-                {
-                    trackAttachments(vobj, false, &ratPtr);
-                }
-            }
-#endif
             pushBatch(*pparams, texture);
 		}
 	}
@@ -415,23 +402,11 @@ void LLRenderPass::renderRiggedGroup(LLSpatialGroup* group, U32 type, bool textu
     LLVOAvatar* lastAvatar = nullptr;
     U64 lastMeshId = 0;
     
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{}; // Perf stats 
     for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)
     {
         LLDrawInfo* pparams = *k;
         if (pparams) 
         {
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-            if(pparams->mFace)
-            {
-                LLViewerObject* vobj = pparams->mFace->getViewerObject();
-                if(vobj->isAttachment())
-                {
-                    trackAttachments( vobj, true ,&ratPtr);
-                }
-            }
-#endif
-
             if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash)
             {
                 uploadMatrixPalette(*pparams);
@@ -447,7 +422,6 @@ void LLRenderPass::renderRiggedGroup(LLSpatialGroup* group, U32 type, bool textu
 void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
     auto* begin = gPipeline.beginRenderMap(type);
     auto* end = gPipeline.endRenderMap(type);
     for (LLCullResult::drawinfo_iterator i = begin; i != end; )
@@ -455,16 +429,6 @@ void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures)
         LLDrawInfo* pparams = *i;
         LLCullResult::increment_iterator(i, end);
 
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-            if(pparams->mFace)
-            {
-                LLViewerObject* vobj = pparams->mFace->getViewerObject();
-                if(vobj->isAttachment())
-                {
-                    trackAttachments( vobj, false, &ratPtr);
-                }
-            }
-#endif
 		pushBatch(*pparams, texture, batch_textures);
 	}
 }
@@ -476,23 +440,11 @@ void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures
     U64 lastMeshId = 0;
     auto* begin = gPipeline.beginRenderMap(type);
     auto* end = gPipeline.endRenderMap(type);
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{}; // Perf stats 
     for (LLCullResult::drawinfo_iterator i = begin; i != end; )
     {
         LLDrawInfo* pparams = *i;
         LLCullResult::increment_iterator(i, end);
 
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-            if(pparams->mFace)
-            {
-                LLViewerObject* vobj = pparams->mFace->getViewerObject();
-                if(vobj->isAttachment())
-                {
-                    trackAttachments( vobj, true, &ratPtr);
-                }
-            }
-#endif
-
         if (pparams->mAvatar.notNull() && (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash))
         {
             uploadMatrixPalette(*pparams);
@@ -507,23 +459,12 @@ void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures
 void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
     auto* begin = gPipeline.beginRenderMap(type);
     auto* end = gPipeline.endRenderMap(type);
 	for (LLCullResult::drawinfo_iterator i = begin; i != end; )
 	{
         LLDrawInfo* pparams = *i;
         LLCullResult::increment_iterator(i, end);
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-            if((*pparams).mFace)
-            {
-                LLViewerObject* vobj = (*pparams).mFace->getViewerObject();
-                if(vobj->isAttachment())
-                {
-                    trackAttachments( vobj, false, &ratPtr);
-                }
-            }
-#endif
 		LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
 		pushBatch(*pparams, texture, batch_textures);
 	}
@@ -534,7 +475,6 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     LLVOAvatar* lastAvatar = nullptr;
     U64 lastMeshId = 0;
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
     auto* begin = gPipeline.beginRenderMap(type);
     auto* end = gPipeline.endRenderMap(type);
     for (LLCullResult::drawinfo_iterator i = begin; i != end; )
@@ -544,16 +484,6 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text
         LLCullResult::increment_iterator(i, end);
 
         llassert(pparams);
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-        if((*pparams).mFace)
-        {
-            LLViewerObject* vobj = (*pparams).mFace->getViewerObject();
-            if(vobj->isAttachment())
-            {
-                trackAttachments( vobj, true, &ratPtr);
-            }
-        }
-#endif
 
         if (LLGLSLShader::sCurBoundShaderPtr)
         {
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index da4c963a975a0bfc208f0a87c0dd2cfb8a66a0ea..404039189b08f7b33c6562a9e0a7a38456c15b1a 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -49,7 +49,6 @@
 #include "llspatialpartition.h"
 #include "llglcommonfunc.h"
 #include "llvoavatar.h"
-#include "llperfstats.h"
 
 #include "llenvironment.h"
 
@@ -349,22 +348,10 @@ void LLDrawPoolAlpha::renderAlphaHighlight(U32 mask)
             {
                 LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA+pass]; // <-- hacky + pass to use PASS_ALPHA_RIGGED on second pass 
 
-                std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{}; // Render time Stats collection
                 for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)
                 {
                     LLDrawInfo& params = **k;
 
-# if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-                    if(params.mFace)
-                    {
-                        LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject();
-                        if(vobj->isAttachment())
-                        {
-                            trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr );
-                        }
-                    }
-#endif
-
                     bool rigged = (params.mAvatar != nullptr);
                     gHighlightProgram.bind(rigged);
                     gGL.diffuseColor4f(1, 0, 0, 1);
@@ -548,17 +535,9 @@ void LLDrawPoolAlpha::renderRiggedEmissives(std::vector<LLDrawInfo*>& emissives)
     LLVOAvatar* lastAvatar = nullptr;
     U64 lastMeshId = 0;
 
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{}; // Render time Stats collection
     for (LLDrawInfo* draw : emissives)
     {
         LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("Emissives");
-# if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-        auto vobj = draw->mFace?draw->mFace->getViewerObject():nullptr;
-        if(vobj && vobj->isAttachment())
-        {
-            trackAttachments( vobj, draw->mFace->isState(LLFace::RIGGED), &ratPtr );
-        }
-#endif
 
         bool tex_setup = TexSetup(draw, false);
         if (lastAvatar != draw->mAvatar || lastMeshId != draw->mSkinInfo->mHash)
@@ -687,7 +666,6 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 
 			LLSpatialGroup::drawmap_elem_t& draw_info = rigged ? group->mDrawMap[LLRenderPass::PASS_ALPHA_RIGGED] : group->mDrawMap[LLRenderPass::PASS_ALPHA];
 
-            std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{}; // Render time Stats collection
             for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
 			{
 				LLDrawInfo& params = **k;
@@ -700,18 +678,6 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 
                 LLRenderPass::applyModelMatrix(params);
 
-# if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-                if(params.mFace)
-                {
-                    LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject();
-
-                    if(vobj->isAttachment())
-                    {
-                        trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr );
-                    }
-                }
-#endif
-
                 LLMaterial* mat = NULL;
                 LLGLTFMaterial *gltf_mat = params.mGLTFMaterial; 
 
@@ -905,8 +871,6 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 				}
 			}
 
-            ratPtr.reset(); // force the final batch to terminate to avoid double counting on the subsidiary batches for FB and Emmissives
-
             // render emissive faces into alpha channel for bloom effects
             if (!depth_only)
             {
diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp
index c398087b20373fb3f4c4d01df3afcc1d0ecc4b2a..19b23609a6a8991f936d3312427868fd06949049 100644
--- a/indra/newview/lldrawpoolavatar.cpp
+++ b/indra/newview/lldrawpoolavatar.cpp
@@ -52,7 +52,6 @@
 #include "llviewerpartsim.h"
 #include "llviewercontrol.h" // for gSavedSettings
 #include "llviewertexturelist.h"
-#include "llperfstats.h"
 
 static U32 sShaderLevel = 0;
 
@@ -370,9 +369,8 @@ void LLDrawPoolAvatar::renderShadow(S32 pass)
 	{
 		return;
 	}
-    LLPerfStats::RecordAvatarTime T(avatarp->getID(), LLPerfStats::StatType_t::RENDER_SHADOWS);
 
-	LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance();
+    LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance();
 	BOOL impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor();
     // no shadows if the shadows are causing this avatar to breach the limit.
     if (avatarp->isTooSlow() || impostor || (oa == LLVOAvatar::AOA_INVISIBLE))
@@ -741,7 +739,6 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass)
 	{
 		return;
 	}
-    LLPerfStats::RecordAvatarTime T(avatarp->getID(), LLPerfStats::StatType_t::RENDER_GEOMETRY);
 
 	if (!single_avatar && !avatarp->isFullyLoaded() )
 	{
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index a548740ec4e80e71a33e119194a0a91a8aee54eb..9ed776f49e762f783787ea26ef22ab34b6995429 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -49,7 +49,6 @@
 #include "llspatialpartition.h"
 #include "llviewershadermgr.h"
 #include "llmodel.h"
-#include "llperfstats.h"
 
 //#include "llimagebmp.h"
 //#include "../tools/imdebug/imdebug.h"
@@ -406,19 +405,10 @@ void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, bool texture =
 {					
 	LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];	
 	
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
     for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) 
 	{
 		LLDrawInfo& params = **k;
 		
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-        LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject();
-        if( vobj && vobj->isAttachment() )
-        {
-            trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr );
-        }
-#endif
-
 		applyModelMatrix(params);
 
 		params.mVertexBuffer->setBuffer();
@@ -568,25 +558,12 @@ void LLDrawPoolBump::renderDeferred(S32 pass)
         LLVOAvatar* avatar = nullptr;
         U64 skin = 0;
 
-        std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
         for (LLCullResult::drawinfo_iterator i = begin; i != end; )
         {
             LLDrawInfo& params = **i;
 
             LLCullResult::increment_iterator(i, end);
 
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-            if(params.mFace)
-            {
-                LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject();
-
-                if(vobj && vobj->isAttachment())
-                {
-                    trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr );
-                }
-            }
-#endif
-
             LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(params.mAlphaMaskCutoff);
             LLDrawPoolBump::bindBumpMap(params, bump_channel);
 
@@ -1215,23 +1192,10 @@ void LLDrawPoolBump::pushBumpBatches(U32 type)
     LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type);
     LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type);
 
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
 	for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i)	
 	{
 		LLDrawInfo& params = **i;
 
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-        if(params.mFace)
-        {
-            LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject();
-
-            if( vobj && vobj->isAttachment() )
-            {
-                trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr );
-            }
-        }
-#endif
-
 		if (LLDrawPoolBump::bindBumpMap(params))
 		{
             if (mRigged)
diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp
index e025651ccedeba2ee281abedffdb1f9404458fd1..6a7e05ac740f231d30e349c7da6c8955875acd9d 100644
--- a/indra/newview/lldrawpoolmaterials.cpp
+++ b/indra/newview/lldrawpoolmaterials.cpp
@@ -32,7 +32,6 @@
 #include "pipeline.h"
 #include "llglcommonfunc.h"
 #include "llvoavatar.h"
-#include "llperfstats.h"
 
 LLDrawPoolMaterials::LLDrawPoolMaterials()
 :  LLRenderPass(LLDrawPool::POOL_MATERIALS)
@@ -151,7 +150,6 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass)
 	LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type);
 	LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type);
 	
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
     F32 lastIntensity = 0.f;
     F32 lastFullbright = 0.f;
     F32 lastMinimumAlpha = 0.f;
@@ -201,18 +199,6 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass)
 		
         LLCullResult::increment_iterator(i, end);
 
-#if 0 // TODO SL-19656 figure out how to reenable trackAttachments()
-        if(params.mFace)
-        {
-            LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject();
-
-            if( vobj && vobj->isAttachment() )
-            {
-                trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr );
-            }
-        }
-#endif
-
         if (specular > -1 && params.mSpecColor != lastSpecular)
         {
             lastSpecular = params.mSpecColor;
diff --git a/indra/newview/llfloaterperformance.cpp b/indra/newview/llfloaterperformance.cpp
index 3321374f65ceab165c49304cea590a20894668a4..25b744a5ab07edd37c1775ea89836212ba988813 100644
--- a/indra/newview/llfloaterperformance.cpp
+++ b/indra/newview/llfloaterperformance.cpp
@@ -57,8 +57,6 @@ const S32 BAR_RIGHT_PAD = 5;
 const S32 BAR_BOTTOM_PAD = 9;
 
 constexpr auto AvType       {LLPerfStats::ObjType_t::OT_AVATAR};
-constexpr auto AttType      {LLPerfStats::ObjType_t::OT_ATTACHMENT};
-constexpr auto HudType      {LLPerfStats::ObjType_t::OT_HUD};
 
 class LLExceptionsContextMenu : public LLListContextMenu
 {
@@ -83,8 +81,7 @@ class LLExceptionsContextMenu : public LLListContextMenu
 
 LLFloaterPerformance::LLFloaterPerformance(const LLSD& key)
 :   LLFloater(key),
-    mUpdateTimer(new LLTimer()),
-    mNearbyMaxComplexity(0)
+    mUpdateTimer(new LLTimer())
 {
     mContextMenu = new LLExceptionsContextMenu(this);
 }
@@ -247,47 +244,82 @@ void LLFloaterPerformance::populateHUDList()
     mHUDList->clearRows();
     mHUDList->updateColumns(true);
 
-    hud_complexity_list_t complexity_list = LLHUDRenderNotifier::getInstance()->getHUDComplexityList();
-
-    hud_complexity_list_t::iterator iter = complexity_list.begin();
-    hud_complexity_list_t::iterator end = complexity_list.end();
-
-    auto huds_max_render_time_raw = LLPerfStats::StatsRecorder::getMax(HudType, LLPerfStats::StatType_t::RENDER_GEOMETRY);
-    for (iter = complexity_list.begin(); iter != end; ++iter)
-    {
-        LLHUDComplexity hud_object_complexity = *iter;
-
-        auto hud_render_time_raw = LLPerfStats::StatsRecorder::get(HudType, hud_object_complexity.objectId, LLPerfStats::StatType_t::RENDER_GEOMETRY);
-
-        LLSD item;
-        item["special_id"] = hud_object_complexity.objectId;
-        item["target"] = LLNameListCtrl::SPECIAL;
-        LLSD& row = item["columns"];
-        row[0]["column"] = "complex_visual";
-        row[0]["type"] = "bar";
-        LLSD& value = row[0]["value"];
-        value["ratio"] = (F32)hud_render_time_raw / huds_max_render_time_raw;
-        value["bottom"] = BAR_BOTTOM_PAD;
-        value["left_pad"] = BAR_LEFT_PAD;
-        value["right_pad"] = BAR_RIGHT_PAD;
-
-        row[1]["column"] = "complex_value";
-        row[1]["type"] = "text";
-        row[1]["value"] = llformat( "%.f", llmax(LLPerfStats::raw_to_us(hud_render_time_raw), (double)1));
-        row[1]["font"]["name"] = "SANSSERIF";
- 
-        row[2]["column"] = "name";
-        row[2]["type"] = "text";
-        row[2]["value"] = hud_object_complexity.objectName;
-        row[2]["font"]["name"] = "SANSSERIF";
-
-        LLScrollListItem* obj = mHUDList->addElement(item);
-        if (obj)
+    LLVOAvatar* avatar = gAgentAvatarp;
+
+    gPipeline.profileAvatar(avatar, true);
+
+    LLVOAvatar::attachment_map_t::iterator iter;
+    LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+    LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+    // get max gpu render time of all attachments
+    F32 max_gpu_time = -1.f;
+
+    for (iter = begin; iter != end; ++iter)
+    {
+        LLViewerJointAttachment* attachment = iter->second;
+        for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+            attachment_iter != attachment->mAttachedObjects.end();
+            ++attachment_iter)
+        {
+            LLViewerObject* attached_object = attachment_iter->get();
+            if (attached_object && attached_object->isHUDAttachment())
+            {
+                max_gpu_time = llmax(max_gpu_time, attached_object->mGPURenderTime);
+            }
+        }
+    }
+
+
+    for (iter = begin; iter != end; ++iter)
+    {
+        if (!iter->second)
         {
-            LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(obj->getColumn(1));
-            if (value_text)
+            continue;
+        }
+
+        LLViewerJointAttachment* attachment = iter->second;
+        for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+            attachment_iter != attachment->mAttachedObjects.end();
+            ++attachment_iter)
+        {
+            LLViewerObject* attached_object = attachment_iter->get();
+            if (attached_object && attached_object->isHUDAttachment())
             {
-                value_text->setAlignment(LLFontGL::HCENTER);
+                F32 gpu_time = attached_object->mGPURenderTime;
+
+                LLSD item;
+                item["special_id"] = attached_object->getID();
+                item["target"] = LLNameListCtrl::SPECIAL;
+                LLSD& row = item["columns"];
+                row[0]["column"] = "complex_visual";
+                row[0]["type"] = "bar";
+                LLSD& value = row[0]["value"];
+                value["ratio"] = gpu_time / max_gpu_time;
+                value["bottom"] = BAR_BOTTOM_PAD;
+                value["left_pad"] = BAR_LEFT_PAD;
+                value["right_pad"] = BAR_RIGHT_PAD;
+
+                row[1]["column"] = "complex_value";
+                row[1]["type"] = "text";
+                // show gpu time in us
+                row[1]["value"] = llformat("%.f", gpu_time * 1000.f);
+                row[1]["font"]["name"] = "SANSSERIF";
+
+                row[2]["column"] = "name";
+                row[2]["type"] = "text";
+                row[2]["value"] = attached_object->getAttachmentItemName();
+                row[2]["font"]["name"] = "SANSSERIF";
+
+                LLScrollListItem* obj = mHUDList->addElement(item);
+                if (obj)
+                {
+                    LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(obj->getColumn(1));
+                    if (value_text)
+                    {
+                        value_text->setAlignment(LLFontGL::HCENTER);
+                    }
+                }
             }
         }
     }
@@ -303,50 +335,82 @@ void LLFloaterPerformance::populateObjectList()
     mObjectList->clearRows();
     mObjectList->updateColumns(true);
 
-    object_complexity_list_t complexity_list = LLAvatarRenderNotifier::getInstance()->getObjectComplexityList();
+    LLVOAvatar* avatar = gAgentAvatarp;
 
-    object_complexity_list_t::iterator iter = complexity_list.begin();
-    object_complexity_list_t::iterator end = complexity_list.end();
+    gPipeline.profileAvatar(avatar, true);
 
-    // for consistency we lock the buffer while we build the list. In theory this is uncontended as the buffer should only toggle on end of frame
-    {
-        std::lock_guard<std::mutex> guard{ LLPerfStats::bufferToggleLock };
-        auto att_max_render_time_raw = LLPerfStats::StatsRecorder::getMax(AttType, LLPerfStats::StatType_t::RENDER_COMBINED);
+    LLVOAvatar::attachment_map_t::iterator iter;
+    LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+    LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
 
-        for (iter = complexity_list.begin(); iter != end; ++iter)
-        {
-            LLObjectComplexity object_complexity = *iter;
+    // get max gpu render time of all attachments
+    F32 max_gpu_time = -1.f;
 
-            auto attach_render_time_raw = LLPerfStats::StatsRecorder::get(AttType, object_complexity.objectId, LLPerfStats::StatType_t::RENDER_COMBINED);
-            LLSD item;
-            item["special_id"] = object_complexity.objectId;
-            item["target"] = LLNameListCtrl::SPECIAL;
-            LLSD& row = item["columns"];
-            row[0]["column"] = "complex_visual";
-            row[0]["type"] = "bar";
-            LLSD& value = row[0]["value"];
-            value["ratio"] = ((F32)attach_render_time_raw) / att_max_render_time_raw;
-            value["bottom"] = BAR_BOTTOM_PAD;
-            value["left_pad"] = BAR_LEFT_PAD;
-            value["right_pad"] = BAR_RIGHT_PAD;
-
-            row[1]["column"] = "complex_value";
-            row[1]["type"] = "text";
-            row[1]["value"] = llformat("%.f", llmax(LLPerfStats::raw_to_us(attach_render_time_raw), (double)1));
-            row[1]["font"]["name"] = "SANSSERIF";
+    for (iter = begin; iter != end; ++iter)
+    {
+        LLViewerJointAttachment* attachment = iter->second;
+        for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+            attachment_iter != attachment->mAttachedObjects.end();
+            ++attachment_iter)
+        {
+            LLViewerObject* attached_object = attachment_iter->get();
+            if (attached_object && !attached_object->isHUDAttachment())
+            {
+                max_gpu_time = llmax(max_gpu_time, attached_object->mGPURenderTime);
+            }
+        }
+    }
 
-            row[2]["column"] = "name";
-            row[2]["type"] = "text";
-            row[2]["value"] = object_complexity.objectName;
-            row[2]["font"]["name"] = "SANSSERIF";
+    {
+        for (iter = begin; iter != end; ++iter)
+        {
+            if (!iter->second)
+            {
+                continue;
+            }
 
-            LLScrollListItem* obj = mObjectList->addElement(item);
-            if (obj)
+            LLViewerJointAttachment* attachment = iter->second;
+            for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                attachment_iter != attachment->mAttachedObjects.end();
+                ++attachment_iter)
             {
-                LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(obj->getColumn(1));
-                if (value_text)
+                LLViewerObject* attached_object = attachment_iter->get();
+                if (attached_object && !attached_object->isHUDAttachment())
                 {
-                    value_text->setAlignment(LLFontGL::HCENTER);
+                    F32 gpu_time = attached_object->mGPURenderTime;
+
+                    LLSD item;
+                    item["special_id"] = attached_object->getID();
+                    item["target"] = LLNameListCtrl::SPECIAL;
+                    LLSD& row = item["columns"];
+                    row[0]["column"] = "complex_visual";
+                    row[0]["type"] = "bar";
+                    LLSD& value = row[0]["value"];
+                    value["ratio"] = gpu_time / max_gpu_time;
+                    value["bottom"] = BAR_BOTTOM_PAD;
+                    value["left_pad"] = BAR_LEFT_PAD;
+                    value["right_pad"] = BAR_RIGHT_PAD;
+
+                    row[1]["column"] = "complex_value";
+                    row[1]["type"] = "text";
+                    // show gpu time in us
+                    row[1]["value"] = llformat("%.f", gpu_time * 1000.f);
+                    row[1]["font"]["name"] = "SANSSERIF";
+
+                    row[2]["column"] = "name";
+                    row[2]["type"] = "text";
+                    row[2]["value"] = attached_object->getAttachmentItemName();
+                    row[2]["font"]["name"] = "SANSSERIF";
+
+                    LLScrollListItem* obj = mObjectList->addElement(item);
+                    if (obj)
+                    {
+                        LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(obj->getColumn(1));
+                        if (value_text)
+                        {
+                            value_text->setAlignment(LLFontGL::HCENTER);
+                        }
+                    }
                 }
             }
         }
@@ -358,6 +422,7 @@ void LLFloaterPerformance::populateObjectList()
 
 void LLFloaterPerformance::populateNearbyList()
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
     static LLCachedControl<bool> showTunedART(gSavedSettings, "ShowTunedART");
     S32 prev_pos = mNearbyList->getScrollPos();
     LLUUID prev_selected_id = mNearbyList->getStringUUIDSelectedItem();
@@ -366,22 +431,16 @@ void LLFloaterPerformance::populateNearbyList()
 
     static LLCachedControl<U32> max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0);
     std::vector<LLCharacter*> valid_nearby_avs;
-    mNearbyMaxComplexity = LLWorld::getInstance()->getNearbyAvatarsAndCompl(valid_nearby_avs);
+    mNearbyMaxGPUTime = LLWorld::getInstance()->getNearbyAvatarsAndMaxGPUTime(valid_nearby_avs);
 
     std::vector<LLCharacter*>::iterator char_iter = valid_nearby_avs.begin();
 
-    LLPerfStats::bufferToggleLock.lock();
-    auto av_render_max_raw = LLPerfStats::StatsRecorder::getMax(AvType, LLPerfStats::StatType_t::RENDER_COMBINED);
-    LLPerfStats::bufferToggleLock.unlock();
-
     while (char_iter != valid_nearby_avs.end())
     {
         LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(*char_iter);
         if (avatar && (LLVOAvatar::AOA_INVISIBLE != avatar->getOverallAppearance()))
         {
-            LLPerfStats::bufferToggleLock.lock();
-            auto render_av_raw  = LLPerfStats::StatsRecorder::get(AvType, avatar->getID(),LLPerfStats::StatType_t::RENDER_COMBINED);
-            LLPerfStats::bufferToggleLock.unlock();
+            F32 render_av_gpu_ms = avatar->getGPURenderTime();
 
             auto is_slow = avatar->isTooSlow();
             LLSD item;
@@ -392,7 +451,7 @@ void LLFloaterPerformance::populateNearbyList()
             LLSD& value = row[0]["value"];
             // The ratio used in the bar is the current cost, as soon as we take action this changes so we keep the 
             // pre-tune value for the numerical column and sorting.
-            value["ratio"] = (double)render_av_raw / av_render_max_raw;
+            value["ratio"] = render_av_gpu_ms / mNearbyMaxGPUTime;
             value["bottom"] = BAR_BOTTOM_PAD;
             value["left_pad"] = BAR_LEFT_PAD;
             value["right_pad"] = BAR_RIGHT_PAD;
@@ -405,14 +464,15 @@ void LLFloaterPerformance::populateNearbyList()
             }
             else
             {
-                row[1]["value"] = llformat( "%.f", LLPerfStats::raw_to_us( render_av_raw ) );
+                // use GPU time in us
+                row[1]["value"] = llformat( "%.f", render_av_gpu_ms * 1000.f);
             }
             row[1]["font"]["name"] = "SANSSERIF";
 
-            row[2]["column"] = "name";
-            row[2]["type"] = "text";
-            row[2]["value"] = avatar->getFullname();
-            row[2]["font"]["name"] = "SANSSERIF";
+            row[3]["column"] = "name";
+            row[3]["type"] = "text";
+            row[3]["value"] = avatar->getFullname();
+            row[3]["font"]["name"] = "SANSSERIF";
 
             LLScrollListItem* av_item = mNearbyList->addElement(item);
             if(av_item)
diff --git a/indra/newview/llfloaterperformance.h b/indra/newview/llfloaterperformance.h
index 00f904f6d62419654078c2859786c6ded9d70b3c..620dbac5bb7c117a6b346793f02c53a3789398f6 100644
--- a/indra/newview/llfloaterperformance.h
+++ b/indra/newview/llfloaterperformance.h
@@ -94,7 +94,9 @@ class LLFloaterPerformance : public LLFloater
 
     LLTimer* mUpdateTimer;
 
-    S32 mNearbyMaxComplexity;
+    // maximum GPU time of nearby avatars in ms according to LLWorld::getNearbyAvatarsAndMaxGPUTime
+    // -1.f if no profile has happened yet
+    F32 mNearbyMaxGPUTime = -1.f;
 
     boost::signals2::connection	mMaxARTChangedSignal;
 };
diff --git a/indra/newview/llperfstats.cpp b/indra/newview/llperfstats.cpp
index c63aae208924cadc12cff2c3fb45b96e46a6bc96..b680d8761b2ed9d1357c5348c76779d7a5a98911 100644
--- a/indra/newview/llperfstats.cpp
+++ b/indra/newview/llperfstats.cpp
@@ -199,18 +199,7 @@ namespace LLPerfStats
                 // LL_INFOS("scenestats") << "Scenestat: " << static_cast<size_t>(statEntry) << " before=" << avg << " new=" << val << " newavg=" << statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast<size_t>(statEntry)] << LL_ENDL;
             }
         }
-// Allow attachment times etc to update even when FPS limited or sleeping.
-        auto& statsMap = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_ATTACHMENT)];
-        for(auto& stat_entry : statsMap)
-        {
-            auto val = stat_entry.second[static_cast<size_t>(ST::RENDER_COMBINED)];
-            if(val > SMOOTHING_PERIODS){
-                auto avg = statsDoubleBuffer[writeBuffer ^ 1][static_cast<size_t>(ObjType_t::OT_ATTACHMENT)][stat_entry.first][static_cast<size_t>(ST::RENDER_COMBINED)];
-                stat_entry.second[static_cast<size_t>(ST::RENDER_COMBINED)] = avg + (val / SMOOTHING_PERIODS) - (avg / SMOOTHING_PERIODS);
-            }
-        }
-
-
+        
         auto& statsMapAv = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_AVATAR)];
         for(auto& stat_entry : statsMapAv)
         {
diff --git a/indra/newview/llperfstats.h b/indra/newview/llperfstats.h
index dbb88a141d5a1d1a7ac1cb913be0e424b36dbc0e..a4768272b9dc979c8c6675361ddb088f1a019538 100644
--- a/indra/newview/llperfstats.h
+++ b/indra/newview/llperfstats.h
@@ -69,8 +69,6 @@ namespace LLPerfStats
     enum class ObjType_t{
         OT_GENERAL=0, // Also Unknown. Used for n/a type stats such as scenery
         OT_AVATAR,
-        OT_ATTACHMENT,
-        OT_HUD,
         OT_COUNT
     };
     enum class StatType_t{
@@ -260,31 +258,6 @@ namespace LLPerfStats
                 doUpd(avKey, ot, type, val);
                 return;
             }
-
-            if (ot == ObjType_t::OT_ATTACHMENT)
-            {
-                if( !upd.isHUD ) // don't include HUD cost in self.
-                {
-                    LL_PROFILE_ZONE_NAMED("Att as Av")
-                    // For all attachments that are not rigged we add them to the avatar (for all avatars) cost.
-                    doUpd(avKey, ObjType_t::OT_AVATAR, type, val);
-                }
-                if( avKey == focusAv )
-                {
-                    LL_PROFILE_ZONE_NAMED("Att as Att")
-                // For attachments that are for the focusAv (self for now) we record them for the attachment/complexity view
-                    if(upd.isHUD)
-                    {
-                        ot = ObjType_t::OT_HUD;
-                    }
-                    // LL_INFOS("perfstats") << "frame: " << gFrameCount << " Attachment update("<< (type==StatType_t::RENDER_GEOMETRY?"GEOMETRY":"SHADOW") << ": " << key.asString() << " = " << val << LL_ENDL;
-                    doUpd(key, ot, type, val);
-                }
-                // else
-                // {
-                //     // LL_INFOS("perfstats") << "frame: " << gFrameCount << " non-self Att update("<< (type==StatType_t::RENDER_GEOMETRY?"GEOMETRY":"SHADOW") << ": " << key.asString() << " = " << val << " for av " << avKey.asString() << LL_ENDL;
-                // }
-            }
         }
 
         static inline void doUpd(const LLUUID& key, ObjType_t ot, StatType_t type, uint64_t val)
@@ -409,51 +382,7 @@ namespace LLPerfStats
 
     using RecordSceneTime = RecordTime<ObjType_t::OT_GENERAL>;
     using RecordAvatarTime = RecordTime<ObjType_t::OT_AVATAR>;
-    using RecordAttachmentTime = RecordTime<ObjType_t::OT_ATTACHMENT>;
-    using RecordHudAttachmentTime = RecordTime<ObjType_t::OT_HUD>;
-     
-};// namespace LLPerfStats
-
-// helper functions
-using RATptr = std::unique_ptr<LLPerfStats::RecordAttachmentTime>;
-using RSTptr = std::unique_ptr<LLPerfStats::RecordSceneTime>;
 
-template <typename T>
-static inline void trackAttachments(const T * vobj, bool isRigged, RATptr* ratPtrp)
-{
-    if( !vobj ){ ratPtrp->reset(); return;};
-    
-    const T* rootAtt{vobj};
-    if (rootAtt->isAttachment())
-    {
-        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
-
-        while( !rootAtt->isRootEdit() )
-        {
-            rootAtt = (T*)(rootAtt->getParent());
-        }
-
-        auto avPtr = (T*)(rootAtt->getParent()); 
-        if(!avPtr){ratPtrp->reset(); return;}
-
-        auto& av = avPtr->getID();
-        auto& obj = rootAtt->getAttachmentItemID();
-        if (!*ratPtrp || (*ratPtrp)->stat.objID != obj || (*ratPtrp)->stat.avID != av)
-        {
-            if (*ratPtrp)
-            {
-                // deliberately reset to ensure destruction before construction of replacement.
-                ratPtrp->reset();
-            };
-            *ratPtrp = std::make_unique<LLPerfStats::RecordAttachmentTime>( 
-                av, 
-                obj,
-                ( LLPipeline::sShadowRender?LLPerfStats::StatType_t::RENDER_SHADOWS : LLPerfStats::StatType_t::RENDER_GEOMETRY ), 
-                isRigged, 
-                rootAtt->isHUDAttachment());
-        }
-    }
-    return;
-};
+};// namespace LLPerfStats
 
 #endif
diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp
index 00e11798961934ab47654aec6472e45eecb11347..5ce8f4023d2bdd1ace08e3abd1102a1d731f2ccc 100644
--- a/indra/newview/llviewerjointmesh.cpp
+++ b/indra/newview/llviewerjointmesh.cpp
@@ -227,16 +227,6 @@ U32 LLViewerJointMesh::drawShape( F32 pixelArea, BOOL first_pass, BOOL is_dummy)
 		return 0;
 	}
 
-    // render time capture
-    // This path does not appear to have attachments. Prove this then remove.
-    std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
-    auto vobj = mFace->getViewerObject();
-    if( vobj && vobj->isAttachment() )
-    {
-        trackAttachments( vobj, mFace->isState(LLFace::RIGGED), &ratPtr );
-        LL_WARNS("trackAttachments") << "Attachment render time is captuted." << LL_ENDL;
-    }
-
 	U32 triangle_count = 0;
 
 	S32 diffuse_channel = LLDrawPoolAvatar::sDiffuseChannel;
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index fe27227e6b8d5684f04776efd0f2de974590bc4f..cd2363a1b90f5777de59fbc24c10cd00872993eb 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -941,6 +941,10 @@ class LLViewerObject
     // reflection probe state
     bool mIsReflectionProbe = false;  // if true, this object should register itself with LLReflectionProbeManager
     LLPointer<LLReflectionMap> mReflectionProbe = nullptr; // reflection probe coupled to this viewer object.  If not null, should be deregistered when this object is destroyed
+
+    // the amount of GPU time (in ms) it took to render this object according to LLPipeline::profileAvatar
+    // -1.f if no profile data available
+    F32 mGPURenderTime = -1.f;
 };
 
 ///////////////////
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 5b8f4767db61eeae50c4796bc8b29b0bbd93e9bc..e38a6457f42baf086aa57b6ae855139fa3f95354 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -8357,7 +8357,7 @@ void LLVOAvatar::updateTooSlow()
     {
         // use the cached values.
         render_time_raw = mRenderTime;
-        render_geom_time_raw = mGeomTime;		
+        render_geom_time_raw = mGeomTime;
     }
 
 	bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf();
@@ -10984,6 +10984,7 @@ void LLVOAvatar::accountRenderComplexityForObject(
 // Calculations for mVisualComplexity value
 void LLVOAvatar::calculateUpdateRenderComplexity()
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
     /*****************************************************************
      * This calculation should not be modified by third party viewers,
      * since it is used to limit rendering and should be uniform for
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 1502c25b4daf176737f80a55228f9e113678459a..0bb19c44206f80c4fafd4369326e4c483df62094 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -305,8 +305,27 @@ class LLVOAvatar :
 	static const U32 VISUAL_COMPLEXITY_UNKNOWN;
 	void			updateVisualComplexity();
 	
-	U32				getVisualComplexity()			{ return mVisualComplexity;				};		// Numbers calculated here by rendering AV
-	F32				getAttachmentSurfaceArea()		{ return mAttachmentSurfaceArea;		};		// estimated surface area of attachments
+    // get the GPU time in ms of rendering this avatar including all attachments
+    // returns -1 if this avatar has not been profiled using gPipeline.profileAvatar
+    F32             getGPURenderTime() { return mGPURenderTime; }
+
+    // get the CPU time in ms of rendering this avatar including all attachments
+    // return -1 if this avatar has not been profiled using gPipeline.mProfileAvatar
+    F32             getCPURenderTime() { return mCPURenderTime; }
+
+    // get the number of samples passed during the avatar profile
+    // return -1 if this avatar has not been profiled using gPipeline.mProfileAvatar
+    S32             getGPUSamplesPassed() { return mGPUSamplesPassed; }
+
+    // get the number of triangles rendered during the avatar profile
+    // return -1 if this avatar has not been profiled using gPipeline.mProfileAvatar
+    S32             getGPUTrianglesRendered() { return mGPUTrianglesRendered; }
+
+    // DEPRECATED -- obsolete avatar render cost
+	U32				getVisualComplexity()			{ return mVisualComplexity;				};
+
+    // DEPRECATED -- obsolete surface area calculation
+	F32				getAttachmentSurfaceArea()		{ return mAttachmentSurfaceArea;		};
 
 	U32				getReportedVisualComplexity()					{ return mReportedVisualComplexity;				};	// Numbers as reported by the SL server
 	void			setReportedVisualComplexity(U32 value)			{ mReportedVisualComplexity = value;			};
@@ -523,6 +542,7 @@ class LLVOAvatar :
 	S32			mSpecialRenderMode; // special lighting
         
 private:
+    friend class LLPipeline;
 	AvatarOverallAppearance mOverallAppearance;
 	F32			mAttachmentSurfaceArea; //estimated surface area of attachments
     U32			mAttachmentVisibleTriangleCount;
@@ -535,7 +555,22 @@ class LLVOAvatar :
 	S32	 		mUpdatePeriod;
 	S32  		mNumInitFaces; //number of faces generated when creating the avatar drawable, does not inculde splitted faces due to long vertex buffer.
 
+    // profile results
+
+    // GPU render time in ms
+    F32 mGPURenderTime = -1.f;
+
+    // CPU render time in ms
+    F32 mCPURenderTime = -1.f;
+
+    // number of samples passed according to GPU
+    S32 mGPUSamplesPassed = -1;
+
+    // number of triangles rendered according to GPU
+    S32 mGPUTrianglesRendered = -1;
+
 	// the isTooComplex method uses these mutable values to avoid recalculating too frequently
+    // DEPRECATED -- obsolete avatar render cost values
 	mutable U32  mVisualComplexity;
 	mutable bool mVisualComplexityStale;
 	U32          mReportedVisualComplexity; // from other viewers through the simulator
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 12ceb86e525b3d43edd1e456354188598a36b7ae..da5a505d3bc44f82419257cafd28e3b3a25db5a0 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -88,7 +88,6 @@
 #include "llcallstack.h"
 #include "llsculptidsize.h"
 #include "llavatarappearancedefines.h"
-#include "llperfstats.h" 
 #include "llgltfmateriallist.h"
 
 const F32 FORCE_SIMPLE_RENDER_AREA = 512.f;
@@ -5657,7 +5656,6 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
         LL_PROFILE_ZONE_NAMED("rebuildGeom - face list");
 
 		//get all the faces into a list
-        std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
 		for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); 
              drawable_iter != group->getDataEnd(); ++drawable_iter)
 		{
@@ -5693,11 +5691,6 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 				continue;
 			}
 
-            if(vobj->isAttachment())
-            {
-                trackAttachments( vobj, drawablep->isState(LLDrawable::RIGGED),&ratPtr);
-            }
-
 			LLVolume* volume = vobj->getVolume();
 			if (volume)
 			{
@@ -6095,8 +6088,7 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 
 			U32 buffer_count = 0;
 
-            std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr{};
-			for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter)
+            for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter)
 			{
 				LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable();
 
@@ -6106,11 +6098,6 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 					
 					if (!vobj) continue;
 
-                    if (vobj->isAttachment())
-                    {
-                        trackAttachments( vobj, drawablep->isState(LLDrawable::RIGGED), &ratPtr );
-                    }
-					
 					if (debugLoggingEnabled("AnimatedObjectsLinkset"))
 					{
 						if (vobj->isAnimatedObject() && vobj->isRiggedMesh())
@@ -6479,16 +6466,11 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace
 		U32 indices_index = 0;
 		U16 index_offset = 0;
 
-        std::unique_ptr<LLPerfStats::RecordAttachmentTime> ratPtr;
         while (face_iter < i)
 		{
 			//update face indices for new buffer
 			facep = *face_iter;
-            LLViewerObject* vobj = facep->getViewerObject();
-            if(vobj && vobj->isAttachment())
-            {
-                trackAttachments(vobj, LLPipeline::sShadowRender, &ratPtr);
-            }
+
 			if (buffer.isNull())
 			{
 				// Bulk allocation failed
diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp
index 72c8cb371fa4bd61b1f898b14df630a4685d3ceb..732ef1da6cb278342d412f6de191585201c26096 100644
--- a/indra/newview/llworld.cpp
+++ b/indra/newview/llworld.cpp
@@ -1424,10 +1424,10 @@ void LLWorld::getAvatars(uuid_vec_t* avatar_ids, std::vector<LLVector3d>* positi
 	}
 }
 
-S32 LLWorld::getNearbyAvatarsAndCompl(std::vector<LLCharacter*> &valid_nearby_avs)
+F32 LLWorld::getNearbyAvatarsAndMaxGPUTime(std::vector<LLCharacter*> &valid_nearby_avs)
 {
     static LLCachedControl<F32> render_far_clip(gSavedSettings, "RenderFarClip", 64);
-    S32 nearby_max_complexity = 0;
+    F32 nearby_max_complexity = 0;
     F32 radius = render_far_clip * render_far_clip;
     std::vector<LLCharacter*>::iterator char_iter = LLCharacter::sInstances.begin();
     while (char_iter != LLCharacter::sInstances.end())
@@ -1441,8 +1441,8 @@ S32 LLWorld::getNearbyAvatarsAndCompl(std::vector<LLCharacter*> &valid_nearby_av
                 char_iter++;
                 continue;
             }
-            avatar->calculateUpdateRenderComplexity();
-            nearby_max_complexity = llmax(nearby_max_complexity, (S32)avatar->getVisualComplexity());
+            gPipeline.profileAvatar(avatar);
+            nearby_max_complexity = llmax(nearby_max_complexity, avatar->getGPURenderTime());
             valid_nearby_avs.push_back(*char_iter);
         }
         char_iter++;
diff --git a/indra/newview/llworld.h b/indra/newview/llworld.h
index b2be36d72cff72315f7e7d5b70af849974a05e9f..f78cbcaa4852e4390597d4e2ccf794bf34edc033 100644
--- a/indra/newview/llworld.h
+++ b/indra/newview/llworld.h
@@ -172,7 +172,9 @@ class LLWorld : public LLSimpleton<LLWorld>
 	// or if the circuit to this simulator had been lost.
 	bool isRegionListed(const LLViewerRegion* region) const;
 
-    S32 getNearbyAvatarsAndCompl(std::vector<LLCharacter*> &valid_nearby_avs);
+    // profile nearby avatars using gPipeline.profileAvatar and update their render times
+    // return max GPU time
+    F32 getNearbyAvatarsAndMaxGPUTime(std::vector<LLCharacter*> &valid_nearby_avs);
 
 private:
     void clearHoleWaterObjects();
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 2912d0da82ab14d9e9fd828f7a3ce573ff30eb95..df8b8a552ac9bd96d08f2cf98b13684457f9cb00 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -10070,7 +10070,88 @@ void LLPipeline::renderRiggedGroups(LLRenderPass* pass, U32 type, bool texture)
 
 static LLTrace::BlockTimerStatHandle FTM_GENERATE_IMPOSTOR("Generate Impostor");
 
-void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
+void LLPipeline::profileAvatar(LLVOAvatar* avatar, bool profile_attachments)
+{
+    if (gGLManager.mGLVersion < 3.25f)
+    { // profiling requires GL 3.3 or later
+        return;
+    }
+    LLGLSLShader* cur_shader = LLGLSLShader::sCurBoundShaderPtr;
+
+    mRT->deferredScreen.bindTarget();
+    mRT->deferredScreen.clear();
+
+    bool profile_enabled = LLGLSLShader::sProfileEnabled;
+    LLGLSLShader::sProfileEnabled = true;
+
+    if (!profile_attachments)
+    {
+        // profile entire avatar all at once
+        
+        // use gDebugProgram as a proxy for getting profile results
+        gDebugProgram.clearStats();
+        gDebugProgram.placeProfileQuery();
+        LLGLSLShader::sProfileEnabled = false;
+
+        LLTimer cpu_timer;
+
+        generateImpostor(avatar, false, true);
+
+        avatar->mCPURenderTime = (F32)cpu_timer.getElapsedTimeF32() * 1000.f;
+
+        LLGLSLShader::sProfileEnabled = true;
+        gDebugProgram.readProfileQuery();
+
+        avatar->mGPURenderTime = gDebugProgram.mTimeElapsed / 1000000.f;
+
+        avatar->mGPUSamplesPassed = gDebugProgram.mSamplesDrawn;
+        avatar->mGPUTrianglesRendered = gDebugProgram.mTrianglesDrawn;
+    }
+    else 
+    { 
+        // profile attachments one at a time
+        LLVOAvatar::attachment_map_t::iterator iter;
+        LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+        LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+        for (iter = begin;
+            iter != end;
+            ++iter)
+        {
+            LLViewerJointAttachment* attachment = iter->second;
+            for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                attachment_iter != attachment->mAttachedObjects.end();
+                ++attachment_iter)
+            {
+                LLViewerObject* attached_object = attachment_iter->get();
+                if (attached_object)
+                {
+                    gDebugProgram.clearStats();
+                    gDebugProgram.placeProfileQuery();
+                    LLGLSLShader::sProfileEnabled = false;
+
+                    generateImpostor(avatar, false, true, attached_object);
+                    LLGLSLShader::sProfileEnabled = true;
+                    gDebugProgram.readProfileQuery();
+
+                    attached_object->mGPURenderTime = gDebugProgram.mTimeElapsed / 1000000.f;
+
+                    // TODO: maybe also record triangles and samples
+                }
+            }
+        }
+    }
+
+    LLGLSLShader::sProfileEnabled = profile_enabled;
+    mRT->deferredScreen.flush();
+
+    if (cur_shader)
+    {
+        cur_shader->bind();
+    }
+}
+
+void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool for_profile, LLViewerObject* specific_attachment)
 {
     LL_RECORD_BLOCK_TIME(FTM_GENERATE_IMPOSTOR);
     LL_PROFILE_GPU_ZONE("generateImpostor");
@@ -10090,11 +10171,11 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 	assertInitialized();
 
     // previews can't be muted or impostered
-	bool visually_muted = !preview_avatar && avatar->isVisuallyMuted();
+	bool visually_muted = !for_profile && !preview_avatar && avatar->isVisuallyMuted();
     LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID()
                               << " is " << ( visually_muted ? "" : "not ") << "visually muted"
                               << LL_ENDL;
-	bool too_complex = !preview_avatar && avatar->isTooComplex();
+	bool too_complex = !for_profile && !preview_avatar && avatar->isTooComplex();
     LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID()
                               << " is " << ( too_complex ? "" : "not ") << "too complex"
                               << LL_ENDL;
@@ -10129,6 +10210,11 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
          );
 	}
 	
+    if (specific_attachment && specific_attachment->isHUDAttachment())
+    { //enable HUD rendering
+        setRenderTypeMask(RENDER_TYPE_HUD, END_RENDER_TYPES);
+    }
+
 	S32 occlusion = sUseOcclusion;
 	sUseOcclusion = 0;
 
@@ -10185,20 +10271,30 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
         }
         else
         {
-            LLVOAvatar::attachment_map_t::iterator iter;
-            for (iter = avatar->mAttachmentPoints.begin();
-                iter != avatar->mAttachmentPoints.end();
-                ++iter)
+            if (specific_attachment)
             {
-                LLViewerJointAttachment *attachment = iter->second;
-                for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
-                    attachment_iter != attachment->mAttachedObjects.end();
-                    ++attachment_iter)
+                markVisible(specific_attachment->mDrawable->getSpatialBridge(), *viewer_camera);
+            }
+            else
+            {
+                LLVOAvatar::attachment_map_t::iterator iter;
+                LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+                LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+                for (iter = begin;
+                    iter != end;
+                    ++iter)
                 {
-                    LLViewerObject* attached_object = attachment_iter->get();
-                    if (attached_object)
+                    LLViewerJointAttachment* attachment = iter->second;
+                    for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                        attachment_iter != attachment->mAttachedObjects.end();
+                        ++attachment_iter)
                     {
-                        markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera);
+                        LLViewerObject* attached_object = attachment_iter->get();
+                        if (attached_object)
+                        {
+                            markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera);
+                        }
                     }
                 }
             }
@@ -10327,6 +10423,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 
 	LLDrawPoolAvatar::sMinimumAlpha = old_alpha;
 
+    if (!for_profile)
 	{ //create alpha mask based on depth buffer (grey out if muted)
 		if (LLPipeline::sRenderDeferred)
 		{
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index dbd1a34f6135853fe0af01e3ca950a21155b7794..45842a3948c5f181b40927cc09963572dae3de97 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -132,7 +132,8 @@ class LLPipeline
     bool allocateShadowBuffer(U32 resX, U32 resY);
 
 	void resetVertexBuffers(LLDrawable* drawable);
-	void generateImpostor(LLVOAvatar* avatar, bool preview_avatar = false);
+    void profileAvatar(LLVOAvatar* avatar, bool profile_attachments = false);
+	void generateImpostor(LLVOAvatar* avatar, bool preview_avatar = false, bool for_profile = false, LLViewerObject* specific_attachment = nullptr);
 	void bindScreenToTexture();
 	void renderFinalize();
 	void copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst);