PluginProbe
ElasticPress / 4.2.2
ElasticPress v4.2.2
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 4.2.2, at includes/classes/Stats.php

332 lines 8.1 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 as 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 'index_time_in_millis' => 0,
62 'query_total' => 0,
63 'query_time_in_millis' => 0,
64 'suggest_time_in_millis' => 0,
65 'suggest_total' => 0,
66 'indices_data' => [],
67 ];
68
69 /**
70 * Cluster node data.
71 *
72 * Used to determine cluster health
73 *
74 * @var int
75 * @since 3.2
76 */
77 protected $nodes = 0;
78
79 /**
80 * Makes an api call to elasticsearch endpoint
81 *
82 * @param string $path Endpoint path to query
83 * @since 3.2
84 * @return array|mixed|object
85 */
86 protected function remote_request_helper( $path ) {
87 $request = Elasticsearch::factory()->remote_request( $path );
88
89 if ( is_wp_error( $request ) || empty( $request ) ) {
90 return false;
91 }
92
93 $body = wp_remote_retrieve_body( $request );
94
95 return json_decode( $body, true );
96 }
97
98 /**
99 * Makes api calls and organizes data depending on the specified context.
100 *
101 * @param boolean $force Force stats to be built even if cached
102 * @since 3.2
103 */
104 public function build_stats( $force = false ) {
105 static $stats_built = false;
106
107 if ( $stats_built && ! $force ) {
108 return;
109 }
110
111 $stats_built = true;
112
113 $this->stats = $this->remote_request_helper( '_stats?format=json' );
114
115 if ( empty( $this->stats ) || empty( $this->stats['_all'] ) || empty( $this->stats['_all']['total'] ) ) {
116 return;
117 }
118
119 $this->populate_indices_stats();
120 $this->populate_indices_averages();
121
122 if ( Utils\is_epio() ) {
123 $node_stats = $this->remote_request_helper( '_nodes/stats/discovery?format=json' );
124 } else {
125 $node_stats = $this->remote_request_helper( '_nodes/stats?format=json' );
126 }
127
128 if ( ! empty( $node_stats ) ) {
129 $this->nodes = $node_stats['_nodes']['total'];
130 }
131 }
132
133 /**
134 * Populate the instantiated object with the correct indices, based on context
135 *
136 * @since 3.x
137 */
138 private function populate_indices_stats() {
139 $network_activated = defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK;
140 $blog_id = get_current_blog_id();
141 $site_indices = $this->get_indices_for_site( $blog_id );
142
143 $indices = $this->remote_request_helper( '_cat/indices?format=json' );
144
145 if ( empty( $indices ) ) {
146 return;
147 }
148
149 // If the plugin is network activated we only want the data from the indexable WP indexes, not any others.
150 if ( $network_activated ) {
151 $indexable_sites = Utils\get_sites();
152 foreach ( $indexable_sites as $site ) {
153 $indexables = $this->get_indices_for_site( $site['blog_id'] );
154 $site_indices = array_merge( $site_indices, $indexables );
155 }
156 }
157
158 // Filter the general list of indices to contain only the ones we care about.
159 $filtered_indices = array_filter(
160 $indices,
161 function ( $index ) use ( $site_indices ) {
162 return in_array( $index['index'], $site_indices, true );
163 }
164 );
165
166 /**
167 * Allow sites to select which indices will be displayed in the Index Health page
168 *
169 * @param {array} $filtered_indices Indices filtered to the site(s) being queried.
170 * @param {array} $indices All indices returned from Elasticsearch
171 *
172 * @return {array} List of indices to use
173 *
174 * @since 3.x
175 * @hook ep_index_health_stats_indices
176 */
177 $filtered_indices = apply_filters( 'ep_index_health_stats_indices', $filtered_indices, $indices );
178
179 foreach ( $filtered_indices as $index ) {
180 $this->populate_index_stats( $index['index'], $index['health'] );
181 }
182 }
183
184 /**
185 * Get all registered index names for a given site ID
186 *
187 * @param int $site_id the site id
188 *
189 * @return array
190 * @since 3.x
191 */
192 public function get_indices_for_site( $site_id ) {
193 $indexables = Indexables::factory()->get_all();
194 $indices = array();
195
196 foreach ( $indexables as $indexable ) {
197 $indices[] = $indexable->get_index_name( $site_id );
198 }
199
200 return $indices;
201 }
202
203 /**
204 * Populate cluster performance data. These use the total key so averages include both primary and replica shards
205 *
206 * @since 3.x
207 */
208 private function populate_indices_averages() {
209
210 if ( empty( $this->stats['_all']['total'] ) ) {
211 return;
212 }
213
214 // General cluster performance stats
215 $this->localized['index_time_in_millis'] = $this->stats['_all']['total']['indexing']['index_time_in_millis'];
216 $this->localized['query_time_in_millis'] = $this->stats['_all']['total']['search']['query_time_in_millis'];
217 $this->localized['suggest_time_in_millis'] = $this->stats['_all']['total']['search']['suggest_time_in_millis'];
218 }
219
220 /**
221 * Populate index storage capacity and metrics
222 * Note: in the numbers below, those using the total key are counting values across all primary and replica shards
223 * while those using the primaries key are reading only from the primary shards
224 *
225 * @param string $index_name index name
226 * @param string $health index health status
227 *
228 * @since 3.x
229 */
230 private function populate_index_stats( $index_name, $health ) {
231
232 if ( empty( $this->stats['indices'][ $index_name ] ) ) {
233 return;
234 }
235
236 // Index-specific data
237 $this->health[ $index_name ]['name'] = $index_name;
238 $this->health[ $index_name ]['health'] = $health;
239
240 $this->localized['indices_data'][ $index_name ]['name'] = $index_name;
241 $this->localized['indices_data'][ $index_name ]['docs'] = $this->stats['indices'][ $index_name ]['primaries']['docs']['count'];
242
243 // General data counts
244 $this->localized['index_total'] += absint( $this->stats['indices'][ $index_name ]['primaries']['indexing']['index_total'] );
245 $this->localized['query_total'] += absint( $this->stats['indices'][ $index_name ]['total']['search']['query_total'] );
246 $this->localized['suggest_total'] += absint( $this->stats['indices'][ $index_name ]['total']['search']['suggest_total'] );
247
248 $this->totals['docs'] += absint( $this->stats['indices'][ $index_name ]['primaries']['docs']['count'] );
249 $this->totals['size'] += absint( $this->stats['indices'][ $index_name ]['total']['store']['size_in_bytes'] );
250 $this->totals['memory'] += absint( $this->stats['indices'][ $index_name ]['total']['segments']['memory_in_bytes'] );
251 }
252
253 /**
254 * Get index list and health data of an elasticsearch endpoint
255 *
256 * @return array
257 * @since 3.2
258 */
259 public function get_health() {
260 $this->build_stats();
261 return $this->health;
262 }
263
264 /**
265 * Get number of nodes in the current cluster
266 *
267 * @since 3.2
268 * @return int
269 */
270 public function get_nodes() {
271 $this->build_stats();
272 return $this->nodes;
273 }
274
275 /**
276 * Gets relevant total data of an elasticsearch endpoint
277 *
278 * @since 3.2
279 * @return array
280 */
281 public function get_totals() {
282 $this->build_stats();
283 return $this->totals;
284 }
285
286 /**
287 * Gets localized data
288 *
289 * @since 3.2
290 * @return mixed Data used in localization for chart creation.
291 */
292 public function get_localized() {
293 $this->build_stats();
294 return $this->localized;
295 }
296
297 /**
298 * Converts a number to a readable size format.
299 *
300 * @param int $size Desired number to convert
301 * @since 3.2
302 * @return string Size with appended unit
303 */
304 public function convert_to_readable_size( $size ) {
305 if ( empty( $size ) ) {
306 return 0;
307 }
308
309 $base = log( $size ) / log( 1024 );
310 $suffix = array( '', 'KB', 'MB', 'GB', 'TB' );
311 $f_base = floor( $base );
312
313 return round( pow( 1024, $base - floor( $base ) ), 1 ) . $suffix[ $f_base ];
314 }
315
316 /**
317 * Return singleton instance of class
318 *
319 * @return self
320 * @since 3.2
321 */
322 public static function factory() {
323 static $instance = false;
324
325 if ( ! $instance ) {
326 $instance = new self();
327 }
328
329 return $instance;
330 }
331 }
332