-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAnalyser.php
277 lines (239 loc) · 7.24 KB
/
Analyser.php
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/php
<?php
require_once "Database.php";
/**
* The Analyzer class.
*/
class Analyzer
{
private $fp;
private $db;
private $stmt;
public function __construct(Database $db)
{
// create db object
$this->db = $db;
}
public function __destruct()
{
@fclose($this->fp);
$this->db = null;
}
/**
* Load a MySQL slow log.
*
* @param string $file Slow query log file
*/
public function load($file, $append = false)
{
// create the database
$this->db->prepare("CREATE TABLE IF NOT EXISTS queries (id INTEGER NOT NULL AUTO_INCREMENT, time INTEGER NOT NULL, user VARCHAR(128), host VARCHAR(512), ip VARCHAR(128), execute_time TEXT, lock_time TEXT, rows_sent INTEGER, rows_examined INTEGER, query TEXT, PRIMARY KEY (id))")->execute();
// open up the slow log for reading
$this->fp = fopen($file, 'rb');
$this->stmt = $this->db->prepare("INSERT INTO queries (time, user, host, ip, execute_time, lock_time, rows_sent, rows_examined, query) VALUES (:time, :user, :host, :ip, :execute_time, :lock_time, :rows_sent, :rows_examined, :query)");
// read off until first Time section
while ($line = fgets($this->fp))
{
if (preg_match('/^# Time: (\d{6}) (\d{2}):(\d{2}):(\d{2})/', $line)) {
$this->read_log_file($line);
return;
}
}
}
/**
* Read a section of the slow query log.
*
* @param string $time_line The first line of the section (# Time)
*/
private function read_log_file($time_line)
{
// parse datetime info
preg_match("/^# Time:[ ]+(\d{6})[ ]+(\d{1,2}):(\d{1,2}):(\d{1,2})/", $time_line, $matches);
$time = date(
"U",
mktime(
$matches[2], // hour
$matches[3], // minute
$matches[4], // second
substr($matches[1], 2, 2), // month
substr($matches[1], 4, 2), // day
substr($matches[1], 0, 2) // year
)
);
$query = array();
$raw_query = '';
// read lines until next time section
while ($line = fgets($this->fp))
{
if (preg_match('/^# Time: (\d{6}) (\d{2}):(\d{2}):(\d{2})/', $line))
{
$query['query'] = $raw_query;
$this->stmt->execute($query);
$this->read_log_file($line);
return;
}
elseif (preg_match('/^# User@Host: (\w+)\[\w+\] @ (.+) \[([^\[]*)\]/', $line, $matches))
{
if (!empty($raw_query))
{
$query['query'] = $raw_query;
$this->stmt->execute($query);
$query = array();
$raw_query = '';
}
$query['time'] = $time;
$query['user'] = $matches[1];
$query['host'] = $matches[2];
$query['ip'] = $matches[3];
}
elseif (preg_match('/^# Query_time: ([\d|\.]+)\s+Lock_time: ([\d|\.]+)\s+Rows_sent: (\d+)\s+Rows_examined: (\d+)/', $line, $matches))
{
$query['execute_time'] = $matches[1];
$query['lock_time'] = $matches[2];
$query['rows_sent'] = $matches[3];
$query['rows_examined'] = $matches[4];
}
elseif (!preg_match('/^SET timestamp=/', $line) && !preg_match('/^use /', $line) && !preg_match('/^#/', $line))
{
// we want to get rid of SET timestamp
// Percona adds extra data, for now let's ignore it
$raw_query .= preg_replace('/^(\s{16}|\t{4})/', '', $line);
}
}
}
/**
* Return slow queries.
*
* @param array $opts Options for slow query results
*/
public function results($opts)
{
$sql = "SELECT time,execute_time,lock_time,query FROM queries";
$start = $opts['start'] ?: $opts['s'];
$end = $opts['end'] ?: $opts['e'];
$lock_time = $opts['lock-time'] ?: $opts['l'];
$time = $opts['time'] ?: $opts['t'];
$order = $opts['order'] ?: $opts['o'];
$where = array();
$params = array();
if (!empty($start))
{
$where[] = "time >= :start";
$params['start'] = date("U", strtotime($start));
}
if (!empty($end))
{
$where[] = "time <= :end";
$params['end'] = date("U", strtotime($end));
}
if (!empty($lock_time))
{
$where[] = "lock_time >= :lock_time";
$params['lock_time'] = $lock_time;
}
if (!empty($time))
{
$where[] = "execute_time >= :execute_time";
$params['execute_time'] = $time;
}
if (!empty($where))
{
$sql .= " WHERE " . implode(" AND ", $where);
}
if (!empty($order)) {
$sql .= " ORDER BY :order DESC";
$params['order'] = $order;
}
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
if (empty($opts['export']))
{
return $this->print_results($stmt);
}
else
{
return $this->export_results($opts['export'], $stmt);
}
}
/**
* Print results to screen.
*
* @param object $stmt PDO prepared statement object
*/
private function print_results($stmt)
{
$stmt->setFetchMode(PDO::FETCH_ASSOC);
while ($row = $stmt->fetch())
{
echo sprintf("Time: %s\n", date("Y-m-d H:i:s", $row['time']));
echo sprintf("Execution Time: %s\n", $row['execute_time']);
echo sprintf("Lock Time: %s\n", $row['lock_time']);
echo sprintf("Query:\n%s%s\n", $row['query'], str_pad('', 20, '-'));
}
}
/**
* Export results to a CSV file.
*
* @param string $file CSV file.
* @param object $stmt PDO prepared statment object
*/
private function export_results($file, $stmt)
{
$stmt->setFetchMode(PDO::FETCH_ASSOC);
// open the csv for writing
$csv = fopen($file, 'w');
// set the headers
fputcsv($csv, array('Date/Time', 'Execution Time', 'Lock Time', 'Query'));
while ($row = $stmt->fetch())
{
fputcsv($csv, array(date("Y-m-d H:i:s", $row['time']), $row['execute_time'], $row['lock_time'], $row['query']));
}
}
}
/**
* The script.
*/
$opts = getopt('s:e:l:t:o:h', array('start:', 'end:', 'lock-time:', 'time:', 'export:', 'order:', 'skip-import', 'append', 'help'));
if (isset($opts['h']) || isset($opts['help'])) {
echo <<<MAN
NAME
slow-log-analyzer - Analyze a slow query log by date.
DESCRIPTION
slow-log-analyzer was designed to quickly find slow queries between two dates/times. It works by scanning a slow query log and importing information to a sqlite database and then running queries against that. It is written in PHP and needs the PDO extension.
USAGE
slow-log-analyzer [OPTIONS] [FILE]
OPTIONS
-s, --start
Show results later then this date.
-e, --end
Show results earlier then this date.
-l, --lock-time
Show only results that had a lock time greater then passed value.
-t, --time
Show only results that took greater then passed value.
-o, --order
Order results by field. (time, execute_time, lock_time)
--skip-import
Skip importing the log file into the db.
--append
Add slow log to current db (instead of clearing it).
--export
Export results (as CSV).
EXAMPLES:
slow-log-analyzer /path/to/slow-query.log
Default with no extra options.
slow-log-analyzer --lock-time 3 /path/to/slow-query.log
Show queries that locked for longer then 3 seconds.
slow-log-analyzer --skip-import --start="5/28 9:00" --end="5/28 8:00"
Don't reload the database, and find queries that were ran between 9 and 8 on 5/28
slow-log-analyzer --export slow.csv /path/to/slow-query.log
Export slow queries to a CSV file.
MAN;
exit;
}
$db = new Database;
$analyzer = new Analyzer($db);
if (!isset($opts['skip-import'])) {
$analyzer->load(array_pop($argv));
}
//$analyzer->results($opts);