Passed
Push — master ( c477f2...f001f5 )
by Harry
02:22
created

Client::configure()   C

Complexity

Conditions 12
Paths 7

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 12

Importance

Changes 0
Metric Value
cc 12
nc 7
nop 1
dl 0
loc 49
ccs 32
cts 32
cp 1
crap 12
rs 6.9666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use Graze\DogStatsD\Stream\StreamWriter;
19
use Graze\DogStatsD\Stream\WriterInterface;
20
21
/**
22
 * StatsD Client Class - Modified to support DataDogs statsd server
23
 */
24
class Client
25
{
26
    const STATUS_OK       = 0;
27
    const STATUS_WARNING  = 1;
28
    const STATUS_CRITICAL = 2;
29
    const STATUS_UNKNOWN  = 3;
30
31
    const PRIORITY_LOW    = 'low';
32
    const PRIORITY_NORMAL = 'normal';
33
34
    const ALERT_ERROR   = 'error';
35
    const ALERT_WARNING = 'warning';
36
    const ALERT_INFO    = 'info';
37
    const ALERT_SUCCESS = 'success';
38
39
    /**
40
     * Instance instances array
41
     *
42
     * @var array
43
     */
44
    protected static $instances = [];
45
46
    /**
47
     * Instance ID
48
     *
49
     * @var string
50
     */
51
    protected $instanceId;
52
53
    /**
54
     * Server Host
55
     *
56
     * @var string
57
     */
58
    protected $host = '127.0.0.1';
59
60
    /**
61
     * Server Port
62
     *
63
     * @var integer
64
     */
65
    protected $port = 8125;
66
67
    /**
68
     * Last message sent to the server
69
     *
70
     * @var string
71
     */
72
    protected $message = '';
73
74
    /**
75
     * Was the last message sucessfully sent
76
     *
77
     * @var bool
78
     */
79
    protected $written;
80
81
    /**
82
     * Class namespace
83
     *
84
     * @var string
85
     */
86
    protected $namespace = '';
87
88
    /**
89
     * Timeout for creating the socket connection
90
     *
91
     * @var null|float
92
     */
93
    protected $timeout;
94
95
    /**
96
     * What we should do on connection failure
97
     *
98
     * Options:
99
     *  - error
100
     *  - exception
101
     *  - ignore
102
     *
103
     * @var string
104
     */
105
    protected $onError = 'exception';
106
107
    /**
108
     * Socket connection
109
     *
110
     * @var WriterInterface
111
     */
112
    protected $stream;
113
114
    /**
115
     * Metadata for the DataDog event message
116
     *
117
     * @var array - time - Assign a timestamp to the event.
118
     *            - hostname - Assign a hostname to the event
119
     *            - key - Assign an aggregation key to the event, to group it with some others
120
     *            - priority - Can be 'normal' or 'low'
121
     *            - source - Assign a source type to the event
122
     *            - alert - Can be 'error', 'warning', 'info' or 'success'
123
     */
124
    protected $eventMetaData = [
125
        'time'     => 'd',
126
        'hostname' => 'h',
127
        'key'      => 'k',
128
        'priority' => 'p',
129
        'source'   => 's',
130
        'alert'    => 't',
131
    ];
132
133
    /**
134
     * @var array - time - Assign a timestamp to the service check
135
     *            - hostname - Assign a hostname to the service check
136
     */
137
    protected $serviceCheckMetaData = [
138
        'time'     => 'd',
139
        'hostname' => 'h',
140
    ];
141
142
    /**
143
     * @var array - message - Assign a message to the service check
144
     */
145
    protected $serviceCheckMessage = [
146
        'message' => 'm',
147
    ];
148
149
    /**
150
     * Is the server type DataDog implementation
151
     *
152
     * @var bool
153
     */
154
    protected $dataDog = true;
155
156
    /**
157
     * Set of default tags to send to every request
158
     *
159
     * @var array
160
     */
161
    protected $tags = [];
162
163
    /**
164
     * List of tags processors to apply to every metric being sent out
165
     *
166
     * @var callable[]
167
     */
168
    protected $tagProcessors = [];
169
170
    /**
171
     * Singleton Reference
172
     *
173
     * @param  string $name Instance name
174
     *
175
     * @return Client Client instance
176
     */
177 3
    public static function instance($name = 'default')
178
    {
179 3
        if (!isset(static::$instances[$name])) {
180 3
            static::$instances[$name] = new static($name);
181
        }
182 3
        return static::$instances[$name];
183
    }
184
185
    /**
186
     * @param string $name Instance name
187
     *
188
     * @return bool true if an instance has been found and removed
189
     */
190 3
    public static function deleteInstance($name = 'default')
191
    {
192 3
        if (isset(static::$instances[$name])) {
193 2
            unset(static::$instances[$name]);
194 2
            return true;
195
        }
196 2
        return false;
197
    }
198
199
    /**
200
     * Create a new instance
201
     *
202
     * @param string|null $instanceId
203
     */
204 72
    public function __construct($instanceId = null)
205
    {
206 72
        $this->instanceId = $instanceId ?: uniqid();
207
208 72
        if (empty($this->timeout)) {
209 72
            $this->timeout = (float) ini_get('default_socket_timeout');
210
        }
211 72
    }
212
213
    /**
214
     * Get string value of instance
215
     *
216
     * @return string String representation of this instance
217
     */
218 2
    public function __toString()
219
    {
220 2
        return 'DogStatsD\Client::[' . $this->instanceId . ']';
221
    }
222
223
    /**
224
     * Initialize Connection Details
225
     *
226
     * @param array $options Configuration options
227
     *                       :host <string|ip> - host to talk to
228
     *                       :port <int> - Port to communicate with
229
     *                       :namespace <string> - Default namespace
230
     *                       :timeout <float> - Timeout in seconds
231
     *                       :onError <enum[error,exception,ignore]> - What we should do on error
232
     *                       :dataDog <bool> - Use DataDog's version of statsd (Default: true)
233
     *                       :tags <array> - List of tags to add to each message
234
     *                       :tagProcessors <array> - List of tags processors to use
235
     *
236
     * @return Client This instance
237
     * @throws ConfigurationException If port is invalid
238
     */
239 72
    public function configure(array $options = [])
240
    {
241
        $setOption = function ($name, $type = null) use ($options) {
242 72
            if (isset($options[$name])) {
243 23
                if (!is_null($type) && (gettype($options[$name]) != $type)) {
244 4
                    throw new ConfigurationException($this->instanceId, sprintf(
245 4
                        "Option: %s is expected to be: '%s', was: '%s'",
246 4
                        $name,
247 4
                        $type,
248 4
                        gettype($options[$name])
249
                    ));
250
                }
251 19
                $this->{$name} = $options[$name];
252
            }
253 72
        };
254
255 72
        $setOption('host', 'string');
256 72
        $setOption('port');
257 72
        $setOption('namespace', 'string');
258 72
        $setOption('timeout');
259 72
        $setOption('onError', 'string');
260 72
        $setOption('dataDog', 'boolean');
261 72
        $setOption('tags', 'array');
262
263 72
        if (isset($options['tagProcessors']) && is_array($options['tagProcessors'])) {
264 2
            foreach ($options['tagProcessors'] as $tagProcessor) {
265 2
                if (!is_callable($tagProcessor)) {
266 1
                    throw new ConfigurationException($this->instanceId, 'supplied tag processor is not a callable');
267
                }
268 1
                $this->addTagProcessor($tagProcessor);
269
            }
270
        }
271
272 72
        $this->port = (int) $this->port;
273 72
        if (!$this->port || !is_numeric($this->port) || $this->port > 65535) {
274 2
            throw new ConfigurationException($this->instanceId, 'Option: Port is invalid or is out of range');
275
        }
276
277 72
        if (!in_array(
278 72
            $this->onError,
279 72
            [StreamWriter::ON_ERROR_ERROR, StreamWriter::ON_ERROR_EXCEPTION, StreamWriter::ON_ERROR_IGNORE]
280
        )) {
281 1
            throw new ConfigurationException(
282 1
                $this->instanceId,
283 1
                sprintf("Option: onError '%s' is not one of: [error,exception,ignore]", $this->onError)
284
            );
285
        }
286
287 72
        return $this;
288
    }
289
290
    /**
291
     * @param callable $tagsProcessor
292
     *
293
     * @return Client
294
     */
295 3
    public function addTagProcessor(callable $tagsProcessor)
296
    {
297 3
        $this->tagProcessors[] = $tagsProcessor;
298 3
        return $this;
299
    }
300
301
    /**
302
     * Get Host
303
     *
304
     * @return string Host
305
     */
306 1
    public function getHost()
307
    {
308 1
        return $this->host;
309
    }
310
311
    /**
312
     * Get Port
313
     *
314
     * @return int Port
315
     */
316 3
    public function getPort()
317
    {
318 3
        return $this->port;
319
    }
320
321
    /**
322
     * Get Namespace
323
     *
324
     * @return string Namespace
325
     */
326 1
    public function getNamespace()
327
    {
328 1
        return $this->namespace;
329
    }
330
331
    /**
332
     * Get Last Message
333
     *
334
     * @return string Last message sent to server
335
     */
336 39
    public function getLastMessage()
337
    {
338 39
        return $this->message;
339
    }
340
341
    /**
342
     * Was the last write successful
343
     *
344
     * @return bool
345
     */
346 2
    public function wasSuccessful()
347
    {
348 2
        return $this->written;
349
    }
350
351
    /**
352
     * Increment a metric
353
     *
354
     * @param string|string[] $metrics    Metric(s) to increment
355
     * @param int             $delta      Value to decrement the metric by
356
     * @param float           $sampleRate Sample rate of metric
357
     * @param string[]        $tags       List of tags for this metric
358
     *
359
     * @return Client This instance
360
     */
361 20
    public function increment($metrics, $delta = 1, $sampleRate = 1.0, array $tags = [])
362
    {
363 20
        $metrics = is_array($metrics) ? $metrics : [$metrics];
364
365 20
        if ($this->isSampled($sampleRate, $postfix)) {
366 19
            $data = [];
367 19
            foreach ($metrics as $metric) {
368 19
                $data[$metric] = $delta . '|c' . $postfix;
369
            }
370 19
            return $this->send($data, $tags);
371
        }
372 2
        return $this;
373
    }
374
375
    /**
376
     * Decrement a metric
377
     *
378
     * @param string|string[] $metrics    Metric(s) to decrement
379
     * @param int             $delta      Value to increment the metric by
380
     * @param int             $sampleRate Sample rate of metric
381
     * @param string[]        $tags       List of tags for this metric
382
     *
383
     * @return Client This instance
384
     */
385 2
    public function decrement($metrics, $delta = 1, $sampleRate = 1, array $tags = [])
386
    {
387 2
        return $this->increment($metrics, 0 - $delta, $sampleRate, $tags);
388
    }
389
390
    /**
391
     * Timing
392
     *
393
     * @param string   $metric Metric to track
394
     * @param float    $time   Time in milliseconds
395
     * @param string[] $tags   List of tags for this metric
396
     *
397
     * @return Client This instance
398
     */
399 3
    public function timing($metric, $time, array $tags = [])
400
    {
401 3
        return $this->send(
402
            [
403 3
                $metric => $time . '|ms',
404
            ],
405 3
            $tags
406
        );
407
    }
408
409
    /**
410
     * Time a function
411
     *
412
     * @param string   $metric Metric to time
413
     * @param callable $func   Function to record
414
     * @param string[] $tags   List of tags for this metric
415
     *
416
     * @return Client This instance
417
     */
418 1
    public function time($metric, callable $func, array $tags = [])
419
    {
420 1
        $timerStart = microtime(true);
421 1
        $func();
422 1
        $timerEnd = microtime(true);
423 1
        $time = round(($timerEnd - $timerStart) * 1000, 4);
424 1
        return $this->timing($metric, $time, $tags);
425
    }
426
427
    /**
428
     * Gauges
429
     *
430
     * @param string   $metric Metric to gauge
431
     * @param int      $value  Set the value of the gauge
432
     * @param string[] $tags   List of tags for this metric
433
     *
434
     * @return Client This instance
435
     */
436 2
    public function gauge($metric, $value, array $tags = [])
437
    {
438 2
        return $this->send(
439
            [
440 2
                $metric => $value . '|g',
441
            ],
442 2
            $tags
443
        );
444
    }
445
446
    /**
447
     * Histogram
448
     *
449
     * @param string   $metric     Metric to send
450
     * @param float    $value      Value to send
451
     * @param float    $sampleRate Sample rate of metric
452
     * @param string[] $tags       List of tags for this metric
453
     *
454
     * @return Client This instance
455
     */
456 4
    public function histogram($metric, $value, $sampleRate = 1.0, array $tags = [])
457
    {
458 4
        if ($this->isSampled($sampleRate, $postfix)) {
459 3
            return $this->send(
460 3
                [$metric => $value . '|h' . $postfix],
461 3
                $tags
462
            );
463
        }
464 2
        return $this;
465
    }
466
467
    /**
468
     * Sets - count the number of unique elements for a group
469
     *
470
     * @param string   $metric
471
     * @param int      $value
472
     * @param string[] $tags List of tags for this metric
473
     *
474
     * @return Client This instance
475
     */
476 2
    public function set($metric, $value, array $tags = [])
477
    {
478 2
        return $this->send(
479
            [
480 2
                $metric => $value . '|s',
481
            ],
482 2
            $tags
483
        );
484
    }
485
486
    /**
487
     * Send a event notification
488
     *
489
     * @link http://docs.datadoghq.com/guides/dogstatsd/#events
490
     *
491
     * @param string   $title     Event Title
492
     * @param string   $text      Event Text
493
     * @param array    $metadata  Set of metadata for this event:
494
     *                            - time - Assign a timestamp to the event.
495
     *                            - hostname - Assign a hostname to the event
496
     *                            - key - Assign an aggregation key to th event, to group it with some others
497
     *                            - priority - Can be 'normal' or 'low'
498
     *                            - source - Assign a source type to the event
499
     *                            - alert - Can be 'error', 'warning', 'info' or 'success'
500
     * @param string[] $tags      List of tags for this event
501
     *
502
     * @return Client This instance
503
     * @throws ConnectionException If there is a connection problem with the host
504
     */
505 7
    public function event($title, $text, array $metadata = [], array $tags = [])
506
    {
507 7
        if (!$this->dataDog) {
508 1
            return $this;
509
        }
510
511 6
        $text = str_replace(["\r", "\n"], ['', "\\n"], $text);
512 6
        $metric = sprintf('_e{%d,%d}', strlen($title), strlen($text));
513 6
        $prefix = $this->namespace ? $this->namespace . '.' : '';
514 6
        $value = sprintf('%s|%s', $prefix . $title, $text);
515
516 6
        foreach ($metadata as $key => $data) {
517 2
            if (isset($this->eventMetaData[$key])) {
518 2
                $value .= sprintf('|%s:%s', $this->eventMetaData[$key], $data);
519
            }
520
        }
521
522 6
        $value .= $this->formatTags($tags);
523
524 6
        return $this->sendMessages([
525 6
            sprintf('%s:%s', $metric, $value),
526
        ]);
527
    }
528
529
    /**
530
     * Service Checks
531
     *
532
     * @link http://docs.datadoghq.com/guides/dogstatsd/#service-checks
533
     *
534
     * @param string   $name     Name of the service
535
     * @param int      $status   digit corresponding to the status you’re reporting (OK = 0, WARNING = 1, CRITICAL = 2,
536
     *                           UNKNOWN = 3)
537
     * @param array    $metadata - time - Assign a timestamp to the service check
538
     *                           - hostname - Assign a hostname to the service check
539
     * @param string[] $tags     List of tags for this event
540
     *
541
     * @return Client This instance
542
     * @throws ConnectionException If there is a connection problem with the host
543
     */
544 6
    public function serviceCheck($name, $status, array $metadata = [], array $tags = [])
545
    {
546 6
        if (!$this->dataDog) {
547 1
            return $this;
548
        }
549
550 5
        $prefix = $this->namespace ? $this->namespace . '.' : '';
551 5
        $value = sprintf('_sc|%s|%d', $prefix . $name, $status);
552
553 5
        $applyMetadata = function ($metadata, $definition) use (&$value) {
554 5
            foreach ($metadata as $key => $data) {
555 3
                if (isset($definition[$key])) {
556 3
                    $value .= sprintf('|%s:%s', $definition[$key], $data);
557
                }
558
            }
559 5
        };
560
561 5
        $applyMetadata($metadata, $this->serviceCheckMetaData);
562 5
        $value .= $this->formatTags($tags);
563 5
        $applyMetadata($metadata, $this->serviceCheckMessage);
564
565 5
        return $this->sendMessages([
566 5
            $value,
567
        ]);
568
    }
569
570
    /**
571
     * @param float  $rate
572
     * @param string $postfix
573
     *
574
     * @return bool
575
     */
576 24
    private function isSampled($rate = 1.0, &$postfix = '')
577
    {
578 24
        if ($rate == 1.0) {
579 20
            return true;
580
        }
581 4
        if ((mt_rand() / mt_getrandmax()) <= $rate) {
582 2
            $postfix = '|@' . $rate;
583 2
            return true;
584
        }
585 4
        return false;
586
    }
587
588
    /**
589
     * @param string[] $tags A list of tags to apply to each message
590
     *
591
     * @return string
592
     */
593 40
    private function formatTags(array $tags = [])
594
    {
595 40
        if (!$this->dataDog || count($tags) === 0) {
596 27
            return '';
597
        }
598
599 14
        $result = [];
600 14
        foreach ($tags as $key => $value) {
601 14
            if (is_numeric($key)) {
602 12
                $result[] = $value;
603
            } else {
604 14
                $result[] = sprintf('%s:%s', $key, $value);
605
            }
606
        }
607
608 14
        return sprintf('|#%s', implode(',', $result));
609
    }
610
611
    /**
612
     * Send Data to StatsD Server
613
     *
614
     * @param string[] $data A list of messages to send to the server
615
     * @param string[] $tags A list of tags to apply to each message
616
     *
617
     * @return Client This instance
618
     * @throws ConnectionException If there is a connection problem with the host
619
     */
620 29
    protected function send(array $data, array $tags = [])
621
    {
622 29
        $messages = [];
623 29
        $prefix = $this->namespace ? $this->namespace . '.' : '';
624 29
        $formattedTags = $this->formatTags($this->processTags(array_merge($this->tags, $tags)));
625 29
        foreach ($data as $key => $value) {
626 29
            $messages[] = $prefix . $key . ':' . $value . $formattedTags;
627
        }
628 29
        return $this->sendMessages($messages);
629
    }
630
631
    /**
632
     * @param string[] $messages
633
     *
634
     * @return Client This instance
635
     * @throws ConnectionException If there is a connection problem with the host
636
     */
637 40
    protected function sendMessages(array $messages)
638
    {
639 40
        if (is_null($this->stream)) {
640 40
            $this->stream = new StreamWriter(
641 40
                $this->instanceId,
642 40
                $this->host,
643 40
                $this->port,
644 40
                $this->onError,
645 40
                $this->timeout
646
            );
647
        }
648 40
        $this->message = implode("\n", $messages);
649 40
        $this->written = $this->stream->write($this->message);
650
651 39
        return $this;
652
    }
653
654
    /**
655
     * Process a set of tags with some user defined processes to add custom runtime data
656
     *
657
     * @param array $tags
658
     *
659
     * @return array|mixed
660
     */
661 29
    private function processTags(array $tags)
662
    {
663 29
        foreach ($this->tagProcessors as $tagProcessor) {
664 2
            $tags = call_user_func($tagProcessor, $tags);
665
        }
666 29
        return $tags;
667
    }
668
}
669