A statsd client for Symfony 2.
-
Add it to your
composer.json
file:"require": { "guerriat/metricsbundle": "dev-master" }
-
Update your vendors using composer:
$ php composer.phar update
-
Register it in
app/AppKernel.php
by adding the following line to the$bundles
array:new Guerriat\MetricsBundle\GuerriatMetricsBundle(),
-
Configure it at will, following the syntax detailed in "Configuration" and "Usage" sections.
This bundle's configuration has two main sections: servers & clients.
guerriat_metrics:
servers:
clients:
You can setup multiple statsd
servers and give them a name. For each one, you can specify the host (defaults to 127.0.0.1
), the port (defaults to 8125
) and the maximum size of UDP payload in bytes (defaults to 512
). Metrics are sent together (as much as possible by datagram) at the end of the page generation.
Here's an sample configuration file for two servers:
guerriat_metrics:
servers:
default:
host: 127.0.0.1
port: 8125
funky:
host: funky.guerriat.be
port: 54321
udp_max_size: 1024
A client is named and can be assigned to all servers or a subset of them (and that's the whole point of clients). This is done by specifying an array of servers name for the servers
property. If you want to use all servers, you can set the array ['all']
or simply omit the servers
property.
When using multiple servers, this bundle makes sure that a given key will always be sent to the same server, avoiding aggregation trouble at the statsd level.
A client can also have other sections such as events
, collectors
and monolog
, as explained in Usage.
Here's a sample configuration file featuring three servers and two clients:
guerriat_metrics:
servers:
srv1:
host: 127.0.0.1
srv2:
host: statsd1.guerriat.be
srv3:
host: statsd2.guerriat.be
clients:
default:
servers: ['all']
no_srv2:
servers: ['srv1', 'srv3']
You can set a key prefix for a given client:
client_name:
prefix: app_name.env_name
It will be used for all metrics sent by the client (including metrics sent via events, collectors and monolog).
By default, collectors
will ignore routes that begin with an underscore (such as _wdt
or _assetic_*
), but you can override that behaviour by adding ignore_underscore_route: false
to the client's config as shown:
client_name:
ignore_underscore_route: false
Every metric is identified by a key. It can only contains alphanumerical characters, hyphens, underscores and dots.
A counter that you can increment and decrement. You can also sample it, in order not to overload the network. By example, if the sampling rate is 0.1, the message will be sent only once every ten calls. The data is automatically corrected in order for graphite to give correct values. Thus, you don't need to worry about data consistency when changing the sampling rate.
(note that decrementing is not supported by the current version of Etsy's implementation of statsd, due to a bug)
A timer is a time in millisecond. Derived values (such as mean, percentile…) are automatically calculated for analytics purposes.
A gauge contains a arbitrary value.
A set is a count of unique numbers among the different numbers sent. By example, you can send the user id in order to count the number of active user.
There is 4 ways to send metrics :
- directly in the controller
- by binding on events
- by using a collector service
- via Monolog
Metrics are automatically sent with the onKernelTerminate
event, but they can be sent at any time by using the flushMetrics()
method on a client as shown below from a controller:
$this->get('guerriat_metrics')->flushMetrics();
In the controller, you can send metrics using the default
client thanks to the guerriat_metrics
service. For other clients, the service is named guerriat_metrics.client_name
.
// Counting
$this->get('guerriat_metrics')->increment('key');
$this->get('guerriat_metrics')->decrement('key');
// … with a sampling rate
$this->get('guerriat_metrics')->increment('key', 0.5);
// … with a custom value
$this->get('guerriat_metrics')->counter('key', 4);
// … with both
$this->get('guerriat_metrics')->counter('key', 4, 0.5);
// Timing
$start = microtime(true);
slow_function();
$this->get('guerriat_metrics')->timer('key', (microtime(true) - $start) * 1000);
// … or give a callable
$this->get('guerriat_metrics')->timing('key', 'slow_function');
// Gauge
$this->get('guerriat_metrics')->gauge('key', 42);
// Set
$this->get('guerriat_metrics')->set('key', 6);
You can setup events to be listened directly in the config file. For each clients, there is an events
section, specifying which metrics should be sent. All types are available: counter
(with increment
and decrement
shortcuts), timer
, gauge
and set
.
The configuration is done like this:
event_name:
type: app.key
Or like this, if you want to specify which method is called to get the metric value (except for increment
and decrement
):
event_name:
type:
key: app.key
method: getValue
By default, timer
will call getDuration()
while counter
, gauge
and set
will call getValue()
.
Here's an excerpt of a sample configuration file:
client_name:
events:
guerriat.app.visit:
increment: guerriat.guest
guerriat.app.long_event:
timer: guerriat.long_event.timer
counter: guerriat.long_event.processed_items
gauge:
key: guerriat.long_event.result
method: getResult
guerriat.app.login:
decrement: guerriat.guest
increment: guerriat.known_user
timer:
key: guerriat.password_hash.timer
method: getHashDuration
set:
key: guerriat.connected_users
method: getUserId
$event = new \Symfony\Component\EventDispatcher\Event();
$this->get('event_dispatcher')->dispatch('event_name', $event);
A MetricCollector
is a class extending MetricCollector
, having at least a method which is called on kernel.response and whose signature is:
public function collect($client, $key, $request, $response, $exception, $master, $ignore_underscore_route);
$client
is a reference to theClient
service and you can use it as you would from the controller$key
is the key specified in the config file$request
is theRequest
object$response
is theResponse
object$exception
is theException
object$master
is a boolean indicating whether it is the master request$ignore_underscore_route
is a boolean indicating whether it should ignore route starting with_
You have to activate a MetricCollector
for a specific client via the config file, specifying the service name of the collector and the key you want to use, as you can see in this excerpt of a sample configuration file:
client_name:
collectors:
sample_bundle.collector.url: guerriat.request.url
guerriat_metrics.collector.time: guerriat.request.time
A few MetricCollector
s are included in this bundle:
guerriat_metrics.collector.time
collects the request time (with route name, in a timer)guerriat_metrics.collector.exception
collects the exception (with code, in a counter)guerriat_metrics.collector.memory
collects PHP memory usage (in KB, in a gauge)guerriat_metrics.collector.hit
collects hits (with route name, in a counter)guerriat_metrics.collector.response
collects responses status code (with request route name, in a counter)
This bundle have a Monolog handler which can send a increment on every log above a set level. As for the events and collectors, this is configured by client, as you can see in this self-explanatory excerpt:
client_name:
monolog:
enable: true
prefix: 'log' ## key prefix
level: 'warning' ## minimum level
formatter:
context_logging: true ## if you want additional packets for context, default is false.
extra_logging: true ## if you want additional packets for extra, default is false.
words: 3 ## the number of the word in the stats key, default is 2.
However, you also must tell Monolog about our handler by adding this to your config file:
monolog:
handlers:
guerriat_metrics:
type: service
id: guerriat_metrics.monolog.handler
Note that the prefix will be added to the client's prefix. The resulting key will have the format client_prefix.monolog_prefix.channel.level.first_words
.
$logger = $this->get('logger');
$logger->error('An error occurred');
$logger->warning('Something strange took place');
$logger->info('Something happened');
If you need to format a string to a valid key, you can use this simple helper:
use Guerriat\MetricsBundle\Metric\KeyFormatter;
KeyFormatter::format("L'éclair au chocolat"); // returns "Leclair-au-chocolat"
You also can specify a maximum number of words:
KeyFormatter::format("L'éclair au chocolat", 2); // returns "Leclair-au"
Or a maximum number of characters:
KeyFormatter::format("L'éclair au chocolat", false, 5); // returns "Lecla"
You also can customize the separator:
KeyFormatter::format("L'éclair au chocolat", false, false, '.'); // returns "Leclair.au.chocolat"
Or do all this at once:
KeyFormatter::format("L'éclair au chocolat", 3, 15, '.'); // returns "Leclair.au.choc"
guerriat_metrics:
servers:
srv1:
host: 127.0.0.1 ## default is 127.0.0.1
port: 8125 ## default is 8125
srv2:
host: statsd.guerriat.be
beta:
host: statsd.guerriat.be
port: 8127
udp_max_size: 1024 ## default is 512
clients:
default:
prefix: guerriat.%kernel.environment%
servers: ['srv1', 'srv2'] ## default is ['all']
events:
guerriat.app.visit:
increment: visit.guest
guerriat.app.long_event:
timer: long_event.timer
counter: long_event.processed_items
gauge:
key: long_event.result
method: getResult
guerriat.app.login:
decrement: visit.guest
increment: visit.known_user
timer:
key: password_hash.timer
method: getHashDuration
set:
key: connected_users
method: getUserId
collectors:
sample_bundle.collector.url: request.url
guerriat_metrics.collector.time: request.time
guerriat_metrics.collector.exception: request
guerriat_metrics.collector.memory: request.memory
guerriat_metrics.collector.hit: request.hit
guerriat_metrics.collector.response: response
monolog:
enable: true
prefix: 'log' ## key prefix
level: 'warning' ## minimum level
test:
prefix: guerriat
ignore_underscore_route: false ## default is true
servers: ['test']
monolog:
enable: true
prefix: 'log'
level: 'debug'
formatter:
context_logging: true ## if you want additional packets for context, default is false.
extra_logging: true ## if you want additional packets for extra, default is false.
words: 3 ## the number of the word in the stats key, default is 2.
monolog:
handlers:
guerriat_metrics:
type: service
id: guerriat_metrics.monolog.handler
$ php composer.phar install
$ phpunit --coverage-html Tests/coverage
Written by Olivier Guerriat and greatly inspired by those two bundles: