diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index c03b9bf72601c4cc51d9d78720095c0c319f9c89..bb2ffc044bedf6eca5b2245a5ab348de05b84993 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -4722,8 +4722,6 @@ void LLAppViewer::idle()
 	LLFrameTimer::updateFrameTime();
 	LLFrameTimer::updateFrameCount();
 	LLEventTimer::updateClass();
-    LLPerfStats::updateClass();
-
 	// LLApp::stepFrame() performs the above three calls plus mRunner.run().
 	// Not sure why we don't call stepFrame() here, except that LLRunner seems
 	// completely redundant with LLEventTimer.
diff --git a/indra/newview/llfloaterperformance.cpp b/indra/newview/llfloaterperformance.cpp
index 19fc3e673e41f0a2523291cf7fcb14fa24528ad5..8c5745aa4322aceee99cf0b5453dd13dd5f6986d 100644
--- a/indra/newview/llfloaterperformance.cpp
+++ b/indra/newview/llfloaterperformance.cpp
@@ -456,8 +456,15 @@ void LLFloaterPerformance::populateNearbyList()
 
             row[1]["column"] = "complex_value";
             row[1]["type"] = "text";
-            // use GPU time in us
-            row[1]["value"] = llformat( "%.f", render_av_gpu_ms * 1000.f);
+            if (is_slow && !showTunedART)
+            {
+                row[1]["value"] = llformat( "%.f", LLPerfStats::raw_to_us( avatar->getLastART() ) );
+            }
+            else
+            {
+                // use GPU time in us
+                row[1]["value"] = llformat( "%.f", render_av_gpu_ms * 1000.f);
+            }
             row[1]["font"]["name"] = "SANSSERIF";
 
             row[3]["column"] = "name";
diff --git a/indra/newview/llperfstats.cpp b/indra/newview/llperfstats.cpp
index 395ac0e788eedc202a49b238e6cba4157f28034f..09b7318b14827888dc07a76b67cfa6660f321bcf 100644
--- a/indra/newview/llperfstats.cpp
+++ b/indra/newview/llperfstats.cpp
@@ -39,11 +39,6 @@ extern LLControlGroup gSavedSettings;
 
 namespace LLPerfStats
 {
-    // avatar timing metrics in ms (updated once per mainloop iteration)
-    std::atomic<F32> sTotalAvatarTime = 0.f;
-    std::atomic<F32> sAverageAvatarTime = 0.f;
-    std::atomic<F32> sMaxAvatarTime = 0.f;
-
     std::atomic<int64_t> tunedAvatars{0};
     std::atomic<U64> renderAvatarMaxART_ns{(U64)(ART_UNLIMITED_NANOS)}; // highest render time we'll allow without culling features
     bool belowTargetFPS{false};
@@ -146,12 +141,14 @@ namespace LLPerfStats
         resetChanges();
     }
 
-    StatsRecorder::StatsRecorder():q(1024*16)
+    StatsRecorder::StatsRecorder():q(1024*16),t(&StatsRecorder::run)
     {
         // create a queue
+        // create a thread to consume from the queue
         tunables.initialiseFromSettings();
         LLPerfStats::cpu_hertz = (F64)LLTrace::BlockTimer::countsPerSecond();
         LLPerfStats::vsync_max_fps = gViewerWindow->getWindow()->getRefreshRate();
+        t.detach();
     }
 
     // static
@@ -175,7 +172,7 @@ namespace LLPerfStats
             // RENDER_MESHREPO,
             StatType_t::RENDER_IDLE };
 
-#if 0
+#if 1
         static constexpr std::initializer_list<StatType_t> avatarStatsToAvg = {
             StatType_t::RENDER_GEOMETRY, 
             StatType_t::RENDER_SHADOWS, 
@@ -205,6 +202,20 @@ namespace LLPerfStats
             }
         }
         
