Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
8.70% covered (danger)
8.70%
4 / 46
7.14% covered (danger)
7.14%
1 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
BufferingStatsdDataFactory
8.89% covered (danger)
8.89%
4 / 45
7.14% covered (danger)
7.14%
1 / 14
537.28
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 timing
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 gauge
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 set
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 increment
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 decrement
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 updateCount
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 normalizeMetricKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 produceStatsdData
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 hasData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getData
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 clearData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDataCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright 2015 Ori Livneh
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace Wikimedia\Stats;
10
11use Liuggio\StatsdClient\Entity\StatsdData;
12use Liuggio\StatsdClient\Entity\StatsdDataInterface;
13use Liuggio\StatsdClient\Factory\StatsdDataFactory;
14
15/**
16 * MediaWiki's adaption of StatsdDataFactory that provides buffering and metric prefixing.
17 *
18 * The buffering functionality exists as a performance optimisation to reduce network
19 * traffic and StatsD processing by maximally utilizing StatsdClient's ability to
20 * compress counter increments, and send all data in a few large UDP packets
21 * over a single connection.
22 *
23 * These buffers are sent from MediaWikiEntryPoint::emitBufferedStats. For web requests,
24 * this happens post-send. For command-line scripts, this happens periodically from calls
25 * to Maintenance::commitTransaction() and Maintenance::commitTransactionRound().
26 *
27 * @todo Evaluate upstream's StatsdService class, which implements similar buffering logic
28 * and was released in statsd-php-client 1.0.13, shortly after we implemented this here
29 * for statsd-php-client 1.0.12 at the time.
30 *
31 * @since 1.25
32 * @deprecated since 1.45, use https://www.mediawiki.org/wiki/Manual:Stats
33 * @method StatsdData produceStatsdDataEntity() We use StatsdData::setKey, which is not in
34 *  StatsdDataInterface https://gerrit.wikimedia.org/r/643976
35 */
36class BufferingStatsdDataFactory extends StatsdDataFactory implements IBufferingStatsdDataFactory {
37    /** @var array */
38    protected $buffer = [];
39    /** @var bool */
40    protected $enabled = true;
41    /** @var string */
42    private $prefix;
43
44    public function __construct( string $prefix ) {
45        parent::__construct();
46        $this->prefix = $prefix;
47    }
48
49    //
50    // Methods for StatsdDataFactoryInterface
51    //
52
53    /**
54     * @param string $key
55     * @param float|int $time
56     * @return void
57     */
58    public function timing( $key, $time ) {
59        if ( !$this->enabled ) {
60            return;
61        }
62        $this->buffer[] = [ $key, $time, StatsdDataInterface::STATSD_METRIC_TIMING ];
63    }
64
65    /**
66     * @param string $key
67     * @param float|int $value
68     * @return void
69     */
70    public function gauge( $key, $value ) {
71        if ( !$this->enabled ) {
72            return;
73        }
74        $this->buffer[] = [ $key, $value, StatsdDataInterface::STATSD_METRIC_GAUGE ];
75    }
76
77    /**
78     * @param string $key
79     * @param float|int $value
80     * @return array
81     */
82    public function set( $key, $value ) {
83        if ( !$this->enabled ) {
84            return [];
85        }
86        $this->buffer[] = [ $key, $value, StatsdDataInterface::STATSD_METRIC_SET ];
87        return [];
88    }
89
90    /**
91     * @param string $key
92     * @return array
93     */
94    public function increment( $key ) {
95        if ( !$this->enabled ) {
96            return [];
97        }
98        $this->buffer[] = [ $key, 1, StatsdDataInterface::STATSD_METRIC_COUNT ];
99        return [];
100    }
101
102    /**
103     * @param string $key
104     * @return void
105     */
106    public function decrement( $key ) {
107        if ( !$this->enabled ) {
108            return;
109        }
110        $this->buffer[] = [ $key, -1, StatsdDataInterface::STATSD_METRIC_COUNT ];
111    }
112
113    /**
114     * @param string $key
115     * @param int $delta
116     * @return void
117     */
118    public function updateCount( $key, $delta ) {
119        if ( !$this->enabled ) {
120            return;
121        }
122        $this->buffer[] = [ $key, $delta, StatsdDataInterface::STATSD_METRIC_COUNT ];
123    }
124
125    /**
126     * Normalize a metric key for StatsD
127     *
128     * The following are changes you may rely on:
129     *
130     * - Non-alphanumerical characters are converted to underscores.
131     * - Empty segments are removed, e.g. "foo..bar" becomes "foo.bar".
132     *   This is mainly for StatsD-Graphite-Carbon setups where each segment is a directory
133     *   and must have a non-empty name.
134     * - Deliberately invalid input that looks like `__METHOD__` (namespaced PHP class and method)
135     *   is changed from "\\Namespace\\Class::method" to "Namespace_Class.method".
136     *   This is used by ProfilerOutputStats.
137     *
138     * @param string $key
139     * @return string
140     */
141    private static function normalizeMetricKey( $key ) {
142        $key = strtr( $key, [ '::' => '.' ] );
143        $key = preg_replace( '/[^a-zA-Z0-9.]+/', '_', $key );
144        $key = trim( $key, '_.' );
145        return strtr( $key, [ '..' => '.' ] );
146    }
147
148    /** @inheritDoc */
149    public function produceStatsdData(
150        $key, $value = 1, $metric = StatsdDataInterface::STATSD_METRIC_COUNT
151    ) {
152        $entity = $this->produceStatsdDataEntity();
153        if ( $key !== null ) {
154            $key = self::normalizeMetricKey( "{$this->prefix}.{$key}" );
155            $entity->setKey( $key );
156        }
157        if ( $value !== null ) {
158            $entity->setValue( $value );
159        }
160        if ( $metric !== null ) {
161            $entity->setMetric( $metric );
162        }
163        return $entity;
164    }
165
166    //
167    // Methods for IBufferingStatsdDataFactory
168    //
169
170    /** @inheritDoc */
171    public function hasData() {
172        return (bool)$this->buffer;
173    }
174
175    /**
176     * @since 1.30
177     * @return StatsdData[]
178     */
179    public function getData() {
180        $data = [];
181        foreach ( $this->buffer as [ $key, $val, $metric ] ) {
182            // Optimization: Don't bother transmitting a counter update with a delta of zero
183            if ( $metric === StatsdDataInterface::STATSD_METRIC_COUNT && !$val ) {
184                continue;
185            }
186
187            // Optimisation: Avoid produceStatsdData cost during web requests (T288702).
188            // Instead, we do it here in getData() right before the data is transmitted.
189            $data[] = $this->produceStatsdData( $key, $val, $metric );
190        }
191
192        return $data;
193    }
194
195    /** @inheritDoc */
196    public function clearData() {
197        $this->buffer = [];
198    }
199
200    /** @inheritDoc */
201    public function getDataCount() {
202        return count( $this->buffer );
203    }
204
205    /** @inheritDoc */
206    public function setEnabled( $enabled ) {
207        $this->enabled = $enabled;
208    }
209}
210
211/** @deprecated class alias since 1.43 */
212class_alias( BufferingStatsdDataFactory::class, 'BufferingStatsdDataFactory' );