diff --git a/README.md b/README.md index 63df0cc..6a93aa4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ ---- -ArtifactType: nupkg. -Language: csharp. -Tags: SLI, OpenTelemetry, Metrics. ---- - # Service Level Indicators +[![Build](https://github.com/xavierjohn/ServiceLevelIndicators/actions/workflows/build.yml/badge.svg)](https://github.com/xavierjohn/ServiceLevelIndicators/actions/workflows/build.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + Service level indicators (SLIs) are metrics used to measure the performance of a service. They are typically used in the context of service level agreements (SLAs), which are contracts between a service provider and its customers that define the expected level of service. @@ -27,14 +24,14 @@ By tracking SLIs over time, service providers can identify trends and make impro ## Service Level Indicator Library **ServiceLevelIndicators** library will help emit latency metrics for each API operation to help monitor the service performance over time. -The metrics is emitted via standard [.NET Meter Class](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter?view=net-7.0). +The metrics are emitted via the standard [.NET Meter Class](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter). By default, a meter named `ServiceLevelIndicator` with instrument name `operation.duration` is added to the service metrics. The metrics are emitted with the following [attributes](https://opentelemetry.io/docs/specs/otel/common/#attribute). - CustomerResourceId - A value that helps identity the customer, customer group or calling service. - LocationId - The location where the service running. eg. Public cloud, West US 3 region. [Azure Core](https://learn.microsoft.com/en-us/dotnet/api/azure.core.azurelocation?view=azure-dotnet) - Operation - The name of the operation. -- activity.status.code - The activity status code is set based on the success or failure of the operation. [ActivityStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitystatuscode?view=net-7.0). +- activity.status.code - The activity status code is set based on the success or failure of the operation. [ActivityStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitystatuscode). **ServiceLevelIndicators.Asp** adds the following dimensions. @@ -78,27 +75,45 @@ Difference between ServiceLevelIndicator and http.server.request.duration [![NuGet Package](https://img.shields.io/nuget/v/ServiceLevelIndicators.Asp.ApiVersioning.svg)](https://www.nuget.org/packages/ServiceLevelIndicators.Asp.ApiVersioning) +## Installation + +```shell +dotnet add package ServiceLevelIndicators +``` + +For ASP.NET Core: + +```shell +dotnet add package ServiceLevelIndicators.Asp +``` + +For API Versioning support: + +```shell +dotnet add package ServiceLevelIndicators.Asp.ApiVersioning +``` + ## Usage for Web API MVC 1. Register SLI with open telemetry by calling `AddServiceLevelIndicatorInstrumentation`. - Example. + Example: - ``` csharp - builder.Services.AddOpenTelemetry() - .ConfigureResource(configureResource) - .WithMetrics(builder => - { - builder.AddServiceLevelIndicatorInstrumentation(); - builder.AddOtlpExporter(); - }); + ```csharp + builder.Services.AddOpenTelemetry() + .ConfigureResource(configureResource) + .WithMetrics(builder => + { + builder.AddServiceLevelIndicatorInstrumentation(); + builder.AddOtlpExporter(); + }); ``` -2. Add ServiceLevelIndicator, into the dependency injection. AddMvc() is required for overrides present in SLI attributes to take effect. +2. Add ServiceLevelIndicator into the dependency injection. `AddMvc()` is required for overrides present in SLI attributes to take effect. - Example. + Example: - ``` csharp + ```csharp builder.Services.AddServiceLevelIndicator(options => { options.LocationId = ServiceLevelIndicator.CreateLocationId("public", AzureLocation.WestUS3.Name); @@ -108,7 +123,7 @@ Difference between ServiceLevelIndicator and http.server.request.duration 3. Add the middleware to the pipeline. - ``` csharp + ```csharp app.UseServiceLevelIndicator(); ``` @@ -116,53 +131,51 @@ Difference between ServiceLevelIndicator and http.server.request.duration 1. Register SLI with open telemetry by calling `AddServiceLevelIndicatorInstrumentation`. - Example. + Example: - - ``` csharp - builder.Services.AddOpenTelemetry() - .ConfigureResource(configureResource) - .WithMetrics(builder => - { - builder.AddServiceLevelIndicatorInstrumentation(); - builder.AddOtlpExporter(); - }); + ```csharp + builder.Services.AddOpenTelemetry() + .ConfigureResource(configureResource) + .WithMetrics(builder => + { + builder.AddServiceLevelIndicatorInstrumentation(); + builder.AddOtlpExporter(); + }); ``` 2. Add ServiceLevelIndicator into the dependency injection. - Example. - - ``` csharp + Example: + ```csharp builder.Services.AddServiceLevelIndicator(options => { options.LocationId = ServiceLevelIndicator.CreateLocationId("public", AzureLocation.WestUS3.Name); }); - ``` -3. Add the middleware to the ASP.NET core pipeline. +3. Add the middleware to the ASP.NET Core pipeline. - Example. + Example: - ``` csharp + ```csharp app.UseServiceLevelIndicator(); ``` -4. To each API route mapping, add `AddServiceLevelIndicator()` +4. To each API route mapping, add `AddServiceLevelIndicator()`. - Example. + Example: - ``` csharp + ```csharp app.MapGet("/hello", () => "Hello World!") .AddServiceLevelIndicator(); ``` ### Usage for background jobs -You can measure a block of code by boxing it in a using clause of MeasuredOperation. -Example. +You can measure a block of code by wrapping it in a `using` clause of `MeasuredOperation`. + +Example: ```csharp async Task MeasureCodeBlock(ServiceLevelIndicator serviceLevelIndicator) @@ -181,9 +194,9 @@ eg GET WeatherForecast/Action1 - To add API versioning as a dimension use package `ServiceLevelIndicators.Asp.ApiVersioning` and enrich the metrics with `AddApiVersion`. - Example. + Example: - ``` csharp + ```csharp builder.Services.AddServiceLevelIndicator(options => { /// Options @@ -194,9 +207,9 @@ eg GET WeatherForecast/Action1 - To add HTTP method as a dimension, add `AddHttpMethod` to Service Level Indicator. - Example. + Example: - ``` csharp + ```csharp builder.Services.AddServiceLevelIndicator(options => { /// Options @@ -205,12 +218,12 @@ eg GET WeatherForecast/Action1 .AddHttpMethod(); ``` -- Enrich SLI with `Enrich` callback. The callback receives a `MeasuredOperation` as context that can be used to set to `CustomerResourceId` or additional attributes. -An async version `EnrichAsync` is also available. +- Enrich SLI with the `Enrich` callback. The callback receives a `MeasuredOperation` as context that can be used to set `CustomerResourceId` or additional attributes. + An async version `EnrichAsync` is also available. - Example. + Example: - ``` csharp + ```csharp builder.Services.AddServiceLevelIndicator(options => { options.LocationId = ServiceLevelIndicator.CreateLocationId(Cloud, Region); @@ -225,11 +238,11 @@ An async version `EnrichAsync` is also available. }); ``` -- To override the default operation name add the attribute `[ServiceLevelIndicator]` and specify the operation name. +- To override the default operation name, add the attribute `[ServiceLevelIndicator]` and specify the operation name. - Example. + Example: - ``` csharp + ```csharp [HttpGet("MyAction2")] [ServiceLevelIndicator(Operation = "MyNewOperationName")] public IEnumerable GetOperation() => GetWeather(); @@ -269,7 +282,7 @@ An async version `EnrichAsync` is also available. measuredOperation.AddAttribute("CustomAttribute", value); ``` - You can add additional dimensions to the SLI data by using the `Measure` attribute. + You can add additional dimensions to the SLI data by using the `Measure` attribute. Parameters decorated with `[Measure]` are automatically added as metric attributes (dimensions) using the parameter name as the attribute key. ```csharp [HttpGet("name/{first}/{surname}")] @@ -289,16 +302,16 @@ An async version `EnrichAsync` is also available. - To measure a process, run it within a `using StartMeasuring` block. - Example. + Example: - ``` csharp - public void StoreItem(MyDomainEvent domainEvent) - { + ```csharp + public void StoreItem(MyDomainEvent domainEvent) + { var attribute = new KeyValuePair("Event", domainEvent.GetType().Name); using var measuredOperation = _serviceLevelIndicator.StartMeasuring("StoreItem", attribute); DoTheWork(); - ) - ``` + } + ``` ### Sample @@ -311,7 +324,7 @@ To view the metrics locally using the [.NET Aspire Dashboard](https://aspire.dev docker run --rm -it -d -p 18888:18888 -p 4317:18889 -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true -e DASHBOARD__OTLP__AUTHMODE=Unsecured --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest ``` 2. Run the sample web API project and call the `GET WeatherForecast` using the Open API UI. -3. Open `http://localhost:18888` to view the dashboard. You should see the SLI metrics under the meter `operation_duration_milliseconds_bucket` where the `Operation = "GET WeatherForecast"`, `http.response.status.code = 200`, `LocationId = "ms-loc://az/public/westus2"`, `activity.status.code = Ok` -![SLI](assets/prometheus.jpg) +3. Open `http://localhost:18888` to view the dashboard. You should see the SLI metrics under the instrument `operation.duration` where `Operation = "GET WeatherForecast"`, `http.response.status.code = 200`, `LocationId = "ms-loc://az/public/westus2"`, `activity.status.code = Ok`. +![SLI](assets/aspire.jpg) 4. If you run the sample with API Versioning, you will see something similar to the following. ![SLI](assets/versioned.jpg) \ No newline at end of file diff --git a/ServiceLevelIndicators.Asp.ApiVersioning/src/README.md b/ServiceLevelIndicators.Asp.ApiVersioning/src/README.md new file mode 100644 index 0000000..8c3faa8 --- /dev/null +++ b/ServiceLevelIndicators.Asp.ApiVersioning/src/README.md @@ -0,0 +1,41 @@ +# ServiceLevelIndicators.Asp.ApiVersioning + +[![NuGet Package](https://img.shields.io/nuget/v/ServiceLevelIndicators.Asp.ApiVersioning.svg)](https://www.nuget.org/packages/ServiceLevelIndicators.Asp.ApiVersioning) + +Adds **API version** as a metric dimension to [ServiceLevelIndicators.Asp](https://www.nuget.org/packages/ServiceLevelIndicators.Asp) SLI metrics. Works with the [Asp.Versioning](https://github.com/dotnet/aspnet-api-versioning) package. + +## Installation + +```shell +dotnet add package ServiceLevelIndicators.Asp.ApiVersioning +``` + +> Requires [ServiceLevelIndicators.Asp](https://www.nuget.org/packages/ServiceLevelIndicators.Asp) (installed automatically as a dependency). + +## Usage + +Chain `AddApiVersion()` onto the SLI builder: + +```csharp +builder.Services.AddServiceLevelIndicator(options => +{ + options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3"); +}) +.AddMvc() +.AddApiVersion(); +``` + +This registers `ApiVersionEnrichment`, which reads the resolved API version from the request and emits it as an attribute on every SLI measurement. + +## Emitted Attribute + +| Attribute | Description | +|-----------|-------------| +| `http.api.version` | The resolved API version string (e.g. `1.0`, `2024-01-15`), `Neutral`, or `Unspecified` | + +This attribute is added alongside all the standard attributes emitted by `ServiceLevelIndicators.Asp` (`Operation`, `CustomerResourceId`, `LocationId`, `activity.status.code`, `http.response.status.code`). + +## Further Reading + +- [Full documentation and samples](https://github.com/xavierjohn/ServiceLevelIndicators) +- [ASP.NET API Versioning](https://github.com/dotnet/aspnet-api-versioning) diff --git a/ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicators.Asp.ApiVersioning.csproj b/ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicators.Asp.ApiVersioning.csproj index b652af9..4f0bcfd 100644 --- a/ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicators.Asp.ApiVersioning.csproj +++ b/ServiceLevelIndicators.Asp.ApiVersioning/src/ServiceLevelIndicators.Asp.ApiVersioning.csproj @@ -4,7 +4,7 @@ ServiceLevelIndicators - + diff --git a/ServiceLevelIndicators.Asp/src/README.md b/ServiceLevelIndicators.Asp/src/README.md new file mode 100644 index 0000000..4ea9c81 --- /dev/null +++ b/ServiceLevelIndicators.Asp/src/README.md @@ -0,0 +1,143 @@ +# ServiceLevelIndicators.Asp + +[![NuGet Package](https://img.shields.io/nuget/v/ServiceLevelIndicators.Asp.svg)](https://www.nuget.org/packages/ServiceLevelIndicators.Asp) + +ASP.NET Core middleware that **automatically emits Service Level Indicator (SLI) latency metrics** for every API operation. Built on the [ServiceLevelIndicators](https://www.nuget.org/packages/ServiceLevelIndicators) core library and the standard [System.Diagnostics.Metrics](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics) API. + +For API versioning support, add [ServiceLevelIndicators.Asp.ApiVersioning](https://www.nuget.org/packages/ServiceLevelIndicators.Asp.ApiVersioning). + +## Installation + +```shell +dotnet add package ServiceLevelIndicators.Asp +``` + +## Quick Start — MVC Controllers + +```csharp +// 1. Register with OpenTelemetry +builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddServiceLevelIndicatorInstrumentation(); + metrics.AddOtlpExporter(); + }); + +// 2. Configure SLI — AddMvc() enables attribute-based overrides +builder.Services.AddServiceLevelIndicator(options => +{ + options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3"); +}) +.AddMvc(); + +// 3. Add the middleware +app.UseServiceLevelIndicator(); +``` + +## Quick Start — Minimal APIs + +```csharp +builder.Services.AddServiceLevelIndicator(options => +{ + options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3"); +}); + +app.UseServiceLevelIndicator(); + +// Mark individual endpoints +app.MapGet("/hello", () => "Hello World!") + .AddServiceLevelIndicator(); +``` + +## Emitted Metrics + +A meter named `ServiceLevelIndicator` with instrument `operation.duration` (milliseconds) is emitted with the following attributes: + +| Attribute | Description | +|-----------|-------------| +| `Operation` | Defaults to the route template, e.g. `GET WeatherForecast` | +| `CustomerResourceId` | Identifies the customer or caller | +| `LocationId` | Where the service is running | +| `activity.status.code` | `Ok` (2xx), `Error` (5xx), or `Unset` (other) | +| `http.response.status.code` | The HTTP response status code | +| `http.request.method` | *(Optional)* The HTTP method — enabled via `AddHttpMethod()` | + +## Customizations + +### Add HTTP method as a dimension + +```csharp +builder.Services.AddServiceLevelIndicator(options => { /* ... */ }) + .AddMvc() + .AddHttpMethod(); +``` + +### Enrich with custom data + +Use `Enrich` (or `EnrichAsync`) to set `CustomerResourceId` or add custom attributes: + +```csharp +builder.Services.AddServiceLevelIndicator(options => { /* ... */ }) + .AddMvc() + .Enrich(context => + { + var upn = context.HttpContext.User.Claims + .FirstOrDefault(c => c.Type == "upn")?.Value ?? "Unknown"; + context.SetCustomerResourceId(upn); + context.AddAttribute("UserPrincipalName", upn); + }); +``` + +### Override the operation name + +```csharp +[HttpGet("MyAction")] +[ServiceLevelIndicator(Operation = "MyCustomName")] +public IActionResult Get() => Ok(); +``` + +### Set CustomerResourceId from a route parameter + +```csharp +[HttpGet("get-by-zip-code/{zipCode}")] +public IActionResult GetByZipcode([CustomerResourceId] string zipCode) => Ok(); +``` + +Or imperatively: + +```csharp +HttpContext.GetMeasuredOperation().CustomerResourceId = customerResourceId; +``` + +### Add custom attributes from route parameters + +Parameters decorated with `[Measure]` are automatically added as metric dimensions: + +```csharp +[HttpGet("name/{first}/{surname}")] +public IActionResult Get([Measure] string first, [CustomerResourceId] string surname) => Ok(); +``` + +### Add custom attributes manually + +```csharp +HttpContext.GetMeasuredOperation().AddAttribute("CustomKey", value); + +// Safe version for middleware (won't throw if SLI is not configured for the route) +if (HttpContext.TryGetMeasuredOperation(out var op)) + op.AddAttribute("CustomKey", value); +``` + +### Opt-in mode + +To disable automatic SLI emission on all controllers: + +```csharp +options.AutomaticallyEmitted = false; +``` + +Then add `[ServiceLevelIndicator]` only to the controllers you want measured. + +## Further Reading + +- [Full documentation and samples](https://github.com/xavierjohn/ServiceLevelIndicators) diff --git a/ServiceLevelIndicators.Asp/src/ServiceLevelIndicators.Asp.csproj b/ServiceLevelIndicators.Asp/src/ServiceLevelIndicators.Asp.csproj index fd0bb30..2f1b0e2 100644 --- a/ServiceLevelIndicators.Asp/src/ServiceLevelIndicators.Asp.csproj +++ b/ServiceLevelIndicators.Asp/src/ServiceLevelIndicators.Asp.csproj @@ -4,7 +4,7 @@ ServiceLevelIndicators - + diff --git a/ServiceLevelIndicators/src/README.md b/ServiceLevelIndicators/src/README.md new file mode 100644 index 0000000..8b3cf68 --- /dev/null +++ b/ServiceLevelIndicators/src/README.md @@ -0,0 +1,83 @@ +# ServiceLevelIndicators + +[![NuGet Package](https://img.shields.io/nuget/v/ServiceLevelIndicators.svg)](https://www.nuget.org/packages/ServiceLevelIndicators) + +A .NET library for emitting **Service Level Indicator (SLI)** latency metrics via the standard [System.Diagnostics.Metrics](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics) API. Use it to measure operation duration across any .NET application — console apps, background services, worker processes, and more. + +For ASP.NET Core applications, see [ServiceLevelIndicators.Asp](https://www.nuget.org/packages/ServiceLevelIndicators.Asp). + +## Installation + +```shell +dotnet add package ServiceLevelIndicators +``` + +## Quick Start + +### 1. Register with OpenTelemetry + +```csharp +builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddServiceLevelIndicatorInstrumentation(); + metrics.AddOtlpExporter(); + }); +``` + +### 2. Configure options + +```csharp +builder.Services.AddServiceLevelIndicator(options => +{ + options.LocationId = ServiceLevelIndicator.CreateLocationId("public", "westus3"); + options.CustomerResourceId = "my-customer"; +}); +``` + +### 3. Measure operations + +Wrap any block of code in a `using StartMeasuring` scope: + +```csharp +async Task DoWork(ServiceLevelIndicator sli) +{ + using var op = sli.StartMeasuring("ProcessOrder"); + // Do work... + op.SetActivityStatusCode(ActivityStatusCode.Ok); +} +``` + +You can also pass custom attributes: + +```csharp +var attribute = new KeyValuePair("Event", "OrderCreated"); +using var op = sli.StartMeasuring("ProcessOrder", attribute); +``` + +## Emitted Metrics + +A meter named `ServiceLevelIndicator` with instrument `operation.duration` (milliseconds) is emitted with the following attributes: + +| Attribute | Description | +|-----------|-------------| +| `Operation` | The name of the measured operation | +| `CustomerResourceId` | Identifies the customer, customer group, or calling service | +| `LocationId` | Where the service is running (e.g. `ms-loc://az/public/westus3`) | +| `activity.status.code` | `Ok`, `Error`, or `Unset` based on the operation outcome | + +## Key APIs + +| Type / Method | Description | +|---------------|-------------| +| `ServiceLevelIndicator.StartMeasuring(operation, attributes)` | Start a measured operation scope | +| `MeasuredOperation.SetActivityStatusCode(code)` | Set the outcome status | +| `MeasuredOperation.AddAttribute(name, value)` | Add a custom metric attribute | +| `MeasuredOperation.CustomerResourceId` | Get/set the customer resource ID | +| `ServiceLevelIndicator.CreateLocationId(cloud, region?, zone?)` | Helper to build a location ID string | +| `ServiceLevelIndicator.CreateCustomerResourceId(guid)` | Helper to build a customer resource ID from a service tree GUID | + +## Further Reading + +- [Full documentation and samples](https://github.com/xavierjohn/ServiceLevelIndicators) +- [OpenTelemetry .NET](https://opentelemetry.io/docs/languages/dotnet/) diff --git a/ServiceLevelIndicators/src/ServiceLevelIndicators.csproj b/ServiceLevelIndicators/src/ServiceLevelIndicators.csproj index 7aad4a3..f3a13da 100644 --- a/ServiceLevelIndicators/src/ServiceLevelIndicators.csproj +++ b/ServiceLevelIndicators/src/ServiceLevelIndicators.csproj @@ -3,7 +3,7 @@ README.md - + diff --git a/assets/prometheus.jpg b/assets/aspire.jpg similarity index 100% rename from assets/prometheus.jpg rename to assets/aspire.jpg diff --git a/sample/ConsoleApp/SampleConsoleSLI.csproj b/sample/ConsoleApp/SampleConsoleSLI.csproj index 4bc87ed..f836afe 100644 --- a/sample/ConsoleApp/SampleConsoleSLI.csproj +++ b/sample/ConsoleApp/SampleConsoleSLI.csproj @@ -5,6 +5,7 @@ net10.0 enable enable + false diff --git a/sample/Service Level Indicator Grafana.json b/sample/Service Level Indicator Grafana.json deleted file mode 100644 index 45ff640..0000000 --- a/sample/Service Level Indicator Grafana.json +++ /dev/null @@ -1,1212 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "description": "ASP.NET Core metrics using Service Level Indicator library from OpenTelemetry", - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": 19924, - "graphTooltip": 0, - "id": 4, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "dark-green", - "mode": "continuous-GrYlRd", - "seriesBy": "max" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMin": 0, - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 50, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [ - { - "options": { - "match": "null+nan", - "result": { - "index": 1, - "text": "0 ms" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "s", - "unitScale": true - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "p99.9" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 0 - }, - "id": 40, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "min", - "max" - ], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.50, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "legendFormat": "p50", - "range": true, - "refId": "p50" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.75, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "legendFormat": "p75", - "range": true, - "refId": "p75" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.90, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "legendFormat": "p90", - "range": true, - "refId": "p90" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.95, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "legendFormat": "p95", - "range": true, - "refId": "p95" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.98, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "legendFormat": "p98", - "range": true, - "refId": "p98" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.99, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "legendFormat": "p99", - "range": true, - "refId": "p99" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "histogram_quantile(0.999, sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))", - "hide": false, - "legendFormat": "p99.9", - "range": true, - "refId": "p99.9" - } - ], - "title": "Requests Duration", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic", - "seriesBy": "max" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 50, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [ - { - "options": { - "match": "null+nan", - "result": { - "index": 1, - "text": "0%" - } - }, - "type": "special" - } - ], - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "percentunit", - "unitScale": true - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "All" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-orange", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "4XX" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "5XX" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-red", - "mode": "fixed" - } - } - ] - } - ] - }, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 0 - }, - "id": 47, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "min", - "max" - ], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\", http_response_status_code=~\"4..|5..\"}[$__rate_interval]) or vector(0)) / sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))", - "legendFormat": "All", - "range": true, - "refId": "All" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\", http_response_status_code=~\"4..\"}[$__rate_interval]) or vector(0)) / sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))", - "hide": false, - "legendFormat": "4XX", - "range": true, - "refId": "4XX" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\", http_response_status_code=~\"5..\"}[$__rate_interval]) or vector(0)) / sum(rate(ServiceLevelIndicator_milliseconds_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))", - "hide": false, - "legendFormat": "5XX", - "range": true, - "refId": "5XX" - } - ], - "title": "Errors Rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 90, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unitScale": true - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 0, - "y": 9 - }, - "id": 49, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "count(group(present_over_time(ServiceLevelIndicator_milliseconds_count[5m])) by (CustomerResourceId))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "legendFormat": "Users", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Unique active users", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unitScale": true - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Requests" - }, - "properties": [ - { - "id": "custom.width", - "value": 300 - }, - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "gauge" - } - }, - { - "id": "color", - "value": { - "mode": "continuous-BlPu" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Endpoint" - }, - "properties": [ - { - "id": "links", - "value": [ - { - "targetBlank": false, - "title": "Test", - "url": "/d/NagEsjE4z/asp-net-core-endpoint-details?var-route=${__data.fields.http_route}&var-method=${__data.fields.http_request_method}&${__url_time_range}" - } - ] - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "http_route" - }, - "properties": [ - { - "id": "custom.hidden", - "value": true - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "http_request_method" - }, - "properties": [ - { - "id": "custom.hidden", - "value": true - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 6, - "x": 6, - "y": 9 - }, - "hideTimeOverride": false, - "id": 51, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Value" - } - ] - }, - "pluginVersion": "10.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "exemplar": false, - "expr": " topk(10,\r\n sum by (Operation) (max_over_time(ServiceLevelIndicator_milliseconds_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval])))", - "format": "table", - "instant": true, - "interval": "", - "legendFormat": "{{route}}", - "range": false, - "refId": "A" - } - ], - "title": "Top 10 Requested Endpoints", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "method": false, - "route": false - }, - "indexByName": { - "Time": 0, - "Value": 4, - "method": 2, - "method_route": 3, - "route": 1 - }, - "renameByName": { - "Value": "Requests", - "method": "", - "method_route": "Endpoint", - "route": "" - } - } - } - ], - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "blue", - "mode": "fixed" - }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unitScale": true - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 9 - }, - "id": 58, - "options": { - "colorMode": "background", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "10.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(ServiceLevelIndicator_milliseconds_count{job=\"$job\", instance=\"$instance\"})", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Total Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "dark-red", - "mode": "fixed" - }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unitScale": true - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 18, - "y": 9 - }, - "id": 59, - "options": { - "colorMode": "background", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "10.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(ServiceLevelIndicator_milliseconds_count{job=\"$job\", instance=\"$instance\", activity_status_code=\"Error\"})", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Total Unhandled Exceptions", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unitScale": true - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Requests" - }, - "properties": [ - { - "id": "custom.width", - "value": 300 - }, - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "gauge" - } - }, - { - "id": "color", - "value": { - "mode": "continuous-YlRd" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Endpoint" - }, - "properties": [ - { - "id": "links", - "value": [ - { - "title": "", - "url": "/d/NagEsjE4z/asp-net-core-endpoint-details?var-route=${__data.fields.http_route}&var-method=${__data.fields.http_request_method}&${__url_time_range}" - } - ] - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "http_route" - }, - "properties": [ - { - "id": "custom.hidden", - "value": true - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "http_request_method" - }, - "properties": [ - { - "id": "custom.hidden", - "value": true - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 13 - }, - "hideTimeOverride": false, - "id": 54, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Value" - } - ] - }, - "pluginVersion": "10.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "topk(10, sum by(Operation) (max_over_time(ServiceLevelIndicator_milliseconds_count{job=\"$job\", instance=\"$instance\", activity_status_code=\"Error\"}[$__rate_interval])))", - "format": "table", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "interval": "", - "legendFormat": "{{route}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Top 10 Unhandled Exception Endpoints", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "method": false - }, - "indexByName": { - "Time": 0, - "Value": 4, - "method": 2, - "method_route": 3, - "route": 1 - }, - "renameByName": { - "Value": "Requests", - "method": "", - "method_route": "Endpoint", - "route": "" - } - } - } - ], - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unitScale": true - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 17 - }, - "id": 60, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "disableTextWrap": false, - "editorMode": "code", - "expr": "sum(increase(ServiceLevelIndicator_milliseconds_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A", - "useBackend": false - } - ], - "title": "Traffic", - "type": "timeseries" - } - ], - "refresh": "", - "revision": 1, - "schemaVersion": 39, - "tags": [ - "dotnet", - "prometheus", - "aspnetcore" - ], - "templating": { - "list": [ - { - "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" - }, - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "definition": "label_values(http_server_active_requests,job)", - "hide": 0, - "includeAll": false, - "label": "Job", - "multi": false, - "name": "job", - "options": [], - "query": { - "query": "label_values(http_server_active_requests,job)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "type": "query" - }, - { - "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" - }, - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "definition": "label_values(http_server_active_requests{job=~\"$job\"},instance)", - "hide": 0, - "includeAll": false, - "label": "Instance", - "multi": false, - "name": "instance", - "options": [], - "query": { - "query": "label_values(http_server_active_requests{job=~\"$job\"},instance)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "type": "query" - } - ] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "1s", - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "Service Level Indicator", - "uid": "KdDACDp4z", - "version": 1, - "weekStart": "" -} \ No newline at end of file diff --git a/sample/grafana.cmd b/sample/grafana.cmd deleted file mode 100644 index 189568a..0000000 --- a/sample/grafana.cmd +++ /dev/null @@ -1 +0,0 @@ -docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm \ No newline at end of file