+        auto& statsMapAv = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_AVATAR)];
+        for(auto& stat_entry : statsMapAv)
+        {
+            for(auto& stat : avatarStatsToAvg)
+            {
+                auto val = stat_entry.second[static_cast<size_t>(stat)];
+                if(val > SMOOTHING_PERIODS)
+                {
+                    auto avg = statsDoubleBuffer[writeBuffer ^ 1][static_cast<size_t>(ObjType_t::OT_AVATAR)][stat_entry.first][static_cast<size_t>(stat)];
+                    stat_entry.second[static_cast<size_t>(stat)] = avg + (val / SMOOTHING_PERIODS) - (avg / SMOOTHING_PERIODS);
+                }
+            }
+        }
+
         // swap the buffers
         if(enabled())
         {
@@ -284,36 +295,6 @@ namespace LLPerfStats
         }
     }
 
-    // called once per main loop iteration on main thread
-    void updateClass()
-    {
-        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
-        
-        sTotalAvatarTime = LLVOAvatar::getTotalGPURenderTime();
-        sAverageAvatarTime = LLVOAvatar::getAverageGPURenderTime();
-        sMaxAvatarTime = LLVOAvatar::getMaxGPURenderTime();
-
-        auto general = LL::WorkQueue::getInstance("General");
-
-        if (general)
-        {
-            general->post([] { StatsRecorder::update(); });
-        }
-    }
-
-    // called once per main loop iteration on General thread
-    void StatsRecorder::update()
-    {
-        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
-        StatsRecord upd;
-        auto& instance{ StatsRecorder::getInstance() };
-
-        while (enabled() && !LLApp::isQuitting() && instance.q.tryPop(upd))
-        {
-            instance.processUpdate(upd);
-        }
-    }
-
     //static
     int StatsRecorder::countNearbyAvatars(S32 distance)
     {
@@ -348,8 +329,6 @@ namespace LLPerfStats
     // static
     void StatsRecorder::updateAvatarParams()
     {
-        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
-
         if(tunables.autoTuneTimeout)
         {
             LLPerfStats::lastSleepedFrame = gFrameCount;
@@ -403,10 +382,10 @@ namespace LLPerfStats
             }
         }
 
-        auto av_render_max_raw = ms_to_raw(sMaxAvatarTime);
+        auto av_render_max_raw = LLPerfStats::StatsRecorder::getMax(ObjType_t::OT_AVATAR, LLPerfStats::StatType_t::RENDER_COMBINED);
         // Is our target frame time lower than current? If so we need to take action to reduce draw overheads.
         // cumulative avatar time (includes idle processing, attachments and base av)
-        auto tot_avatar_time_raw = ms_to_raw(sTotalAvatarTime); 
+        auto tot_avatar_time_raw = LLPerfStats::StatsRecorder::getSum(ObjType_t::OT_AVATAR, LLPerfStats::StatType_t::RENDER_COMBINED);
 
         // The frametime budget we have based on the target FPS selected
         auto target_frame_time_raw = (U64)llround(LLPerfStats::cpu_hertz / (target_fps == 0 ? 1 : target_fps));
@@ -440,7 +419,7 @@ namespace LLPerfStats
             // if so we've got work to do
 
             // how much of the frame was spent on non avatar related work?
-            U64 non_avatar_time_raw = tot_frame_time_raw > tot_avatar_time_raw ? tot_frame_time_raw - tot_avatar_time_raw : 0;
+            U64 non_avatar_time_raw = tot_frame_time_raw - tot_avatar_time_raw;
 
             // If the target frame time < scene time (estimated as non_avatar time)
             U64 target_avatar_time_raw;
@@ -498,11 +477,7 @@ namespace LLPerfStats
                 {
                     new_render_limit_ns = renderAvatarMaxART_ns;
                 }
-
-                if (new_render_limit_ns > LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS)
-                {
-                    new_render_limit_ns -= LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS;
-                }
+                new_render_limit_ns -= LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS;
 
                 // bounce at the bottom to prevent "no limit" 
                 new_render_limit_ns = std::max((U64)new_render_limit_ns, (U64)LLPerfStats::ART_MINIMUM_NANOS);
diff --git a/indra/newview/llperfstats.h b/indra/newview/llperfstats.h
index bb5677f237e8b57c031af7213eaa42bb5e2e0d9f..a4768272b9dc979c8c6675361ddb088f1a019538 100644
--- a/indra/newview/llperfstats.h
+++ b/indra/newview/llperfstats.h
@@ -42,10 +42,6 @@ extern U32 gFrameCount;
 extern LLUUID gAgentID;
 namespace LLPerfStats
 {
-
-    // called once per main loop iteration
-    void updateClass();
-
 // Note if changing these, they should correspond with the log range of the correpsonding sliders
     static constexpr U64 ART_UNLIMITED_NANOS{50000000};
     static constexpr U64 ART_MINIMUM_NANOS{100000};
@@ -72,6 +68,7 @@ namespace LLPerfStats
 
     enum class ObjType_t{
         OT_GENERAL=0, // Also Unknown. Used for n/a type stats such as scenery
+        OT_AVATAR,
         OT_COUNT
     };
     enum class StatType_t{
@@ -165,9 +162,6 @@ namespace LLPerfStats
         using Queue = LLThreadSafeQueue<StatsRecord>;
     public:
 
-        // called once per main loop iteration on General thread
-        static void update();
-
         static inline StatsRecorder& getInstance()
         {
             static StatsRecorder instance;
@@ -247,6 +241,7 @@ namespace LLPerfStats
 
             auto ot{upd.objType};
             auto& key{upd.objID};
+            auto& avKey{upd.avID};
             auto type {upd.statType};
             auto val {upd.time};
 
@@ -256,6 +251,13 @@ namespace LLPerfStats
                 doUpd(key, ot, type,val);
                 return;
             }
+
+            if (ot == ObjType_t::OT_AVATAR)
+            {
+                // LL_INFOS("perfstats") << "Avatar update:" << LL_ENDL;
+                doUpd(avKey, ot, type, val);
+                return;
+            }
         }
 
         static inline void doUpd(const LLUUID& key, ObjType_t ot, StatType_t type, uint64_t val)
@@ -284,7 +286,42 @@ namespace LLPerfStats
         static void toggleBuffer();
         static void clearStatsBuffers();
 
+        // thread entry
+        static void run()
+        {
+            StatsRecord upd[10];
+            auto & instance {StatsRecorder::getInstance()};
+            LL_PROFILER_SET_THREAD_NAME("PerfStats");
+
+            while( enabled() && !LLApp::isExiting() )
+            {
+                auto count = 0;
+                while (count < 10)
+                {
+                    if (instance.q.tryPopFor(std::chrono::milliseconds(10), upd[count]))
+                    {
+                        count++;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+                //LL_PROFILER_THREAD_BEGIN("PerfStats");
+                if(count)
+                {
+                    // LL_INFOS("perfstats") << "processing " << count << " updates." << LL_ENDL;
+                    for(auto i =0; i < count; i++)
+                    {
+                        instance.processUpdate(upd[i]);
+                    }
+                }
+                //LL_PROFILER_THREAD_END("PerfStats");
+            }
+        }
+
         Queue q;
+        std::thread t;
 
         ~StatsRecorder() = default;
         StatsRecorder(const StatsRecorder&) = delete;
@@ -317,6 +354,13 @@ namespace LLPerfStats
             LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
         };
 
+        template < ObjType_t OD = ObjTypeDiscriminator,
+                   std::enable_if_t<OD == ObjType_t::OT_AVATAR> * = nullptr>
+        RecordTime( const LLUUID & av, StatType_t type ):RecordTime<ObjTypeDiscriminator>(std::move(av), LLUUID::null, type)
+        {
+            //LL_PROFILE_ZONE_COLOR(tracy::Color::Purple);
+        };
+
         ~RecordTime()
         { 
             if(!LLPerfStats::StatsRecorder::enabled())
@@ -331,19 +375,13 @@ namespace LLPerfStats
         };
     };
 
-
+    
     inline double raw_to_ns(U64 raw)    { return (static_cast<double>(raw) * 1000000000.0) / LLPerfStats::cpu_hertz; };
     inline double raw_to_us(U64 raw)    { return (static_cast<double>(raw) *    1000000.0) / LLPerfStats::cpu_hertz; };
     inline double raw_to_ms(U64 raw)    { return (static_cast<double>(raw) *       1000.0) / LLPerfStats::cpu_hertz; };
 
-    inline U64 ns_to_raw(double ns)     { return (U64)(LLPerfStats::cpu_hertz * (ns / 1000000000.0)); }
-    inline U64 us_to_raw(double us)     { return (U64)(LLPerfStats::cpu_hertz * (us / 1000000.0)); }
-    inline U64 ms_to_raw(double ms)     { return (U64)(LLPerfStats::cpu_hertz * (ms / 1000.0));
-
-    }
-    
-
     using RecordSceneTime = RecordTime<ObjType_t::OT_GENERAL>;
+    using RecordAvatarTime = RecordTime<ObjType_t::OT_AVATAR>;
 
 };// namespace LLPerfStats
 
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index 12c8d32fa4fca925353db45869752ed1e9458560..22f583e912842f52a4258a4df4715e448085cbde 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -473,7 +473,7 @@ void update_statistics()
 
             auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME);
             // cumulative avatar time (includes idle processing, attachments and base av)
