Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" --no-restore --no-build -v minimal -c Release -o ${{ env.ArtifactStagingDirectory }} /p:BuildNumber=${{ env.Suffix }} /p:IsTagBuild=${{ env.IsTagBuild }}
dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" --no-restore --no-build -v minimal -c Release -o ${{ env.ArtifactStagingDirectory }} /p:BuildNumber=${{ env.Suffix }} /p:IsTagBuild=${{ env.IsTagBuild }}
dotnet pack "src\Discord.Net.Interactions\Discord.Net.Interactions.csproj" --no-restore --no-build -v minimal -c Release -o ${{ env.ArtifactStagingDirectory }} /p:BuildNumber=${{ env.Suffix }} /p:IsTagBuild=${{ env.IsTagBuild }}
dotnet pack "src\Discord.Net.OpenTelemetry\Discord.Net.OpenTelemetry.csproj" --no-restore --no-build -v minimal -c Release -o ${{ env.ArtifactStagingDirectory }} /p:BuildNumber=${{ env.Suffix }} /p:IsTagBuild=${{ env.IsTagBuild }}
# dotnet pack "experiment\Discord.Net.BuildOverrides\Discord.Net.BuildOverrides.csproj" --no-restore --no-build -v minimal -c Release -o ${{ env.ArtifactStagingDirectory }} /p:BuildNumber=${{ env.Suffix }} /p:IsTagBuild=${{ env.IsTagBuild }}

- name: Publish Artifacts
Expand Down
15 changes: 15 additions & 0 deletions Discord.Net.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{BB59
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.BuildOverrides", "experiment\Discord.Net.BuildOverrides\Discord.Net.BuildOverrides.csproj", "{115F4921-B44D-4F69-996B-69796959C99D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.OpenTelemetry", "src\Discord.Net.OpenTelemetry\Discord.Net.OpenTelemetry.csproj", "{88D77C2C-547E-41B8-8AFC-D1C089652767}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -244,6 +246,18 @@ Global
{115F4921-B44D-4F69-996B-69796959C99D}.Release|x64.Build.0 = Release|Any CPU
{115F4921-B44D-4F69-996B-69796959C99D}.Release|x86.ActiveCfg = Release|Any CPU
{115F4921-B44D-4F69-996B-69796959C99D}.Release|x86.Build.0 = Release|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Debug|x64.ActiveCfg = Debug|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Debug|x64.Build.0 = Debug|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Debug|x86.ActiveCfg = Debug|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Debug|x86.Build.0 = Debug|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Release|Any CPU.Build.0 = Release|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Release|x64.ActiveCfg = Release|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Release|x64.Build.0 = Release|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Release|x86.ActiveCfg = Release|Any CPU
{88D77C2C-547E-41B8-8AFC-D1C089652767}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -264,6 +278,7 @@ Global
{B61AAE66-15CC-40E4-873A-C23E697C3411} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
{115F4921-B44D-4F69-996B-69796959C99D} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
{88D77C2C-547E-41B8-8AFC-D1C089652767} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}
Expand Down
22 changes: 22 additions & 0 deletions src/Discord.Net.OpenTelemetry/Discord.Net.OpenTelemetry.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<Import Project="../../StyleAnalyzer.targets" />
<PropertyGroup>
<TargetFrameworks>net9.0;net8.0;net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<RootNamespace>Discord.OpenTelemetry</RootNamespace>
<AssemblyName>Discord.Net.OpenTelemetry</AssemblyName>
<Description>A Discord.Net extension adding support for the OpenTelemetry (otel) Sdk.</Description>
<WarningLevel>5</WarningLevel>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="1.11.2" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net4')) AND '$(MSBuildRuntimeType)' == 'Core'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
</ItemGroup>

</Project>
41 changes: 41 additions & 0 deletions src/Discord.Net.OpenTelemetry/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using System;

namespace Discord.OpenTelemetry
{
/// <summary>
/// An extension class which contains methods to add the Discord.Net OpenTelemetry instrumentation.
/// </summary>
public static class Extensions
{
private static readonly string[] _sourceNames = ["Discord.Net.WebSocket", "Discord.Net.Audio"];

/// <summary>
/// Adds the trace sources of DNet.
/// </summary>
/// <param name="builder">The trace provider to add these sources to.</param>
/// <returns>The provided trace provider to chain calls.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static TracerProviderBuilder AddDiscordNetInstrumentation(this TracerProviderBuilder builder)
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));
return builder.AddSource(_sourceNames);
}

/// <summary>
/// Adds the meters of DNet.
/// </summary>
/// <param name="builder">The meter provider to add the meters to.</param>
/// <returns>The provided meter builder to chain calls.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static MeterProviderBuilder AddDiscordNetInstrumentation(this MeterProviderBuilder builder)
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));
return builder.AddMeter(_sourceNames);
}
}

}
56 changes: 49 additions & 7 deletions src/Discord.Net.WebSocket/Audio/AudioClient.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Discord.API.Voice;
using Discord.Audio.Streams;
using Discord.Logging;
using Discord.Net;
using Discord.Net.Converters;
using Discord.WebSocket;
using Discord.WebSocket.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
Expand Down Expand Up @@ -58,6 +59,7 @@ public StreamPair(AudioInStream reader, AudioOutStream writer)
private StopReason _stopReason;
private bool _resuming;

