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