Skip to content

Commit

Permalink
Topic/dogstatsd support (#30)
Browse files Browse the repository at this point in the history
* Added DogStatsD support.

Co-authored-by: Oliver THEBAULT <[email protected]>
  • Loading branch information
raing3 and Oliboy50 authored Mar 3, 2022
1 parent 8c8dfe0 commit 84e808d
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 26 deletions.
3 changes: 2 additions & 1 deletion doc/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
array(
'serv1' => array('address' => 'udp://200.22.143.12'),
'serv2' => array('port' => 8125, 'address' => 'udp://200.22.143.12')
)
),
new \M6Web\Component\Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter()
);

$client->increment('a.graphite.node');
Expand Down
13 changes: 11 additions & 2 deletions src/M6Web/Component/Statsd/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace M6Web\Component\Statsd;

use M6Web\Component\Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter;
use M6Web\Component\Statsd\MessageFormatter\MessageFormatterInterface;

/**
* client Statsd
*/
Expand Down Expand Up @@ -38,15 +41,19 @@ class Client
*/
private $serverKeys = [];

/** @var MessageFormatterInterface */
private $messageFormatter;

/**
* contructeur
*
* @param array $servers les serveurs
*/
public function __construct(array $servers)
public function __construct(array $servers, MessageFormatterInterface $messageFormatter = null)
{
$this->init($servers);
$this->initQueue();
$this->messageFormatter = $messageFormatter ?: new InfluxDBStatsDMessageFormatter();
}

/**
Expand Down Expand Up @@ -170,7 +177,9 @@ protected function buildSampledData()

foreach ($this->getToSend() as $metric) {
$server = $metric['server'];
$sampledData[$server][] = $metric['message']->getStatsdMessage();
/** @var MessageEntity $message */
$message = $metric['message'];
$sampledData[$server][] = $this->messageFormatter->format($message);
}

