Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
fix: extracted logic for retrieving shard status
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusbegby committed Sep 6, 2023
1 parent 9223377 commit 8da6f44
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 125 deletions.
76 changes: 17 additions & 59 deletions src/interactions/commands/info/status.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { EmbedBuilder, Guild, SlashCommandBuilder } from 'discord.js';
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import osu from 'node-os-utils';
// @ts-ignore
import { version } from '../../../../package.json';
import { BaseSlashCommandInteraction } from '../../../classes/interactions';
import { BaseSlashCommandParams, BaseSlashCommandReturnType } from '../../../types/interactionTypes';
import { fetchTotalGuildStatistics, fetchTotalPlayerStatistics } from '../../../utils/shardUtils';
import { getUptimeFormatted } from '../../../utils/system/getUptimeFormatted';

class StatusCommand extends BaseSlashCommandInteraction {
Expand All @@ -20,86 +21,43 @@ class StatusCommand extends BaseSlashCommandInteraction {
const usedMemoryInMB: string = Math.ceil((await osu.mem.info()).usedMemMb).toLocaleString('en-US');
const cpuUsage: number = await osu.cpu.usage();
const releaseVersion: string = version;
let guildCount: number = 0;
let memberCount: number = 0;
let activeVoiceConnections: number = 0;
let totalTracks: number = 0;
let totalListeners: number = 0;
const { totalGuildCount, totalMemberCount } = await fetchTotalGuildStatistics(client);
const { totalVoiceConnections, totalTracksInQueues, totalListeners } = await fetchTotalPlayerStatistics(client);

await client!
.shard!.broadcastEval(() => {
/* eslint-disable no-undef */
return player.generateStatistics();
})
.then((results) => {
results.map((result) => {
activeVoiceConnections += result.queues.length;
result.queues.map((queue) => {
totalTracks += queue.status.playing ? queue.tracksCount + 1 : queue.tracksCount;
totalListeners += queue.listeners;
});
});

logger.debug('Successfully fetched player statistics from shards.');
})
.catch((error) => {
logger.error(error, 'Failed to fetch player statistics from shards.');
});

await client!
.shard!.fetchClientValues('guilds.cache')
.then((results) => {
const guildCaches: Guild[][] = results as Guild[][];
guildCaches.map((guildCache: Guild[]) => {
if (guildCache) {
guildCount += guildCache.length;
memberCount += guildCache.reduce((acc: number, guild: Guild) => acc + guild.memberCount, 0);
}
});

logger.debug('Successfully fetched client values from shards.');
})
.catch((error) => {
logger.error(error, 'Failed to fetch client values from shards.');
});

const botStatusString =
`**${guildCount.toLocaleString('en-US')}** Joined servers\n` +
`**${memberCount.toLocaleString('en-US')}** Total members\n` +
const botStatisticsEmbedString =
`**${totalGuildCount.toLocaleString('en-US')}** Joined servers\n` +
`**${totalMemberCount.toLocaleString('en-US')}** Total members\n` +
`**v${releaseVersion}** Release version`;

const queueStatusString =
`**${activeVoiceConnections.toLocaleString('en-US')}** Voice connections\n` +
`**${totalTracks.toLocaleString('en-US')}** Tracks in queues\n` +
const playerStatisticsEmbedString =
`**${totalVoiceConnections.toLocaleString('en-US')}** Voice connections\n` +
`**${totalTracksInQueues.toLocaleString('en-US')}** Tracks in queues\n` +
`**${totalListeners.toLocaleString('en-US')}** Users listening`;

const systemStatusString =
const systemStatusEmbedString =
`**${uptimeString}** Uptime\n` + `**${cpuUsage}%** CPU usage\n` + `**${usedMemoryInMB} MB** Memory usage`;

const discordStatusString: string = `**${client!.ws.ping} ms** Discord API latency`;
const discordWebsocketPingEmbedString: string = `**${client!.ws.ping} ms** Discord API latency`;

logger.debug('Transformed status into into embed description.');
logger.debug('Transformed status info into embed description.');

logger.debug('Responding with info embed.');
return await interaction.editReply({
embeds: [
new EmbedBuilder()
.setDescription(`**${this.embedOptions.icons.bot} Bot status**\n` + botStatusString)
.setDescription(`**${this.embedOptions.icons.bot} Bot status**\n` + botStatisticsEmbedString)
.addFields(
{
name: `**${this.embedOptions.icons.queue} Queue status**`,
value: queueStatusString,
inline: false
value: playerStatisticsEmbedString
},
{
name: `**${this.embedOptions.icons.server} System status**`,
value: systemStatusString,
inline: false
value: systemStatusEmbedString
},
{
name: `**${this.embedOptions.icons.discord} Discord status**`,
value: discordStatusString,
inline: false
value: discordWebsocketPingEmbedString
}
)
.setColor(this.embedOptions.colors.info)
Expand Down
87 changes: 21 additions & 66 deletions src/interactions/commands/system/systemstatus.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { EmbedBuilder, Guild, SlashCommandBuilder } from 'discord.js';
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import osu from 'node-os-utils';
// @ts-ignore
import { dependencies, version } from '../../../../package.json';
import { BaseSlashCommandInteraction } from '../../../classes/interactions';
import { BaseSlashCommandParams, BaseSlashCommandReturnType } from '../../../types/interactionTypes';
import { fetchTotalGuildStatistics, fetchTotalPlayerStatistics } from '../../../utils/shardUtils';
import { getUptimeFormatted } from '../../../utils/system/getUptimeFormatted';
import { checkValidGuildId } from '../../../utils/validation/systemCommandValidator';

Expand All @@ -14,31 +15,26 @@ class SystemStatusCommand extends BaseSlashCommandInteraction {
.setDescription('Show operational status of the bot with additional technical information.');
const isSystemCommand: boolean = true;
super(data, isSystemCommand);

this.validators = [(args) => checkValidGuildId(args)];
}

async execute(params: BaseSlashCommandParams): BaseSlashCommandReturnType {
const { executionId, interaction, client } = params;
const logger = this.getLogger(this.name, executionId, interaction);

await this.runValidators({ interaction, executionId });
await this.runValidators({ interaction, executionId }, [checkValidGuildId]);

// from normal /status command
const uptimeString: string = await getUptimeFormatted({ executionId });
const usedMemoryInMB: string = Math.ceil((await osu.mem.info()).usedMemMb).toLocaleString('en-US');
const cpuUsage: number = await osu.cpu.usage();
const releaseVersion: string = version;
let guildCount: number = 0;
let memberCount: number = 0;
let activeVoiceConnections: number = 0;
let totalTracks: number = 0;
let totalListeners: number = 0;
const { totalGuildCount, totalMemberCount } = await fetchTotalGuildStatistics(client);
const { totalVoiceConnections, totalTracksInQueues, totalListeners } = await fetchTotalPlayerStatistics(client);

// specific to /systemstatus command
const totalMemoryInMb: string = Math.ceil((await osu.mem.info()).totalMemMb).toLocaleString('en-US');
const cpuCores: number = await osu.cpu.count();
const platform: string = await osu.os.platform();
const cpuCores: number = osu.cpu.count();
const platform: string = osu.os.platform();
const discordJsVersion: string = dependencies['discord.js'];
const opusVersion: string = dependencies['@discord-player/opus'];
const restVersion: string = dependencies['@discordjs/rest'];
Expand All @@ -50,60 +46,23 @@ class SystemStatusCommand extends BaseSlashCommandInteraction {

logger.debug('Fetching player statistics from all shards.');

await client!
.shard!.broadcastEval(() => {
/* eslint-disable no-undef */
return player.generateStatistics();
})
.then((results) => {
results.map((result) => {
activeVoiceConnections += result.queues.length;
result.queues.map((queue) => {
totalTracks += queue.status.playing ? queue.tracksCount + 1 : queue.tracksCount;
totalListeners += queue.listeners;
});
});

logger.debug('Successfully fetched player statistics from shards.');
})
.catch((error) => {
logger.error(error, 'Failed to fetch player statistics from shards.');
});

logger.debug('Fetching client values from all shards.');
await client!
.shard!.fetchClientValues('guilds.cache')
.then((results) => {
const guildCaches: Guild[][] = results as Guild[][];
guildCaches.map((guildCache: Guild[]) => {
if (guildCache) {
guildCount += guildCache.length;
memberCount += guildCache.reduce((acc: number, guild: Guild) => acc + guild.memberCount, 0);
}
});
logger.debug('Successfully fetched client values from shards.');
})
.catch((error) => {
logger.error(error, 'Failed to fetch client values from shards.');
});

const botStatusString =
`**${guildCount.toLocaleString('en-US')}** Joined servers\n` +
`**${memberCount.toLocaleString('en-US')}** Total members\n` +
const botStatisticsEmbedString =
`**${totalGuildCount.toLocaleString('en-US')}** Joined servers\n` +
`**${totalMemberCount.toLocaleString('en-US')}** Total members\n` +
`**v${releaseVersion}** Release version`;

const queueStatusString =
`**${activeVoiceConnections.toLocaleString('en-US')}** Voice connections\n` +
`**${totalTracks.toLocaleString('en-US')}** Tracks in queues\n` +
const playerStatisticsEmbedString =
`**${totalVoiceConnections.toLocaleString('en-US')}** Voice connections\n` +
`**${totalTracksInQueues.toLocaleString('en-US')}** Tracks in queues\n` +
`**${totalListeners.toLocaleString('en-US')}** Users listening`;

const systemStatusString =
const systemStatusEmbedString =
`**${platform}** Platform\n` +
`**${uptimeString}** Uptime\n` +
`**${cpuUsage}% @ ${cpuCores} cores** CPU usage\n` +
`**${usedMemoryInMB} / ${totalMemoryInMb} MB** Memory usage`;

const dependenciesString =
const dependenciesEmbedString =
`**${discordJsVersion}** discord.js\n` +
`**┗ ${restVersion}** @discordjs/rest\n` +
`**${discordPlayerVersion}** discord-player\n` +
Expand All @@ -113,35 +72,31 @@ class SystemStatusCommand extends BaseSlashCommandInteraction {
`**${mediaplexVersion}** mediaplex\n` +
`**${distubeYtdlVersion}** @distube/ytdl-core`;

const discordStatusString: string = `**${client!.ws.ping} ms** Discord API latency`;
const discordWebsocketPingEmbedString: string = `**${client!.ws.ping} ms** Discord API latency`;

logger.debug('Transformed system status into embed description.');

logger.debug('Responding with info embed.');
return await interaction.editReply({
embeds: [
new EmbedBuilder()
.setDescription(`**${this.embedOptions.icons.bot} Bot status**\n` + botStatusString)
.setDescription(`**${this.embedOptions.icons.bot} Bot status**\n` + botStatisticsEmbedString)
.addFields(
{
name: `**${this.embedOptions.icons.queue} Queue status**`,
value: queueStatusString,
inline: false
value: playerStatisticsEmbedString
},
{
name: `**${this.embedOptions.icons.server} System status**`,
value: systemStatusString,
inline: false
value: systemStatusEmbedString
},
{
name: `**${this.embedOptions.icons.discord} Discord status**`,
value: discordStatusString,
inline: false
value: discordWebsocketPingEmbedString
},
{
name: `**${this.embedOptions.icons.bot} Dependencies**`,
value: dependenciesString,
inline: false
value: dependenciesEmbedString
}
)
.setColor(this.embedOptions.colors.info)
Expand Down
13 changes: 13 additions & 0 deletions src/types/utilTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,16 @@ export type PostBotStatsSite = {
body: object;
token: string;
};

export type ShardPlayerStatistics = {
totalVoiceConnections: number;
totalTracksInQueues: number;
totalListeners: number;
};

export type PlayerStatistics = {
instances: number;
queuesCount: number;
queryCacheEnabled: boolean;
queues: GuildQueueStatisticsMetadata[];
};
91 changes: 91 additions & 0 deletions src/utils/shardUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { GuildQueueStatisticsMetadata } from 'discord-player';
import { Client, Guild } from 'discord.js';
import { PlayerStatistics, ShardPlayerStatistics } from '../types/utilTypes';

async function fetchGuildsPerShard(client?: Client): Promise<Guild[][]> {
if (!client || !client.shard) {
throw new Error('Client is undefined or not sharded.');
}

try {
return (await client.shard.fetchClientValues('guilds.cache')) as Guild[][];
} catch (error) {
throw new Error('Failed to fetch client values from shards.');
}
}

function calculateTotalCountForShard(guildList: Guild[]) {
const totalGuildCount: number = guildList.length;
const totalMemberCount: number = guildList.reduce((acc: number, guild: Guild) => acc + guild.memberCount, 0);

return { totalGuildCount, totalMemberCount };
}

function calculateTotalCountForAllShards(guildListPerShard: Guild[][]) {
return guildListPerShard.reduce(
(totals, guildList) => {
const { totalGuildCount, totalMemberCount } = calculateTotalCountForShard(guildList);
totals.totalGuildCount += totalGuildCount;
totals.totalMemberCount += totalMemberCount;

return totals;
},
{ totalGuildCount: 0, totalMemberCount: 0 }
);
}

export async function fetchTotalGuildStatistics(client?: Client) {
const guildsPerShard: Guild[][] = await fetchGuildsPerShard(client);
const { totalGuildCount, totalMemberCount } = calculateTotalCountForAllShards(guildsPerShard);

return { totalGuildCount, totalMemberCount };
}

async function fetchPlayerStatisticsPerShard(client?: Client): Promise<PlayerStatistics[]> {
if (!client || !client.shard) {
throw new Error('Client is undefined or not sharded.');
}

try {
const playerStatisticsPerShard: PlayerStatistics[] = await client.shard.broadcastEval(() => {
/* eslint-disable no-undef */
return player.generateStatistics();
});
return playerStatisticsPerShard;
} catch (error) {
throw new Error('Failed to fetch player statistics from shards.');
}
}

function calculateTotalPlayerStatisticsForShard(result: PlayerStatistics): ShardPlayerStatistics {
const totalVoiceConnections = result.queues.length;
let totalTracksInQueues = 0;
let totalListeners = 0;

result.queues.map((queue: GuildQueueStatisticsMetadata) => {
totalTracksInQueues += queue.status.playing ? queue.tracksCount + 1 : queue.tracksCount;
totalListeners += queue.listeners;
});

return { totalVoiceConnections, totalTracksInQueues, totalListeners };
}

function calculateTotalPlayerStatisticsForAllShards(results: PlayerStatistics[]): ShardPlayerStatistics {
return results.reduce(
(totals: ShardPlayerStatistics, result: PlayerStatistics) => {
const { totalVoiceConnections, totalTracksInQueues, totalListeners } =
calculateTotalPlayerStatisticsForShard(result);
totals.totalVoiceConnections += totalVoiceConnections;
totals.totalTracksInQueues += totalTracksInQueues;
totals.totalListeners += totalListeners;

return totals;
},
{ totalVoiceConnections: 0, totalTracksInQueues: 0, totalListeners: 0 }
);
}

export async function fetchTotalPlayerStatistics(client?: Client): Promise<ShardPlayerStatistics> {
const results: PlayerStatistics[] = await fetchPlayerStatisticsPerShard(client);
return calculateTotalPlayerStatisticsForAllShards(results);
}

0 comments on commit 8da6f44

Please # to comment.