statsd-tg(1): Add manpage.
[statsd-tg.git] / src / statsd-tg.c
1 /**
2  * collectd-td - collectd traffic generator
3  * Copyright (C) 2013       Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian Forster <ff at octo.it>
20  **/
21
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <time.h>
31 #include <signal.h>
32 #include <errno.h>
33 #include <assert.h>
34 #include <time.h>
35 #include <pthread.h>
36
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <netdb.h>
40
41 #if !__GNUC__
42 # define __attribute__(x) /**/
43 #endif
44
45 #define DEF_NODE "localhost"
46 #define DEF_SERVICE "8125"
47
48 #define DEF_NUM_COUNTERS  1000
49 #define DEF_NUM_TIMERS    1000
50 #define DEF_NUM_GAUGES     100
51 #define DEF_NUM_SETS       100
52 #define DEF_SET_SIZE       128
53
54 static int conf_num_counters = DEF_NUM_COUNTERS;
55 static int conf_num_timers   = DEF_NUM_TIMERS;
56 static int conf_num_gauges   = DEF_NUM_GAUGES;
57 static int conf_num_sets     = DEF_NUM_SETS;
58 static int conf_set_size     = DEF_SET_SIZE;
59 static const char *conf_node = DEF_NODE;
60 static const char *conf_service = DEF_SERVICE;
61
62 static int conf_threads_num = 1;
63
64 static struct sigaction sigint_action;
65 static struct sigaction sigterm_action;
66
67 static unsigned long long events_sent = 0;
68 pthread_mutex_t events_sent_lock = PTHREAD_MUTEX_INITIALIZER;
69 static _Bool loop = 1;
70
71 __attribute__((noreturn))
72 static void exit_usage (int exit_status) /* {{{ */
73 {
74   fprintf ((exit_status == EXIT_FAILURE) ? stderr : stdout,
75       PACKAGE_NAME" -- statsd traffic generator\n"
76       "\n"
77       "  Usage: statsd-ng [OPTION]\n"
78       "\n"
79       "  Valid options:\n"
80       "    -c <number>    Number of counters to emulate. (Default: %i)\n"
81       "    -t <number>    Number of timers to emulate. (Default: %i)\n"
82       "    -g <number>    Number of gauges to emulate. (Default: %i)\n"
83       "    -s <number>    Number of sets to emulate. (Default: %i)\n"
84       "    -S <size>      Number of elements in each set. (Default: %i)\n"
85       "    -d <dest>      Destination address of the network packets.\n"
86       "                   (Default: "DEF_NODE")\n"
87       "    -D <port>      Destination port of the network packets.\n"
88       "                   (Default: "DEF_SERVICE")\n"
89       "    -T <threads>   Number of threads to use to generate load.\n"
90       "    -h             Print usage information (this output).\n"
91       "\n"
92       "Copyright (C) 2013  Florian Forster\n"
93       "Licensed under the GNU General Public License, version 2 (GPLv2)\n",
94       DEF_NUM_COUNTERS, DEF_NUM_TIMERS, DEF_NUM_GAUGES,
95       DEF_NUM_SETS, DEF_SET_SIZE);
96   exit (exit_status);
97 } /* }}} void exit_usage */
98
99 static void signal_handler (int signal __attribute__((unused))) /* {{{ */
100 {
101   loop = 0;
102 } /* }}} void signal_handler */
103
104 static int sock_open (void) /* {{{ */
105 {
106   struct addrinfo ai_hints;
107   struct addrinfo *ai_list = NULL;
108   struct addrinfo *ai_ptr;
109   int sock;
110
111   int status;
112
113   memset (&ai_hints, 0, sizeof (ai_hints));
114 #ifdef AI_ADDRCONFIG
115   ai_hints.ai_flags = AI_ADDRCONFIG;
116 #endif
117   ai_hints.ai_family = AF_UNSPEC;
118   ai_hints.ai_socktype = SOCK_DGRAM;
119
120   status = getaddrinfo (conf_node, conf_service, &ai_hints, &ai_list);
121   if (status != 0)
122   {
123     fprintf (stderr, "getaddrinfo failed: %s\n", gai_strerror (status));
124     exit (EXIT_FAILURE);
125   }
126
127   for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
128   {
129     int fd;
130
131     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
132     if (fd < 0)
133     {
134       continue;
135     }
136
137     status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
138     if (status != 0)
139     {
140       close (fd);
141       continue;
142     }
143
144     sock = fd;
145     break;
146   }
147
148   freeaddrinfo (ai_list);
149
150   if (sock < 0)
151   {
152     fprintf (stderr, "Opening network socket failed.\n");
153     exit (EXIT_FAILURE);
154   }
155
156   return (sock);
157 } /* }}} int sock_open */
158
159 static int send_random_event (int sock, unsigned short seed[static 3]) /* {{{ */
160 {
161   long conf_num_total = conf_num_counters + conf_num_timers
162       + conf_num_gauges + conf_num_sets;
163   /* Not completely fair, but good enough for our use-case. */
164   long rnd = nrand48 (seed) % conf_num_total;
165
166   long value = nrand48 (seed);
167   char *type;
168
169   char buffer[1024];
170   int buffer_size;
171   ssize_t status;
172
173   if (rnd < conf_num_counters)
174   {
175     /* counter */
176     type = "c";
177     value = (value % 8) + 1;
178   }
179   else if (rnd < (conf_num_counters + conf_num_timers))
180   {
181     /* timer */
182     type = "ms";
183     value = (value % 1024) + 1;
184   }
185   else if (rnd < (conf_num_counters + conf_num_timers + conf_num_gauges))
186   {
187     /* gauge */
188     type = "g";
189     value = (value % 128) - 64;
190   }
191   else
192   {
193     /* set */
194     type = "s";
195     value %= conf_set_size;
196   }
197
198   buffer_size = snprintf (buffer, sizeof (buffer), "%06li:%li|%s",
199                           rnd, value, type);
200   assert (buffer_size > 0);
201   if (((size_t) buffer_size) >= sizeof (buffer))
202     return (-1);
203   assert (buffer[buffer_size] == 0);
204
205   status = send (sock, buffer, (size_t) buffer_size, /* flags = */ 0);
206   if (status < 0)
207   {
208     fprintf (stderr, "send failed: %s\n", strerror (errno));
209     return (-1);
210   }
211
212   return (0);
213 } /* }}} int send_random_event */
214
215 static int get_integer_opt (const char *str, int *ret_value) /* {{{ */
216 {
217   char *endptr;
218   int tmp;
219
220   errno = 0;
221   endptr = NULL;
222   tmp = (int) strtol (str, &endptr, /* base = */ 0);
223   if (errno != 0)
224   {
225     fprintf (stderr, "Unable to parse option as a number: \"%s\": %s\n",
226         str, strerror (errno));
227     exit (EXIT_FAILURE);
228   }
229   else if (endptr == str)
230   {
231     fprintf (stderr, "Unable to parse option as a number: \"%s\"\n", str);
232     exit (EXIT_FAILURE);
233   }
234   else if (*endptr != 0)
235   {
236     fprintf (stderr, "Garbage after end of value: \"%s\"\n", str);
237     exit (EXIT_FAILURE);
238   }
239
240   *ret_value = tmp;
241   return (0);
242 } /* }}} int get_integer_opt */
243
244 static int read_options (int argc, char **argv) /* {{{ */
245 {
246   int opt;
247
248 #ifdef _SC_NPROCESSORS_ONLN
249   conf_threads_num = (int) sysconf (_SC_NPROCESSORS_ONLN);
250 #endif
251
252   while ((opt = getopt (argc, argv, "c:t:g:s:S:d:D:T:h")) != -1)
253   {
254     switch (opt)
255     {
256       case 'c':
257         get_integer_opt (optarg, &conf_num_counters);
258         break;
259
260       case 't':
261         get_integer_opt (optarg, &conf_num_timers);
262         break;
263
264       case 'g':
265         get_integer_opt (optarg, &conf_num_gauges);
266         break;
267
268       case 's':
269         get_integer_opt (optarg, &conf_num_sets);
270         break;
271
272       case 'S':
273         get_integer_opt (optarg, &conf_set_size);
274         break;
275
276       case 'd':
277         conf_node = optarg;
278         break;
279
280       case 'D':
281         conf_service = optarg;
282         break;
283
284       case 'T':
285         get_integer_opt (optarg, &conf_threads_num);
286         break;
287
288       case 'h':
289         exit_usage (EXIT_SUCCESS);
290
291       default:
292         exit_usage (EXIT_FAILURE);
293     } /* switch (opt) */
294   } /* while (getopt) */
295
296   return (0);
297 } /* }}} int read_options */
298
299 static void *send_thread (void *args __attribute__((unused))) /* {{{ */
300 {
301   int sock;
302   unsigned short seed[3];
303   struct timespec ts;
304
305   unsigned long long local_events_sent = 0;
306
307   clock_gettime (CLOCK_REALTIME, &ts);
308   seed[2] = (unsigned short) (ts.tv_nsec);
309   seed[1] = (unsigned short) (ts.tv_nsec >> 16);
310   seed[0] = (unsigned short) (ts.tv_sec);
311
312   sock = sock_open ();
313
314   while (loop)
315   {
316     send_random_event (sock, seed);
317     local_events_sent++;
318   }
319
320   close (sock);
321
322   pthread_mutex_lock (&events_sent_lock);
323   events_sent += local_events_sent;
324   pthread_mutex_unlock (&events_sent_lock);
325
326   return (NULL);
327 } /* }}} void *send_thread */
328
329 static void run_threads (void) /* {{{ */
330 {
331   pthread_t threads[conf_threads_num];
332   int i;
333
334   for (i = 0; i < conf_threads_num; i++)
335   {
336     int status;
337
338     status = pthread_create (&threads[i], /* attr = */ NULL,
339         send_thread, /* args = */ NULL);
340     if (status != 0)
341     {
342       fprintf (stderr, "pthread_create failed.");
343       abort ();
344     }
345   }
346
347   for (i = 0; i < conf_threads_num; i++)
348     pthread_join (threads[i], /* retval = */ NULL);
349 } /* }}} void run_threads */
350
351 static double timespec_diff (struct timespec const *ts0, /* {{{ */
352     struct timespec const *ts1)
353 {
354   time_t diff_sec;
355   long diff_nsec;
356
357   diff_sec = ts1->tv_sec - ts0->tv_sec;
358   diff_nsec += ts1->tv_nsec - ts0->tv_nsec;
359
360   return ((double) diff_sec) + (((double) diff_nsec) / 1.0e9);
361 } /* }}} double timespec_diff */
362
363 int main (int argc, char **argv) /* {{{ */
364 {
365   struct timespec ts_begin;
366   struct timespec ts_end;
367   double runtime;
368
369   read_options (argc, argv);
370
371   sigint_action.sa_handler = signal_handler;
372   sigaction (SIGINT, &sigint_action, /* old = */ NULL);
373
374   sigterm_action.sa_handler = signal_handler;
375   sigaction (SIGTERM, &sigterm_action, /* old = */ NULL);
376
377   clock_gettime (CLOCK_MONOTONIC, &ts_begin);
378   run_threads ();
379   clock_gettime (CLOCK_MONOTONIC, &ts_end);
380
381   runtime = timespec_diff (&ts_begin, &ts_end);
382   printf ("Sent %llu events in %.0fs (%.0f events/s).\n",
383       events_sent, runtime, ((double) events_sent) / runtime);
384
385   exit (EXIT_SUCCESS);
386   return (0);
387 } /* }}} int main */
388
389 /* vim: set sw=2 sts=2 et fdm=marker : */