From 37cd6bbf64940ae69e61eeab9b550d9891800e1d Mon Sep 17 00:00:00 2001 From: Alberto Soto Date: Wed, 5 Feb 2025 11:54:00 +0100 Subject: [PATCH] doc: dotnet tracing example --- .../dotnet-span-profiles.md | 4 +- examples/tracing/dotnet/.gitignore | 1 + examples/tracing/dotnet/Dockerfile | 48 ++++++ .../tracing/dotnet/Dockerfile.load-generator | 10 ++ examples/tracing/dotnet/README.md | 53 +++++++ examples/tracing/dotnet/docker-compose.yml | 69 ++++++++ examples/tracing/dotnet/example/.gitignore | 2 + .../tracing/dotnet/example/BikeService.cs | 16 ++ examples/tracing/dotnet/example/CarService.cs | 16 ++ .../tracing/dotnet/example/Example.csproj | 17 ++ .../dotnet/example/Folder.DotSettings.user | 4 + .../tracing/dotnet/example/OrderService.cs | 57 +++++++ examples/tracing/dotnet/example/Program.cs | 147 ++++++++++++++++++ .../tracing/dotnet/example/ScooterService.cs | 39 +++++ .../datasources/pyroscope.yml | 31 ++++ .../plugins/explore-profiles.yml | 7 + examples/tracing/dotnet/load-generator.py | 26 ++++ examples/tracing/dotnet/musl.Dockerfile | 48 ++++++ examples/tracing/dotnet/tempo/tempo.yml | 41 +++++ 19 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 examples/tracing/dotnet/.gitignore create mode 100644 examples/tracing/dotnet/Dockerfile create mode 100644 examples/tracing/dotnet/Dockerfile.load-generator create mode 100644 examples/tracing/dotnet/README.md create mode 100644 examples/tracing/dotnet/docker-compose.yml create mode 100644 examples/tracing/dotnet/example/.gitignore create mode 100644 examples/tracing/dotnet/example/BikeService.cs create mode 100644 examples/tracing/dotnet/example/CarService.cs create mode 100644 examples/tracing/dotnet/example/Example.csproj create mode 100644 examples/tracing/dotnet/example/Folder.DotSettings.user create mode 100644 examples/tracing/dotnet/example/OrderService.cs create mode 100644 examples/tracing/dotnet/example/Program.cs create mode 100644 examples/tracing/dotnet/example/ScooterService.cs create mode 100644 examples/tracing/dotnet/grafana-provisioning/datasources/pyroscope.yml create mode 100644 examples/tracing/dotnet/grafana-provisioning/plugins/explore-profiles.yml create mode 100644 examples/tracing/dotnet/load-generator.py create mode 100644 examples/tracing/dotnet/musl.Dockerfile create mode 100644 examples/tracing/dotnet/tempo/tempo.yml diff --git a/docs/sources/configure-client/trace-span-profiles/dotnet-span-profiles.md b/docs/sources/configure-client/trace-span-profiles/dotnet-span-profiles.md index 75591779f6..e683bf6180 100644 --- a/docs/sources/configure-client/trace-span-profiles/dotnet-span-profiles.md +++ b/docs/sources/configure-client/trace-span-profiles/dotnet-span-profiles.md @@ -80,4 +80,6 @@ Refer to the [Tempo data source configuration documentation](https://grafana.com ## Examples -Check out the [examples](https://github.com/grafana/pyroscope/tree/main/examples/tracing/tempo) directory for a complete demo application of span profiles in multiple languages. +Check out these demo applications for span profiles: +- [.NET example](https://github.com/grafana/pyroscope/tree/main/examples/tracing/dotnet) +- [Other examples](https://github.com/grafana/pyroscope/tree/main/examples/tracing/tempo) in multiple languages diff --git a/examples/tracing/dotnet/.gitignore b/examples/tracing/dotnet/.gitignore new file mode 100644 index 0000000000..140f8cf80f --- /dev/null +++ b/examples/tracing/dotnet/.gitignore @@ -0,0 +1 @@ +*.so diff --git a/examples/tracing/dotnet/Dockerfile b/examples/tracing/dotnet/Dockerfile new file mode 100644 index 0000000000..f1e11e8173 --- /dev/null +++ b/examples/tracing/dotnet/Dockerfile @@ -0,0 +1,48 @@ +ARG SDK_VERSION=8.0 +# The build images takes an SDK image of the buildplatform, so the platform the build is running on. +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:$SDK_VERSION AS build + +ARG TARGETPLATFORM +ARG BUILDPLATFORM +ARG SDK_VERSION + +WORKDIR /dotnet + +ADD example . + +# Set the target framework to SDK_VERSION +RUN sed -i -E 's|.*|net'$SDK_VERSION'|' Example.csproj + +# We hardcode linux-x64 here, as the profiler doesn't support any other platform +RUN dotnet publish -o . --framework net$SDK_VERSION --runtime linux-x64 --no-self-contained + +# This fetches the SDK +FROM --platform=linux/amd64 pyroscope/pyroscope-dotnet:0.9.2-glibc AS sdk + +# Runtime only image of the targetplatfrom, so the platform the image will be running on. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/aspnet:$SDK_VERSION + +WORKDIR /dotnet + +COPY --from=sdk /Pyroscope.Profiler.Native.so ./Pyroscope.Profiler.Native.so +COPY --from=sdk /Pyroscope.Linux.ApiWrapper.x64.so ./Pyroscope.Linux.ApiWrapper.x64.so +COPY --from=build /dotnet/ ./ + + +ENV CORECLR_ENABLE_PROFILING=1 +ENV CORECLR_PROFILER={BD1A650D-AC5D-4896-B64F-D6FA25D6B26A} +ENV CORECLR_PROFILER_PATH=/dotnet/Pyroscope.Profiler.Native.so +ENV LD_PRELOAD=/dotnet/Pyroscope.Linux.ApiWrapper.x64.so + +ENV PYROSCOPE_APPLICATION_NAME=rideshare.dotnet.push.app +ENV PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040 +ENV PYROSCOPE_LOG_LEVEL=debug +ENV PYROSCOPE_PROFILING_ENABLED=1 +ENV PYROSCOPE_PROFILING_ALLOCATION_ENABLED=true +ENV PYROSCOPE_PROFILING_CONTENTION_ENABLED=true +ENV PYROSCOPE_PROFILING_EXCEPTION_ENABLED=true +ENV PYROSCOPE_PROFILING_HEAP_ENABLED=true +ENV RIDESHARE_LISTEN_PORT=5000 + + +CMD sh -c "ASPNETCORE_URLS=http://*:${RIDESHARE_LISTEN_PORT} exec dotnet /dotnet/example.dll" diff --git a/examples/tracing/dotnet/Dockerfile.load-generator b/examples/tracing/dotnet/Dockerfile.load-generator new file mode 100644 index 0000000000..5c55f3d27c --- /dev/null +++ b/examples/tracing/dotnet/Dockerfile.load-generator @@ -0,0 +1,10 @@ +FROM python:3.9 + +RUN pip3 install requests + +COPY load-generator.py ./load-generator.py + +ENV PYTHONUNBUFFERED=1 + +CMD [ "python", "load-generator.py" ] + diff --git a/examples/tracing/dotnet/README.md b/examples/tracing/dotnet/README.md new file mode 100644 index 0000000000..14602f8c5e --- /dev/null +++ b/examples/tracing/dotnet/README.md @@ -0,0 +1,53 @@ +# Span Profiles with Grafana Tempo and Pyroscope + +The docker compose consists of: +- The .NET Rideshare App +- Tempo +- Pyroscope +- Grafana + +The `rideshare` app generate traces and profiling data that should be available in Grafana. +Datasources for Pyroscope and Tempo are provisioned automatically. + +### Build and run + +The project can be run locally with the following commands: + +```shell +# (optionally) pull latest pyroscope and grafana images: +docker pull grafana/pyroscope:latest +docker pull grafana/grafana:latest + +# build and run the example +docker compose up --build +``` + +Navigate to the [Explore page](http://localhost:3000/explore?schemaVersion=1&panes=%7B%22f36%22:%7B%22datasource%22:%22tempo%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22tempo%22,%22uid%22:%22tempo%22%7D,%22queryType%22:%22traceqlSearch%22,%22limit%22:20,%22tableType%22:%22traces%22,%22filters%22:%5B%7B%22id%22:%22e73a615e%22,%22operator%22:%22%3D%22,%22scope%22:%22span%22%7D,%7B%22id%22:%22service-name%22,%22tag%22:%22service.name%22,%22operator%22:%22%3D%22,%22scope%22:%22resource%22,%22value%22:%5B%22rideshare.dotnet.push.app%22%5D,%22valueType%22:%22string%22%7D%5D,%22query%22:%22%7Bresource.service.name%3D%5C%22rideshare.dotnet.push.app%5C%22%7D%22%7D%5D,%22range%22:%7B%22from%22:%22now-15m%22,%22to%22:%22now%22%7D%7D%7D&orgId=1), select a trace and click on a span that has a linked profile: + +![image](https://github.com/grafana/otel-profiling-go/assets/12090599/31e33cd1-818b-4116-b952-c9ec7b1fb593) + +By default, only the root span gets labeled (the first span created locally): such spans are marked with the _link_ icon +and have the `pyroscope.profile.id` attribute set to the corresponding span ID. +Please note that presence of the attribute does not necessarily +indicate that the span has a profile: stack trace samples might not be collected, if the utilized CPU time is +less than the sample interval (10ms). + +### Instrumentation + +The `rideshare` demo application is instrumented with Pyroscope: [Pyroscope .NET Agent](https://github.com/grafana/pyroscope-dotnet) + +### Grafana Tempo configuration + +In order to correlate trace spans with profiling data, the Tempo datasource should have the following configured: +- The profiling data source +- Tags to use when making profiling queries + +![image](https://github.com/grafana/pyroscope/assets/12090599/380ac574-a298-440d-acfb-7bc0935a3a7c) + +While tags are optional, configuring them is highly recommended for optimizing query performance. +In our example, we configured the `service.name` tag for use in Pyroscope queries as the `service_name` label. +This configuration restricts the data set for lookup, ensuring that queries remain +consistently fast. Note that the tags you configure must be present in the span attributes or resources +for a trace to profiles span link to appear. + +Please refer to our [documentation](https://grafana.com/docs/grafana/next/datasources/tempo/configure-tempo-data-source/#trace-to-profiles) for more details. diff --git a/examples/tracing/dotnet/docker-compose.yml b/examples/tracing/dotnet/docker-compose.yml new file mode 100644 index 0000000000..2f54e5199e --- /dev/null +++ b/examples/tracing/dotnet/docker-compose.yml @@ -0,0 +1,69 @@ +services: + pyroscope: + image: grafana/pyroscope + ports: + - "4040:4040" + + us-east: + ports: + - "5000" + environment: &env + OTLP_URL: tempo:4318 + OTEL_TRACES_EXPORTER: otlp + OTEL_EXPORTER_OTLP_ENDPOINT: http://tempo:4317 + OTEL_SERVICE_NAME: rideshare.dotnet.push.app + OTEL_METRICS_EXPORTER: none + OTEL_TRACES_SAMPLER: always_on + OTEL_PROPAGATORS: tracecontext + REGION: us-east + PYROSCOPE_LABELS: region=us-east + PYROSCOPE_SERVER_ADDRESS: http://pyroscope:4040 + build: + context: . + eu-north: + ports: + - "5000" + environment: + <<: *env + REGION: eu-north + build: + context: . + ap-south: + ports: + - "5000" + environment: + <<: *env + REGION: ap-south + build: + context: . + + load-generator: + build: + context: . + dockerfile: Dockerfile.load-generator + + grafana: + image: grafana/grafana:latest + environment: + - GF_INSTALL_PLUGINS=grafana-pyroscope-app + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_FEATURE_TOGGLES_ENABLE=traceToProfiles tracesEmbeddedFlameGraph + volumes: + - ./grafana-provisioning:/etc/grafana/provisioning + ports: + - "3000:3000" + + tempo: + image: grafana/tempo:latest + command: [ "-config.file=/etc/tempo.yml" ] + volumes: + - ./tempo/tempo.yml:/etc/tempo.yml + ports: + - "14268:14268" # jaeger ingest + - "3200:3200" # tempo + - "9095:9095" # tempo grpc + - "4317:4317" # otlp grpc + - "4318:4318" # otlp http + - "9411:9411" # zipkin diff --git a/examples/tracing/dotnet/example/.gitignore b/examples/tracing/dotnet/example/.gitignore new file mode 100644 index 0000000000..cd42ee34e8 --- /dev/null +++ b/examples/tracing/dotnet/example/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/examples/tracing/dotnet/example/BikeService.cs b/examples/tracing/dotnet/example/BikeService.cs new file mode 100644 index 0000000000..009180e01a --- /dev/null +++ b/examples/tracing/dotnet/example/BikeService.cs @@ -0,0 +1,16 @@ +namespace Example; + +internal class BikeService +{ + private readonly OrderService _orderService; + + public BikeService(OrderService orderService) + { + _orderService = orderService; + } + + public void Order(int searchRadius) + { + _orderService.FindNearestVehicle(searchRadius, "bike"); + } +} \ No newline at end of file diff --git a/examples/tracing/dotnet/example/CarService.cs b/examples/tracing/dotnet/example/CarService.cs new file mode 100644 index 0000000000..885b534ee6 --- /dev/null +++ b/examples/tracing/dotnet/example/CarService.cs @@ -0,0 +1,16 @@ +namespace Example; + +internal class CarService +{ + private readonly OrderService _orderService; + + public CarService(OrderService orderService) + { + _orderService = orderService; + } + + public void Order(int searchRadius) + { + _orderService.FindNearestVehicle(searchRadius, "car"); + } +} \ No newline at end of file diff --git a/examples/tracing/dotnet/example/Example.csproj b/examples/tracing/dotnet/example/Example.csproj new file mode 100644 index 0000000000..4f41aee696 --- /dev/null +++ b/examples/tracing/dotnet/example/Example.csproj @@ -0,0 +1,17 @@ + + + net8.0 + example + Exe + example + enable + + + + + + + + + + diff --git a/examples/tracing/dotnet/example/Folder.DotSettings.user b/examples/tracing/dotnet/example/Folder.DotSettings.user new file mode 100644 index 0000000000..13b97375cf --- /dev/null +++ b/examples/tracing/dotnet/example/Folder.DotSettings.user @@ -0,0 +1,4 @@ + + <AssemblyExplorer> + <Assembly Path="/home/korniltsev/.nuget/packages/pyroscope/0.4.0/lib/net6.0/Pyroscope.dll" /> +</AssemblyExplorer> diff --git a/examples/tracing/dotnet/example/OrderService.cs b/examples/tracing/dotnet/example/OrderService.cs new file mode 100644 index 0000000000..2332330ffe --- /dev/null +++ b/examples/tracing/dotnet/example/OrderService.cs @@ -0,0 +1,57 @@ +using System; + +namespace Example; + +internal class OrderService +{ + public void FindNearestVehicle(long searchRadius, string vehicle) + { + lock (_lock) + { + var labels = Pyroscope.LabelSet.Empty.BuildUpon() + .Add("vehicle", vehicle) + .Build(); + Pyroscope.LabelsWrapper.Do(labels, () => + { + for (long i = 0; i < searchRadius * 1000000000; i++) + { + } + + if (vehicle.Equals("car")) + { + CheckDriverAvailability(labels, searchRadius); + } + }); + } + } + + private readonly object _lock = new(); + + private static void CheckDriverAvailability(Pyroscope.LabelSet ctx, long searchRadius) + { + var region = System.Environment.GetEnvironmentVariable("REGION") ?? "unknown_region"; + ctx = ctx.BuildUpon() + .Add("driver_region", region) + .Build(); + Pyroscope.LabelsWrapper.Do(ctx, () => + { + for (long i = 0; i < searchRadius * 1000000000; i++) + { + } + + var now = DateTime.Now.Minute % 2 == 0; + var forceMutexLock = DateTime.Now.Minute % 2 == 0; + if ("eu-north".Equals(region) && forceMutexLock) + { + MutexLock(searchRadius); + } + }); + } + + private static void MutexLock(long searchRadius) + { + for (long i = 0; i < 30 * searchRadius * 1000000000; i++) + { + } + } +} \ No newline at end of file diff --git a/examples/tracing/dotnet/example/Program.cs b/examples/tracing/dotnet/example/Program.cs new file mode 100644 index 0000000000..83beb1a632 --- /dev/null +++ b/examples/tracing/dotnet/example/Program.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Collections; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +using OpenTelemetry.Trace; + +using Pyroscope.OpenTelemetry; + +namespace Example; + +public static class Program +{ + private static readonly List Files = new(); + public const string CustomActivitySourceName = "Example.ScooterService"; + public static void Main(string[] args) + { + for (int i = 0; i < 1024; i++) + { + Files.Add(File.Open("/dev/null", FileMode.Open, FileAccess.Read, FileShare.Read)); + } + object globalLock = new(); + var strings = new List(); + + var orderService = new OrderService(); + var bikeService = new BikeService(orderService); + var scooterService = new ScooterService(orderService); + var carService = new CarService(orderService); + + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddOpenTelemetry() + .WithTracing(b => + { + b + .AddAspNetCoreInstrumentation() + .AddSource(CustomActivitySourceName) + .AddConsoleExporter() + .AddOtlpExporter() + .AddProcessor(new PyroscopeSpanProcessor()); + }); + var app = builder.Build(); + + app.MapGet("/bike", () => + { + bikeService.Order(1); + return "Bike ordered"; + }); + app.MapGet("/scooter", () => + { + scooterService.Order(2); + return "Scooter ordered"; + }); + + app.MapGet("/car", () => + { + carService.Order(3); + return "Car ordered"; + }); + + + app.MapGet("/pyroscope/cpu", (HttpRequest request) => + { + var enable = request.Query["enable"] == "true"; + Pyroscope.Profiler.Instance.SetCPUTrackingEnabled(enable); + return "OK"; + }); + app.MapGet("/pyroscope/allocation", (HttpRequest request) => + { + var enable = request.Query["enable"] == "true"; + Pyroscope.Profiler.Instance.SetAllocationTrackingEnabled(enable); + return "OK"; + }); + app.MapGet("/pyroscope/contention", (HttpRequest request) => + { + var enable = request.Query["enable"] == "true"; + Pyroscope.Profiler.Instance.SetContentionTrackingEnabled(enable); + return "OK"; + }); + app.MapGet("/pyroscope/exception", (HttpRequest request) => + { + var enable = request.Query["enable"] == "true"; + Pyroscope.Profiler.Instance.SetExceptionTrackingEnabled(enable); + return "OK"; + }); + + + app.MapGet("/playground/allocation", (HttpRequest request) => + { + var strings = new List(); + for (var i = 0; i < 10000; i++) + { + strings.Add("foobar" + i); + } + + return "OK"; + }); + app.MapGet("/playground/contention", (HttpRequest request) => + { + for (var i = 0; i < 100; i++) + { + lock (globalLock) + { + Thread.Sleep(10); + } + } + return "OK"; + }); + app.MapGet("/playground/exception", (HttpRequest request) => + { + for (var i = 0; i < 1000; i++) + { + try + { + throw new Exception("foobar" + i); + } + catch (Exception) + { + } + } + return "OK"; + }); + app.MapGet("/playground/leak", (HttpRequest request) => + { + for (var i = 0; i < 1000; i++) + { + strings.Add("leak " + i); + } + return "OK"; + }); + app.MapGet("/", () => + { + string env = ""; + foreach (DictionaryEntry e in System.Environment.GetEnvironmentVariables()) + { + env += e.Key + " = " + e.Value + "
\n"; + } + return env; + }); + + app.Run(); + } +} diff --git a/examples/tracing/dotnet/example/ScooterService.cs b/examples/tracing/dotnet/example/ScooterService.cs new file mode 100644 index 0000000000..a67fdbb993 --- /dev/null +++ b/examples/tracing/dotnet/example/ScooterService.cs @@ -0,0 +1,39 @@ +using System.Diagnostics; + +namespace Example; + +internal class ScooterService +{ + private static readonly ActivitySource CustomActivity = new(Program.CustomActivitySourceName); + + private readonly OrderService _orderService; + + public ScooterService(OrderService orderService) + { + _orderService = orderService; + } + + public void Order(int searchRadius) + { + using var activity = CustomActivity.StartActivity("OrderScooter"); + activity?.SetTag("type", "scooter"); + for (long i = 0; i < 2000000000; i++) + { + } + OrderInternal(searchRadius); + DoSomeOtherWork(); + } + + private void OrderInternal(int searchRadius) + { + using var activity = CustomActivity.StartActivity("OrderScooterInternal"); + _orderService.FindNearestVehicle(searchRadius, "scooter"); + } + + private void DoSomeOtherWork() + { + for (long i = 0; i < 1000000000; i++) + { + } + } +} diff --git a/examples/tracing/dotnet/grafana-provisioning/datasources/pyroscope.yml b/examples/tracing/dotnet/grafana-provisioning/datasources/pyroscope.yml new file mode 100644 index 0000000000..25c0e11477 --- /dev/null +++ b/examples/tracing/dotnet/grafana-provisioning/datasources/pyroscope.yml @@ -0,0 +1,31 @@ +--- +apiVersion: 1 +datasources: + - name: Tempo + type: tempo + access: proxy + orgId: 1 + url: http://tempo:3200 + basicAuth: false + isDefault: true + version: 1 + editable: false + apiVersion: 1 + uid: tempo + jsonData: + httpMethod: GET + serviceMap: + datasourceUid: prometheus + tracesToProfiles: + customQuery: false + datasourceUid: "pyroscope" + profileTypeId: "process_cpu:cpu:nanoseconds:cpu:nanoseconds" + tags: + - key: "service.name" + value: "service_name" + - uid: pyroscope + type: grafana-pyroscope-datasource + name: Pyroscope + url: http://pyroscope:4040 + jsonData: + keepCookies: [pyroscope_git_session] diff --git a/examples/tracing/dotnet/grafana-provisioning/plugins/explore-profiles.yml b/examples/tracing/dotnet/grafana-provisioning/plugins/explore-profiles.yml new file mode 100644 index 0000000000..0b979b4c1c --- /dev/null +++ b/examples/tracing/dotnet/grafana-provisioning/plugins/explore-profiles.yml @@ -0,0 +1,7 @@ +--- +apiVersion: 1 +apps: + - type: grafana-pyroscope-app + jsonData: + backendUrl: http://pyroscope:4040 + secureJsonData: diff --git a/examples/tracing/dotnet/load-generator.py b/examples/tracing/dotnet/load-generator.py new file mode 100644 index 0000000000..792a914cfe --- /dev/null +++ b/examples/tracing/dotnet/load-generator.py @@ -0,0 +1,26 @@ +import random +import requests +import time + +HOSTS = [ + 'us-east', + 'eu-north', + 'ap-south', +] + +VEHICLES = [ + 'bike', + 'scooter', + 'car', +] + +if __name__ == "__main__": + print(f"starting load generator") + time.sleep(3) + while True: + host = HOSTS[random.randint(0, len(HOSTS) - 1)] + vehicle = VEHICLES[random.randint(0, len(VEHICLES) - 1)] + print(f"requesting {vehicle} from {host}") + resp = requests.get(f'http://{host}:5000/{vehicle}') + print(f"received {resp}") + time.sleep(random.uniform(0.2, 0.4)) diff --git a/examples/tracing/dotnet/musl.Dockerfile b/examples/tracing/dotnet/musl.Dockerfile new file mode 100644 index 0000000000..26433b59a1 --- /dev/null +++ b/examples/tracing/dotnet/musl.Dockerfile @@ -0,0 +1,48 @@ +ARG SDK_VERSION=8.0 +# The build images takes an SDK image of the buildplatform, so the platform the build is running on. +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:$SDK_VERSION-alpine AS build + +ARG TARGETPLATFORM +ARG BUILDPLATFORM +ARG SDK_VERSION + +WORKDIR /dotnet + +ADD example . + +# Set the target framework to SDK_VERSION +RUN sed -i -E 's|.*|net'$SDK_VERSION'|' Example.csproj + +# We hardcode linux-x64 here, as the profiler doesn't support any other platform +RUN dotnet publish -o . --framework net$SDK_VERSION --runtime linux-musl-x64 --no-self-contained + +# This fetches the SDK +FROM --platform=linux/amd64 pyroscope/pyroscope-dotnet:0.9.2-musl AS sdk + +# Runtime only image of the targetplatfrom, so the platform the image will be running on. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/aspnet:$SDK_VERSION-alpine + +WORKDIR /dotnet + +COPY --from=sdk /Pyroscope.Profiler.Native.so ./Pyroscope.Profiler.Native.so +COPY --from=sdk /Pyroscope.Linux.ApiWrapper.x64.so ./Pyroscope.Linux.ApiWrapper.x64.so +COPY --from=build /dotnet/ ./ + + +ENV CORECLR_ENABLE_PROFILING=1 +ENV CORECLR_PROFILER={BD1A650D-AC5D-4896-B64F-D6FA25D6B26A} +ENV CORECLR_PROFILER_PATH=/dotnet/Pyroscope.Profiler.Native.so +ENV LD_PRELOAD=/dotnet/Pyroscope.Linux.ApiWrapper.x64.so + +ENV PYROSCOPE_APPLICATION_NAME=rideshare.dotnet.push.app +ENV PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040 +ENV PYROSCOPE_LOG_LEVEL=debug +ENV PYROSCOPE_PROFILING_ENABLED=1 +ENV PYROSCOPE_PROFILING_ALLOCATION_ENABLED=true +ENV PYROSCOPE_PROFILING_CONTENTION_ENABLED=true +ENV PYROSCOPE_PROFILING_EXCEPTION_ENABLED=true +ENV PYROSCOPE_PROFILING_HEAP_ENABLED=true +ENV RIDESHARE_LISTEN_PORT=5000 + + +CMD sh -c "ASPNETCORE_URLS=http://*:${RIDESHARE_LISTEN_PORT} exec dotnet /dotnet/example.dll" diff --git a/examples/tracing/dotnet/tempo/tempo.yml b/examples/tracing/dotnet/tempo/tempo.yml new file mode 100644 index 0000000000..6c536317dd --- /dev/null +++ b/examples/tracing/dotnet/tempo/tempo.yml @@ -0,0 +1,41 @@ +server: + http_listen_port: 3200 + +query_frontend: + search: + duration_slo: 5s + throughput_bytes_slo: 1.073741824e+09 + trace_by_id: + duration_slo: 5s + +distributor: + receivers: # this configuration will listen on all ports and protocols that tempo is capable of. + jaeger: # the receives all come from the OpenTelemetry collector. more configuration information can + protocols: # be found there: https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver + thrift_http: # + grpc: # for a production deployment you should only enable the receivers you need! + thrift_binary: + thrift_compact: + zipkin: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + grpc: + endpoint: 0.0.0.0:4317 + opencensus: + +ingester: + max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally + +compactor: + compaction: + block_retention: 1h # overall Tempo trace retention. set for demo purposes + +storage: + trace: + backend: local # backend configuration to use + wal: + path: /tmp/tempo/wal # where to store the wal locally + local: + path: /tmp/tempo/blocks