From ff10e1b652c1cf1eed68dc40b5ca33dcfe16e762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 13 Jun 2015 21:22:57 +0200 Subject: [PATCH] kike: implement STATS --- kike-replies | 4 ++ kike.c | 187 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 148 insertions(+), 43 deletions(-) diff --git a/kike-replies b/kike-replies index ed26232..0011120 100644 --- a/kike-replies +++ b/kike-replies @@ -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)" diff --git a/kike.c b/kike.c index 1e3fb9b..08b015b 100644 --- a/kike.c +++ b/kike.c @@ -25,6 +25,8 @@ #include "kike-replies.c" #include +// 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);