Skip to content

Commit 84e808d

Browse files
raing3Oliboy50
andauthored
Topic/dogstatsd support (#30)
* Added DogStatsD support. Co-authored-by: Oliver THEBAULT <[email protected]>
1 parent 8c8dfe0 commit 84e808d

File tree

10 files changed

+269
-26
lines changed

10 files changed

+269
-26
lines changed

doc/usage.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
array(
66
'serv1' => array('address' => 'udp://200.22.143.12'),
77
'serv2' => array('port' => 8125, 'address' => 'udp://200.22.143.12')
8-
)
8+
),
9+
new \M6Web\Component\Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter()
910
);
1011

1112
$client->increment('a.graphite.node');

src/M6Web/Component/Statsd/Client.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
namespace M6Web\Component\Statsd;
77

8+
use M6Web\Component\Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter;
9+
use M6Web\Component\Statsd\MessageFormatter\MessageFormatterInterface;
10+
811
/**
912
* client Statsd
1013
*/
@@ -38,15 +41,19 @@ class Client
3841
*/
3942
private $serverKeys = [];
4043

44+
/** @var MessageFormatterInterface */
45+
private $messageFormatter;
46+
4147
/**
4248
* contructeur
4349
*
4450
* @param array $servers les serveurs
4551
*/
46-
public function __construct(array $servers)
52+
public function __construct(array $servers, MessageFormatterInterface $messageFormatter = null)
4753
{
4854
$this->init($servers);
4955
$this->initQueue();
56+
$this->messageFormatter = $messageFormatter ?: new InfluxDBStatsDMessageFormatter();
5057
}
5158

5259
/**
@@ -170,7 +177,9 @@ protected function buildSampledData()
170177

171178
foreach ($this->getToSend() as $metric) {
172179
$server = $metric['server'];
173-
$sampledData[$server][] = $metric['message']->getStatsdMessage();
180+
/** @var MessageEntity $message */
181+
$message = $metric['message'];
182+
$sampledData[$server][] = $this->messageFormatter->format($message);
174183
}
175184

176185
return $sampledData;

