Completed
Pull Request — master (#6)
by
unknown
11:12
created

Client::histogram()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 3
Ratio 23.08 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 3
loc 13
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 4
crap 12
1
<?php
2
/**
3
 * This file is part of graze/dog-statsd
4
 *
5
 * Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license https://github.com/graze/dog-statsd/blob/master/LICENSE.md
11
 * @link    https://github.com/graze/dog-statsd
12
 */
13
14
namespace Graze\DogStatsD;
15
16
use Graze\DogStatsD\Exception\ConfigurationException;
17
use Graze\DogStatsD\Exception\ConnectionException;
18
19
/**
20
 * StatsD Client Class - Modified to support DataDogs statsd server
21
 */
22
class Client
23
{
24
    const STATUS_OK       = 0;
25
    const STATUS_WARNING  = 1;
26
    const STATUS_CRITICAL = 2;
27
    const STATUS_UNKNOWN  = 3;
28
29
    const PRIORITY_LOW    = 'low';
30
    const PRIORITY_NORMAL = 'normal';
31
32
    const ALERT_ERROR   = 'error';
33
    const ALERT_WARNING = 'warning';
34
    const ALERT_INFO    = 'info';
35
    const ALERT_SUCCESS = 'success';
36
37
    /**
38
     * Instance instances array
39
     *
40
     * @var array
41
     */
42
    protected static $instances = [];
43
44
    /**
45
     * Instance ID
46
     *
47
     * @var string
48
     */
49
    protected $instanceId;
50
51
    /**
52
     * Server Host
53
     *
54
     * @var string
55
     */
56
    protected $host = '127.0.0.1';
57
58
    /**
59
     * Server Port
60
     *
61
     * @var integer
62
     */
63
    protected $port = 8125;
64
65
    /**
66
     * Last message sent to the server
67
     *
68
     * @var string
69
     */
70
    protected $message = '';
71
72
    /**
73
     * Class namespace
74
     *
75
     * @var string
76
     */
77
    protected $namespace = '';
78
79
    /**
80
     * Timeout for creating the socket connection
81
     *
82
     * @var null|int
83
     */
84
    protected $timeout;
85
86
    /**
87
     * Whether or not an exception should be thrown on failed connections
88
     *
89
     * @var bool
90
     */
91
    protected $throwExceptions = true;
92
93
    /**
94
     * Metadata for the DataDog event message
95
     *
96
     * @var array - time - Assign a timestamp to the event.
97
     *            - hostname - Assign a hostname to the event
98
     *            - key - Assign an aggregation key to th event, to group it with some others
99
     *            - priority - Can be 'normal' or 'low'
100
     *            - source - Assign a source type to the event
101
     *            - alert - Can be 'error', 'warning', 'info' or 'success'
102
     */
103
    protected $eventMetaData = [
104
        'time'     => 'd',
105
        'hostname' => 'h',
106
        'key'      => 'k',
107
        'priority' => 'p',
108
        'source'   => 's',
109
        'alert'    => 't',
110
    ];
111
112
    /**
113
     * @var array - time - Assign a timestamp to the service check
114
     *            - hostname - Assign a hostname to the service check
115
     */
116
    protected $serviceCheckMetaData = [
117
        'time'     => 'd',
118
        'hostname' => 'h',
119
    ];
120
121
    /**
122
     * @var array - message - Assign a message to the service check
123
     */
124
    protected $serviceCheckMessage = [
125
        'message' => 'm',
126
    ];
127
128
    /**
129
     * Is the server type DataDog implementation
130
     *
131
     * @var bool
132
     */
133
    protected $dataDog = true;
134
135
    /**
136
     * Set of default tags to send to every request
137
     *
138 1
     * @var array
139
     */
140 1
    protected $tags = [];
141 1
142 1
    /**
143 1
     * Singleton Reference
144
     *
145
     * @param  string $name Instance name
146
     *
147
     * @return Client Client instance
148
     */
149
    public static function instance($name = 'default')
150
    {
151 45
        if (!isset(static::$instances[$name])) {
152
            static::$instances[$name] = new static($name);
153 45
        }
154
        return static::$instances[$name];
155 45
    }
156 45
157 45
    /**
158 45
     * Create a new instance
159
     *
160
     * @param string|null $instanceId
161
     */
162
    public function __construct($instanceId = null)
163
    {
164
        $this->instanceId = $instanceId ?: uniqid();
165 2
166
        if (empty($this->timeout)) {
167 2
            $this->timeout = (int) ini_get('default_socket_timeout');
168
        }
169
    }
170
171
    /**
172
     * Get string value of instance
173
     *
174
     * @return string String representation of this instance
175
     */
176
    public function __toString()
177
    {
178
        return 'DogStatsD\Client::[' . $this->instanceId . ']';
179
    }
180
181
    /**
182
     * Initialize Connection Details
183
     *
184
     * @param array $options Configuration options
185 45
     *                       :host <string|ip> - host to talk to
186
     *                       :port <int> - Port to communicate with
187
     *                       :namespace <string> - Default namespace
188 45
     *                       :timeout <int> - Timeout in seconds
189 15
     *                       :throwExceptions <bool> - Throw an exception on connection error
190 15
     *                       :dataDog <bool> - Use DataDog's version of statsd (Default: true)
191 45
     *                       :tags <array> - List of tags to add to each message
192
     *
193 45
     * @return Client This instance
194 45
     * @throws ConfigurationException If port is invalid
195 45
     */
196 45
    public function configure(array $options = [])
197 45
    {
198 45
        $setOption = function ($name) use ($options) {
199 45
            if (isset($options[$name])) {
200
                $this->{$name} = $options[$name];
201 45
            }
202 2
        };
203
204
        $setOption('host');
205 45
        $setOption('port');
206
        $setOption('namespace');
207
        $setOption('timeout');
208
        $setOption('throwExceptions');
209
        $setOption('dataDog');
210
        $setOption('tags');
211
212
        if (!$this->port || !is_numeric($this->port) || $this->port > 65535) {
213 1
            throw new ConfigurationException($this, 'Port is out of range');
214
        }
215 1
216
        return $this;
217
    }
218
219
    /**
220
     * Get Host
221
     *
222
     * @return string Host
223 2
     */
224
    public function getHost()
225 2
    {
226
        return $this->host;
227
    }
228
229
    /**
230
     * Get Port
231
     *
232
     * @return int Port
233 1
     */
234
    public function getPort()
235 1
    {
236
        return $this->port;
237
    }
238
239
    /**
240
     * Get Namespace
241
     *
242
     * @return string Namespace
243 31
     */
244
    public function getNamespace()
245 31
    {
246
        return $this->namespace;
247
    }
248
249
    /**
250
     * Get Last Message
251
     *
252
     * @return string Last message sent to server
253
     */
254
    public function getLastMessage()
255
    {
256
        return $this->message;
257
    }
258 14
259
    /**
260 14
     * Increment a metric
261
     *
262 14
     * @param string|string[] $metrics    Metric(s) to increment
263 14
     * @param int             $delta      Value to decrement the metric by
264 1
     * @param float           $sampleRate Sample rate of metric
265 1
     * @param string[]        $tags       List of tags for this metric
266 1
     *
267 1
     * @return Client This instance
268 1
     */
269 1
    public function increment($metrics, $delta = 1, $sampleRate = 1.0, array $tags = [])
270 13
    {
271 13
        $metrics = is_array($metrics) ? $metrics : [$metrics];
272 13
273
        $data = [];
274 14
        if ($sampleRate < 1.0) {
275
            foreach ($metrics as $metric) {
276 View Code Duplication
                if ((mt_rand() / mt_getrandmax()) <= $sampleRate) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
277
                    $data[$metric] = $delta . '|c|@' . $sampleRate;
278
                }
279
            }
280
        } else {
281
            foreach ($metrics as $metric) {
282
                $data[$metric] = $delta . '|c';
283
            }
284
        }
285
        return $this->send($data, $tags);
286
    }
287 2
288
    /**
289 2
     * Decrement a metric
290
     *
291
     * @param string|string[] $metrics    Metric(s) to decrement
292
     * @param int             $delta      Value to increment the metric by
293
     * @param int             $sampleRate Sample rate of metric
294
     * @param string[]        $tags       List of tags for this metric
295
     *
296
     * @return Client This instance
297
     */
298
    public function decrement($metrics, $delta = 1, $sampleRate = 1, array $tags = [])
299
    {
300
        return $this->increment($metrics, 0 - $delta, $sampleRate, $tags);
301 3
    }
302
303 3
    /**
304
     * Timing
305 3
     *
306 3
     * @param string   $metric Metric to track
307
     * @param float    $time   Time in milliseconds
308 3
     * @param string[] $tags   List of tags for this metric
309
     *
310
     * @return Client This instance
311
     */
312
    public function timing($metric, $time, array $tags = [])
313
    {
314
        return $this->send(
315
            [
316
                $metric => $time . '|ms',
317
            ],
318
            $tags
319
        );
320 1
    }
321
322 1
    /**
323 1
     * Time a function
324 1
     *
325 1
     * @param string   $metric Metric to time
326 1
     * @param callable $func   Function to record
327
     * @param string[] $tags   List of tags for this metric
328
     *
329
     * @return Client This instance
330
     */
331
    public function time($metric, callable $func, array $tags = [])
332
    {
333
        $timerStart = microtime(true);
334
        $func();
335
        $timerEnd = microtime(true);
336
        $time = round(($timerEnd - $timerStart) * 1000, 4);
337
        return $this->timing($metric, $time, $tags);
338
    }
339 2
340
    /**
341 2
     * Gauges
342
     *
343 2
     * @param string   $metric Metric to gauge
344 2
     * @param int      $value  Set the value of the gauge
345
     * @param string[] $tags   List of tags for this metric
346 2
     *
347
     * @return Client This instance
348
     */
349
    public function gauge($metric, $value, array $tags = [])
350
    {
351
        return $this->send(
352
            [
353
                $metric => $value . '|g',
354
            ],
355
            $tags
356
        );
357
    }
358 2
359
    /**
360 2
     * Histogram
361
     *
362 2
     * @param string   $metric     Metric to send
363 2
     * @param float    $value      Value to send
364
     * @param float    $sampleRate Sample rate of metric
365 2
     * @param string[] $tags       List of tags for this metric
366
     *
367
     * @return Client This instance
368
     */
369
    public function histogram($metric, $value, $sampleRate = 1.0, array $tags = [])
370
    {
371
        $data = [];
372
        if ($sampleRate < 1.0) {
373 View Code Duplication
            if ((mt_rand() / mt_getrandmax()) <= $sampleRate) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374
                $data[$metric] = $value . '|h|@' . $sampleRate;
375
            }
376
        } else {
377
            $data[$metric] = $value . '|h';
378
        }
379
380
        return $this->send($data, $tags);
381
    }