-            auto tot_avatar_time_raw = LLPerfStats::us_to_raw(LLVOAvatar::getTotalGPURenderTime());
+            auto tot_avatar_time_raw = LLPerfStats::StatsRecorder::getSum(LLPerfStats::ObjType_t::OT_AVATAR, LLPerfStats::StatType_t::RENDER_COMBINED);
             // cumulative avatar render specific time (a bit arbitrary as the processing is too.)
             // auto tot_av_idle_time_raw = LLPerfStats::StatsRecorder::getSum(AvType, LLPerfStats::StatType_t::RENDER_IDLE);
             // auto tot_avatar_render_time_raw = tot_avatar_time_raw - tot_av_idle_time_raw;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 7ce4ddd1d53e9fd9f76d07f0f8afefdad44f2026..93246b99b275788e682fff486d7269cf3d569071 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -2597,6 +2597,7 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time)
 		return;
 	}
     // record time and refresh "tooSlow" status
+    LLPerfStats::RecordAvatarTime T(getID(), LLPerfStats::StatType_t::RENDER_IDLE); // per avatar "idle" time.
     updateTooSlow();
 
 	static LLCachedControl<bool> disable_all_render_types(gSavedSettings, "DisableAllRenderTypes");
@@ -8674,6 +8675,14 @@ bool LLVOAvatar::isTooSlow() const
     return mTooSlow;
 }
 
