Kaynağa Gözat

Capture and tranmission of Recycler Telemetry stats.

Mike Kaufman 8 yıl önce
ebeveyn
işleme
9bd1005dc2

+ 1 - 1
bin/GCStress/GCStress.cpp

@@ -273,7 +273,7 @@ void SimpleRecyclerTest()
         AUTO_NESTED_HANDLED_EXCEPTION_TYPE(ExceptionType_DisableCheck);
 #endif
 
-        recyclerInstance = HeapNewZ(Recycler, nullptr, &pageAllocator, Js::Throw::OutOfMemory, Js::Configuration::Global.flags);
+        recyclerInstance = HeapNewZ(Recycler, nullptr, &pageAllocator, Js::Throw::OutOfMemory, Js::Configuration::Global.flags, nullptr);
 
         recyclerInstance->Initialize(false /* forceInThread */, nullptr /* threadService */);
 

+ 3 - 1
bin/GCStress/GCStress.vcxproj

@@ -31,8 +31,10 @@
         $(ChakraCommonMemoryLib);
         $(ChakraRuntimePlatformAgnostic);
         $(ChakraCommonLinkDependencies);
+        Ole32.lib;
         Advapi32.lib;
-        %(AdditionalDependencies)</AdditionalDependencies>
+        %(AdditionalDependencies)
+      </AdditionalDependencies>
       <AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories);$(SdkLibPath)</AdditionalLibraryDirectories>
       <SubSystem>Console</SubSystem>
     </Link>

+ 1 - 0
lib/Common/Common/Chakra.Common.Common.vcxproj

@@ -69,6 +69,7 @@
     <ClInclude Include="MathUtil.h" />
     <ClInclude Include="NumberUtilities.h" />
     <ClInclude Include="NumberUtilitiesBase.h" />
+    <ClInclude Include="ObservableValue.h" />
     <ClInclude Include="RejitReason.h" />
     <ClInclude Include="RejitReasons.h" />
     <ClInclude Include="SmartFpuControl.h" />

+ 1 - 1
lib/Common/Common/Chakra.Common.Common.vcxproj.filters

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <ClCompile Include="$(MSBuildThisFileDirectory)Api.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)DateUtilities.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)Event.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)Int32Math.cpp" />
@@ -38,6 +37,7 @@
     <ClInclude Include="NumberUtilitiesBase.h" />
     <ClInclude Include="SmartFpuControl.h" />
     <ClInclude Include="Int64Math.h" />
+    <ClInclude Include="ObservableValue.h" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Jobs.inl" />

+ 51 - 0
lib/Common/Common/ObservableValue.h

@@ -0,0 +1,51 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+template<class T>
+class ObservableValueObserver
+{
+public:
+    virtual void ValueChanged(const T& newVal, const T& oldVal) = 0;
+};
+
+
+template<class T>
+class ObservableValue final
+{
+private:
+    T value;
+    ObservableValueObserver<T>* observer;
+
+    void SetValue(const T newValue)
+    {
+        if (observer != nullptr && newValue != this->value)
+        {
+            observer->ValueChanged(newValue, this->value);
+        }
+        this->value = newValue;
+    }
+
+public:
+    ObservableValue(T val, ObservableValueObserver<T>* observer) :
+        observer(observer),
+        value(val)
+    {
+    }
+
+    ObservableValue(ObservableValue<T>& other) = delete;
+
+    void operator= (const T value)
+    {
+        this->SetValue(value);
+    }
+
+    operator T() const
+    {
+        return this->value;
+    }
+};
+

+ 0 - 1
lib/Common/Common/Tick.h

@@ -109,7 +109,6 @@ namespace Js {
     // Construction
     public:
                 TickDelta();
-    private:
                 TickDelta(int64 lnDelta);
 
     // Properties

+ 25 - 0
lib/Common/Memory/AllocatorTelemetryStats.h

@@ -0,0 +1,25 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+#include "Common/Tick.h"
+
+struct AllocatorDecommitStats
+{
+    Js::Tick lastLeaveDecommitRegion;
+    Js::TickDelta maxDeltaBetweenDecommitRegionLeaveAndDecommit;
+    int64 numDecommitCalls;
+    int64 numPagesDecommitted;
+    int64 numFreePageCount;
+};
+
+struct AllocatorSizes
+{
+    size_t usedBytes;
+    size_t reservedBytes;
+    size_t committedBytes;
+    size_t numberOfSegments;
+};

+ 7 - 0
lib/Common/Memory/BucketStatsReporter.h

@@ -2,6 +2,11 @@
 // Copyright (C) Microsoft. All rights reserved.
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+#include "HeapBucketStats.h"
+
 namespace Memory
 {
 
@@ -68,6 +73,8 @@ public:
         DUMP_FRAGMENTATION_STATS_ONLY(DumpHeader());
     }
 
+    HeapBucketStats* GetTotalStats() { return &total; }
+
     bool IsEtwEnabled() const
     {
         return IS_GCETW_Enabled(GC_BUCKET_STATS);

+ 5 - 0
lib/Common/Memory/Chakra.Common.Memory.vcxproj

@@ -76,6 +76,7 @@
     <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerObjectGraphDumper.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerPageAllocator.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerSweep.cpp" />
+    <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerTelemetryInfo.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerWriteBarrierManager.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)SmallFinalizableHeapBlock.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)SmallFinalizableHeapBucket.cpp" />
@@ -98,6 +99,7 @@
   <ItemGroup>
     <ClInclude Include="AllocationPolicyManager.h" />
     <ClInclude Include="Allocator.h" />
+    <ClInclude Include="AllocatorTelemetryStats.h" />
     <ClInclude Include="amd64\XDataAllocator.h">
       <ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
     </ClInclude>
@@ -144,6 +146,7 @@
     <ClInclude Include="RecyclerRootPtr.h" />
     <ClInclude Include="RecyclerSweep.h" />
     <ClInclude Include="RecyclerSweepManager.h" />
+    <ClInclude Include="RecyclerTelemetryInfo.h" />
     <ClInclude Include="RecyclerWeakReference.h" />
     <ClInclude Include="RecyclerWriteBarrierManager.h" />
     <ClInclude Include="SectionAllocWrapper.h" />
@@ -157,6 +160,8 @@
     <ClInclude Include="MemoryLogger.h" />
     <ClInclude Include="StressTest.h" />
     <ClInclude Include="VirtualAllocWrapper.h" />
+    <ClInclude Include="RecyclerWaitReason.h" />
+    <ClInclude Include="RecyclerWaitReasonInc.h" />
     <ClInclude Include="WriteBarrierMacros.h" />
     <ClInclude Include="XDataAllocator.h" />
   </ItemGroup>

+ 6 - 0
lib/Common/Memory/Chakra.Common.Memory.vcxproj.filters

@@ -59,6 +59,7 @@
     <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerSweepManager.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)DelayDeletingFunctionTable.cpp" />
     <ClCompile Include="$(MSBuildThisFileDirectory)HeapBucketStats.cpp" />
+    <ClCompile Include="$(MSBuildThisFileDirectory)RecyclerTelemetryInfo.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Allocator.h" />
@@ -124,6 +125,11 @@
     <ClInclude Include="BucketStatsReporter.h" />
     <ClInclude Include="RecyclerSweepManager.h" />
     <ClInclude Include="HeapBucketStats.h" />
+    <ClInclude Include="RecyclerTelemetryInfo.h" />
+    <ClInclude Include="AllocatorTelemetryStats.h" />
+    <ClInclude Include="HeapBucketStats.h" />
+    <ClInclude Include="RecyclerWaitReason.h" />
+    <ClInclude Include="RecyclerWaitReasonInc.h" />
   </ItemGroup>
   <ItemGroup>
     <None Include="HeapBlock.inl" />

+ 8 - 0
lib/Common/Memory/CollectionState.h

@@ -2,6 +2,12 @@
 // Copyright (C) Microsoft. All rights reserved.
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+namespace Memory
+{
+
 enum CollectionState
 {
     Collection_Mark                 = 0x00000001,
@@ -95,3 +101,5 @@ enum CollectionState
 
     CollectionStateConcurrentMarkWeakRef = Collection_ConcurrentMark | Collection_ExecutingConcurrent | Collection_WeakRefMark,
 };
+
+}

+ 4 - 2
lib/Common/Memory/HeapBlock.cpp