382
383
    /**
384
     * Sets - count the number of unique elements for a group
385
     *
386
     * @param string   $metric
387 6
     * @param int      $value
388
     * @param string[] $tags List of tags for this metric
389 6
     *
390 1
     * @return Client This instance
391
     */
392
    public function set($metric, $value, array $tags = [])
393 5
    {
394 5
        return $this->send(
395 5
            [
396 5
                $metric => $value . '|s',
397
            ],
398 5
            $tags
399 2
        );
400 2
    }
401 2
402 5
    /**
403
     * Send a event notification
404 5
     *
405
     * @link http://docs.datadoghq.com/guides/dogstatsd/#events
406 5
     *
407 5
     * @param string   $title     Event Title
408 5
     * @param string   $text      Event Text
409
     * @param array    $metadata  Set of metadata for this event:
410
     *                            - time - Assign a timestamp to the event.
411
     *                            - hostname - Assign a hostname to the event
412
     *                            - key - Assign an aggregation key to th event, to group it with some others
413
     *                            - priority - Can be 'normal' or 'low'
414
     *                            - source - Assign a source type to the event
415
     *                            - alert - Can be 'error', 'warning', 'info' or 'success'
416
     * @param string[] $tags      List of tags for this event
417
     *
418
     * @return Client This instance
419
     * @throws ConnectionException If there is a connection problem with the host
420
     */
