Skip to content

Commit

Permalink
Merge pull request #4 from habbes/dotnet-perf-analysis-intro
Browse files Browse the repository at this point in the history
Dotnet perf analysis intro
  • Loading branch information
habbes authored Jan 27, 2025
2 parents 5330ab9 + 822e6d4 commit f2698eb
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 1 deletion.
30 changes: 30 additions & 0 deletions DotNetPerfAnalysisIntro/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
22 changes: 22 additions & 0 deletions DotNetPerfAnalysisIntro/DotNetPerfAnalysisIntro.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35707.178 d17.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApi", "SampleApi\SampleApi.csproj", "{33834EF1-FF41-4B00-8279-9AF2861C6E20}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{33834EF1-FF41-4B00-8279-9AF2861C6E20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33834EF1-FF41-4B00-8279-9AF2861C6E20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33834EF1-FF41-4B00-8279-9AF2861C6E20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33834EF1-FF41-4B00-8279-9AF2861C6E20}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
6 changes: 6 additions & 0 deletions DotNetPerfAnalysisIntro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# .NET Performance analysis intro

This sample project aims to demonstrate how to analyze and improve the performance of a .NET
service using Visual Studio profilers.

In this exercise we attempt to improve the latency and throughput of a simple API.
37 changes: 37 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/DataReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace SampleApi;

internal class DataReader
{
public static async Task<List<LocationData>> ReadDataAsync(Stream input)
{
List<LocationData> data = new();
var reader = new StreamReader(input);

string content = await reader.ReadToEndAsync();

var lines = content.Split(Environment.NewLine);

foreach (var line in lines)
{
var words = line.Split(" ");
string name = default;
double temperature = default;
foreach (var word in words)
{
if (word.StartsWith("location:"))
{
name = word.Split(':')[1];
}
else if (word.StartsWith("temp:"))
{
temperature = double.Parse(word.Split(':')[1]);
}
}

var locationData = new LocationData(name!, temperature);
data.Add(locationData);
}

return data;
}
}
54 changes: 54 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/DataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace SampleApi;

internal class DataStore
{
private Dictionary<string, RecordStats> cache = new();

public void Update(IEnumerable<LocationData> data)
{
lock (cache)
{
foreach (var item in data)
{
if (cache.TryGetValue(item.Name, out RecordStats stats))
{
stats.Count++;
stats.Sum += item.Temperature;
stats.Max = Math.Max(stats.Max, item.Temperature);
stats.Min = Math.Min(stats.Min, item.Temperature);
}
else
{
cache[item.Name] = new RecordStats
{
Count = 1,
Sum = item.Temperature,
Max = item.Temperature,
Min = item.Temperature
};
}
}
}
}

public LocationStats GetStats(string location)
{
lock (cache)
{
if (cache.TryGetValue(location, out RecordStats stats))
{
return new LocationStats(location, stats.Max, stats.Min, stats.Sum / stats.Count);
}
}

return new LocationStats(location, 0, 0, 0);
}

class RecordStats
{
public int Count { get; set; }
public double Sum { get; set; }
public double Max { get; set; }
public double Min { get; set; }
}
}
30 changes: 30 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.

# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081


# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["SampleApi/SampleApi.csproj", "SampleApi/"]
RUN dotnet restore "./SampleApi/SampleApi.csproj"
COPY . .
WORKDIR "/src/SampleApi"
RUN dotnet build "./SampleApi.csproj" -c $BUILD_CONFIGURATION -o /app/build

# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./SampleApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "SampleApi.dll"]
5 changes: 5 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace SampleApi;

record LocationData(string Name, double Temperature);

record LocationStats(string Name, double MaxTemperature, double MinTemperature, double MeanTemperature);
16 changes: 16 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using SampleApi;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<DataStore>();
var app = builder.Build();

app.MapPost("/readings", async (DataStore store, HttpRequest request) => {
var data = await DataReader.ReadDataAsync(request.Body);
store.Update(data);

return data;
});

app.MapGet("/readings/{location}", (DataStore store, string location) => store.GetStats(location));

app.Run();
34 changes: 34 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5208"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7121;http://localhost:5208"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}
15 changes: 15 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/SampleApi.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>39974002-6ee7-4e20-9bbc-abf4fc53fe51</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
35 changes: 35 additions & 0 deletions DotNetPerfAnalysisIntro/SampleApi/sample_requests.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# For more info on HTTP files go to https://aka.ms/vs/httpfile


POST http://localhost:5208/readings
Content-Type: text/plain
Accept-Language: en-US,en;q=0.5

location:Nairobi temp:24.5
location:Kisumu temp:30.5
location:Mombasa temp:28.5
location:Nairobi temp:30

###


POST http://localhost:5208/readings
Content-Type: text/plain
Accept-Language: en-US,en;q=0.5

location:Mombasa temp:26.6
location:Mombasa temp:6.6
location:Mombasa temp:34.9
location:Dublin temp:33.6
location:Nairobi temp:3.1
location:Dublin temp:36.1
location:Mombasa temp:29.0
location:Mombasa temp:21.0
location:Mombasa temp:9.5
location:Dublin temp:41.0

###


GET http://localhost:5208/readings/Nairobi

37 changes: 37 additions & 0 deletions DotNetPerfAnalysisIntro/load-tests/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { sleep } from 'k6';
import http from 'k6/http';

const BASE_URL = __ENV.BASE_URL || "http://localhost:5208";

const places = ['Nairobi', 'Mombasa', 'Dublin', 'Redmond'];

function generateValue(size) {

let s = '';

for (let i = 0; i < size; i++) {
const place = places[Math.floor(Math.random() * places.length)];
const temp = (Math.random() * 50).toFixed(1);
s += `location:${place} temp:${temp}\n`;
}

return s;
}

const data = generateValue(10000);

export default function () {

http.post(`${BASE_URL}/readings`, data, {
headers: {
'Content-Type': 'text/plain'
}
});

// sleep(1);

http.get(`${BASE_URL}/readings/Nairobi`);

// sleep(1);
}

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
- [.NET's `JsonSerializer` large memory allocations when serializing large string and byte array values](./JsonSerializerLargeValuesMemoryAllocations/)
- [Sample Data Structure Analysis](./SampleDataStructureAnalysis/)
- [OData schema change analyzer](./ODataModelChangeAnalyzer/)
- [OData Lightweight URI parser](./ODataSlimUrlParserConcept/)
- [OData Lightweight URI parser](./ODataSlimUrlParserConcept/)
- [.NET performance analysis intro](./DotNetPerfAnalysisIntro/)

0 comments on commit f2698eb

Please sign in to comment.