return $sampledData;
Expand Down
23 changes: 22 additions & 1 deletion src/M6Web/Component/Statsd/MessageEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected function checkConstructor()
*
* @return bool
*/
protected function useSampleRate()
public function useSampleRate()
{
if (($this->getSampleRate() < 1) && (mt_rand() / mt_getrandmax()) <= $this->getSampleRate()) {
return true;
Expand Down Expand Up @@ -117,6 +117,14 @@ public function getUnit()
return $this->unit;
}

/**
* @return array
*/
public function getTags()
{
return $this->tags;
}

/**
* @return string Tags formatted for sending
* ex: "server=5,country=fr"
Expand Down Expand Up @@ -147,9 +155,22 @@ private function getFullNode()
* format a statsd message
*
* @return string
*
* @deprecated
*/
public function getStatsdMessage()
{
trigger_error(
sprintf(
'%s is deprecated and will be removed in the next major version. '.
'Update your code to use %s::%s.',
__METHOD__,
'M6Web\Component\Statsd\MessageFormatter\MessageFormatterInterface',
'format'
),
E_USER_DEPRECATED
);

$message = sprintf('%s:%s|%s', $this->getFullNode(), $this->getValue(), $this->getUnit());
if ($this->useSampleRate()) {
$message .= sprintf('|@%s', $this->getSampleRate());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace M6Web\Component\Statsd\MessageFormatter;

use M6Web\Component\Statsd\MessageEntity;

/**
* Formats a StatsD message using the DogStatsD style:
* https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/?tab=metrics#tagging
*/
class DogStatsDMessageFormatter implements MessageFormatterInterface
{
/**
* {@inheritdoc}
*/
public function format(MessageEntity $message)
{
$formatted = sprintf('%s:%s|%s', $message->getNode(), $message->getValue(), $message->getUnit());

if ($message->useSampleRate()) {
$formatted .= sprintf('|@%s', $message->getSampleRate());
}

if ($message->getTags()) {
$formatted .= '|#'.$this->getTagsAsString($message);
}

return $formatted."\n";
}

/**
* @return string
*/
private function getTagsAsString(MessageEntity $message)
{
$tags = array_map(static function ($k, $v) {
return $k.':'.$v;
}, array_keys($message->getTags()), $message->getTags());

return implode(',', $tags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace M6Web\Component\Statsd\MessageFormatter;

use M6Web\Component\Statsd\MessageEntity;

/**
* Formats a StatsD message using the InfluxDB StatsD style:
* https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/#introducing-influx-statsd
*/
class InfluxDBStatsDMessageFormatter implements MessageFormatterInterface
{
/**
* {@inheritdoc}
*/
public function format(MessageEntity $message)
{
$node = $message->getNode();

if ($message->getTags()) {
$node .= ','.$this->getTagsAsString($message);
}

$formatted = sprintf('%s:%s|%s', $node, $message->getValue(), $message->getUnit());

if ($message->useSampleRate()) {
$formatted .= sprintf('|@%s', $message->getSampleRate());
}

return $formatted;
}

/**
* @return string
*/
private function getTagsAsString(MessageEntity $message)
{
$tags = array_map(static function ($k, $v) {
return $k.'='.$v;
}, array_keys($message->getTags()), $message->getTags());

return implode(',', $tags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace M6Web\Component\Statsd\MessageFormatter;

use M6Web\Component\Statsd\MessageEntity;

/**
* Interface for formatting StatsD messages for different StatsD server implementations.
*/
interface MessageFormatterInterface
{
/**
* @return string
*/
public function format(MessageEntity $message);
}
19 changes: 15 additions & 4 deletions src/M6Web/Component/Tests/Units/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ public function testSend()
->boolean($client->send())
->isEqualTo(true)
->mock($client)
->call('writeDatas')->exactly(1);
->call('writeDatas')
->withArguments('serv2', ['service.foo:1|c'])->once()
->withAnyArguments()->exactly(1);
$client = new \mock\M6Web\Component\Statsd\Client($this->getConf());
$client->getMockController()->writeDatas = function ($server, $datas) {
return true;
Expand All @@ -315,7 +317,9 @@ public function testSend()
->then()
->boolean($client->send())
->mock($client)
->call('writeDatas')->exactly(1); // but one call
->call('writeDatas')
->withArguments('serv2', ['service.foo:1|c', 'service.foo:1|c'])->once()
->withAnyArguments()->exactly(1); // but one call
$client = new \mock\M6Web\Component\Statsd\Client($this->getConf());
$client->getMockController()->writeDatas = function ($server, $datas) {
return true;
Expand All @@ -324,12 +328,19 @@ public function testSend()
->then()
->boolean($client->send())
->mock($client)
->call('writeDatas')->exactly(2);
->call('writeDatas')
->withArguments('serv1', ['foo2:1|c'])->once()
->withArguments('serv2', ['foo:1|c'])->once()
->withAnyArguments()->exactly(2);

$this->if($client->count('foocount', 5))
->then()
->boolean($client->send())
->mock($client)
->call('writeDatas')->exactly(3);
->call('writeDatas')
->withArguments('serv1', ['foo2:1|c'])->once()
->withArguments('serv2', ['foo:1|c'])->once()
->withArguments('serv2', ['foocount:5|c'])->once()
->withAnyArguments()->exactly(3);
}
}
59 changes: 41 additions & 18 deletions src/M6Web/Component/Tests/Units/MessageEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,55 @@ public function testGet()
*/
public function testgetStatsdMessage()
{
$expectedDeprecation =
'M6Web\Component\Statsd\MessageEntity::getStatsdMessage is deprecated and will be '.
'removed in the next major version. Update your code to use '.
'M6Web\Component\Statsd\MessageFormatter\MessageFormatterInterface::format.';

// not sampled message
$this->if($messageEntity = new Statsd\MessageEntity(
'raoul.node', 1, 'c'))
->then()
->string($messageEntity->getStatsdMessage())
->isEqualTo('raoul.node:1|c')
;
$this
->when(function () {
$this->if($messageEntity = new Statsd\MessageEntity(
'raoul.node', 1, 'c'))
->then()
->string($messageEntity->getStatsdMessage())
->isEqualTo('raoul.node:1|c')
;
})
->error()
->withMessage($expectedDeprecation)
->exists();

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

$this->if($messageEntity = new Statsd\MessageEntity(
'raoul.node', 1, 'c', 0.2))
->then()
->string($messageEntity->getStatsdMessage())
->isEqualTo('raoul.node:1|c|@0.2')
;
$this
->when(function () {
$this->if($messageEntity = new Statsd\MessageEntity(
'raoul.node', 1, 'c', 0.2))
->then()
->string($messageEntity->getStatsdMessage())
->isEqualTo('raoul.node:1|c|@0.2')
;
})
->error()
->withMessage($expectedDeprecation)
->exists();

// with tags
$this->if($messageEntity = new Statsd\MessageEntity(
'raoul.node', 1, 'c', 0.2, ['foo' => 'bar']))
->then()
->string($messageEntity->getStatsdMessage())
->isEqualTo('raoul.node,foo=bar:1|c|@0.2')
;
$this
->when(function () {
$this->if($messageEntity = new Statsd\MessageEntity(
'raoul.node', 1, 'c', 0.2, ['foo' => 'bar']))
->then()
->string($messageEntity->getStatsdMessage())
->isEqualTo('raoul.node,foo=bar:1|c|@0.2')
;
})
->error()
->withMessage($expectedDeprecation)
->exists();
}

public function testErrorConstructorStatsdMessage()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace M6Web\Component\Statsd\Tests\Units\MessageFormatter;

use M6Web\Component\Statsd;
use mageekguy\atoum;

class DogStatsDMessageFormatter extends atoum\test
{
public function testFormat()
{
// not sampled message
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c');
$this
->if($formatter = new Statsd\MessageFormatter\DogStatsDMessageFormatter())
->then()
->string($formatter->format($message))
->isEqualTo("raoul.node:1|c\n");

// sampled message
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2);
$this->calling($message)->useSampleRate = true;
$this
->given($formatter = new Statsd\MessageFormatter\DogStatsDMessageFormatter())
->then()
->string($formatter->format($message))
->isEqualTo("raoul.node:1|c|@0.2\n");

// with tags
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2, ['foo' => 'bar']);
$this->calling($message)->useSampleRate = true;
$this
->if($formatter = new Statsd\MessageFormatter\DogStatsDMessageFormatter())
->then()
->string($formatter->format($message))
->isEqualTo("raoul.node:1|c|@0.2|#foo:bar\n");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace M6Web\Component\Statsd\Tests\Units\MessageFormatter;

use M6Web\Component\Statsd;
use mageekguy\atoum;

class InfluxDBStatsDMessageFormatter extends atoum\test
{
public function testFormat()
{
// not sampled message
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c');
$this
->if($formatter = new Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter())
->then()
->string($formatter->format($message))
->isEqualTo('raoul.node:1|c');

// sampled message
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2);
$this->calling($message)->useSampleRate = true;
$this
->given($formatter = new Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter())
->then()
->string($formatter->format($message))
->isEqualTo('raoul.node:1|c|@0.2');

// with tags
$message = new \mock\M6Web\Component\Statsd\MessageEntity('raoul.node', 1, 'c', 0.2, ['foo' => 'bar']);
$this->calling($message)->useSampleRate = true;
$this
->if($formatter = new Statsd\MessageFormatter\InfluxDBStatsDMessageFormatter())
->then()
->string($formatter->format($message))
->isEqualTo('raoul.node,foo=bar:1|c|@0.2');
}
}

0 comments on commit 84e808d

Please sign in to comment.