2019-12-22 12:50:47 +01:00
using System.Diagnostics ;
2020-02-12 15:16:19 +01:00
2019-07-16 21:59:06 +02:00
using App.Metrics ;
2019-12-22 12:50:47 +01:00
2020-12-24 14:52:44 +01:00
using Myriad.Builders ;
using Myriad.Rest.Types.Requests ;
using Myriad.Types ;
2025-01-05 00:52:45 +00:00
using Newtonsoft.Json ;
2021-11-26 21:10:56 -05:00
using NodaTime ;
using PluralKit.Core ;
namespace PluralKit.Bot ;
public class Misc
2021-08-27 11:03:47 -04:00
{
2021-11-26 21:10:56 -05:00
private readonly BotConfig _botConfig ;
private readonly CpuStatService _cpu ;
private readonly IMetrics _metrics ;
private readonly ShardInfoService _shards ;
2022-01-22 03:52:52 -05:00
private readonly ModelRepository _repo ;
2021-11-26 21:10:56 -05:00
2022-09-06 09:52:37 +00:00
public Misc ( BotConfig botConfig , IMetrics metrics , CpuStatService cpu , ModelRepository repo , ShardInfoService shards )
2019-10-05 07:41:00 +02:00
{
2021-11-26 21:10:56 -05:00
_botConfig = botConfig ;
_metrics = metrics ;
_cpu = cpu ;
_repo = repo ;
2022-01-22 03:52:52 -05:00
_shards = shards ;
2021-11-26 21:10:56 -05:00
}
2020-04-24 15:50:28 -04:00
2021-11-26 21:10:56 -05:00
public async Task Invite ( Context ctx )
{
var permissions =
PermissionSet . AddReactions |
PermissionSet . AttachFiles |
PermissionSet . EmbedLinks |
PermissionSet . ManageMessages |
PermissionSet . ManageWebhooks |
PermissionSet . ReadMessageHistory |
PermissionSet . SendMessages ;
var invite =
2022-09-06 09:52:37 +00:00
$"https://discord.com/oauth2/authorize?client_id={_botConfig.ClientId}&scope=bot%20applications.commands&permissions={(ulong)permissions}" ;
2021-11-27 11:09:08 -05:00
var botName = _botConfig . IsBetaBot ? "PluralKit Beta" : "PluralKit" ;
await ctx . Reply ( $"{Emojis.Success} Use this link to add {botName} to your server:\n<{invite}>" ) ;
2021-11-26 21:10:56 -05:00
}
public async Task Stats ( Context ctx )
{
var timeBefore = SystemClock . Instance . GetCurrentInstant ( ) ;
var msg = await ctx . Reply ( "..." ) ;
var timeAfter = SystemClock . Instance . GetCurrentInstant ( ) ;
var apiLatency = timeAfter - timeBefore ;
2025-01-05 00:52:45 +00:00
var process = Process . GetCurrentProcess ( ) ;
var stats = await GetStats ( ctx ) ;
2022-01-22 03:52:52 -05:00
var shards = await _shards . GetShards ( ) ;
2021-11-26 21:10:56 -05:00
2022-02-04 14:53:56 -05:00
var shardInfo = shards . Where ( s = > s . ShardId = = ctx . ShardId ) . FirstOrDefault ( ) ;
2025-01-05 01:49:32 +00:00
var shardsUp = shards . Where ( s = > s . Up ) . Count ( ) ;
if ( stats = = null )
{
var content = $"Stats unavailable (is scheduled_tasks service running?)\n\n**Quick info:**"
+ $"\nPluralKit [{BuildInfoService.Version}](<https://github.com/pluralkit/pluralkit/commit/{BuildInfoService.FullVersion}>)"
2025-01-05 05:23:38 +00:00
// + (BuildInfoService.IsDev ? ", **development build**" : "")
2025-01-05 01:49:32 +00:00
+ $"\nCurrently on shard {ctx.ShardId}, {shardsUp}/{shards.Count()} shards up,"
+ $" API latency: {apiLatency.TotalMilliseconds:F0}ms" ;
await ctx . Rest . EditMessage ( msg . ChannelId , msg . Id ,
new MessageEditRequest { Content = content } ) ;
return ;
}
2021-11-26 21:10:56 -05:00
2025-01-05 00:52:45 +00:00
var embed = new EmbedBuilder ( ) ;
2022-01-22 03:52:52 -05:00
embed
2025-01-05 01:49:32 +00:00
. Field ( new ( "Connection status" , $"**{shards.Count()}** shards across **{shards.Select(s => s.ClusterId).Distinct().Count()}** clusters (**{shardsUp} up**)\n"
2025-01-05 00:52:45 +00:00
+ $"Current server is on **shard {ctx.ShardId} (cluster {shardInfo.ClusterId ?? 0})**\n"
+ $"Latency: API **{apiLatency.TotalMilliseconds:F0}ms** (p90: {stats.prom.nirn_proxy_latency_p90 * 1000:F0}ms, p99: {stats.prom.nirn_proxy_latency_p99 * 1000:F0}ms), "
+ $"shard **{shardInfo.Latency}ms** (avg: {stats.prom.shard_latency_average}ms)" , true ) )
. Field ( new ( "Resource usage" , $"**CPU:** {stats.prom.cpu_used}% used / {stats.prom.cpu_total_cores} total cores ({stats.prom.cpu_total_threads} threads)\n"
+ $"**Memory:** {(stats.prom.memory_used / 1_000_000_000):N1}GB used / {(stats.prom.memory_total / 1_000_000_000):N1}GB total" , true ) )
. Field ( new ( "Usage metrics" , $"Messages received: **{stats.prom.messages_1m}/s** ({stats.prom.messages_15m}/s over 15m)\n" +
2025-01-11 01:40:04 +00:00
$"Messages proxied: **{stats.prom.proxy_1m}/s** ({stats.prom.proxy_15m}/s over 15m, {stats.db.messages_24h:N0} total in last 24h)\n" +
2025-01-05 00:52:45 +00:00
$"Commands executed: **{stats.prom.commands_1m}/m** ({stats.prom.commands_15m}/m over 15m)" ) ) ;
embed . Field ( new ( "Total numbers" , $"**{stats.db.systems:N0}** systems, **{stats.db.members:N0}** members, **{stats.db.groups:N0}** groups, "
2025-01-11 01:40:04 +00:00
+ $"**{stats.db.switches:N0}** switches, **{stats.db.messages:N0}** messages\n" +
2025-01-05 00:52:45 +00:00
$"**{stats.db.guilds:N0}** servers with **{stats.db.channels:N0}** channels" ) ) ;
2025-09-07 03:20:57 +00:00
embed . Field ( new ( "" , Help . EmbedFooter ( " | " ) ) ) ;
2025-01-05 00:52:45 +00:00
var uptime = ( ( DateTimeOffset ) process . StartTime ) . ToUnixTimeSeconds ( ) ;
embed . Description ( $"### PluralKit [{BuildInfoService.Version}](https://github.com/pluralkit/pluralkit/commit/{BuildInfoService.FullVersion})\n" +
$"Built on <t:{BuildInfoService.Timestamp}> (<t:{BuildInfoService.Timestamp}:R>)"
2025-01-05 05:23:38 +00:00
// + (BuildInfoService.IsDev ? ", **development build**" : "")
2025-01-05 00:52:45 +00:00
+ $"\nLast restart: <t:{uptime}:R>" ) ;
2022-01-22 03:52:52 -05:00
2021-11-26 21:10:56 -05:00
await ctx . Rest . EditMessage ( msg . ChannelId , msg . Id ,
2022-02-26 16:28:20 -05:00
new MessageEditRequest { Content = "" , Embeds = new [ ] { embed . Build ( ) } } ) ;
2019-04-29 20:21:16 +02:00
}
2025-01-05 00:52:45 +00:00
2025-01-05 01:49:32 +00:00
private async Task < Stats ? > GetStats ( Context ctx )
2025-01-05 00:52:45 +00:00
{
var db = ctx . Redis . Connection . GetDatabase ( ) ;
var data = await db . StringGetAsync ( "statsapi" ) ;
2025-01-05 01:49:32 +00:00
return data . HasValue ? JsonConvert . DeserializeObject < Stats > ( data ) : null ;
2025-01-05 00:52:45 +00:00
}
}
// none of these fields are "assigned to" for some reason
#pragma warning disable CS0649
class Stats
{
public DbStats db ;
public PrometheusStats prom ;
} ;
class DbStats
{
public double systems ;
public double members ;
public double groups ;
public double switches ;
public double messages ;
public double messages_24h ;
public double guilds ;
public double channels ;
} ;
class PrometheusStats
{
public double messages_1m ;
public double messages_15m ;
public double proxy_1m ;
public double proxy_15m ;
public double commands_1m ;
public double commands_15m ;
public double cpu_total_cores ;
public double cpu_total_threads ;
public double cpu_used ;
public double memory_total ;
public double memory_used ;
public double nirn_proxy_rps ;
public double nirn_proxy_latency_p90 ;
public double nirn_proxy_latency_p99 ;
public double shard_latency_average ;
} ;