1 /*************************************************************************************
2 Copyright (C) 2012, 2013 James Slocum
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy of this
5 software and associated documentation files (the "Software"), to deal in the Software
6 without restriction, including without limitation the rights to use, copy, modify,
7 merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8 permit persons to whom the Software is furnished to do so, subject to the following
9 conditions:
10
11 The above copyright notice and this permission notice shall be included in all copies
12 or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16 PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
19 OR OTHER DEALINGS IN THE SOFTWARE.
20 **************************************************************************************/
21
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <inttypes.h>
27 #include <math.h>
28 #include <errno.h>
29
30 #if defined (_WIN32)
31 #include <windows.h>
32 #include <winsock2.h>
33 #include <winsock.h>
34 #include <ws2tcpip.h>
35 #else
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <arpa/inet.h>
40 #include <netinet/in.h>
41 #include <netdb.h>
42 #endif
43
44 #include "statsd.h"
45
46 //Define the private functions
47 static const char *networkToPresentation(int af, const void *src, char *dst, size_t size);
48 static int sendToServer(Statsd* stats, const char* bucket, StatsType type, int64_t delta, double sampleRate);
49 static ssize_t buildStatString(char* stat, size_t stat_size, const char* nameSpace, const char* bucket, StatsType type, int64_t delta, double sampleRate);
50 static ssize_t buildStatString_dbl(char* stat, size_t stat_size, const char* nameSpace, const char* bucket, StatsType type, double delta, double sampleRate);
51
networkToPresentation(int af,const void * src,char * dst,size_t size)52 static const char *networkToPresentation(int af, const void *src, char *dst, size_t size){
53 return inet_ntop(af, src, dst, size);
54 }
55
56 /**
57 This is a helper function that will do the dirty work of sending
58 the stats to the statsd server.
59
60 @param[in] stats - The stats client object
61 @param[in] bucket - The optional bucket name, if this is null then
62 the defualt bucket name is used from the stats object.
63 @param[in] type - The type of stat being sent
64 @param[in] delta - The value to send
65 @param[in] sampleRate - The sample rate of this stat. If the value is
66 less then or equal to 0, or greater then or equal to 1, it is ignored.
67
68 @return STATSD_SUCCESS on success, STATSD_BAD_STATS_TYPE if the type is
69 not recognized. STATSD_UDP_SEND if the sendto() failed.
70 */
sendToServer(Statsd * stats,const char * bucket,StatsType type,int64_t delta,double sampleRate)71 static int sendToServer(Statsd* stats, const char* bucket, StatsType type, int64_t delta, double sampleRate){
72 //See if we randomly fall under the sample rate
73 if (sampleRate > 0 && sampleRate < 1 && (double)((double)stats->random() / RAND_MAX) >= sampleRate){
74 return STATSD_SUCCESS;
75 }
76
77 char data[256];
78 ssize_t dataLength = 0;
79
80 //If the user has not specified a bucket, we will use the defualt
81 //bucket instead.
82 if (!bucket){
83 bucket = stats->bucket;
84 }
85
86 dataLength = buildStatString(data, sizeof(data), stats->nameSpace, bucket, type, delta, sampleRate);
87 if (dataLength < 0) {
88 return -1;
89 }
90
91 //Send the packet
92 int sent = sendto(stats->socketFd, data, dataLength, 0, (const struct sockaddr*)&stats->destination, sizeof(struct sockaddr_in));
93
94 if (sent == -1) {
95 return STATSD_UDP_SEND;
96 }
97
98 return STATSD_SUCCESS;
99 }
100
101 /**
102 This is a helper function that will build up a stats string and return its
103 length.
104
105 @param[in,out] stat - This is where the final string will be placed
106 @param[in] nameSpace - The namespace of the stat
107 @param[in] bucket - The bucket where to put the stat
108 @param[in] type - The type of stat being packed
109 @param[in] delta - The value of the stat
110 @param[in] sampleRate - The intervals at which this data was gathered
111
112 @return The length of the stat string, or -1 on error
113 */
buildStatString(char * stat,size_t stat_size,const char * nameSpace,const char * bucket,StatsType type,int64_t delta,double sampleRate)114 static ssize_t buildStatString(char* stat, size_t stat_size, const char* nameSpace, const char* bucket, StatsType type, int64_t delta, double sampleRate){
115 char* statType = NULL;
116
117 //Figure out what type of message to generate
118 switch(type){
119 case STATSD_COUNT:
120 statType = "c";
121 break;
122 case STATSD_GAUGE:
123 statType = "g";
124 break;
125 case STATSD_SET:
126 statType = "s";
127 break;
128 case STATSD_TIMING:
129 statType = "ms";
130 break;
131 default:
132 return -STATSD_BAD_STATS_TYPE;
133 }
134
135 //Do we have a sample rate?
136 int resulting_size;
137 if (sampleRate > 0.0 && sampleRate < 1.0) {
138 resulting_size = snprintf(stat, stat_size, "%s%s%s:%"PRId64"|%s|@%.2f\n",
139 nameSpace ? nameSpace : "", nameSpace ? "." : "",
140 bucket, delta, statType, sampleRate);
141 } else {
142 resulting_size = snprintf(stat, stat_size, "%s%s%s:%"PRId64"|%s\n",
143 nameSpace ? nameSpace : "", nameSpace ? "." : "",
144 bucket, delta, statType);
145 }
146 if(resulting_size >= stat_size) {
147 if(stat_size >= 1)
148 stat[0] = '\0';
149 return -1;
150 }
151
152 return resulting_size;
153 }
154
buildStatString_dbl(char * stat,size_t stat_size,const char * nameSpace,const char * bucket,StatsType type,double delta,double sampleRate)155 static ssize_t buildStatString_dbl(char* stat, size_t stat_size, const char* nameSpace, const char* bucket, StatsType type, double delta, double sampleRate){
156 char* statType = NULL;
157
158 if(!isfinite(delta))
159 return 0;
160
161 //Figure out what type of message to generate
162 switch(type){
163 case STATSD_COUNT:
164 statType = "c";
165 break;
166 case STATSD_GAUGE:
167 statType = "g";
168 break;
169 case STATSD_SET:
170 statType = "s";
171 break;
172 case STATSD_TIMING:
173 statType = "ms";
174 break;
175 default:
176 return -STATSD_BAD_STATS_TYPE;
177 }
178
179 //Do we have a sample rate?
180 int resulting_size;
181 if (sampleRate > 0.0 && sampleRate < 1.0) {
182 resulting_size = snprintf(stat, stat_size, "%s%s%s:%.1f|%s|@%.2f\n",
183 nameSpace ? nameSpace : "", nameSpace ? "." : "",
184 bucket, delta, statType, sampleRate);
185 } else {
186 resulting_size = snprintf(stat, stat_size, "%s%s%s:%.1f|%s\n",
187 nameSpace ? nameSpace : "", nameSpace ? "." : "",
188 bucket, delta, statType);
189 }
190 if(resulting_size >= stat_size) {
191 if(stat_size >= 1)
192 stat[0] = '\0';
193 return -1;
194 }
195
196 return resulting_size;
197 }
198
199
200 //Implement the public functions
201
202 /**
203 This is allocate the memory for a statsd object from the heap
204 initialize it with the givin values and return the newly constructed
205 object ready for use.
206
207 @param[out] stats - This is where the statsd object will be placed.
208 @param[in] serverAddress - The hostname, or ip address of the statsd server
209 @param[in] port - The port number to use for statsd packets
210 @param[in] bucket - The unique bucket name to use for reporting stats
211
212 @return STATSD_SUCCESS on success, or an error if something went wrong
213 @see StatsError
214 */
statsd_new(Statsd ** stats,const char * serverAddress,int port,const char * nameSpace,const char * bucket)215 int ADDCALL statsd_new(Statsd** stats, const char* serverAddress, int port, const char* nameSpace, const char* bucket){
216 Statsd* newStats = (Statsd*)malloc(sizeof(Statsd));
217 if (!newStats){
218 return STATSD_MALLOC;
219 }
220
221 memset(newStats, 0, sizeof(Statsd));
222 newStats->socketFd = -1;
223 *stats = newStats;
224 return statsd_init(*stats, serverAddress, port, nameSpace, bucket);
225 }
226
227 /**
228 Free a statsd object created through a call to statsd_new()
229
230 @param[in] statsd - The statsd object created by a call to
231 statsd_new().
232 */
statsd_free(Statsd * statsd)233 void ADDCALL statsd_free(Statsd* statsd){
234 if(!statsd)
235 return;
236
237 statsd_release(statsd);
238 free(statsd);
239 }
240
241 /**
242 Free the resources in a statsd object initialized through a
243 call to statsd_init()
244
245 @param[in] statsd - The statsd object initialized by a call
246 to statsd_init().
247 */
statsd_release(Statsd * statsd)248 void ADDCALL statsd_release(Statsd* statsd){
249 if(!statsd)
250 return;
251
252 if (statsd->socketFd > 0){
253 close(statsd->socketFd);
254 statsd->socketFd = -1;
255 }
256 }
257
258 /**
259 This will initialize (or reinitialize) a statsd object that
260 has already been created by a call to statsd_new() or has been
261 allocated statically on the stack.
262
263 @param[in,out] statsd - A previously allocated statsd object
264 @param[in] server - The hostname or ip address of the server
265 @param[in] port - The port number that the packets will be sent to
266 @param[in] bucket - The default bucket name that will be used for
267 the stats
268
269 @return SCTE_SUCCESS on success, or an error otherwise
270 @see StatsError
271 */
statsd_init(Statsd * statsd,const char * server,int port,const char * nameSpace,const char * bucket)272 int ADDCALL statsd_init(Statsd* statsd, const char* server, int port, const char* nameSpace, const char* bucket){
273 //Do a DNS lookup (or IP address conversion) for the serverAddress
274 struct addrinfo hints, *result = NULL;
275 memset(&hints, 0, sizeof(hints));
276
277 //Set the hints to narrow downs the DNS entry we want
278 hints.ai_family = AF_INET;
279
280 int addrinfoStatus = getaddrinfo(server, NULL, &hints, &result);
281 if (addrinfoStatus != 0){
282 return STATSD_BAD_SERVER_ADDRESS;
283 }
284
285 //Copy the result into the UDP destination socket structure
286 memcpy(&statsd->destination, result->ai_addr, sizeof(struct sockaddr_in));
287 statsd->destination.sin_port = htons((short)port);
288
289 statsd->serverAddress = server;
290 statsd->port = port;
291 statsd->nameSpace = nameSpace;
292 statsd->bucket = bucket;
293 statsd->random = rand;
294
295 //Free the result now that we have copied the data out of it.
296 freeaddrinfo(result);
297
298 //Store the IP address in readable form
299 if (networkToPresentation(AF_INET, &statsd->destination.sin_addr, statsd->ipAddress, sizeof(statsd->ipAddress)) == NULL){
300 return STATSD_NTOP;
301 }
302
303 // Check to see if there is already an open socket, and close it
304 if (statsd->socketFd > 0){
305 close(statsd->socketFd);
306 statsd->socketFd = -1;
307 }
308
309 //Open up the socket file descriptor
310 statsd->socketFd = socket(AF_INET, SOCK_DGRAM, 0);
311 if (statsd->socketFd == -1){
312 return STATSD_SOCKET;
313 }
314
315 return STATSD_SUCCESS;
316 }
317
318 /**
319 Increment the bucket value by 1
320
321 @param[in] stats - The statsd client object
322 @param[in] bucket - The optional bucket name. If this is not
323 provided, the default bucket will be used from the statsd
324 object.
325
326 @return STATSD_SUCCESS on success, an error if there is a problem.
327 @see sendToServer
328 */
statsd_increment(Statsd * stats,const char * bucket)329 int ADDCALL statsd_increment(Statsd* stats, const char* bucket){
330 return sendToServer(stats, bucket, STATSD_COUNT, 1, 1);
331 }
332
333 /**
334 Decrement the bucket value by 1
335
336 @param[in] stats - The statsd client object
337 @param[in] bucket - The optional bucket name. If this is not
338 provided, the defualt bucket will be used from the statsd
339 object.
340
341 @return STATSD_SUCCESS on success, an error if there is a problem.
342 @see sendToServer
343 */
statsd_decrement(Statsd * stats,const char * bucket)344 int ADDCALL statsd_decrement(Statsd* stats, const char* bucket){
345 return sendToServer(stats, bucket, STATSD_COUNT, -1, 1);
346 }
347
348 /**
349 Add a count value to the bucket
350
351 @param[in] stats - The statsd client object.
352 @param[in] bucket - The optional bucket name. If this is
353 not provided, the default bucket will be used from the
354 statsd object.
355 @param[in] count - The value to increment (or decrement) the bucket
356 by. If the value is negative, it will be decremented.
357 @param[in] sampleRate - The sample rate of this statistic. If you specify
358 a value 0 or less, or 1 or more then this value is ignored. Otherwise
359 this value is sent on to the server.
360
361 @return STATSD_SUCCESS on success, an error if there is a problem.
362 @see sendToServer
363 */
statsd_count(Statsd * stats,const char * bucket,int64_t count,double sampleRate)364 int ADDCALL statsd_count(Statsd* stats, const char* bucket, int64_t count, double sampleRate){
365 return sendToServer(stats, bucket, STATSD_COUNT, count, sampleRate);
366 }
367
368 /**
369 Sets the value of a bucket to an arbitrary value
370
371 @param[in] stats - The statsd client object.
372 @param[in] bucket - The optional bucket name. If this is
373 not provided, the default bucket will be used from the
374 statsd object.
375 @param[in] value - The value to set the bucket to.
376 @param[in] sampleRate - The sample rate of this statistic. If you specify
377 a value 0 or less, or 1 or more then this value is ignored. Otherwise
378 this value is sent on to the server.
379
380 @return STATSD_SUCCESS on success, an error if there is a problem.
381 @see sendToServer
382 */
statsd_gauge(Statsd * stats,const char * bucket,int64_t value,double sampleRate)383 int ADDCALL statsd_gauge(Statsd* stats, const char* bucket, int64_t value, double sampleRate){
384 return sendToServer(stats, bucket, STATSD_GAUGE, value, sampleRate);
385 }
386
387 /**
388 Counts unique occurrences of events between flushes.
389
390 @param[in] stats - The statsd client object.
391 @param[in] bucket - The optional bucket name. If this is
392 not provided, the default bucket will be used from the
393 statsd object.
394 @param[in] value - The value to set the bucket to.
395 @param[in] sampleRate - The sample rate of this statistic. If you specify
396 a value 0 or less, or 1 or more then this value is ignored. Otherwise
397 this value is sent on to the server.
398
399 @return STATSD_SUCCESS on success, an error if there is a problem.
400 @see sendToServer
401 */
statsd_set(Statsd * stats,const char * bucket,int64_t value,double sampleRate)402 int ADDCALL statsd_set(Statsd* stats, const char* bucket, int64_t value, double sampleRate){
403 return sendToServer(stats, bucket, STATSD_SET, value, sampleRate);
404 }
405
406 /**
407 Records the time it took something to take in milliseconds.
408
409 @param[in] stats - The statsd client object.
410 @param[in] bucket - The optional bucket name. If this is
411 not provided, the default bucket will be used from the
412 statsd object.
413 @param[in] value - The value to set the bucket to.
414 @param[in] sampleRate - The sample rate of this statistic. If you specify
415 a value 0 or less, or 1 or more then this value is ignored. Otherwise
416 this value is sent on to the server. NOTE: The actual sampling rate
417 must be done externally, as each statistic will be sent on
418 regardless of the sampleRate value.
419
420 @return STATSD_SUCCESS on success, an error if there is a problem.
421 @see sendToServer
422 */
statsd_timing(Statsd * stats,const char * bucket,int timing,double sampleRate)423 int ADDCALL statsd_timing(Statsd* stats, const char* bucket, int timing, double sampleRate){
424 return sendToServer(stats, bucket, STATSD_TIMING, timing, sampleRate);
425 }
426
427 /**
428 This function will reset the batch data that is being
429 held. This is called automatically whenever you send
430 the batch data to the server. You can also call this
431 manually to removed any stored batch data.
432
433 @param[in] statsd - The statsd client object
434 @return STATSD_SUCCESS
435 */
statsd_resetBatch(Statsd * statsd)436 int ADDCALL statsd_resetBatch(Statsd* statsd){
437 statsd->batch[0] = '\0';
438 statsd->batchIndex = 0;
439 return STATSD_SUCCESS;
440 }
441
442 /**
443 Add stats to the batch buffer to be sent later.
444
445 @param[in] statsd - The statsd client object
446 @param[in] type - The type of stat being added
447 @param[in] bucket - The name of a bucket to put the stat. This
448 is optional and if not provided the default bucket name from
449 the statsd object will be used.
450 @param[in] value - The value of the stat
451 @param[in] sampleRate - The rate at which the stat was gathered.
452
453 @return STATSD_SUCCESS if everything was successful.
454 */
statsd_addToBatch(Statsd * statsd,StatsType type,const char * bucket,int64_t value,double sampleRate)455 int ADDCALL statsd_addToBatch(Statsd* statsd, StatsType type, const char* bucket, int64_t value, double sampleRate){
456 //See if we randomly fall under the sample rate
457 if (sampleRate > 0 && sampleRate < 1 && (double)((double)statsd->random() / RAND_MAX) >= sampleRate){
458 return STATSD_SUCCESS;
459 }
460
461 if (!bucket) {
462 bucket = statsd->bucket;
463 }
464
465 ssize_t strLength = buildStatString(statsd->batch + statsd->batchIndex,
466 sizeof(statsd->batch) - statsd->batchIndex,
467 statsd->nameSpace, bucket, type, value, sampleRate);
468 if (strLength < 0) {
469 return STATSD_BATCH_FULL;
470 }
471
472 statsd->batchIndex += strLength;
473
474 return STATSD_SUCCESS;
475 }
476
statsd_addToBatch_dbl(Statsd * statsd,StatsType type,const char * bucket,double value,double sampleRate)477 int ADDCALL statsd_addToBatch_dbl(Statsd* statsd, StatsType type, const char* bucket, double value, double sampleRate){
478 //See if we randomly fall under the sample rate
479 if (sampleRate > 0 && sampleRate < 1 && (double)((double)statsd->random() / RAND_MAX) >= sampleRate){
480 return STATSD_SUCCESS;
481 }
482
483 if (!bucket) {
484 bucket = statsd->bucket;
485 }
486
487 ssize_t strLength = buildStatString_dbl(statsd->batch + statsd->batchIndex,
488 sizeof(statsd->batch) - statsd->batchIndex,
489 statsd->nameSpace, bucket, type, value, sampleRate);
490 if (strLength < 0) {
491 return STATSD_BATCH_FULL;
492 }
493
494 statsd->batchIndex += strLength;
495
496 return STATSD_SUCCESS;
497 }
498
499 /**
500 Send the batch message to the server. After a successful send
501 this will reset the batch buffer.
502
503 @param[in] statsd - The statsd client object
504 @return STATSD_SUCCESS on success, STATSD_UDP_SEND if the
505 sendto() function failed.
506 */
statsd_sendBatch(Statsd * statsd)507 int ADDCALL statsd_sendBatch(Statsd* statsd){
508 if (statsd->batchIndex <= 0){
509 return STATSD_NO_BATCH;
510 }
511
512 int sent = sendto(statsd->socketFd, statsd->batch, statsd->batchIndex, 0, (const struct sockaddr*)&statsd->destination, sizeof(struct sockaddr_in));
513
514 if (sent == -1){
515 return STATSD_UDP_SEND;
516 }
517
518 statsd_resetBatch(statsd);
519 return STATSD_SUCCESS;
520 }
521
522