Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 101 additions & 19 deletions assets/profiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ window.WP_Hook_Profiler = (function($) {
<tr>
<td><span class="wp-hook-profiler-callback-name" title="${escapeHtml(callback.source_file)}">${escapeHtml(callback.callback)}</span></td>
<td><span class="wp-hook-profiler-hook-name">${escapeHtml(callback.hook)}</span></td>
<td><span class="wp-hook-profiler-plugin-name">${escapeHtml(callback.plugin)}</span></td>
<td><span class="wp-hook-profiler-plugin-name">${escapeHtml(callback.plugin_name || callback.plugin)}</span></td>
<td class="numeric ${timeClass}">${timeMs.toFixed(3)}</td>
<td class="numeric">${callback.call_count}</td>
</tr>
Expand All @@ -167,14 +167,14 @@ window.WP_Hook_Profiler = (function($) {
container.empty();

Object.entries(hookGroups).forEach(([hookName, callbacks]) => {
const totalTime = callbacks.reduce((sum, cb) => sum + cb.execution_time, 0) * 1000;
const totalTime = callbacks.reduce((sum, cb) => sum + (cb.total_time || 0), 0) * 1000;
const timeClass = getTimeColorClass(totalTime);

const hookGroup = $(`
<div class="wp-hook-profiler-hook-group" data-hook="${hookName}">
<div class="wp-hook-profiler-hook-header">
${escapeHtml(hookName)}
<span class="${timeClass}" style="float: right;">${totalTime.toFixed(3)}ms</span>
<span class="${timeClass}" style="float: right;">${isNaN(totalTime) ? '0.000' : totalTime.toFixed(3)}ms</span>
</div>
Comment on lines +170 to 178
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: unit error inflates hook total time by 1000x.

callback.total_time is already in milliseconds; multiplying by 1000 mislabels values and color classes.

Apply this diff:

-            const totalTime = callbacks.reduce((sum, cb) => sum + (cb.total_time || 0), 0) * 1000;
+            const totalTime = callbacks.reduce((sum, cb) => sum + (cb.total_time || 0), 0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const totalTime = callbacks.reduce((sum, cb) => sum + (cb.total_time || 0), 0) * 1000;
const timeClass = getTimeColorClass(totalTime);
const hookGroup = $(`
<div class="wp-hook-profiler-hook-group" data-hook="${hookName}">
<div class="wp-hook-profiler-hook-header">
${escapeHtml(hookName)}
<span class="${timeClass}" style="float: right;">${totalTime.toFixed(3)}ms</span>
<span class="${timeClass}" style="float: right;">${isNaN(totalTime) ? '0.000' : totalTime.toFixed(3)}ms</span>
</div>
const totalTime = callbacks.reduce((sum, cb) => sum + (cb.total_time || 0), 0);
const timeClass = getTimeColorClass(totalTime);
const hookGroup = $(`
<div class="wp-hook-profiler-hook-group" data-hook="${hookName}">
<div class="wp-hook-profiler-hook-header">
${escapeHtml(hookName)}
<span class="${timeClass}" style="float: right;">${isNaN(totalTime) ? '0.000' : totalTime.toFixed(3)}ms</span>
</div>
🤖 Prompt for AI Agents
In assets/profiler.js around lines 170 to 178, the code multiplies
callback.total_time by 1000 but callback.total_time is already in milliseconds,
inflating displayed totals and affecting color classification; remove the * 1000
multiplier so totalTime is computed as const totalTime = callbacks.reduce((sum,
cb) => sum + (cb.total_time || 0), 0); keep the subsequent
getTimeColorClass(totalTime) and the isNaN/ toFixed formatting unchanged so
values and classes reflect true milliseconds.

<div class="wp-hook-profiler-hook-callbacks"></div>
</div>
Expand All @@ -191,11 +191,11 @@ window.WP_Hook_Profiler = (function($) {
<div class="wp-hook-profiler-callback-info">
<div class="wp-hook-profiler-callback-name">${escapeHtml(callback.callback)}</div>
<div class="wp-hook-profiler-callback-meta">
Plugin: ${escapeHtml(callback.plugin)} | Priority: ${callback.priority}
Plugin: ${escapeHtml(callback.plugin_name || callback.plugin)} | Priority: ${callback.priority}
</div>
</div>
<div class="wp-hook-profiler-callback-time ${timeClass}">
${timeMs.toFixed(3)}ms
${isNaN(timeMs) ? '0.000' : timeMs.toFixed(3)}ms
</div>
</div>
`);
Expand All @@ -209,16 +209,16 @@ window.WP_Hook_Profiler = (function($) {

function populatePluginFilter() {
if (!profileData?.plugins) return;

const select = $('#wp-hook-profiler-filter-plugin');
const currentValue = select.val();

select.find('option:not(:first)').remove();

Object.values(profileData.plugins).forEach(plugin => {
select.append(`<option value="${escapeHtml(plugin.plugin_name)}">${escapeHtml(plugin.plugin_name)}</option>`);
});

select.val(currentValue);
}

Expand Down Expand Up @@ -295,21 +295,103 @@ window.WP_Hook_Profiler = (function($) {
function filterHooksList() {
const searchTerm = $('#wp-hook-profiler-search-hooks').val().toLowerCase();
const pluginFilter = $('#wp-hook-profiler-filter-plugin').val();


// console.log('Plugin Filter:', pluginFilter, 'Search Term:', searchTerm);

// If only search term and no plugin filter, use simpler logic
if (!pluginFilter && searchTerm) {
return filterHooksBySearch(searchTerm);
}

$('.wp-hook-profiler-hook-group').each(function() {
const hookName = $(this).data('hook').toLowerCase();
const matchesSearch = !searchTerm || hookName.includes(searchTerm);

let matchesPlugin = !pluginFilter;

let hookHasMatchingCallbacks = !pluginFilter;
let visibleCallbackCount = 0;

if (pluginFilter) {
const hasMatchingPlugin = $(this).find('.wp-hook-profiler-callback-meta')
.toArray()
.some(el => $(el).text().includes(`Plugin: ${pluginFilter}`));
matchesPlugin = hasMatchingPlugin;
// Filter individual callbacks within this hook group
const callbackItems = $(this).find('.wp-hook-profiler-callback-item');
callbackItems.each(function() {
const callbackMeta = $(this).find('.wp-hook-profiler-callback-meta');
const metaText = callbackMeta.text();

// Check if this callback matches the plugin filter
const matchesPlugin = metaText.includes(`Plugin: ${pluginFilter}`) ||
metaText.toLowerCase().includes(`plugin: ${pluginFilter.toLowerCase()}`) ||
metaText.toLowerCase().includes(pluginFilter.toLowerCase());

if (matchesPlugin) {
hookHasMatchingCallbacks = true;
visibleCallbackCount++;
$(this).show();
} else {
$(this).hide();
}
});
} else {
// No plugin filter - show all callbacks
$(this).find('.wp-hook-profiler-callback-item').show();
hookHasMatchingCallbacks = true;
visibleCallbackCount = $(this).find('.wp-hook-profiler-callback-item').length;
}

// Show/hide the entire hook group based on whether it has matching callbacks
const shouldShowHook = matchesSearch && hookHasMatchingCallbacks && visibleCallbackCount > 0;
$(this).toggle(shouldShowHook);

// Update hook header to reflect filtered callback count
if (shouldShowHook && pluginFilter) {
const totalCallbacks = $(this).find('.wp-hook-profiler-callback-item').length;
const hookHeader = $(this).find('.wp-hook-profiler-hook-header');
const originalText = hookHeader.text().replace(/\(\d+\/\d+\)/, '').trim();
if (visibleCallbackCount !== totalCallbacks) {
hookHeader.html(`${originalText} <small>(${visibleCallbackCount}/${totalCallbacks} callbacks)</small>`);
}
}
});
Comment on lines +310 to +353
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: header rewrite drops the right-aligned time span.

Using hookHeader.text() then .html() nukes existing child nodes (time badge) and may duplicate labels on subsequent filters.

Apply this diff to preserve the time span and only toggle a dedicated count node:

-            // Update hook header to reflect filtered callback count
-            if (shouldShowHook && pluginFilter) {
-                const totalCallbacks = $(this).find('.wp-hook-profiler-callback-item').length;
-                const hookHeader = $(this).find('.wp-hook-profiler-hook-header');
-                const originalText = hookHeader.text().replace(/\(\d+\/\d+\)/, '').trim();
-                if (visibleCallbackCount !== totalCallbacks) {
-                    hookHeader.html(`${originalText} <small>(${visibleCallbackCount}/${totalCallbacks} callbacks)</small>`);
-                }
-            }
+            // Update hook header to reflect filtered callback count (preserve time span)
+            if (shouldShowHook && pluginFilter) {
+                const totalCallbacks = $(this).find('.wp-hook-profiler-callback-item').length;
+                const hookHeader = $(this).find('.wp-hook-profiler-hook-header');
+                let countEl = hookHeader.find('.wp-hook-profiler-hook-count');
+                if (!countEl.length) {
+                    countEl = $('<small class="wp-hook-profiler-hook-count"></small>').appendTo(hookHeader);
+                }
+                if (visibleCallbackCount !== totalCallbacks) {
+                    countEl.text(` (${visibleCallbackCount}/${totalCallbacks} callbacks)`);
+                } else {
+                    countEl.remove();
+                }
+            } else {
+                $(this).find('.wp-hook-profiler-hook-header .wp-hook-profiler-hook-count').remove();
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let hookHasMatchingCallbacks = !pluginFilter;
let visibleCallbackCount = 0;
if (pluginFilter) {
const hasMatchingPlugin = $(this).find('.wp-hook-profiler-callback-meta')
.toArray()
.some(el => $(el).text().includes(`Plugin: ${pluginFilter}`));
matchesPlugin = hasMatchingPlugin;
// Filter individual callbacks within this hook group
const callbackItems = $(this).find('.wp-hook-profiler-callback-item');
callbackItems.each(function() {
const callbackMeta = $(this).find('.wp-hook-profiler-callback-meta');
const metaText = callbackMeta.text();
// Check if this callback matches the plugin filter
const matchesPlugin = metaText.includes(`Plugin: ${pluginFilter}`) ||
metaText.toLowerCase().includes(`plugin: ${pluginFilter.toLowerCase()}`) ||
metaText.toLowerCase().includes(pluginFilter.toLowerCase());
if (matchesPlugin) {
hookHasMatchingCallbacks = true;
visibleCallbackCount++;
$(this).show();
} else {
$(this).hide();
}
});
} else {
// No plugin filter - show all callbacks
$(this).find('.wp-hook-profiler-callback-item').show();
hookHasMatchingCallbacks = true;
visibleCallbackCount = $(this).find('.wp-hook-profiler-callback-item').length;
}
// Show/hide the entire hook group based on whether it has matching callbacks
const shouldShowHook = matchesSearch && hookHasMatchingCallbacks && visibleCallbackCount > 0;
$(this).toggle(shouldShowHook);
// Update hook header to reflect filtered callback count
if (shouldShowHook && pluginFilter) {
const totalCallbacks = $(this).find('.wp-hook-profiler-callback-item').length;
const hookHeader = $(this).find('.wp-hook-profiler-hook-header');
const originalText = hookHeader.text().replace(/\(\d+\/\d+\)/, '').trim();
if (visibleCallbackCount !== totalCallbacks) {
hookHeader.html(`${originalText} <small>(${visibleCallbackCount}/${totalCallbacks} callbacks)</small>`);
}
}
});
let hookHasMatchingCallbacks = !pluginFilter;
let visibleCallbackCount = 0;
if (pluginFilter) {
// Filter individual callbacks within this hook group
const callbackItems = $(this).find('.wp-hook-profiler-callback-item');
callbackItems.each(function() {
const callbackMeta = $(this).find('.wp-hook-profiler-callback-meta');
const metaText = callbackMeta.text();
// Check if this callback matches the plugin filter
const matchesPlugin = metaText.includes(`Plugin: ${pluginFilter}`) ||
metaText.toLowerCase().includes(`plugin: ${pluginFilter.toLowerCase()}`) ||
metaText.toLowerCase().includes(pluginFilter.toLowerCase());
if (matchesPlugin) {
hookHasMatchingCallbacks = true;
visibleCallbackCount++;
$(this).show();
} else {
$(this).hide();
}
});
} else {
// No plugin filter - show all callbacks
$(this).find('.wp-hook-profiler-callback-item').show();
hookHasMatchingCallbacks = true;
visibleCallbackCount = $(this).find('.wp-hook-profiler-callback-item').length;
}
// Show/hide the entire hook group based on whether it has matching callbacks
const shouldShowHook = matchesSearch && hookHasMatchingCallbacks && visibleCallbackCount > 0;
$(this).toggle(shouldShowHook);
// Update hook header to reflect filtered callback count (preserve time span)
if (shouldShowHook && pluginFilter) {
const totalCallbacks = $(this).find('.wp-hook-profiler-callback-item').length;
const hookHeader = $(this).find('.wp-hook-profiler-hook-header');
let countEl = hookHeader.find('.wp-hook-profiler-hook-count');
if (!countEl.length) {
countEl = $('<small class="wp-hook-profiler-hook-count"></small>').appendTo(hookHeader);
}
if (visibleCallbackCount !== totalCallbacks) {
countEl.text(` (${visibleCallbackCount}/${totalCallbacks} callbacks)`);
} else {
countEl.remove();
}
} else {
$(this).find('.wp-hook-profiler-hook-header .wp-hook-profiler-hook-count').remove();
}
});


// Summary of filtering results
const visibleHooks = $('.wp-hook-profiler-hook-group:visible').length;
const totalHooks = $('.wp-hook-profiler-hook-group').length;
const visibleCallbacks = $('.wp-hook-profiler-callback-item:visible').length;
const totalCallbacks = $('.wp-hook-profiler-callback-item').length;

// console.log(`Filter Results: ${visibleHooks}/${totalHooks} hooks visible, ${visibleCallbacks}/${totalCallbacks} callbacks visible`);
}

function filterHooksBySearch(searchTerm) {
$('.wp-hook-profiler-hook-group').each(function() {
const hookName = $(this).data('hook').toLowerCase();
const hookMatches = hookName.includes(searchTerm);

// Also search within callback names and meta
let callbackMatches = false;
$(this).find('.wp-hook-profiler-callback-item').each(function() {
const callbackName = $(this).find('.wp-hook-profiler-callback-name').text().toLowerCase();
const callbackMeta = $(this).find('.wp-hook-profiler-callback-meta').text().toLowerCase();

if (callbackName.includes(searchTerm) || callbackMeta.includes(searchTerm)) {
callbackMatches = true;
$(this).show();
} else {
$(this).hide();
}
});

const shouldShow = hookMatches || callbackMatches;
$(this).toggle(shouldShow);

// If hook matches but no specific callbacks match, show all callbacks
if (hookMatches && !callbackMatches) {
$(this).find('.wp-hook-profiler-callback-item').show();
}

$(this).toggle(matchesSearch && matchesPlugin);
});

const visibleHooks = $('.wp-hook-profiler-hook-group:visible').length;
const totalHooks = $('.wp-hook-profiler-hook-group').length;
// console.log(`Search Results: ${visibleHooks}/${totalHooks} hooks visible`);
}

function groupCallbacksByHook(callbacks) {
Expand All @@ -323,7 +405,7 @@ window.WP_Hook_Profiler = (function($) {
});

Object.keys(groups).forEach(hookName => {
groups[hookName].sort((a, b) => b.execution_time - a.execution_time);
groups[hookName].sort((a, b) => (b.total_time || 0) - (a.total_time || 0));
});

return groups;
Expand Down
1 change: 1 addition & 0 deletions inc/class-callback-wrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function __invoke(...$args ) {
'hook' => $this->hook_name,
'callback' => $callback_name,
'plugin' => $plugin_info['plugin'],
'plugin_name' => $plugin_info['plugin_name'],
'source_file' => $plugin_info['file'],
'total_time' => 0,
'call_count' => 0,
Expand Down