Kit.Core/LibExternal/Npgsql/NpgsqlEventSource.cs

237 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
namespace Npgsql;
sealed class NpgsqlEventSource : EventSource
{
public static readonly NpgsqlEventSource Log = new();
const string EventSourceName = "Npgsql";
internal const int CommandStartId = 3;
internal const int CommandStopId = 4;
#if !NETSTANDARD2_0
IncrementingPollingCounter? _bytesWrittenPerSecondCounter;
IncrementingPollingCounter? _bytesReadPerSecondCounter;
IncrementingPollingCounter? _commandsPerSecondCounter;
PollingCounter? _totalCommandsCounter;
PollingCounter? _failedCommandsCounter;
PollingCounter? _currentCommandsCounter;
PollingCounter? _preparedCommandsRatioCounter;
PollingCounter? _poolsCounter;
PollingCounter? _idleConnectionsCounter;
PollingCounter? _busyConnectionsCounter;
PollingCounter? _multiplexingAverageCommandsPerBatchCounter;
PollingCounter? _multiplexingAverageWriteTimePerBatchCounter;
#endif
long _bytesWritten;
long _bytesRead;
long _totalCommands;
long _totalPreparedCommands;
long _currentCommands;
long _failedCommands;
readonly object _poolsLock = new();
readonly HashSet<ConnectorSource> _pools = new();
long _multiplexingBatchesSent;
long _multiplexingCommandsSent;
long _multiplexingTicksWritten;
internal NpgsqlEventSource() : base(EventSourceName) {}
// NOTE
// - The 'Start' and 'Stop' suffixes on the following event names have special meaning in EventSource. They
// enable creating 'activities'.
// For more information, take a look at the following blog post:
// https://blogs.msdn.microsoft.com/vancem/2015/09/14/exploring-eventsource-activity-correlation-and-causation-features/
// - A stop event's event id must be next one after its start event.
internal void BytesWritten(long bytesWritten) => Interlocked.Add(ref _bytesWritten, bytesWritten);
internal void BytesRead(long bytesRead) => Interlocked.Add(ref _bytesRead, bytesRead);
public void CommandStart(string sql)
{
Interlocked.Increment(ref _totalCommands);
Interlocked.Increment(ref _currentCommands);
NpgsqlSqlEventSource.Log.CommandStart(sql);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void CommandStop()
{
Interlocked.Decrement(ref _currentCommands);
NpgsqlSqlEventSource.Log.CommandStop();
}
internal void CommandStartPrepared() => Interlocked.Increment(ref _totalPreparedCommands);
internal void CommandFailed() => Interlocked.Increment(ref _failedCommands);
internal void PoolCreated(ConnectorSource pool)
{
lock (_poolsLock)
{
_pools.Add(pool);
}
}
internal void MultiplexingBatchSent(int numCommands, Stopwatch stopwatch)
{
// TODO: CAS loop instead of 3 separate interlocked operations?
Interlocked.Increment(ref _multiplexingBatchesSent);
Interlocked.Add(ref _multiplexingCommandsSent, numCommands);
Interlocked.Add(ref _multiplexingTicksWritten, stopwatch.ElapsedTicks);
}
#if !NETSTANDARD2_0
double GetIdleConnections()
{
// Note: there's no attempt here to be coherent in terms of race conditions, especially not with regards
// to different counters. So idle and busy and be unsynchronized, as they're not polled together.
lock (_poolsLock)
{
var sum = 0;
foreach (var pool in _pools)
{
sum += pool.Statistics.Idle;
}
return sum;
}
}
double GetBusyConnections()
{
// Note: there's no attempt here to be coherent in terms of race conditions, especially not with regards
// to different counters. So idle and busy and be unsynchronized, as they're not polled together.
lock (_poolsLock)
{
var sum = 0;
foreach (var pool in _pools)
{
sum += pool.Statistics.Busy;
}
return sum;
}
}
double GetPoolsCount()
{
lock (_poolsLock)
{
return _pools.Count;
}
}
double GetMultiplexingAverageCommandsPerBatch()
{
var batchesSent = Interlocked.Read(ref _multiplexingBatchesSent);
if (batchesSent == 0)
return -1;
var commandsSent = (double)Interlocked.Read(ref _multiplexingCommandsSent);
return commandsSent / batchesSent;
}
double GetMultiplexingAverageWriteTimePerBatch()
{
var batchesSent = Interlocked.Read(ref _multiplexingBatchesSent);
if (batchesSent == 0)
return -1;
var ticksWritten = (double)Interlocked.Read(ref _multiplexingTicksWritten);
return ticksWritten / batchesSent / 1000;
}
protected override void OnEventCommand(EventCommandEventArgs command)
{
if (command.Command == EventCommand.Enable)
{
// Comment taken from RuntimeEventSource in CoreCLR
// NOTE: These counters will NOT be disposed on disable command because we may be introducing
// a race condition by doing that. We still want to create these lazily so that we aren't adding
// overhead by at all times even when counters aren't enabled.
// On disable, PollingCounters will stop polling for values so it should be fine to leave them around.
_bytesWrittenPerSecondCounter = new IncrementingPollingCounter("bytes-written-per-second", this, () => Interlocked.Read(ref _bytesWritten))
{
DisplayName = "Bytes Written",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};
_bytesReadPerSecondCounter = new IncrementingPollingCounter("bytes-read-per-second", this, () => Interlocked.Read(ref _bytesRead))
{
DisplayName = "Bytes Read",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};
_commandsPerSecondCounter = new IncrementingPollingCounter("commands-per-second", this, () => Interlocked.Read(ref _totalCommands))
{
DisplayName = "Command Rate",
DisplayRateTimeScale = TimeSpan.FromSeconds(1)
};
_totalCommandsCounter = new PollingCounter("total-commands", this, () => Interlocked.Read(ref _totalCommands))
{
DisplayName = "Total Commands",
};
_currentCommandsCounter = new PollingCounter("current-commands", this, () => Interlocked.Read(ref _currentCommands))
{
DisplayName = "Current Commands"
};
_failedCommandsCounter = new PollingCounter("failed-commands", this, () => Interlocked.Read(ref _failedCommands))
{
DisplayName = "Failed Commands"
};
_preparedCommandsRatioCounter = new PollingCounter(
"prepared-commands-ratio",
this,
() => (double)Interlocked.Read(ref _totalPreparedCommands) / Interlocked.Read(ref _totalCommands) * 100)
{
DisplayName = "Prepared Commands Ratio",
DisplayUnits = "%"
};
_poolsCounter = new PollingCounter("connection-pools", this, GetPoolsCount)
{
DisplayName = "Connection Pools"
};
_idleConnectionsCounter = new PollingCounter("idle-connections", this, GetIdleConnections)
{
DisplayName = "Idle Connections"
};
_busyConnectionsCounter = new PollingCounter("busy-connections", this, GetBusyConnections)
{
DisplayName = "Busy Connections"
};
_multiplexingAverageCommandsPerBatchCounter = new PollingCounter("multiplexing-average-commands-per-batch", this, GetMultiplexingAverageCommandsPerBatch)
{
DisplayName = "Average commands per multiplexing batch"
};
_multiplexingAverageWriteTimePerBatchCounter = new PollingCounter("multiplexing-average-write-time-per-batch", this, GetMultiplexingAverageWriteTimePerBatch)
{
DisplayName = "Average write time per multiplexing batch (us)",
DisplayUnits = "us"
};
}
}
#endif
}