#include "imap-common.h"
#include "module-context.h"
#include "hash.h"
#include "hostpid.h"
#include "str.h"
#include "imap-commands.h"
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <dlfcn.h>
#define STATS_IMAP_CONTEXT(obj) \
MODULE_CONTEXT(obj, stats_imap_module)
struct stats_io {
uoff_t rchar, wchar, rbytes, wbytes, cwbytes;
unsigned int syscr, syscw;
};
struct stats_cmd_context {
union imap_module_context module_ctx;
command_func_t *orig_func;
struct timeval t1;
struct stats_io io1;
};
const char *stats_plugin_version = PACKAGE_VERSION;
static MODULE_CONTEXT_DEFINE_INIT(stats_imap_module, &imap_module_register);
static struct hash_table *orig_cmds;
static bool io_disabled = FALSE;
static int *sk_assert_build_cancel;
static bool stats_command(struct client_command_context *cmd);
static void stats_get_io(struct stats_io *io_r)
{
#ifdef __APPLE__
memset(io_r, 0, sizeof *io_r);
io_disabled = TRUE;
#else
char path[100], buf[1024];
const char *const *tmp;
int fd, ret;
memset(io_r, 0, sizeof(*io_r));
i_snprintf(path, sizeof(path), "/proc/%s/io", my_pid);
fd = open(path, O_RDONLY);
if (fd == -1) {
if (errno != ENOENT)
i_error("open(%s) failed: %m", path);
io_disabled = TRUE;
return;
}
ret = read(fd, buf, sizeof(buf));
if (ret <= 0) {
if (ret == -1)
i_error("read(%s) failed: %m", path);
else
i_error("read(%s) returned EOF", path);
} else if (ret == sizeof(buf)) {
i_error("%s is larger than expected", path);
} else {
buf[ret] = '\0';
tmp = t_strsplit(buf, "\n");
for (; *tmp != NULL; tmp++) {
if (strncmp(*tmp, "rchar: ", 7) == 0)
io_r->rchar = strtoull(*tmp + 7, NULL, 10);
else if (strncmp(*tmp, "wchar: ", 7) == 0)
io_r->wchar = strtoull(*tmp + 7, NULL, 10);
else if (strncmp(*tmp, "syscr: ", 7) == 0)
io_r->syscr = strtoul(*tmp + 7, NULL, 10);
else if (strncmp(*tmp, "syscw: ", 7) == 0)
io_r->syscw = strtoul(*tmp + 7, NULL, 10);
else if (strncmp(*tmp, "read_bytes: ", 12) == 0)
io_r->rbytes = strtoull(*tmp + 12, NULL, 10);
else if (strncmp(*tmp, "write_bytes: ", 13) == 0)
io_r->wbytes = strtoull(*tmp + 13, NULL, 10);
else if (strncmp(*tmp, "cancelled_write_bytes: ", 23) == 0)
io_r->cwbytes = strtoull(*tmp + 23, NULL, 10);
}
}
(void)close(fd);
#endif
}
static void stats_command_done(struct client_command_context *cmd,
struct stats_cmd_context *ctx)
{
struct timeval t2;
struct stats_io io2;
string_t *str;
if (gettimeofday(&t2, NULL) < 0)
i_error("gettimeofday() failed: %m");
t2.tv_sec -= ctx->t1.tv_sec;
t2.tv_usec -= ctx->t1.tv_usec;
if (t2.tv_usec < 0) {
t2.tv_sec++;
t2.tv_usec += 1000000;
}
str = t_str_new(256);
str_printfa(str, "Command: secs=%u.%03u",
(int)t2.tv_sec, (int)t2.tv_usec/1000);
stats_get_io(&io2);
if (!io_disabled) {
io2.rchar -= ctx->io1.rchar;
io2.wchar -= ctx->io1.wchar;
io2.syscr -= ctx->io1.syscr;
io2.syscw -= ctx->io1.syscw;
io2.rbytes -= ctx->io1.rbytes;
io2.wbytes -= ctx->io1.wbytes;
io2.cwbytes -= ctx->io1.cwbytes;
str_printfa(str, " rchar=%"PRIuUOFF_T" wchar=%"PRIuUOFF_T" "
"syscr=%u syscw=%u rbytes=%"PRIuUOFF_T" "
"wbytes=%"PRIuUOFF_T" cwbytes=%"PRIuUOFF_T,
io2.rchar, io2.wchar, io2.syscr, io2.syscw,
io2.rbytes, io2.wbytes, io2.cwbytes);
}
str_printfa(str, ": %s %s", cmd->tag, cmd->name);
if (cmd->args != NULL)
str_printfa(str, " %s", cmd->args);
i_info("%s", str_c(str));
}
static bool stats_command_continue(struct client_command_context *cmd)
{
struct stats_cmd_context *ctx = STATS_IMAP_CONTEXT(cmd);
bool ret;
cmd->func = ctx->orig_func;
ret = cmd->func(cmd);
i_assert(cmd->func != stats_command_continue);
i_assert(cmd->func != stats_command);
ctx->orig_func = cmd->func;
cmd->func = stats_command_continue;
if (ret || cmd->state >= CLIENT_COMMAND_STATE_WAIT_SYNC) {
stats_command_done(cmd, ctx);
return ret;
}
return FALSE;
}
static bool stats_command(struct client_command_context *cmd)
{
command_func_t *func;
struct stats_cmd_context *ctx;
bool ret;
ctx = p_new(cmd->pool, struct stats_cmd_context, 1);
func = hash_table_lookup(orig_cmds, cmd->name);
if (func == NULL)
i_panic("Lost command %s", cmd->name);
stats_get_io(&ctx->io1);
if (gettimeofday(&ctx->t1, NULL) < 0)
i_error("gettimeofday() failed: %m");
ret = func(cmd);
if (ret || cmd->state >= CLIENT_COMMAND_STATE_WAIT_SYNC) {
stats_command_done(cmd, ctx);
return ret;
}
MODULE_CONTEXT_SET(cmd, stats_imap_module, ctx);
ctx->orig_func = cmd->func == stats_command ? func : cmd->func;
cmd->func = stats_command_continue;
return FALSE;
}
void stats_plugin_init(void);
void stats_plugin_deinit(void);
void stats_plugin_init(void)
{
struct command *commands;
unsigned int i, count;
orig_cmds = hash_table_create(default_pool, default_pool, 0,
strcase_hash,
(hash_cmp_callback_t *)strcasecmp);
commands = array_get_modifiable(&imap_commands, &count);
for (i = 0; i < count; i++) {
if (strcasecmp(commands[i].name, "UID") == 0)
continue;
hash_table_insert(orig_cmds, (char *)commands[i].name,
commands[i].func);
commands[i].func = stats_command;
}
sk_assert_build_cancel = dlsym(RTLD_DEFAULT, "sk_assert_build_cancel");
if (sk_assert_build_cancel != NULL)
*sk_assert_build_cancel = 0;
}
void stats_plugin_deinit(void)
{
if (sk_assert_build_cancel != NULL)
*sk_assert_build_cancel = 1;
}