RecyclerTelemetryInfo.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "CommonMemoryPch.h"
  6. #ifdef ENABLE_BASIC_TELEMETRY
  7. #include "Recycler.h"
  8. #include "DataStructures/SList.h"
  9. #include "HeapBucketStats.h"
  10. #include "BucketStatsReporter.h"
  11. #include <psapi.h>
  12. namespace Memory
  13. {
  14. #ifdef DBG
  15. #define AssertOnValidThread(recyclerTelemetryInfo, loc) { AssertMsg(this->IsOnScriptThread(), STRINGIZE("Unexpected thread ID at " ## loc)); }
  16. #else
  17. #define AssertOnValidThread(recyclerTelemetryInfo, loc)
  18. #endif
  19. RecyclerTelemetryInfo::RecyclerTelemetryInfo(Recycler * recycler, RecyclerTelemetryHostInterface* hostInterface) :
  20. passCount(0),
  21. perfTrackPassCount(0),
  22. hostInterface(hostInterface),
  23. gcPassStats(&HeapAllocator::Instance),
  24. recyclerStartTime(Js::Tick::Now()),
  25. abortTelemetryCapture(false),
  26. inPassActiveState(false),
  27. recycler(recycler)
  28. {
  29. mainThreadID = ::GetCurrentThreadId();
  30. }
  31. RecyclerTelemetryInfo::~RecyclerTelemetryInfo()
  32. {
  33. if (this->hostInterface != nullptr)
  34. {
  35. AssertOnValidThread(this, RecyclerTelemetryInfo::~RecyclerTelemetryInfo);
  36. if (this->gcPassStats.Empty() == false)
  37. {
  38. this->hostInterface->TransmitGCTelemetryStats(*this);
  39. this->FreeGCPassStats();
  40. }
  41. }
  42. }
  43. const GUID& RecyclerTelemetryInfo::GetRecyclerID() const
  44. {
  45. return this->recycler->GetRecyclerID();
  46. }
  47. RecyclerFlagsTableSummary RecyclerTelemetryInfo::GetRecyclerConfigFlags() const
  48. {
  49. // select set of config flags that we can pack into an uint32
  50. RecyclerFlagsTableSummary flags = RecyclerFlagsTableSummary::None;
  51. if (this->recycler->IsMemProtectMode()) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::IsMemProtectMode); }
  52. if (this->recycler->IsConcurrentEnabled()) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::IsConcurrentEnabled); }
  53. if (this->recycler->enableScanInteriorPointers) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::EnableScanInteriorPointers); }
  54. if (this->recycler->enableScanImplicitRoots) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::EnableScanImplicitRoots); }
  55. if (this->recycler->disableCollectOnAllocationHeuristics) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::DisableCollectOnAllocationHeuristics); }
  56. #ifdef RECYCLER_STRESS
  57. if (this->recycler->recyclerStress) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::RecyclerStress); }
  58. #if ENABLE_CONCURRENT_GC
  59. if (this->recycler->recyclerBackgroundStress) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::RecyclerBackgroundStress); }
  60. if (this->recycler->recyclerConcurrentStress) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::RecyclerConcurrentStress); }
  61. if (this->recycler->recyclerConcurrentRepeatStress) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::RecyclerConcurrentRepeatStress); }
  62. #endif
  63. #if ENABLE_PARTIAL_GC
  64. if (this->recycler->recyclerPartialStress) { flags = static_cast<RecyclerFlagsTableSummary>(flags | RecyclerFlagsTableSummary::RecyclerPartialStress); }
  65. #endif
  66. #endif
  67. return flags;
  68. }
  69. bool RecyclerTelemetryInfo::ShouldStartTelemetryCapture() const
  70. {
  71. return
  72. this->hostInterface != nullptr &&
  73. this->abortTelemetryCapture == false &&
  74. this->hostInterface->IsTelemetryProviderEnabled();
  75. }
  76. void RecyclerTelemetryInfo::FillInSizeData(IdleDecommitPageAllocator* allocator, AllocatorSizes* sizes) const
  77. {
  78. sizes->committedBytes = allocator->GetCommittedBytes();
  79. sizes->reservedBytes = allocator->GetReservedBytes();
  80. sizes->usedBytes = allocator->GetUsedBytes();
  81. sizes->numberOfSegments = allocator->GetNumberOfSegments();
  82. }
  83. GCPassStatsList::Iterator RecyclerTelemetryInfo::GetGCPassStatsIterator() const
  84. {
  85. return this->gcPassStats.GetIterator();
  86. }
  87. RecyclerTelemetryGCPassStats* RecyclerTelemetryInfo::GetLastPassStats() const
  88. {
  89. RecyclerTelemetryGCPassStats* stats = nullptr;
  90. if (this->gcPassStats.Empty() == false)
  91. {
  92. RecyclerTelemetryGCPassStats& ref = const_cast<RecyclerTelemetryGCPassStats&>(this->gcPassStats.Head());
  93. stats = &ref;
  94. }
  95. return stats;
  96. }
  97. void RecyclerTelemetryInfo::StartPass(CollectionState collectionState)
  98. {
  99. this->inPassActiveState = false;
  100. if (this->ShouldStartTelemetryCapture())
  101. {
  102. Js::Tick start = Js::Tick::Now();
  103. AssertOnValidThread(this, RecyclerTelemetryInfo::StartPass);
  104. #if DBG
  105. // validate state of existing GC pass stats structs
  106. uint16 count = 0;
  107. if (this->gcPassStats.Empty() == false)
  108. {
  109. GCPassStatsList::Iterator it = this->GetGCPassStatsIterator();
  110. while (it.Next())
  111. {
  112. RecyclerTelemetryGCPassStats& curr = it.Data();
  113. AssertMsg(curr.isGCPassActive == false, "unexpected value for isGCPassActive");
  114. ++count;
  115. }
  116. }
  117. AssertMsg(count == this->passCount, "RecyclerTelemetryInfo::StartPass() - mismatch between passCount and count.");
  118. #endif
  119. RecyclerTelemetryGCPassStats* stats = this->gcPassStats.PrependNodeNoThrow(&HeapAllocator::Instance);
  120. if (stats == nullptr)
  121. {
  122. // failed to allocate memory - disable any further telemetry capture for this recycler
  123. // and free any existing GC stats we've accumulated
  124. this->abortTelemetryCapture = true;
  125. FreeGCPassStats();
  126. this->hostInterface->TransmitTelemetryError(*this, "Memory Allocation Failed");
  127. }
  128. else
  129. {
  130. this->inPassActiveState = true;
  131. passCount++;
  132. perfTrackPassCount++;
  133. memset(stats, 0, sizeof(RecyclerTelemetryGCPassStats));
  134. stats->startPassCollectionState = collectionState;
  135. stats->isGCPassActive = true;
  136. stats->passStartTimeTick = Js::Tick::Now();
  137. GetSystemTimePreciseAsFileTime(&stats->passStartTimeFileTime);
  138. if (this->hostInterface != nullptr)
  139. {
  140. LPFILETIME ft = this->hostInterface->GetLastScriptExecutionEndTime();
  141. stats->lastScriptExecutionEndTime = *ft;
  142. stats->closedContextCount = this->hostInterface->GetClosedContextCount();
  143. }
  144. stats->processCommittedBytes_start = RecyclerTelemetryInfo::GetProcessCommittedBytes();
  145. stats->processAllocaterUsedBytes_start = PageAllocator::GetProcessUsedBytes();
  146. stats->isInScript = this->recycler->GetIsInScript();
  147. stats->isScriptActive = this->recycler->GetIsScriptActive();
  148. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLeafPageAllocator(), &stats->threadPageAllocator_start);
  149. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerPageAllocator(), &stats->recyclerLeafPageAllocator_start);
  150. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLargeBlockPageAllocator(), &stats->recyclerLargeBlockPageAllocator_start);
  151. #ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
  152. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerWithBarrierPageAllocator(), &stats->recyclerWithBarrierPageAllocator_start);
  153. #endif
  154. stats->startPassProcessingElapsedTime = Js::Tick::Now() - start;
  155. stats->pinnedObjectCount = this->recycler->pinnedObjectMap.Count();
  156. }
  157. }
  158. }
  159. void RecyclerTelemetryInfo::EndPass(CollectionState collectionState)
  160. {
  161. if (!this->inPassActiveState)
  162. {
  163. return;
  164. }
  165. this->inPassActiveState = false;
  166. Js::Tick start = Js::Tick::Now();
  167. AssertOnValidThread(this, RecyclerTelemetryInfo::EndPass);
  168. RecyclerTelemetryGCPassStats* lastPassStats = this->GetLastPassStats();
  169. lastPassStats->endPassCollectionState = collectionState;
  170. lastPassStats->collectionStartReason = this->recycler->collectionStartReason;
  171. lastPassStats->collectionFinishReason = this->recycler->collectionFinishReason;
  172. lastPassStats->collectionStartFlags = this->recycler->collectionStartFlags;
  173. lastPassStats->isGCPassActive = false;
  174. lastPassStats->passEndTimeTick = Js::Tick::Now();
  175. lastPassStats->processCommittedBytes_end = RecyclerTelemetryInfo::GetProcessCommittedBytes();
  176. lastPassStats->processAllocaterUsedBytes_end = PageAllocator::GetProcessUsedBytes();
  177. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLeafPageAllocator(), &lastPassStats->threadPageAllocator_end);
  178. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerPageAllocator(), &lastPassStats->recyclerLeafPageAllocator_end);
  179. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerLargeBlockPageAllocator(), &lastPassStats->recyclerLargeBlockPageAllocator_end);
  180. #ifdef RECYCLER_WRITE_BARRIER_ALLOC_SEPARATE_PAGE
  181. this->FillInSizeData(this->recycler->GetHeapInfo()->GetRecyclerWithBarrierPageAllocator(), &lastPassStats->recyclerWithBarrierPageAllocator_end);
  182. #endif
  183. // get bucket stats
  184. Js::Tick bucketStatsStart = Js::Tick::Now();
  185. BucketStatsReporter bucketReporter(this->recycler);
  186. this->recycler->GetHeapInfo()->GetBucketStats(bucketReporter);
  187. memcpy(&lastPassStats->bucketStats, bucketReporter.GetTotalStats(), sizeof(HeapBucketStats));
  188. lastPassStats->computeBucketStatsElapsedTime = Js::Tick::Now() - bucketStatsStart;
  189. lastPassStats->endPassProcessingElapsedTime = Js::Tick::Now() - start;
  190. // use separate events for perftrack specific data & general telemetry data
  191. if (this->ShouldTransmitPerfTrackEvents())
  192. {
  193. if (this->hostInterface->TransmitHeapUsage(bucketReporter.GetTotalStats()->totalByteCount, bucketReporter.GetTotalStats()->objectByteCount, bucketReporter.GetTotalStats()->UsedRatio()))
  194. {
  195. this->ResetPerfTrackCounts();
  196. }
  197. }
  198. if (this->ShouldTransmitGCStats() && this->hostInterface != nullptr)
  199. {
  200. if (this->hostInterface->TransmitGCTelemetryStats(*this))
  201. {
  202. this->lastTransmitTime = lastPassStats->passEndTimeTick;
  203. Reset();
  204. }
  205. }
  206. }
  207. void RecyclerTelemetryInfo::ResetPerfTrackCounts()
  208. {
  209. this->perfTrackPassCount = 0;
  210. }
  211. void RecyclerTelemetryInfo::Reset()
  212. {
  213. FreeGCPassStats();
  214. memset(&this->threadPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
  215. memset(&this->recyclerLeafPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
  216. memset(&this->recyclerLargeBlockPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
  217. memset(&this->threadPageAllocator_decommitStats, 0, sizeof(AllocatorDecommitStats));
  218. }
  219. void RecyclerTelemetryInfo::FreeGCPassStats()
  220. {
  221. if (this->gcPassStats.Empty() == false)
  222. {
  223. AssertMsg(this->passCount > 0, "unexpected value for passCount");
  224. AssertOnValidThread(this, RecyclerTelemetryInfo::FreeGCPassStats);
  225. this->gcPassStats.Clear();
  226. this->passCount = 0;
  227. }
  228. }
  229. bool RecyclerTelemetryInfo::ShouldTransmitGCStats() const
  230. {
  231. // for now, try to transmit telemetry when we have >= 16
  232. return (this->hostInterface != nullptr && this->passCount >= 16);
  233. }
  234. bool RecyclerTelemetryInfo::ShouldTransmitPerfTrackEvents() const
  235. {
  236. // for now, try to transmit telemetry when we have >= 16
  237. return (this->hostInterface != nullptr && this->perfTrackPassCount >= 128);
  238. }
  239. void RecyclerTelemetryInfo::IncrementUserThreadBlockedCount(Js::TickDelta waitTime, RecyclerWaitReason caller)
  240. {
  241. RecyclerTelemetryGCPassStats* lastPassStats = this->GetLastPassStats();
  242. #ifdef DBG
  243. if (this->inPassActiveState)
  244. {
  245. AssertMsg(lastPassStats != nullptr && lastPassStats->isGCPassActive == true, "unexpected Value in RecyclerTelemetryInfo::IncrementUserThreadBlockedCount");
  246. }
  247. #endif
  248. if (this->inPassActiveState && lastPassStats != nullptr)
  249. {
  250. AssertOnValidThread(this, RecyclerTelemetryInfo::IncrementUserThreadBlockedCount);
  251. lastPassStats->uiThreadBlockedTimes[caller] += waitTime;
  252. }
  253. }
  254. void RecyclerTelemetryInfo::IncrementUserThreadBlockedCpuTimeUser(uint64 userMicroseconds, RecyclerWaitReason caller)
  255. {
  256. RecyclerTelemetryGCPassStats* lastPassStats = this->GetLastPassStats();
  257. #ifdef DBG
  258. if (this->inPassActiveState)
  259. {
  260. AssertMsg(lastPassStats != nullptr && lastPassStats->isGCPassActive == true, "unexpected Value in RecyclerTelemetryInfo::IncrementUserThreadBlockedCpuTimeUser");
  261. }
  262. #endif
  263. if (this->inPassActiveState && lastPassStats != nullptr)
  264. {
  265. AssertOnValidThread(this, RecyclerTelemetryInfo::IncrementUserThreadBlockedCpuTimeUser);
  266. lastPassStats->uiThreadBlockedCpuTimesUser[caller] += userMicroseconds;
  267. }
  268. }
  269. void RecyclerTelemetryInfo::IncrementUserThreadBlockedCpuTimeKernel(uint64 kernelMicroseconds, RecyclerWaitReason caller)
  270. {
  271. RecyclerTelemetryGCPassStats* lastPassStats = this->GetLastPassStats();
  272. #ifdef DBG
  273. if (this->inPassActiveState)
  274. {
  275. AssertMsg(lastPassStats != nullptr && lastPassStats->isGCPassActive == true, "unexpected Value in RecyclerTelemetryInfo::IncrementUserThreadBlockedCpuTimeKernel");
  276. }
  277. #endif
  278. if (this->inPassActiveState && lastPassStats != nullptr)
  279. {
  280. AssertOnValidThread(this, RecyclerTelemetryInfo::IncrementUserThreadBlockedCpuTimeKernel);
  281. lastPassStats->uiThreadBlockedCpuTimesKernel[caller] += kernelMicroseconds;
  282. }
  283. }
  284. bool RecyclerTelemetryInfo::IsOnScriptThread() const
  285. {
  286. bool isValid = false;
  287. if (this->hostInterface != nullptr)
  288. {
  289. if (this->hostInterface->IsThreadBound())
  290. {
  291. isValid = ::GetCurrentThreadId() == this->hostInterface->GetCurrentScriptThreadID();
  292. }
  293. else
  294. {
  295. isValid = ::GetCurrentThreadId() == this->mainThreadID;
  296. }
  297. }
  298. return isValid;
  299. }
  300. size_t RecyclerTelemetryInfo::GetProcessCommittedBytes()
  301. {
  302. HANDLE process = GetCurrentProcess();
  303. PROCESS_MEMORY_COUNTERS memCounters = { 0 };
  304. size_t committedBytes = 0;
  305. if (GetProcessMemoryInfo(process, &memCounters, sizeof(PROCESS_MEMORY_COUNTERS)))
  306. {
  307. committedBytes = memCounters.PagefileUsage;
  308. }
  309. return committedBytes;
  310. }
  311. }
  312. #endif