From 11588603f9b1d9e48a1c620d15c3f88c9b48ddbf Mon Sep 17 00:00:00 2001
From: Stanislav Khromov
Date: Mon, 16 May 2016 01:42:33 +0200
Subject: [PATCH] Adding version 0.3
---
README.md | 2 -
load-wp-performance-profiler.php | 16 +
readme.txt | 105 +++
wp-performance-profiler/admin/database.php | 16 +
wp-performance-profiler/admin/index.php | 604 ++++++++++++++++++
wp-performance-profiler/admin/maintenance.php | 30 +
wp-performance-profiler/admin/settings.php | 100 +++
wp-performance-profiler/assets/css/admin.css | 224 +++++++
wp-performance-profiler/assets/js/admin.js | 105 +++
wp-performance-profiler/classes/helpers.php | 50 ++
.../classes/logger-advanced.php | 280 ++++++++
.../classes/logger-base.php | 99 +++
.../classes/logger-basic.php | 38 ++
.../classes/pagination.php | 55 ++
wp-performance-profiler/compatibility.php | 15 +
wp-performance-profiler/index.php | 120 ++++
wp-performance-profiler/must-run.php | 66 ++
wp-performance-profiler/upgrade.php | 167 +++++
wp-performance-profiler/util.php | 146 +++++
.../views/admin/database.php | 71 ++
.../views/admin/detail.php | 231 +++++++
wp-performance-profiler/views/admin/help.php | 64 ++
.../views/admin/maintenance.php | 22 +
.../views/admin/plugins.php | 41 ++
.../views/admin/requests.php | 82 +++
.../views/admin/settings.php | 7 +
.../views/results-advanced.php | 26 +
27 files changed, 2780 insertions(+), 2 deletions(-)
delete mode 100644 README.md
create mode 100644 load-wp-performance-profiler.php
create mode 100644 readme.txt
create mode 100644 wp-performance-profiler/admin/database.php
create mode 100644 wp-performance-profiler/admin/index.php
create mode 100644 wp-performance-profiler/admin/maintenance.php
create mode 100644 wp-performance-profiler/admin/settings.php
create mode 100644 wp-performance-profiler/assets/css/admin.css
create mode 100644 wp-performance-profiler/assets/js/admin.js
create mode 100644 wp-performance-profiler/classes/helpers.php
create mode 100644 wp-performance-profiler/classes/logger-advanced.php
create mode 100644 wp-performance-profiler/classes/logger-base.php
create mode 100644 wp-performance-profiler/classes/logger-basic.php
create mode 100644 wp-performance-profiler/classes/pagination.php
create mode 100644 wp-performance-profiler/compatibility.php
create mode 100644 wp-performance-profiler/index.php
create mode 100644 wp-performance-profiler/must-run.php
create mode 100644 wp-performance-profiler/upgrade.php
create mode 100644 wp-performance-profiler/util.php
create mode 100644 wp-performance-profiler/views/admin/database.php
create mode 100644 wp-performance-profiler/views/admin/detail.php
create mode 100644 wp-performance-profiler/views/admin/help.php
create mode 100644 wp-performance-profiler/views/admin/maintenance.php
create mode 100644 wp-performance-profiler/views/admin/plugins.php
create mode 100644 wp-performance-profiler/views/admin/requests.php
create mode 100644 wp-performance-profiler/views/admin/settings.php
create mode 100644 wp-performance-profiler/views/results-advanced.php
diff --git a/README.md b/README.md
deleted file mode 100644
index 0022a06..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# wp-performance-profiler
-A fork of WP Performance Profiler by interconnect/it in accordance to the GPL.
diff --git a/load-wp-performance-profiler.php b/load-wp-performance-profiler.php
new file mode 100644
index 0000000..4f5b92f
--- /dev/null
+++ b/load-wp-performance-profiler.php
@@ -0,0 +1,16 @@
+ Settings in the admin area and configure what level of logging you want
+5. For multisite networks, repeat steps 3 and 4 for every site in the network
+
+To install the WordPress Performance Profiler as a regular plugin, repeat the steps above, but copy the files into `wp-content/plugins` instead of `wp-content/mu-plugins`.
+
+To uninstall the plugin, assuming it's been installed as a must-use plugin:
+
+1. Go to Profiler > Maintenance and click uninstall - This will delete all custom tables and set a flag in the options table to not run the plugin any more
+
+OR
+
+1. Delete `load-wp-performance-profiler.php` and `wp-performance-profiler` from the `mu-plugins` directory
+2. Delete the database tables beginning `wp_profiler_` (prefix depending on setup)
+
+== Frequently Asked Questions ==
+
+= What benefit does this have over server level profiling? =
+
+Tools like the xdebug profiler are great, but they add a lot of overhead and are focused on individual requests, and also require setup at the server level, which you may not have access to. The WordPress Performance Profiler is optimised to work with WordPress with minimal setup. It's also focused on being lightweight and can help to spot trends in aggregate data.
+
+= Why does this plugin need to be installed as a must-use plugin instead of a regular one? =
+
+Although it's possible to run this plugin as a normal one instead of a must-use one, it would capture less data, and the data it does capture would be less accurate. This is because must-use plugins are loaded much earlier than normal ones, allowing the profiler to monitor the load time of all plugins, and not just ones loaded after it (which could be none).
+
+= Why does it create its own database tables instead of using the core WordPress tables? =
+
+As the plugin captures and stores a lot of information, it creates its own tables for best performance. This also has the benefit of not cluttering up the core WordPress tables, and makes it much easier for the plugin to clean up after itself.
+
+= Why is this not logging exactly 10% of requests? =
+
+The logging is based on probability, not absolute metrics. Although it would be possible to make the number of requests logged more accorate, this would add overhead to every single request, not just the ones being logged. Therefore, in the interest of performance, it will only log the request if the probably of a random number if less than or equal to your target. This will mean that sometimes you get slightly more or less than the target number of requests.
+
+= What's the difference between basic and advanced logging? =
+
+Basic logging is very light weight and will only capture the URL, total duration, amount of memory used, number of database queries, template used and the type of request. Advanced logging will also capture the execution time of each plugin down to the function level, as well as all the database queries.
+
+Basic logging is great to leave running in the background to get an idea of the overall performance of your site and spot trends, such as slow performing requests, templates or types of requests. Advanced logging is then great for drilling down in more detail to find out how the time is spent on those slow requests. In the requests table, advanced requests will have the link to view details in the actions column.
+
+= Can this be used on a production website? =
+
+Yes, although this plugin is primarily meant as a developer tool - so is most beneficial running on your local development environment and staging servers - it can be used in production. We've tried to minimise the overhead as much as possible, but any tool like this will innevitably carry an overhead in terms of performance and also database size.
+
+If you are going to run it in a production environment, it's recommended to set the advanced logging level to a very low number, and the basic level to a low-medium number. With a large amount of traffic, this will still capture a lot of data with minimal overhead. If you run it with higher values (especially for the advanced logging), you'll need to periodically purge the database in the maintenance tab.
+
+= Can I manually log requests =
+
+If you don't want to run the profiler all the time, or only want to log specific requests, you can manually log them by adding `?profiler` to the query string. This will enable the advanced profiler, but if you want just basic logging, add `?profiler=basic`.
+By using the query string, you will override any default settings.
+
+== Changelog ==
+
+= 0.3.0 =
+* Added ability to view plugins ordered by average duration, minimum duration, maximum duration and standard deviation
+* Ability to order plugin overview screen by overall, front-end back-end, admin or cron performance
+* Added more visual representation of details and time spent on detailed request view
+* Added all database queries to the detailed request page
+* Added best and worst performing requests for the same URL and template to the detailed request view
+* Security fix to prevent a potential XSS exploit
+
+= 0.2.3 =
+* Updated table columns to make information clearer
+* Updated request view to show that plugin information can be expanded
+* Ability to manually log requests by adding ?profiler to the query string
+* Added function level profiling information for the plugins section
+* Redesigned summary for detailed requests to make them clearer
+* Added ability to sort plugin details and show a variety of information
+* All logged requests can now click through for additional information
+* Detailed request view also shows best/worst response times for that URL and template
+
+= 0.2.2 =
+* Resolved numerous file path issues when running the plugin on Windows
+
+= 0.2 =
+* Initial public release
+
+= 0.1 =
+* Initial internal release
diff --git a/wp-performance-profiler/admin/database.php b/wp-performance-profiler/admin/database.php
new file mode 100644
index 0000000..ee70219
--- /dev/null
+++ b/wp-performance-profiler/admin/database.php
@@ -0,0 +1,16 @@
+get_col( "
+ SELECT DISTINCT plugin
+ FROM {$wpdb->prefix}profiler_queries
+ WHERE plugin != ''
+ ORDER BY plugin ASC
+ " );
+ }
+}
diff --git a/wp-performance-profiler/admin/index.php b/wp-performance-profiler/admin/index.php
new file mode 100644
index 0000000..5c3cd3a
--- /dev/null
+++ b/wp-performance-profiler/admin/index.php
@@ -0,0 +1,604 @@
+
+
+
Performance Profiler
+
+
+
+
+
+ get_active_tab();
+
+ switch( $tab ) {
+ case 'requests':
+ // Setup the database query
+ $results_query = "SELECT * FROM {$wpdb->prefix}profiler_requests WHERE 1 = 1";
+ $count_query = "SELECT COUNT(id) FROM {$wpdb->prefix}profiler_requests WHERE 1 = 1";
+ $values = array();
+
+ // Are we filtering by date?
+ if( ! empty( $_GET['date'] ) ) {
+ $results_query .= " AND timestamp > %d";
+ $count_query .= " AND timestamp > %d";
+ $values[] = strtotime( $_GET['date'] );
+ }
+
+ // Do we want to filter on the URL?
+ if( ! empty( $_GET['url'] ) ) {
+ $results_query .= " AND request LIKE %s";
+ $count_query .= " AND request LIKE %s";
+ $values[] = '%' . $_GET['url'] . '%';
+ }
+
+ // Do we want to filter just slow loading request?
+ if( ! empty( $_GET['duration'] ) ) {
+ $results_query .= " AND duration > %d";
+ $count_query .= " AND duration > %d";
+ $values[] = absint( $_GET['duration'] );
+ }
+
+ // Do we want to filter by memory consumption
+ if( ! empty( $_GET['memory'] ) ) {
+ $results_query .= " AND memory > %d";
+ $count_query .= " AND memory > %d";
+ $values[] = absint( $_GET['memory'] ) * 1024 * 1024;
+ }
+
+ // Are we filtering by number of database queries
+ if( ! empty( $_GET['queries'] ) ) {
+ $results_query .= " AND queries > %d";
+ $count_query .= " AND queries > %d";
+ $values[] = absint( $_GET['queries'] );
+ }
+
+ // Are we filtering by template?
+ if( ! empty( $_GET['template'] ) ) {
+ $results_query .= " AND template = %s";
+ $count_query .= " AND template = %s";
+ $values[] = $_GET['template'];
+ }
+
+ // Are we filtering by type?
+ if( ! empty( $_GET['type'] ) ) {
+ $results_query .= " AND type = %s";
+ $count_query .= " AND type = %s";
+ $values[] = $_GET['type'];
+ }
+
+ // Set the order
+ $results_query .= " ORDER BY id DESC";
+
+ // Add pagination
+ $results_query .= " LIMIT %d, 100";
+ $values[] = isset( $_GET['p'] ) ? ( $_GET['p'] - 1 ) * 100 : 0;
+
+ // Execute the query
+ $results_query = $wpdb->prepare( $results_query, $values );
+ $rows = $wpdb->get_results( $results_query );
+
+ // Get the total number of rows
+ if( count( $values) > 0 ) {
+ array_pop($values);
+ }
+ if( count( $values ) ) {
+ $count_query = $wpdb->prepare( $count_query, $values );
+ }
+
+ $total_rows = $wpdb->get_var( $count_query );
+
+ break;
+ case 'plugins':
+ $data = array();
+ $types = array( 'front', 'admin', 'ajax', 'cron' );
+ $showing = ! empty( $_GET['profiler_plugin_showing'] ) ? $_GET['profiler_plugin_showing'] : 'average';
+
+ // Set our queries depending on what we want to show
+ if( $showing === 'median' ) {
+ $query_average = "
+ SELECT functions.plugin, functions.function, STD(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ WHERE 1=1
+ GROUP BY plugin, function;
+ ";
+ $query_type = "
+ SELECT functions.plugin, functions.function, STD(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE 1=1
+ AND functions.request_id = requests.id
+ AND requests.type = '%s'
+ GROUP BY plugin, function;
+ ";
+ } else if( $showing === 'deviation' ) {
+ $query_average = "
+ SELECT functions.plugin, functions.function, STD(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ WHERE 1=1
+ GROUP BY plugin, function;
+ ";
+ $query_type = "
+ SELECT functions.plugin, functions.function, STD(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE 1=1
+ AND functions.request_id = requests.id
+ AND requests.type = '%s'
+ GROUP BY plugin, function;
+ ";
+ } else if( $showing === 'minimum' ) {
+ $query_average = "
+ SELECT functions.plugin, functions.function, MIN(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ WHERE 1=1
+ GROUP BY plugin, function;
+ ";
+ $query_type = "
+ SELECT functions.plugin, functions.function, MIN(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE 1=1
+ AND functions.request_id = requests.id
+ AND requests.type = '%s'
+ GROUP BY plugin, function;
+ ";
+ } else if( $showing === 'maximum' ) {
+ $query_average = "
+ SELECT functions.plugin, functions.function, MAX(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ WHERE 1=1
+ GROUP BY plugin, function;
+ ";
+ $query_type = "
+ SELECT functions.plugin, functions.function, MAX(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE 1=1
+ AND functions.request_id = requests.id
+ AND requests.type = '%s'
+ GROUP BY plugin, function;
+ ";
+ } else {
+ $query_average = "
+ SELECT functions.plugin, functions.function, SUM(functions.duration)/COUNT(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ WHERE 1=1
+ GROUP BY plugin, function;
+ ";
+ $query_type = "
+ SELECT functions.plugin, functions.function, SUM(functions.duration)/COUNT(functions.duration) AS duration
+ FROM {$wpdb->prefix}profiler_functions functions
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE 1=1
+ AND functions.request_id = requests.id
+ AND requests.type = '%s'
+ GROUP BY plugin, function;
+ ";
+ }
+
+ // Get and process the durations across all request types
+ $results = $wpdb->get_results( $query_average );
+
+ foreach( $results as $row ) {
+ // Check the plugin is set
+ if( ! isset( $data[ $row->plugin ] ) ) {
+ $data[ $row->plugin ] = array(
+ 'plugin' => $row->plugin,
+ 'duration' => array( 'average' => 0, 'front' => 0, 'admin' => 0, 'ajax' => 0, 'cron' => 0 ),
+ 'functions' => array(),
+ );
+ }
+
+ // Check the function is set
+ if( ! isset( $data[ $row->plugin ]['functions'][ $row->function ] ) ) {
+ $data[ $row->plugin ]['functions'][ $row->function ] = array( 'function' => $row->function );
+ }
+
+ // Set the average duration and increment the plugin timer
+ $data[ $row->plugin ]['functions'][ $row->function ]['average'] = $row->duration;
+ $data[ $row->plugin ]['duration']['average'] += $row->duration;
+ }
+
+ // Get and process the durations for eachrequest type
+ foreach( $types as $type ) {
+ $results = $wpdb->get_results( $wpdb->prepare( $query_type, $type ) );
+
+ foreach( $results as $row ) {
+ // Set the type specific duration and increment the plugin timer
+ $data[ $row->plugin ]['functions'][ $row->function ][ $type] = $row->duration;
+ $data[ $row->plugin ]['duration'][ $type] += $row->duration;
+ }
+ }
+
+ // What column are we sorting on
+ $sort_column = ! empty( $_GET['profiler_sort_by'] ) ? $_GET['profiler_sort_by'] : 'average';
+ $sort_order = ! empty( $_GET['profiler_sort_order'] ) ? $_GET['profiler_sort_order'] : 'desc';
+
+ // Sort the top level plugins
+ usort( $data, function( $a, $b ) use ( $sort_column, $sort_order ) {
+ if( $sort_order === 'asc' ) {
+ return $a['duration'][ $sort_column ] > $b['duration'][ $sort_column ];
+ } else {
+ return $a['duration'][ $sort_column ] < $b['duration'][ $sort_column ];
+ }
+ } );
+
+ // Order the nested functions
+ // If we don't have any data for a specific function, revert to the average
+ foreach( $data as $index => $plugin ) {
+ usort( $plugin['functions'], function( $a, $b ) use ( $sort_column, $sort_order ) {
+ if( $sort_order === 'asc' ) {
+ if( ! isset( $a[ $sort_column ] ) && ! isset( $b[ $sort_column ] ) ) {
+ return $a['average'] > $b['average'];
+ } else {
+ return $a[ $sort_column ] > $b[ $sort_column ];
+ }
+ } else {
+ if( ! isset( $a[ $sort_column ] ) && ! isset( $b[ $sort_column ] ) ) {
+ return $a['average'] < $b['average'];
+ } else {
+ return $a[ $sort_column ] < $b[ $sort_column ];
+ }
+ }
+ } );
+
+ $data[ $index ] = $plugin;
+ }
+
+ break;
+ case 'detail':
+ if( ! isset( $_GET['request_id'] ) ) break;
+
+ $request_id = absint( $_GET['request_id'] );
+
+ // Get basic request details
+ $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}profiler_requests WHERE id = %d", $request_id );
+ $request = $wpdb->get_row( $query );
+
+ // Get the more advanced details
+ $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}profiler_details WHERE request_id = %d", $request_id );
+ $details = $wpdb->get_row( $query );
+
+ $details->duration = $details->duration_core + $details->duration_themes + $details->duration_plugins + $details->duration_mu_plugins + $details->duration_database;
+
+ // Get all the function level stats from the database
+ $query = "SELECT * FROM {$wpdb->prefix}profiler_functions WHERE request_id = %d";
+ $query = $wpdb->prepare( $query, $request_id );
+ $rows = $wpdb->get_results( $query );
+
+ // Pre-process them so it's all in a nice, structured array
+ $plugins = array();
+
+ foreach( $rows as $row ) {
+ // Is this the first stat for this plugin?
+ // If so, we need to initialise the data structures
+ if( ! isset( $plugins[ $row->plugin ] ) ) {
+ $plugins[ $row->plugin ] = array(
+ 'plugin' => $row->plugin,
+ 'count' => 0,
+ 'duration' => 0,
+ 'functions' => array(),
+ );
+ }
+
+ $plugins[ $row->plugin ]['count'] += $row->count;
+ $plugins[ $row->plugin ]['duration'] += $row->duration;
+ $plugins[ $row->plugin ]['functions'][] = $row;
+ }
+
+ // And now sort everything so we have the slowest at the top
+ // Do this at both the plugin and function level
+ usort( $plugins, array( __NAMESPACE__ . '\Helpers', 'order' ) );
+
+ foreach( $plugins as $index => $row ) {
+ usort( $row['functions'], array( __NAMESPACE__ . '\Helpers', 'order' ) );
+
+ $plugins[ $index ] = $row;
+ }
+
+ // Get the payload data
+ $payload = maybe_unserialize( $request->payload );
+
+ // Get database queries
+ $results_query = "SELECT queries.request_id, queries.duration, queries.plugin, queries.the_query, requests.timestamp, requests.type
+ FROM {$wpdb->prefix}profiler_queries queries
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE queries.request_id = requests.id
+ AND request_id = %d
+ ORDER BY duration DESC";
+
+ // Execute the query
+ $results_query = $wpdb->prepare( $results_query, array( $request_id ) );
+ $database = $wpdb->get_results( $results_query );
+
+ // Get similar requests
+ // We need to get the top and bottom performing requests
+ // If it's a front end request (and has a template) we'll do the same for templates
+ // In both cases, we don't want duplicate rows, which could occur with small data sets
+ $similar_requests_bottom = $wpdb->get_results( $wpdb->prepare( "
+ SELECT id, timestamp, duration, memory, queries
+ FROM {$wpdb->prefix}profiler_requests
+ WHERE 1=1
+ AND request = %s
+ AND id != %d
+ ORDER BY duration DESC
+ LIMIT 5
+ ", $request->request, $request_id ) );
+
+ $bottom_ids = wp_list_pluck( $similar_requests_bottom, 'id' );
+ $bottom_ids_str = implode( ',', $bottom_ids );
+
+ $similar_requests_top = $wpdb->get_results( $wpdb->prepare( "
+ SELECT id, timestamp, duration, memory, queries
+ FROM {$wpdb->prefix}profiler_requests
+ WHERE 1=1
+ AND request = %s
+ AND id != %d " .
+ ( !empty( $bottom_ids_str ) ? "AND id NOT IN ({$bottom_ids_str}) " : "" ) .
+ "ORDER BY duration ASC
+ LIMIT 5
+ ", $request->request, $request_id ) );
+
+ if( ! empty( $request->template ) ) {
+ $similar_templates_bottom = $wpdb->get_results( $wpdb->prepare( "
+ SELECT id, timestamp, request, duration, memory, queries
+ FROM {$wpdb->prefix}profiler_requests
+ WHERE 1=1
+ AND template = %s
+ AND id != %d
+ ORDER BY duration DESC
+ LIMIT 5
+ ", $request->template, $request_id ) );
+
+ $bottom_ids = wp_list_pluck( $similar_requests_bottom, 'id' );
+ $bottom_ids_str = implode( ',', $bottom_ids );
+
+ $similar_templates_top = $wpdb->get_results( $wpdb->prepare( "
+ SELECT id, timestamp, request, duration, memory, queries
+ FROM {$wpdb->prefix}profiler_requests
+ WHERE 1=1
+ AND template = %s
+ AND id != %d " .
+ ( !empty( $bottom_ids_str ) ? "AND id NOT IN ({$bottom_ids_str}) " : "" ) .
+ "ORDER BY duration ASC
+ LIMIT 5
+ ", $request->template, $request_id ) );
+ }
+
+ break;
+ case 'database':
+ require_once ICIT_PERFORMANCE_PROFILER_DIR . 'admin/database.php';
+
+ // Setup the database queries
+ // We'll have one for the results and one for the count
+ $results_query = "SELECT queries.request_id, queries.duration, queries.plugin, queries.the_query, requests.timestamp, requests.type
+ FROM {$wpdb->prefix}profiler_queries queries
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE queries.request_id = requests.id";
+ $count_query = "SELECT COUNT(queries.request_id)
+ FROM {$wpdb->prefix}profiler_queries queries
+ JOIN {$wpdb->prefix}profiler_requests requests
+ WHERE queries.request_id = requests.id";
+ $values = array();
+
+ // Are we filtering by request?
+ if( ! empty( $_GET['request_id'] ) ) {
+ $results_query .= " AND request_id = %d";
+ $count_query .= " AND request_id = %d";
+ $values[] = absint( $_GET['request_id'] );
+ }
+
+ // Are we filtering by date?
+ if( ! empty( $_GET['date'] ) ) {
+ $results_query .= " AND timestamp > %d";
+ $count_query .= " AND timestamp > %d";
+ $values[] = strtotime( $_GET['date'] );
+ }
+
+ // Are we filtering by plugin?
+ if( ! empty( $_GET['plugin'] ) ) {
+ $results_query .= " AND plugin = %s";
+ $count_query .= " AND plugin = %s";
+ $values[] = $_GET['plugin'];
+ }
+
+ // Do we want to filter on the SQL query
+ if( ! empty( $_GET['the_query'] ) ) {
+ $results_query .= " AND the_query LIKE %s";
+ $count_query .= " AND the_query LIKE %s";
+ $values[] = '%' . $_GET['the_query'] . '%';
+ }
+
+ // Do we want to filter just slow loading request?
+ if( ! empty( $_GET['duration'] ) ) {
+ $results_query .= " AND queries.duration > %d";
+ $count_query .= " AND queries.duration > %d";
+ $values[] = absint( $_GET['duration'] );
+ }
+
+ // Are we filtering by type?
+ if( ! empty( $_GET['type'] ) ) {
+ $results_query .= " AND type = %s";
+ $count_query .= " AND type = %s";
+ $values[] = $_GET['type'];
+ }
+
+ // Set the order
+ // We want the slowest ones first
+ $results_query .= " ORDER BY duration DESC";
+
+ // Add pagination
+ $results_query .= " LIMIT %d, 100";
+ $values[] = isset( $_GET['p'] ) ? ( $_GET['p'] - 1 ) * 100 : 0;
+
+ // Execute the query
+ $results_query = $wpdb->prepare( $results_query, $values );
+ $rows = $wpdb->get_results( $results_query );
+
+ // Get the total number of rows
+ if( count( $values) > 0 ) {
+ array_pop($values);
+ }
+ if( count( $values ) ) {
+ $count_query = $wpdb->prepare( $count_query, $values );
+ }
+
+ $total_rows = $wpdb->get_var( $count_query );
+
+ break;
+ case 'maintenance':
+ if( isset( $_GET['action'] ) ) {
+ $action = $_GET['action'];
+
+ if( $action === 'uninstall' ) {
+
+ } else if( $action === 'delete' ) {
+ $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}profiler_functions" );
+ $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}profiler_plugins" );
+ $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}profiler_queries" );
+ $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}profiler_requests" );
+ }
+ }
+
+ break;
+ }
+
+ include ICIT_PERFORMANCE_PROFILER_DIR . 'views/admin/' . $tab . '.php';
+ ?>
+
+
+
+ get_active_tab() ? 'nav-tab-active' : '';
+ }
+
+ private function get_plugin_stats_by_request_type( $type ) {
+ global $wpdb;
+
+ if( $type === 'all' ) {
+ return $wpdb->get_results( "
+ SELECT plugin.plugin, ( SUM(plugin.duration) / COUNT(plugin.duration) ) AS duration
+ FROM {$wpdb->prefix}profiler_plugins plugin
+ WHERE 1 = 1
+ GROUP BY plugin.plugin
+ ORDER BY duration DESC
+ " );
+ } else {
+ return $wpdb->get_results( $wpdb->prepare( "
+ SELECT plugin.plugin, ( SUM(plugin.duration) / COUNT(plugin.duration) ) AS duration
+ FROM {$wpdb->prefix}profiler_plugins plugin
+ JOIN {$wpdb->prefix}profiler_requests request
+ WHERE 1 = 1
+ AND plugin.request_id = request.id
+ AND request.type = %s
+ GROUP BY plugin.plugin
+ ORDER BY duration DESC
+ ", $type ) );
+ }
+ }
+}
+Admin::instance();
diff --git a/wp-performance-profiler/admin/maintenance.php b/wp-performance-profiler/admin/maintenance.php
new file mode 100644
index 0000000..bac0282
--- /dev/null
+++ b/wp-performance-profiler/admin/maintenance.php
@@ -0,0 +1,30 @@
+query( "DROP TABLE {$wpdb->prefix}profiler_functions" );
+ $wpdb->query( "DROP TABLE {$wpdb->prefix}profiler_plugins" );
+ $wpdb->query( "DROP TABLE {$wpdb->prefix}profiler_queries" );
+ $wpdb->query( "DROP TABLE {$wpdb->prefix}profiler_requests" );
+
+ // Update the settings to say the plugin is disabled
+ // This is necessary because we can't conventionally disable the plugin
+ $settings = get_option( 'icit_performance_profiler', array() );
+ $settings['active'] = false;
+ update_option( 'icit_performance_profiler', $settings );
+
+ wp_redirect( admin_url( '/' ) );
+ exit;
+ }
+ }
+}
diff --git a/wp-performance-profiler/admin/settings.php b/wp-performance-profiler/admin/settings.php
new file mode 100644
index 0000000..88000c6
--- /dev/null
+++ b/wp-performance-profiler/admin/settings.php
@@ -0,0 +1,100 @@
+ 'basic_frequency',
+ 'name' => 'icit_performance_profiler[basic_frequency]',
+ 'description' => 'The frequency the basic logger should run as a percentage. For example, to run this on every hundredth request, set this to 1.',
+ ) );
+
+ add_settings_field( 'advanced_frequency', 'Advanced Logging Frequency', array( $this, 'render_numeric' ), 'icit-profiler', 'icit-profiler', array(
+ 'id' => 'advanced_frequency',
+ 'name' => 'icit_performance_profiler[advanced_frequency]',
+ 'description' => 'The frequency the advanced logger should run as a percentage. For example, to run this on every hundredth request, set this to 1.',
+ ) );
+
+ add_settings_field( 'request_types', 'Request Types', array( $this, 'render_checkbox_list' ), 'icit-profiler', 'icit-profiler', array(
+ 'id' => 'request_types',
+ 'name' => 'icit_performance_profiler[request_types]',
+ 'description' => 'What request types should logging occur on?',
+ 'options' => array(
+ array(
+ 'id' => 'front',
+ 'label' => 'Front',
+ 'name' => 'icit_performance_profiler[request_types][front]',
+ ),
+ array(
+ 'id' => 'admin',
+ 'label' => 'Admin',
+ 'name' => 'icit_performance_profiler[request_types][admin]',
+ ),
+ array(
+ 'id' => 'cron',
+ 'label' => 'Cron',
+ 'name' => 'icit_performance_profiler[request_types][cron]',
+ ),
+ array(
+ 'id' => 'ajax',
+ 'label' => 'AJAX',
+ 'name' => 'icit_performance_profiler[request_types][ajax]',
+ ),
+ ),
+ ) );
+ }
+
+ public function render_numeric( $args ) {
+ $settings = get_option( 'icit_performance_profiler' );
+ $value = !empty( $settings[ $args['id'] ] ) ? $settings[ $args['id'] ] : '';
+ ?>
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+ f;f++)if("0"===m[0]&&1f&&(m=h,f=l)):"0"===g[k]&&(r=!0,h=k,l=1);l>f&&(m=h,f=l);1=c&&h>>10&1023|55296),b=56320|b&1023);return e+=A(b)}).join("")}function y(b,
+e){return b+22+75*(26>b)-((0!=e)<<5)}function p(b,e,h){var a=0;b=h?q(b/700):b>>1;for(b+=q(b/e);455w&&(w=0);for(x=0;x=h&&l("invalid-input");f=b.charCodeAt(w++);f=10>f-48?f-22:26>f-65?f-65:26>f-97?f-97:36;(36<=f||f>q((2147483647-c)/a))&&l("overflow");c+=f*a;m=
+g<=t?1:g>=t+26?26:g-t;if(fq(2147483647/f)&&l("overflow");a*=f}a=e.length+1;t=p(c-x,a,0==x);q(c/a)>2147483647-d&&l("overflow");d+=q(c/a);c%=a;e.splice(c++,0,d)}return k(e)}function r(e){var h,g,a,c,d,t,w,x,f,m=[],r,k,n;e=b(e);r=e.length;h=128;g=0;d=72;for(t=0;tf&&m.push(A(f));for((a=c=m.length)&&m.push("-");a=h&&fq((2147483647-g)/k)&&l("overflow");g+=(w-h)*k;h=w;for(t=0;t=d+26?26:w-d;if(x= 0x80 (not a basic code point)",
+"invalid-input":"Invalid input"},q=Math.floor,A=String.fromCharCode,D;s={version:"1.2.3",ucs2:{decode:b,encode:k},decode:h,encode:r,toASCII:function(b){return m(b,function(b){return e.test(b)?"xn--"+r(b):b})},toUnicode:function(b){return m(b,function(b){return n.test(b)?h(b.slice(4).toLowerCase()):b})}};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define(function(){return s});else if(B&&!B.nodeType)if(C)C.exports=s;else for(D in s)s.hasOwnProperty(D)&&(B[D]=s[D]);else f.punycode=
+s})(this);
+(function(f,l){"object"===typeof exports?module.exports=l():"function"===typeof define&&define.amd?define(l):f.SecondLevelDomains=l(f)})(this,function(f){var l=f&&f.SecondLevelDomains,g={list:{ac:" com gov mil net org ",ae:" ac co gov mil name net org pro sch ",af:" com edu gov net org ",al:" com edu gov mil net org ",ao:" co ed gv it og pb ",ar:" com edu gob gov int mil net org tur ",at:" ac co gv or ",au:" asn com csiro edu gov id net org ",ba:" co com edu gov mil net org rs unbi unmo unsa untz unze ",bb:" biz co com edu gov info net org store tv ",
+bh:" biz cc com edu gov info net org ",bn:" com edu gov net org ",bo:" com edu gob gov int mil net org tv ",br:" adm adv agr am arq art ato b bio blog bmd cim cng cnt com coop ecn edu eng esp etc eti far flog fm fnd fot fst g12 ggf gov imb ind inf jor jus lel mat med mil mus net nom not ntr odo org ppg pro psc psi qsl rec slg srv tmp trd tur tv vet vlog wiki zlg ",bs:" com edu gov net org ",bz:" du et om ov rg ",ca:" ab bc mb nb nf nl ns nt nu on pe qc sk yk ",ck:" biz co edu gen gov info net org ",
+cn:" ac ah bj com cq edu fj gd gov gs gx gz ha hb he hi hl hn jl js jx ln mil net nm nx org qh sc sd sh sn sx tj tw xj xz yn zj ",co:" com edu gov mil net nom org ",cr:" ac c co ed fi go or sa ",cy:" ac biz com ekloges gov ltd name net org parliament press pro tm ","do":" art com edu gob gov mil net org sld web ",dz:" art asso com edu gov net org pol ",ec:" com edu fin gov info med mil net org pro ",eg:" com edu eun gov mil name net org sci ",er:" com edu gov ind mil net org rochest w ",es:" com edu gob nom org ",
+et:" biz com edu gov info name net org ",fj:" ac biz com info mil name net org pro ",fk:" ac co gov net nom org ",fr:" asso com f gouv nom prd presse tm ",gg:" co net org ",gh:" com edu gov mil org ",gn:" ac com gov net org ",gr:" com edu gov mil net org ",gt:" com edu gob ind mil net org ",gu:" com edu gov net org ",hk:" com edu gov idv net org ",hu:" 2000 agrar bolt casino city co erotica erotika film forum games hotel info ingatlan jogasz konyvelo lakas media news org priv reklam sex shop sport suli szex tm tozsde utazas video ",
+id:" ac co go mil net or sch web ",il:" ac co gov idf k12 muni net org ","in":" ac co edu ernet firm gen gov i ind mil net nic org res ",iq:" com edu gov i mil net org ",ir:" ac co dnssec gov i id net org sch ",it:" edu gov ",je:" co net org ",jo:" com edu gov mil name net org sch ",jp:" ac ad co ed go gr lg ne or ",ke:" ac co go info me mobi ne or sc ",kh:" com edu gov mil net org per ",ki:" biz com de edu gov info mob net org tel ",km:" asso com coop edu gouv k medecin mil nom notaires pharmaciens presse tm veterinaire ",
+kn:" edu gov net org ",kr:" ac busan chungbuk chungnam co daegu daejeon es gangwon go gwangju gyeongbuk gyeonggi gyeongnam hs incheon jeju jeonbuk jeonnam k kg mil ms ne or pe re sc seoul ulsan ",kw:" com edu gov net org ",ky:" com edu gov net org ",kz:" com edu gov mil net org ",lb:" com edu gov net org ",lk:" assn com edu gov grp hotel int ltd net ngo org sch soc web ",lr:" com edu gov net org ",lv:" asn com conf edu gov id mil net org ",ly:" com edu gov id med net org plc sch ",ma:" ac co gov m net org press ",
+mc:" asso tm ",me:" ac co edu gov its net org priv ",mg:" com edu gov mil nom org prd tm ",mk:" com edu gov inf name net org pro ",ml:" com edu gov net org presse ",mn:" edu gov org ",mo:" com edu gov net org ",mt:" com edu gov net org ",mv:" aero biz com coop edu gov info int mil museum name net org pro ",mw:" ac co com coop edu gov int museum net org ",mx:" com edu gob net org ",my:" com edu gov mil name net org sch ",nf:" arts com firm info net other per rec store web ",ng:" biz com edu gov mil mobi name net org sch ",
+ni:" ac co com edu gob mil net nom org ",np:" com edu gov mil net org ",nr:" biz com edu gov info net org ",om:" ac biz co com edu gov med mil museum net org pro sch ",pe:" com edu gob mil net nom org sld ",ph:" com edu gov i mil net ngo org ",pk:" biz com edu fam gob gok gon gop gos gov net org web ",pl:" art bialystok biz com edu gda gdansk gorzow gov info katowice krakow lodz lublin mil net ngo olsztyn org poznan pwr radom slupsk szczecin torun warszawa waw wroc wroclaw zgora ",pr:" ac biz com edu est gov info isla name net org pro prof ",
+ps:" com edu gov net org plo sec ",pw:" belau co ed go ne or ",ro:" arts com firm info nom nt org rec store tm www ",rs:" ac co edu gov in org ",sb:" com edu gov net org ",sc:" com edu gov net org ",sh:" co com edu gov net nom org ",sl:" com edu gov net org ",st:" co com consulado edu embaixada gov mil net org principe saotome store ",sv:" com edu gob org red ",sz:" ac co org ",tr:" av bbs bel biz com dr edu gen gov info k12 name net org pol tel tsk tv web ",tt:" aero biz cat co com coop edu gov info int jobs mil mobi museum name net org pro tel travel ",
+tw:" club com ebiz edu game gov idv mil net org ",mu:" ac co com gov net or org ",mz:" ac co edu gov org ",na:" co com ",nz:" ac co cri geek gen govt health iwi maori mil net org parliament school ",pa:" abo ac com edu gob ing med net nom org sld ",pt:" com edu gov int net nome org publ ",py:" com edu gov mil net org ",qa:" com edu gov mil net org ",re:" asso com nom ",ru:" ac adygeya altai amur arkhangelsk astrakhan bashkiria belgorod bir bryansk buryatia cbg chel chelyabinsk chita chukotka chuvashia com dagestan e-burg edu gov grozny int irkutsk ivanovo izhevsk jar joshkar-ola kalmykia kaluga kamchatka karelia kazan kchr kemerovo khabarovsk khakassia khv kirov koenig komi kostroma kranoyarsk kuban kurgan kursk lipetsk magadan mari mari-el marine mil mordovia mosreg msk murmansk nalchik net nnov nov novosibirsk nsk omsk orenburg org oryol penza perm pp pskov ptz rnd ryazan sakhalin samara saratov simbirsk smolensk spb stavropol stv surgut tambov tatarstan tom tomsk tsaritsyn tsk tula tuva tver tyumen udm udmurtia ulan-ude vladikavkaz vladimir vladivostok volgograd vologda voronezh vrn vyatka yakutia yamal yekaterinburg yuzhno-sakhalinsk ",
+rw:" ac co com edu gouv gov int mil net ",sa:" com edu gov med net org pub sch ",sd:" com edu gov info med net org tv ",se:" a ac b bd c d e f g h i k l m n o org p parti pp press r s t tm u w x y z ",sg:" com edu gov idn net org per ",sn:" art com edu gouv org perso univ ",sy:" com edu gov mil net news org ",th:" ac co go in mi net or ",tj:" ac biz co com edu go gov info int mil name net nic org test web ",tn:" agrinet com defense edunet ens fin gov ind info intl mincom nat net org perso rnrt rns rnu tourism ",
+tz:" ac co go ne or ",ua:" biz cherkassy chernigov chernovtsy ck cn co com crimea cv dn dnepropetrovsk donetsk dp edu gov if in ivano-frankivsk kh kharkov kherson khmelnitskiy kiev kirovograd km kr ks kv lg lugansk lutsk lviv me mk net nikolaev od odessa org pl poltava pp rovno rv sebastopol sumy te ternopil uzhgorod vinnica vn zaporizhzhe zhitomir zp zt ",ug:" ac co go ne or org sc ",uk:" ac bl british-library co cym gov govt icnet jet lea ltd me mil mod national-library-scotland nel net nhs nic nls org orgn parliament plc police sch scot soc ",
+us:" dni fed isa kids nsn ",uy:" com edu gub mil net org ",ve:" co com edu gob info mil net org web ",vi:" co com k12 net org ",vn:" ac biz com edu gov health info int name net org pro ",ye:" co com gov ltd me net org plc ",yu:" ac co edu gov org ",za:" ac agric alt bourse city co cybernet db edu gov grondar iaccess imt inca landesign law mil net ngo nis nom olivetti org pix school tm web ",zm:" ac co com edu gov net org sch "},has:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return!1;
+var k=f.lastIndexOf(".",b-1);if(0>=k||k>=b-1)return!1;var l=g.list[f.slice(b+1)];return l?0<=l.indexOf(" "+f.slice(k+1,b)+" "):!1},is:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1||0<=f.lastIndexOf(".",b-1))return!1;var k=g.list[f.slice(b+1)];return k?0<=k.indexOf(" "+f.slice(0,b)+" "):!1},get:function(f){var b=f.lastIndexOf(".");if(0>=b||b>=f.length-1)return null;var k=f.lastIndexOf(".",b-1);if(0>=k||k>=b-1)return null;var l=g.list[f.slice(b+1)];return!l||0>l.indexOf(" "+f.slice(k+
+1,b)+" ")?null:f.slice(k+1)},noConflict:function(){f.SecondLevelDomains===this&&(f.SecondLevelDomains=l);return this}};return g});
+(function(f,l){"object"===typeof exports?module.exports=l(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):"function"===typeof define&&define.amd?define(["./punycode","./IPv6","./SecondLevelDomains"],l):f.URI=l(f.punycode,f.IPv6,f.SecondLevelDomains,f)})(this,function(f,l,g,m){function b(a,c){if(!(this instanceof b))return new b(a,c);void 0===a&&(a="undefined"!==typeof location?location.href+"":"");this.href(a);return void 0!==c?this.absoluteTo(c):this}function k(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,
+"\\$1")}function y(a){return void 0===a?"Undefined":String(Object.prototype.toString.call(a)).slice(8,-1)}function p(a){return"Array"===y(a)}function h(a,c){var d,b;if(p(c)){d=0;for(b=c.length;d]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;b.findUri={start:/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,end:/[\s\r\n]|$/,trim:/[`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u201e\u2018\u2019]+$/};b.defaultPorts={http:"80",https:"443",ftp:"21",gopher:"70",ws:"80",wss:"443"};b.invalid_hostname_characters=
+/[^a-zA-Z0-9\.-]/;b.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src",audio:"src",video:"src"};b.getDomAttribute=function(a){if(a&&a.nodeName){var c=a.nodeName.toLowerCase();return"input"===c&&"image"!==a.type?void 0:b.domAttributes[c]}};b.encode=C;b.decode=decodeURIComponent;b.iso8859=function(){b.encode=escape;b.decode=unescape};b.unicode=function(){b.encode=C;b.decode=
+decodeURIComponent};b.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@","%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",",
+"%3B":";","%3D":"="}}}};b.encodeQuery=function(a,c){var d=b.encode(a+"");void 0===c&&(c=b.escapeQuerySpace);return c?d.replace(/%20/g,"+"):d};b.decodeQuery=function(a,c){a+="";void 0===c&&(c=b.escapeQuerySpace);try{return b.decode(c?a.replace(/\+/g,"%20"):a)}catch(d){return a}};b.recodePath=function(a){a=(a+"").split("/");for(var c=0,d=a.length;cb)return a.charAt(0)===c.charAt(0)&&"/"===a.charAt(0)?"/":"";if("/"!==a.charAt(b)||"/"!==c.charAt(b))b=a.substring(0,b).lastIndexOf("/");return a.substring(0,b+1)};b.withinString=function(a,c,d){d||(d={});var e=d.start||b.findUri.start,f=d.end||b.findUri.end,h=d.trim||b.findUri.trim,g=/[a-z0-9-]=["']?$/i;for(e.lastIndex=0;;){var r=e.exec(a);if(!r)break;r=r.index;if(d.ignoreHtml){var k=
+a.slice(Math.max(r-3,0),r);if(k&&g.test(k))continue}var k=r+a.slice(r).search(f),m=a.slice(r,k).replace(h,"");d.ignore&&d.ignore.test(m)||(k=r+m.length,m=c(m,r,k,a),a=a.slice(0,r)+m+a.slice(k),e.lastIndex=r+m.length)}e.lastIndex=0;return a};b.ensureValidHostname=function(a){if(a.match(b.invalid_hostname_characters)){if(!f)throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-] and Punycode.js is not available');if(f.toASCII(a).match(b.invalid_hostname_characters))throw new TypeError('Hostname "'+
+a+'" contains characters other than [A-Z0-9.-]');}};b.noConflict=function(a){if(a)return a={URI:this.noConflict()},m.URITemplate&&"function"===typeof m.URITemplate.noConflict&&(a.URITemplate=m.URITemplate.noConflict()),m.IPv6&&"function"===typeof m.IPv6.noConflict&&(a.IPv6=m.IPv6.noConflict()),m.SecondLevelDomains&&"function"===typeof m.SecondLevelDomains.noConflict&&(a.SecondLevelDomains=m.SecondLevelDomains.noConflict()),a;m.URI===this&&(m.URI=n);return this};e.build=function(a){if(!0===a)this._deferred_build=
+!0;else if(void 0===a||this._deferred_build)this._string=b.build(this._parts),this._deferred_build=!1;return this};e.clone=function(){return new b(this)};e.valueOf=e.toString=function(){return this.build(!1)._string};e.protocol=z("protocol");e.username=z("username");e.password=z("password");e.hostname=z("hostname");e.port=z("port");e.query=s("query","?");e.fragment=s("fragment","#");e.search=function(a,c){var d=this.query(a,c);return"string"===typeof d&&d.length?"?"+d:d};e.hash=function(a,c){var d=
+this.fragment(a,c);return"string"===typeof d&&d.length?"#"+d:d};e.pathname=function(a,c){if(void 0===a||!0===a){var d=this._parts.path||(this._parts.hostname?"/":"");return a?b.decodePath(d):d}this._parts.path=a?b.recodePath(a):"/";this.build(!c);return this};e.path=e.pathname;e.href=function(a,c){var d;if(void 0===a)return this.toString();this._string="";this._parts=b._parts();var e=a instanceof b,f="object"===typeof a&&(a.hostname||a.path||a.pathname);a.nodeName&&(f=b.getDomAttribute(a),a=a[f]||
+"",f=!1);!e&&f&&void 0!==a.pathname&&(a=a.toString());if("string"===typeof a||a instanceof String)this._parts=b.parse(String(a),this._parts);else if(e||f)for(d in e=e?a._parts:a,e)u.call(this._parts,d)&&(this._parts[d]=e[d]);else throw new TypeError("invalid input");this.build(!c);return this};e.is=function(a){var c=!1,d=!1,e=!1,f=!1,h=!1,r=!1,k=!1,m=!this._parts.urn;this._parts.hostname&&(m=!1,d=b.ip4_expression.test(this._parts.hostname),e=b.ip6_expression.test(this._parts.hostname),c=d||e,h=(f=
+!c)&&g&&g.has(this._parts.hostname),r=f&&b.idn_expression.test(this._parts.hostname),k=f&&b.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return m;case "absolute":return!m;case "domain":case "name":return f;case "sld":return h;case "ip":return c;case "ip4":case "ipv4":case "inet4":return d;case "ip6":case "ipv6":case "inet6":return e;case "idn":return r;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return k}return null};
+var D=e.protocol,E=e.port,F=e.hostname;e.protocol=function(a,c){if(void 0!==a&&a&&(a=a.replace(/:(\/\/)?$/,""),!a.match(b.protocol_expression)))throw new TypeError('Protocol "'+a+"\" contains characters other than [A-Z0-9.+-] or doesn't start with [A-Z]");return D.call(this,a,c)};e.scheme=e.protocol;e.port=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a&&(0===a&&(a=null),a&&(a+="",":"===a.charAt(0)&&(a=a.substring(1)),a.match(/[^0-9]/))))throw new TypeError('Port "'+a+'" contains characters other than [0-9]');
+return E.call(this,a,c)};e.hostname=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a){var d={};b.parseHost(a,d);a=d.hostname}return F.call(this,a,c)};e.host=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?b.buildHost(this._parts):"";b.parseHost(a,this._parts);this.build(!c);return this};e.authority=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?b.buildAuthority(this._parts):
+"";b.parseAuthority(a,this._parts);this.build(!c);return this};e.userinfo=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.username)return"";var d=b.buildUserinfo(this._parts);return d.substring(0,d.length-1)}"@"!==a[a.length-1]&&(a+="@");b.parseUserinfo(a,this._parts);this.build(!c);return this};e.resource=function(a,c){var d;if(void 0===a)return this.path()+this.search()+this.hash();d=b.parse(a);this._parts.path=d.path;this._parts.query=d.query;this._parts.fragment=
+d.fragment;this.build(!c);return this};e.subdomain=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.length-this.domain().length-1;return this._parts.hostname.substring(0,d)||""}d=this._parts.hostname.length-this.domain().length;d=this._parts.hostname.substring(0,d);d=new RegExp("^"+k(d));a&&"."!==a.charAt(a.length-1)&&(a+=".");a&&b.ensureValidHostname(a);this._parts.hostname=this._parts.hostname.replace(d,
+a);this.build(!c);return this};e.domain=function(a,c){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.match(/\./g);if(d&&2>d.length)return this._parts.hostname;d=this._parts.hostname.length-this.tld(c).length-1;d=this._parts.hostname.lastIndexOf(".",d-1)+1;return this._parts.hostname.substring(d)||""}if(!a)throw new TypeError("cannot set domain empty");b.ensureValidHostname(a);
+!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(d=new RegExp(k(this.domain())+"$"),this._parts.hostname=this._parts.hostname.replace(d,a));this.build(!c);return this};e.tld=function(a,c){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(c=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var d=this._parts.hostname.lastIndexOf("."),d=this._parts.hostname.substring(d+1);return!0!==c&&g&&g.list[d.toLowerCase()]?g.get(this._parts.hostname)||d:d}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(g&&
+g.is(a))d=new RegExp(k(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(d,a);else throw new TypeError('TLD "'+a+'" contains characters other than [A-Z0-9]');else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");d=new RegExp(k(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(d,a)}else throw new TypeError("cannot set TLD empty");this.build(!c);return this};e.directory=function(a,c){if(this._parts.urn)return void 0===
+a?"":this;if(void 0===a||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var d=this._parts.path.length-this.filename().length-1,d=this._parts.path.substring(0,d)||(this._parts.hostname?"/":"");return a?b.decodePath(d):d}d=this._parts.path.length-this.filename().length;d=this._parts.path.substring(0,d);d=new RegExp("^"+k(d));this.is("relative")||(a||(a="/"),"/"!==a.charAt(0)&&(a="/"+a));a&&"/"!==a.charAt(a.length-1)&&(a+="/");a=b.recodePath(a);this._parts.path=
+this._parts.path.replace(d,a);this.build(!c);return this};e.filename=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this._parts.path.lastIndexOf("/"),d=this._parts.path.substring(d+1);return a?b.decodePathSegment(d):d}d=!1;"/"===a.charAt(0)&&(a=a.substring(1));a.match(/\.?\//)&&(d=!0);var e=new RegExp(k(this.filename())+"$");a=b.recodePath(a);this._parts.path=this._parts.path.replace(e,a);d?this.normalizePath(c):
+this.build(!c);return this};e.suffix=function(a,c){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var d=this.filename(),e=d.lastIndexOf(".");if(-1===e)return"";d=d.substring(e+1);d=/^[a-z0-9%]+$/i.test(d)?d:"";return a?b.decodePathSegment(d):d}"."===a.charAt(0)&&(a=a.substring(1));if(d=this.suffix())e=a?new RegExp(k(d)+"$"):new RegExp(k("."+d)+"$");else{if(!a)return this;this._parts.path+="."+b.recodePath(a)}e&&(a=b.recodePath(a),
+this._parts.path=this._parts.path.replace(e,a));this.build(!c);return this};e.segment=function(a,c,d){var b=this._parts.urn?":":"/",e=this.path(),f="/"===e.substring(0,1),e=e.split(b);void 0!==a&&"number"!==typeof a&&(d=c,c=a,a=void 0);if(void 0!==a&&"number"!==typeof a)throw Error('Bad segment "'+a+'", must be 0-based integer');f&&e.shift();0>a&&(a=Math.max(e.length+a,0));if(void 0===c)return void 0===a?e:e[a];if(null===a||void 0===e[a])if(p(c)){e=[];a=0;for(var h=c.length;aduration < $b->duration;
+ } else {
+ return $a['duration'] < $b['duration'];
+ }
+ }
+
+ public static function human_filesize( $bytes, $decimals = 2 ) {
+ $size = array('B','KB','MB','GB','TB','PB','EB','ZB','YB');
+ $factor = floor((strlen($bytes) - 1) / 3);
+
+ return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $size[$factor];
+ }
+
+ public static function pagination_link( $page ) {
+ $url = $_SERVER['SCRIPT_NAME'];
+ $args = $_GET;
+ $args['p'] = $page;
+ $querystring = http_build_query( $args );
+
+ return $url . '?' . $querystring;
+ }
+
+ public static function querystring_value( $key, $default = '' ) {
+ return isset( $_GET[ $key ] ) ? $_GET[ $key ] : $default;
+ }
+
+ public static function has_details( $request_id ) {
+ global $wpdb;
+
+ static $requests;
+
+ if( $requests === null ) {
+ $requests = $wpdb->get_col( "
+ SELECT DISTINCT request_id
+ FROM {$wpdb->prefix}profiler_functions"
+ );
+
+ $requests = array_flip( $requests );
+ }
+
+ return isset( $requests[ $request_id ] );
+ }
+}
diff --git a/wp-performance-profiler/classes/logger-advanced.php b/wp-performance-profiler/classes/logger-advanced.php
new file mode 100644
index 0000000..609cbb6
--- /dev/null
+++ b/wp-performance-profiler/classes/logger-advanced.php
@@ -0,0 +1,280 @@
+time = microtime( true ) * 1000;
+ $this->content_dir = wp_normalize_path( WP_CONTENT_DIR );
+ $this->theme_dir = get_theme_root();
+ $this->plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
+ $this->mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
+
+ declare( ticks = 1 );
+ register_tick_function( array( $this, 'do_tick' ) );
+
+ if( ! defined( 'SAVEQUERIES') ) {
+ define( 'SAVEQUERIES', true );
+ }
+
+ add_filter( 'query', array( $this, 'log_query' ) );
+ }
+
+ /**
+ * Master function which records all the stats
+ *
+ * This is called once for every tick (not including core PHP)
+ * And we log metrics between ticks to get an idea of stats
+ */
+ function do_tick() {
+ // Get the current trace
+ $trace = debug_backtrace();
+
+ // Ensure we have some data to work with
+ // Check the second slot as this is the first one in the trace
+ if( ! isset( $trace[1]['file'] ) ) return;
+
+ // Get all the information we need
+ $now = microtime( true ) * 1000;
+ $file = wp_normalize_path( $trace[1]['file'] );
+ $function = $trace[1]['function'];
+ $line = $trace[1]['line'];
+ $duration = $now - $this->time;
+
+ // If the file and function are the same as the last ones
+ // Ignore this tick, as it's (most likely) the same function call
+ if( $file === $this->last_file && $function === $this->last_function ) return;
+
+ // We can safely update the class variables now
+ $this->last_file = $file;
+ $this->last_function = $function;
+ $this->time = $now;
+
+ // Determine if this tick is core, themes, plugins or mu-plugins
+ $is_theme = strpos( $file, $this->theme_dir ) !== false;
+ $is_plugin = strpos( $file, $this->plugin_dir ) !== false;
+ $is_muplugin = strpos( $file, $this->mu_plugin_dir ) !== false;
+ $is_core = ! $is_theme && ! $is_plugin && ! $is_muplugin;
+
+ // Update the running tally for how long this type of request took
+ if( $is_theme ) $this->time_theme += $duration;
+ if( $is_plugin ) $this->time_plugin += $duration;
+ if( $is_muplugin ) $this->time_muplugin += $duration;
+ if( $is_core ) $this->time_core += $duration;
+
+ // At this point, we're no longer interested in measuring core
+ // Crucially, we've updated the time, so future time logs will be accurate
+ if( $is_core ) return;
+
+ // What plugin is this part of?
+ // $1 - One of themes, plugins, mu-plugins etc
+ // $2 - The folder name of the plugin
+ // $3 - The filename within the plugin
+ $regex = '#' . $this->content_dir . '/([^\/]+)/([^\/]+)/(.+)#';
+ $plugin = preg_replace( $regex, '$2', $file );
+
+ // If this plugin / function doesn't exist, initialise it
+ if( ! isset( $this->stats[ $plugin ] ) ) {
+ $this->stats[ $plugin ] = array( 'plugin' => $plugin, 'duration' => 0, 'count' => 0, 'functions' => array() );
+ }
+ if( ! isset( $this->stats[ $plugin ]['functions'][ $function ] ) ) {
+ $this->stats[ $plugin ]['functions'][ $function ] = array( 'function' => $function, 'duration' => 0, 'count' => 0 );
+ }
+
+ // Add the new stats
+ $this->stats[ $plugin ]['duration'] += $duration;
+ $this->stats[ $plugin ]['count'] += 1;
+
+ $this->stats[ $plugin ]['functions'][ $function ]['duration'] += $duration;
+ $this->stats[ $plugin ]['functions'][ $function ]['count'] += 1;
+
+ $this->stack[] = array(
+ 'file' => $file,
+ 'line' => $line,
+ 'function' => $function,
+ 'plugin' => $plugin,
+ 'duration' => $duration,
+ );
+ }
+
+ public function save() {
+ global $wpdb;
+
+ // First, we need to save the basic request, so we can link everything together
+ $request_id = $this->save_request();
+
+ // Next, we save top level plugin data
+ // This will be the plugin, runtime and call count
+ // Although this information could be computed from the function calls when doign reporting
+ // It would suffer a huge performance hit due to the large number of records there
+ // And this also allows for an intermediate level of logging in the future, where we just store plugin level stats
+ $table = $wpdb->prefix . 'profiler_plugins';
+ $schema = array(
+ 'request_id' => '%d',
+ 'plugin' => '%s',
+ 'count' => '%d',
+ 'duration' => '%f',
+ );
+ $data = array();
+
+ foreach( $this->stats as $plugin ) {
+ $data[] = array(
+ 'request_id' => $request_id,
+ 'plugin' => $plugin['plugin'],
+ 'count' => $plugin['count'],
+ 'duration' => $plugin['duration'],
+ );
+ }
+
+ $this->bulk_insert( $table, $schema, $data );
+
+ // We'll also store all the database queries and link them to this request
+ $table = $wpdb->prefix . 'profiler_queries';
+ $schema = array(
+ 'request_id' => '%d',
+ 'duration' => '%f',
+ 'plugin' => '%s',
+ 'the_query' => '%s',
+ 'stack' => '%s',
+ );
+ $data = array();
+ foreach( $wpdb->queries as $query ) {
+ $data[] = array(
+ 'request_id' => $request_id,
+ 'duration' => $query[1] * 1000,
+ 'plugin' => $this->get_plugin_from_query( $query[0] ),
+ 'the_query' => $query[0],
+ 'stack' => $query[2],
+ );
+
+ $this->time_database += $query[1] * 1000;
+ }
+
+ $this->bulk_insert( $table, $schema, $data );
+
+ // Then, we'll store the detailed logs for each function
+ $table = $wpdb->prefix . 'profiler_functions';
+ $schema = array(
+ 'request_id' => '%d',
+ 'plugin' => '%s',
+ 'function' => '%s',
+ 'count' => '%d',
+ 'duration' => '%f',
+ );
+ $data = array();
+
+ foreach( $this->stats as $plugin ) {
+ foreach( $plugin['functions'] as $function ) {
+ $data[] = array(
+ 'request_id' => $request_id,
+ 'plugin' => $plugin['plugin'],
+ 'function' => $function['function'],
+ 'count' => $function['count'],
+ 'duration' => $function['duration'],
+ );
+ }
+ }
+
+ $this->bulk_insert( $table, $schema, $data );
+
+ // Insert the detailed information for this request
+ $table = $wpdb->prefix . 'profiler_details';
+ $wpdb->insert( $table, array(
+ 'request_id' => $request_id,
+ 'duration_core' => $this->time_core,
+ 'duration_themes' => $this->time_theme,
+ 'duration_plugins' => $this->time_plugin,
+ 'duration_mu_plugins' => $this->time_muplugin,
+ 'duration_database' => $this->time_database,
+ ) );
+ }
+
+ public function render() {
+ $stats = $this->stats;
+
+ // Order the stats
+ // First, we order the function time for each plugin
+ // And then the plugins as a whole
+ foreach( $stats as $name => $plugin ) {
+ usort( $plugin['functions'], function( $a, $b ) {
+ return $a['duration'] < $b['duration'];
+ } );
+
+ $stats[ $name ] = $plugin;
+ }
+
+ usort( $stats, function( $a, $b ) {
+ return $a['duration'] < $b['duration'];
+ } );
+
+ include ICIT_PERFORMANCE_PROFILER_DIR . 'views/results-advanced.php';
+ }
+
+ public function log_query( $query ) {
+ // debug_backtrace() and $exception->getTrace() give different traces
+ // The exception route seems to give the actual call trace
+ $exception = new \Exception;
+ $trace = $exception->getTrace();
+ $type = 'core';
+ $source = '';
+
+ // Loop through all the included files until we find one that is either a theme or plugin
+ foreach( $trace as $point ) {
+ if( ! isset( $point['file'] ) ) continue;
+
+ if( strpos( $point['file'], $this->plugin_dir ) !== false ) {
+ $type = 'plugin';
+ $source = preg_replace( '#' . $this->plugin_dir . '/([^\/]+)/.+#', '$1', $point['file'] );
+ continue;
+ } else if( strpos( $point['file'], $this->theme_dir ) !== false ) {
+ $type = 'theme';
+ $source = preg_replace( '#' . $this->theme_dir . '/([^\/]+)/.+#', '$1', $point['file'] );
+ continue;
+ }
+ }
+
+ $this->queries[] = array(
+ 'query' => $query,
+ 'type' => $type,
+ 'source' => $source,
+ );
+
+ return $query;
+ }
+
+ private function get_plugin_from_query( $sql ) {
+ foreach( $this->queries as $query ) {
+ if( $query['query'] === $sql ) {
+ return $query['source'];
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/wp-performance-profiler/classes/logger-base.php b/wp-performance-profiler/classes/logger-base.php
new file mode 100644
index 0000000..4bf3cc6
--- /dev/null
+++ b/wp-performance-profiler/classes/logger-base.php
@@ -0,0 +1,99 @@
+start = microtime( true ) * 1000;
+
+ // We'll need to get the template file for later
+ add_filter( 'template_include', array( $this, 'get_template' ), 999 );
+ }
+
+ /**
+ * Save the basic request and return the request ID
+ *
+ * This will save the duration, memory usage, database queries, URL request, template and type
+ */
+ public function save_request() {
+ global $wpdb;
+
+ $table = $wpdb->prefix . 'profiler_requests';
+ $now = microtime( true ) * 1000;
+ $payload = ! empty( $_POST ) ? serialize( $_POST ) : '';
+ $data = array(
+ 'timestamp' => time(),
+ 'duration' => $now - $this->start,
+ 'memory' => memory_get_usage( true ),
+ 'queries' => get_num_queries(),
+ 'request' => $_SERVER['REQUEST_URI'],
+ 'template' => $this->template,
+ 'type' => icit_profiler_current_request_type(),
+ 'payload' => $payload,
+ );
+
+ $wpdb->insert( $table, $data );
+
+ return $wpdb->insert_id;
+ }
+
+ /**
+ * Allow for the bulk insertion for records into a single table in one query
+ *
+ * @param $table String - The name of the table to insert to (with prefix)
+ * @param $schema Array - Array of columns to insert, with the key as the column name as the value as the placeholder string (%s or %d)
+ * @param $data Array - Array of data arrays, with the same column keys as $schema
+ */
+ public function bulk_insert( $table, $schema, $data ) {
+ global $wpdb;
+
+ // Create the basic query structure
+ // We'll then create the other variables later
+ $query = "INSERT INTO %s (%s) VALUES %s;";
+
+ // Create the schema string
+ $columns = implode( ', ', array_keys( $schema ) );
+
+ // Loop through the data and setup the placeholders and values
+ // This is because we're going to use prepare() to replace them
+ $placeholders = array();
+ $values = array();
+
+ foreach( $data as $row ) {
+ $placeholders[] = '(' . implode( ', ', $schema ) . ')';
+
+ foreach( $row as $value ) {
+ $values[] = $value;
+ }
+ }
+
+ // Finish building the query string
+ $placeholders = implode( ', ', $placeholders );
+ $query = sprintf( $query, $table, $columns, $placeholders );
+ $query = $wpdb->prepare( $query, $values );
+
+ return $wpdb->query( $query );
+ }
+
+ /**
+ * Save the loaded template file for use later
+ *
+ * This will strip out the full path
+ */
+ public function get_template( $template ) {
+ $this->template = str_replace( get_template_directory(), '', $template );
+
+ return $template;
+ }
+}
diff --git a/wp-performance-profiler/classes/logger-basic.php b/wp-performance-profiler/classes/logger-basic.php
new file mode 100644
index 0000000..8e1bedc
--- /dev/null
+++ b/wp-performance-profiler/classes/logger-basic.php
@@ -0,0 +1,38 @@
+save_request();
+
+ // We need to save the duration of DB queries as well
+ $database_duration = 0;
+ foreach( $wpdb->queries as $query ) {
+ $database_duration += $query[1] * 1000;
+ }
+
+ $table = $wpdb->prefix . 'profiler_details';
+ $wpdb->insert( $table, array(
+ 'request_id' => $request_id,
+ 'duration_database' => $database_duration,
+ ) );
+ }
+
+ public function render() {
+
+ }
+}
diff --git a/wp-performance-profiler/classes/pagination.php b/wp-performance-profiler/classes/pagination.php
new file mode 100644
index 0000000..789da10
--- /dev/null
+++ b/wp-performance-profiler/classes/pagination.php
@@ -0,0 +1,55 @@
+';
+
+ // Do we need links before the current page - First, Previous and numeric?
+ if( $current > 1 ) {
+ echo '« ';
+ echo '‹ ';
+
+ for( $i = $previous_index; $i < $current; $i++ ) {
+ echo '' . $i . ' ';
+ }
+ }
+
+ // The current page link
+ echo '' . $current . ' ';
+
+ // Do we need links after the current page - Numeric links, Next and Last?
+ if( $current < $max ) {
+ for( $i = $current + 1; $i <= $next_index; $i++ ) {
+ echo '' . $i . ' ';
+ }
+
+ echo '› ';
+ echo '» ';
+ }
+
+ echo '';
+ }
+
+ public static function pagination_link( $page ) {
+ $url = $_SERVER['SCRIPT_NAME'];
+ $args = $_GET;
+ $args['p'] = $page;
+ $querystring = http_build_query( $args );
+
+ return $url . '?' . $querystring;
+ }
+}
diff --git a/wp-performance-profiler/compatibility.php b/wp-performance-profiler/compatibility.php
new file mode 100644
index 0000000..ec3cffd
--- /dev/null
+++ b/wp-performance-profiler/compatibility.php
@@ -0,0 +1,15 @@
+get_logging_level();
+
+ // If we're in the admin, do the core setup
+ // This is on top of optionally logging as well
+ if( is_admin() ) {
+ require_once ICIT_PERFORMANCE_PROFILER_DIR . 'admin/index.php';
+ require_once ICIT_PERFORMANCE_PROFILER_DIR . 'classes/pagination.php';
+ }
+
+ // Create an instance of the logger, which will take over from here
+ switch( $logging_level ) {
+ case 'basic':
+ require_once ICIT_PERFORMANCE_PROFILER_DIR . 'classes/logger-basic.php';
+ self::$logger = new Basic_Logger;
+ break;
+ case 'advanced':
+ require_once ICIT_PERFORMANCE_PROFILER_DIR . 'classes/logger-advanced.php';
+ self::$logger = new Advanced_Logger;
+ break;
+ }
+
+ // Register a callback to save and render the data
+ if( self::$logger !== null ) {
+ add_action( 'shutdown', array( self::$logger, 'save' ), 999 );
+ }
+ }
+
+ public function is_active() {
+ $settings = get_option( 'icit_performance_profiler' );
+
+ return isset( $settings['active'] ) ? $settings['active'] : true;
+ }
+
+ public function get_logging_level() {
+ $settings = get_option( 'icit_performance_profiler' );
+ $type = icit_profiler_current_request_type();
+
+ // Are we forcing logging via the query string?
+ // This overrides the other settings below
+ if( isset( $_GET['profiler'] ) ) {
+ $level = $_GET['profiler'];
+
+ // If we've set the type, it must be part of our whitelist
+ // Else, we're set it to the default
+ return in_array( $level, array( 'basic', 'advanced' ) ) ? $level : 'advanced';
+ }
+
+ // Are we logging this type of request?
+ if( ! isset( $settings['request_types'][ $type ] ) ) return 'none';
+
+ // Do we need either basic or advanced logging
+ // We'll always check advanced first, as we'd rather that than basic
+ // As we want the ability to have a decimal percentage
+ // We get a number between 0 and 10000 and divide by 100
+ // This will give us 2 decimal places
+ $chance = rand( 0, 10000 ) / 100;
+ if( $chance <= $settings['advanced_frequency'] ) return 'advanced';
+ if( $chance <= $settings['basic_frequency'] ) return 'basic';
+
+ return 'none';
+ }
+}
+Core::instance();
diff --git a/wp-performance-profiler/must-run.php b/wp-performance-profiler/must-run.php
new file mode 100644
index 0000000..1e66907
--- /dev/null
+++ b/wp-performance-profiler/must-run.php
@@ -0,0 +1,66 @@
+Activate ';
+ } else if( $is_bootstrapped && $is_active && $is_mustuse ) {
+ $meta[] = 'Deactivate ';
+ }
+
+ return $meta;
+ }
+
+ public function admin_actions() {
+ if ( empty( $_GET['action'] ) ) return;
+
+ switch( $_GET['action'] ) {
+ case 'activate_icit_performance_profiler':
+ $settings = get_option( 'icit_performance_profiler', array() );
+ $settings['active'] = true;
+
+ update_option( 'icit_performance_profiler', $settings );
+ wp_redirect( admin_url( 'plugins.php?plugin_status=mustuse' ) );
+ exit;
+ case 'deactivate_icit_performance_profiler':
+ $settings = get_option( 'icit_performance_profiler', array() );
+ $settings['active'] = false;
+
+ update_option( 'icit_performance_profiler', $settings );
+ wp_redirect( admin_url( 'plugins.php?plugin_status=mustuse' ) );
+ exit;
+ }
+ }
+ }
+
+ Must_Run::instance();
+}
diff --git a/wp-performance-profiler/upgrade.php b/wp-performance-profiler/upgrade.php
new file mode 100644
index 0000000..5f946c2
--- /dev/null
+++ b/wp-performance-profiler/upgrade.php
@@ -0,0 +1,167 @@
+create_request_table();
+ $this->create_plugins_table();
+ $this->create_functions_table();
+ $this->create_queries_table();
+ }
+
+ if( $db_version < 2 ) {
+ $this->create_details_table();
+ }
+
+ // Update the database version, so we don't do this again
+ $settings['database_version'] = Core::DB_VERSION;
+ update_option( 'icit_performance_profiler', $settings );
+ }
+ }
+
+ private function create_request_table() {
+ $table = $this->get_table( 'requests' );
+ $charset = $this->get_charset();
+
+ maybe_create_table( $table, "CREATE TABLE $table (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `timestamp` int(11) DEFAULT NULL,
+ `request` varchar(1024) DEFAULT NULL,
+ `type` varchar(8) DEFAULT NULL,
+ `template` varchar(1024) DEFAULT NULL,
+ `duration` double DEFAULT NULL,
+ `memory` int(11) DEFAULT NULL,
+ `queries` int(11) DEFAULT NULL,
+ `payload` text,
+ PRIMARY KEY (`id`),
+ KEY `timestamp` (`timestamp`),
+ KEY `type` (`type`),
+ KEY `template` (`template`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=1 $charset;" );
+ }
+
+ private function create_plugins_table() {
+ $table = $this->get_table( 'plugins' );
+ $charset = $this->get_charset();
+
+ maybe_create_table( $table, "CREATE TABLE $table (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `request_id` int(11) DEFAULT NULL,
+ `plugin` varchar(64) DEFAULT NULL,
+ `count` int(11) DEFAULT NULL,
+ `duration` double DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `request_id` (`request_id`),
+ KEY `count` (`count`),
+ KEY `duration` (`duration`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=1 $charset;" );
+ }
+
+ private function create_functions_table() {
+ $table = $this->get_table( 'functions' );
+ $charset = $this->get_charset();
+
+ maybe_create_table( $table, "CREATE TABLE $table (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `request_id` int(11) DEFAULT NULL,
+ `plugin` varchar(64) DEFAULT NULL,
+ `function` varchar(256) DEFAULT NULL,
+ `count` int(11) DEFAULT NULL,
+ `duration` float DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `request_id` (`request_id`),
+ KEY `plugin` (`plugin`),
+ KEY `function` (`function`),
+ KEY `count` (`count`),
+ KEY `duration` (`duration`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=1 $charset;" );
+ }
+
+ private function create_queries_table() {
+ $table = $this->get_table( 'queries' );
+ $charset = $this->get_charset();
+
+ maybe_create_table( $table, "CREATE TABLE $table (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `request_id` int(11) DEFAULT NULL,
+ `duration` double DEFAULT NULL,
+ `plugin` varchar(64) DEFAULT NULL,
+ `the_query` varchar(2048) DEFAULT NULL,
+ `stack` varchar(2048) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `request_id` (`request_id`),
+ KEY `duration` (`duration`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=1 $charset;" );
+ }
+
+ private function create_details_table() {
+ $table = $this->get_table( 'details' );
+ $charset = $this->get_charset();
+
+ maybe_create_table( $table, "CREATE TABLE $table (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `request_id` int(11) DEFAULT NULL,
+ `duration_core` double DEFAULT NULL,
+ `duration_themes` double DEFAULT NULL,
+ `duration_plugins` double DEFAULT NULL,
+ `duration_mu_plugins` double DEFAULT NULL,
+ `duration_database` double DEFAULT NULL,
+ `duration_http` double DEFAULT NULL,
+ `duration_misc` double DEFAULT NULL,
+ PRIMARY KEY (`id`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=1 $charset;" );
+ }
+
+ private function get_table( $name ) {
+ global $wpdb;
+
+ return $wpdb->prefix . 'profiler_' . $name;
+ }
+
+ private function get_charset() {
+ global $wpdb;
+
+ $charset = '';
+
+ if ( ! empty( $wpdb->charset ) )
+ $charset .= "DEFAULT CHARACTER SET $wpdb->charset";
+ if ( ! empty( $wpdb->collate ) )
+ $charset .= " COLLATE $wpdb->collate";
+
+ return $charset;
+ }
+ }
+
+ add_action( 'admin_init', array( '\ICIT_Performance_Profiler\Upgrade', 'instance' ) );
+}
diff --git a/wp-performance-profiler/util.php b/wp-performance-profiler/util.php
new file mode 100644
index 0000000..6ee7187
--- /dev/null
+++ b/wp-performance-profiler/util.php
@@ -0,0 +1,146 @@
+get_col( "SHOW TABLES" );
+ $tables = array_flip( $tables );
+
+ return ! empty( $settings ) &&
+ isset( $tables["{$wpdb->prefix}profiler_functions"] ) &&
+ isset( $tables["{$wpdb->prefix}profiler_plugins"] ) &&
+ isset( $tables["{$wpdb->prefix}profiler_queries"] ) &&
+ isset( $tables["{$wpdb->prefix}profiler_requests"] );
+ }
+}
+
+if( ! function_exists( 'icit_profiler_request_types' ) ) {
+ /**
+ * Returns an array of all the supported request types
+ *
+ * The array key is the internal name and the value is the human readable name
+ */
+ function icit_profiler_request_types() {
+ return array(
+ 'front' => 'Front-end',
+ 'admin' => 'Admin',
+ 'ajax' => 'AJAX',
+ 'cron' => 'Cron',
+ // 'feed' => 'Feed',
+ // '404' => '404',
+ // 'search' => 'Search',
+ );
+ }
+}
+
+if( ! function_exists( 'icit_profiler_current_request_type' ) ) {
+ /**
+ * Return the internal name for the current request type
+ */
+ function icit_profiler_current_request_type() {
+ if( defined( 'DOING_CRON' ) && DOING_CRON ) return 'cron';
+ if( defined( 'DOING_AJAX' ) && DOING_AJAX ) return 'ajax';
+ // if( is_404() ) return '404';
+ // if( is_search() ) return 'search';
+ // if( is_feed() ) return 'feed';
+ if( is_admin() ) return 'admin';
+ return 'front';
+ }
+}
+
+if( ! function_exists( 'icit_profiler_cross_platform_path' ) ) {
+ function icit_profiler_cross_platform_path( $path ) {
+ return str_replace( '\\', '/', $path );
+ }
+}
+
+if( ! function_exists( 'icit_profiler_order_icon' ) ) {
+ /**
+ * Render the order icon for each column
+ * For the active column, it will show the current order, and for inactive columns, it will show the descending arrow
+ * Clicking on the active column will toggle the order, and clicking on inactive columns will activate that order
+ */
+ function icit_profiler_order_icon( $column ) {
+ $sort_column = ! empty( $_GET['profiler_sort_by'] ) ? $_GET['profiler_sort_by'] : 'average';
+ $sort_order = ! empty( $_GET['profiler_sort_order'] ) ? $_GET['profiler_sort_order'] : 'desc';
+
+ if( $column === $sort_column ) {
+ // This is the active column
+ if( $sort_order === 'asc' ) {
+ echo ' ';
+ } else if( $sort_order === 'desc' ) {
+ echo ' ';
+ }
+ } else {
+ // This is the inactive column
+ echo ' ';
+ }
+ }
+}
+
+if( ! function_exists( 'icit_profiler_order_url' ) ) {
+ function icit_profiler_order_url( $column ) {
+ $sort_column = ! empty( $_GET['profiler_sort_by'] ) ? $_GET['profiler_sort_by'] : 'average';
+ $sort_order = ! empty( $_GET['profiler_sort_order'] ) ? $_GET['profiler_sort_order'] : 'desc';
+ $params = $_GET;
+
+ // Set the column we're sorting on, and figure out the order
+ $params['profiler_sort_by'] = $column;
+
+ if( $column === $sort_column && $sort_order === 'desc' ) {
+ $params['profiler_sort_order'] = 'asc';
+ } else {
+ $params['profiler_sort_order'] = 'desc';
+ }
+
+ return add_query_arg( $params );
+ }
+}
diff --git a/wp-performance-profiler/views/admin/database.php b/wp-performance-profiler/views/admin/database.php
new file mode 100644
index 0000000..8bbf818
--- /dev/null
+++ b/wp-performance-profiler/views/admin/database.php
@@ -0,0 +1,71 @@
+
+
+
diff --git a/wp-performance-profiler/views/admin/detail.php b/wp-performance-profiler/views/admin/detail.php
new file mode 100644
index 0000000..a7f882d
--- /dev/null
+++ b/wp-performance-profiler/views/admin/detail.php
@@ -0,0 +1,231 @@
+To view in-depth information on a specific request, select it from the requests tab.
', admin_url( 'admin.php?page=icit-profiler&tab=requests' ) );
+ echo 'Not all requests have detailed information available due to the level of logging.
';
+ return;
+}
+?>
+
+ Requesting request )?>
.
+ template ) ):?>
+ Rendering output with the template?>
template.
+
+
+
+
+ duration, 2 )?>
+ ms duration
+
+
+ timestamp )?>
+ timestamp )?>
+
+
+ memory / 1024 / 1024, 2 )?>
+ MB Memory
+
+
+
+ duration_database, 2 )?>
+ ms of DB queries
+
+
+ type )?>
+ Request type
+
+
+
+duration_core ) ):?>
+
+
+
+
+
+
+
+
+
+
+
+ Core (duration_core, 2 )?> ms)
+
+
+
+ Themes (duration_themes, 2 )?> ms)
+
+
+
+ Plugins (duration_plugins, 2 )?> ms)
+
+
+
+ MU Plugins (duration_mu_plugins, 2 )?> ms)
+
+
+
+ Database (duration_database, 2 )?> ms)
+
+
+
+
+
+ Plugins
+
+
+ Plugin
+ Duration (ms)
+ Call count
+
+
+
+
+
+
+
+
+
+
+
+ function?>
+ duration?>
+ count?>
+
+
+
+
+
+
+
+ Database
+
+
+ Duration (ms)
+ Plugin
+ Query
+
+
+
+
+ duration?>
+ plugin?>
+ the_query )?>
+
+
+
+
+
+Other requests for this URL
+
+
+
+
+
Worst performers
+
+
+ Date
+ Duration (ms)
+ Memory (mb)
+ DB Queries (#)
+
+
+
+ timestamp )?>
+ duration, 2 )?>
+ memory )?>
+ queries?>
+
+
+
+
+
+
+
+
Best performers
+
+
+
+ Date
+ Duration (ms)
+ Memory (mb)
+ DB Queries (#)
+
+
+
+ timestamp )?>
+ duration, 2 )?>
+ memory )?>
+ queries?>
+
+
+
+
+
There aren't enough matching requests to show the best performing requests without duplicating the worst performing requests.
+
+
+
+
+ There are currently no other requests that match this URL.
+ Note: If a URL has a unique query string, it won't appear here.
+
+
+
+ Other requests with this template
+
+
+
Worst performers
+
+
+
+ Date
+ URL
+ Duration (ms)
+ Memory (mb)
+ DB Queries (#)
+
+
+
+ timestamp )?>
+ request?>
+ duration, 2 )?>
+ memory )?>
+ queries?>
+
+
+
+
+
+
+
+
Best performers
+
+
+
+ Date
+ URL
+ Duration (ms)
+ Memory (mb)
+ DB Queries (#)
+
+
+
+ timestamp )?>
+ request?>
+ duration, 2 )?>
+ memory )?>
+ queries?>
+
+
+
+
+
There aren't enough matching requests to show the best performing requests without duplicating the worst performing requests.
+
+
+
+
+
+
+ Payload
+
+
diff --git a/wp-performance-profiler/views/admin/help.php b/wp-performance-profiler/views/admin/help.php
new file mode 100644
index 0000000..b69fc46
--- /dev/null
+++ b/wp-performance-profiler/views/admin/help.php
@@ -0,0 +1,64 @@
+About
+The WordPress Performance Profiler monitors the performance of your site and helps you pinpoint sluggish behavious. The admin is split into the following sections:
+
+Requests
+This shows a list of all your logged requests. You can filter them using the form at the top of the table, and for requests logged with the advanced logger, there is a details link to get more in depth information.
+For each request, you have the following information:
+
+ Date — The date and time the request occured. These are ordered newest first and filtering by date/time will show all requests that occured since that time.
+ URL — The URL of the request. Filtering on this field will search anywhere within the field
+ Duration — The duration of the request in milliseconds. This is the time from when the request hit the server and the plugin is instantiated. Filtering on this field will return requests taking longer than X milliseconds
+ Memory — The amount of memory consumed serviceing the request. This is based on process memory at the end of the request, and actual memory usage could fluctuate during the request. Filtering on this field will return requests using more than X MB of memory
+ Database Queries — The total number of database queries used to render the page. Filtering on this field will return all requests which made more than X queries
+ Template — The template file used for the request. This is only applicable for front end requests, and when filtering on this field, you need to full file path from the theme root including the leading slash, such as /single.php
+ Type — The type of request, such as front end, admin, AJAX or cron
+
+
+Plugins
+This tab shows you an aggregate view of plugin performance. It uses all the available data from the advanced logging data to give a summary of which plugins perform worst overall, as well as breaking down the performance for each request type, as some plugins might perform very well in the front end but terribly in the admin and vice versa.
+
+In Depth
+The in depth tab gives a detailed breakdown about the execution time for an individual request. This information is not available for all requests, only ones captured with the advanced logger. Going directly to this tab will give an error message - instead, you need to click on the details link from the requests tab.
+On this page, you can see an overview of basic information at the top, followed by a breakdown of eecution time by plugin. Clicking on a plugin will expand the table to show all the functions within that plugin and their execution time and call count.
+
+Database
+This tab logs all database queries ordered by duration, slowest first. As with requests, you can filter the contents of this table based on date, duration or search within the SQL query. Once you've found a query you're interested in, you can view the request it originated from by clicking on the details link (if applicable).
+
+Settings
+Here you can control how frequently the profiler captures data. The basic and advanced logging frequency is set as a percentage, so if you want it to log all requests, you'd set it to 100, to log half of requests you'd set it to 50 etc. You can also have decimal numbers, so to only log 1 in a thousand requests, you'd set it to 0.1. The frequency the profiler will run may vary from the settings here, as it's the probability that the profiler will run.
+You can also control which types of requests are logged. For example, if you're attempting to diagnose a performance problem on the front end, you can disable the other request types to minimise overhead.
+During development, you'll most likely wan tto have advanced logging set to 100, so that it logs every request, but if you're running it in a production environment, then set the logging levels to be much lower, especially the advanced one.
+
+Maintenance
+Over time, the WordPress Performance Profiler will accumulate a lot of data, making the database increase in size significantly. You can clear out the database from the maintenance tab, either deleting all the stored data, or deleting all the data and the database tables.
+
+
+FAQs
+
+What benefit does this have over server level profiling?
+Tools like the xdebug profiler are great, but they add a lot of overhead and are focused on individual requests, and also require setup at the server level, which you may not have access to. The WordPress Performance Profiler is optimised to work with WordPress with minimal setup. It's also focused on being lightweight and can help to spot trends in aggregate data.
+
+Why does this plugin need to be installed as a must-use plugin instead of a regular one?
+Although it's possible to run this plugin as a normal one instead of a must-use one, it would capture less data, and the data it does capture would be less accurate. This is because must-use plugins are loaded much earlier than normal ones, allowing the profiler to monitor the load time of all plugins, and not just ones loaded after it (which could be none).
+To install the this as a must-use plugin, de-activate the plugin and then move load-wp-performance-profiler.php
and wp-performance-profiler
from the plugins directory of your site to wp-content/mu-plugins
. If this directory doesn't already exist, you can create it.
+
+Why does it create its own database tables instead of using the core WordPress tables?
+As the plugin captures and stores a lot of information, it creates its own tables for best performance. This also has the benefit of not cluttering up the core WordPress tables, and makes it much easier for the plugin to clean up after itself.
+
+Why is this not logging exactly 10% of requests?
+The logging is based on probability, not absolute metrics. Although it would be possible to make the number of requests logged more accorate, this would add overhead to every single request, not just the ones being logged. Therefore, in the interest of performance, it will only log the request if the probably of a random number if less than or equal to your target. This will mean that sometimes you get slightly more or less than the target number of requests.
+
+What's the difference between basic and advanced logging?
+Basic logging is very light weight and will only capture the URL, total duration, amount of memory used, number of database queries, template used and the type of request. Advanced logging will also capture the execution time of each plugin down to the function level, as well as all the database queries.
+Basic logging is great to leave running in the background to get an idea of the overall performance of your site and spot trends, such as slow performing requests, templates or types of requests. Advanced logging is then great for drilling down in more detail to find out how the time is spent on those slow requests. In the requests table, advanced requests will have the link to view details in the actions column.
+
+Can this be used on a production website?
+Yes, although this plugin is primarily meant as a developer tool - so is most beneficial running on your local development environment and staging servers - it can be used in production. We've tried to minimise the overhead as much as possible, but any tool like this will innevitably carry an overhead in terms of performance and also database size.
+If you are going to run it in a production environment, it's recommended to set the advanced logging level to a very low number, and the basic level to a low-medium number. With a large amount of traffic, this will still capture a lot of data with minimal overhead. If you run it with higher values (especially for the advanced logging), you'll need to periodically purge the database in the maintenance tab.
+
+Can I manually log requests
+If you don't want to run the profiler all the time, or only want to log specific requests, you can manually log them by adding ?profiler
to the query string. This will enable the advanced profiler, but if you want just basic logging, add ?profiler=basic
.
+By using the query string, you will override any default settings.
+
+Any other questions?
+If you have a question that's not answered here, or have encountered an issue, please email cases@interconnectit.fogbugz.com .
diff --git a/wp-performance-profiler/views/admin/maintenance.php b/wp-performance-profiler/views/admin/maintenance.php
new file mode 100644
index 0000000..337c40a
--- /dev/null
+++ b/wp-performance-profiler/views/admin/maintenance.php
@@ -0,0 +1,22 @@
+Cleanup Database
+
+ Over time, this plugin captures and stores a large amount of data.
+ If you wish to delete that information, click the button below.
+
+
+ Delete all data
+ Warning - This operation cannot be undone.
+
+
+
+
+Uninstall
+
+ If you want to uninstall the WordPress Performance Profiler, click the button below.
+ This will delete all the data from the database and de-activate the plugin.
+ As it's a must-use plugin, you will have to delete the files yourself.
+
+
+ Uninstall
+ Warning - This operation cannot be undone.
+
diff --git a/wp-performance-profiler/views/admin/plugins.php b/wp-performance-profiler/views/admin/plugins.php
new file mode 100644
index 0000000..affdbfe
--- /dev/null
+++ b/wp-performance-profiler/views/admin/plugins.php
@@ -0,0 +1,41 @@
+
+ Showing:
+ >Average duration
+
+ >Standard Deviation
+ >Minimum duration
+ >Maximum duration
+
+
+
diff --git a/wp-performance-profiler/views/admin/requests.php b/wp-performance-profiler/views/admin/requests.php
new file mode 100644
index 0000000..5c75a2b
--- /dev/null
+++ b/wp-performance-profiler/views/admin/requests.php
@@ -0,0 +1,82 @@
+
+
+
diff --git a/wp-performance-profiler/views/admin/settings.php b/wp-performance-profiler/views/admin/settings.php
new file mode 100644
index 0000000..c01cba6
--- /dev/null
+++ b/wp-performance-profiler/views/admin/settings.php
@@ -0,0 +1,7 @@
+
+
+
diff --git a/wp-performance-profiler/views/results-advanced.php b/wp-performance-profiler/views/results-advanced.php
new file mode 100644
index 0000000..66dde12
--- /dev/null
+++ b/wp-performance-profiler/views/results-advanced.php
@@ -0,0 +1,26 @@
+
+
+
+
+ Function
+ Duration
+ Count
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+