421
    public function event($title, $text, array $metadata = [], array $tags = [])
422
    {
423
        if (!$this->dataDog) {
424
            return $this;
425
        }
426 6
427
        $text = str_replace(["\r", "\n"], ['', "\\n"], $text);
428 6
        $metric = sprintf('_e{%d,%d}', strlen($title), strlen($text));
429 1
        $prefix = $this->namespace ? $this->namespace . '.' : '';
430
        $value = sprintf('%s|%s', $prefix . $title, $text);
431
432 5
        foreach ($metadata as $key => $data) {
433 5
            if (isset($this->eventMetaData[$key])) {
434
                $value .= sprintf('|%s:%s', $this->eventMetaData[$key], $data);
435 5
            }
436 5
        }
437 3
438 3
        $value .= $this->formatTags($tags);
439 3
440 5
        return $this->sendMessages([
441 5
            sprintf('%s:%s', $metric, $value),
442
        ]);
443 5
    }
444 5
445 5
    /**
446
     * Service Checks
447 5
     *
448 5
     * @link http://docs.datadoghq.com/guides/dogstatsd/#service-checks
449 5
     *
450
     * @param string   $name     Name of the service
451
     * @param int      $status   digit corresponding to the status you’re reporting (OK = 0, WARNING = 1, CRITICAL = 2,
452
     *                           UNKNOWN = 3)
453
     * @param array    $metadata - time - Assign a timestamp to the service check
454
     *                           - hostname - Assign a hostname to the service check
455
     * @param string[] $tags     List of tags for this event
456
     *
457 31
     * @return Client This instance
458
     * @throws ConnectionException If there is a connection problem with the host
459 31
     */
