1
|
|
|
<?php |
2
|
|
|
class Xhgui_Storage_PDO implements \Xhgui_StorageInterface, \Xhgui_WatchedFunctionsStorageInterface { |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* @var \PDO |
6
|
|
|
*/ |
7
|
|
|
protected $connection; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* PDO constructor. |
11
|
|
|
* @param $config |
12
|
|
|
*/ |
13
|
|
|
public function __construct($config) |
14
|
|
|
{ |
15
|
|
|
$this->connection = new \PDO( |
16
|
|
|
$config['db.dsn'], |
17
|
|
|
!empty($config['db.user']) ? $config['db.user'] : null, |
18
|
|
|
!empty($config['db.password']) ? $config['db.password'] : null, |
19
|
|
|
!empty($config['db.options']) ? $config['db.options'] : [] |
20
|
|
|
); |
21
|
|
|
$this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @param \Xhgui_Storage_Filter $filter |
26
|
|
|
* @param bool $projections |
27
|
|
|
* @return \Xhgui_Storage_ResultSet |
28
|
|
|
*/ |
29
|
|
|
public function find(\Xhgui_Storage_Filter $filter, $projections = false) |
30
|
|
|
{ |
31
|
|
|
list($query, $params) = $this->getQuery($filter, false); |
32
|
|
|
// print '<pre>'; |
33
|
|
|
// print_r($query); |
34
|
|
|
// print_r($params); |
35
|
|
|
// print '</pre>'; |
36
|
|
|
// exit; |
37
|
|
|
try { |
38
|
|
|
$stmt = $this->connection->prepare($query); |
39
|
|
|
$stmt->execute($params); |
40
|
|
|
|
41
|
|
|
} catch (\Exception $e) { |
42
|
|
|
print_r($e->getMessage()); |
43
|
|
|
exit; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
$tmp = []; |
47
|
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
48
|
|
|
$tmp[$row['id']] = $row; |
49
|
|
|
$tmp[$row['id']]['profile'] = json_decode($row['profiles'], true); |
50
|
|
|
$tmp[$row['id']]['meta'] = json_decode($row['meta'], true); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
return new \Xhgui_Storage_ResultSet($tmp); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @param \Xhgui_Storage_Filter $filter |
58
|
|
|
* @param bool $count |
59
|
|
|
* @return array |
60
|
|
|
*/ |
61
|
|
|
protected function getQuery(\Xhgui_Storage_Filter $filter, $count = false) { |
62
|
|
|
$params = []; |
63
|
|
|
|
64
|
|
|
if ($count === true) { |
65
|
|
|
$columns = ' count(*) as c '; |
66
|
|
|
} else { |
67
|
|
|
$columns = ' p.*, i.*, m.*, p.profile_id as _id, main_wt as duration '; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$sql = " |
71
|
|
|
select |
72
|
|
|
$columns |
73
|
|
|
from |
74
|
|
|
profiles as p left join |
75
|
|
|
profiles_info as i on (p.profile_id = i.id) LEFT JOIN |
76
|
|
|
profiles_meta as m on (p.profile_id = m.profile_id) |
77
|
|
|
"; |
78
|
|
|
|
79
|
|
|
$where = []; |
80
|
|
|
|
81
|
|
|
foreach([ |
82
|
|
|
'url' => 'url', |
83
|
|
|
'method' => 'method', |
84
|
|
|
'application' => 'application', |
85
|
|
|
'version' => 'version', |
86
|
|
|
'branch' => 'branch', |
87
|
|
|
'controller' => 'controller', |
88
|
|
|
'action' => 'action', |
89
|
|
|
] as $dbField => $field) { |
90
|
|
|
|
91
|
|
|
$method = 'get'.ucfirst($field); |
92
|
|
|
|
93
|
|
|
if ($filter->{$method}()) { |
94
|
|
|
$where[] = ' '.$dbField.' = :'.$field.' '; |
95
|
|
|
$params[$field] = $filter->{$method}(); |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
if ($filter->getStartDate()) { |
100
|
|
|
$where[] = ' request_time >= datetime(:startDate)'; |
101
|
|
|
$params['startDate'] = $this->getDateTimeFromString($filter->getStartDate())->format('Y-m-d H:i:s'); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
if ($filter->getEndDate()) { |
105
|
|
|
$where[] = ' request_time <= datetime(:endDate)'; |
106
|
|
|
$params['endDate'] = $this->getDateTimeFromString($filter->getEndDate())->format('Y-m-d H:i:s'); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
if (!empty($where)) { |
110
|
|
|
$sql .= ' WHERE '.join(' AND ', $where); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
if ($count === true) { |
114
|
|
|
return [$sql, $params]; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
switch ($filter->getSort()) { |
118
|
|
|
case 'ct': |
119
|
|
|
$sql .= ' order by main_ct'; |
120
|
|
|
break; |
121
|
|
|
|
122
|
|
|
case 'wt': |
123
|
|
|
$sql .= ' order by main_wt'; |
124
|
|
|
break; |
125
|
|
|
|
126
|
|
|
case 'cpu': |
127
|
|
|
$sql .= ' order by main_cpu'; |
128
|
|
|
break; |
129
|
|
|
|
130
|
|
|
case 'mu': |
131
|
|
|
$sql .= ' order by main_mu'; |
132
|
|
|
break; |
133
|
|
|
|
134
|
|
|
case 'pmu': |
135
|
|
|
$sql .= ' order by main_pmu'; |
136
|
|
|
break; |
137
|
|
|
|
138
|
|
|
case 'controller': |
139
|
|
|
$sql .= ' order by controller'; |
140
|
|
|
break; |
141
|
|
|
|
142
|
|
|
case 'action': |
143
|
|
|
$sql .= ' order by action'; |
144
|
|
|
break; |
145
|
|
|
|
146
|
|
|
case 'application': |
147
|
|
|
$sql .= ' order by application'; |
148
|
|
|
break; |
149
|
|
|
|
150
|
|
|
case 'branch': |
151
|
|
|
$sql .= ' order by branch'; |
152
|
|
|
break; |
153
|
|
|
|
154
|
|
|
case 'version': |
155
|
|
|
$sql .= ' order by version'; |
156
|
|
|
break; |
157
|
|
|
|
158
|
|
|
default: |
159
|
|
|
case 'time': |
|
|
|
|
160
|
|
|
$sql .= ' order by request_time'; |
161
|
|
|
break; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
switch ($filter->getDirection()) { |
165
|
|
|
case 'asc': |
166
|
|
|
$sql .= ' asc '; |
167
|
|
|
break; |
168
|
|
|
|
169
|
|
|
default: |
170
|
|
|
case 'desc': |
|
|
|
|
171
|
|
|
$sql .= ' desc '; |
172
|
|
|
break; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
if ($filter->getPerPage()) { |
176
|
|
|
$sql .= ' LIMIT :limit '; |
177
|
|
|
$params['limit'] = $filter->getPerPage(); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
if ($filter->getPage()) { |
181
|
|
|
$sql .= ' OFFSET :offset '; |
182
|
|
|
$params['offset'] = ($filter->getPerPage()*($filter->getPage()-1)); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return [$sql, $params]; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
public function aggregate(\Xhgui_Storage_Filter $filter, $col, $percentile = 1) |
189
|
|
|
{ |
190
|
|
|
$stmt = $this->connection->prepare('select |
191
|
|
|
* |
192
|
|
|
from |
193
|
|
|
profiles_info |
194
|
|
|
where |
195
|
|
|
url = :url |
196
|
|
|
'); |
197
|
|
|
$stmt->execute(['url'=> $filter->getUrl()]); |
198
|
|
|
$aggregatedData = []; |
199
|
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
200
|
|
|
$date = new \DateTime($row['request_time']); |
201
|
|
|
$formattedDate = $date->format('Y-m-d'); |
202
|
|
|
if (empty($aggregatedData[$date->format('Y-m-d')])) { |
203
|
|
|
$aggregatedData[$date->format('Y-m-d')] = [ |
204
|
|
|
'wall_times' => [], |
205
|
|
|
'cpu_times' => [], |
206
|
|
|
'mu_times' => [], |
207
|
|
|
'pmu_times' => [], |
208
|
|
|
'row_count' => 0 |
209
|
|
|
]; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$aggregatedData[$formattedDate]['wall_times'][] = $row['main_wt']; |
213
|
|
|
$aggregatedData[$formattedDate]['cpu_times'][] = $row['main_ct']; |
214
|
|
|
$aggregatedData[$formattedDate]['mu_times'][] = $row['main_mu']; |
215
|
|
|
$aggregatedData[$formattedDate]['pmu_times'][] = $row['main_pmu']; |
216
|
|
|
$aggregatedData[$formattedDate]['row_count']++; |
217
|
|
|
$aggregatedData[$formattedDate]['_id'] = $formattedDate; |
218
|
|
|
$aggregatedData[$formattedDate]['raw_index'] = $aggregatedData[$formattedDate]['row_count']*($percentile/100); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$return = [ |
222
|
|
|
'ok' => 1, |
223
|
|
|
'result'=> array_values($aggregatedData), |
224
|
|
|
]; |
225
|
|
|
return $return; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
|
229
|
|
|
public function count(\Xhgui_Storage_Filter $filter) { |
230
|
|
|
list($query, $params) = $this->getQuery($filter, true); |
231
|
|
|
try { |
232
|
|
|
$stmt = $this->connection->prepare($query); |
233
|
|
|
$stmt->execute($params); |
234
|
|
|
|
235
|
|
|
} catch (\Exception $e) { |
236
|
|
|
print_r($e->getMessage()); |
237
|
|
|
exit; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
$ret = $stmt->fetch(\PDO::FETCH_ASSOC); |
241
|
|
|
|
242
|
|
|
if (!empty($ret['c'])) { |
243
|
|
|
return $ret['c']; |
244
|
|
|
} |
245
|
|
|
return 0; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
public function findOne($id) |
249
|
|
|
{ |
250
|
|
|
$stmt = $this->connection->prepare(' |
251
|
|
|
select |
252
|
|
|
* |
253
|
|
|
from |
254
|
|
|
profiles as p left join |
255
|
|
|
profiles_info as i on (p.profile_id = i.id) LEFT JOIN |
256
|
|
|
profiles_meta as m on (p.profile_id = m.profile_id) |
257
|
|
|
where |
258
|
|
|
p.profile_id = :id |
259
|
|
|
'); |
260
|
|
|
|
261
|
|
|
$stmt->execute(['id'=>$id]); |
262
|
|
|
$row = $stmt->fetch(\PDO::FETCH_ASSOC); |
263
|
|
|
$row['profile'] = json_decode($row['profiles'], true); |
264
|
|
|
$row['meta'] = json_decode($row['meta'], true); |
265
|
|
|
$row['_id'] = $id; |
266
|
|
|
|
267
|
|
|
return $row; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
public function remove($id) |
271
|
|
|
{ |
272
|
|
|
$this->connection->beginTransaction(); |
273
|
|
|
try { |
274
|
|
|
$profileStmt = $this->connection->prepare('delete from profiles where profile_id = :id'); |
275
|
|
|
$profileStmt->execute(['id'=>$id]); |
276
|
|
|
|
277
|
|
|
$metaStmt = $this->connection->prepare('delete from profiles_meta where profile_id = :id'); |
278
|
|
|
$metaStmt->execute(['id'=>$id]); |
279
|
|
|
|
280
|
|
|
$infoStmt = $this->connection->prepare('delete from profiles_info where id = :id'); |
281
|
|
|
$infoStmt->execute(['id'=>$id]); |
282
|
|
|
|
283
|
|
|
$this->connection->commit(); |
284
|
|
|
} catch (\Exception $e) { |
285
|
|
|
$this->connection->rollBack(); |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* |
291
|
|
|
*/ |
292
|
|
|
public function drop() |
293
|
|
|
{ |
294
|
|
|
$this->connection->exec('delete from profiles'); |
295
|
|
|
$this->connection->exec('delete from profiles_meta'); |
296
|
|
|
$this->connection->exec('delete from profiles_info'); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
public function getWatchedFunctions() |
300
|
|
|
{ |
301
|
|
|
$stmt = $this->connection->query('select * from watched order by name desc'); |
302
|
|
|
return $stmt->fetchAll(\PDO::FETCH_ASSOC); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
public function addWatchedFunction($name) |
306
|
|
|
{ |
307
|
|
|
$name = trim($name); |
308
|
|
|
if (empty($name)) { |
309
|
|
|
return false; |
310
|
|
|
} |
311
|
|
|
$stmt = $this->connection->prepare('INSERT INTO watched (name) VALUES (:name)'); |
312
|
|
|
$stmt->execute(['name'=>trim($name)]); |
313
|
|
|
return true; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
public function updateWatchedFunction($id, $name) |
317
|
|
|
{ |
318
|
|
|
$stmt = $this->connection->prepare('update watched set name=:name where id = :id'); |
319
|
|
|
$stmt->execute(['id'=>$id, 'name'=>$name]); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
public function removeWatchedFunction($id) |
323
|
|
|
{ |
324
|
|
|
$stmt = $this->connection->prepare('delete from watched where id = :id'); |
325
|
|
|
$stmt->execute(['id'=>$id]); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* @param \Xhgui_Storage_Filter $options |
|
|
|
|
330
|
|
|
* @return \DateTime |
331
|
|
|
*/ |
332
|
|
|
protected function getDateTimeFromString($date) { |
333
|
|
|
|
334
|
|
|
try { |
335
|
|
|
$datetime = \DateTime::createFromFormat('Y-m-d H:i:s', $date); |
336
|
|
|
if ($datetime instanceof \DateTime) { |
337
|
|
|
return $datetime; |
338
|
|
|
} |
339
|
|
|
} catch (\Exception $e){ |
|
|
|
|
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
try { |
343
|
|
|
$datetime = \DateTime::createFromFormat('U', $date); |
344
|
|
|
if ($datetime instanceof \DateTime) { |
345
|
|
|
return $datetime; |
346
|
|
|
} |
347
|
|
|
} catch (\Exception $e){ |
|
|
|
|
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
throw new \InvalidArgumentException('Unable to parse date'); |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.