Skip to content

Commit 7fe1327

Browse files
committed
Add metrics.
1 parent 7dd65e8 commit 7fe1327

File tree

4 files changed

+169
-12
lines changed

4 files changed

+169
-12
lines changed

src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using System.Threading.Tasks;
2727
using FirebirdSql.Data.Common;
2828
using FirebirdSql.Data.Logging;
29+
using FirebirdSql.Data.Metrics;
2930
using FirebirdSql.Data.Trace;
3031
using Microsoft.Extensions.Logging;
3132

@@ -53,6 +54,7 @@ public sealed class FbCommand : DbCommand, IFbPreparedCommand, IDescriptorFiller
5354
private int _fetchSize;
5455
private Type[] _expectedColumnTypes;
5556
private Activity _currentActivity;
57+
private long _startedAtTicks;
5658

5759
#endregion
5860

@@ -1068,13 +1070,9 @@ internal void Release()
10681070
_statement = null;
10691071
}
10701072

1071-
if (_currentActivity != null)
1072-
{
1073-
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1074-
_currentActivity.Dispose();
1075-
_currentActivity = null;
1076-
}
1073+
TraceCommandStop();
10771074
}
1075+
10781076
Task IFbPreparedCommand.ReleaseAsync(CancellationToken cancellationToken) => ReleaseAsync(cancellationToken);
10791077
internal async Task ReleaseAsync(CancellationToken cancellationToken = default)
10801078
{
@@ -1093,12 +1091,7 @@ internal async Task ReleaseAsync(CancellationToken cancellationToken = default)
10931091
_statement = null;
10941092
}
10951093

1096-
if (_currentActivity != null)
1097-
{
1098-
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1099-
_currentActivity.Dispose();
1100-
_currentActivity = null;
1101-
}
1094+
TraceCommandStop();
11021095
}
11031096

11041097
void IFbPreparedCommand.TransactionCompleted() => TransactionCompleted();
@@ -1326,6 +1319,21 @@ private void TraceCommandStart()
13261319
Debug.Assert(_currentActivity == null);
13271320
if (FbActivitySource.Source.HasListeners())
13281321
_currentActivity = FbActivitySource.CommandStart(this);
1322+
1323+
_startedAtTicks = FbMetricsStore.CommandStart();
1324+
}
1325+
1326+
private void TraceCommandStop()
1327+
{
1328+
if (_currentActivity != null)
1329+
{
1330+
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1331+
_currentActivity.Dispose();
1332+
_currentActivity = null;
1333+
}
1334+
1335+
FbMetricsStore.CommandStop(_startedAtTicks, Connection);
1336+
_startedAtTicks = 0;
13291337
}
13301338

13311339
private void TraceCommandException(Exception e)

src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnection.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
//$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net)
1717

1818
using System;
19+
using System.Collections.Generic;
1920
using System.ComponentModel;
2021
using System.Data;
2122
using System.Data.Common;
2223
using System.Threading;
2324
using System.Threading.Tasks;
2425
using FirebirdSql.Data.Common;
2526
using FirebirdSql.Data.Logging;
27+
using FirebirdSql.Data.Metrics;
2628
using Microsoft.Extensions.Logging;
2729

2830
namespace FirebirdSql.Data.FirebirdClient;
@@ -190,6 +192,12 @@ public override string ConnectionString
190192
_options = new ConnectionString(value);
191193
_options.Validate();
192194
_connectionString = value;
195+
196+
MetricsConnectionAttributes = [
197+
new("db.system", "firebird"),
198+
new("db.namespace", _options.Database),
199+
new("server.address", $"{_options.DataSource}:{_options.Port}")
200+
];
193201
}
194202
}
195203
}
@@ -270,6 +278,8 @@ internal bool IsClosed
270278
get { return _state == ConnectionState.Closed; }
271279
}
272280

281+
internal KeyValuePair<string, object>[] MetricsConnectionAttributes;
282+
273283
#endregion
274284

275285
#region Protected Properties
@@ -524,6 +534,7 @@ public override async Task ChangeDatabaseAsync(string databaseName, Cancellation
524534
public override void Open()
525535
{
526536
LogMessages.ConnectionOpening(Log, this);
537+
var startedAtTicks = FbMetricsStore.ConnectionOpening();
527538

528539
if (string.IsNullOrEmpty(_connectionString))
529540
{
@@ -616,10 +627,13 @@ public override void Open()
616627
}
617628

618629
LogMessages.ConnectionOpened(Log, this);
630+
FbMetricsStore.ConnectionOpened(startedAtTicks, this._options.NormalizedConnectionString);
619631
}
632+
620633
public override async Task OpenAsync(CancellationToken cancellationToken)
621634
{
622635
LogMessages.ConnectionOpening(Log, this);
636+
var startedAtTicks = FbMetricsStore.ConnectionOpening();
623637

624638
if (string.IsNullOrEmpty(_connectionString))
625639
{
@@ -712,6 +726,7 @@ public override async Task OpenAsync(CancellationToken cancellationToken)
712726
}
713727

714728
LogMessages.ConnectionOpened(Log, this);
729+
FbMetricsStore.ConnectionOpened(startedAtTicks, this._options.NormalizedConnectionString);
715730
}
716731

717732
public override void Close()

src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnectionPoolManager.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ static long GetTicks()
167167
var ticks = Environment.TickCount;
168168
return ticks + -(long)int.MinValue;
169169
}
170+
171+
internal int AvailableCount => _available.Count;
172+
internal int BusyCount => _busy.Count;
173+
internal int MaxSize => _connectionString.MaxPoolSize;
170174
}
171175

