/*- * Copyright 2016 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "rspamd.h" #include "util.h" #include "libutil/http.h" #include "libutil/http_private.h" #include "ottery.h" #include "cryptobox.h" #include "unix-std.h" #include #include #ifdef HAVE_SYS_WAIT_H #include #endif static guint port = 43000; static gchar *host = "127.0.0.1"; static gchar *server_key = NULL; static guint cache_size = 10; static guint nworkers = 1; static gboolean openssl_mode = FALSE; static guint file_size = 500; static guint pconns = 100; static gdouble test_time = 10.0; static gchar *latencies_file = NULL; static gboolean csv_output = FALSE; /* Dynamic vars */ static rspamd_inet_addr_t *addr; static guint32 workers_left = 0; static guint32 *conns_done = NULL; static const guint store_latencies = 1000; static guint32 conns_pending = 0; static GOptionEntry entries[] = { {"port", 'p', 0, G_OPTION_ARG_INT, &port, "Port number (default: 43000)", NULL}, {"cache", 'c', 0, G_OPTION_ARG_INT, &cache_size, "Keys cache size (default: 10)", NULL}, {"workers", 'n', 0, G_OPTION_ARG_INT, &nworkers, "Number of workers to start (default: 1)", NULL}, {"size", 's', 0, G_OPTION_ARG_INT, &file_size, "Size of payload to transfer (default: 500)", NULL}, {"conns", 'C', 0, G_OPTION_ARG_INT, &pconns, "Number of parallel connections (default: 100)", NULL}, {"time", 't', 0, G_OPTION_ARG_DOUBLE, &test_time, "Time to run tests (default: 10.0 sec)", NULL}, {"openssl", 'o', 0, G_OPTION_ARG_NONE, &openssl_mode, "Use openssl crypto", NULL}, {"host", 'h', 0, G_OPTION_ARG_STRING, &host, "Connect to the specified host (default: localhost)", NULL}, {"key", 'k', 0, G_OPTION_ARG_STRING, &server_key, "Use the specified key (base32 encoded)", NULL}, {"latency", 'l', 0, G_OPTION_ARG_FILENAME, &latencies_file, "Write latencies to the specified file", NULL}, {"csv", 0, 0, G_OPTION_ARG_NONE, &csv_output, "Output CSV", NULL}, {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL} }; struct lat_elt { gdouble lat; guchar checked; }; static struct lat_elt *latencies; static gint rspamd_client_body (struct rspamd_http_connection *conn, struct rspamd_http_message *msg, const gchar *chunk, gsize len) { g_assert (chunk[0] == '\0'); return 0; } struct client_cbdata { struct lat_elt *lat; guint32 *wconns; gdouble ts; struct ev_loop *ev_base; }; static void rspamd_client_err (struct rspamd_http_connection *conn, GError *err) { msg_info ("abnormally closing connection from: error: %s", err->message); g_assert (0); close (conn->fd); rspamd_http_connection_unref (conn); } static gint rspamd_client_finish (struct rspamd_http_connection *conn, struct rspamd_http_message *msg) { struct client_cbdata *cb = conn->ud; cb->lat->lat = rspamd_get_ticks () - cb->ts; cb->lat->checked = TRUE; (*cb->wconns) ++; conns_pending --; close (conn->fd); rspamd_http_connection_unref (conn); g_free (cb); if (conns_pending == 0) { event_base_loopexit (cb->ev_base, NULL); } return 0; } static void rspamd_http_client_func (struct ev_loop *ev_base, struct lat_elt *latency, guint32 *wconns, struct rspamd_cryptobox_pubkey *peer_key, struct rspamd_cryptobox_keypair* client_key, struct rspamd_keypair_cache *c) { struct rspamd_http_message *msg; struct rspamd_http_connection *conn; gchar urlbuf[PATH_MAX]; struct client_cbdata *cb; gint fd, flags; fd = rspamd_inet_address_connect (addr, SOCK_STREAM, TRUE); g_assert (fd != -1); flags = 1; (void)setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof (flags)); conn = rspamd_http_connection_new (rspamd_client_body, rspamd_client_err, rspamd_client_finish, RSPAMD_HTTP_CLIENT_SIMPLE, RSPAMD_HTTP_CLIENT, c, NULL); rspamd_snprintf (urlbuf, sizeof (urlbuf), "http://%s/%d", host, file_size); msg = rspamd_http_message_from_url (urlbuf); g_assert (conn != NULL && msg != NULL); if (peer_key != NULL) { g_assert (client_key != NULL); rspamd_http_connection_set_key (conn, client_key); msg->peer_key = rspamd_pubkey_ref (peer_key); } cb = g_malloc (sizeof (*cb)); cb->ts = rspamd_get_ticks (); cb->lat = latency; cb->ev_base = ev_base; cb->wconns = wconns; latency->checked = FALSE; rspamd_http_connection_write_message (conn, msg, NULL, NULL, cb, fd, NULL, ev_base); } static void rspamd_worker_func (struct lat_elt *plat, guint32 *wconns) { guint i, j; struct ev_loop *ev_base; struct itimerval itv; struct rspamd_keypair_cache *c = NULL; struct rspamd_cryptobox_keypair *client_key = NULL; struct rspamd_cryptobox_pubkey *peer_key = NULL; if (server_key) { peer_key = rspamd_pubkey_from_base32 (server_key, 0, RSPAMD_KEYPAIR_KEX, openssl_mode ? RSPAMD_CRYPTOBOX_MODE_NIST : RSPAMD_CRYPTOBOX_MODE_25519); g_assert (peer_key != NULL); client_key = rspamd_keypair_new (RSPAMD_KEYPAIR_KEX, openssl_mode ? RSPAMD_CRYPTOBOX_MODE_NIST : RSPAMD_CRYPTOBOX_MODE_25519); if (cache_size > 0) { c = rspamd_keypair_cache_new (cache_size); } } memset (&itv, 0, sizeof (itv)); double_to_tv (test_time, &itv.it_value); ev_base = event_init (); g_assert (setitimer (ITIMER_REAL, &itv, NULL) != -1); for (i = 0; ; i = (i + 1) % store_latencies) { for (j = 0; j < pconns; j++) { rspamd_http_client_func (ev_base, &plat[i * pconns + j], wconns, peer_key, client_key, c); } conns_pending = pconns; event_base_loop (ev_base, 0); } } static int cmpd (const void *p1, const void *p2) { const struct lat_elt *d1 = p1, *d2 = p2; return (d1->lat) - (d2->lat); } double rspamd_http_calculate_mean (struct lat_elt *lats, double *std) { guint i, cnt, checked = 0; gdouble mean = 0., dev = 0.; cnt = store_latencies * pconns; qsort (lats, cnt, sizeof (*lats), cmpd); for (i = 0; i < cnt; i++) { if (lats[i].checked) { mean += lats[i].lat; checked ++; } } g_assert (checked > 0); mean /= checked; for (i = 0; i < cnt; i++) { if (lats[i].checked) { dev += pow ((lats[i].lat - mean), 2); } } dev /= checked; *std = sqrt (dev); return mean; } static void rspamd_http_start_workers (pid_t *sfd) { guint i; for (i = 0; i < nworkers; i++) { sfd[i] = fork (); g_assert (sfd[i] != -1); if (sfd[i] == 0) { gperf_profiler_init (NULL, "http-bench"); rspamd_worker_func (&latencies[i * pconns * store_latencies], &conns_done[i]); gperf_profiler_stop (); exit (EXIT_SUCCESS); } workers_left ++; } } static void rspamd_http_stop_workers (pid_t *sfd) { guint i; gint res; for (i = 0; i < nworkers; i++) { kill (sfd[i], SIGTERM); wait (&res); } } static void rspamd_http_bench_term (int fd, short what, void *arg) { pid_t *sfd = arg; rspamd_http_stop_workers (sfd); event_loopexit (NULL); } static void rspamd_http_bench_cld (int fd, short what, void *arg) { gint res; while (waitpid (-1, &res, WNOHANG) > 0) { if (--workers_left == 0) { event_loopexit (NULL); } } } int main (int argc, char **argv) { GOptionContext *context; GError *error = NULL; pid_t *sfd; struct ev_loop *ev_base; rspamd_mempool_t *pool = rspamd_mempool_new (8192, "http-bench"); struct event term_ev, int_ev, cld_ev; guint64 total_done; FILE *lat_file; gdouble mean, std; guint i; rspamd_init_libs (); context = g_option_context_new ( "rspamd-http-bench - test server for benchmarks"); g_option_context_set_summary (context, "Summary:\n Rspamd test HTTP benchmark " RVERSION "\n Release id: " RID); g_option_context_add_main_entries (context, entries, NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { rspamd_fprintf (stderr, "option parsing failed: %s\n", error->message); g_error_free (error); exit (1); } rspamd_parse_inet_address (&addr, host, 0); g_assert (addr != NULL); rspamd_inet_address_set_port (addr, port); latencies = rspamd_mempool_alloc_shared (pool, nworkers * pconns * store_latencies * sizeof (*latencies)); sfd = g_malloc (sizeof (*sfd) * nworkers); conns_done = rspamd_mempool_alloc_shared (pool, sizeof (guint32) * nworkers); memset (conns_done, 0, sizeof (guint32) * nworkers); rspamd_http_start_workers (sfd); ev_base = event_init (); event_set (&term_ev, SIGTERM, EV_SIGNAL, rspamd_http_bench_term, sfd); event_base_set (ev_base, &term_ev); event_add (&term_ev, NULL); event_set (&int_ev, SIGINT, EV_SIGNAL, rspamd_http_bench_term, sfd); event_base_set (ev_base, &int_ev); event_add (&int_ev, NULL); event_set (&cld_ev, SIGCHLD, EV_SIGNAL|EV_PERSIST, rspamd_http_bench_cld, NULL); event_base_set (ev_base, &cld_ev); event_add (&cld_ev, NULL); event_base_loop (ev_base, 0); total_done = 0; for (i = 0; i < nworkers; i ++) { total_done += conns_done[i]; } mean = rspamd_http_calculate_mean (latencies, &std); if (!csv_output) { rspamd_printf ( "Made %L connections of size %d in %.6fs, %.6f cps, %.6f MB/sec\n", total_done, file_size, test_time, total_done / test_time, total_done * file_size / test_time / (1024.0 * 1024.0)); rspamd_printf ("Latency: %.6f ms mean, %.6f dev\n", mean * 1000.0, std * 1000.0); } else { /* size,connections,time,mean,stddev,conns,workers */ rspamd_printf ("%ud,%L,%.1f,%.6f,%.6f,%ud,%ud\n", file_size, total_done, test_time, mean*1000.0, std*1000.0, pconns, nworkers); } if (latencies_file) { lat_file = fopen (latencies_file, "w"); if (lat_file) { for (i = 0; i < store_latencies * pconns; i ++) { if (latencies[i].checked) { rspamd_fprintf (lat_file, "%.6f\n", latencies[i].lat); } } fclose (lat_file); } } rspamd_mempool_delete (pool); return 0; }