Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 47a5b9b

Browse files
committed
Add metrics.
1 parent 25c96b3 commit 47a5b9b

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

3132
namespace FirebirdSql.Data.FirebirdClient;
@@ -52,6 +53,7 @@ public sealed class FbCommand : DbCommand, IFbPreparedCommand, IDescriptorFiller
5253
private int _fetchSize;
5354
private Type[] _expectedColumnTypes;
5455
private Activity _currentActivity;
56+
private long _startedAtTicks;
5557

5658
#endregion
5759

@@ -1098,13 +1100,9 @@ internal void Release()
10981100
_statement = null;
10991101
}
11001102

1101-
if (_currentActivity != null)
1102-
{
1103-
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1104-
_currentActivity.Dispose();
1105-
_currentActivity = null;
1106-
}
1103+
TraceCommandStop();
11071104
}
1105+
11081106
Task IFbPreparedCommand.ReleaseAsync(CancellationToken cancellationToken) => ReleaseAsync(cancellationToken);
11091107
internal async Task ReleaseAsync(CancellationToken cancellationToken = default)
11101108
{
@@ -1123,12 +1121,7 @@ internal async Task ReleaseAsync(CancellationToken cancellationToken = default)
11231121
_statement = null;
11241122
}
11251123

1126-
if (_currentActivity != null)
1127-
{
1128-
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1129-
_currentActivity.Dispose();
1130-
_currentActivity = null;
1131-
}
1124+
TraceCommandStop();
11321125
}
11331126

11341127
void IFbPreparedCommand.TransactionCompleted() => TransactionCompleted();
@@ -1356,6 +1349,21 @@ private void TraceCommandStart()
13561349
Debug.Assert(_currentActivity == null);
13571350
if (FbActivitySource.Source.HasListeners())
13581351
_currentActivity = FbActivitySource.CommandStart(this);
1352+
1353+
_startedAtTicks = FbMetricsStore.CommandStart();
1354+
}
1355+
1356+
private void TraceCommandStop()
1357+
{
1358+
if (_currentActivity != null)
1359+
{
1360+
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1361+
_currentActivity.Dispose();
1362+
_currentActivity = null;
1363+
}
1364+
1365+
FbMetricsStore.CommandStop(_startedAtTicks, Connection);
1366+
_startedAtTicks = 0;
13591367
}
13601368

13611369
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

2729
namespace FirebirdSql.Data.FirebirdClient;
2830

@@ -189,6 +191,12 @@ public override string ConnectionString
189191
_options = new ConnectionString(value);
190192
_options.Validate();
191193
_connectionString = value;
194+
195+
MetricsConnectionAttributes = [
196+
new("db.system", "firebird"),
197+
new("db.namespace", _options.Database),
198+
new("server.address", $"{_options.DataSource}:{_options.Port}")
199+
];
192200
}
193201
}
194202
}
@@ -269,6 +277,8 @@ internal bool IsClosed
269277
get { return _state == ConnectionState.Closed; }
270278
}
271279

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

274284
#region Protected Properties
@@ -553,6 +563,7 @@ public override async Task ChangeDatabaseAsync(string databaseName, Cancellation
553563
public override void Open()
554564
{
555565
LogMessages.ConnectionOpening(Log, this);
566+
var startedAtTicks = FbMetricsStore.ConnectionOpening();
556567

557568
if (string.IsNullOrEmpty(_connectionString))
558569
{
@@ -645,10 +656,13 @@ public override void Open()
645656
}
646657

647658
LogMessages.ConnectionOpened(Log, this);
659+
FbMetricsStore.ConnectionOpened(startedAtTicks, this._options.NormalizedConnectionString);
648660
}
661+
649662
public override async Task OpenAsync(CancellationToken cancellationToken)
650663
{
651664
LogMessages.ConnectionOpening(Log, this);
665+
var startedAtTicks = FbMetricsStore.ConnectionOpening();
652666

653667
if (string.IsNullOrEmpty(_connectionString))
654668
{
@@ -741,6 +755,7 @@ public override async Task OpenAsync(CancellationToken cancellationToken)
741755
}
742756

743757
LogMessages.ConnectionOpened(Log, this);
758+
FbMetricsStore.ConnectionOpened(startedAtTicks, this._options.NormalizedConnectionString);
744759
}
745760

746761
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)

AltStyle によって変換されたページ (->オリジナル) /