Completed
Pull Request — master (#26)
by Harry
03:49 queued 01:22
created

Client::configure()   D

Complexity

Conditions 10
Paths 6

Size

Total Lines 45
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 34
cts 34
cp 1
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 29
nc 6
nop 1
crap 10

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 1
    public static function instance($name = 'default')
178
    {
179 1
        if (!isset(static::$instances[$name])) {
180 1
            static::$instances[$name] = new static($name);
181 1
        }
182 1
        return static::$instances[$name];
183
    }
184
185
    /**
186
     * Create a new instance
187
     *
188
     * @param string|null $instanceId
189
     */
190 68
    public function __construct($instanceId = null)
191
    {
192 68
        $this->instanceId = $instanceId ?: uniqid();
193
194 68
        if (empty($this->timeout)) {
195 68
            $this->timeout = (float) ini_get('default_socket_timeout');
196 68
        }
197 68
    }
198
199
    /**
200
     * Get string value of instance
201
     *
202
     * @return string String representation of this instance
203
     */
204 2
    public function __toString()
205
    {
206 2
        return 'DogStatsD\Client::[' . $this->instanceId . ']';
207
    }
208
209
    /**
210
     * Initialize Connection Details
211
     *
212
     * @param array $options Configuration options
213
     *                       :host <string|ip> - host to talk to
214
     *                       :port <int> - Port to communicate with
215
     *                       :namespace <string> - Default namespace
216
     *                       :timeout <float> - Timeout in seconds
217
     *                       :onError <enum[error,exception,ignore]> - What we should do on error
218
     *                       :dataDog <bool> - Use DataDog's version of statsd (Default: true)
219
     *                       :tags <array> - List of tags to add to each message
220
     *                       :tagProcessors <array> - List of tags processors to use
221
     *
222
     * @return Client This instance
223
     * @throws ConfigurationException If port is invalid
224
     */
225 68
    public function configure(array $options = [])
226
    {
227
        $setOption = function ($name, $type = null) use ($options) {
228 68
            if (isset($options[$name])) {
229 23
                if (!is_null($type) && (gettype($options[$name]) != $type)) {
230 4
                    throw new ConfigurationException($this->instanceId, sprintf(
231 4
                        "Option: %s is expected to be: '%s', was: '%s'",
232 4
                        $name,
233 4
                        $type,
234 4
                        gettype($options[$name])
235 4
                    ));
236
                }
237 19
                $this->{$name} = $options[$name];
238 19
            }
239 68
        };
240
241 68
        $setOption('host', 'string');
242 68
        $setOption('port');
243 68
        $setOption('namespace', 'string');
244 68
        $setOption('timeout');
245 68
        $setOption('onError', 'string');
246 68
        $setOption('dataDog', 'boolean');
247 68
        $setOption('tags', 'array');
248
249 68
        if (isset($options['tagProcessors']) && is_array($options['tagProcessors'])) {
250 2
            array_map([$this, 'addTagProcessor'], $options['tagProcessors']);
251 1
        }
252
253 68
        $this->port = (int) $this->port;
254 68
        if (!$this->port || !is_numeric($this->port) || $this->port > 65535) {
255 2
            throw new ConfigurationException($this->instanceId, 'Option: Port is invalid or is out of range');
256
        }
257
258 68
        if (!in_array(
259 68
            $this->onError,
260 68
            [StreamWriter::ON_ERROR_ERROR, StreamWriter::ON_ERROR_EXCEPTION, StreamWriter::ON_ERROR_IGNORE]
261 68
        )) {
262 1
            throw new ConfigurationException(
263 1
                $this->instanceId,
264 1
                sprintf("Option: onError '%s' is not one of: [error,exception,ignore]", $this->onError)
265 1
            );
266
        }
267
268 68
        return $this;
269
    }
270
271
    /**
272
     * @param callable $tagsProcessor
273
     *
274
     * @return Client
275
     */
276 3
    public function addTagProcessor(callable $tagsProcessor)
277
    {
278 3
        $this->tagProcessors[] = $tagsProcessor;
279 3
        return $this;
280
    }
281
282
    /**
283
     * Get Host
284
     *
285
     * @return string Host
286
     */
287 1
    public function getHost()
288
    {
289 1
        return $this->host;
290
    }
291
292
    /**
293
     * Get Port
294
     *
295
     * @return int Port
296
     */
297 3
    public function getPort()
298
    {
299 3
        return $this->port;
300
    }
301
302
    /**
303
     * Get Namespace
304
     *
305
     * @return string Namespace
306
     */
307 1
    public function getNamespace()
308
    {
309 1
        return $this->namespace;
310
    }
311
312
    /**
313
     * Get Last Message
314
     *
315
     * @return string Last message sent to server
316
     */
317 39
    public function getLastMessage()
318
    {
319 39
        return $this->message;
320
    }
321
322
    /**
323
     * Was the last write successful
324
     *
325
     * @return bool
326
     */
327 2
    public function wasSuccessful()
328
    {
329 2
        return $this->written;
330
    }
331
332
    /**
333
     * Increment a metric
334
     *
335
     * @param string|string[] $metrics    Metric(s) to increment
336
     * @param int             $delta      Value to decrement the metric by
337
     * @param float           $sampleRate Sample rate of metric
338
     * @param string[]        $tags       List of tags for this metric
339
     *
340
     * @return Client This instance
341
     */
342 19
    public function increment($metrics, $delta = 1, $sampleRate = 1.0, array $tags = [])
343
    {
344 19
        $metrics = is_array($metrics) ? $metrics : [$metrics];
345
346 19
        if ($this->isSampled($sampleRate, $postfix)) {
347 18
            $data = [];
348 18
            foreach ($metrics as $metric) {
349 18
                $data[$metric] = $delta . '|c' . $postfix;
350 18
            }
351 18
            return $this->send($data, $tags);
352
        }
353 1
        return $this;
354
    }
355
356
    /**
357
     * Decrement a metric
358
     *
359
     * @param string|string[] $metrics    Metric(s) to decrement
360
     * @param int             $delta      Value to increment the metric by
361
     * @param int             $sampleRate Sample rate of metric
362
     * @param string[]        $tags       List of tags for this metric
363
     *
364
     * @return Client This instance
365
     */
366 2
    public function decrement($metrics, $delta = 1, $sampleRate = 1, array $tags = [])
367
    {
368 2
        return $this->increment($metrics, 0 - $delta, $sampleRate, $tags);
369
    }
370
371
    /**
372
     * Timing
373
     *
374
     * @param string   $metric Metric to track
375
     * @param float    $time   Time in milliseconds
376
     * @param string[] $tags   List of tags for this metric
377
     *
378
     * @return Client This instance
379
     */
380 3
    public function timing($metric, $time, array $tags = [])
381
    {
382 3
        return $this->send(
383
            [
384 3
                $metric => $time . '|ms',
385 3
            ],
386
            $tags
387 3
        );
388
    }
389
390
    /**
391
     * Time a function
392
     *
393
     * @param string   $metric Metric to time
394
     * @param callable $func   Function to record
395
     * @param string[] $tags   List of tags for this metric
396
     *
397
     * @return Client This instance
398
     */
399 1
    public function time($metric, callable $func, array $tags = [])
400
    {
401 1
        $timerStart = microtime(true);
402 1
        $func();
403 1
        $timerEnd = microtime(true);
404 1
        $time = round(($timerEnd - $timerStart) * 1000, 4);
405 1
        return $this->timing($metric, $time, $tags);
406
    }
407
408
    /**
409
     * Gauges
410
     *
411
     * @param string   $metric Metric to gauge
412
     * @param int      $value  Set the value of the gauge
413
     * @param string[] $tags   List of tags for this metric
414
     *
415
     * @return Client This instance
416
     */
417 2
    public function gauge($metric, $value, array $tags = [])
418
    {
419 2
        return $this->send(
420
            [
421 2
                $metric => $value . '|g',
422 2
            ],
423
            $tags
424 2
        );
425
    }
426
427
    /**
428
     * Histogram
429
     *
430
     * @param string   $metric     Metric to send
431
     * @param float    $value      Value to send
432
     * @param float    $sampleRate Sample rate of metric
433
     * @param string[] $tags       List of tags for this metric
434
     *
435
     * @return Client This instance
436
     */
437 4
    public function histogram($metric, $value, $sampleRate = 1.0, array $tags = [])
438
    {
439 4
        if ($this->isSampled($sampleRate, $postfix)) {
440 3
            return $this->send(
441 3
                [$metric => $value . '|h' . $postfix],
442
                $tags
443 3
            );
444
        }
445 1
        return $this;
446
    }
447
448
    /**
449
     * Sets - count the number of unique elements for a group
450
     *
451
     * @param string   $metric
452
     * @param int      $value
453
     * @param string[] $tags List of tags for this metric
454
     *
455
     * @return Client This instance
456
     */
457 2
    public function set($metric, $value, array $tags = [])
458
    {
459 2
        return $this->send(
460
            [
461 2
                $metric => $value . '|s',
462 2
            ],
463
            $tags
464 2
        );
465
    }
466
467
    /**
468
     * Send a event notification
469
     *
470
     * @link http://docs.datadoghq.com/guides/dogstatsd/#events
471
     *
472
     * @param string   $title     Event Title
473
     * @param string   $text      Event Text
474
     * @param array    $metadata  Set of metadata for this event:
475
     *                            - time - Assign a timestamp to the event.
476
     *                            - hostname - Assign a hostname to the event
477
     *                            - key - Assign an aggregation key to th event, to group it with some others
478
     *                            - priority - Can be 'normal' or 'low'
479
     *                            - source - Assign a source type to the event
480
     *                            - alert - Can be 'error', 'warning', 'info' or 'success'
481
     * @param string[] $tags      List of tags for this event
482
     *
483
     * @return Client This instance
484
     * @throws ConnectionException If there is a connection problem with the host
485
     */
486 7
    public function event($title, $text, array $metadata = [], array $tags = [])
487
    {
488 7
        if (!$this->dataDog) {
489 1
            return $this;
490
        }
491
492 6
        $text = str_replace(["\r", "\n"], ['', "\\n"], $text);
493 6
        $metric = sprintf('_e{%d,%d}', strlen($title), strlen($text));
494 6
        $prefix = $this->namespace ? $this->namespace . '.' : '';
495 6
        $value = sprintf('%s|%s', $prefix . $title, $text);
496
497 6
        foreach ($metadata as $key => $data) {
498 2
            if (isset($this->eventMetaData[$key])) {
499 2
                $value .= sprintf('|%s:%s', $this->eventMetaData[$key], $data);
500 2
            }
501 6
        }
502
503 6
        $value .= $this->formatTags($tags);
504
505 6
        return $this->sendMessages([
506 6
            sprintf('%s:%s', $metric, $value),
507 6
        ]);
508
    }
509
510
    /**
511
     * Service Checks
512
     *
513
     * @link http://docs.datadoghq.com/guides/dogstatsd/#service-checks
514
     *
515
     * @param string   $name     Name of the service
516
     * @param int      $status   digit corresponding to the status you’re reporting (OK = 0, WARNING = 1, CRITICAL = 2,
517
     *                           UNKNOWN = 3)
518
     * @param array    $metadata - time - Assign a timestamp to the service check
519
     *                           - hostname - Assign a hostname to the service check
520
     * @param string[] $tags     List of tags for this event
521
     *
522
     * @return Client This instance
523
     * @throws ConnectionException If there is a connection problem with the host
524
     */
525 6
    public function serviceCheck($name, $status, array $metadata = [], array $tags = [])
526
    {
527 6
        if (!$this->dataDog) {
528 1
            return $this;
529
        }
530
531 5
        $prefix = $this->namespace ? $this->namespace . '.' : '';
532 5
        $value = sprintf('_sc|%s|%d', $prefix . $name, $status);
533
534 5
        $applyMetadata = function ($metadata, $definition) use (&$value) {
535 5
            foreach ($metadata as $key => $data) {
536 3
                if (isset($definition[$key])) {
537 3
                    $value .= sprintf('|%s:%s', $definition[$key], $data);
538 3
                }
539 5
            }
540 5
        };
541
542 5
        $applyMetadata($metadata, $this->serviceCheckMetaData);
543 5
        $value .= $this->formatTags($tags);
544 5
        $applyMetadata($metadata, $this->serviceCheckMessage);
545
546 5
        return $this->sendMessages([
547 5
            $value,
548 5
        ]);
549
    }
550
551
    /**
552
     * @param float  $rate
553
     * @param string $postfix
554
     *
555
     * @return bool
556
     */
557 23
    private function isSampled($rate = 1.0, &$postfix = '')
558
    {
559 23
        if ($rate == 1.0) {
560 19
            return true;
561
        }
562 4
        if ((mt_rand() / mt_getrandmax()) <= $rate) {
563 2
            $postfix = '|@' . $rate;
564 2
            return true;
565
        }
566 2
        return false;
567
    }
568
569
    /**
570
     * @param string[] $tags A list of tags to apply to each message
571
     *
572
     * @return string
573
     */
574 39
    private function formatTags(array $tags = [])
575
    {
576 39
        if (!$this->dataDog || count($tags) === 0) {
577 26
            return '';
578
        }
579
580 14
        $result = [];
581 14
        foreach ($tags as $key => $value) {
582 14
            if (is_numeric($key)) {
583 12
                $result[] = $value;
584 12
            } else {
585 4
                $result[] = sprintf('%s:%s', $key, $value);
586
            }
587 14
        }
588
589 14
        return sprintf('|#%s', implode(',', $result));
590
    }
591
592
    /**
593
     * Send Data to StatsD Server
594
     *
595
     * @param string[] $data A list of messages to send to the server
596
     * @param string[] $tags A list of tags to apply to each message
597
     *
598
     * @return Client This instance
599
     * @throws ConnectionException If there is a connection problem with the host
600
     */
601 28
    protected function send(array $data, array $tags = [])
602
    {
603 28
        $messages = [];
604 28
        $prefix = $this->namespace ? $this->namespace . '.' : '';
605 28
        $formattedTags = $this->formatTags($this->processTags(array_merge($this->tags, $tags)));
606 28
        foreach ($data as $key => $value) {
607 28
            $messages[] = $prefix . $key . ':' . $value . $formattedTags;
608 28
        }
609 28
        return $this->sendMessages($messages);
610
    }
611
612
    /**
613
     * @param string[] $messages
614
     *
615
     * @return Client This instance
616
     * @throws ConnectionException If there is a connection problem with the host
617
     */
618 39
    protected function sendMessages(array $messages)
619
    {
620 39
        if (is_null($this->stream)) {
621 39
            $this->stream = new StreamWriter($this, $this->host, $this->port, $this->onError, $this->timeout);
622 39
        }
623 39
        $this->message = implode("\n", $messages);
624 39
        $this->written = $this->stream->write($this->message);
625
626 38
        return $this;
627
    }
628
629
    /**
630
     * Process a set of tags with some user defined processes to add custom runtime data
631
     *
632
     * @param array $tags
633
     *
634
     * @return array|mixed
635
     */
636 28
    private function processTags(array $tags)
637
    {
638 28
        foreach ($this->tagProcessors as $tagProcessor) {
639 2
            $tags = call_user_func($tagProcessor, $tags);
640 28
        }
641 28
        return $tags;
642
    }
643
}
644