Laravel 13 应用性能监控是保障系统高效运行的关键,本文介绍如何构建完整的性能监控体系。

性能监控概述

性能指标收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php

namespace App\Services\Performance;

class PerformanceMetrics
{
protected array $metrics = [];
protected float $startTime;
protected int $startMemory;

public function __construct()
{
$this->startTime = defined('LARAVEL_START') ? LARAVEL_START : microtime(true);
$this->startMemory = memory_get_usage(true);
}

public function record(string $name, float $value, array $tags = []): void
{
$this->metrics[] = [
'name' => $name,
'value' => $value,
'tags' => $tags,
'timestamp' => microtime(true),
];
}

public function recordTiming(string $name, callable $callback): mixed
{
$start = microtime(true);
$result = $callback();
$duration = (microtime(true) - $start) * 1000;

$this->record($name, $duration, ['unit' => 'ms']);

return $result;
}

public function getExecutionTime(): float
{
return (microtime(true) - $this->startTime) * 1000;
}

public function getMemoryUsage(): int
{
return memory_get_usage(true) - $this->startMemory;
}

public function getPeakMemory(): int
{
return memory_get_peak_usage(true);
}

public function getMetrics(): array
{
return $this->metrics;
}

public function toArray(): array
{
return [
'execution_time' => $this->getExecutionTime(),
'memory_usage' => $this->getMemoryUsage(),
'peak_memory' => $this->getPeakMemory(),
'metrics' => $this->metrics,
];
}
}

请求性能监控

性能监控中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php

namespace App\Http\Middleware;

use App\Services\Performance\PerformanceTracker;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PerformanceMonitor
{
protected PerformanceTracker $tracker;

protected array $slowRequestThresholds = [
'warning' => 1000,
'critical' => 3000,
];

public function __construct(PerformanceTracker $tracker)
{
$this->tracker = $tracker;
}

public function handle(Request $request, Closure $next): Response
{
$this->tracker->startTransaction($request);

$response = $next($request);

$this->tracker->endTransaction($request, $response);

$this->addPerformanceHeaders($response);

return $response;
}

protected function addPerformanceHeaders(Response $response): void
{
if (config('app.debug')) {
$response->headers->set('X-Execution-Time', $this->tracker->getDuration() . 'ms');
$response->headers->set('X-Memory-Usage', $this->formatBytes($this->tracker->getMemoryUsage()));
$response->headers->set('X-DB-Queries', $this->tracker->getQueryCount());
}
}

protected function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return number_format($bytes / pow(1024, $power), 2) . ' ' . $units[$power];
}
}

性能追踪器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<?php

