nyxx_utils/lib/nyxx_analytics/nyxx_analytics.dart
2024-03-13 15:16:49 +01:00

166 lines
5.2 KiB
Dart

import 'dart:async';
import 'package:nyxx/nyxx.dart';
import 'package:prometheus_client/prometheus_client.dart';
import 'package:prometheus_client_shelf/shelf_handler.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:prometheus_client_shelf/shelf_metrics.dart' as shelf_metrics;
import 'package:prometheus_client/runtime_metrics.dart' as runtime_metrics;
// final logger = Logger('Analytics');
/// A global instance of the [Analytics] plugin.
final analytics = Analytics();
class Analytics extends NyxxPlugin<NyxxRest> {
@override
String get name => 'Analytics';
/// Whether to include runtime metrics in the analytics.
final bool includeDefaultMetrics;
/// The port to bind the analytics server to.
final int port;
/// The analytics route.
final String route;
/// The refresh interval for the analytics.
final Duration refreshInterval;
/// Custom gauges for the analytics. It's up to you to update these.
final List<Gauge> gauges;
/// Custom counters for the analytics.
final List<Counter> counters;
/// Custom summaries for the analytics.
final List<Summary> summaries;
/// Custom histograms for the analytics.
final List<Histogram> histograms;
Analytics({
this.includeDefaultMetrics = true,
this.port = 4242,
this.route = '/analytics',
this.refreshInterval = const Duration(seconds: 5),
this.gauges = const [],
this.counters = const [],
this.summaries = const [],
this.histograms = const [],
});
@override
Future<void> beforeConnect(
ApiOptions apiOptions,
ClientOptions clientOptions,
) async {
final router = Router();
router.get('/', (req) => Response.found(route));
router.get(route, prometheusHandler());
final pipeline = const Pipeline();
if (includeDefaultMetrics) {
runtime_metrics.register();
pipeline.addMiddleware(shelf_metrics.register());
}
final server = await io.serve(
pipeline.addHandler(router.call),
'0.0.0.0',
port,
);
// this.s
logger.info('Serving analytics at http://${server.address.host}:${server.port}$route');
}
@override
NyxxPluginState<NyxxRest, Analytics> createState() => AnalyticsPluginState(this);
}
class AnalyticsPluginState extends NyxxPluginState<NyxxRest, Analytics> {
late final Timer timer;
AnalyticsPluginState(super.plugin);
@override
Future<void> afterConnect(NyxxRest client) async {
await super.afterConnect(client);
registerPeriodicMetrics(client);
registerCustomMetrics();
}
void registerCustomMetrics() {
final m = [plugin.gauges, plugin.counters, plugin.summaries, plugin.histograms].expand((e) => e);
if (m.isEmpty) return;
for (dynamic metric in m) {
metric.register();
}
}
void registerPeriodicMetrics(NyxxRest client) {
final cachedUsers = Gauge(name: 'nyxx_analytics_cached_users', help: 'The number of cached users')..register();
final cachedGuilds = Gauge(name: 'nyxx_analytics_cached_guilds', help: 'The number of cached guilds')..register();
final cachedChannels = Gauge(name: 'nyxx_analytics_cached_channels', help: 'The number of cached channels')
..register();
final cachedMessages = Gauge(name: 'nyxx_analytics_cached_messages', help: 'The total number of cached messages')
..register();
final restLatency = Gauge(
name: 'nyxx_analytics_rest_latency',
help: 'The average REST latency',
labelNames: ['client_id'],
)..register();
final realRestLatency = Gauge(
name: 'nyxx_analytics_real_rest_latency',
help: 'The real average REST latency',
labelNames: ['client_id'],
)..register();
Gauge? gatewayLatency;
Gauge? shardGatewayLatency;
if (client is NyxxGateway) {
gatewayLatency = Gauge(
name: 'nyxx_analytics_gateway_latency',
help: 'The average gateway latency',
labelNames: ['client_id'],
)..register();
shardGatewayLatency = Gauge(
name: 'nyxx_analytics_shard_gateway_latency',
help: 'The average gateway latency per shard',
labelNames: ['client_id', 'shard_id'],
)..register();
}
Timer.periodic(plugin.refreshInterval, (timer) {
cachedUsers.value = client.users.cache.length.toDouble();
cachedGuilds.value = client.guilds.cache.length.toDouble();
cachedChannels.value = client.channels.cache.length.toDouble();
cachedMessages.value = client.channels.cache.values
.whereType<TextChannel>()
.fold(0, (count, channel) => count + channel.messages.cache.length)
.toDouble();
restLatency.labels([client.user.id.toString()]).value = client.httpHandler.latency.inMilliseconds.toDouble();
realRestLatency.labels([client.user.id.toString()]).value =
client.httpHandler.realLatency.inMilliseconds.toDouble();
if (client case final NyxxGateway client) {
gatewayLatency!.labels([client.user.id.toString()]).value = client.gateway.latency.inMilliseconds.toDouble();
for (final shard in client.gateway.shards) {
shardGatewayLatency!.labels([client.user.id.toString(), shard.id.toString()]).value =
shard.latency.inMilliseconds.toDouble();
}
}
});
}
}