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 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23namespace Wikimedia\Stats;
24
25use Liuggio\StatsdClient\Entity\StatsdData;
26use Liuggio\StatsdClient\Entity\StatsdDataInterface;
27use Liuggio\StatsdClient\Factory\StatsdDataFactory;
28
29/**
30 * MediaWiki's adaption of StatsdDataFactory that provides buffering and metric prefixing.
31 *
32 * The buffering functionality exists as a performance optimisation to reduce network
33 * traffic and StatsD processing by maximally utilizing StatsdClient's ability to
34 * compress counter increments, and send all data in a few large UDP packets
35 * over a single connection.
36 *
37 * These buffers are sent from MediaWikiEntryPoint::emitBufferedStatsdData. For web requests,
38 * this happens post-send. For command-line scripts, this happens periodically from a database
39 * callback (see MWLBFactory::applyGlobalState).
40 *
41 * @todo Evaluate upstream's StatsdService class, which implements similar buffering logic
42 * and was released in statsd-php-client 1.0.13, shortly after we implemented this here
43 * for statsd-php-client 1.0.12 at the time.
44 *
45 * @since 1.25
46 * @method StatsdData produceStatsdDataEntity() We use StatsdData::setKey, which is not in
47 *  StatsdDataInterface https://gerrit.wikimedia.org/r/643976
48 */
49class BufferingStatsdDataFactory extends StatsdDataFactory implements IBufferingStatsdDataFactory {
50    /** @var array */
51    protected $buffer = [];
52    /** @var bool */
53    protected $enabled = true;
54    /** @var string */
55    private $prefix;
56
57    public function __construct( $prefix ) {
58        parent::__construct();
59        $this->prefix = $prefix;
60    }
61
62    //
63    // Methods for StatsdDataFactoryInterface
64    //
65
66    /**
67     * @param string $key
68     * @param float|int $time
69     * @return void
70     */
71    public function timing( $key, $time ) {
72        if ( !$this->enabled ) {
73            return;
74        }
75        $this->buffer[] = [ $key, $time, StatsdDataInterface::STATSD_METRIC_TIMING ];
76    }
77
78    /**
79     * @param string $key
80     * @param float|int $value
81     * @return void
82     */
83    public function gauge( $key, $value ) {
84        if ( !$this->enabled ) {
85            return;
86        }
87        $this->buffer[] = [ $key, $value, StatsdDataInterface::STATSD_METRIC_GAUGE ];
88    }
89
90    /**
91     * @param string $key
92     * @param float|int $value
93     * @return array
94     */
95    public function set( $key, $value ) {
96        if ( !$this->enabled ) {
97            return [];
98        }
99        $this->buffer[] = [ $key, $value, StatsdDataInterface::STATSD_METRIC_SET ];
100        return [];
101    }
102
103    /**
104     * @param string $key
105     * @return array
106     */
107    public function increment( $key ) {
108        if ( !$this->enabled ) {
109            return [];
110        }
111        $this->buffer[] = [ $key, 1, StatsdDataInterface::STATSD_METRIC_COUNT ];
112        return [];
113    }
114
115    /**
116     * @param string $key
117     * @return void
118     */
119    public function decrement( $key ) {
120        if ( !$this->enabled ) {
121            return;
122        }
123        $this->buffer[] = [ $key, -1, StatsdDataInterface::STATSD_METRIC_COUNT ];
124    }
125
126    /**
127     * @param string $key
128     * @param int $delta
129     * @return void
130     */
131    public function updateCount( $key, $delta ) {
132        if ( !$this->enabled ) {
133            return;
134        }
135        $this->buffer[] = [ $key, $delta, StatsdDataInterface::STATSD_METRIC_COUNT ];
136    }
137
138    /**
139     * Normalize a metric key for StatsD
140     *
141     * The following are changes you may rely on:
142     *
143     * - Non-alphanumerical characters are converted to underscores.
144     * - Empty segments are removed, e.g. "foo..bar" becomes "foo.bar".
145     *   This is mainly for StatsD-Graphite-Carbon setups where each segment is a directory
146     *   and must have a non-empty name.
147     * - Deliberately invalid input that looks like `__METHOD__` (namespaced PHP class and method)
148     *   is changed from "\\Namespace\\Class::method" to "Namespace_Class.method".
149     *   This is used by ProfilerOutputStats.
150     *
151     * @param string $key
152     * @return string
153     */
154    private static function normalizeMetricKey( $key ) {
155        $key = strtr( $key, [ '::' => '.' ] );
156        $key = preg_replace( '/[^a-zA-Z0-9.]+/', '_', $key );
157        $key = trim( $key, '_.' );
158        return strtr( $key, [ '..' => '.' ] );
159    }
160
161    public function produceStatsdData(
162        $key, $value = 1, $metric = StatsdDataInterface::STATSD_METRIC_COUNT
163    ) {
164        $entity = $this->produceStatsdDataEntity();
165        if ( $key !== null ) {
166            $key = self::normalizeMetricKey( "{$this->prefix}.{$key}" );
167            $entity->setKey( $key );
168        }
169        if ( $value !== null ) {
170            $entity->setValue( $value );
171        }
172        if ( $metric !== null ) {
173            $entity->setMetric( $metric );
174        }
175        return $entity;
176    }
177
178    //
179    // Methods for IBufferingStatsdDataFactory
180    //
181
182    public function hasData() {
183        return (bool)$this->buffer;
184    }
185
186    /**
187     * @since 1.30
188     * @return StatsdData[]
189     */
190    public function getData() {
191        $data = [];
192        foreach ( $this->buffer as [ $key, $val, $metric ] ) {
193            // Optimization: Don't bother transmitting a counter update with a delta of zero
194            if ( $metric === StatsdDataInterface::STATSD_METRIC_COUNT && !$val ) {
195                continue;
196            }
197
198            // Optimisation: Avoid produceStatsdData cost during web requests (T288702).
199            // Instead, we do it here in getData() right before the data is transmitted.
200            $data[] = $this->produceStatsdData( $key, $val, $metric );
201        }
202
203        return $data;
204    }
205
206    public function clearData() {
207        $this->buffer = [];
208    }
209
210    public function getDataCount() {
211        return count( $this->buffer );
212    }
213
214    public function setEnabled( $enabled ) {
215        $this->enabled = $enabled;
216    }
217}
218
219/** @deprecated class alias since 1.43 */
220class_alias( BufferingStatsdDataFactory::class, 'BufferingStatsdDataFactory' );