PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Stats.php

Stats.php in ElasticPress 5.3.5, at includes/classes/Stats.php

354 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ElasticPress index health stats page handler
4 *
5 * @since 3.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress;
10
11 use ElasticPress\Utils;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Class Stats
19 *
20 * @package ElasticPress
21 */
22 class Stats {
23 /**
24 * Index list with health data of current cluster
25 *
26 * @var array
27 * @since 3.0
28 */
29 protected $health = [];
30
31 /**
32 * Stats retrieved directly from the current cluster
33 *
34 * @var array
35 * @since 3.x
36 */
37 protected $stats = [];
38
39 /**
40 * Overall stats of the cluster
41 *
42 * @var array
43 * @since 3.2
44 */
45 protected $totals = [
46 'size' => 0,
47 'memory' => 0,
48 'docs' => 0,
49 ];
50
51 /**
52 * Later localized data.
53 *
54 * Used for chart building purposes
55 *
56 * @var array
57 * @since 3.2
58 */
59 protected $localized = [
60 'index_total' => 0,
61 'query_total' => 0,
62 'suggest_total' => 0,
63 'indices_data' => [],
64 ];
65
66 /**
67 * Cluster node data.
68 *
69 * Used to determine cluster health
70 *
71 * @var int
72 * @since 3.2
73 */
74 protected $nodes = 0;
75
76 /**
77 * Failed queries and their errors.
78 *
79 * @since 5.0.1
80 * @var string
81 */
82 protected $failed_queries = [];
83
84 /**
85 * Makes an api call to elasticsearch endpoint
86 *
87 * @param string $path Endpoint path to query
88 * @since 3.2
89 * @return array|mixed|object
90 */
91 protected function remote_request_helper( $path ) {
92 $request = Elasticsearch::factory()->remote_request( $path );
93
94 if ( empty( $request ) ) {
95 return false;
96 }
97
98 if ( is_wp_error( $request ) ) {
99 $this->failed_queries[] = [
100 'path' => $path,
101 'error' => $request->get_error_message(),
102 ];
103 return false;
104 }
105
106 $body = wp_remote_retrieve_body( $request );
107 $return = json_decode( $body, true );
108
109 if ( ! empty( $return['errors'] ) ) {
110 $this->failed_queries[] = [
111 'path' => $path,
112 'error' => wp_json_encode( $return['errors'] ),
113 ];
114 }
115
116 return $return;
117 }
118
119 /**
120 * Makes api calls and organizes data depending on the specified context.
121 *
122 * @param boolean $force Force stats to be built even if cached
123 * @since 3.2
124 */
125 public function build_stats( $force = false ) {
126 static $stats_built = false;
127
128 if ( $stats_built && ! $force ) {
129 return;
130 }
131
132 $stats_built = true;
133
134 $this->stats = $this->remote_request_helper( '_stats?format=json' );
135
136 if ( empty( $this->stats ) || empty( $this->stats['_all'] ) || empty( $this->stats['_all']['total'] ) ) {
137 return;
138 }
139
140 $this->populate_indices_stats();
141
142 if ( Utils\is_epio() ) {
143 $node_stats = $this->remote_request_helper( '_nodes/stats/discovery?format=json' );
144 } else {
145 $node_stats = $this->remote_request_helper( '_nodes/stats?format=json' );
146 }
147
148 if ( ! empty( $node_stats ) ) {
149 $this->nodes = $node_stats['_nodes']['total'];
150 }
151 }
152
153 /**
154 * Populate the instantiated object with the correct indices, based on context
155 *
156 * @since 3.x
157 */
158 private function populate_indices_stats() {
159 $network_activated = defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK;
160 $blog_id = get_current_blog_id();
161 $site_indices = $this->get_indices_for_site( $blog_id );
162
163 $indices = $this->remote_request_helper( '_cat/indices?format=json' );
164
165 if ( empty( $indices ) ) {
166 return;
167 }
168
169 // If the plugin is network activated we only want the data from the indexable WP indexes, not any others.
170 if ( $network_activated ) {
171 $indexable_sites = Utils\get_sites();
172 foreach ( $indexable_sites as $site ) {
173 $indexables = $this->get_indices_for_site( $site['blog_id'] );
174 $site_indices = array_merge( $site_indices, $indexables );
175 }
176 }
177
178 // Filter the general list of indices to contain only the ones we care about.
179 $filtered_indices = array_filter(
180 $indices,
181 function ( $index ) use ( $site_indices ) {
182 return in_array( $index['index'], $site_indices, true );
183 }
184 );
185
186 /**
187 * Allow sites to select which indices will be displayed in the Index Health page
188 *
189 * @param {array} $filtered_indices Indices filtered to the site(s) being queried.
190 * @param {array} $indices All indices returned from Elasticsearch
191 *
192 * @return {array} List of indices to use
193 *
194 * @since 3.x
195 * @hook ep_index_health_stats_indices
196 */
197 $filtered_indices = apply_filters( 'ep_index_health_stats_indices', $filtered_indices, $indices );
198
199 foreach ( $filtered_indices as $index ) {
200 $this->populate_index_stats( $index['index'], $index['health'] );
201 }
202 }
203
204 /**
205 * Get all registered index names for a given site ID
206 *
207 * @param int $site_id the site id
208 *
209 * @return array
210 * @since 3.x
211 */
212 public function get_indices_for_site( $site_id ) {
213 $indexables = Indexables::factory()->get_all();
214 $indices = array();
215
216 foreach ( $indexables as $indexable ) {
217 $indices[] = $indexable->get_index_name( $site_id );
218 }
219
220 return $indices;
221 }
222
223 /**
224 * Populate index storage capacity and metrics
225 * Note: in the numbers below, those using the total key are counting values across all primary and replica shards
226 * while those using the primaries key are reading only from the primary shards
227 *
228 * @param string $index_name index name
229 * @param string $health index health status
230 *
231 * @since 3.x
232 */
233 private function populate_index_stats( $index_name, $health ) {
234
235 if ( empty( $this->stats['indices'][ $index_name ] ) ) {
236 return;
237 }
238
239 // Index-specific data
240 $this->health[ $index_name ]['name'] = $index_name;
241 $this->health[ $index_name ]['health'] = $health;
242
243 $this->localized['indices_data'][ $index_name ]['name'] = $index_name;
244 $this->localized['indices_data'][ $index_name ]['docs'] = $this->stats['indices'][ $index_name ]['primaries']['docs']['count'];
245
246 // General data counts
247 $this->localized['index_total'] += absint( $this->stats['indices'][ $index_name ]['primaries']['indexing']['index_total'] );
248 $this->localized['query_total'] += absint( $this->stats['indices'][ $index_name ]['total']['search']['query_total'] );
249 $this->localized['suggest_total'] += absint( $this->stats['indices'][ $index_name ]['total']['search']['suggest_total'] );
250
251 $this->totals['docs'] += absint( $this->stats['indices'][ $index_name ]['primaries']['docs']['count'] );
252 $this->totals['size'] += absint( $this->stats['indices'][ $index_name ]['total']['store']['size_in_bytes'] );
253 $this->totals['memory'] += absint( $this->stats['indices'][ $index_name ]['total']['segments']['memory_in_bytes'] );
254 }
255
256 /**
257 * Get index list and health data of an elasticsearch endpoint
258 *
259 * @return array
260 * @since 3.2
261 */
262 public function get_health() {
263 $this->build_stats();
264 return $this->health;
265 }
266
267 /**
268 * Get number of nodes in the current cluster
269 *
270 * @since 3.2
271 * @return int
272 */
273 public function get_nodes() {
274 $this->build_stats();
275 return $this->nodes;
276 }
277
278 /**
279 * Gets relevant total data of an elasticsearch endpoint
280 *
281 * @since 3.2
282 * @return array
283 */
284 public function get_totals() {
285 $this->build_stats();
286 return $this->totals;
287 }
288
289 /**
290 * Gets localized data
291 *
292 * @since 3.2
293 * @return mixed Data used in localization for chart creation.
294 */
295 public function get_localized() {
296 $this->build_stats();
297 return $this->localized;
298 }
299
300 /**
301 * Converts a number to a readable size format.
302 *
303 * @param int $size Desired number to convert
304 * @since 3.2
305 * @return string Size with appended unit
306 */
307 public function convert_to_readable_size( $size ) {
308 if ( empty( $size ) ) {
309 return 0;
310 }
311
312 $base = log( $size ) / log( 1024 );
313 $suffix = array( '', 'KB', 'MB', 'GB', 'TB' );
314 $f_base = floor( $base );
315
316 return round( pow( 1024, $base - floor( $base ) ), 1 ) . $suffix[ $f_base ];
317 }
318
319 /**
320 * Return all failed queries.
321 *
322 * @return array
323 * @since 5.0.1
324 */
325 public function get_failed_queries() {
326 return $this->failed_queries;
327 }
328
329 /**
330 * Clear all failed queries registered.
331 *
332 * @since 5.0.1
333 */
334 public function clear_failed_queries() {
335 $this->failed_queries = [];
336 }
337
338 /**
339 * Return singleton instance of class
340 *
341 * @return self
342 * @since 3.2
343 */
344 public static function factory() {
345 static $instance = false;
346
347 if ( ! $instance ) {
348 $instance = new self();
349 }
350
351 return $instance;
352 }
353 }
354