+// use Avatar Render Time as complexity metric
+// markARTStale - Mark stale and set the frameupdate to now so that we can wait at least one frame to get a revised number.
+void LLVOAvatar::markARTStale()
+{
+    mARTStale=true;
+    mLastARTUpdateFrame = LLFrameTimer::getFrameCount();
+}
+
 // Udpate Avatar state based on render time
 void LLVOAvatar::updateTooSlow()
 {
@@ -8684,9 +8693,41 @@ void LLVOAvatar::updateTooSlow()
 
     // mTooSlow - Is the avatar flagged as being slow (includes shadow time)
     // mTooSlowWithoutShadows - Is the avatar flagged as being slow even with shadows removed.
-    
-    // get max render time in ms
-    F32 max_art_ms = (F32) (LLPerfStats::renderAvatarMaxART_ns / 1000000.0);
+    // mARTStale - the rendertime we have is stale because of an update. We need to force a re-render to re-assess slowness
+
+    if( mARTStale )
+    {
+        if ( LLFrameTimer::getFrameCount() - mLastARTUpdateFrame < 5 ) 
+        {
+            // LL_INFOS() << this->getFullname() << " marked stale " << LL_ENDL;
+            // we've not had a chance to update yet (allow a few to be certain a full frame has passed)
+            return;
+        }
+
+        mARTStale = false;
+        mTooSlow = false;
+        mTooSlowWithoutShadows = false;
+        // LL_INFOS() << this->getFullname() << " refreshed ART combined = " << mRenderTime << " @ " << mLastARTUpdateFrame << LL_ENDL;
+    }
+
+    // Either we're not stale or we've updated.
+
+    U64 render_time_raw;
+    U64 render_geom_time_raw;
+
+    if( !mTooSlow ) 
+    {
+        // we are fully rendered, so we use the live values
+        std::lock_guard<std::mutex> lock{LLPerfStats::bufferToggleLock};
+        render_time_raw = LLPerfStats::StatsRecorder::get(LLPerfStats::ObjType_t::OT_AVATAR, id, LLPerfStats::StatType_t::RENDER_COMBINED);
+        render_geom_time_raw = LLPerfStats::StatsRecorder::get(LLPerfStats::ObjType_t::OT_AVATAR, id, LLPerfStats::StatType_t::RENDER_GEOMETRY);
+    }
+    else
+    {
+        // use the cached values.
+        render_time_raw = mRenderTime;
+        render_geom_time_raw = mGeomTime;
+    }
 
 	bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf();
 
@@ -8702,13 +8743,19 @@ void LLVOAvatar::updateTooSlow()
     }
 
 	bool exceeds_max_ART =