public int ClientId { get; }
public SocketGuild Guild { get; }
public DiscordVoiceAPIClient ApiClient { get; private set; }
public int Latency { get; private set; }
Expand All @@ -73,21 +75,37 @@ public StreamPair(AudioInStream reader, AudioOutStream writer)
internal AudioClient(SocketGuild guild, int clientId, ulong channelId)
{
Guild = guild;
ClientId = clientId;
ChannelId = channelId;
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}");

ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider);
ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
ApiClient.SentGatewayMessage += async opCode =>
{
AudioMeter.RecordSocketEventSent(opCode, this);
await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
};
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false);
//ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false);
ApiClient.SentData += bytes =>
{
//await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false);
AudioMeter.RecordBytesSent(bytes, this);
return Task.CompletedTask;
};

ApiClient.ReceivedEvent += ProcessMessageAsync;
ApiClient.ReceivedPacket += ProcessPacketAsync;

_stateLock = new SemaphoreSlim(1, 1);
_connection = new ConnectionManager(_stateLock, _audioLogger, ConnectionTimeoutMs,
OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
_connection.Connected += () => _connectedEvent.InvokeAsync();
_connection.Disconnected += (exception, _) => _disconnectedEvent.InvokeAsync(exception);
_connection.Disconnected += (exception, reconnect) =>
{
if (reconnect)
AudioMeter.AddAudioReconnect(this);
return _disconnectedEvent.InvokeAsync(exception);
};
_heartbeatTimes = new ConcurrentQueue<long>();
_keepaliveTimes = new ConcurrentQueue<KeyValuePair<ulong, int>>();
_ssrcMap = new ConcurrentDictionary<uint, ulong>();
Expand All @@ -100,8 +118,16 @@ internal AudioClient(SocketGuild guild, int clientId, ulong channelId)
e.ErrorContext.Handled = true;
};

LatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false);
UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false);
LatencyUpdated += async (old, val) =>
{
AudioMeter.RecordSocketLatency((double)val / 1000, this);
await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false);
};
UdpLatencyUpdated += async (old, val) =>
{
await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false);
AudioMeter.RecordUdpLatency((double)val / 1000, this);
};
}

internal Task StartAsync(string url, ulong userId, string sessionId, string token)
Expand Down Expand Up @@ -132,6 +158,7 @@ private async Task OnConnectingAsync()
await _audioLogger.DebugAsync($"Connecting ApiClient. Voice server: wss://{_url}").ConfigureAwait(false);
await ApiClient.ConnectAsync($"wss://{_url}?v={DiscordConfig.VoiceAPIVersion}").ConfigureAwait(false);
await _audioLogger.DebugAsync($"Listening on port {ApiClient.UdpPort}").ConfigureAwait(false);
AudioMeter.AddAudioConnections(1, this);

if (!_resuming)
{
Expand All @@ -151,6 +178,7 @@ private async Task OnDisconnectingAsync(Exception ex)
{
await _audioLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false);
await ApiClient.DisconnectAsync().ConfigureAwait(false);
AudioMeter.AddAudioConnections(-1, this);

if (_stopReason == StopReason.Unknown && ex.InnerException is WebSocketException exception)
{
Expand Down Expand Up @@ -303,6 +331,9 @@ private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
{
_lastMessageTime = Environment.TickCount;

var activity = AudioActivity.StartEventReceivedActivity(opCode, this);
var watch = Stopwatch.StartNew();

try
{
switch (opCode)
Expand Down Expand Up @@ -388,7 +419,7 @@ private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
_heartbeatTask = RunHeartbeatAsync(_heartbeatInterval, _connection.CancelToken);
_keepaliveTask = RunKeepaliveAsync(_connection.CancelToken);

_ = _connection.CompleteAsync();
_ = _connection.CompleteAsync();
}
break;
default:
Expand All @@ -398,11 +429,22 @@ private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
}
catch (Exception ex)
{
activity?.AddExceptionToActivity(ex);
AudioMeter.RecordSocketEventException(ex, opCode, this);

await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false);
}
finally
{
watch.Stop();
AudioMeter.RecordSocketEventReceived(watch.Elapsed, opCode, this);

activity?.Dispose();
}
}
private async Task ProcessPacketAsync(byte[] packet)
{
AudioMeter.RecordBytesReceived(packet.Length, this);
try
{
if (_connection.State == ConnectionState.Connecting)
Expand Down
32 changes: 32 additions & 0 deletions src/Discord.Net.WebSocket/Diagnostics/AudioActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Discord.Audio;
using Discord.API.Voice;
using System;

#if NET5_0_OR_GREATER
using System.Collections.Generic;
using System.Diagnostics;
#endif

namespace Discord.WebSocket.Diagnostics
{
public static class AudioActivity
{
#if NET5_0_OR_GREATER
private static readonly ActivitySource _source = new("Discord.Net.Audio", typeof(DiagnosticTags).Assembly.GetName().Version!.ToString());

internal static Activity StartEventReceivedActivity(VoiceOpCode opCode, AudioClient client)
{
Activity.Current = null; // This activity doesn't have a parent so it have to be explicitly set

IEnumerable<KeyValuePair<string, object>> tags = [
.. DiagnosticTags.CreateAudioClientTags(client),
.. DiagnosticTags.CreateAudioEventTags(opCode)
];
return _source.StartActivity($"process {opCode}", ActivityKind.Consumer, null, tags: tags);
}

#else
internal static IDisposable StartEventReceivedActivity(VoiceOpCode opCode, AudioClient client) => null;
#endif
}
}
Loading