Passed
Pull Request — master (#28)
by
unknown
02:39
created

Client::getPort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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