static void stats_add_session(struct mail_user *user) { struct stats_user *suser = STATS_USER_CONTEXT(user); struct stats *new_stats, *diff_stats; const char *error; new_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); mail_user_stats_fill(user, new_stats); /* we'll count new_stats-pre_io_stats and add the changes to session_stats. the new_stats can't be directly copied to session_stats because there are some fields that don't start from zero, like clock_time. (actually with stats_global_user code we're requiring that clock_time is the only such field..) */ if (!stats_diff(suser->pre_io_stats, new_stats, diff_stats, &error)) i_error("stats: session stats shrank: %s", error); stats_add(suser->session_stats, diff_stats); /* copying is only needed if stats_global_user=NULL */ stats_copy(suser->pre_io_stats, new_stats); }
int mail_session_update_parse(const char *const *args, const char **error_r) { struct mail_session *session; struct stats *new_stats, *diff_stats; buffer_t *buf; const char *error; /* <session id> <stats> */ if (mail_session_get(args[0], &session, error_r) < 0) return -1; buf = buffer_create_dynamic(pool_datastack_create(), 256); if (args[1] == NULL || base64_decode(args[1], strlen(args[1]), NULL, buf) < 0) { *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: Invalid base64 input", session->user->name, session->service, session->id); return -1; } new_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); if (!stats_import(buf->data, buf->used, session->stats, new_stats, &error)) { *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: %s", session->user->name, session->service, session->id, error); return -1; } if (!stats_diff(session->stats, new_stats, diff_stats, &error)) { *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: stats shrank: %s", session->user->name, session->service, session->id, error); i_panic("%s", *error_r); return -1; } mail_session_refresh(session, diff_stats); return 0; }
static void stats_command_pre(struct client_command_context *cmd) { struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user); struct stats_client_command *scmd; static unsigned int stats_cmd_id_counter = 0; if (suser == NULL || !suser->track_commands) return; if (strcasecmp(cmd->name, "IDLE") == 0) { /* IDLE can run forever and waste stats process's memory while waiting for it to timeout. don't send them. */ return; } scmd = IMAP_STATS_IMAP_CONTEXT(cmd); if (scmd == NULL) { scmd = p_new(cmd->pool, struct stats_client_command, 1); scmd->id = ++stats_cmd_id_counter; scmd->stats = stats_alloc(cmd->pool); scmd->pre_stats = stats_alloc(cmd->pool); MODULE_CONTEXT_SET(cmd, imap_stats_imap_module, scmd); }
static void stats_user_created(struct mail_user *user) { struct ioloop_context *ioloop_ctx = io_loop_get_current_context(current_ioloop); struct stats_user *suser; struct mail_user_vfuncs *v = user->vlast; const char *path, *str, *error; unsigned int refresh_secs; if (ioloop_ctx == NULL) { /* we're probably running some test program, or at least mail-storage-service wasn't used to create this user. disable stats tracking. */ return; } if (user->autocreated) { /* lda / shared user. we're not tracking this one. */ return; } /* get refresh time */ str = mail_user_plugin_getenv(user, "stats_refresh"); if (str == NULL) return; if (settings_get_time(str, &refresh_secs, &error) < 0) { i_error("stats: Invalid stats_refresh setting: %s", error); return; } if (refresh_secs == 0) return; if (refresh_secs > SESSION_STATS_FORCE_REFRESH_SECS) { i_warning("stats: stats_refresh too large, changing to %u", SESSION_STATS_FORCE_REFRESH_SECS); refresh_secs = SESSION_STATS_FORCE_REFRESH_SECS; } if (global_stats_conn == NULL) { path = t_strconcat(user->set->base_dir, "/"MAIL_STATS_SOCKET_NAME, NULL); global_stats_conn = stats_connection_create(path); } stats_connection_ref(global_stats_conn); if (stats_user_count == 0) { /* first user connection */ stats_global_user = user; } else if (stats_user_count == 1) { /* second user connection. we'll need to start doing per-io callback tracking now. (we might have been doing it also previously but just temporarily quickly dropped to having 1 user, in which case stats_global_user=NULL) */ if (stats_global_user != NULL) { stats_add_session(stats_global_user); stats_global_user = NULL; } } stats_user_count++; suser = p_new(user->pool, struct stats_user, 1); suser->module_ctx.super = *v; user->vlast = &suser->module_ctx.super; v->deinit = stats_user_deinit; v->stats_fill = stats_user_stats_fill; suser->refresh_secs = refresh_secs; str = mail_user_plugin_getenv(user, "stats_track_cmds"); if (str != NULL && strcmp(str, "yes") == 0) suser->track_commands = TRUE; suser->stats_conn = global_stats_conn; if (user->session_id != NULL && user->session_id[0] != '\0') suser->stats_session_id = user->session_id; else { guid_128_t guid; guid_128_generate(guid); suser->stats_session_id = p_strdup(user->pool, guid_128_to_string(guid)); } suser->last_session_update = time(NULL); user->stats_enabled = TRUE; suser->ioloop_ctx = ioloop_ctx; io_loop_context_add_callbacks(ioloop_ctx, stats_io_activate, stats_io_deactivate, user); suser->pre_io_stats = stats_alloc(user->pool); suser->session_stats = stats_alloc(user->pool); suser->last_sent_session_stats = stats_alloc(user->pool); MODULE_CONTEXT_SET(user, stats_user_module, suser); mail_stats_connection_connect(suser->stats_conn, user); suser->to_stats_timeout = timeout_add(suser->refresh_secs*1000, session_stats_refresh_timeout, user); /* fill the initial values. this is necessary for the process-global values (e.g. getrusage()) if the process is reused for multiple users. otherwise the next user will start with the previous one's last values. */ mail_user_stats_fill(user, suser->pre_io_stats); }
int main(int argc, char **argv) { struct addrinfo *addrs, *addr; char *host = "127.0.0.1"; char *port = "1337"; int rc; // for checking that the server's up char poke[SHA_LENGTH * 2]; tinymt64_t rando; if (parse_args(&cfg, &host, &port, argc, argv)) { usage(); exit(1); } struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM }; if ((rc = getaddrinfo(host, port, &hints, &addrs)) != 0) { const char *msg = gai_strerror(rc); fprintf(stderr, "unable to resolve %s:%s: %s\n", host, port, msg); exit(1); } for (addr = addrs; addr != NULL; addr = addr->ai_next) { int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (fd == -1) continue; rc = connect(fd, addr->ai_addr, addr->ai_addrlen); tinymt64_init(&rando, time_us()); random_hash(&rando, poke); if (rc == 0 && write(fd, poke, SHA_LENGTH * 2) == SHA_LENGTH * 2) { read(fd, poke, SHA_LENGTH * 2); close(fd); break; } close(fd); } if (addr == NULL) { char *msg = strerror(errno); fprintf(stderr, "unable to connect to %s:%s: %s\n", host, port, msg); exit(1); } signal(SIGPIPE, SIG_IGN); signal(SIGINT, SIG_IGN); cfg.addr = *addr; pthread_mutex_init(&statistics.mutex, NULL); statistics.latency = stats_alloc(SAMPLES); statistics.requests = stats_alloc(SAMPLES); thread *threads = zcalloc(cfg.threads * sizeof(thread)); uint64_t connections = cfg.connections / cfg.threads; uint64_t stop_at = time_us() + (cfg.duration * 1000000); for (uint64_t i = 0; i < cfg.threads; i++) { thread *t = &threads[i]; t->connections = connections; t->stop_at = stop_at; if (pthread_create(&t->thread, NULL, &thread_main, t)) { char *msg = strerror(errno); fprintf(stderr, "unable to create thread %"PRIu64" %s\n", i, msg); exit(2); } } struct sigaction sa = { .sa_handler = handler, .sa_flags = 0, }; sigfillset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL); char *time = format_time_s(cfg.duration); printf("Running %s test @ %s:%s\n", time, host, port); printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); uint64_t start = time_us(); uint64_t complete = 0; uint64_t bytes = 0; errors errors = { 0 }; for (uint64_t i = 0; i < cfg.threads; i++) { thread *t = &threads[i]; pthread_join(t->thread, NULL); complete += t->complete; bytes += t->bytes; errors.connect += t->errors.connect; errors.handshake += t->errors.handshake; errors.read += t->errors.read; errors.validate += t->errors.validate; errors.write += t->errors.write; errors.timeout += t->errors.timeout; } uint64_t runtime_us = time_us() - start; long double runtime_s = runtime_us / 1000000.0; long double req_per_s = complete / runtime_s; long double bytes_per_s = bytes / runtime_s; print_stats_header(); print_stats("Latency", statistics.latency, format_time_us); print_stats("Req/Sec", statistics.requests, format_metric); if (cfg.latency) print_stats_latency(statistics.latency); char *runtime_msg = format_time_us(runtime_us); printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); if (errors.connect || errors.read || errors.write || errors.timeout) { printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", errors.connect, errors.read, errors.write, errors.timeout); } if (errors.handshake) { printf(" Bad handshakes from server: %d\n", errors.handshake); } if (errors.validate) { printf(" %d proofs failed verification.\n", errors.validate); } printf("Requests/sec: %9.2Lf\n", req_per_s); printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s)); return 0; } void *thread_main(void *arg) { thread *thread = arg; aeEventLoop *loop = aeCreateEventLoop(10 + cfg.connections * 3); thread->cs = zmalloc(thread->connections * sizeof(connection)); thread->loop = loop; tinymt64_init(&thread->rand, time_us()); thread->latency = stats_alloc(100000); connection *c = thread->cs; for (uint64_t i = 0; i < thread->connections; i++, c++) { c->thread = thread; random_hash(&thread->rand, c->hash); connect_socket(thread, c); } aeCreateTimeEvent(loop, CALIBRATE_DELAY_MS, calibrate, thread, NULL); aeCreateTimeEvent(loop, TIMEOUT_INTERVAL_MS, check_timeouts, thread, NULL); thread->start = time_us(); aeMain(loop); aeDeleteEventLoop(loop); zfree(thread->cs); uint64_t max = thread->latency->max; stats_free(thread->latency); pthread_mutex_lock(&statistics.mutex); for (uint64_t i = 0; i < thread->missed; i++) { stats_record(statistics.latency, max); } pthread_mutex_unlock(&statistics.mutex); return NULL; }
int mail_command_update_parse(const char *const *args, const char **error_r) { struct mail_session *session; struct mail_command *cmd; struct stats *new_stats, *diff_stats; buffer_t *buf; const char *error; unsigned int i, cmd_id; bool done = FALSE, continued = FALSE; /* <session guid> <cmd id> [d] <name> <args> <stats> <session guid> <cmd id> c[d] <stats> */ if (str_array_length(args) < 3) { *error_r = "UPDATE-CMD: Too few parameters"; return -1; } if (mail_session_get(args[0], &session, error_r) < 0) return -1; if (str_to_uint(args[1], &cmd_id) < 0 || cmd_id == 0) { *error_r = "UPDATE-CMD: Invalid command id"; return -1; } for (i = 0; args[2][i] != '\0'; i++) { switch (args[2][i]) { case 'd': done = TRUE; break; case 'c': continued = TRUE; break; default: *error_r = "UPDATE-CMD: Invalid flags parameter"; return -1; } } cmd = mail_command_find(session, cmd_id); if (!continued) { /* new command */ if (cmd != NULL) { *error_r = "UPDATE-CMD: Duplicate new command id"; return -1; } if (str_array_length(args) < 5) { *error_r = "UPDATE-CMD: Too few parameters"; return -1; } cmd = mail_command_add(session, args[3], args[4]); cmd->id = cmd_id; session->highest_cmd_id = I_MAX(session->highest_cmd_id, cmd_id); session->num_cmds++; session->user->num_cmds++; session->user->domain->num_cmds++; if (session->ip != NULL) session->ip->num_cmds++; mail_global_stats.num_cmds++; args += 5; } else { if (cmd == NULL) { /* already expired command, ignore */ i_warning("UPDATE-CMD: Already expired"); return 0; } args += 3; cmd->last_update = ioloop_timeval; } buf = t_buffer_create(256); if (args[0] == NULL || base64_decode(args[0], strlen(args[0]), NULL, buf) < 0) { *error_r = t_strdup_printf("UPDATE-CMD: Invalid base64 input"); return -1; } new_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); if (!stats_import(buf->data, buf->used, cmd->stats, new_stats, &error)) { *error_r = t_strdup_printf("UPDATE-CMD: %s", error); return -1; } if (!stats_diff(cmd->stats, new_stats, diff_stats, &error)) { *error_r = t_strdup_printf("UPDATE-CMD: stats shrank: %s", error); return -1; } stats_add(cmd->stats, diff_stats); if (done) { cmd->id = 0; mail_command_unref(&cmd); } mail_session_refresh(session, NULL); return 0; }
int main (int argc, char **argv) { List nflist; int summary = FALSE; int error_occurred = FALSE; struct stats *total_stats = stats_alloc (); int processed = 0; int opt; int option_index = 0; extern char *optarg; extern int optind, opterr, optopt; struct option long_options[] = { {"debug",0,0,'D'}, {"summary",0,0,'s'}, {"help",0,0,'h'}, {"version",0,0,0}, {0,0,0,0} }; #ifdef __GLIBC__ program_name = program_invocation_short_name; #else program_name = base_name (argv[0]); #endif /* Initialize i18n. */ #ifdef HAVE_SETLOCALE setlocale (LC_ALL, ""); #endif #if ENABLE_NLS bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); #endif setup (); while ((opt = getopt_long (argc, argv, "sh", long_options, &option_index)) != -1) { switch (opt) { case 0: { printf_version_string (N_("nfstats")); teardown (); if (fclose (stdout) == EOF) error (EXIT_FAILURE, errno, _("error writing output")); exit (EXIT_SUCCESS); } case 'D': debug = TRUE; break; case 's': summary = TRUE; break; case 'h': printf (_("Usage: %s [OPTION]... NOTESFILE...\n" "Display usage statistics for NOTESFILE(s), including a total.\n\n"), program_name); printf (_(" -s, --summary Print only the total for all listed notesfiles\n" " --debug Display debugging messages\n\n" " -h, --help Display this help and exit\n" " --version Display version information and exit\n\n")); printf (_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); teardown (); if (fclose (stdout) == EOF) error (EXIT_FAILURE, errno, _("error writing output")); exit (EXIT_SUCCESS); case '?': fprintf (stderr, _("Try '%s --help' for more information.\n"), program_name); teardown (); exit (EXIT_FAILURE); } } if (optind == argc) { fprintf (stderr, _("%s: too few arguments\n"), program_name); fprintf (stderr, _("Try '%s --help' for more information.\n"), program_name); teardown (); exit (EXIT_FAILURE); } list_init (&nflist, (void * (*) (void)) nfref_alloc, (void (*) (void *)) nfref_free, NULL); while (optind < argc) parse_nf (argv[optind++], &nflist); { ListNode *node = list_head (&nflist); struct stats *stats = stats_alloc (); while (node != NULL && !error_occurred) { newts_nfref *ref = (newts_nfref *) list_data (node); int result = get_stats (ref, stats); if (result != NEWTS_NO_ERROR) { fprintf (stderr, _("%s: error getting stats for '%s'\n"), program_name, nfref_pretty_name (ref)); error_occurred = TRUE; break; } if (!summary) { if (processed) printf ("\n"); printf (_("Usage statistics for %s\n"), nfref_pretty_name (ref)); printf (_(" NOTES RESPS TOTALS\n")); printf (_("Local Reads: %7u %7u %7u\n"), stats->notes_read, stats->resps_read, (stats->notes_read + stats->resps_read)); printf (_("Local Writes: %7u %7u %7u\n"), (stats->notes_written - stats->notes_received), (stats->resps_written - stats->resps_received), (stats->notes_written + stats->resps_written - stats->notes_received - stats->resps_received)); printf (_("Entries into Notesfile: %u\n"), stats->entries); printf (_("Total Time in Notesfile: %.2f minutes\n"), ((float) stats->total_time / 60.0)); if (stats->entries) printf (_("Average Time/Entry: %.2f minutes\n"), (((float) stats->total_time / 60.0) / (float) stats->entries)); } stats_accumulate (stats, total_stats); processed++; node = list_next (node); } stats_free (stats); } if (processed && (summary || processed != 1) && !error_occurred) { if (!summary) printf ("\n"); printf (_("Total for all requested notesfiles\n")); printf (_(" NOTES RESPS TOTALS\n")); printf (_("Local Reads: %7u %7u %7u\n"), total_stats->notes_read, total_stats->resps_read, (total_stats->notes_read + total_stats->resps_read)); printf (_("Local Writes: %7u %7u %7u\n"), (total_stats->notes_written - total_stats->notes_received), (total_stats->resps_written - total_stats->resps_received), (total_stats->notes_written + total_stats->resps_written - total_stats->notes_received - total_stats->resps_received)); printf (_("Entries into Notesfiles: %u\n"), total_stats->entries); printf (_("Total Time in Notesfiles: %.2f minutes\n"), ((float) total_stats->total_time / 60.0)); if (total_stats->entries) printf (_("Average Time/Entry: %.2f minutes\n"), (((float) total_stats->total_time / 60.0) / (float) total_stats->entries)); } stats_free (total_stats); list_destroy (&nflist); teardown (); if (fclose (stdout) == EOF) error (EXIT_FAILURE, errno, _("error writing output")); exit (error_occurred ? EXIT_FAILURE : EXIT_SUCCESS); }