Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 53 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
TotalsLookup | |
0.00% |
0 / 53 |
|
0.00% |
0 / 6 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
makeKey | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTotals | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
updateStats | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
72 | |||
touchCategoryCache | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
touchAllCategoriesCache | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
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 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Linter; |
22 | |
23 | use IBufferingStatsdDataFactory; |
24 | use MediaWiki\Config\ServiceOptions; |
25 | use MediaWiki\WikiMap\WikiMap; |
26 | use WANObjectCache; |
27 | use Wikimedia\Rdbms\Database as MWDatabase; |
28 | |
29 | /** |
30 | * Lookup to find and cache the total amount of |
31 | * lint errors in each category |
32 | */ |
33 | class TotalsLookup { |
34 | public const CONSTRUCTOR_OPTIONS = [ |
35 | 'LinterStatsdSampleFactor', |
36 | ]; |
37 | |
38 | private ServiceOptions $options; |
39 | private WANObjectCache $cache; |
40 | private IBufferingStatsdDataFactory $statsdDataFactory; |
41 | private CategoryManager $categoryManager; |
42 | private Database $database; |
43 | |
44 | /** |
45 | * @param ServiceOptions $options |
46 | * @param WANObjectCache $cache |
47 | * @param IBufferingStatsdDataFactory $statsdDataFactory |
48 | * @param CategoryManager $categoryManager |
49 | * @param Database $database |
50 | */ |
51 | public function __construct( |
52 | ServiceOptions $options, |
53 | WANObjectCache $cache, |
54 | IBufferingStatsdDataFactory $statsdDataFactory, |
55 | CategoryManager $categoryManager, |
56 | Database $database |
57 | ) { |
58 | $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
59 | $this->options = $options; |
60 | $this->cache = $cache; |
61 | $this->statsdDataFactory = $statsdDataFactory; |
62 | $this->categoryManager = $categoryManager; |
63 | $this->database = $database; |
64 | } |
65 | |
66 | /** |
67 | * @param string $cat |
68 | * @return string |
69 | */ |
70 | private function makeKey( $cat ) { |
71 | return $this->cache->makeKey( 'linter', 'total', $cat ); |
72 | } |
73 | |
74 | /** |
75 | * Get the totals for every category in the database |
76 | * |
77 | * @return array |
78 | */ |
79 | public function getTotals(): array { |
80 | $cats = $this->categoryManager->getVisibleCategories(); |
81 | $fetchedTotals = false; |
82 | $totals = []; |
83 | foreach ( $cats as $cat ) { |
84 | $totals[$cat] = $this->cache->getWithSetCallback( |
85 | $this->makeKey( $cat ), |
86 | WANObjectCache::TTL_INDEFINITE, |
87 | function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $cat, &$fetchedTotals ) { |
88 | $setOpts += MWDatabase::getCacheSetOptions( |
89 | $this->database->getDBConnectionRef( DB_REPLICA ) |
90 | ); |
91 | if ( $fetchedTotals === false ) { |
92 | $fetchedTotals = $this->database->getTotals(); |
93 | } |
94 | return $fetchedTotals[$cat]; |
95 | }, |
96 | [ |
97 | 'checkKeys' => [ |
98 | $this->cache->makeKey( 'linter', 'totals' ), |
99 | $this->makeKey( $cat ), |
100 | ], |
101 | 'lockTSE' => 30, |
102 | ] |
103 | ); |
104 | } |
105 | return $totals; |
106 | } |
107 | |
108 | /** |
109 | * Send stats to statsd and update totals cache |
110 | * |
111 | * @param array $changes |
112 | */ |
113 | public function updateStats( array $changes ) { |
114 | $linterStatsdSampleFactor = $this->options->get( 'LinterStatsdSampleFactor' ); |
115 | |
116 | if ( $linterStatsdSampleFactor === false ) { |
117 | // Don't send to statsd, but update cache with $changes |
118 | $raw = $changes['added']; |
119 | foreach ( $changes['deleted'] as $cat => $count ) { |
120 | if ( isset( $raw[$cat] ) ) { |
121 | $raw[$cat] -= $count; |
122 | } else { |
123 | // Negative value |
124 | $raw[$cat] = 0 - $count; |
125 | } |
126 | } |
127 | |
128 | foreach ( $raw as $cat => $count ) { |
129 | if ( $count != 0 ) { |
130 | // There was a change in counts, invalidate the cache |
131 | $this->touchCategoryCache( $cat ); |
132 | } |
133 | } |
134 | return; |
135 | } elseif ( mt_rand( 1, $linterStatsdSampleFactor ) != 1 ) { |
136 | return; |
137 | } |
138 | |
139 | $totals = $this->database->getTotals(); |
140 | $wiki = WikiMap::getCurrentWikiId(); |
141 | $stats = $this->statsdDataFactory; |
142 | foreach ( $totals as $name => $count ) { |
143 | $stats->gauge( "linter.category.$name.$wiki", $count ); |
144 | } |
145 | $stats->gauge( "linter.totals.$wiki", array_sum( $totals ) ); |
146 | |
147 | $this->touchAllCategoriesCache(); |
148 | } |
149 | |
150 | /** |
151 | * Have a single category be recalculated |
152 | * |
153 | * @param string $cat category name |
154 | */ |
155 | public function touchCategoryCache( $cat ) { |
156 | $this->cache->touchCheckKey( $this->makeKey( $cat ) ); |
157 | } |
158 | |
159 | /** |
160 | * Have all categories be recalculated |
161 | */ |
162 | public function touchAllCategoriesCache() { |
163 | $this->cache->touchCheckKey( $this->cache->makeKey( 'linter', 'totals' ) ); |
164 | } |
165 | } |