Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
62.86% covered (warning)
62.86%
22 / 35
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
StatsUtils
62.86% covered (warning)
62.86%
22 / 35
37.50% covered (danger)
37.50%
3 / 8
46.80
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
 mergeLabels
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 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%
3 / 3
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 *
29 * StatsUtils Implementation
30 *
31 * Functionality common to all metric types.
32 *
33 * @author Cole White
34 * @since 1.38
35 */
36class StatsUtils {
37
38    /** @var string */
39    public const RE_VALID_NAME_AND_LABEL_NAME = "/^[a-zA-Z_][a-zA-Z0-9_]*$/";
40
41    /** @var float */
42    public const DEFAULT_SAMPLE_RATE = 1.0;
43
44    /**
45     * Validates the new sample rate.  Throws InvalidArgumentException if provided an invalid rate.
46     *
47     * @param float $newSampleRate
48     * @throws InvalidArgumentException
49     */
50    public static function validateNewSampleRate( float $newSampleRate ): void {
51        if ( $newSampleRate < 0.0 || $newSampleRate > 1.0 ) {
52            throw new InvalidArgumentException( "Sample rate can only be between 0.0 and 1.0. Got: " . $newSampleRate );
53        }
54    }
55
56    /**
57     * Returns a subset of samples based on configured sample rate.
58     *
59     * @param float $sampleRate
60     * @param array $samples
61     * @return array
62     */
63    public static function getFilteredSamples( float $sampleRate, array $samples ): array {
64        if ( $sampleRate === 1.0 ) {
65            return $samples;
66        }
67        $output = [];
68        $randMax = mt_getrandmax();
69        foreach ( $samples as $sample ) {
70            if ( mt_rand() / $randMax < $sampleRate ) {
71                $output[] = $sample;
72            }
73        }
74        return $output;
75    }
76
77    /**
78     * Determines if provided string is a valid name.
79     *
80     * @param string $name
81     * @return void
82     * @throws InvalidArgumentException
83     * @throws InvalidConfigurationException
84     */
85    public static function validateMetricName( string $name ) {
86        if ( $name === "" ) {
87            throw new InvalidArgumentException( "Stats: Metric name cannot be empty." );
88        }
89        if ( !preg_match( self::RE_VALID_NAME_AND_LABEL_NAME, $name ) ) {
90            throw new InvalidConfigurationException( "Invalid metric name: '" . $name . "'" );
91        }
92    }
93
94    /**
95     * Determines if provided string is a valid label key.
96     *
97     * @param string $key
98     * @return void
99     * @throws InvalidArgumentException
100     * @throws InvalidConfigurationException
101     */
102    public static function validateLabelKey( string $key ) {
103        if ( $key === "" ) {
104            throw new InvalidArgumentException( "Stats: Label key cannot be empty." );
105        }
106        if ( !preg_match( self::RE_VALID_NAME_AND_LABEL_NAME, $key ) ) {
107            throw new InvalidConfigurationException( "Invalid label key: '" . $key . "'" );
108        }
109    }
110
111    public static function validateLabelValue( string $value ) {
112        if ( $value === "" ) {
113            throw new InvalidArgumentException( "Stats: Label value cannot be empty." );
114        }
115    }
116
117    /**
118     * Merges two associative arrays of labels.  Prioritizes leftmost labels.
119     *
120     * @param array $leftLabels
121     * @param array $rightLabels
122     * @return array
123     */
124    public static function mergeLabels( array $leftLabels, array $rightLabels ): array {
125        $output = [];
126        foreach ( $leftLabels as $key => $value ) {
127            $output[$key] = $value;
128        }
129        foreach ( $rightLabels as $key => $value ) {
130            if ( array_key_exists( $key, $output ) ) {
131                continue;
132            }
133            $output[$key] = $value;
134        }
135        return $output;
136    }
137
138    /**
139     * Normalize an array of strings.
140     *
141     * @param string[] $entities
142     * @return string[]
143     */
144    public static function normalizeArray( array $entities ): array {
145        $normalizedEntities = [];
146        foreach ( $entities as $entity ) {
147            $normalizedEntities[] = self::normalizeString( $entity );
148        }
149        return $normalizedEntities;
150    }
151
152    /**
153     * Normalize strings to a metrics-compatible format.
154     *
155     * Replace any other non-alphanumeric characters with underscores.
156     * Eliminate repeated underscores.
157     * Trim leading or trailing underscores.
158     *
159     * @param string $entity
160     * @return string
161     */
162    public static function normalizeString( string $entity ): string {
163        $entity = preg_replace( "/[^a-z0-9]/i", "_", $entity );
164        $entity = preg_replace( "/_+/", "_", $entity );
165        return trim( $entity, "_" );
166    }
167}