-        ((LLPerfStats::renderAvatarMaxART_ns > 0) && 
-            (mGPURenderTime >= max_art_ms)); // NOTE: don't use getGPURenderTime accessor here to avoid "isTooSlow" feedback loop
+        ((LLPerfStats::renderAvatarMaxART_ns > 0) && (LLPerfStats::raw_to_ns(render_time_raw) >= LLPerfStats::renderAvatarMaxART_ns));
 
     if (exceeds_max_ART && !ignore_tune)
     {
-        mTooSlow = true;
-        
+        if( !mTooSlow ) // if we were previously not slow (with or without shadows.)
+        {			
+            // if we weren't capped, we are now
+            mLastARTUpdateFrame = LLFrameTimer::getFrameCount();
+            mRenderTime = render_time_raw;
+            mGeomTime = render_geom_time_raw;
+            mARTStale = false;
+            mTooSlow = true;
+        }
         if(!mTooSlowWithoutShadows) // if we were not previously above the full impostor cap
         {
             bool render_friend_or_exception =  	( alwaysRenderFriends && LLAvatarTracker::instance().isBuddy( id ) ) ||
@@ -8716,12 +8763,13 @@ void LLVOAvatar::updateTooSlow()
             if( (!isSelf() || allowSelfImpostor) && !render_friend_or_exception  )
             {
                 // Note: slow rendering Friends still get their shadows zapped.
-                mTooSlowWithoutShadows = getGPURenderTime()*2.f >= max_art_ms;  // NOTE: assumes shadow rendering doubles render time
+                mTooSlowWithoutShadows = (LLPerfStats::raw_to_ns(render_geom_time_raw) >= LLPerfStats::renderAvatarMaxART_ns);
             }
         }
     }
     else
     {
+        // LL_INFOS() << this->getFullname() << " ("<< (combined?"combined":"geometry") << ") good render time = " << LLPerfStats::raw_to_ns(render_time_raw) << " vs ("<< LLVOAvatar::sRenderTimeCap_ns << " set @ " << mLastARTUpdateFrame << LL_ENDL;
         mTooSlow = false;
         mTooSlowWithoutShadows = false;
 
@@ -11506,21 +11554,6 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
         {
             LLSidepanelAppearance::updateAvatarComplexity(mVisualComplexity, item_complexity, temp_item_complexity, body_parts_complexity);
         }
-
-        //schedule an update to ART next frame if needed
-        if (LLPerfStats::tunables.userAutoTuneEnabled && 
-            LLPerfStats::tunables.userFPSTuningStrategy != LLPerfStats::TUNE_SCENE_ONLY &&
-            !isVisuallyMuted())
-        {
-            LLUUID id = getID(); // <== use id to make sure this avatar didn't get deleted between frames
-            LL::WorkQueue::getInstance("mainloop")->post([this, id]()
-                {
-                    if (gObjectList.findObject(id) != nullptr)
-                    {
-                        gPipeline.profileAvatar(this);
-                    }
-                });
-        }
     }
 }
 
