Skip to content

Instantly share code, notes, and snippets.

@Aaronontheweb
Last active April 25, 2025 15:19
Show Gist options
  • Save Aaronontheweb/45902c48dd7c5802d2a60436b3051aab to your computer and use it in GitHub Desktop.
Save Aaronontheweb/45902c48dd7c5802d2a60436b3051aab to your computer and use it in GitHub Desktop.
.NET OpenTelemetry Metrics for TCP Listeners
{
"__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": ""
}
// -----------------------------------------------------------------------
// <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;
}
}
}
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