kike: implement STATS

This commit is contained in:
Přemysl Eric Janouch 2015-06-13 21:22:57 +02:00
parent ba14a30c0a
commit ff10e1b652
2 changed files with 148 additions and 43 deletions

View File

@ -3,7 +3,11 @@
3 IRC_RPL_CREATED ":This server was created %s"
4 IRC_RPL_MYINFO "%s %s %s %s"
5 IRC_RPL_ISUPPORT "%s :are supported by this server"
211 IRC_RPL_STATSLINKINFO "%s %zu %zu %zu %zu %zu %lld"
212 IRC_RPL_STATSCOMMANDS "%s %zu %zu %zu"
219 IRC_RPL_ENDOFSTATS "%c :End of STATS report"
221 IRC_RPL_UMODEIS "+%s"
242 IRC_RPL_STATSUPTIME ":Server Up %d days %d:%02d:%02d"
251 IRC_RPL_LUSERCLIENT ":There are %d users and %d services on %d servers"
252 IRC_RPL_LUSEROP "%d :operator(s) online"
253 IRC_RPL_LUSERUNKNOWN "%d :unknown connection(s)"

187
kike.c
View File

@ -25,6 +25,8 @@
#include "kike-replies.c"
#include <nl_types.h>
// FIXME: don't use time_t to compute time deltas
// --- Configuration (application-specific) ------------------------------------
static struct config_item g_config_table[] =
@ -306,6 +308,12 @@ struct client
LIST_HEADER (struct client)
struct server_context *ctx; ///< Server context
time_t opened; ///< When the connection was opened
size_t n_sent_messages; ///< Number of sent messages total
size_t sent_bytes; ///< Number of sent bytes total
size_t n_received_messages; ///< Number of received messages total
size_t received_bytes; ///< Number of received bytes total
int socket_fd; ///< The TCP socket
struct str read_buffer; ///< Unprocessed input
struct str write_buffer; ///< Output yet to be sent out
@ -566,12 +574,24 @@ whowas_info_destroy (struct whowas_info *self)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct irc_command
{
const char *name;
bool requires_registration;
void (*handler) (const struct irc_message *, struct client *);
size_t n_received; ///< Number of commands received
size_t bytes_received; ///< Number of bytes received total
};
struct server_context
{
int *listen_fds; ///< Listening socket FD's
struct poller_fd *listen_events; ///< New connections available
size_t n_listen_fds; ///< Number of listening sockets
time_t started; ///< When has the server been started
SSL_CTX *ssl_ctx; ///< SSL context
struct client *clients; ///< Clients
unsigned n_clients; ///< Current number of connections
@ -798,6 +818,7 @@ client_send_str (struct client *c, const struct str *s)
{
hard_assert (c->initialized && !c->closing_link);
size_t old_sendq = c->write_buffer.len;
// TODO: kill the connection above some "SendQ" threshold (careful!)
str_append_data (&c->write_buffer, s->str,
s->len > IRC_MAX_MESSAGE_LENGTH ? IRC_MAX_MESSAGE_LENGTH : s->len);
@ -805,6 +826,10 @@ client_send_str (struct client *c, const struct str *s)
// XXX: we might want to move this elsewhere, so that it doesn't get called
// as often; it's going to cause a lot of syscalls with epoll.
client_update_poller (c, NULL);
// Technically we haven't sent it yet but that's a minor detail
c->n_sent_messages++;
c->sent_bytes += c->write_buffer.len - old_sendq;
}
static void
@ -2821,6 +2846,77 @@ irc_handle_admin (const struct irc_message *msg, struct client *c)
irc_send_reply (c, IRC_ERR_NOADMININFO, c->ctx->server_name);
}
static void
irc_handle_stats_links (struct client *c, const struct irc_message *msg)
{
// There is only an "l" query in RFC 2812 but we cannot link,
// so instead we provide the "L" query giving information for all users
const char *filter = NULL;
if (msg->params.len > 1)
filter = msg->params.vector[1];
for (struct client *iter = c->ctx->clients; iter; iter = iter->next)
{
if (filter && irc_strcmp (iter->nickname, filter))
continue;
irc_send_reply (c, IRC_RPL_STATSLINKINFO,
iter->address, // linkname
iter->write_buffer.len, // sendq
iter->n_sent_messages, iter->sent_bytes / 1024,
iter->n_received_messages, iter->received_bytes / 1024,
(long long) (time (NULL) - iter->opened));
}
}
static void
irc_handle_stats_commands (struct client *c)
{
struct str_map_iter iter;
str_map_iter_init (&iter, &c->ctx->handlers);
struct irc_command *handler;
while ((handler = str_map_iter_next (&iter)))
{
if (!handler->n_received)
continue;
irc_send_reply (c, IRC_RPL_STATSCOMMANDS, handler->name,
handler->n_received, handler->bytes_received, (size_t) 0);
}
}
static void
irc_handle_stats_uptime (struct client *c)
{
time_t uptime = time (NULL) - c->ctx->started;
int days = uptime / 60 / 60 / 24;
int hours = (uptime % (60 * 60 * 24)) / 60 / 60;
int mins = (uptime % (60 * 60)) / 60;
int secs = uptime % 60;
irc_send_reply (c, IRC_RPL_STATSUPTIME, days, hours, mins, secs);
}
static void
irc_handle_stats (const struct irc_message *msg, struct client *c)
{
char query;
if (msg->params.len < 1 || !(query = *msg->params.vector[0]))
RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1]))
RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]);
if (!(c->mode & IRC_USER_MODE_OPERATOR))
RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES);
switch (query)
{
case 'L': irc_handle_stats_links (c, msg); break;
case 'm': irc_handle_stats_commands (c); break;
case 'u': irc_handle_stats_uptime (c); break;
}
irc_send_reply (c, IRC_RPL_ENDOFSTATS, query);
}
static void
irc_handle_kill (const struct irc_message *msg, struct client *c)
{
@ -2851,56 +2947,50 @@ irc_handle_die (const struct irc_message *msg, struct client *c)
// -----------------------------------------------------------------------------
struct irc_command
{
const char *name;
bool requires_registration;
void (*handler) (const struct irc_message *, struct client *);
};
static void
irc_register_handlers (struct server_context *ctx)
{
// TODO: add an index for IRC_ERR_NOSUCHSERVER validation?
// TODO: add a minimal parameter count?
// TODO: add a field for oper-only commands?
static const struct irc_command message_handlers[] =
static struct irc_command message_handlers[] =
{
{ "CAP", false, irc_handle_cap },
{ "PASS", false, irc_handle_pass },
{ "NICK", false, irc_handle_nick },
{ "USER", false, irc_handle_user },
{ "CAP", false, irc_handle_cap, 0, 0 },
{ "PASS", false, irc_handle_pass, 0, 0 },
{ "NICK", false, irc_handle_nick, 0, 0 },
{ "USER", false, irc_handle_user, 0, 0 },
{ "USERHOST", true, irc_handle_userhost },
{ "LUSERS", true, irc_handle_lusers },
{ "MOTD", true, irc_handle_motd },
{ "PING", true, irc_handle_ping },
{ "PONG", false, irc_handle_pong },
{ "QUIT", false, irc_handle_quit },
{ "TIME", true, irc_handle_time },
{ "VERSION", true, irc_handle_version },
{ "USERS", true, irc_handle_users },
{ "SUMMON", true, irc_handle_summon },
{ "AWAY", true, irc_handle_away },
{ "ADMIN", true, irc_handle_admin },
{ "USERHOST", true, irc_handle_userhost, 0, 0 },
{ "LUSERS", true, irc_handle_lusers, 0, 0 },
{ "MOTD", true, irc_handle_motd, 0, 0 },
{ "PING", true, irc_handle_ping, 0, 0 },
{ "PONG", false, irc_handle_pong, 0, 0 },
{ "QUIT", false, irc_handle_quit, 0, 0 },
{ "TIME", true, irc_handle_time, 0, 0 },
{ "VERSION", true, irc_handle_version, 0, 0 },
{ "USERS", true, irc_handle_users, 0, 0 },
{ "SUMMON", true, irc_handle_summon, 0, 0 },
{ "AWAY", true, irc_handle_away, 0, 0 },
{ "ADMIN", true, irc_handle_admin, 0, 0 },
{ "STATS", true, irc_handle_stats, 0, 0 },
{ "MODE", true, irc_handle_mode },
{ "PRIVMSG", true, irc_handle_privmsg },
{ "NOTICE", true, irc_handle_notice },
{ "JOIN", true, irc_handle_join },
{ "PART", true, irc_handle_part },
{ "KICK", true, irc_handle_kick },
{ "INVITE", true, irc_handle_invite },
{ "TOPIC", true, irc_handle_topic },
{ "LIST", true, irc_handle_list },
{ "NAMES", true, irc_handle_names },
{ "WHO", true, irc_handle_who },
{ "WHOIS", true, irc_handle_whois },
{ "WHOWAS", true, irc_handle_whowas },
{ "ISON", true, irc_handle_ison },
{ "MODE", true, irc_handle_mode, 0, 0 },
{ "PRIVMSG", true, irc_handle_privmsg, 0, 0 },
{ "NOTICE", true, irc_handle_notice, 0, 0 },
{ "JOIN", true, irc_handle_join, 0, 0 },
{ "PART", true, irc_handle_part, 0, 0 },
{ "KICK", true, irc_handle_kick, 0, 0 },
{ "INVITE", true, irc_handle_invite, 0, 0 },
{ "TOPIC", true, irc_handle_topic, 0, 0 },
{ "LIST", true, irc_handle_list, 0, 0 },
{ "NAMES", true, irc_handle_names, 0, 0 },
{ "WHO", true, irc_handle_who, 0, 0 },
{ "WHOIS", true, irc_handle_whois, 0, 0 },
{ "WHOWAS", true, irc_handle_whowas, 0, 0 },
{ "ISON", true, irc_handle_ison, 0, 0 },
{ "KILL", true, irc_handle_kill },
{ "DIE", true, irc_handle_die },
{ "KILL", true, irc_handle_kill, 0, 0 },
{ "DIE", true, irc_handle_die, 0, 0 },
};
for (size_t i = 0; i < N_ELEMENTS (message_handlers); i++)
@ -2920,6 +3010,9 @@ irc_process_message (const struct irc_message *msg,
if (c->closing_link)
return;
c->n_received_messages++;
c->received_bytes += strlen (raw) + 2;
if (!flood_detector_check (&c->antiflood))
{
client_close_link (c, "Excess flood");
@ -2929,10 +3022,16 @@ irc_process_message (const struct irc_message *msg,
struct irc_command *cmd = str_map_find (&c->ctx->handlers, msg->command);
if (!cmd)
irc_send_reply (c, IRC_ERR_UNKNOWNCOMMAND, msg->command);
else if (cmd->requires_registration && !c->registered)
irc_send_reply (c, IRC_ERR_NOTREGISTERED);
else
cmd->handler (msg, c);
{
cmd->n_received++;
cmd->bytes_received += strlen (raw) + 2;
if (cmd->requires_registration && !c->registered)
irc_send_reply (c, IRC_ERR_NOTREGISTERED);
else
cmd->handler (msg, c);
}
}
// --- Network I/O -------------------------------------------------------------
@ -3263,6 +3362,7 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd)
struct client *c = xmalloc (sizeof *c);
client_init (c);
c->ctx = ctx;
c->opened = time (NULL);
c->socket_fd = fd;
c->hostname = xstrdup (host);
c->address = address;
@ -3741,6 +3841,7 @@ main (int argc, char *argv[])
struct server_context ctx;
server_context_init (&ctx);
ctx.started = time (NULL);
irc_register_handlers (&ctx);
irc_register_cap_handlers (&ctx);