@@ -11898,9 +11931,6 @@ void LLVOAvatar::readProfileQuery(S32 retries)
         glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT, &time_elapsed);
         mGPURenderTime = time_elapsed / 1000000.f;
         mGPUProfilePending = false;
-
-        setDebugText(llformat("%d", (S32)(mGPURenderTime * 1000.f)));
-
     }
     else
     { // wait until next frame
@@ -11913,67 +11943,3 @@ void LLVOAvatar::readProfileQuery(S32 retries)
     }
 }
 
-
-F32 LLVOAvatar::getGPURenderTime()
-{
-    return isVisuallyMuted() ? 0.f : mGPURenderTime;
-}
-
-// static
-F32 LLVOAvatar::getTotalGPURenderTime()
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
-
-    F32 ret = 0.f;
-
-    for (LLCharacter* iter : LLCharacter::sInstances)
-    {
-        LLVOAvatar* inst = (LLVOAvatar*) iter;
-        ret += inst->getGPURenderTime();
-    }
-
-    return ret;
-}
-
-F32 LLVOAvatar::getMaxGPURenderTime()
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
-
-    F32 ret = 0.f;
-
-    for (LLCharacter* iter : LLCharacter::sInstances)
-    {
-        LLVOAvatar* inst = (LLVOAvatar*)iter;
-        ret = llmax(inst->getGPURenderTime(), ret);
-    }
-
-    return ret;
-}
-
-F32 LLVOAvatar::getAverageGPURenderTime()
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
-
-    F32 ret = 0.f;
-
-    S32 count = 0;
-
-    for (LLCharacter* iter : LLCharacter::sInstances)
-    {
-        LLVOAvatar* inst = (LLVOAvatar*)iter;
-        if (!inst->isTooSlow())
-        {
-            ret += inst->getGPURenderTime();
-            ++count;
-        }
-    }
-
-    if (count > 0)
-    {
-        ret /= count;
-    }
-
-    return ret;
-}
-
-
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index ddf0ab37e3e6b319c0b8e29c0c0e8cb9829024a8..bdf275308d8d9b144174670f36ed63726ecd4a7f 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -318,21 +318,11 @@ class LLVOAvatar :
     void readProfileQuery(S32 retries);
 
     // get the GPU time in ms of rendering this avatar including all attachments
-    // returns 0.f if this avatar has not been profiled using gPipeline.profileAvatar
-    // or the avatar is visually muted
-    F32             getGPURenderTime();
-
-    // get the total GPU render time in ms of all avatars that have been benched
-    static F32      getTotalGPURenderTime();
-
-    // get the max GPU render time in ms of all avatars that have been benched
-    static F32      getMaxGPURenderTime();
-
-    // get the average GPU render time in ms of all avatars that have been benched
-    static F32      getAverageGPURenderTime();
+    // 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 0.f if this avatar has not been profiled using gPipeline.mProfileAvatar
+    // return -1 if this avatar has not been profiled using gPipeline.mProfileAvatar
     F32             getCPURenderTime() { return mCPURenderTime; }
 
     
@@ -421,7 +411,8 @@ class LLVOAvatar :
 	void 			logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed);
 
     void            calcMutedAVColor();
-    
+    void            markARTStale();
+
 protected:
 	LLViewerStats::PhaseMap& getPhases() { return mPhases; }
 	BOOL			updateIsFullyLoaded();
@@ -442,6 +433,11 @@ class LLVOAvatar :
 	LLFrameTimer	mFullyLoadedTimer;
 	LLFrameTimer	mRuthTimer;
 
+    U32				mLastARTUpdateFrame{0};
+    U64				mRenderTime{0};
+    U64				mGeomTime{0};
+    bool			mARTStale{true};
+    bool			mARTCapped{false};
     // variables to hold "slowness" status
     bool			mTooSlow{false};
     bool			mTooSlowWithoutShadows{false};