460 20
    public function serviceCheck($name, $status, array $metadata = [], array $tags = [])
461
    {
462
        if (!$this->dataDog) {
463 11
            return $this;
464 11
        }
465 11
466 10
        $prefix = $this->namespace ? $this->namespace . '.' : '';
467 10
        $value = sprintf('_sc|%s|%d', $prefix . $name, $status);
468 2
469
        $applyMetadata = function ($metadata, $definition) use (&$value) {
470 11
            foreach ($metadata as $key => $data) {
471
                if (isset($definition[$key])) {
472 11
                    $value .= sprintf('|%s:%s', $definition[$key], $data);
473
                }
474
            }
475
        };
476
477
        $applyMetadata($metadata, $this->serviceCheckMetaData);
478
        $value .= $this->formatTags($tags);
479
        $applyMetadata($metadata, $this->serviceCheckMessage);
480
481
        return $this->sendMessages([
482
            $value,
483
        ]);
484 21
    }
485
486 21
    /**
487 21
     * @param string[] $tags A list of tags to apply to each message
488 21
     *
489 21
     * @return string
490 21
     */
491 21
    private function formatTags(array $tags = [])
492 21
    {
493
        if (!$this->dataDog || count($tags) === 0) {
494
            return '';
495
        }
496
497
        $result = [];
498
        foreach ($tags as $key => $value) {
499
            if (is_numeric($key)) {
500
                $result[] = $value;
501 31
            } else {
502
                $result[] = sprintf('%s:%s', $key, $value);
503 31
            }
504 31
        }
505 2
506 1
        return sprintf('|#%s', implode(',', $result));
507
    }
508 1
509 1
    /**
510
     * Send Data to StatsD Server
511 1
     *
512 1
     * @param string[] $data A list of messages to send to the server
513
     * @param string[] $tags A list of tags to apply to each message
514
     *
515 29
     * @return Client This instance
516 29
     * @throws ConnectionException If there is a connection problem with the host
517 29
     */
518 29
    protected function send(array $data, array $tags = [])
519
    {
520
        $messages = [];
521
        $prefix = $this->namespace ? $this->namespace . '.' : '';
522
        $formattedTags = $this->formatTags(array_merge($this->tags, $tags));
523
        foreach ($data as $key => $value) {
524
            $messages[] = $prefix . $key . ':' . $value . $formattedTags;
525
        }
526
        return $this->sendMessages($messages);
527
    }
528
529
    /**
530
     * @param string[] $messages
531
     *
532
     * @return Client This instance
533
     * @throws ConnectionException If there is a connection problem with the host
534
     */
535
    protected function sendMessages(array $messages)
536
    {
537
        $socket = @fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, $this->timeout);
538
        if (!$socket) {
539
            if ($this->throwExceptions) {
540
                throw new ConnectionException($this, '(' . $errno . ') ' . $errstr);
541
            } else {
542
                trigger_error(
543
                    sprintf('StatsD server connection failed (udp://%s:%d)', $this->host, $this->port),
544
                    E_USER_WARNING
545
                );
546
                return $this;
547
            }
548
        }
549
        $this->message = implode("\n", $messages);
550
        @fwrite($socket, $this->message);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
551
        fclose($socket);
552
        return $this;
553
    }
554
}
555