Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Profiler] Add gen2 leak scenario #6071

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,51 @@

using System;
using System.Collections.Generic;
using System.Threading;
using BuggyBits.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace BuggyBits.Controllers
{

public class NewsController : Controller
{
#pragma warning disable IDE0052 // Remove unread private members | this field is used to better show memory leaks
private readonly int[] bits = new int[25000];
#pragma warning restore IDE0052
private static int _id = 0;
private int _instanceId;

//#pragma warning disable IDE0052 // Remove unread private members | this field is used to better show memory leaks
// private readonly int[] bits = new int[25000];
//#pragma warning restore IDE0052
private IMemoryCache cache;
private DateTime _creationTime;

public NewsController(IMemoryCache cache)
{
_creationTime = DateTime.Now;
_instanceId = Interlocked.Increment(ref _id);
this.cache = cache;
GC.Collect();
}

~NewsController()
{
Console.WriteLine($"{DateTime.Now.ToShortTimeString()} | {(DateTime.Now -_creationTime).TotalSeconds, 4} - ~NewsController #{_instanceId}");
}

public IActionResult Index()
{
string key = Guid.NewGuid().ToString();
var cachedResult = cache.GetOrCreate(key, cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(5);
////Adding a sliding expiration will help to evict cache entries sooner
////but the LOH will become fragmented
//cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(5);

cacheEntry.RegisterPostEvictionCallback(CacheRemovedCallback);
cacheEntry.Priority = CacheItemPriority.NeverRemove;

return new string("New site launched 2008-02-02");
return new string($"New site launched " + DateTime.Now);
});

var news = new List<News>
Expand All @@ -45,6 +61,10 @@ public IActionResult Index()

private void CacheRemovedCallback(object key, object value, EvictionReason reason, object state)
{
if (reason == EvictionReason.Capacity)
{
Console.WriteLine($"Cache entry {key} = '{value}' was removed due to capacity");
}
}
}
}
64 changes: 55 additions & 9 deletions profiler/src/Demos/Samples.BuggyBits/SelfInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ public class SelfInvoker : IDisposable
private readonly HttpClient _httpClient;
private readonly Scenario _scenario;
private readonly int _nbIdleThreads;

public SelfInvoker(CancellationToken token, Scenario scenario, int nbIdleThreds)
private readonly int _nbNewsThreads;
public SelfInvoker(CancellationToken token, Scenario scenario, int nbIdleThreads)
{
_exitToken = token;
_httpClient = new HttpClient();
_scenario = scenario;
_nbIdleThreads = nbIdleThreds;
_nbIdleThreads = nbIdleThreads;
_nbNewsThreads = 6;
}

public void Dispose()
Expand All @@ -46,6 +47,8 @@ public async Task RunAsync(string rootUrl, int iterations = 0)
}
else
{
StartThreadsForLeaks(rootUrl, iterations);

try
{
List<string> asyncEndpoints = GetEndpoints(rootUrl);
Expand Down Expand Up @@ -75,6 +78,49 @@ public async Task RunAsync(string rootUrl, int iterations = 0)
Console.WriteLine($"{this.GetType().Name} stopped.");
}

private void StartThreadsForLeaks(string rootUrl, int iterations)
{
if ((_scenario & Scenario.MemoryLeak) == 0)
{
return;
}

// Each LOH region is 32 MB so if we want to see the RSS/committed bytes to grow,
// it is needed to allocate 32 x 100 KB. But this is not correct because the GC will
// use 1 LOH heap per core. So, we need to allocate 32 x 100 KB x nbCores.
// Let's tune it for a slower growth. Remove the LOH buffer to allow longer lived application.
// Note: use DOTNET_GCHeapCount=nbHeaps to set a limited number of heaps and see the RSS/Private bytes grow.
// Also, we don't want to allocate the same object too many time in a row to avoid
// forcing the sampling of these allocations.
for (var i = 0; i < _nbNewsThreads; i++)
{
Task.Factory.StartNew(
async () =>
{
var guid = Guid.NewGuid();
var bytes = guid.ToByteArray();
int randomIncrement = bytes[0];
TimeSpan delay = TimeSpan.FromMilliseconds(300 + randomIncrement);
Console.WriteLine($"Delay for leak = {delay}");
await Task.Delay(delay);

// Run for the given number of iterations
// 0 means wait for cancellation
int current = 0;
while (
((iterations == 0) && !_exitToken.IsCancellationRequested) ||
(iterations > current))
{
await ExecuteIterationAsync($"{rootUrl}/News");
await Task.Delay(TimeSpan.FromMilliseconds(1000 + delay.TotalMilliseconds));
current++;
}

},
creationOptions: TaskCreationOptions.LongRunning);
}
}

private void CreateIdleThreads()
{
if (_nbIdleThreads == 0)
Expand Down Expand Up @@ -129,11 +175,6 @@ private List<string> GetEndpoints(string rootUrl)
urls.Add($"{rootUrl}/Products/ParallelLock");
}

if ((_scenario & Scenario.MemoryLeak) == Scenario.MemoryLeak)
{
urls.Add($"{rootUrl}/News");
}

if ((_scenario & Scenario.EndpointsCount) == Scenario.EndpointsCount)
{
urls.Add($"{rootUrl}/End.Point.With.Dots");
Expand Down Expand Up @@ -163,7 +204,12 @@ private async Task ExecuteIterationAsync(string endpointUrl)
string responsePayload = await response.Content.ReadAsStringAsync();
int responseLen = responsePayload.Length;
sw.Stop();
Console.WriteLine($"{endpointUrl} | response length = {responseLen} in {sw.ElapsedMilliseconds} ms");

// hide traces when memory leaks
if ((_scenario & Scenario.MemoryLeak) == 0)
{
Console.WriteLine($"{endpointUrl} | response length = {responseLen} in {sw.ElapsedMilliseconds} ms");
}
}
catch (TaskCanceledException)
{
Expand Down
Loading