172176
int _disposed;
@@ -220,6 +224,12 @@ internal void ClearPool(ConnectionString connectionString)
220224
}
221225
}
222226

227+
internal Dictionary<string, (int idleCount, int busyCount, int maxSize)> GetMetrics() =>
228+
_pools.ToDictionary(
229+
kvp => kvp.Key,
230+
kvp => (kvp.Value.AvailableCount, kvp.Value.BusyCount, kvp.Value.MaxSize)
231+
);
232+
223233
public void Dispose()
224234
{
225235
if (Interlocked.Exchange(ref _disposed, 1) == 1)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Diagnostics.Metrics;
5+
using System.Linq;
6+
using FirebirdSql.Data.FirebirdClient;
7+
8+
namespace FirebirdSql.Data.Metrics
9+
{
10+
internal static class FbMetricsStore
11+
{
12+
private const string ConnectionPoolNameAttributeName = "db.client.connection.pool.name";
13+
private const string ConnectionStateAttributeName = "db.client.connection.state";
14+
private const string ConnectionStateIdleValue = "idle";
15+
private const string ConnectionStateUsedValue = "used";
16+
17+
internal static readonly Meter Source = new("FirebirdSql.Data", "1.0.0");
18+
19+
static readonly Histogram<double> OperationDuration;
20+
static readonly Histogram<double> ConnectionCreateTime;
21+
22+
static FbMetricsStore()
23+
{
24+
// Reference: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md
25+
26+
OperationDuration = Source.CreateHistogram<double>(
27+
"db.client.operation.duration",
28+
unit: "s",
29+
description: "Duration of database client operations."
30+
);
31+
32+
Source.CreateObservableUpDownCounter(
33+
"db.client.connection.count",
34+
GetConnectionCount,
35+
unit: "{connection}",
36+
description: "The number of connections that are currently in state described by the 'state' attribute."
37+
);
38+
39+
// db.client.connection.idle.max
40+
// The maximum number of idle open connections allowed
41+
42+
// db.client.connection.idle.min
43+
// The minimum number of idle open connections allowed
44+
45+
Source.CreateObservableUpDownCounter(
46+
"db.client.connection.max",
47+
GetConnectionMax,
48+
unit: "{connection}",
49+
description: "The maximum number of open connections allowed."
50+
);
51+
52+
// db.client.connection.pending_requests
53+
// The number of current pending requests for an open connection
54+
55+
// db.client.connection.timeouts
56+
// The number of connection timeouts that have occurred trying to obtain a connection from the pool
57+
58+
ConnectionCreateTime = Source.CreateHistogram<double>(
59+
"db.client.connection.create_time",
60+
unit: "s",
61+
description: "The time it took to create a new connection."
62+
);
63+
64+
// db.client.connection.wait_time
65+
// The time it took to obtain an open connection from the pool
66+
67+
// db.client.connection.use_time
68+
// The time between borrowing a connection and returning it to the pool
69+
}
70+
71+
internal static long CommandStart() => Stopwatch.GetTimestamp();
72+
73+
internal static void CommandStop(long startedAtTicks, FbConnection connection)
74+
{
75+
if (OperationDuration.Enabled && startedAtTicks > 0)
76+
{
77+
var elapsedTicks = Stopwatch.GetTimestamp() - startedAtTicks;
78+
var elapsedSeconds = TimeSpan.FromTicks(elapsedTicks).TotalSeconds;
79+
80+
OperationDuration.Record(elapsedSeconds, connection.MetricsConnectionAttributes);
81+
}
82+
}
83+
84+
internal static long ConnectionOpening() => Stopwatch.GetTimestamp();
85+
86+
internal static void ConnectionOpened(long startedAtTicks, string poolName)
87+
{
88+
if (ConnectionCreateTime.Enabled && startedAtTicks > 0)
89+
{
90+
var elapsedTicks = Stopwatch.GetTimestamp() - startedAtTicks;
91+
var elapsedSeconds = TimeSpan.FromTicks(elapsedTicks).TotalSeconds;
92+
93+
ConnectionCreateTime.Record(elapsedSeconds, [new(ConnectionPoolNameAttributeName, poolName)]);
94+
}
95+
}
96+
97+
static IEnumerable<Measurement<int>> GetConnectionCount() =>
98+
FbConnectionPoolManager.Instance.GetMetrics()
99+
.SelectMany(kvp => new List<Measurement<int>>
100+
{
101+
new(
102+
kvp.Value.idleCount,
103+
new(ConnectionPoolNameAttributeName, kvp.Key),
104+
new(ConnectionStateAttributeName, ConnectionStateIdleValue)
105+
),
106+
107+
new(
108+
kvp.Value.busyCount,
109+
new(ConnectionPoolNameAttributeName, kvp.Key),
110+
new(ConnectionStateAttributeName, ConnectionStateUsedValue)
111+
),
112+
});
113+
114+
static IEnumerable<Measurement<int>> GetConnectionMax() =>
115+
FbConnectionPoolManager.Instance.GetMetrics()
116+
.SelectMany(kvp => new List<Measurement<int>>
117+
{
118+
new(
119+
kvp.Value.maxSize,
120+
[new(ConnectionPoolNameAttributeName, kvp.Key)]
121+
),
122+
});
123+
}
124+
}

0 commit comments

Comments
 (0)