[
  {
    "path": ".gitattributes",
    "content": "*.doc  diff=astextplain\n*.DOC\tdiff=astextplain\n*.docx\tdiff=astextplain\n*.DOCX\tdiff=astextplain\n*.dot\tdiff=astextplain\n*.DOT\tdiff=astextplain\n*.pdf\tdiff=astextplain\n*.PDF\tdiff=astextplain\n*.rtf\tdiff=astextplain\n*.RTF\tdiff=astextplain\n\n*.jpg  \tbinary\n*.png \tbinary\n*.gif \tbinary\n\n*.cs text=auto diff=csharp \n*.vb text=auto\n*.resx text=auto\n*.c text=auto\n*.cpp text=auto\n*.cxx text=auto\n*.h text=auto\n*.hxx text=auto\n*.py text=auto\n*.rb text=auto\n*.java text=auto\n*.html text=auto\n*.htm text=auto\n*.css text=auto\n*.scss text=auto\n*.sass text=auto\n*.less text=auto\n*.js text=auto\n*.lisp text=auto\n*.clj text=auto\n*.sql text=auto\n*.php text=auto\n*.lua text=auto\n*.m text=auto\n*.asm text=auto\n*.erl text=auto\n*.fs text=auto\n*.fsx text=auto\n*.hs text=auto\n\n*.csproj text=auto\n*.vbproj text=auto\n*.fsproj text=auto\n*.dbproj text=auto\n*.sln text=auto eol=crlf\n\n*.sh eol=lf"
  },
  {
    "path": ".gitignore",
    "content": "[Oo]bj/\n[Bb]in/\nTestResults/\n.nuget/\n_ReSharper.*/\npackages/\nartifacts/\nPublishProfiles/\n*.user\n*.suo\n*.cache\n*.docstates\n_ReSharper.*\nnuget.exe\n*net45.csproj\n*net451.csproj\n*k10.csproj\n*.psess\n*.vsp\n*.pidb\n*.userprefs\n*DS_Store\n*.ncrunchsolution\n*.*sdf\n*.ipch\n*.sln.ide\nproject.lock.json\n.vs\n.vscode/\n.build/\n.testPublish/\nglobal.json\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Sébastien Ros\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Memory Management and Patterns in ASP.NET Core\n\nMemory management is complex, even in a managed framework like .NET. Analyzing and understanding memory issues can be challenging.\n\nRecently a user [reported an issue](https://github.com/aspnet/Home/issues/1976) in the ASP.NET Core GitHub Home repository stating that The Garbage Collector (GC) was \"not collecting the garbage\", which would make it quite useless. The symptoms, as described by the original creator, were that the memory would keep growing request after request, letting them think that the issue was in the GC.\n\nWe tried to get more information about this issue, to understand if the problem was in the GC or in the application itself, but what we got instead was a wave of other contributors posting reports of such behavior: the memory keeps growing. The thread grew to the extent that we decided to split it into multiple issues and follow-up on them independently. In the end most of the issues can be explained by some misunderstanding about how memory consumption works in .NET, but also issues in how it was measured.\n\nTo help .NET developers better understand their applications, we need to understand how memory management works in ASP.NET Core, how to detect memory related issues, and how to prevent common mistakes.\n\n## How Garbage Collection works in ASP.NET Core\n\nThe GC allocates heap segments where each segment is a contiguous range of memory. Objects placed in it are categorized into one of 3 generations - 0, 1, or 2. The generation determines the frequency with which the GC attempts to release memory on a managed object that are no longer referenced by the application - lower numbers imply higher frequency.\n\nObjects are moved from one generation to another based on their lifetime. As objects live longer they will be moved in a higher generation, and assessed for collection less often. Short term lived objects like the ones that are referenced during the life of a web request will always remain in generation 0. Application level singletons however will most probably move to generation 1 and eventually 2.\n\nWhen an ASP.NET Core applications has started, the GC will reserve some memory for the initial heap segments and commit a small portion of it when the runtime is loaded. This is done for performance reasons so a heap segment can be in contiguous memory.\n\n> Important: An ASP.NET Core process will preemptively allocate a significant amount of memory at startup.\n\n### Calling the GC explicitly \n\nTo manually invoke the GC execute `GC.Collect()`. This will trigger a generation 2 collection and all lower generations. This is usually only used when investigating memory leaks, to be sure the GC has removed all dangling objects from memory before we can measure it.\n\n> Note: An application should not have to call `GC.Collect()` directly.\n\n## Analyzing the memory usage of an application\n\nDedicated tools can help analyzing memory usage:\n- counting object references\n- measuring how much impact the GC has on CPU\n- measuring space used for each generation\n\nHowever for the sake of simplicity this article won't use any of these but instead render some in-app live charts.\n\nFor in-depth anlysis please read these articles which demonstrate how to use Visual Studio .NET:\n\n[Analyze memory usage without the Visual Studio debugger](https://docs.microsoft.com/en-us/visualstudio/profiling/memory-usage-without-debugging2)\n\n[Profile memory usage in Visual Studio](https://docs.microsoft.com/en-us/visualstudio/profiling/memory-usage)\n\n\n### Detecting memory issues\n\nMost of the time the memory measure displayed in the __Task Manager__ is used to get an idea of how much memory an ASP.NET application is using. This value represents the amount of memory that is used by the ASP.NET process which includes the application's living objects and other memory consumers such as native memory usage.\n\nSeeing this value increasing indefinitely is a clue that there is a memory leak somewhere in the code but it doesn't explain what it is. The next sections will introduce you to specific memory usage patterns and explain them.\n\n### Running the application\n\nThe full source code is available on GitHub at https://github.com/sebastienros/memoryleak\n\nOnce it has started the application displays some memory and GC statistics and the page refreshes by itself every second. Specific API endpoints execute specific memory allocation patterns. \n\nTo test this application, simply start it. You can see that the allocated memory keeps increasing, because displaying these statistics is allocating custom objects for instance. The GC eventually runs and collects them.\n\nThis pages shows a graphs including allocated memory and GC collections. The legend also displays the CPU usage and throughput in requests per second.\n\nThe chart displays two values for the memory usage:\n- Allocated: the amount of memory occupied by managed objects\n- Working Set: the total physical memory (RAM) used by the process (as displayed in the Task Manager)\n\n#### Transient objects\n\nThe following API creates a 10KB `String` instance and returns it to the client. On each request a new object is allocated in memory and written on the response. \n\n> Note: Strings are stored as UTF-16 characters in .NET so each char takes two bytes in memory.\n\n```csharp\n[HttpGet(\"bigstring\")]\npublic ActionResult<string> GetBigString()\n{\n    return new String('x', 10 * 1024);\n}\n```\n\nThe following graph is generated with a relatively small load of 5K RPS in order to see how the memory allocations are impacted by the GC.\n\n![](images/bigstring.png)\n\nIn this example, the GC collect the generation 0 instances about every two seconds once the allocations reach a threshold of a little above 300 MB. The working set is stable at around 500 MB, and the CPU usage is low.\n\nWhat this graph shows is how on a relatively low requests throughput the memory consumption is very stable to an amount that has been chosen by the GC.\n\nThe following chart is taken once the load is increased to the max throughput that can be handled by the machine.\n\n![](images/bigstring2.png)\n\nThere are some notable points:\n- The collections happen much more frequently, as in many times per second\n- There are now generation 1 collections, which is due to the fact that we allocated much more of them in the same time interval\n- The working set is still stable\n\nWhat we see is that as long as the CPU is not over-utilized, the garbage collection can deal with a high number of allocations.\n\n#### Workstation GC vs. Server GC\n\nThe .NET Garbage Collector can work in two different modes, namely the __Workstation GC__ and the __Server GC__. As their names suggest, they are optimized for different workloads. ASP.NET applications default to the Server GC mode, while desktop applications use the Workstation GC mode.\n\nTo visualize the actual impact of these modes, we can force the Workstation GC on our web application by using the `ServerGarbageCollection` parameter in the project file (`.csproj`). This will require the application to be rebuilt.\n\n```xml\n    <ServerGarbageCollection>false</ServerGarbageCollection>\n```\n\nIt can also be done by setting the `System.GC.Server` property in the `runtimeconfig.json` file of the published application.\n\nHere is the memory profile under a 5K RPS for the Workstation GC.\n\n![](images/workstation.png)\n\nThe differences are drastic:\n- The working set came from 500MB to 70MB\n- The GC does generation 0 collections multiple times per second instead of every two seconds\n- The GC threshold went from 300MB to 10MB\n\nOn a typical web server environment the CPU resource is more critical than memory, hence using the Server GC is better suited. However, some server scenarios might be more adapted for a Workstation GC, for instance on a high density hosting several web application where memory becomes a scarce resource. \n\n> Note: On machines with a single core, the GC mode will always be Workstation.\n\n#### Eternal references\n\nEven though the garbage collector does a good job at preventing memory to grow, if objects are simply held live by the user code GC cannot release them. If the amount of memory used by such objects keeps increasing, it’s called a managed memory leak.\n\nThe following API creates a 10KB `String` instance and returns it to the client. The difference with the first example is that this instance is referenced by a static member, which means it will never available for collection.\n\n```csharp\nprivate static ConcurrentBag<string> _staticStrings = new ConcurrentBag<string>();\n\n[HttpGet(\"staticstring\")]\npublic ActionResult<string> GetStaticString()\n{\n    var bigString = new String('x', 10 * 1024);\n    _staticStrings.Add(bigString);\n    return bigString;\n}\n```\n\nThis is a typical user code memory leak as the memory will keep increasing until the process crashes with an `OutOfMemory` exception.\n\n![](images/eternal.png)\n\nWhat we can see on this chart once we start issuing requests on this new endpoint is that the working set is no more stable and increases constantly. During that increase the GC tries to free memory as the memory pressure grows, by calling a generation 2 collection. This succeeds and frees some of it, but this can't stop the working set from increasing.\n\nSome scenarios require to keep object references indefinitely, in which case a way to mitigate this issue would be to use the `WeakReference` class in order to keep a reference on an object that can still be collected under memory pressure. This is what the default implementation of `IMemoryCache` does in ASP.NET Core. \n\n#### Native memory\n\nMemory leaks don't have to be caused by eternal references to managed objects. Some .NET objects rely on native memory to function. This memory cannot be collected by the GC and the .NET objects need to free it using native code.\n\nFortunately .NET provides the `IDisposable` interface to let developers release this native memory proactively. And even if `Dispose()` is not called in time, classes usually do it automatically when the finalizer runs... unless the class is not correctly implemented.\n\nLet's take a look at this code for instance:\n\n```csharp\n[HttpGet(\"fileprovider\")]\npublic void GetFileProvider()\n{\n    var fp = new PhysicalFileProvider(TempPath);\n    fp.Watch(\"*.*\");\n}\n```\n\n`PhysicaFileProvider` is a managed class, so any instance will be collected at the end of the request.\n\nHere is the resulting memory profile while invoking this API continuously.\n\n![](images/fileprovider.png)\n\nThis chart shows an obvious issue with the implementation of this class, as it keeps increasing memory usage. This is a known issue that is being tracked here https://github.com/aspnet/Home/issues/3110\n\nThe same issue could be easily happening in user code, by not releasing the class correctly or forgetting to invoke the `Dispose()` method of the dependent objects which should be disposed. \n\n#### Large Objects Heap\n\nAs memory gets allocated and freed continuously, fragmentation in the memory can happen. This is an issue as objects have to be allocated in a contiguous block of memory. To mitigate this issue, whenever the garbage collector frees some memory, it will try to defragment it. This process is called __compaction__.\n\nThe problem that compaction faces is that the bigger the object, the slower it is to move it. There is a size after which an object will take so much time to be moved that it is not as efficient anymore to move it. For this reason the GC creates a special memory zone for these _large_ objects, called the __Large Object Heap__ (LOH). Object that are greater than 85,000 bytes (not 85 KB) are placed there, not compacted, and eventually released during generation 2 collections. But another effect is that whenever the LOH is full, it will trigger an automatic generation 2 collection, which is inherently slower as it triggers a collection on all other generations too.\n\nHere is an API that illustrates this behavior:\n\n```csharp\n[HttpGet(\"loh/{size=85000}\")]\npublic int GetLOH1(int size)\n{\n    return new byte[size].Length;\n}\n```\n\nThe following chart shows the memory profile of calling this endpoint with a `84,975` bytes array, under maximum load:\n\n![](images/loh1.png)\n\nAnd then the chart when calling the same endpoint but using _just_ one more byte, i.e. `84,976` bytes (the `byte[]` structure has some little overhead on top of the actual bytes serialization).\n\n![](images/loh2.png)\n\nThe working set is about the same on both scenarios, at a steady 450 MB. But what we notice is that instead of having mostly generation 0 collections, we instead get generation 2 collections, which require more CPU time and directly impacts the throughput which decreases from 35K to 18K RPS, __almost halving it__.\n\nThis shows that very large objects should be avoided. As an example the __Response Caching__ middleware in ASP.NET Core split the cache entries in block of a size lower than 85,000 bytes to handle this scenario.\n\nHere are some links to the specific implementation handling this behavior \n- https://github.com/aspnet/ResponseCaching/blob/c1cb7576a0b86e32aec990c22df29c780af29ca5/src/Microsoft.AspNetCore.ResponseCaching/Streams/StreamUtilities.cs#L16\n- https://github.com/aspnet/ResponseCaching/blob/c1cb7576a0b86e32aec990c22df29c780af29ca5/src/Microsoft.AspNetCore.ResponseCaching/Internal/MemoryResponseCache.cs#L55\n\n#### HttpClient\n\nNot specifically a memory leak issue, more of a resource leak one, but this has been seen enough times in user code that it deserved to be mentioned here.\n\nSeasoned .NET developer are used to disposing objects that implement `IDisposable`. Not doing so might result is leaked memory (see previous examples), or other native resources like database connections and file handlers.\n\nBut `HttpClient`, even though it implements `IDisposable`, should not be used then disposed on every invocation but reused instead.\n\nHere is an API endpoint that creates and disposes a new instance on every request.\n\n```csharp\n[HttpGet(\"httpclient1\")]\npublic async Task<int> GetHttpClient1(string url)\n{\n    using (var httpClient = new HttpClient())\n    {\n        var result = await httpClient.GetAsync(url);\n        return (int)result.StatusCode;\n    }\n}\n```\n\nWhile putting some load on this endpoint, some error messages are logged:\n\n```\nfail: Microsoft.AspNetCore.Server.Kestrel[13]\n      Connection id \"0HLG70PBE1CR1\", Request id \"0HLG70PBE1CR1:00000031\": An unhandled exception was thrown by the application.\nSystem.Net.Http.HttpRequestException: Only one usage of each socket address (protocol/network address/port) is normally permitted ---> System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted\n   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)\n```\n\nWhat happens is that even though the `HttpClient` instances are disposed, the actual network connection will take some time to be released by the operating system. By continuously creating new connections we finally hit _ports exhaustion_ as each client connection requires its own client port.\n\nThe solution is to actually reuse the same `HttpClient` instance like this:\n\n```csharp\nprivate static readonly HttpClient _httpClient = new HttpClient();\n\n[HttpGet(\"httpclient2\")]\npublic async Task<int> GetHttpClient2(string url)\n{\n    var result = await _httpClient.GetAsync(url);\n    return (int)result.StatusCode;\n}\n```\n\nThis instance will eventually get released when the application stops.\n\nThis shows that it's not because a resource is disposable that it needs to be disposed right away.\n\n> Note: there are better ways to handle the lifetime of an `HttpClient` instance since ASP.NET Core 2.1 https://blogs.msdn.microsoft.com/webdev/2018/02/28/asp-net-core-2-1-preview1-introducing-httpclient-factory/\n\n#### Object pooling\n\nIn the previous example we saw how the `HttpClient` instance can be made static and reused by all requests to prevent resource exhaustion.\n\nA similar pattern is to use object pooling. The idea is that if an object is expensive to create, then we should reuse its instances to prevent resource allocations. A pool is a collection of pre-initialized objects that can be reserved and released across threads. Pools can define allocation rules like hard limits, predefined sizes, or growth rate.\n\nThe Nuget package `Microsoft.Extensions.ObjectPool` contains classes that help to manage such pools.\n\nTo show how beneficial it can be, let's use an API endpoint that instantiates a `byte` buffer that is filled with random numbers on each request:\n\n```csharp\n        [HttpGet(\"array/{size}\")]\n        public byte[] GetArray(int size)\n        {\n            var random = new Random();\n            var array = new byte[size];\n            random.NextBytes(array);\n\n            return array;\n        }\n```\n\nWith some load we can see generation 0 collections happening around every second.\n\n![](images/array.png)\n\nTo optimize this code we can pool the `byte` buffer by using the `ArrayPool<>` class. A static instance is reused across requests. \n\nThe special part of this scenario is that we are returning a pooled object from the API, which means we lose control of it as soon as we return from the method, and we can't release it. To solve that we need to encapsulate the pooled array in a disposable object and then register this special object with `HttpContext.Response.RegisterForDispose()`. This method will take care of calling `Dispose()` on the target object so that it's only released when the HTTP request is done.\n\n```csharp\nprivate static ArrayPool<byte> _arrayPool = ArrayPool<byte>.Create();\n\nprivate class PooledArray : IDisposable\n{\n    public byte[] Array { get; private set; }\n\n    public PooledArray(int size)\n    {\n        Array = _arrayPool.Rent(size);\n    }\n\n    public void Dispose()\n    {\n        _arrayPool.Return(Array);\n    }\n}\n\n[HttpGet(\"pooledarray/{size}\")]\npublic byte[] GetPooledArray(int size)\n{\n    var pooledArray = new PooledArray(size);\n\n    var random = new Random();\n    random.NextBytes(pooledArray.Array);\n\n    HttpContext.Response.RegisterForDispose(pooledArray);\n\n    return pooledArray.Array;\n}\n```\n\nApplying the same load as the non-pooled version results in the following chart:\n\n![](images/pooledarray.png)\n\nYou can see that the main difference is allocated bytes, and as a consequence much fewer generation 0 collections.\n\n## Conclusion\n\nUnderstanding how Garbage Collection works together with ASP.NET Core can be helpful to investigate memory pressure issues, and ultimately the performance of an application. \n\nApplying the practices explained in this article should prevent applications from showing signs of memory leaks.\n\n### Reference Articles\n\nTo go further in the understanding of how memory management works in .NET, here are some recommended articles.\n\n[Garbage Collection](https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/)\n\n[Understanding different GC modes with Concurrency Visualizer](https://blogs.msdn.microsoft.com/seteplia/2017/01/05/understanding-different-gc-modes-with-concurrency-visualizer/)\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/Controllers/ApiController.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.FileProviders;\nusing System;\nusing System.Buffers;\nusing System.Collections.Concurrent;\nusing System.IO;\nusing System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace MemoryLeak.Controllers\n{\n    [Route(\"api\")]\n    [ApiController]\n    public class ApiController : ControllerBase\n    {\n        public ApiController()\n        {\n            Interlocked.Increment(ref DiagnosticsController.Requests);\n        }\n\n        private static ConcurrentBag<string> _staticStrings = new ConcurrentBag<string>();\n\n        [HttpGet(\"staticstring\")]\n        public ActionResult<string> GetStaticString()\n        {\n            var bigString = new String('x', 10 * 1024);\n            _staticStrings.Add(bigString);\n            return bigString;\n        }\n\n        [HttpGet(\"bigstring\")]\n        public ActionResult<string> GetBigString()\n        {\n            return new String('x', 10 * 1024);\n        }\n\n        [HttpGet(\"loh/{size=85000}\")]\n        public int GetLOH(int size)\n        {\n            return new byte[size].Length;\n        }\n\n        private static readonly string TempPath = Path.GetTempPath();\n\n        [HttpGet(\"fileprovider\")]\n        public void GetFileProvider()\n        {\n            var fp = new PhysicalFileProvider(TempPath);\n            fp.Watch(\"*.*\");\n        }\n\n        [HttpGet(\"httpclient1\")]\n        public async Task<int> GetHttpClient1(string url)\n        {\n            using (var httpClient = new HttpClient())\n            {\n                var result = await httpClient.GetAsync(url);\n                return (int)result.StatusCode;\n            }\n        }\n\n        private static readonly HttpClient _httpClient = new HttpClient();\n\n        [HttpGet(\"httpclient2\")]\n        public async Task<int> GetHttpClient2(string url)\n        {\n            var result = await _httpClient.GetAsync(url);\n            return (int)result.StatusCode;\n        }\n\n        [HttpGet(\"array/{size}\")]\n        public byte[] GetArray(int size)\n        {\n            var array = new byte[size];\n\n            var random = new Random();\n            random.NextBytes(array);\n\n            return array;\n        }\n\n        private static ArrayPool<byte> _arrayPool = ArrayPool<byte>.Create();\n\n        private class PooledArray : IDisposable\n        {\n            public byte[] Array { get; private set; }\n\n            public PooledArray(int size)\n            {\n                Array = _arrayPool.Rent(size);\n            }\n\n            public void Dispose()\n            {\n                _arrayPool.Return(Array);\n            }\n        }\n\n        [HttpGet(\"pooledarray/{size}\")]\n        public byte[] GetPooledArray(int size)\n        {\n            var pooledArray = new PooledArray(size);\n\n            var random = new Random();\n            random.NextBytes(pooledArray.Array);\n\n            HttpContext.Response.RegisterForDispose(pooledArray);\n\n            return pooledArray.Array;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/Controllers/DiagnosticsController.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc;\nusing System;\nusing System.Diagnostics;\nusing System.Runtime.InteropServices;\nusing System.Threading;\n\nnamespace MemoryLeak.Controllers\n{\n    [Route(\"api\")]\n    [ApiController]\n    public class DiagnosticsController : ControllerBase\n    {\n        private static Process _process = Process.GetCurrentProcess();\n        private static TimeSpan _oldCPUTime = TimeSpan.Zero;\n        private static DateTime _lastMonitorTime = DateTime.UtcNow;\n        private static DateTime _lastRpsTime = DateTime.UtcNow;\n        private static double _cpu = 0, _rps = 0;\n        private static readonly double RefreshRate = TimeSpan.FromSeconds(1).TotalMilliseconds;\n        public static long Requests = 0;\n\n        [HttpGet(\"collect\")]\n        public ActionResult GetCollect()\n        {\n            GC.Collect();\n            GC.WaitForPendingFinalizers();\n            GC.Collect();\n\n            return Ok();\n        }\n\n        [HttpGet(\"diagnostics\")]\n        public ActionResult GetDiagnostics()\n        {\n            var now = DateTime.UtcNow;\n            _process.Refresh();\n\n            var cpuElapsedTime = now.Subtract(_lastMonitorTime).TotalMilliseconds;\n\n            if (cpuElapsedTime > RefreshRate)\n            {\n                var newCPUTime = _process.TotalProcessorTime;\n                var elapsedCPU = (newCPUTime - _oldCPUTime).TotalMilliseconds;\n                _cpu = elapsedCPU * 100 / Environment.ProcessorCount / cpuElapsedTime;\n\n                _lastMonitorTime = now;\n                _oldCPUTime = newCPUTime;\n            }\n\n            var rpsElapsedTime = now.Subtract(_lastRpsTime).TotalMilliseconds;\n            if (rpsElapsedTime > RefreshRate)\n            {\n                _rps = Requests * 1000 / rpsElapsedTime;\n                Interlocked.Exchange(ref Requests, 0);\n                _lastRpsTime = now;\n            }\n\n            var diagnostics = new\n            {\n                PID = _process.Id,\n\n                // The memory occupied by objects.\n                Allocated = GC.GetTotalMemory(false),\n\n                // The working set includes both shared and private data. The shared data includes the pages that contain all the \n                // instructions that the process executes, including instructions in the process modules and the system libraries.\n                WorkingSet = _process.WorkingSet64,\n\n                // The value returned by this property represents the current size of memory used by the process, in bytes, that \n                // cannot be shared with other processes.\n                PrivateBytes = _process.PrivateMemorySize64,\n\n                // The number of generation 0 collections\n                Gen0 = GC.CollectionCount(0),\n\n                // The number of generation 1 collections\n                Gen1 = GC.CollectionCount(1),\n\n                // The number of generation 2 collections\n                Gen2 = GC.CollectionCount(2),\n\n                CPU = _cpu,\n\n                RPS = _rps\n            };\n\n            return new ObjectResult(diagnostics);\n        }\n    }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/GlobalGC.cs",
    "content": "﻿using System.Runtime;\n\nnamespace MemoryLeak\n{\n    public static class GlobalGC\n    {\n        public static string GC = (GCSettings.IsServerGC == true) ? \"Server\" : \"Workstation\";\n    }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/MemoryLeak.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>net5.0</TargetFramework>\n    <ServerGarbageCollection>true</ServerGarbageCollection>\n  </PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/Pages/Index.cshtml",
    "content": "﻿@page\n\n@{\n    Layout = null;\n}\n\n<!DOCTYPE html>\n\n<html>\n<head>\n    <meta name=\"viewport\" content=\"width=device-width\" />\n    <title>Home</title>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js\"></script>\n\n    <style>\n        canvas {\n            -moz-user-select: none;\n            -webkit-user-select: none;\n            -ms-user-select: none;\n        }\n\n        .chart-container {\n            width: 800px;\n            margin-left: 40px;\n            margin-right: 40px;\n            margin-bottom: 40px;\n        }\n\n        .container {\n            display: flex;\n            flex-direction: row;\n            flex-wrap: wrap;\n            justify-content: center;\n        }\n    </style>\n\n</head>\n<body>\n    <p>\n        <button id=\"collect-btn\" type=\"button\" style=\"visibility:hidden\">Collect</button>\n    </p>\n\n    <div class=\"container\">\n        <div class=\"chart-container\">\n            <canvas id=\"memory-canvas\"></canvas>\n        </div>\n    </div>\n\n    <p>  GC = @MemoryLeak.GlobalGC.GC</p>\n\n    <script>\n        Chart.defaults.global.defaultFontFamily = \"'Segoe UI', Tahoma, Geneva, Verdana, sans-serif\";\n        var memoryContext = document.getElementById(\"memory-canvas\").getContext('2d');\n        var memoryChart = new Chart(memoryContext, {\n            type: 'line',\n            data: {\n                datasets: [\n                    {\n                        label: 'Allocated',\n                        data: [],\n                        yAxisID: 'y-axis-memory',\n                        borderColor: 'red',\n                        backgroundColor: 'red',\n                        pointRadius: 0,\n                        fill: false\n                    },\n                    {\n                        label: 'Working Set',\n                        data: [],\n                        yAxisID: 'y-axis-memory',\n                        borderColor: 'blue',\n                        backgroundColor: 'blue',\n                        pointRadius: 0,\n                        fill: false\n                    },\n                    {\n                        label: 'Gen 0',\n                        data: [],\n                        yAxisID: 'y-axis-collections',\n                        borderColor: 'green',\n                        backgroundColor: 'green',\n                        fill: false,\n                        showLine: false, // only show point\n                        pointStyle: 'triangle',\n                        pointRadius: 5\n\n                    },\n                    {\n                        label: 'Gen 1',\n                        data: [],\n                        yAxisID: 'y-axis-collections',\n                        borderColor: 'orange',\n                        backgroundColor: 'orange',\n                        fill: false,\n                        showLine: false, // only show point\n                        pointStyle: 'triangle',\n                        pointRadius: 5\n\n                    },\n                    {\n                        label: 'Gen 2',\n                        data: [],\n                        yAxisID: 'y-axis-collections',\n                        borderColor: 'black',\n                        backgroundColor: 'black',\n                        fill: false,\n                        showLine: false, // only show point\n                        pointStyle: 'triangle',\n                        pointRadius: 5\n                    },\n                    {\n                        label: 'CPU',\n                        data: []\n                    },\n                    {\n                        label: 'RPS',\n                        data: []\n                    }\n                ]\n            },\n            options: {\n                responsive: true,\n                scales: {\n                    xAxes: [{\n                        type: 'time',\n                        distribution: 'linear',\n                        time: {\n                            unit: 'second',\n                            displayFormats: {\n                                second: 'mm:ss'\n                            }\n                        },\n                        bounds: 'ticks',\n                        ticks: {\n                            callback: function (value, index, values) {\n                                return index;\n                            }\n                        }\n                    }],\n                    yAxes: [{\n                        id: 'y-axis-memory',\n                        ticks: {\n                            beginAtZero: true,\n                            bounds: 'data',\n                            position: 'left',\n                            callback: function (value, index, values) {\n                                return value + ' MB';\n                            }\n                        }\n                    },\n                    {\n                        id: 'y-axis-collections',\n                        display: false,\n                        ticks: {\n                            beginAtZero: true,\n                            min: 0,\n                            max: 2\n                        }\n                    }]\n                },\n                tooltips: {\n                    enabled: false\n                }\n            }\n        });\n\n        const diagnosticsUrl = \"/api/diagnostics\";\n        const collectUrl = \"/api/collect\";\n\n        const maxEntries = 50;\n        const interval = 300;\n        var allocated = memoryChart.data.datasets[0].data;\n        var workingSet = memoryChart.data.datasets[1].data;\n        var generation0 = memoryChart.data.datasets[2].data;\n        var generation1 = memoryChart.data.datasets[3].data;\n        var generation2 = memoryChart.data.datasets[4].data;\n        var previousGen0 = 0,\n            previousGen1 = 0,\n            previousGen2 = 0;\n\n        setInterval(function () {\n            fetch(diagnosticsUrl)\n                .then(response => {\n                    return response.json();\n                })\n                .then(diagnostics => {\n                    var now = new Date();\n                    allocated.push({ x: now, y: diagnostics.allocated / 1000000 });\n                    workingSet.push({ x: now, y: diagnostics.workingSet / 1000000 });\n\n                    memoryChart.data.datasets[5].label = `CPU (${Math.round(diagnostics.cpu)}%)`;\n                    memoryChart.data.datasets[6].label = `RPS (${Math.round(diagnostics.rps / 1000)}K)`;\n\n                    if (previousGen2 < diagnostics.gen2) {\n                        generation2.push({ x: now, y: 1 });\n                        previousGen2 = diagnostics.gen2;\n                        previousGen1 = diagnostics.gen1;\n                        previousGen0 = diagnostics.gen0;\n                    }\n                    else if (previousGen1 < diagnostics.gen1) {\n                        generation1.push({ x: now, y: 1 });\n                        previousGen2 = diagnostics.gen2;\n                        previousGen1 = diagnostics.gen1;\n                        previousGen0 = diagnostics.gen0;\n                    }\n                    else if (previousGen0 < diagnostics.gen0) {\n                        generation0.push({ x: now, y: 1 });\n                        previousGen2 = diagnostics.gen2;\n                        previousGen1 = diagnostics.gen1;\n                        previousGen0 = diagnostics.gen0;\n                    }\n\n                    if (allocated.length > maxEntries) {\n                        let firstDate = allocated[0].x;\n\n                        allocated.shift();\n                        workingSet.shift();\n\n                        while (generation0.length > 0 && generation0[0].x < firstDate) generation0.shift();\n                        while (generation1.length > 0 && generation1[0].x < firstDate) generation1.shift();\n                        while (generation2.length > 0 && generation2[0].x < firstDate) generation2.shift();\n                    }\n\n                    memoryChart.update();\n                });\n        }, interval);\n\n        document.getElementById(\"collect-btn\").addEventListener(\"click\", function () { fetch(collectUrl); });\n\n\n    </script>\n</body>\n\n\n\n</html>\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/Program.cs",
    "content": "using Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Hosting;\n\nnamespace MemoryLeak\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateHostBuilder(args).Build().Run();\n        }\n\n        public static IHostBuilder CreateHostBuilder(string[] args) =>\n            Host.CreateDefaultBuilder(args)\n                .ConfigureWebHostDefaults(webBuilder =>\n                {\n                    webBuilder.UseStartup<Startup>();\n                });\n    }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/Properties/launchSettings.json",
    "content": "﻿{\n  \"iisSettings\": {\n    \"windowsAuthentication\": false,\n    \"anonymousAuthentication\": true,\n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:4038\",\n      \"sslPort\": 44340\n    }\n  },\n  \"$schema\": \"http://json.schemastore.org/launchsettings.json\",\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Production\"\n      }\n    },\n    \"MemoryLeak\": {\n      \"commandName\": \"Project\",\n      \"dotnetRunMessages\": \"true\",\n      \"launchBrowser\": true,\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace MemoryLeak\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddRazorPages();\n            services.AddControllers();\n        }\n\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n\n            app.UseRouting();\n\n            app.UseAuthorization();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapRazorPages();\n                endpoints.MapControllers();\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/appsettings.Development.json",
    "content": "{\n  \"DetailedErrors\": true,\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "src/MemoryLeak/MemoryLeak.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 16\r\nVisualStudioVersion = 16.0.31313.381\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"MemoryLeak\", \"MemoryLeak\\MemoryLeak.csproj\", \"{93FD8BB4-223C-4957-8BE0-277290191F11}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|Any CPU = Debug|Any CPU\r\n\t\tRelease|Any CPU = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{93FD8BB4-223C-4957-8BE0-277290191F11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{93FD8BB4-223C-4957-8BE0-277290191F11}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{93FD8BB4-223C-4957-8BE0-277290191F11}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{93FD8BB4-223C-4957-8BE0-277290191F11}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {CA71DCF4-5254-429E-B68E-47B22DD8F1E7}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  }
]