Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Search_Performance_Logger
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 9
342
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 begin_log_query
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 log_mysql_query
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 log_jetpack_search_query
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 reset_query_state
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 should_log_query
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 record_query_time
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 print_stats
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3/**
4 * Measure the performance of Jetpack Search queries.
5 */
6class Jetpack_Search_Performance_Logger {
7    /**
8     * Jetpack_Search_Performance_Logger instance.
9     *
10     * @var null|Jetpack_Search_Performance_Logger
11     */
12    private static $instance = null;
13
14    /**
15     * WP_Query instance.
16     *
17     * @var null|WP_Query
18     */
19    private $current_query = null;
20
21    /**
22     * Time when the query was started.
23     *
24     * @var null|float
25     */
26    private $query_started = null;
27
28    /**
29     * Performance results.
30     *
31     * @var null|array
32     */
33    private $stats = null;
34
35    /**
36     * Initialize the class.
37     */
38    public static function init() {
39        if ( self::$instance === null ) {
40            self::$instance = new Jetpack_Search_Performance_Logger();
41        }
42
43        return self::$instance;
44    }
45
46    /**
47     * The constructor.
48     */
49    private function __construct() {
50        $this->stats = array();
51        add_action( 'pre_get_posts', array( $this, 'begin_log_query' ), 10, 1 );
52        add_action( 'did_jetpack_search_query', array( $this, 'log_jetpack_search_query' ) );
53        add_filter( 'found_posts', array( $this, 'log_mysql_query' ), 10, 2 );
54        add_action( 'wp_footer', array( $this, 'print_stats' ) );
55    }
56
57    /**
58     * Log the time when the query was started.
59     *
60     * @param WP_Query $query The query.
61     */
62    public function begin_log_query( $query ) {
63        if ( $this->should_log_query( $query ) ) {
64            $this->query_started = microtime( true );
65            $this->current_query = $query;
66        }
67    }
68
69    /**
70     * Record the time when an SQL query was completed.
71     *
72     * @param int      $found_posts The number of posts found.
73     * @param WP_Query $query       The WP_Query instance (passed by reference).
74     */
75    public function log_mysql_query( $found_posts, $query ) {
76        if ( $this->current_query === $query ) {
77            $duration = microtime( true ) - $this->query_started;
78            if ( $duration < 60 ) { // eliminate outliers, likely tracking errors.
79                $this->record_query_time( $duration, false );
80            }
81            $this->reset_query_state();
82        }
83
84        return $found_posts;
85    }
86
87    /**
88     * Log Jetpack Search query.
89     */
90    public function log_jetpack_search_query() {
91        $duration = microtime( true ) - $this->query_started;
92        if ( $duration < 60 ) { // eliminate outliers, likely tracking errors.
93            $this->record_query_time( $duration, true );
94        }
95        $this->reset_query_state();
96    }
97
98    /**
99     * Reset data after each log.
100     */
101    private function reset_query_state() {
102        $this->query_started = null;
103        $this->current_query = null;
104    }
105
106    /**
107     * Check if a query should be logged (a main query, or a jetpack search query).
108     *
109     * @param WP_Query $query The WP_Query instance.
110     */
111    private function should_log_query( $query ) {
112        return $query->is_main_query() && $query->is_search();
113    }
114
115    /**
116     * Record the time of a query.
117     *
118     * @param float $duration           The duration of the query.
119     * @param bool  $was_jetpack_search Was this a Jetpack Search query.
120     */
121    private function record_query_time( $duration, $was_jetpack_search ) {
122        $this->stats[] = array( $was_jetpack_search, (int) ( $duration * 1000 ) );
123    }
124
125    /**
126     * Print performance stats in the footer.
127     */
128    public function print_stats() {
129        $beacons = array();
130        if ( ! empty( $this->stats ) ) {
131            foreach ( $this->stats as $stat ) {
132                $search_type = $stat[0] ? 'es' : 'mysql';
133                $beacons[]   = "%22jetpack.search.{$search_type}.duration:{$stat[1]}|ms%22";
134            }
135
136            $encoded_json     = '{%22beacons%22:[' . implode( ',', $beacons ) . ']}';
137            $encoded_site_url = rawurlencode( site_url() );
138            $url              = "https://pixel.wp.com/boom.gif?v=0.9&u={$encoded_site_url}&json={$encoded_json}";
139            echo '<img src="' . esc_url( $url ) . '" width="1" height="1" style="display:none;" alt=""/>';
140        }
141    }
142}