@@ -1360,7 +1360,8 @@ SmallHeapBlockT<TBlockAttributes>::Sweep(RecyclerSweep& recyclerSweep, bool queu
         if (recycler->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
         {
             SweepState stateReturned = (this->freeCount == 0) ? SweepStateFull : state;
-            Output::Print(_u("[GC #%d] [HeapBucket 0x%p] HeapBlock 0x%p %s %d [CollectionState: %d] \n"), recycler->collectionCount, this->heapBucket, this, _u("[**37**] heapBlock swept. State returned:"), stateReturned, recycler->collectionState);
+            CollectionState collectionState = recycler->collectionState;
+            Output::Print(_u("[GC #%d] [HeapBucket 0x%p] HeapBlock 0x%p %s %d [CollectionState: %d] \n"), recycler->collectionCount, this->heapBucket, this, _u("[**37**] heapBlock swept. State returned:"), stateReturned, collectionState);
         }
 #endif
         return (this->freeCount == 0) ? SweepStateFull : state;
@@ -1425,7 +1426,8 @@ SmallHeapBlockT<TBlockAttributes>::Sweep(RecyclerSweep& recyclerSweep, bool queu
         if (recycler->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
         {
             SweepState stateReturned = (this->freeCount == 0) ? SweepStateFull : state;
-            Output::Print(_u("[GC #%d] [HeapBucket 0x%p] HeapBlock 0x%p %s %d [CollectionState: %d] \n"), recycler->collectionCount, this->heapBucket, this, _u("[**38**] heapBlock swept. State returned:"), stateReturned, recycler->collectionState);
+            CollectionState collectionState = recycler->collectionState;
+            Output::Print(_u("[GC #%d] [HeapBucket 0x%p] HeapBlock 0x%p %s %d [CollectionState: %d] \n"), recycler->collectionCount, this->heapBucket, this, _u("[**38**] heapBlock swept. State returned:"), stateReturned, collectionState);
         }
 #endif
         // We always need to check the free count as we may have allocated from this block during concurrent sweep.

+ 3 - 0
lib/Common/Memory/HeapBlock.h

@@ -9,6 +9,8 @@
 
 class ScriptMemoryDumper;
 
+#include "HeapBucketStats.h"
+
 namespace Memory
 {
 #ifdef RECYCLER_PAGE_HEAP
@@ -44,6 +46,7 @@ class  RecyclerSweep;
 class MarkContext;
 
 #if ENABLE_MEM_STATS
+
 #ifdef DUMP_FRAGMENTATION_STATS
 #define DUMP_FRAGMENTATION_STATS_ONLY(x) x
 #define DUMP_FRAGMENTATION_STATS_IS(x) x

+ 6 - 3
lib/Common/Memory/HeapBucket.cpp

@@ -1541,7 +1541,8 @@ HeapBucketT<TBlockType>::PrepareForAllocationsDuringConcurrentSweep(TBlockType *
                 if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
                 {
                     size_t currentHeapBlockCount = QueryDepthInterlockedSList(allocableHeapBlockListHead);
-                    Output::Print(_u("[GC #%d] [HeapBucket 0x%p] Starting allocations during  concurrent sweep with %d blocks. [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, currentHeapBlockCount, this->GetRecycler()->collectionState);
+                    CollectionState collectionState = this->GetRecycler()->collectionState;
+                    Output::Print(_u("[GC #%d] [HeapBucket 0x%p] Starting allocations during  concurrent sweep with %d blocks. [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, currentHeapBlockCount, collectionState);
                     Output::Print(_u("[GC #%d] [HeapBucket 0x%p] The heapBlockList has %d blocks. Total heapBlockCount is %d.\n\n"), this->GetRecycler()->collectionCount, this, HeapBlockList::Count(this->heapBlockList), this->heapBlockCount);
                 }
 #endif
@@ -1790,7 +1791,8 @@ HeapBucketT<TBlockType>::FinishConcurrentSweep()
 #ifdef RECYCLER_TRACE
         if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
         {
-            Output::Print(_u("[GC #%d] [HeapBucket 0x%p] starting FinishConcurrentSweep [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, this->GetRecycler()->collectionState);
+            CollectionState collectionState = this->GetRecycler()->collectionState;
+            Output::Print(_u("[GC #%d] [HeapBucket 0x%p] starting FinishConcurrentSweep [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, collectionState);
         }
 #endif
 
@@ -1827,7 +1829,8 @@ HeapBucketT<TBlockType>::AppendAllocableHeapBlockList(TBlockType * list)
 #ifdef RECYCLER_TRACE
     if (this->GetRecycler()->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
     {
-        Output::Print(_u("[GC #%d] [HeapBucket 0x%p] in AppendAllocableHeapBlockList [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, this->GetRecycler()->collectionState);
+        CollectionState collectionState = this->GetRecycler()->collectionState;
+        Output::Print(_u("[GC #%d] [HeapBucket 0x%p] in AppendAllocableHeapBlockList [CollectionState: %d] \n"), this->GetRecycler()->collectionCount, this, collectionState);
     }
 #endif
     // Add the list to the end of the current list

+ 3 - 0
lib/Common/Memory/HeapInfo.h

@@ -2,6 +2,9 @@
 // Copyright (C) Microsoft. All rights reserved.
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
 namespace Memory
 {
 #if ENABLE_MEM_STATS

+ 4 - 1
lib/Common/Memory/IdleDecommitPageAllocator.cpp

@@ -76,10 +76,13 @@ IdleDecommitPageAllocator::EnterIdleDecommit()
 IdleDecommitSignal
 IdleDecommitPageAllocator::LeaveIdleDecommit(bool allowTimer)
 {
-
     Assert(this->idleDecommitEnterCount > 0);
     Assert(this->maxFreePageCount == maxIdleDecommitFreePageCount);
 
+#ifdef ENABLE_BASIC_TELEMETRY
+    this->GetDecommitStats()->lastLeaveDecommitRegion = Js::Tick::Now();
+#endif
+
 #ifdef IDLE_DECOMMIT_ENABLED
     Assert(!hasDecommitTimer);
 #endif

+ 45 - 19
lib/Common/Memory/PageAllocator.cpp

@@ -677,8 +677,13 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::PageAllocatorBase(Allo
 #if ENABLE_BACKGROUND_PAGE_FREEING
     BackgroundPageQueue * backgroundPageQueue,
 #endif
-    uint maxAllocPageCount, uint secondaryAllocPageCount,
-    bool stopAllocationOnOutOfMemory, bool excludeGuardPages, HANDLE processHandle, bool enableWriteBarrier) :
+    uint maxAllocPageCount, 
+    uint secondaryAllocPageCount,
+    bool stopAllocationOnOutOfMemory, 
+    bool excludeGuardPages, 
+    HANDLE processHandle, 
+    bool enableWriteBarrier
+) :
     policyManager(policyManager),
     pageAllocatorFlagTable(flagTable),
     maxFreePageCount(maxFreePageCount),
@@ -705,6 +710,9 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::PageAllocatorBase(Allo
     , numberOfSegments(0)
     , processHandle(processHandle)
     , enableWriteBarrier(enableWriteBarrier)
+#ifdef ENABLE_BASIC_TELEMETRY
+    ,decommitStats(nullptr)
+#endif
 {
     AssertMsg(Math::IsPow2(maxAllocPageCount + secondaryAllocPageCount), "Illegal maxAllocPageCount: Why is this not a power of 2 aligned?");
 
@@ -1936,9 +1944,20 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
 {
     Assert(!this->HasMultiThreadAccess());
 
-#if DBG_DUMP
-    size_t deleteCount = 0;
+#ifdef ENABLE_BASIC_TELEMETRY
+    if (this->decommitStats != nullptr)
+    {
+        this->decommitStats->numDecommitCalls++;
+        Js::TickDelta delta = Js::Tick::Now() - this->decommitStats->lastLeaveDecommitRegion;
+        if (delta > this->decommitStats->maxDeltaBetweenDecommitRegionLeaveAndDecommit)
+        {
+            this->decommitStats->maxDeltaBetweenDecommitRegionLeaveAndDecommit = delta;
+        }
+    }
 #endif
+
+    size_t deleteCount = 0;
+
 #if ENABLE_BACKGROUND_PAGE_ZEROING
     if (CONFIG_FLAG(EnableBGFreeZero))
     {
@@ -1979,9 +1998,9 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
                     {
                         LogFreePartiallyDecommittedPageSegment(segment);
                         fromSegmentList->RemoveElement(&NoThrowNoMemProtectHeapAllocator::Instance, segment);
-#if DBG_DUMP
+
                         deleteCount += maxAllocPageCount;
-#endif
+
                         continue;
                     }
                 }
@@ -2054,9 +2073,8 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
     PAGE_ALLOC_TRACE_AND_STATS(_u("Free page count = %d"), this->freePageCount);
     PAGE_ALLOC_TRACE_AND_STATS(_u("New free page count = %d"), newFreePageCount);
 
-#if DBG_DUMP
     size_t decommitCount = 0;
-#endif
+
 
     // decommit from page that already has other decommitted page already
     {
@@ -2066,16 +2084,16 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
         {
             size_t pageDecommitted = i.Data().DecommitFreePages(pageToDecommit);
             LogDecommitPages(pageDecommitted);
-#if DBG_DUMP
+
             decommitCount += pageDecommitted;
-#endif
+
             if (i.Data().GetDecommitPageCount() == maxAllocPageCount)
             {
                 LogFreePartiallyDecommittedPageSegment(&i.Data());
                 i.RemoveCurrent(&NoThrowNoMemProtectHeapAllocator::Instance);
-#if DBG_DUMP
+
                 deleteCount += maxAllocPageCount;
-#endif
+
             }
             pageToDecommit -= pageDecommitted;
         }
@@ -2092,18 +2110,18 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
             emptySegments.RemoveHead(&NoThrowNoMemProtectHeapAllocator::Instance);
 
             pageToDecommit -= maxAllocPageCount;
-#if DBG_DUMP
+
             decommitCount += maxAllocPageCount;
             deleteCount += maxAllocPageCount;
-#endif
+
         }
         else
         {
             size_t pageDecommitted = emptySegments.Head().DecommitFreePages(pageToDecommit);
             LogDecommitPages(pageDecommitted);
-#if DBG_DUMP
+
             decommitCount += pageDecommitted;
-#endif
+
             Assert(pageDecommitted == pageToDecommit);
             emptySegments.MoveHeadTo(&decommitSegments);
             pageToDecommit = 0;
@@ -2117,9 +2135,9 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
         {
             size_t pageDecommitted = i.Data().DecommitFreePages(pageToDecommit);
             LogDecommitPages(pageDecommitted);
-#if DBG_DUMP
+
             decommitCount += pageDecommitted;
-#endif
+
             Assert(i.Data().GetDecommitPageCount() != 0);
             Assert(i.Data().GetDecommitPageCount() <= maxAllocPageCount);
             i.MoveCurrentTo(&decommitSegments);
@@ -2128,15 +2146,23 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
         }
     }
 
-
     Assert(pageToDecommit == 0);
 
+
 #if DBG_DUMP
     Assert(this->freePageCount == newFreePageCount + decommitCount);
 #endif
 
     this->freePageCount = newFreePageCount;
 
+#ifdef ENABLE_BASIC_TELEMETRY
+    if (this->decommitStats != nullptr)
+    {
+        this->decommitStats->numPagesDecommitted += decommitCount;
+        this->decommitStats->numFreePageCount += newFreePageCount;
+    }
+#endif
+
 #if DBG
     UpdateMinimum(this->debugMinFreePageCount, this->freePageCount);
     Check();

+ 22 - 0
lib/Common/Memory/PageAllocator.h

@@ -6,6 +6,10 @@
 #include "PageAllocatorDefines.h"
 #include "Exceptions/ExceptionBase.h"
 
+#ifdef ENABLE_BASIC_TELEMETRY
+#include "AllocatorTelemetryStats.h"
+#endif
+
 #ifdef PROFILE_MEM
 struct PageMemoryData;
 #endif
@@ -746,6 +750,12 @@ public:
 #if DBG_DUMP
     char16 const * debugName;
 #endif
+
+#ifdef ENABLE_BASIC_TELEMETRY
+    AllocatorDecommitStats* GetDecommitStats() { return this->decommitStats; }
+    void SetDecommitStats(AllocatorDecommitStats* val) { this->decommitStats = val; }
+#endif
+
 protected:
     void InitVirtualAllocator(TVirtualAlloc * virtualAllocator);
 
@@ -902,6 +912,10 @@ private:
     PageMemoryData * memoryData;
 #endif
 
+#ifdef ENABLE_BASIC_TELEMETRY
+    AllocatorDecommitStats* decommitStats;
+#endif
+
     size_t usedBytes;
     PageAllocatorType type;
 
@@ -944,6 +958,14 @@ private:
     void AddNumberOfSegments(size_t segmentCount);
     void SubNumberOfSegments(size_t segmentCount);
 
+public:
+    size_t GetReservedBytes() const { return this->reservedBytes; };
+    size_t GetCommittedBytes() const { return this->committedBytes; }
+    size_t GetUsedBytes() const { return this->usedBytes; }
+    size_t GetNumberOfSegments() const { return this->numberOfSegments; }
+
+private:
+
     bool RequestAlloc(size_t byteCount)
     {
         if (disableAllocationOutOfMemory)

+ 91 - 53
lib/Common/Memory/Recycler.cpp

@@ -2,6 +2,7 @@
 // Copyright (C) Microsoft. All rights reserved.
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
+
 #include "CommonMemoryPch.h"
 
 #ifdef _M_AMD64
@@ -19,6 +20,7 @@
 #include "Core/BinaryFeatureControl.h"
 #include "Common/ThreadService.h"
 #include "Memory/AutoAllocatorObjectPtr.h"
+#include "Common/Tick.h"
 
 DEFINE_RECYCLER_TRACKER_PERF_COUNTER(RecyclerWeakReferenceBase);
 
@@ -104,8 +106,9 @@ template _ALWAYSINLINE char * Recycler::AllocWithAttributesInlined<NoBit, false>
 template _ALWAYSINLINE char* Recycler::RealAlloc<NoBit, false>(HeapInfo* heap, size_t size);
 template _ALWAYSINLINE _Ret_notnull_ void * __cdecl operator new<Recycler>(size_t byteSize, Recycler * alloc, char * (Recycler::*AllocFunc)(size_t));
 
-Recycler::Recycler(AllocationPolicyManager * policyManager, IdleDecommitPageAllocator * pageAllocator, void (*outOfMemoryFunc)(), Js::ConfigFlagsTable& configFlagsTable) :
-    collectionState(CollectionStateNotCollecting),
+Recycler::Recycler(AllocationPolicyManager * policyManager, IdleDecommitPageAllocator * pageAllocator, void (*outOfMemoryFunc)(), Js::ConfigFlagsTable& configFlagsTable, RecyclerTelemetryHostInterface* hostInterface) :
+    collectionStateChangedObserver(this),
+    collectionState(CollectionStateNotCollecting, &collectionStateChangedObserver),
     recyclerFlagsTable(configFlagsTable),
     autoHeap(policyManager, configFlagsTable, pageAllocator),
 #ifdef ENABLE_JS_ETW
@@ -232,6 +235,9 @@ Recycler::Recycler(AllocationPolicyManager * policyManager, IdleDecommitPageAllo
 #ifdef NTBUILD
     , telemetryBlock(&localTelemetryBlock)
 #endif
+#ifdef ENABLE_BASIC_TELEMETRY
+    , telemetryStats(this, hostInterface)
+#endif
 #ifdef ENABLE_JS_ETW
     ,bulkFreeMemoryWrittenCount(0)
 #endif
@@ -249,6 +255,23 @@ Recycler::Recycler(AllocationPolicyManager * policyManager, IdleDecommitPageAllo
     , trackerCriticalSection(nullptr)
 #endif
 {
+
+#ifdef ENABLE_BASIC_TELEMETRY
+
+    if (CoCreateGuid(&recyclerID) != S_OK)
+    {
+        // CoCreateGuid failed
+        recyclerID = { 0 };
+    }
+
+    this->GetHeapInfo()->GetRecyclerPageAllocator()->SetDecommitStats(this->GetRecyclerTelemetryInfo().GetThreadPageAllocator_decommitStats());
+    this->GetHeapInfo()->GetRecyclerLeafPageAllocator()->SetDecommitStats(this->GetRecyclerTelemetryInfo().GetRecyclerLeafPageAllocator_decommitStats());
+    this->GetHeapInfo()->GetRecyclerLargeBlockPageAllocator()->SetDecommitStats(this->GetRecyclerTelemetryInfo().GetRecyclerLargeBlockPageAllocator_decommitStats());
+#ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
+    this->GetHeapInfo()->GetRecyclerWithBarrierPageAllocator()->SetDecommitStats(this->GetRecyclerTelemetryInfo().GetRecyclerWithBarrierPageAllocator_decommitStats());
+#endif
+#endif
+
 #ifdef RECYCLER_MARK_TRACK
     this->markMap = NoCheckHeapNew(MarkMap, &NoCheckHeapAllocator::Instance, 163, &markMapCriticalSection);
     markContext.SetMarkMap(markMap);
@@ -2024,7 +2047,7 @@ void
 Recycler::ResetMarks(ResetMarkFlags flags)
 {
     Assert(!this->CollectionInProgress());
-    collectionState = CollectionStateResetMarks;
+    this->SetCollectionState(CollectionStateResetMarks);
 
     RecyclerVerboseTrace(GetRecyclerFlagsTable(), _u("Reset marks\n"));
     GCETW(GC_RESETMARKS_START, (this));
@@ -2236,7 +2259,7 @@ Recycler::Mark()
 {
     // Marking in thread, we can just pre-mark them
     ResetMarks(this->enableScanImplicitRoots ? ResetMarkFlags_InThreadImplicitRoots : ResetMarkFlags_InThread);
-    collectionState = CollectionStateFindRoots;
+    this->SetCollectionState(CollectionStateFindRoots);
     RootMark(CollectionStateMark);
 }
 
@@ -2275,7 +2298,7 @@ Recycler::ResetCollectionState()
 {
     Assert(IsMarkStackEmpty());
 
-    this->collectionState = CollectionStateNotCollecting;
+    this->SetCollectionState(CollectionStateNotCollecting);
 #if ENABLE_CONCURRENT_GC
     this->backgroundFinishMarkCount = 0;
 #endif
@@ -2399,7 +2422,7 @@ Recycler::RescanMark(DWORD waitTime)
             this->backgroundFinishMarkCount++;
             this->PrepareSweep();
             GCETW(GC_RESCANMARKWAIT_START, (this, waitTime));
-            const BOOL waited = WaitForConcurrentThread(waitTime);
+            const BOOL waited = WaitForConcurrentThread(waitTime, RecyclerWaitReason::RescanMark);
             GCETW(GC_RESCANMARKWAIT_STOP, (this, !waited));
             if (!waited)
             {
@@ -2414,7 +2437,7 @@ Recycler::RescanMark(DWORD waitTime)
                 return Recycler::InvalidScanRootBytes;
             }
             Assert(collectionState == CollectionStateRescanWait);
-            collectionState = CollectionStateRescanFindRoots;
+            this->SetCollectionState(CollectionStateRescanFindRoots);
 #ifdef RECYCLER_WRITE_WATCH
             if (!CONFIG_FLAG(ForceSoftwareWriteBarrier))
             {
@@ -2525,7 +2548,7 @@ Recycler::DoParallelMark()
     // If we failed, then process the work in-thread now.
     if (concurrentSuccess)
     {
-        WaitForConcurrentThread(INFINITE);
+        WaitForConcurrentThread(INFINITE, RecyclerWaitReason::DoParallelMark);
     }
     else
     {
@@ -2556,7 +2579,7 @@ Recycler::DoParallelMark()
         }
     }
 
-    this->collectionState = CollectionStateMark;
+    this->SetCollectionState(CollectionStateMark);
 
     // Process tracked objects, if any, then do one final mark phase in case they marked any new objects.
     // (Unless it's a partial collect, in which case we don't process tracked objects at all)
@@ -2609,7 +2632,7 @@ Recycler::DoBackgroundParallelMark()
     Assert(this->DoQueueTrackedObject());
 #endif
 
-    this->collectionState = CollectionStateBackgroundParallelMark;
+    this->SetCollectionState(CollectionStateBackgroundParallelMark);
 
     // Kick off marking on parallel threads too, if there is work for them
     // If the threads haven't been created yet, this will create them (or fail).
@@ -2647,7 +2670,7 @@ Recycler::DoBackgroundParallelMark()
         }
     }
 
-    this->collectionState = CollectionStateConcurrentMark;
+    this->SetCollectionState(CollectionStateConcurrentMark);
 }
 #endif
 
@@ -2680,7 +2703,7 @@ Recycler::RootMark(CollectionState markState)
         scannedRootBytes += ScanStack();
     }
 
-    this->collectionState = markState;
+    this->SetCollectionState(markState);
 
 #if ENABLE_CONCURRENT_GC
     if (this->enableParallelMark)
@@ -2815,7 +2838,7 @@ Recycler::EndMarkOnLowMemory()
         }
 #endif
 
-        this->collectionState = CollectionStateRescanFindRoots;
+        this->SetCollectionState(CollectionStateRescanFindRoots);
 
         this->ClearNeedOOMRescan();
 
@@ -3035,7 +3058,7 @@ Recycler::Sweep(bool concurrent)
 
     RECYCLER_PROFILE_EXEC_END(this, concurrent? Js::ConcurrentSweepPhase : Js::SweepPhase);
 
-    this->collectionState = CollectionStatePostSweepRedeferralCallback;
+    this->SetCollectionState(CollectionStatePostSweepRedeferralCallback);
     // Note that PostSweepRedeferralCallback can't have exception escape.
     collectionWrapper->PostSweepRedeferralCallBack();
 
@@ -3054,7 +3077,7 @@ Recycler::Sweep(bool concurrent)
                 this->allowAllocationsDuringConcurrentSweepForCollection = false;
             }
 #endif
-            this->collectionState = CollectionStateConcurrentSweep;
+            this->SetCollectionState(CollectionStateConcurrentSweep);
 
             DoBackgroundWork(true);
             // Continue as if the concurrent sweep were executing
@@ -3218,7 +3241,7 @@ Recycler::SweepHeap(bool concurrent, RecyclerSweepManager& recyclerSweepManager)
     Assert(!this->DoQueueTrackedObject());
     if (concurrent)
     {
-        collectionState = CollectionStateSetupConcurrentSweep;
+        SetCollectionState(CollectionStateSetupConcurrentSweep);
 
 #if ENABLE_BACKGROUND_PAGE_ZEROING
         if (CONFIG_FLAG(EnableBGFreeZero))
@@ -3231,7 +3254,7 @@ Recycler::SweepHeap(bool concurrent, RecyclerSweepManager& recyclerSweepManager)
 #endif
     {
         Assert(!concurrent);
-        collectionState = CollectionStateSweep;
+        SetCollectionState(CollectionStateSweep);
     }
 
     this->SweepWeakReference();
@@ -3397,7 +3420,7 @@ Recycler::FinishDisposeObjects()
     if (!this->inDispose && this->hasDisposableObject
         && GetRecyclerFlagsTable().Trace.IsEnabled(Js::RecyclerPhase))
     {
-        Output::Print(_u("%04X> RC(%p): %s %d\n"), this->mainThreadId, this, _u("Dispose object delayed"), this->collectionState);
+        Output::Print(_u("%04X> RC(%p): %s %d\n"), this->mainThreadId, this, _u("Dispose object delayed"), static_cast<CollectionState>(this->collectionState));
     }
 #endif
     return false;
@@ -3932,9 +3955,9 @@ Recycler::DoCollect(CollectionFlags flags)
 #if DBG || defined RECYCLER_TRACE
         collectionCount++;
 #endif
-        collectionState = Collection_PreCollection;
+        this->SetCollectionState(Collection_PreCollection);
         collectionWrapper->PreCollectionCallBack(flags);
-        collectionState = CollectionStateNotCollecting;
+        this->SetCollectionState(CollectionStateNotCollecting);
 
         hasExhaustiveCandidate = false;         // reset the candidate detection
 
@@ -4116,7 +4139,7 @@ Recycler::PartialCollect(bool concurrent)
     Assert(this->inPartialCollectMode);
     Assert(collectionState == CollectionStateNotCollecting);
     // Rescan again
-    collectionState = CollectionStateRescanFindRoots;
+    this->SetCollectionState(CollectionStateRescanFindRoots);
 #if ENABLE_CONCURRENT_GC
     if (concurrent && enableConcurrentMark && this->partialConcurrentNextCollection)
     {
@@ -4446,11 +4469,11 @@ Recycler::RequestConcurrentWrapperCallback()
     if (StartConcurrent(CollectionStateConcurrentWrapperCallback))
     {
         // Wait for the callback to complete
-        WaitForConcurrentThread(INFINITE);
+        WaitForConcurrentThread(INFINITE, RecyclerWaitReason::RequestConcurrentCallbackWrapper);
 
         // The state must not change back until we restore the original state
         Assert(collectionState == CollectionStateConcurrentWrapperCallback);
-        this->collectionState = oldState;
+        this->SetCollectionState(oldState);
 
         return true;
     }
@@ -4481,7 +4504,7 @@ Recycler::CollectOnConcurrentThread()
 
     const DWORD waitTime = RecyclerHeuristic::FinishConcurrentCollectWaitTime(this->GetRecyclerFlagsTable());
     GCETW(GC_SYNCHRONOUSMARKWAIT_START, (this, waitTime));
-    const BOOL waited = WaitForConcurrentThread(waitTime);
+    const BOOL waited = WaitForConcurrentThread(waitTime, RecyclerWaitReason::CollectOnConcurrentThread);
     GCETW(GC_SYNCHRONOUSMARKWAIT_STOP, (this, !waited));
     if (!waited)
     {
@@ -4509,7 +4532,7 @@ Recycler::CollectOnConcurrentThread()
     // Assert(markContext.Empty());
     DebugOnly(this->isProcessingRescan = false);
 
-    this->collectionState = CollectionStateMark;
+    this->SetCollectionState(CollectionStateMark);
     this->ProcessTrackedObjects();
     this->ProcessMark(false);
     this->EndMark();
@@ -4832,8 +4855,9 @@ bool Recycler::AbortConcurrent(bool restoreState)
 
                 this->FinishSweepPrep();
                 this->FinishConcurrentSweepPass1();
-                this->collectionState = CollectionStateConcurrentSweepPass2;
+                this->SetCollectionState(CollectionStateConcurrentSweepPass2);
                 this->recyclerSweepManager->FinishSweep();
+
                 this->FinishConcurrentSweep();
                 this->recyclerSweepManager->EndBackground();
 
@@ -4844,7 +4868,7 @@ bool Recycler::AbortConcurrent(bool restoreState)
 
                 GCETW(GC_BACKGROUNDSWEEP_STOP, (this, sweptBytes));
 
-                this->collectionState = CollectionStateTransferSweptWait;
+                this->SetCollectionState(CollectionStateTransferSweptWait);
                 RECYCLER_PROFILE_EXEC_BACKGROUND_END(this, Js::ConcurrentSweepPhase);
 
                 // AbortConcurrent already consumed the event from the concurrent thread, just signal it so
@@ -4952,7 +4976,7 @@ Recycler::FinalizeConcurrent(bool restoreState)
 #endif
 
     bool aborted = AbortConcurrent(needCleanExitState);
-    collectionState = CollectionStateExit;
+    SetCollectionState(CollectionStateExit);
     if (aborted && this->concurrentThread != NULL)
     {
         // In case the thread already died, wait for that too
@@ -5151,7 +5175,7 @@ Recycler::DisableConcurrent()
         Assert(concurrentThread != NULL || threadService->HasCallback());
 
         FinalizeConcurrent(true);
-        this->collectionState = CollectionStateNotCollecting;
+        this->SetCollectionState(CollectionStateNotCollecting);
     }
 }
 
@@ -5162,7 +5186,7 @@ Recycler::StartConcurrent(CollectionState const state)
     tickCountStartConcurrent = GetTickCount();
 
     CollectionState oldState = this->collectionState;
-    this->collectionState = state;
+    this->SetCollectionState(state);
 
     if (threadService->HasCallback())
     {
@@ -5171,7 +5195,7 @@ Recycler::StartConcurrent(CollectionState const state)
 
         if (!threadService->Invoke(Recycler::StaticBackgroundWorkCallback, this))
         {
-            this->collectionState = oldState;
+            this->SetCollectionState(oldState);
             return false;
         }
 
@@ -5237,7 +5261,7 @@ Recycler::StartBackgroundMark(bool foregroundResetMark, bool foregroundFindRoots
 
         if (foregroundFindRoots)
         {
-            this->collectionState = CollectionStateFindRoots;
+            this->SetCollectionState(CollectionStateFindRoots);
             FindRoots();
             ScanStack();
             Assert(collectionState == CollectionStateFindRoots);
@@ -5263,7 +5287,6 @@ Recycler::StartBackgroundMark(bool foregroundResetMark, bool foregroundFindRoots
             this->RevertPrepareBackgroundFindRoots();
         }
         this->collectionState = CollectionStateNotCollecting;
-
 #ifdef ENABLE_JS_ETW
         collectionFinishReason = ETWEventGCActivationTrigger::ETWEvent_GC_Trigger_Status_Failed;
 #endif
@@ -5593,7 +5616,7 @@ Recycler::BackgroundFindRoots()
 
     RECYCLER_PROFILE_EXEC_BACKGROUND_END(this, Js::BackgroundFindRootsPhase);
     this->hasPendingConcurrentFindRoot = false;
-    this->collectionState = CollectionStateConcurrentMark;
+    this->SetCollectionState(CollectionStateConcurrentMark);
 
     GCETW(GC_BACKGROUNDSCANROOTS_STOP, (this));
     RECYCLER_STATS_ADD(this, rootCount, this->collectionStats.markData.markCount - lastMarkCount);
@@ -5611,9 +5634,9 @@ Recycler::BackgroundFinishMark()
 #endif
     Assert(collectionState == CollectionStateConcurrentFinishMark);
     size_t rescannedRootBytes = FinishMarkRescan(true) * AutoSystemInfo::PageSize;
-    this->collectionState = CollectionStateConcurrentFindRoots;
+    this->SetCollectionState(CollectionStateConcurrentFindRoots);
     rescannedRootBytes += this->BackgroundFindRoots();
-    this->collectionState = CollectionStateConcurrentFinishMark;
+    this->SetCollectionState(CollectionStateConcurrentFinishMark);
     RECYCLER_PROFILE_EXEC_BACKGROUND_BEGIN(this, Js::MarkPhase);
     ProcessMark(true);
     RECYCLER_PROFILE_EXEC_BACKGROUND_END(this, Js::MarkPhase);
@@ -5664,7 +5687,7 @@ Recycler::FinishConcurrentCollectWrapped(CollectionFlags flags)
 }
 
 BOOL
-Recycler::WaitForConcurrentThread(DWORD waitTime)
+Recycler::WaitForConcurrentThread(DWORD waitTime, RecyclerWaitReason caller)
 {
     Assert(this->IsConcurrentState() || this->collectionState == CollectionStateParallelMark);
 
@@ -5676,8 +5699,22 @@ Recycler::WaitForConcurrentThread(DWORD waitTime)
         SetThreadPriority(this->concurrentThread, THREAD_PRIORITY_NORMAL);
     }
 
+#ifdef ENABLE_BASIC_TELEMETRY
+    bool isBlockingMainThread = this->telemetryStats.IsOnScriptThread();
+    Js::Tick start = Js::Tick::Now();
+#endif
+
     DWORD ret = WaitForSingleObject(concurrentWorkDoneEvent, waitTime);
 
+#ifdef ENABLE_BASIC_TELEMETRY
+    if (isBlockingMainThread)
+    {
+        Js::Tick end = Js::Tick::Now();
+        Js::TickDelta elapsed = end - start;
+        this->telemetryStats.IncrementUserThreadBlockedCount(elapsed.ToMicroseconds(), caller);
+    }
+#endif
+
     if (concurrentThread != NULL)
     {
         if (ret == WAIT_TIMEOUT)
@@ -5781,7 +5818,7 @@ Recycler::FinishConcurrentCollect(CollectionFlags flags)
         PrintCollectTrace(Js::ConcurrentMarkPhase, true);
 #endif
 #endif
-        collectionState = CollectionStateRescanFindRoots;
+        SetCollectionState(CollectionStateRescanFindRoots);
 
 #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
         // TODO: Change this behavior
@@ -5840,7 +5877,7 @@ Recycler::FinishConcurrentCollect(CollectionFlags flags)
         if (forceInThread)
         {
             this->FinishConcurrentSweepPass1();
-            this->collectionState = CollectionStateConcurrentSweepPass2;
+            this->SetCollectionState(CollectionStateConcurrentSweepPass2);
 #ifdef RECYCLER_TRACE
             if (this->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
             {
@@ -5858,7 +5895,7 @@ Recycler::FinishConcurrentCollect(CollectionFlags flags)
 
             GCETW(GC_BACKGROUNDSWEEP_STOP, (this, sweptBytes));
 
-            this->collectionState = CollectionStateTransferSweptWait;
+            this->SetCollectionState(CollectionStateTransferSweptWait);
             RECYCLER_PROFILE_EXEC_BACKGROUND_END(this, Js::ConcurrentSweepPhase);
 
             FinishTransferSwept(flags);
@@ -5907,7 +5944,7 @@ Recycler::FinishTransferSwept(CollectionFlags flags)
 #ifdef RECYCLER_TRACE
     PrintCollectTrace(Js::ConcurrentSweepPhase, true);
 #endif
-    collectionState = CollectionStateTransferSwept;
+    SetCollectionState(CollectionStateTransferSwept);
 
 #if ENABLE_BACKGROUND_PAGE_FREEING
         if (CONFIG_FLAG(EnableBGFreeZero))
@@ -6043,12 +6080,12 @@ Recycler::DoBackgroundWork(bool forceForeground)
         case CollectionStateConcurrentResetMarks:
             this->BackgroundResetMarks();
             this->BackgroundResetWriteWatchAll();
-            this->collectionState = CollectionStateConcurrentFindRoots;
+            this->SetCollectionState(CollectionStateConcurrentFindRoots);
             // fall-through
         case CollectionStateConcurrentFindRoots:
             this->BackgroundFindRoots();
             this->BackgroundScanStack();
-            this->collectionState = CollectionStateConcurrentMark;
+            this->SetCollectionState(CollectionStateConcurrentMark);
             // fall-through
         case CollectionStateConcurrentMark:
             this->BackgroundMark();
@@ -6072,7 +6109,7 @@ Recycler::DoBackgroundWork(bool forceForeground)
         RECYCLER_PROFILE_EXEC_BACKGROUND_END(this, this->collectionState == CollectionStateConcurrentFinishMark ?
             Js::BackgroundFinishMarkPhase : Js::ConcurrentMarkPhase);
 
-        this->collectionState = CollectionStateRescanWait;
+        this->SetCollectionState(CollectionStateRescanWait);
         DebugOnly(this->markContext.GetPageAllocator()->ClearConcurrentThreadId());
     }
     else
@@ -6088,7 +6125,7 @@ Recycler::DoBackgroundWork(bool forceForeground)
 
                 if (this->AllowAllocationsDuringConcurrentSweep())
                 {
-                    this->collectionState = CollectionStateConcurrentSweepPass1;
+                    this->SetCollectionState(CollectionStateConcurrentSweepPass1);
                 }
             }
 
@@ -6149,7 +6186,7 @@ Recycler::DoBackgroundWork(bool forceForeground)
 #if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
             if (CONFIG_FLAG_RELEASE(EnableConcurrentSweepAlloc) && this->AllowAllocationsDuringConcurrentSweep())
             {
-                this->collectionState = CollectionStateConcurrentSweepPass1Wait;
+                this->SetCollectionState(CollectionStateConcurrentSweepPass1Wait);
             }
 #endif
         }
@@ -6179,7 +6216,7 @@ Recycler::DoBackgroundWork(bool forceForeground)
                 this->FinishConcurrentSweep();
                 this->recyclerSweepManager->EndBackground();
 
-                this->collectionState = CollectionStateConcurrentSweepPass2Wait;
+                this->SetCollectionState(CollectionStateConcurrentSweepPass2Wait);
             }
         }
 #endif
@@ -6218,7 +6255,7 @@ Recycler::DoBackgroundWork(bool forceForeground)
                 GCETW_INTERNAL(GC_STOP, (this, ETWEvent_ConcurrentSweep));
                 GCETW_INTERNAL(GC_STOP2, (this, ETWEvent_ConcurrentSweep, this->collectionStartReason, this->collectionStartFlags));
             }
-            this->collectionState = CollectionStateTransferSweptWait;
+            this->SetCollectionState(CollectionStateTransferSweptWait);
         }
 
         RECYCLER_PROFILE_EXEC_BACKGROUND_END(this, Js::ConcurrentSweepPhase);
@@ -6498,6 +6535,7 @@ Recycler::FinishCollection()
     }
 #endif
 
+
 #ifdef RECYCLER_STATS
     if (CUSTOM_PHASE_STATS1(this->GetRecyclerFlagsTable(), Js::RecyclerPhase))
     {
@@ -7123,7 +7161,7 @@ Recycler::PrintBlockStatus(HeapBucket * heapBucket, HeapBlock * heapBlock, char1
 {
     if (this->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && CONFIG_FLAG_RELEASE(Verbose))
     {
-        Output::Print(_u("[GC #%d] [HeapBucket 0x%p] HeapBlock 0x%p %s [CollectionState: %d] \n"), this->collectionCount, heapBucket, heapBlock, statusMessage, this->collectionState);
+        Output::Print(_u("[GC #%d] [HeapBucket 0x%p] HeapBlock 0x%p %s [CollectionState: %d] \n"), this->collectionCount, heapBucket, heapBlock, statusMessage, static_cast<CollectionState>(this->collectionState));
     }
 }
 #endif
@@ -7830,7 +7868,7 @@ void Recycler::AutoSetupRecyclerForNonCollectingMark::SetupForHeapEnumeration()
     m_recycler.EnsureNotCollecting();
     DoCommonSetup();
     m_recycler.ResetMarks(ResetMarkFlags_HeapEnumeration);
-    m_recycler.collectionState = CollectionStateNotCollecting;
+    m_recycler.SetCollectionState(CollectionStateNotCollecting);
     m_recycler.isHeapEnumInProgress = true;
     m_recycler.isCollectionDisabled = true;
 }
@@ -7842,7 +7880,7 @@ Recycler::AutoSetupRecyclerForNonCollectingMark::~AutoSetupRecyclerForNonCollect
 #ifdef RECYCLER_STATS
     m_recycler.collectionStats = m_previousCollectionStats;
 #endif
-    m_recycler.collectionState = m_previousCollectionState;
+    m_recycler.SetCollectionState(m_previousCollectionState);
     m_recycler.isHeapEnumInProgress = false;
     m_recycler.isCollectionDisabled = false;
 }
@@ -7854,7 +7892,7 @@ bool Recycler::DumpObjectGraph(RecyclerObjectGraphDumper::Param * param)
     bool isExited = (this->collectionState == CollectionStateExit);
     if (isExited)
     {
-        this->collectionState = CollectionStateNotCollecting;
+        this->SetCollectionState(CollectionStateNotCollecting);
     }
     if (this->collectionState != CollectionStateNotCollecting)
     {
@@ -7884,7 +7922,7 @@ bool Recycler::DumpObjectGraph(RecyclerObjectGraphDumper::Param * param)
 
     if (isExited)
     {
-        this->collectionState = CollectionStateExit;
+        this->SetCollectionState(CollectionStateExit);
     }
 
     if (!succeeded)

+ 73 - 5
lib/Common/Memory/Recycler.h

@@ -5,6 +5,9 @@
 #pragma once
 
 #include "CollectionState.h"
+#include "RecyclerTelemetryInfo.h"
+#include "RecyclerWaitReason.h"
+#include "Common/ObservableValue.h"
 
 namespace Js
 {
@@ -710,6 +713,10 @@ class Recycler
 #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
     friend class AutoProtectPages;
 #endif
+#ifdef ENABLE_BASIC_TELEMETRY
+    friend class RecyclerTelemetryInfo;
+#endif
+
 
     template <typename T> friend class RecyclerWeakReference;
     template <typename T> friend class WeakReferenceHashTable;
@@ -779,12 +786,12 @@ private:
             _recycler(recycler),
             _exitState(exitState)
         {
-            _recycler->collectionState = entryState;
+            _recycler->SetCollectionState(entryState);
         }
 
         ~AutoSwitchCollectionStates()
         {
-            _recycler->collectionState = _exitState;
+            _recycler->SetCollectionState(_exitState);
         }
 
     private:
@@ -798,7 +805,39 @@ private:
     uint collectionFinishReason;
 #endif
 
-    CollectionState collectionState;
+    class CollectionStateChangedObserver : public ObservableValueObserver<CollectionState>
+    {
+    private:
+        Recycler* recycler;
+    public:
+        CollectionStateChangedObserver(Recycler* recycler)
+        {
+            this->recycler = recycler;
+        }
+
+        virtual void ValueChanged(const CollectionState& newVal, const CollectionState& oldVal)
+        {
+#ifdef ENABLE_BASIC_TELEMETRY
+            if (oldVal == CollectionState::CollectionStateNotCollecting && newVal != CollectionState::CollectionStateNotCollecting && newVal != CollectionState::Collection_PreCollection)
+            {
+                this->recycler->GetRecyclerTelemetryInfo().StartPass();
+            }
+            else if (oldVal != CollectionState::CollectionStateNotCollecting && oldVal != CollectionState::Collection_PreCollection && newVal == CollectionState::CollectionStateNotCollecting)
+            {
+                this->recycler->GetRecyclerTelemetryInfo().EndPass();
+            }
+#endif
+        }
+    };
+
+    CollectionStateChangedObserver collectionStateChangedObserver;
+    ObservableValue<CollectionState> collectionState;
+
+    inline void SetCollectionState(CollectionState newState)
+    {
+        this->collectionState = newState;
+    }
+
     JsUtil::ThreadService *threadService;
 #if ENABLE_ALLOCATIONS_DURING_CONCURRENT_SWEEP
     bool allowAllocationsDuringConcurrentSweepForCollection;
@@ -1126,6 +1165,11 @@ private:
         return this->autoHeap.GetDefaultHeap();
     }
 
+    HeapInfo * GetHeapInfo()
+    {
+        return this->autoHeap.GetDefaultHeap();
+    }
+
 #ifdef PROFILE_MEM
     RecyclerMemoryData * memoryData;
 #endif
@@ -1142,6 +1186,22 @@ private:
     RecyclerWatsonTelemetryBlock localTelemetryBlock;
     RecyclerWatsonTelemetryBlock * telemetryBlock;
 #endif
+
+#ifdef ENABLE_BASIC_TELEMETRY
+private:
+    RecyclerTelemetryInfo telemetryStats;
+    GUID recyclerID;
+public:
+    GUID& GetRecyclerID() { return this->recyclerID; }
+#endif
+  
+
+public:
+    bool GetIsInScript() { return this->isInScript; }
+    bool GetIsScriptActive() { return this->isScriptActive; }
+
+private:
+
 #ifdef RECYCLER_STATS
     RecyclerCollectionStats collectionStats;
     void PrintHeapBlockStats(char16 const * name, HeapBlock::HeapBlockType type);
@@ -1170,7 +1230,8 @@ public:
 #endif
 public:
 
-    Recycler(AllocationPolicyManager * policyManager, IdleDecommitPageAllocator * pageAllocator, void(*outOfMemoryFunc)(), Js::ConfigFlagsTable& flags);
+    Recycler(AllocationPolicyManager * policyManager, IdleDecommitPageAllocator * pageAllocator, void(*outOfMemoryFunc)(), Js::ConfigFlagsTable& flags, RecyclerTelemetryHostInterface* hostInterface);
+
     ~Recycler();
 
     void Initialize(const bool forceInThread, JsUtil::ThreadService *threadService, const bool deferThreadStartup = false
@@ -1785,7 +1846,10 @@ private:
 
     template <CollectionFlags flags>
     BOOL TryFinishConcurrentCollect();
-    BOOL WaitForConcurrentThread(DWORD waitTime);
+
+    BOOL WaitForConcurrentThread(DWORD waitTime, RecyclerWaitReason caller = RecyclerWaitReason::Other);
+    void FlushBackgroundPages();
+
     BOOL FinishConcurrentCollect(CollectionFlags flags);
     void FinishTransferSwept(CollectionFlags flags);
     BOOL FinishConcurrentCollectWrapped(CollectionFlags flags);
@@ -1882,6 +1946,10 @@ private:
     // while we have debug only flag for each of the two scenarios.
     bool isCollectionDisabled;
 
+#ifdef ENABLE_BASIC_TELEMETRY
+    RecyclerTelemetryInfo& GetRecyclerTelemetryInfo() { return this->telemetryStats; }
+#endif
+
 #ifdef TRACK_ALLOC
 public:
     Recycler * TrackAllocInfo(TrackAllocData const& data);

+ 276 - 0
lib/Common/Memory/RecyclerTelemetryInfo.cpp

@@ -0,0 +1,276 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#include "CommonMemoryPch.h"
+
+
+#ifdef ENABLE_BASIC_TELEMETRY
+
+#include "Recycler.h"
+#include "HeapBucketStats.h"
+#include "BucketStatsReporter.h"
+#include <psapi.h>
+
+namespace Memory
+{
+#ifdef DBG
+#define AssertOnValidThread(recyclerTelemetryInfo, loc) { AssertMsg(this->IsOnScriptThread(), STRINGIZE("Unexpected thread ID at " ## loc)); }
+#else
+#define AssertOnValidThread(recyclerTelemetryInfo, loc)
+#endif
+
+    RecyclerTelemetryInfo::RecyclerTelemetryInfo(Recycler * recycler, RecyclerTelemetryHostInterface* hostInterface) :
+        passCount(0),
+        hostInterface(hostInterface),
+        lastPassStats(nullptr),
+        recyclerStartTime(Js::Tick::Now()),
+        abortTelemetryCapture(false)
+    {
+        this->recycler = recycler;
+        mainThreadID = ::GetCurrentThreadId();
+    }
+
+    RecyclerTelemetryInfo::~RecyclerTelemetryInfo()
+    {
+        AssertOnValidThread(this, RecyclerTelemetryInfo::~RecyclerTelemetryInfo);
+        if (this->hostInterface != nullptr && this->passCount > 0)
+        {
+            this->hostInterface->TransmitTelemetry(*this);
+        }
+        this->FreeGCPassStats();
+    }
+
+    const GUID& RecyclerTelemetryInfo::GetRecyclerID() const
+    {
+        return this->recycler->GetRecyclerID();
+    }
+
+    bool RecyclerTelemetryInfo::GetIsConcurrentEnabled() const
+    {
+        return this->recycler->IsConcurrentEnabled();
+    }
+
+    bool RecyclerTelemetryInfo::ShouldCaptureRecyclerTelemetry() const
+    {
+        return this->hostInterface != nullptr && this->abortTelemetryCapture == false;
+    }
+
+    void RecyclerTelemetryInfo::FillInSizeData(IdleDecommitPageAllocator* allocator, AllocatorSizes* sizes) const
+    {
+        sizes->committedBytes = allocator->GetCommittedBytes();
+        sizes->reservedBytes = allocator->GetReservedBytes();
+        sizes->usedBytes = allocator->GetUsedBytes();
+        sizes->numberOfSegments = allocator->GetNumberOfSegments();
+    }
+
+    void RecyclerTelemetryInfo::StartPass()
+    {
+        Js::Tick start = Js::Tick::Now();
+        if (!this->ShouldCaptureRecyclerTelemetry())
+        {
+            return;
+        }
+
+        AssertOnValidThread(this, RecyclerTelemetryInfo::StartPass);
+#if DBG
+        // validate state of existing GC pass stats structs
+        uint16 count = 0;
+        if (this->lastPassStats != nullptr)
+        {
+            RecyclerTelemetryGCPassStats* head = this->lastPassStats->next;
+            RecyclerTelemetryGCPassStats* curr = head;
+            do
+            {
+                AssertMsg(curr->isGCPassActive == false, "unexpected value for isGCPassActive");
+                count++;
+                curr = curr->next;
+            } while (curr != head);
+        }
+        AssertMsg(count == this->passCount, "RecyclerTelemetryInfo::StartPass() - mismatch between passCount and count.");
+#endif
+
+        RecyclerTelemetryGCPassStats* p = HeapNewNoThrow(RecyclerTelemetryGCPassStats);
+        if (p == nullptr)
+        {
+            // failed to allocate memory - disable any further telemetry capture for this recycler 
+            // and free any existing GC stats we've accumulated
+            this->abortTelemetryCapture = true;
+            FreeGCPassStats();
+            this->hostInterface->TransmitTelemetryError(*this, "Memory Allocation Failed");
+        }
+        else
+        {
+            passCount++;
+            memset(p, 0, sizeof(RecyclerTelemetryGCPassStats));
+            if (this->lastPassStats == nullptr)
+            {
+                p->next = p;
+            }
+            else
+            {
+                p->next = lastPassStats->next;
+                this->lastPassStats->next = p;
+            }
+            this->lastPassStats = p;
+
+            this->lastPassStats->isGCPassActive = true;
+            this->lastPassStats->passStartTimeTick = Js::Tick::Now();
+            GetSystemTimePreciseAsFileTime(&this->lastPassStats->passStartTimeFileTime);
+            if (this->hostInterface != nullptr)
+            {
+                LPFILETIME ft = this->hostInterface->GetLastScriptExecutionEndTime();
+                this->lastPassStats->lastScriptExecutionEndTime = *ft;
+            }
+
+            this->lastPassStats->processCommittedBytes_start = RecyclerTelemetryInfo::GetProcessCommittedBytes();
+            this->lastPassStats->processAllocaterUsedBytes_start = PageAllocator::GetProcessUsedBytes();
+            this->lastPassStats->isInScript = this->recycler->GetIsInScript();
+            this->lastPassStats->isScriptActive = this->recycler->GetIsScriptActive();
+
+            this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLeafPageAllocator(), &this->lastPassStats->threadPageAllocator_start);
+            this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerPageAllocator(), &this->lastPassStats->recyclerLeafPageAllocator_start);
+            this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLargeBlockPageAllocator(), &this->lastPassStats->recyclerLargeBlockPageAllocator_start);
+#ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
+            this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerWithBarrierPageAllocator(), &this->lastPassStats->recyclerWithBarrierPageAllocator_start);
+#endif
+            this->lastPassStats->startPassProcessingElapsedTime = Js::Tick::Now() - start;
+        }
+
+
+    }
+
+    void RecyclerTelemetryInfo::EndPass()
+    {
+        if (!this->ShouldCaptureRecyclerTelemetry())
+        {
+            return;
+        }
+
+        Js::Tick start = Js::Tick::Now();
+
+        AssertOnValidThread(this, RecyclerTelemetryInfo::EndPass);
+
+        this->lastPassStats->isGCPassActive = false;
+        this->lastPassStats->passEndTimeTick = Js::Tick::Now();
+
+        this->lastPassStats->processCommittedBytes_end = RecyclerTelemetryInfo::GetProcessCommittedBytes();
+        this->lastPassStats->processAllocaterUsedBytes_end = PageAllocator::GetProcessUsedBytes();
+
+        this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLeafPageAllocator(), &this->lastPassStats->threadPageAllocator_end);
+        this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerPageAllocator(), &this->lastPassStats->recyclerLeafPageAllocator_end);
+        this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLargeBlockPageAllocator(), &this->lastPassStats->recyclerLargeBlockPageAllocator_end);
+#ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
+        this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerWithBarrierPageAllocator(), &this->lastPassStats->recyclerWithBarrierPageAllocator_end);
+#endif
+
+        // get bucket stats
+        Js::Tick bucketStatsStart = Js::Tick::Now();
+        BucketStatsReporter bucketReporter(this->recycler);
+        this->recycler->GetHeapInfo()->GetBucketStats(bucketReporter);
+        memcpy(&this->lastPassStats->bucketStats, bucketReporter.GetTotalStats(), sizeof(HeapBucketStats));
+        this->lastPassStats->computeBucketStatsElapsedTime = Js::Tick::Now() - bucketStatsStart;
+
+        this->lastPassStats->endPassProcessingElapsedTime = Js::Tick::Now() - start;
+
+        if (ShouldTransmit() && this->hostInterface != nullptr)
+        {
+            if (this->hostInterface->TransmitTelemetry(*this))
+            {
+                this->lastTransmitTime = this->lastPassStats->passEndTimeTick;
+                Reset();
+            }
+        }
+    }
+
+    void RecyclerTelemetryInfo::Reset()
+    {
+        FreeGCPassStats();
+        memset(&this->threadPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
+        memset(&this->recyclerLeafPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
+        memset(&this->recyclerLargeBlockPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
+        memset(&this->threadPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
+    }
+
+    void RecyclerTelemetryInfo::FreeGCPassStats()
+    {
+        AssertOnValidThread(this, RecyclerTelemetryInfo::FreeGCPassStats);
+
+        if (this->lastPassStats != nullptr)
+        {
+            RecyclerTelemetryGCPassStats* head = this->lastPassStats->next;
+            RecyclerTelemetryGCPassStats* curr = head;
+#ifdef DBG
+            uint16 freeCount = 0;
+#endif
+            do
+            {
+                RecyclerTelemetryGCPassStats* next = curr->next;
+                HeapDelete(curr);
+                curr = next;
+#ifdef DBG
+                freeCount++;
+#endif
+            } while (curr != head);
+#ifdef DBG
+            AssertMsg(freeCount == this->passCount, "Unexpected mismatch between freeCount and passCount");
+#endif
+            this->lastPassStats = nullptr;
+            this->passCount = 0;
+        }
+    }
+
+    bool RecyclerTelemetryInfo::ShouldTransmit()
+    {
+        // for now, try to transmit telemetry when we have >= 16
+        return (this->hostInterface != nullptr &&  this->passCount >= 16);
+    }
+
+    void RecyclerTelemetryInfo::IncrementUserThreadBlockedCount(Js::TickDelta waitTime, RecyclerWaitReason caller)
+    {
+#ifdef DBG
+        if (this->ShouldCaptureRecyclerTelemetry())
+        {
+            AssertMsg(this->lastPassStats != nullptr && this->lastPassStats->isGCPassActive == true, "unexpected Value in  RecyclerTelemetryInfo::IncrementUserThreadBlockedCount");
+        }
+#endif
+
+        if (this->ShouldCaptureRecyclerTelemetry() && this->lastPassStats != nullptr)
+        {
+            AssertOnValidThread(this, RecyclerTelemetryInfo::IncrementUserThreadBlockedCount);
+            this->lastPassStats->uiThreadBlockedTimes[caller] += waitTime;
+        }
+    }
+
+    bool RecyclerTelemetryInfo::IsOnScriptThread() const
+    {
+        bool isValid = false;
+        if (this->hostInterface != nullptr)
+        {
+            if (this->hostInterface->IsThreadBound())
+            {
+                isValid = ::GetCurrentThreadId() == this->hostInterface->GetCurrentScriptThreadID();
+            }
+            else
+            {
+                isValid = ::GetCurrentThreadId() == this->mainThreadID;
+            }
+        }
+        return isValid;
+    }
+
+    size_t RecyclerTelemetryInfo::GetProcessCommittedBytes()
+    {
+        HANDLE process = GetCurrentProcess();
+        PROCESS_MEMORY_COUNTERS memCounters = { 0 };
+        size_t committedBytes = 0;
+        if (GetProcessMemoryInfo(process, &memCounters, sizeof(PROCESS_MEMORY_COUNTERS)))
+        {
+            committedBytes = memCounters.PagefileUsage;
+        }
+        return committedBytes;
+    }
+
+}
+#endif

+ 140 - 0
lib/Common/Memory/RecyclerTelemetryInfo.h

@@ -0,0 +1,140 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+#include "CommonDefines.h"
+#include "RecyclerWaitReason.h"
+#include "Common/Tick.h"
+#include "AllocatorTelemetryStats.h"
+#include "HeapBucketStats.h"
+
+#ifdef ENABLE_BASIC_TELEMETRY
+#include "Windows.h"
+#endif
+
+namespace Memory
+{
+    class Recycler;
+    class IdleDecommitPageAllocator;
+    class RecyclerTelemetryInfo;
+
+    // struct that defines an interface with host on how we can get key data stats
+    class RecyclerTelemetryHostInterface
+    {
+    public:
+        virtual LPFILETIME GetLastScriptExecutionEndTime() const = 0;
+        virtual bool TransmitTelemetry(RecyclerTelemetryInfo& rti) = 0;
+        virtual bool TransmitTelemetryError(const RecyclerTelemetryInfo& rti, const char* msg) = 0;
+        virtual bool IsThreadBound() const = 0;
+        virtual DWORD GetCurrentScriptThreadID() const = 0;
+    };
+
+#ifdef ENABLE_BASIC_TELEMETRY
+
+    /**
+     *  struct with all data we want to capture for a specific GC pass.
+     *
+     *  This forms a linked list, one for each per-stats pass.
+     *  The last element in the list points to the head instead of nullptr.
+     */
+    struct RecyclerTelemetryGCPassStats
+    {
+        FILETIME passStartTimeFileTime;
+        Js::Tick passStartTimeTick;
+        Js::Tick passEndTimeTick;
+        Js::TickDelta startPassProcessingElapsedTime;
+        Js::TickDelta endPassProcessingElapsedTime;
+        Js::TickDelta computeBucketStatsElapsedTime;
+        FILETIME lastScriptExecutionEndTime;
+        RecyclerTelemetryGCPassStats* next;
+        Js::TickDelta uiThreadBlockedTimes[static_cast<size_t>(RecyclerWaitReason::Other) + 1];
+        bool isInScript;
+        bool isScriptActive;
+        bool isGCPassActive;
+
+        size_t processAllocaterUsedBytes_start;
+        size_t processAllocaterUsedBytes_end;
+        size_t processCommittedBytes_start;
+        size_t processCommittedBytes_end;
+
+        HeapBucketStats bucketStats;
+
+        AllocatorSizes threadPageAllocator_start;
+        AllocatorSizes threadPageAllocator_end;
+        AllocatorSizes recyclerLeafPageAllocator_start;
+        AllocatorSizes recyclerLeafPageAllocator_end;
+        AllocatorSizes recyclerLargeBlockPageAllocator_start;
+        AllocatorSizes recyclerLargeBlockPageAllocator_end;
+
+#ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
+        AllocatorSizes recyclerWithBarrierPageAllocator_start;
+        AllocatorSizes recyclerWithBarrierPageAllocator_end;
+#endif
+    };
+
+    /**
+     *
+     */
+    class RecyclerTelemetryInfo
+    {
+    public:
+        RecyclerTelemetryInfo(Recycler* recycler, RecyclerTelemetryHostInterface* hostInterface);
+        ~RecyclerTelemetryInfo();
+
+        void StartPass();
+        void EndPass();
+        void IncrementUserThreadBlockedCount(Js::TickDelta waitTime, RecyclerWaitReason source);
+        
+        inline const Js::Tick& GetRecyclerStartTime() const { return this->recyclerStartTime;  }
+        inline const RecyclerTelemetryGCPassStats* GetLastPassStats() const { return this->lastPassStats; }
+        inline const Js::Tick& GetLastTransmitTime() const { return this->lastTransmitTime; }
+        inline const uint16 GetPassCount() const { return this->passCount; }
+        const GUID& GetRecyclerID() const;
+        bool GetIsConcurrentEnabled() const;
+        bool ShouldCaptureRecyclerTelemetry() const;
+        bool IsOnScriptThread() const;
+
+        AllocatorDecommitStats* GetThreadPageAllocator_decommitStats() { return &this->threadPageAllocator_decommitStats; }
+        AllocatorDecommitStats* GetRecyclerLeafPageAllocator_decommitStats() { return &this->recyclerLeafPageAllocator_decommitStats; }
+        AllocatorDecommitStats* GetRecyclerLargeBlockPageAllocator_decommitStats() { return &this->recyclerLargeBlockPageAllocator_decommitStats; }
+#ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
+        AllocatorDecommitStats* GetRecyclerWithBarrierPageAllocator_decommitStats() { return &this->recyclerWithBarrierPageAllocator_decommitStats; }
+#endif
+
+    private:
+        Recycler* recycler;
+        DWORD mainThreadID;
+        RecyclerTelemetryHostInterface * hostInterface;
+        Js::Tick recyclerStartTime;
+
+        // TODO: update list below to SList.  Need to ensure we have same allocation semantics (specifically heap allocs, no exceptions on failure)
+        RecyclerTelemetryGCPassStats* lastPassStats;
+        Js::Tick lastTransmitTime;
+        uint16 passCount;
+        bool abortTelemetryCapture;
+
+        AllocatorDecommitStats threadPageAllocator_decommitStats;
+        AllocatorDecommitStats recyclerLeafPageAllocator_decommitStats;
+        AllocatorDecommitStats recyclerLargeBlockPageAllocator_decommitStats;
+#ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
+        AllocatorDecommitStats recyclerWithBarrierPageAllocator_decommitStats;
+#endif
+
+        bool ShouldTransmit();
+        void FreeGCPassStats();
+        void Reset();
+        void FillInSizeData(IdleDecommitPageAllocator* allocator, AllocatorSizes* sizes) const;
+
+        static size_t GetProcessCommittedBytes();
+    };
+#else
+    class RecyclerTelemetryInfo
+    {
+    };
+#endif // #ifdef ENABLE_BASIC_TELEMETRY
+
+}
+

+ 15 - 0
lib/Common/Memory/RecyclerWaitReason.h

@@ -0,0 +1,15 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#pragma once
+
+#include "Core/CommonTypedefs.h"
+
+enum RecyclerWaitReason : uint8
+{
+#define P(n) n,
+#include "RecyclerWaitReasonInc.h"
+#undef P
+};

+ 17 - 0
lib/Common/Memory/RecyclerWaitReasonInc.h

@@ -0,0 +1,17 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#ifndef P
+  #error P not defined
+#endif
+
+P(WaitReasonNone)
+P(RescanMark)
+P(DoParallelMark)
+P(RequestConcurrentCallbackWrapper)
+P(CollectOnConcurrentThread)
+P(FinishConcurrentCollect)
+P(Other)
+

+ 1 - 1
lib/Common/Memory/SmallHeapBlockAllocator.h

@@ -225,7 +225,7 @@ SmallHeapBlockAllocator<TBlockType>::InlinedAllocImpl(Recycler * recycler, DECLS
 #ifdef RECYCLER_TRACE
             if (recycler->GetRecyclerFlagsTable().Trace.IsEnabled(Js::ConcurrentSweepPhase) && recycler->GetRecyclerFlagsTable().Trace.IsEnabled(Js::MemoryAllocationPhase) && CONFIG_FLAG_RELEASE(Verbose))
             {
-                Output::Print(_u("[**33**]FreeListAlloc: Object 0x%p from HeapBlock 0x%p used for allocation during ConcurrentSweep [CollectionState: %d] \n"), memBlock, heapBlock, recycler->collectionState);
+                Output::Print(_u("[**33**]FreeListAlloc: Object 0x%p from HeapBlock 0x%p used for allocation during ConcurrentSweep [CollectionState: %d] \n"), memBlock, heapBlock, static_cast<CollectionState>(recycler->collectionState));
             }
 #endif
         }

+ 3 - 3
lib/Common/Util/Chakra.Common.Util.vcxproj

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Condition="'$(ChakraBuildPathImported)'!='true'" Project="$(SolutionDir)Chakra.Build.Paths.props"/>
+  <Import Condition="'$(ChakraBuildPathImported)'!='true'" Project="$(SolutionDir)Chakra.Build.Paths.props" />
   <Import Project="$(BuildConfigPropsPath)Chakra.Build.ProjectConfiguration.props" />
   <PropertyGroup Label="Globals">
     <TargetName>Chakra.Common.Util</TargetName>
@@ -36,6 +36,6 @@
   <ItemGroup>
     <ClInclude Include="Pinned.h" />
   </ItemGroup>
-  <Import Project="$(BuildConfigPropsPath)Chakra.Build.targets" Condition="exists('$(BuildConfigPropsPath)Chakra.Build.targets')"/>
+  <Import Project="$(BuildConfigPropsPath)Chakra.Build.targets" Condition="exists('$(BuildConfigPropsPath)Chakra.Build.targets')" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
-</Project>
+</Project>

+ 1 - 0
lib/Runtime/Base/ScriptContext.cpp

@@ -345,6 +345,7 @@ namespace Js
 #endif
 
 #ifdef ENABLE_BASIC_TELEMETRY
+        // TODO - allocate this on the Heap instead of using a custom allocator?
         this->telemetry = Anew(this->TelemetryAllocator(), Js::ScriptContextTelemetry, this);
 #endif
 

+ 42 - 2
lib/Runtime/Base/ThreadContext.cpp

@@ -35,7 +35,8 @@
 
 #ifdef ENABLE_BASIC_TELEMETRY
 #include "Telemetry.h"
-#endif
+#include "Recycler/RecyclerTelemetryTransmitter.h"
+#endif // ENABLE_BASIC_TELEMETRY
 
 const int TotalNumberOfBuiltInProperties = Js::PropertyIds::_countJSOnlyProperty;
 
@@ -208,6 +209,7 @@ ThreadContext::ThreadContext(AllocationPolicyManager * allocationPolicyManager,
     , noJsReentrancy(false)
 #endif
     , emptyStringPropertyRecord(nullptr)
+    , recyclerTelemetryHostInterface(this)
 {
     pendingProjectionContextCloseList = JsUtil::List<IProjectionContext*, ArenaAllocator>::New(GetThreadAlloc());
     hostScriptContextStack = Anew(GetThreadAlloc(), JsUtil::Stack<HostScriptContext*>, GetThreadAlloc());
@@ -662,11 +664,49 @@ public:
     }
 };
 
+LPFILETIME ThreadContext::ThreadContextRecyclerTelemetryHostInterface::GetLastScriptExecutionEndTime() const
+{
+#if defined(ENABLE_BASIC_TELEMETRY) && defined(NTBUILD)
+    return &(tc->telemetryBlock->lastScriptEndTime);
+#else
+    return nullptr;
+#endif
+}
+
+bool ThreadContext::ThreadContextRecyclerTelemetryHostInterface::TransmitTelemetry(RecyclerTelemetryInfo& rti)
+{
+#if defined(ENABLE_BASIC_TELEMETRY) && defined(NTBUILD)
+    return Js::TransmitRecyclerTelemetry(rti);
+#else
+    return false;
+#endif
+}
+
+bool ThreadContext::ThreadContextRecyclerTelemetryHostInterface::TransmitTelemetryError(const RecyclerTelemetryInfo& rti, const char * msg)
+{
+#if defined(ENABLE_BASIC_TELEMETRY) && defined(NTBUILD)
+    return Js::TransmitRecyclerTelemetryError(rti, msg);
+#else
+    return false;
+#endif
+}
+
+bool ThreadContext::ThreadContextRecyclerTelemetryHostInterface::IsThreadBound() const
+{
+    return this->tc->IsThreadBound(); 
+}
+
+
+DWORD ThreadContext::ThreadContextRecyclerTelemetryHostInterface::GetCurrentScriptThreadID() const
+{
+    return this->tc->GetCurrentThreadId();
+}
+
 Recycler* ThreadContext::EnsureRecycler()
 {
     if (recycler == NULL)
     {
-        AutoRecyclerPtr newRecycler(HeapNew(Recycler, GetAllocationPolicyManager(), &pageAllocator, Js::Throw::OutOfMemory, Js::Configuration::Global.flags));
+        AutoRecyclerPtr newRecycler(HeapNew(Recycler, GetAllocationPolicyManager(), &pageAllocator, Js::Throw::OutOfMemory, Js::Configuration::Global.flags, &recyclerTelemetryHostInterface));
         newRecycler->Initialize(isOptimizedForManyInstances, &threadService); // use in-thread GC when optimizing for many instances
         newRecycler->SetCollectionWrapper(this);
 

+ 21 - 0
lib/Runtime/Base/ThreadContext.h

@@ -885,6 +885,7 @@ public:
 
 #ifdef ENABLE_BASIC_TELEMETRY
     GUID activityId;
+    LPFILETIME GetLastScriptExecutionEndTime() const;
 #endif
     void *tridentLoadAddress;
 
@@ -1826,6 +1827,26 @@ private:
 
     DWORD threadId;
 #endif
+
+private:
+    class ThreadContextRecyclerTelemetryHostInterface : public RecyclerTelemetryHostInterface
+    {
+    public:
+        ThreadContextRecyclerTelemetryHostInterface(ThreadContext* tc) :
+            tc(tc)
+        {
+        }
+
+        virtual LPFILETIME GetLastScriptExecutionEndTime() const;
+        virtual bool TransmitTelemetry(RecyclerTelemetryInfo& rti);
+        virtual bool TransmitTelemetryError(const RecyclerTelemetryInfo& rti, const char * msg);
+        virtual bool ThreadContextRecyclerTelemetryHostInterface::IsThreadBound() const;
+        virtual DWORD ThreadContextRecyclerTelemetryHostInterface::GetCurrentScriptThreadID() const;
+
+    private:
+        ThreadContext * tc;
+    };
+    ThreadContextRecyclerTelemetryHostInterface recyclerTelemetryHostInterface;
 };
 
 extern void(*InitializeAdditionalProperties)(ThreadContext *threadContext);