src/M6Web/Component/Statsd/MessageEntity.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ protected function checkConstructor()
7676
*
7777
* @return bool
7878
*/
79-
protected function useSampleRate()
79+
public function useSampleRate()
8080
{
8181
if (($this->getSampleRate() < 1) && (mt_rand() / mt_getrandmax()) <= $this->getSampleRate()) {
8282
return true;
@@ -117,6 +117,14 @@ public function getUnit()
117117
return $this->unit;
118118
}
119119

120+
/**
121+
* @return array
122+
*/
123+
public function getTags()
124+
{
125+
return $this->tags;
126+
}
127+
120128
/**
121129
* @return string Tags formatted for sending
122130
* ex: "server=5,country=fr"
@@ -147,9 +155,22 @@ private function getFullNode()
147155
* format a statsd message
148156
*
149157
* @return string
158+
*
159+
* @deprecated
150160
*/
151161
public function getStatsdMessage()
152162
{
163+
trigger_error(
164+
sprintf(
165+
'%s is deprecated and will be removed in the next major version. '.
166+
'Update your code to use %s::%s.',
167+
__METHOD__,
168+
'M6Web\Component\Statsd\MessageFormatter\MessageFormatterInterface',
169+
'format'
170+
),
171+
E_USER_DEPRECATED
172+
);
173+
153174
$message = sprintf('%s:%s|%s', $this->getFullNode(), $this->getValue(), $this->getUnit());
154175
if ($this->useSampleRate()) {
155176
$message .= sprintf('|@%s', $this->getSampleRate());
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace M6Web\Component\Statsd\MessageFormatter;
4+
5+
use M6Web\Component\Statsd\MessageEntity;
6+
7+
/**
8+
* Formats a StatsD message using the DogStatsD style:
9+
* https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics#tagging
10+
*/
11+
class DogStatsDMessageFormatter implements MessageFormatterInterface
12+
{
13+
/**
14+
* {@inheritdoc}
15+
*/
16+
public function format(MessageEntity $message)
17+
{
18+
$formatted = sprintf('%s:%s|%s', $message->getNode(), $message->getValue(), $message->getUnit());
19+
20+
if ($message->useSampleRate()) {
21+
$formatted .= sprintf('|@%s', $message->getSampleRate());
22+
}
23+
24+
if ($message->getTags()) {
25+
$formatted .= '|#'.$this->getTagsAsString($message);
26+
}
27+
28+
return $formatted."\n";
29+
}
30+
31+
/**
32+
* @return string
33+
*/
34+
private function getTagsAsString(MessageEntity $message)
35+
{
36+
$tags = array_map(static function ($k, $v) {
37+
return $k.':'.$v;
38+
}, array_keys($message->getTags()), $message->getTags());
39+
40+
return implode(',', $tags);
41+
}
42+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace M6Web\Component\Statsd\MessageFormatter;
4+
5+
use M6Web\Component\Statsd\MessageEntity;
6+
7+
/**
8+
* Formats a StatsD message using the InfluxDB StatsD style:
9+
* https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/#introducing-influx-statsd
10+
*/
11+
class InfluxDBStatsDMessageFormatter implements MessageFormatterInterface
12+
{
13+
/**
14+
* {@inheritdoc}
15+
*/
16+
public function format(MessageEntity $message)
17+
{
18+
$node = $message->getNode();
19+
20+
if ($message->getTags()) {
21+
$node .= ','.$this->getTagsAsString($message);
22+
}
23+
24+
$formatted = sprintf('%s:%s|%s', $node, $message->getValue(), $message->getUnit());
25+
26+
if ($message->useSampleRate()) {
27+
$formatted .= sprintf('|@%s', $message->getSampleRate());
28+
}
29+
30+
return $formatted;
31+
}
32+
33+
/**
34+
* @return string
35+
*/
36+
private function getTagsAsString(MessageEntity $message)
37+
{
38+
$tags = array_map(static function ($k, $v) {
39+
return $k.'='.$v;
40+
}, array_keys($message->getTags()), $message->getTags());
41+
42+
return implode(',', $tags);
43+
}
44+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace M6Web\Component\Statsd\MessageFormatter;
4+
5+
use M6Web\Component\Statsd\MessageEntity;
6+
7+
/**
8+
* Interface for formatting StatsD messages for different StatsD server implementations.
9+
*/
10+
interface MessageFormatterInterface
11+
{
12+
/**
13+
* @return string
14+
*/
15+
public function format(MessageEntity $message);
16+
}

src/M6Web/Component/Tests/Units/Client.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,9 @@ public function testSend()
306306
->boolean($client->send())
307307
->isEqualTo(true)
308308
->mock($client)
309-
->call('writeDatas')->exactly(1);
309+
->call('writeDatas')
310+
->withArguments('serv2', ['service.foo:1|c'])->once()
311+
->withAnyArguments()->exactly(1);
310312
$client = new \mock\M6Web\Component\Statsd\Client($this->getConf());
311313
$client->getMockController()->writeDatas = function ($server, $datas) {
312314
return true;
@@ -315,7 +317,9 @@ public function testSend()
315317
->then()
316318
->boolean($client->send())
317319
->mock($client)
318-
->call('writeDatas')->exactly(1); // but one call
320+
->call('writeDatas')
321+
->withArguments('serv2', ['service.foo:1|c', 'service.foo:1|c'])->once()
322+
->withAnyArguments()->exactly(1); // but one call
319323
$client = new \mock\M6Web\Component\Statsd\Client($this->getConf());
320324
$client->getMockController()->writeDatas = function ($server, $datas) {
321325
return true;
@@ -324,12 +328,19 @@ public function testSend()
324328
->then()
325329
->boolean($client->send())
326330
->mock($client)
327-
->call('writeDatas')->exactly(2);
331+
->call('writeDatas')
332+
->withArguments('serv1', ['foo2:1|c'])->once()
333+
->withArguments('serv2', ['foo:1|c'])->once()
334+
->withAnyArguments()->exactly(2);
328335

329336
$this->if($client->count('foocount', 5))
330337
->then()
331338
->boolean($client->send())
332339
->mock($client)
333-
->call('writeDatas')->exactly(3);
340+
->call('writeDatas')
341+
->withArguments('serv1', ['foo2:1|c'])->once()
342+
->withArguments('serv2', ['foo:1|c'])->once()
343+
->withArguments('serv2', ['foocount:5|c'])->once()
344+
->withAnyArguments()->exactly(3);
334345
}
335346
}

src/M6Web/Component/Tests/Units/MessageEntity.php

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,55 @@ public function testGet()
3737
*/
3838
public function testgetStatsdMessage()
3939
{
40+
$expectedDeprecation =
41+
'M6Web\Component\Statsd\MessageEntity::getStatsdMessage is deprecated and will be '.
42+
'removed in the next major version. Update your code to use '.
43+
'M6Web\Component\Statsd\MessageFormatter\MessageFormatterInterface::format.';
44+
4045
// not sampled message
41-
$this->if($messageEntity = new Statsd\MessageEntity(
42-
'raoul.node', 1, 'c'))
43-
->then()
44-
->string($messageEntity->getStatsdMessage())
45-
->isEqualTo('raoul.node:1|c')
46-
;
46+
$this
47+
->when(function () {
48+
$this->if($messageEntity = new Statsd\MessageEntity(
49+
'raoul.node', 1, 'c'))
50+
->then()
51+
->string($messageEntity->getStatsdMessage())
52+
->isEqualTo('raoul.node:1|c')
53+
;
54+
})
55+
->error()
56+
->withMessage($expectedDeprecation)
57+
->exists();
4758

4859
// sampled message
4960
$this->function->mt_rand = function () { return 1; };
5061
$this->function->mt_getrandmax = function () { return 10; };
5162

52-
$this->if($messageEntity = new Statsd\MessageEntity(
53-
'raoul.node', 1, 'c', 0.2))
54-
->then()
55-
->string($messageEntity->getStatsdMessage())
56-
->isEqualTo('raoul.node:1|c|@0.2')
57-
;
63+
$this
64+
->when(function () {
65+
$this->if($messageEntity = new Statsd\MessageEntity(
66+
'raoul.node', 1, 'c', 0.2))
67+
->then()
68+
->string($messageEntity->getStatsdMessage())
69+
->isEqualTo('raoul.node:1|c|@0.2')
70+
;
71+
})
72+
->error()
73+
->withMessage($expectedDeprecation)
74+
->exists();
5875

5976
// with tags
60-
$this->if($messageEntity = new Statsd\MessageEntity(
61-
'raoul.node', 1, 'c', 0.2, ['foo' => 'bar']))
62-
->then()
63-
->string($messageEntity->getStatsdMessage())
64-
->isEqualTo('raoul.node,foo=bar:1|c|@0.2')
65-
;
77+
$this
78+
->when(function () {
79+
$this->if($messageEntity = new Statsd\MessageEntity(
80+
'raoul.node', 1, 'c', 0.2, ['foo' => 'bar']))
81+
->then()
82+
->string($messageEntity->getStatsdMessage())
83+
->isEqualTo('raoul.node,foo=bar:1|c|@0.2')
84+
;
85+
})
86+
->error()
87+
->withMessage($expectedDeprecation)
88+
->exists();
6689
}
6790

6891
public function testErrorConstructorStatsdMessage()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace M6Web\Component\Statsd\Tests\Units\MessageFormatter;
4+
5+
use M6Web\Component\Statsd;
6+
use mageekguy\atoum;
7+
8+
class DogStatsDMessageFormatter extends atoum\test
9+
{
10+
public function testFormat()
11+
{
12+
// not sampled message
13+
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c');
14+
$this
15+
->if($formatter = new Statsd\MessageFormatter\DogStatsDMessageFormatter())
16+
->then()
17+
->string($formatter->format($message))
18+
->isEqualTo("raoul.node:1|c\n");
19+
20+
// sampled message
21+
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2);
22+
$this->calling($message)->useSampleRate = true;
23+
$this
24+
->given($formatter = new Statsd\MessageFormatter\DogStatsDMessageFormatter())
25+
->then()
26+
->string($formatter->format($message))
27+
->isEqualTo("raoul.node:1|c|@0.2\n");
28+
29+
// with tags
30+
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2, ['foo' => 'bar']);
31+
$this->calling($message)->useSampleRate = true;
32+
$this
33+
->if($formatter = new Statsd\MessageFormatter\DogStatsDMessageFormatter())
34+
->then()
35+
->string($formatter->format($message))
36+
->isEqualTo("raoul.node:1|c|@0.2|#foo:bar\n");
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace M6Web\Component\Statsd\Tests\Units\MessageFormatter;
4+
5+
use M6Web\Component\Statsd;
6+
use mageekguy\atoum;
7+
8+
class InfluxDBStatsDMessageFormatter extends atoum\test
9+
{
10+
public function testFormat()
11+
{
12+
// not sampled message
13+
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c');
14+
$this
15+
->if($formatter = new Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter())
16+
->then()
17+
->string($formatter->format($message))
18+
->isEqualTo('raoul.node:1|c');
19+
20+
// sampled message
21+
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2);
22+
$this->calling($message)->useSampleRate = true;
23+
$this
24+
->given($formatter = new Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter())
25+
->then()
26+
->string($formatter->format($message))
27+
->isEqualTo('raoul.node:1|c|@0.2');
28+
29+
// with tags
30+
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2, ['foo' => 'bar']);
31+
$this->calling($message)->useSampleRate = true;
32+
$this
33+
->if($formatter = new Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter())
34+
->then()
35+
->string($formatter->format($message))
36+
->isEqualTo('raoul.node,foo=bar:1|c|@0.2');
37+
}
38+
}

0 commit comments

Comments
 (0)