Last active
April 25, 2025 15:19
-
-
Save Aaronontheweb/45902c48dd7c5802d2a60436b3051aab to your computer and use it in GitHub Desktop.
.NET OpenTelemetry Metrics for TCP Listeners
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"__inputs": [ | |
{ | |
"name": "DS_PROMETHEUS", | |
"label": "Prometheus", | |
"description": "", | |
"type": "datasource", | |
"pluginId": "prometheus", | |
"pluginName": "Prometheus" | |
} | |
], | |
"__elements": {}, | |
"__requires": [ | |
{ | |
"type": "grafana", | |
"id": "grafana", | |
"name": "Grafana", | |
"version": "11.0.0" | |
}, | |
{ | |
"type": "datasource", | |
"id": "prometheus", | |
"name": "Prometheus", | |
"version": "1.0.0" | |
}, | |
{ | |
"type": "panel", | |
"id": "timeseries", | |
"name": "Time series", | |
"version": "" | |
} | |
], | |
"annotations": { | |
"list": [ | |
{ | |
"builtIn": 1, | |
"datasource": { | |
"type": "grafana", | |
"uid": "-- Grafana --" | |
}, | |
"enable": true, | |
"hide": true, | |
"iconColor": "rgba(0, 211, 255, 1)", | |
"name": "Annotations & Alerts", | |
"type": "dashboard" | |
} | |
] | |
}, | |
"description": "Looking at various .NET runtime metrics by namespace inside the solution", | |
"editable": true, | |
"fiscalYearStartMonth": 0, | |
"graphTooltip": 0, | |
"id": null, | |
"links": [], | |
"panels": [ | |
{ | |
"collapsed": false, | |
"gridPos": { | |
"h": 1, | |
"w": 24, | |
"x": 0, | |
"y": 0 | |
}, | |
"id": 5, | |
"panels": [], | |
"title": "TCP", | |
"type": "row" | |
}, | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_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 | |
} | |
] | |
} | |
}, | |
"overrides": [] | |
}, | |
"gridPos": { | |
"h": 8, | |
"w": 12, | |
"x": 0, | |
"y": 1 | |
}, | |
"id": 3, | |
"options": { | |
"legend": { | |
"calcs": [], | |
"displayMode": "list", | |
"placement": "bottom", | |
"showLegend": true | |
}, | |
"tooltip": { | |
"maxHeight": 600, | |
"mode": "single", | |
"sort": "none" | |
} | |
}, | |
"targets": [ | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_PROMETHEUS}" | |
}, | |
"disableTextWrap": false, | |
"editorMode": "builder", | |
"expr": "sum by(exported_instance) (tcp_listeners_active_tcp_listeners{exported_job=\"$workload\"})", | |
"fullMetaSearch": false, | |
"includeNullMetadata": true, | |
"instant": false, | |
"legendFormat": "__auto", | |
"range": true, | |
"refId": "A", | |
"useBackend": false | |
} | |
], | |
"title": "Active TCP Listeners per Workload", | |
"type": "timeseries" | |
}, | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_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 | |
} | |
] | |
} | |
}, | |
"overrides": [] | |
}, | |
"gridPos": { | |
"h": 8, | |
"w": 12, | |
"x": 12, | |
"y": 1 | |
}, | |
"id": 2, | |
"options": { | |
"legend": { | |
"calcs": [], | |
"displayMode": "list", | |
"placement": "bottom", | |
"showLegend": true | |
}, | |
"tooltip": { | |
"maxHeight": 600, | |
"mode": "single", | |
"sort": "none" | |
} | |
}, | |
"targets": [ | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_PROMETHEUS}" | |
}, | |
"disableTextWrap": false, | |
"editorMode": "builder", | |
"expr": "sum by(exported_instance) (tcp_connections_active_tcp_connections{exported_job=\"$workload\"})", | |
"fullMetaSearch": false, | |
"includeNullMetadata": true, | |
"instant": false, | |
"legendFormat": "__auto", | |
"range": true, | |
"refId": "A", | |
"useBackend": false | |
} | |
], | |
"title": "Active TCP Connections Per Workload", | |
"type": "timeseries" | |
}, | |
{ | |
"collapsed": false, | |
"gridPos": { | |
"h": 1, | |
"w": 24, | |
"x": 0, | |
"y": 9 | |
}, | |
"id": 6, | |
"panels": [], | |
"title": "HTTP Client", | |
"type": "row" | |
}, | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_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 | |
} | |
] | |
} | |
}, | |
"overrides": [] | |
}, | |
"gridPos": { | |
"h": 8, | |
"w": 12, | |
"x": 0, | |
"y": 10 | |
}, | |
"id": 1, | |
"options": { | |
"legend": { | |
"calcs": [], | |
"displayMode": "list", | |
"placement": "bottom", | |
"showLegend": true | |
}, | |
"tooltip": { | |
"maxHeight": 600, | |
"mode": "single", | |
"sort": "none" | |
} | |
}, | |
"targets": [ | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_PROMETHEUS}" | |
}, | |
"disableTextWrap": false, | |
"editorMode": "builder", | |
"expr": "sum by(exported_instance) (http_client_open_connections{exported_job=\"$workload\"})", | |
"format": "time_series", | |
"fullMetaSearch": false, | |
"includeNullMetadata": true, | |
"instant": false, | |
"legendFormat": "__auto", | |
"range": true, | |
"refId": "A", | |
"useBackend": false | |
} | |
], | |
"title": "Active HTTP Connections per Workload", | |
"type": "timeseries" | |
}, | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_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 | |
} | |
] | |
} | |
}, | |
"overrides": [] | |
}, | |
"gridPos": { | |
"h": 8, | |
"w": 12, | |
"x": 12, | |
"y": 10 | |
}, | |
"id": 4, | |
"options": { | |
"legend": { | |
"calcs": [], | |
"displayMode": "list", | |
"placement": "bottom", | |
"showLegend": true | |
}, | |
"tooltip": { | |
"maxHeight": 600, | |
"mode": "single", | |
"sort": "none" | |
} | |
}, | |
"targets": [ | |
{ | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_PROMETHEUS}" | |
}, | |
"editorMode": "code", | |
"expr": "sum by(exported_instance) (http_client_active_requests{exported_job=\"$workload\"})", | |
"instant": false, | |
"legendFormat": "__auto", | |
"range": true, | |
"refId": "A" | |
} | |
], | |
"title": "Active HTTP Requests per Workload", | |
"type": "timeseries" | |
} | |
], | |
"refresh": "", | |
"schemaVersion": 39, | |
"tags": [], | |
"templating": { | |
"list": [ | |
{ | |
"current": {}, | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_PROMETHEUS}" | |
}, | |
"definition": "label_values(akka_actor_live_actors,cluster)", | |
"description": "Job exported from OTEL metrics", | |
"hide": 0, | |
"includeAll": false, | |
"label": "cluster", | |
"multi": false, | |
"name": "cluster", | |
"options": [], | |
"query": { | |
"qryType": 1, | |
"query": "label_values(akka_actor_live_actors,cluster)", | |
"refId": "PrometheusVariableQueryEditor-VariableQuery" | |
}, | |
"refresh": 1, | |
"regex": "", | |
"skipUrlSync": false, | |
"sort": 0, | |
"type": "query" | |
}, | |
{ | |
"current": {}, | |
"datasource": { | |
"type": "prometheus", | |
"uid": "${DS_PROMETHEUS}" | |
}, | |
"definition": "label_values(akka_actor_live_actors{cluster=\"$cluster\"},job)", | |
"description": "Job exported from OTEL metrics", | |
"hide": 0, | |
"includeAll": false, | |
"label": "Workload", | |
"multi": false, | |
"name": "workload", | |
"options": [], | |
"query": { | |
"qryType": 1, | |
"query": "label_values(akka_actor_live_actors{cluster=\"$cluster\"},job)", | |
"refId": "PrometheusVariableQueryEditor-VariableQuery" | |
}, | |
"refresh": 1, | |
"regex": "", | |
"skipUrlSync": false, | |
"sort": 0, | |
"type": "query" | |
} | |
] | |
}, | |
"time": { | |
"from": "now-6h", | |
"to": "now" | |
}, | |
"timeRangeUpdatedDuringEditOrView": false, | |
"timepicker": {}, | |
"timezone": "browser", | |
"title": ".NET HTTP Client and TCP Metrics by OTEL Workload", | |
"uid": "bdpaeeu4xyuioc", | |
"version": 10, | |
"weekStart": "" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ----------------------------------------------------------------------- | |
// <copyright file="TcpInstrumentation.cs" company="Petabridge, LLC"> | |
// Copyright (C) 2024 - 2024 Petabridge, LLC <https://petabridge.com> | |
// </copyright> | |
// ----------------------------------------------------------------------- | |
using System.Net; | |
using System.Net.NetworkInformation; | |
using OpenTelemetry.Metrics; | |
using static OpenTelemetry.TcpInstrumentation.TcpInstrumentationMeter; | |
namespace OpenTelemetry.TcpInstrumentation; | |
/// <summary> | |
/// Publicly available instrumentation code for tracking TCP connections and listeners | |
/// </summary> | |
public static class TcpInstrumentation | |
{ | |
/// <summary> | |
/// Subscribe to TCP connection and listener events and track them using OpenTelemetry metrics. | |
/// </summary> | |
/// <param name="builder"></param> | |
/// <param name="onlyCareAboutTheseEndpoints">Optional. When populated, we will only report | |
/// about activity involving these endpoints.</param> | |
/// <remarks> | |
/// This method will automatically start tracking TCP connection and listener activity in the background - captures | |
/// everything happening in the current environment. | |
/// </remarks> | |
public static MeterProviderBuilder AddTcpConnectionInstrumentation(this MeterProviderBuilder builder, | |
IPEndPoint[]? onlyCareAboutTheseEndpoints = null) | |
{ | |
builder.AddMeter(MeterName); | |
// these will just run in the background | |
var (connectionsTracker, tcpListenersTracker) = TrackTcpActivity(KeepConnectionData, KeepListenerData); | |
return builder; | |
bool KeepListenerData(IPEndPoint endpoint) | |
{ | |
if (onlyCareAboutTheseEndpoints is null || onlyCareAboutTheseEndpoints.Length == 0) return true; | |
foreach (var caredAboutEndpoint in onlyCareAboutTheseEndpoints) | |
{ | |
if (caredAboutEndpoint.Equals(endpoint)) return true; | |
} | |
return false; | |
} | |
bool KeepConnectionData(TcpConnectionInformation connection) | |
{ | |
if (onlyCareAboutTheseEndpoints is null || onlyCareAboutTheseEndpoints.Length == 0) return true; | |
foreach (var endpoint in onlyCareAboutTheseEndpoints) | |
{ | |
if (connection.LocalEndPoint.Equals(endpoint) || connection.RemoteEndPoint.Equals(endpoint)) return true; | |
} | |
return false; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics; | |
using System.Diagnostics.Metrics; | |
using System.Net; | |
using System.Net.NetworkInformation; | |
namespace OpenTelemetry.TcpInstrumentation; | |
/// <summary> | |
/// INTERNAL API - private implementation details for tracking TCP connections and listeners | |
/// </summary> | |
internal static class TcpInstrumentationMeter | |
{ | |
public const string MeterName = "OpenTelemetry.TcpInstrumentation"; | |
public static readonly Meter Meter = new Meter(MeterName); | |
public const string ActiveTcpConnectionsName = "tcp.connections.active"; | |
public const string TcpConnectionsUnit = "tcp_connections"; | |
public const string ActiveTcpListenersName = "tcp.listeners.active"; | |
public const string TcpListenersUnit = "tcp_listeners"; | |
private static readonly Predicate<TcpConnectionInformation> DefaultTcpConnectionKeepFn = information => true; | |
private static readonly Predicate<IPEndPoint> DefaultListeningEndpointKeepFn = ip => true; | |
public static (ObservableGauge<int> connectionsTracker, ObservableGauge<int> tcpListenersTracker) TrackTcpActivity( | |
Predicate<TcpConnectionInformation>? keepConnectionData = null, | |
Predicate<IPEndPoint>? keepListenerData = null) | |
{ | |
var connectionsTracker = TrackActiveTcpConnections(ActiveTcpConnectionsName, "Active TCP Connections", keepData: keepConnectionData); | |
var tcpListenersTracker = TrackActiveTcpListeners(ActiveTcpListenersName, "Active TCP Listeners", keepData:keepListenerData); | |
return (connectionsTracker, tcpListenersTracker); | |
} | |
public static ObservableGauge<int> TrackActiveTcpConnections(string metricName, string description, | |
string units = TcpConnectionsUnit, Predicate<TcpConnectionInformation>? keepData = null) | |
=> Meter.CreateObservableGauge<int>(metricName, | |
() => | |
{ | |
keepData ??= DefaultTcpConnectionKeepFn; | |
var rawMeasurements = new Dictionary<TcpState, int>(); | |
var realMeasurements = Array.Empty<Measurement<int>>(); | |
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); | |
TcpConnectionInformation[] connections = properties.GetActiveTcpConnections(); | |
// for each connection, if the connection is to be kept, add it to the measurements | |
foreach (var connection in connections) | |
if (keepData(connection)) | |
{ | |
rawMeasurements.TryAdd(connection.State, 0); | |
rawMeasurements[connection.State]++; | |
} | |
// bail out early if we have no data | |
if (rawMeasurements.Count == 0) | |
return realMeasurements; | |
realMeasurements = new Measurement<int>[rawMeasurements.Count]; | |
var i = 0; | |
foreach (var (state, count) in rawMeasurements) | |
{ | |
// some boxing here, but it's fine | |
var tag = new KeyValuePair<string, object?>("tcp.state", state); | |
realMeasurements[i] = new Measurement<int>(count, tag); | |
i++; | |
} | |
return realMeasurements; | |
}, units, description); | |
public static ObservableGauge<int> TrackActiveTcpListeners(string metricName, string description, | |
string units = TcpListenersUnit, Predicate<IPEndPoint>? keepData = null) | |
=> Meter.CreateObservableGauge<int>(metricName, | |
() => | |
{ | |
keepData ??= DefaultListeningEndpointKeepFn; | |
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); | |
var endPoints = properties.GetActiveTcpListeners().Where(c => keepData(c)); | |
return endPoints.Select(c => | |
{ | |
var tags = new[] | |
{ | |
new KeyValuePair<string, object?>("listener_addr", c.Address), | |
new KeyValuePair<string, object?>("listener_port", c.Port), | |
new KeyValuePair<string, object?>("ip_family", c.AddressFamily) | |
}; | |
return new Measurement<int>(1, tags); | |
}); | |
}, units, description); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment