Request-scoped performance profiling library for PHP with detailed timing breakdowns, memory tracking, percentile metrics, and intelligent threshold monitoring.
- ⏱️ Precise Timing - Microsecond-precision operation timing with checkpoint support
- 💾 Memory Profiling - Track current, peak, and delta memory usage
- 📊 Percentile Metrics - P50, P75, P90, P95, P99 for statistical analysis
- 📈 Comparative Analysis - Before/after comparisons for optimization measurement
- 🎲 Sampling Mode - Reduce overhead with configurable sampling rates
⚠️ Smart Thresholds - Configurable warnings for slow operations and high memory- 🔧 Context-Aware - Automatic configuration based on environment
- 💿 Persistent Storage - Store metrics for later analysis
- 🗄️ Query Analysis - MySQL EXPLAIN integration for slow queries
- 🎯 Zero Production Overhead - Can be completely disabled
- 🔌 Easy Integration - Simple trait for repository/service integration
- 📝 Multiple Formats - Console-friendly and JSON output
composer require methorz/php-profileruse MethorZ\Profiler\OperationProfiler;
$profiler = OperationProfiler::start('database_query');
$profiler->checkpoint('prep');
$params = $this->prepareParams($data);
$profiler->checkpoint('exec');
$result = $this->executeQuery($query, $params);
$profiler->checkpoint('hydrate');
$objects = $this->hydrate($result);
$metrics = $profiler->end();use MethorZ\Profiler\Concern\ProfilesOperations;
class UserRepository
{
use ProfilesOperations;
public function fetchUsers(array $ids): array
{
$profiler = $this->startProfiling('fetch_users');
try {
$profiler->checkpoint('prep');
$query = $this->buildQuery($ids);
$profiler->checkpoint('exec');
$result = $this->dal->execute($query);
$profiler->checkpoint('hydrate');
$users = $this->hydrate($result);
$profiler->addCount('rows', count($users));
return $users;
} finally {
$profiler->end();
}
}
public function getLastQueryMetrics(): array
{
return $this->getLastProfilingMetrics();
}
}Automatically adjusts behavior based on environment:
use MethorZ\Profiler\ContextAwareProfiler;
// Auto-detects environment and configures appropriately
$profiler = ContextAwareProfiler::create();
$opProfiler = $profiler->start('operation');
// Production web: disabled
// Production CLI: 10% sampling
// Development: full profilingProfile only a percentage of operations:
use MethorZ\Profiler\SamplingProfiler;
$sampler = new SamplingProfiler(0.1); // 10% sampling
$profiler = $sampler->start('high_traffic_operation');
// ... operation
$metrics = $profiler->end();Statistical analysis of operation performance:
use MethorZ\Profiler\MetricsCollector;
$collector = new MetricsCollector();
foreach ($batches as $batch) {
$profiler = OperationProfiler::start('batch_process');
// ... process batch
$metrics = $profiler->end();
$collector->record('batch_process', $metrics);
}
$aggregate = $collector->aggregate();
// Returns: ['percentiles' => ['p50' => 0.05, 'p95' => 0.15, 'p99' => 0.25, ...]]Measure optimization impact:
use MethorZ\Profiler\Analysis\MetricsComparator;
use MethorZ\Profiler\Storage\FileStorage;
$storage = new FileStorage('./metrics');
// Before optimization
$profiler = OperationProfiler::start('slow_query');
$result = $this->oldImplementation();
$before = $profiler->end();
$storage->store('before', $before);
// After optimization
$profiler = OperationProfiler::start('slow_query');
$result = $this->newImplementation();
$after = $profiler->end();
$comparator = new MetricsComparator();
$comparison = $comparator->compare($before, $after);
// Returns detailed comparison with improvement/regression detection
echo $comparison['summary']['messages'][0];
// "Performance improvement: 43.2% faster (0.5s → 0.284s)"Store metrics for later analysis:
use MethorZ\Profiler\Storage\FileStorage;
$storage = new FileStorage('./profiling-data');
$profiler = OperationProfiler::start('operation');
// ... operation
$metrics = $profiler->end();
// Store metrics
$storage->store('optimization_v1', $metrics);
// Retrieve later
$historical = $storage->retrieve('optimization_v1');Automatic EXPLAIN for slow queries:
use MethorZ\Profiler\Database\MysqlQueryExplainer;
$explainer = new MysqlQueryExplainer($pdo);
$profiler = OperationProfiler::start('complex_query');
$result = $this->executeQuery($query);
$metrics = $profiler->end();
if ($metrics['total'] > 1.0) { // Slow query
$explain = $explainer->explain($query);
if ($explainer->needsOptimization($explain)) {
$suggestions = $explainer->getSuggestions($explain);
// ['Full table scan on "users" - consider adding an index']
}
}Automatic warnings for performance issues:
use MethorZ\Profiler\PerformanceMonitor;
$monitor = PerformanceMonitor::create()
->setSlowOperationThreshold(1.0) // 1 second
->setSlowPhaseThreshold(0.5) // 500ms
->setHighMemoryThreshold(0.7) // 70% of memory limit
->setHighRowCountThreshold(10000); // 10k rows
$profiler = OperationProfiler::start('operation', $monitor);
// ... operation
$metrics = $profiler->end();
// Metrics include warnings if thresholds exceeded
if (isset($metrics['context']['warnings'])) {
foreach ($metrics['context']['warnings'] as $warning) {
error_log($warning);
}
}use MethorZ\Profiler\Formatter\ConsoleFormatter;
$formatter = new ConsoleFormatter();
echo $formatter->format($metrics);Output:
Operation: fetch_users
Total: 125.50ms
Phases:
prep: 3.20ms (2.5%)
exec: 100.10ms (79.7%)
hydrate: 22.20ms (17.7%)
Memory: 45.2MB (peak: 48.7MB, Δ+3.5MB)
Counts: ids: 150, rows: 2340
use MethorZ\Profiler\Formatter\JsonFormatter;
$formatter = new JsonFormatter(prettyPrint: true);
$json = $formatter->format($metrics);
file_put_contents('metrics.json', $json);// Disable globally (zero overhead)
OperationProfiler::setEnabled(false);// .env file
APP_ENV=production // Auto-disables for web, samples for CLI
// Override in code
$profiler = ContextAwareProfiler::create()
->forceEnable() // Force enabled regardless of environment
->forceSamplingRate(0.05); // 5% sampling- Profile at repository/port layer for database operations
- Use checkpoints to identify bottlenecks within operations
- Aggregate metrics for batch operations
- Use sampling mode in production
- Store metrics for trend analysis
- Compare before/after for optimization verification
- Profile every method call (too much overhead)
- Use for production monitoring without sampling (use proper APM tools)
- Store metrics in database (they're request-scoped)
- Profile trivial operations (<1ms)
- Forget to disable in production web requests
- PHP 8.2 or higher
- ext-json
# Run tests
composer test
# Run tests with coverage
composer test-coverage
# Run all quality checks
composer checkContributions are welcome! Please see CONTRIBUTING.md for details.
MIT License. See LICENSE for details.
| Feature | php-profiler | OpenTelemetry | Prometheus | StatsD |
|---|---|---|---|---|
| Purpose | Dev profiling | Distributed tracing | Time-series metrics | Real-time metrics |
| Overhead | Medium (dev) | Low (sampling) | Low | Very low |
| Memory tracking | ✅ Yes | ❌ No | ❌ No | ❌ No |
| Percentiles | ✅ Yes | ✅ Yes | ✅ Yes | |
| Sub-operations | ✅ Checkpoints | ✅ Spans | ||
| Comparison | ✅ Built-in | ❌ No | ❌ No | ❌ No |
| Production | ✅ Yes | ✅ Yes | ✅ Yes | |
| Setup | ✅ Simple | ✅ Simple | ✅ Simple |
Use php-profiler for: Development profiling, optimization measurement, debugging performance issues
Use OpenTelemetry/Prometheus for: Production monitoring, distributed tracing, long-term metrics storage
Created by Thorsten Merz