namespace App\Services\Performance;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class PerformanceTracker
{
protected ?Transaction $currentTransaction = null;
protected array $spans = [];

public function startTransaction(Request $request): void
{
$this->currentTransaction = new Transaction(
id: $request->header('X-Request-ID', uniqid()),
name: $request->route()?->getName() ?? $request->path(),
type: 'request',
start: microtime(true),
context: [
'method' => $request->method(),
'url' => $request->fullUrl(),
'route' => $request->route()?->getName(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]
);

DB::listen(function ($query) {
$this->addSpan(new Span(
name: 'db.query',
start: microtime(true),
duration: $query->time,
context: [
'sql' => $query->sql,
'bindings' => $query->bindings,
]
));
});
}

public function endTransaction(Request $request, Response $response): void
{
if (!$this->currentTransaction) {
return;
}

$this->currentTransaction->end = microtime(true);
$this->currentTransaction->result = $response->status();

$this->currentTransaction->spans = $this->spans;

$this->recordTransaction($this->currentTransaction);

$this->spans = [];
$this->currentTransaction = null;
}

public function addSpan(Span $span): void
{
$this->spans[] = $span;
}

public function getDuration(): float
{
return $this->currentTransaction
? ($this->currentTransaction->end ?? microtime(true)) - $this->currentTransaction->start
: 0;
}

public function getMemoryUsage(): int
{
return memory_get_usage(true);
}

public function getQueryCount(): int
{
return count(array_filter($this->spans, fn($s) => $s->name === 'db.query'));
}

protected function recordTransaction(Transaction $transaction): void
{
$duration = ($transaction->end - $transaction->start) * 1000;

if ($duration > config('performance.slow_request_threshold', 1000)) {
Log::channel('performance')->warning('Slow request detected', [
'transaction' => $transaction->toArray(),
'duration_ms' => $duration,
]);
}

$this->storeMetrics($transaction);
}

protected function storeMetrics(Transaction $transaction): void
{
$metrics = [
'response_time' => ($transaction->end - $transaction->start) * 1000,
'memory_peak' => memory_get_peak_usage(true),
'query_count' => $this->getQueryCount(),
'request_id' => $transaction->id,
];

cache()->put(
'performance:' . $transaction->id,
$metrics,
now()->addHours(1)
);
}
}

class Transaction
{
public function __construct(
public string $id,
public string $name,
public string $type,
public float $start,
public ?float $end = null,
public mixed $result = null,
public array $context = [],
public array $spans = []
) {}

public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'type' => $this->type,
'start' => $this->start,
'end' => $this->end,
'duration' => $this->end ? ($this->end - $this->start) * 1000 : null,
'result' => $this->result,
'context' => $this->context,
'spans' => array_map(fn($s) => $s->toArray(), $this->spans),
];
}
}

class Span
{
public function __construct(
public string $name,
public float $start,
public ?float $duration = null,
public array $context = []
) {}

public function toArray(): array
{
return [
'name' => $this->name,
'start' => $this->start,
'duration' => $this->duration,
'context' => $this->context,
];
}
}

数据库性能监控

查询性能分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php

namespace App\Services\Performance;

use Illuminate\Support\Facades\DB;

class QueryProfiler
{
protected array $queries = [];
protected bool $enabled = true;

public function enable(): void
{
$this->enabled = true;

DB::listen(function ($query) {
if (!$this->enabled) {
return;
}

$this->queries[] = [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time,
'connection' => $query->connectionName,
'backtrace' => $this->getBacktrace(),
];
});
}

public function disable(): void
{
$this->enabled = false;
}

public function getQueries(): array
{
return $this->queries;
}

public function getSlowQueries(float $threshold = 100): array
{
return array_filter($this->queries, fn($q) => $q['time'] > $threshold);
}

public function getDuplicateQueries(): array
{
$counts = [];

foreach ($this->queries as $query) {
$key = md5($query['sql'] . serialize($query['bindings']));
$counts[$key][] = $query;
}

return array_filter($counts, fn($queries) => count($queries) > 1);
}

public function getNPlusOneQueries(): array
{
$patterns = [];

foreach ($this->queries as $query) {
$pattern = preg_replace('/\d+/', '?', $query['sql']);
$patterns[$pattern][] = $query;
}

$nplusone = [];

foreach ($patterns as $pattern => $queries) {
if (count($queries) > 5) {
$nplusone[] = [
'pattern' => $pattern,
'count' => count($queries),
'sample' => $queries[0],
];
}
}

return $nplusone;
}

public function getSummary(): array
{
$totalTime = array_sum(array_column($this->queries, 'time'));

return [
'total_queries' => count($this->queries),
'total_time' => $totalTime,
'slow_queries' => count($this->getSlowQueries()),
'duplicate_queries' => count($this->getDuplicateQueries()),
'n_plus_one_suspects' => count($this->getNPlusOneQueries()),
];
}

protected function getBacktrace(): array
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);

return array_filter($trace, function ($item) {
$file = $item['file'] ?? '';
return str_contains($file, app_path());
});
}
}

缓存性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php

namespace App\Services\Performance;

use Illuminate\Support\Facades\Cache;