@@ -575,11 +571,11 @@ class LLVOAvatar :
     // profile results
 
     // GPU render time in ms
-    F32 mGPURenderTime = 0.f;
+    F32 mGPURenderTime = -1.f;
     bool mGPUProfilePending = false;
 
     // CPU render time in ms
-    F32 mCPURenderTime = 0.f;
+    F32 mCPURenderTime = -1.f;
 
 	// the isTooComplex method uses these mutable values to avoid recalculating too frequently
     // DEPRECATED -- obsolete avatar render cost values
@@ -1230,6 +1226,8 @@ class LLVOAvatar :
 	// COF version of last appearance message received for this av.
 	S32 mLastUpdateReceivedCOFVersion;
 
+    U64 getLastART() const { return mRenderTime; }
+
 /**                    Diagnostics
  **                                                                            **
  *******************************************************************************/
diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp
index ae6280529c1e62b12d0cb26f2af4d43395f1ea6f..7f95ba74d1eae35b692a5957a0a85424fa43788b 100644
--- a/indra/newview/llworld.cpp
+++ b/indra/newview/llworld.cpp
@@ -1651,11 +1651,7 @@ F32 LLWorld::getNearbyAvatarsAndMaxGPUTime(std::vector<LLCharacter*> &valid_near
                 char_iter++;
                 continue;
             }
-
-            if (!avatar->isTooSlow())
-            {
-                gPipeline.profileAvatar(avatar);
-            }
+            gPipeline.profileAvatar(avatar);
             nearby_max_complexity = llmax(nearby_max_complexity, avatar->getGPURenderTime());
             valid_nearby_avs.push_back(*char_iter);
         }
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index e5a92a57e3c242f5fbdbc5db9a11a8782a34edd1..92052b8ccbd25cb9cb094a3b321b9503b53db260 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -10138,9 +10138,6 @@ void LLPipeline::profileAvatar(LLVOAvatar* avatar, bool profile_attachments)
 
     LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
 
-    // don't continue to profile an avatar that is known to be too slow
-    llassert(!avatar->isTooSlow());
-
     LLGLSLShader* cur_shader = LLGLSLShader::sCurBoundShaderPtr;
 
     mRT->deferredScreen.bindTarget();
@@ -10413,28 +10410,25 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool
 		resY = llmin(nhpo2((U32) (fov*pa)), (U32) 512);
 		resX = llmin(nhpo2((U32) (atanf(tdim.mV[0]/distance)*2.f*RAD_TO_DEG*pa)), (U32) 512);
 
-        if (!for_profile)
-        {
-            if (!avatar->mImpostor.isComplete())
-            {
-                avatar->mImpostor.allocate(resX, resY, GL_RGBA, true);
-
-                if (LLPipeline::sRenderDeferred)
-                {
-                    addDeferredAttachments(avatar->mImpostor, true);
-                }
+		if (!avatar->mImpostor.isComplete())
+		{
+            avatar->mImpostor.allocate(resX, resY, GL_RGBA, true);
 
-                gGL.getTexUnit(0)->bind(&avatar->mImpostor);
-                gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
-                gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-            }
-            else if (resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight())
-            {
-                avatar->mImpostor.resize(resX, resY);
-            }
+			if (LLPipeline::sRenderDeferred)
+			{
+				addDeferredAttachments(avatar->mImpostor, true);
+			}
+		
+			gGL.getTexUnit(0)->bind(&avatar->mImpostor);
+			gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
+			gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+		}
+		else if(resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight())
+		{
+			avatar->mImpostor.resize(resX,resY);
+		}
 
-            avatar->mImpostor.bindTarget();
-        }
+		avatar->mImpostor.bindTarget();
 	}
 
 	F32 old_alpha = LLDrawPoolAvatar::sMinimumAlpha;
@@ -10537,7 +10531,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool
 		gGL.popMatrix();
 	}
 
-    if (!preview_avatar && !for_profile)
+    if (!preview_avatar)
     {
         avatar->mImpostor.flush();
         avatar->setImpostorDim(tdim);