diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs new file mode 100644 index 00000000000..453d9095a7f --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -0,0 +1,164 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Threading; + +namespace testwebapi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class DiagScenarioController : ControllerBase + { + object o1 = new object(); + object o2 = new object(); + + private static Processor p = new Processor(); + + [HttpGet] + [Route("deadlock/")] + public ActionResult deadlock() + { + (new System.Threading.Thread(() => { + DeadlockFunc(); + })).Start(); + + Thread.Sleep(5000); + + var threads = new Thread[300]; + for(int i = 0; i < 300; i++) + { + (threads[i] = new Thread(() => { + lock (o1) {Thread.Sleep(100);} + })).Start(); + } + + foreach(Thread thread in threads) + { + thread.Join(); + } + + return "success:deadlock"; + } + + private void DeadlockFunc() + { + lock (o1) + { + (new Thread(() => { + lock (o2) { Monitor.Enter(o1); } + })).Start(); + + Thread.Sleep(2000); + Monitor.Enter(o2); + } + } + + [HttpGet] + [Route("memspike/{seconds}")] + public ActionResult memspike(int seconds) + { + var watch = new Stopwatch(); + watch.Start(); + + while(true) + { + p = new Processor(); + watch.Stop(); + if(watch.ElapsedMilliseconds > seconds*1000) + break; + watch.Start(); + + int it = (2000 * 1000); + for(int i = 0; i < it; i++) + { + p.ProcessTransaction(new Customer(Guid.NewGuid().ToString())); + } + + Thread.Sleep(5000); // Sleep for 5 seconds before cleaning up + + // Cleanup + p = null; + + // GC + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Thread.Sleep(5000); // Sleep for 5 seconds before spiking memory again + } + return "success:memspike"; + } + + [HttpGet] + [Route("memleak/{kb}")] + public ActionResult memleak(int kb) + { + int it = (kb * 1000) / 100; + for(int i = 0; i < it; i++) + { + p.ProcessTransaction(new Customer(Guid.NewGuid().ToString())); + } + + return "success:memleak"; + } + + [HttpGet] + [Route("exception")] + public ActionResult exception() + { + throw new Exception("bad, bad code"); + } + + + [HttpGet] + [Route("highcpu/{milliseconds}")] + public ActionResult highcpu(int milliseconds) + { + var watch = new Stopwatch(); + watch.Start(); + + while (true) + { + watch.Stop(); + if(watch.ElapsedMilliseconds > milliseconds) + break; + watch.Start(); + } + + return "success:highcpu"; + } + } + + class Customer + { + private string id; + + public Customer(string id) + { + this.id = id; + } + } + + class CustomerCache + { + private List cache = new List(); + + public void AddCustomer(Customer c) + { + cache.Add(c); + } + } + + class Processor + { + private CustomerCache cache = new CustomerCache(); + + public void ProcessTransaction(Customer customer) + { + cache.AddCustomer(customer); + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs b/core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs new file mode 100644 index 00000000000..c38be03dc86 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace DiagnosticScenarios.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj b/core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj new file mode 100644 index 00000000000..4fa8377c237 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.0 + + + + + + + diff --git a/core/diagnostics/DiagnosticScenarios/Program.cs b/core/diagnostics/DiagnosticScenarios/Program.cs new file mode 100644 index 00000000000..9269655239d --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DiagnosticScenarios +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json b/core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json new file mode 100644 index 00000000000..ce8acd42589 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:26127", + "sslPort": 44359 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DiagnosticScenarios": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/core/diagnostics/DiagnosticScenarios/Startup.cs b/core/diagnostics/DiagnosticScenarios/Startup.cs new file mode 100644 index 00000000000..3fdcd61484e --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Startup.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DiagnosticScenarios +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddNewtonsoftJson(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/appsettings.Development.json b/core/diagnostics/DiagnosticScenarios/appsettings.Development.json new file mode 100644 index 00000000000..e203e9407e7 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/appsettings.json b/core/diagnostics/DiagnosticScenarios/appsettings.json new file mode 100644 index 00000000000..d9d9a9bff6f --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/core/diagnostics/DiagnosticScenarios/readme.md b/core/diagnostics/DiagnosticScenarios/readme.md new file mode 100644 index 00000000000..f4075602888 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/readme.md @@ -0,0 +1,41 @@ +--- +languages: + - csharp + products: + - dotnet-core +page_type: sample +name: "Diagnostic Scenarios" +urlFragment: "diagnostic-scenarios" +description: "A .NET Core sample with methods that trigger undesirable behaviors to diagnose." +--- +# DiagnosticScenarios sample debug target + +This sample is for the [diagnostics tutorials](https://docs.microsoft.com/dotnet/core/diagnostics/tutorial/diagnostic-scenarios) in the .NET Core Guide. + +The scenarios are implemented using a `webapi` target with methods that trigger undesirable behaviors for us to diagnose. + +## Target methods + +### Deadlock + +http://localhost:5000/api/diagscenario/deadlock + +This method will cause the target to hang and accumulate many threads. + +### High CPU usage + +http://localhost:5000/api/diagscenario/highcpu/{milliseconds} + +The method will cause to target to heavily use the CPU for a duration specified by {milliseconds}. + +### Memory leak + +http://localhost:5000/api/diagscenario/memleak/{kb} + +This method will cause the target to leak memory (amount specified by {kb}). + +### Memory usage spike + +http://localhost:5000/api/diagscenario/memspike/{seconds} + +This method will cause intermittent memory spikes over the specified number of seconds. Memory will go from base line to spike and back to baseline several times.