class CacheProfiler
{
protected array $hits = [];
protected array $misses = [];
protected array $writes = [];

public function recordHit(string $key, float $time): void
{
$this->hits[] = [
'key' => $key,
'time' => $time,
'timestamp' => microtime(true),
];
}

public function recordMiss(string $key, float $time): void
{
$this->misses[] = [
'key' => $key,
'time' => $time,
'timestamp' => microtime(true),
];
}

public function recordWrite(string $key, float $time): void
{
$this->writes[] = [
'key' => $key,
'time' => $time,
'timestamp' => microtime(true),
];
}

public function getHitRate(): float
{
$total = count($this->hits) + count($this->misses);

if ($total === 0) {
return 0.0;
}

return count($this->hits) / $total;
}

public function getStats(): array
{
return [
'hits' => count($this->hits),
'misses' => count($this->misses),
'writes' => count($this->writes),
'hit_rate' => $this->getHitRate(),
'avg_hit_time' => $this->averageTime($this->hits),
'avg_miss_time' => $this->averageTime($this->misses),
];
}

protected function averageTime(array $records): float
{
if (empty($records)) {
return 0.0;
}

return array_sum(array_column($records, 'time')) / count($records);
}
}

性能指标存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?php

namespace App\Services\Performance;

use Illuminate\Support\Facades\Redis;

class MetricsStore
{
protected string $prefix = 'performance:metrics:';
protected int $retention = 3600;

public function store(string $name, float $value, array $tags = []): void
{
$key = $this->prefix . $name;
$timestamp = time();

$data = json_encode([
'value' => $value,
'tags' => $tags,
'timestamp' => $timestamp,
]);

Redis::zadd($key, $timestamp, $data);
Redis::expire($key, $this->retention);
}

public function getRange(string $name, int $from, int $to): array
{
$key = $this->prefix . $name;

$results = Redis::zrangebyscore($key, $from, $to);

return array_map(fn($r) => json_decode($r, true), $results);
}

public function getAverage(string $name, int $seconds = 60): float
{
$from = time() - $seconds;
$to = time();

$values = $this->getRange($name, $from, $to);

if (empty($values)) {
return 0.0;
}

return array_sum(array_column($values, 'value')) / count($values);
}

public function getPercentile(string $name, float $percentile, int $seconds = 60): float
{
$from = time() - $seconds;
$to = time();

$values = $this->getRange($name, $from, $to);

if (empty($values)) {
return 0.0;
}

$sorted = array_column($values, 'value');
sort($sorted);

$index = (int) ceil($percentile / 100 * count($sorted)) - 1;

return $sorted[$index] ?? 0.0;
}

public function increment(string $name, int $value = 1, array $tags = []): void
{
$key = $this->prefix . 'counter:' . $name . ':' . date('YmdH');

Redis::incrby($key, $value);
Redis::expire($key, $this->retention);
}

public function getCounter(string $name, int $hours = 1): int
{
$total = 0;

for ($i = 0; $i < $hours; $i++) {
$key = $this->prefix . 'counter:' . $name . ':' . date('YmdH', strtotime("-{$i} hours"));
$total += (int) Redis::get($key) ?: 0;
}

return $total;
}
}

性能仪表板数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php

namespace App\Services\Performance;

