Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.77% covered (warning)
80.77%
21 / 26
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
StatsUtils
80.77% covered (warning)
80.77%
21 / 26
42.86% covered (danger)
42.86%
3 / 7
20.30
0.00% covered (danger)
0.00%
0 / 1
 validateNewSampleRate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getFilteredSamples
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 validateMetricName
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 validateLabelKey
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
4.12
 validateLabelValue
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 normalizeArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 normalizeString
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 * @file
18 */
19
20declare( strict_types=1 );
21
22namespace Wikimedia\Stats;
23
24use InvalidArgumentException;
25use Wikimedia\Stats\Exceptions\InvalidConfigurationException;
26
27/**
28 * Functionality common to all metric types.
29 *
30 * @author Cole White
31 * @since 1.38
32 */
33class StatsUtils {
34
35    public const RE_VALID_NAME_AND_LABEL_NAME = "/^[a-zA-Z_][a-zA-Z0-9_]*$/";
36    public const DEFAULT_SAMPLE_RATE = 1.0;
37
38    /**
39     * Validates the new sample rate.  Throws InvalidArgumentException if provided an invalid rate.
40     *
41     * @param float $newSampleRate
42     * @throws InvalidArgumentException
43     */
44    public static function validateNewSampleRate( float $newSampleRate ): void {
45        if ( $newSampleRate < 0.0 || $newSampleRate > 1.0 ) {
46            throw new InvalidArgumentException( "Sample rate can only be between 0.0 and 1.0. Got: " . $newSampleRate );
47        }
48    }
49
50    /**
51     * Returns a subset of samples based on configured sample rate.
52     *
53     * @param float $sampleRate
54     * @param array $samples
55     * @return array
56     */
57    public static function getFilteredSamples( float $sampleRate, array $samples ): array {
58        if ( $sampleRate === 1.0 ) {
59            return $samples;
60        }
61        $output = [];
62        $randMax = mt_getrandmax();
63        foreach ( $samples as $sample ) {
64            if ( mt_rand() / $randMax < $sampleRate ) {
65                $output[] = $sample;
66            }
67        }
68        return $output;
69    }
70
71    /**
72     * Determines if provided string is a valid name.
73     *
74     * @param string $name
75     * @return void
76     * @throws InvalidArgumentException
77     * @throws InvalidConfigurationException
78     */
79    public static function validateMetricName( string $name ) {
80        if ( $name === "" ) {
81            throw new InvalidArgumentException( "Stats: Metric name cannot be empty." );
82        }
83        if ( !preg_match( self::RE_VALID_NAME_AND_LABEL_NAME, $name ) ) {
84            throw new InvalidConfigurationException( "Invalid metric name: '" . $name . "'" );
85        }
86    }
87
88    /**
89     * Determines if provided string is a valid label key.
90     *
91     * @param string $key
92     * @return void
93     * @throws InvalidArgumentException
94     * @throws InvalidConfigurationException
95     */
96    public static function validateLabelKey( string $key ) {
97        if ( $key === "" ) {
98            throw new InvalidArgumentException( "Stats: Label key cannot be empty." );
99        }
100        if ( !preg_match( self::RE_VALID_NAME_AND_LABEL_NAME, $key ) ) {
101            throw new InvalidConfigurationException( "Invalid label key: '" . $key . "'" );
102        }
103    }
104
105    public static function validateLabelValue( string $value ) {
106        if ( $value === "" ) {
107            throw new InvalidArgumentException( "Stats: Label value cannot be empty." );
108        }
109    }
110
111    /**
112     * Normalize an array of strings.
113     *
114     * @param string[] $entities
115     * @return string[]
116     */
117    public static function normalizeArray( array $entities ): array {
118        $normalizedEntities = [];
119        foreach ( $entities as $entity ) {
120            $normalizedEntities[] = self::normalizeString( $entity );
121        }
122        return $normalizedEntities;
123    }
124
125    /**
126     * Normalize strings to a metrics-compatible format.
127     *
128     * Replace all other non-alphanumeric characters with an underscore.
129     * Trim leading or trailing underscores.
130     *
131     * @param string $entity
132     * @return string
133     */
134    public static function normalizeString( string $entity ): string {
135        $entity = preg_replace( '/[^a-z\d]+/i', '_', $entity );
136        return trim( $entity, "_" );
137    }
138}