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