237 lines
8.3 KiB
C#
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
|
|
} |