class DashboardData
{
public function __construct(
protected MetricsStore $metrics
) {}

public function getOverview(): array
{
return [
'response_time' => [
'avg' => $this->metrics->getAverage('response_time', 300),
'p50' => $this->metrics->getPercentile('response_time', 50, 300),
'p95' => $this->metrics->getPercentile('response_time', 95, 300),
'p99' => $this->metrics->getPercentile('response_time', 99, 300),
],
'throughput' => [
'requests_per_minute' => $this->metrics->getCounter('requests', 1) / 60,
'requests_per_hour' => $this->metrics->getCounter('requests', 1),
],
'errors' => [
'total' => $this->metrics->getCounter('errors', 1),
'rate' => $this->calculateErrorRate(),
],
'memory' => [
'avg' => $this->metrics->getAverage('memory_usage', 300),
'peak' => $this->metrics->getAverage('memory_peak', 300),
],
];
}

public function getResponseTimeDistribution(): array
{
$buckets = [
'0-100ms' => 0,
'100-300ms' => 0,
'300-500ms' => 0,
'500-1000ms' => 0,
'1000-3000ms' => 0,
'3000ms+' => 0,
];

$values = $this->metrics->getRange('response_time', time() - 3600, time());

foreach ($values as $data) {
$ms = $data['value'];

if ($ms < 100) $buckets['0-100ms']++;
elseif ($ms < 300) $buckets['100-300ms']++;
elseif ($ms < 500) $buckets['300-500ms']++;
elseif ($ms < 1000) $buckets['500-1000ms']++;
elseif ($ms < 3000) $buckets['1000-3000ms']++;
else $buckets['3000ms+']++;
}

return $buckets;
}

public function getSlowEndpoints(int $limit = 10): array
{
$endpoints = [];

$values = $this->metrics->getRange('response_time', time() - 3600, time());

foreach ($values as $data) {
$endpoint = $data['tags']['endpoint'] ?? 'unknown';

if (!isset($endpoints[$endpoint])) {
$endpoints[$endpoint] = [
'endpoint' => $endpoint,
'count' => 0,
'total_time' => 0,
'max_time' => 0,
];
}

$endpoints[$endpoint]['count']++;
$endpoints[$endpoint]['total_time'] += $data['value'];
$endpoints[$endpoint]['max_time'] = max(
$endpoints[$endpoint]['max_time'],
$data['value']
);
}

return collect($endpoints)
->map(fn($e) => [
...$e,
'avg_time' => $e['total_time'] / $e['count'],
])
->sortByDesc('avg_time')
->take($limit)
->values()
->toArray();
}

protected function calculateErrorRate(): float
{
$requests = $this->metrics->getCounter('requests', 1);
$errors = $this->metrics->getCounter('errors', 1);

if ($requests === 0) {
return 0.0;
}

return $errors / $requests;
}
}

性能报告命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php

namespace App\Console\Commands;

use App\Services\Performance\DashboardData;
use Illuminate\Console\Command;

class PerformanceReportCommand extends Command
{
protected $signature = 'performance:report {--period=1 : Period in hours}';
protected $description = 'Generate performance report';

public function handle(DashboardData $dashboard): int
{
$this->info('=== Performance Report ===');
$this->newLine();

$overview = $dashboard->getOverview();

$this->info('Response Time:');
$this->table(
['Metric', 'Value'],
[
['Average', round($overview['response_time']['avg'], 2) . 'ms'],
['P50', round($overview['response_time']['p50'], 2) . 'ms'],
['P95', round($overview['response_time']['p95'], 2) . 'ms'],
['P99', round($overview['response_time']['p99'], 2) . 'ms'],
]
);

$this->newLine();

$this->info('Throughput:');
$this->table(
['Metric', 'Value'],
[
['Requests/min', round($overview['throughput']['requests_per_minute'], 2)],
['Requests/hour', $overview['throughput']['requests_per_hour']],
]
);

$this->newLine();

$this->info('Response Time Distribution:');
$distribution = $dashboard->getResponseTimeDistribution();
$this->table(
['Bucket', 'Count'],
collect($distribution)->map(fn($count, $bucket) => [$bucket, $count])
);

$this->newLine();

$this->info('Slowest Endpoints:');
$slowEndpoints = $dashboard->getSlowEndpoints();
$this->table(
['Endpoint', 'Avg Time', 'Max Time', 'Count'],
collect($slowEndpoints)->map(fn($e) => [
$e['endpoint'],
round($e['avg_time'], 2) . 'ms',
round($e['max_time'], 2) . 'ms',
$e['count'],
])
);

return self::SUCCESS;
}
}

总结

Laravel 13 的性能监控需要覆盖请求处理、数据库查询、缓存使用等多个方面。通过完善的性能监控体系,可以及时发现性能瓶颈,优化系统响应速度,提升用户体验。