|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Get profiles using PDO database connection |
|
4
|
|
|
*/ |
|
5
|
|
|
class Xhgui_Storage_PDO extends Xhgui_Storage_Abstract implements |
|
6
|
|
|
\Xhgui_StorageInterface, |
|
7
|
|
|
\Xhgui_WatchedFunctionsStorageInterface |
|
8
|
|
|
{ |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* @var \PDO |
|
12
|
|
|
*/ |
|
13
|
|
|
protected $connection; |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* PDO constructor. |
|
17
|
|
|
* @param $config |
|
18
|
|
|
*/ |
|
19
|
|
|
public function __construct($config) |
|
20
|
|
|
{ |
|
21
|
|
|
$this->connection = new \PDO( |
|
22
|
|
|
$config['db.dsn'], |
|
23
|
|
|
!empty($config['db.user']) ? $config['db.user'] : null, |
|
24
|
|
|
!empty($config['db.password']) ? $config['db.password'] : null, |
|
25
|
|
|
!empty($config['db.options']) ? $config['db.options'] : [] |
|
26
|
|
|
); |
|
27
|
|
|
$this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); |
|
28
|
|
|
$this->connection->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); |
|
29
|
|
|
} |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* @inheritDoc |
|
33
|
|
|
* @param \Xhgui_Storage_Filter $filter |
|
34
|
|
|
* @param bool $projections |
|
35
|
|
|
* @return \Xhgui_Storage_ResultSet |
|
36
|
|
|
*/ |
|
37
|
|
|
public function find(\Xhgui_Storage_Filter $filter, $projections = false) |
|
38
|
|
|
{ |
|
39
|
|
|
list($query, $params) = $this->getQuery($filter, false); |
|
40
|
|
|
|
|
41
|
|
|
try { |
|
42
|
|
|
$stmt = $this->connection->prepare($query); |
|
43
|
|
|
$stmt->execute($params); |
|
44
|
|
|
} catch (\Exception $e) { |
|
45
|
|
|
print_r($e->getMessage()); |
|
46
|
|
|
exit; |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
$tmp = []; |
|
50
|
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
|
51
|
|
|
$tmp[$row['id']] = $row; |
|
52
|
|
|
$tmp[$row['id']]['profile'] = json_decode($row['profiles'], true); |
|
53
|
|
|
$tmp[$row['id']]['meta'] = json_decode($row['meta'], true); |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
return new \Xhgui_Storage_ResultSet($tmp); |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Get query that is used for both list and count |
|
61
|
|
|
* |
|
62
|
|
|
* @param \Xhgui_Storage_Filter $filter |
|
63
|
|
|
* @param bool $count |
|
64
|
|
|
* @return array |
|
65
|
|
|
*/ |
|
66
|
|
|
protected function getQuery(\Xhgui_Storage_Filter $filter, $count = false) |
|
67
|
|
|
{ |
|
68
|
|
|
$params = []; |
|
69
|
|
|
|
|
70
|
|
|
if ($count === true) { |
|
71
|
|
|
$columns = ' count(*) as c '; |
|
72
|
|
|
} else { |
|
73
|
|
|
$columns = ' p.*, i.*, m.*, p.profile_id as _id, main_wt as duration '; |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
$sql = " |
|
77
|
|
|
select |
|
78
|
|
|
$columns |
|
79
|
|
|
from |
|
80
|
|
|
profiles as p left join |
|
81
|
|
|
profiles_info as i on (p.profile_id = i.id) LEFT JOIN |
|
82
|
|
|
profiles_meta as m on (p.profile_id = m.profile_id) |
|
83
|
|
|
"; |
|
84
|
|
|
|
|
85
|
|
|
$where = []; |
|
86
|
|
|
|
|
87
|
|
|
foreach ([ |
|
88
|
|
|
'url' => 'url', |
|
89
|
|
|
'method' => 'method', |
|
90
|
|
|
'application' => 'application', |
|
91
|
|
|
'version' => 'version', |
|
92
|
|
|
'branch' => 'branch', |
|
93
|
|
|
'controller' => 'controller', |
|
94
|
|
|
'action' => 'action', |
|
95
|
|
|
] as $dbField => $field) { |
|
96
|
|
|
$method = 'get'.ucfirst($field); |
|
97
|
|
|
|
|
98
|
|
|
if ($filter->{$method}()) { |
|
99
|
|
|
switch($field) { |
|
100
|
|
|
case 'url': |
|
101
|
|
|
$url = $filter->{$method}(); |
|
102
|
|
|
$where[] = ' ( url like :url OR simple_url like :simple_url)'; |
|
103
|
|
|
$params['url'] = '%'.$url.'%'; |
|
104
|
|
|
$params['simple_url'] = '%'.$url.'%'; |
|
105
|
|
|
break; |
|
106
|
|
|
|
|
107
|
|
|
case 'action': |
|
108
|
|
View Code Duplication |
case 'controller': |
|
|
|
|
|
|
109
|
|
|
$where[] = ' '.$dbField.' like :'.$field.' '; |
|
110
|
|
|
$params[$field] = ($filter->{$method}()).'%'; |
|
111
|
|
|
break; |
|
112
|
|
|
|
|
113
|
|
View Code Duplication |
default: |
|
|
|
|
|
|
114
|
|
|
$where[] = ' '.$dbField.' = :'.$field.' '; |
|
115
|
|
|
$params[$field] = $filter->{$method}(); |
|
116
|
|
|
break; |
|
117
|
|
|
} |
|
118
|
|
|
} |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
if ($filter->getStartDate()) { |
|
122
|
|
|
$where[] = ' request_time >= :startDate'; |
|
123
|
|
|
$params['startDate'] = $this->getDateTimeFromString($filter->getStartDate(), 'start') |
|
|
|
|
|
|
124
|
|
|
->format('Y-m-d H:i:s'); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
if ($filter->getEndDate()) { |
|
128
|
|
|
$where[] = ' request_time <= :endDate'; |
|
129
|
|
|
$params['endDate'] = $this->getDateTimeFromString($filter->getEndDate(), 'end') |
|
|
|
|
|
|
130
|
|
|
->format('Y-m-d H:i:s'); |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
if (!empty($where)) { |
|
134
|
|
|
$sql .= ' WHERE '.join(' AND ', $where); |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
if ($count === true) { |
|
138
|
|
|
return [$sql, $params]; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
switch ($filter->getSort()) { |
|
142
|
|
|
case 'ct': |
|
143
|
|
|
$sql .= ' order by main_ct'; |
|
144
|
|
|
break; |
|
145
|
|
|
|
|
146
|
|
|
case 'wt': |
|
147
|
|
|
$sql .= ' order by main_wt'; |
|
148
|
|
|
break; |
|
149
|
|
|
|
|
150
|
|
|
case 'cpu': |
|
151
|
|
|
$sql .= ' order by main_cpu'; |
|
152
|
|
|
break; |
|
153
|
|
|
|
|
154
|
|
|
case 'mu': |
|
155
|
|
|
$sql .= ' order by main_mu'; |
|
156
|
|
|
break; |
|
157
|
|
|
|
|
158
|
|
|
case 'pmu': |
|
159
|
|
|
$sql .= ' order by main_pmu'; |
|
160
|
|
|
break; |
|
161
|
|
|
|
|
162
|
|
|
case 'controller': |
|
163
|
|
|
$sql .= ' order by controller'; |
|
164
|
|
|
break; |
|
165
|
|
|
|
|
166
|
|
|
case 'action': |
|
167
|
|
|
$sql .= ' order by action'; |
|
168
|
|
|
break; |
|
169
|
|
|
|
|
170
|
|
|
case 'application': |
|
171
|
|
|
$sql .= ' order by application'; |
|
172
|
|
|
break; |
|
173
|
|
|
|
|
174
|
|
|
case 'branch': |
|
175
|
|
|
$sql .= ' order by branch'; |
|
176
|
|
|
break; |
|
177
|
|
|
|
|
178
|
|
|
case 'version': |
|
179
|
|
|
$sql .= ' order by version'; |
|
180
|
|
|
break; |
|
181
|
|
|
|
|
182
|
|
|
case 'time': |
|
183
|
|
|
default: |
|
184
|
|
|
$sql .= ' order by request_time'; |
|
185
|
|
|
break; |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
switch ($filter->getDirection()) { |
|
189
|
|
|
case 'asc': |
|
190
|
|
|
$sql .= ' asc '; |
|
191
|
|
|
break; |
|
192
|
|
|
|
|
193
|
|
|
default: |
|
194
|
|
|
case 'desc': |
|
|
|
|
|
|
195
|
|
|
$sql .= ' desc '; |
|
196
|
|
|
break; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
if ($filter->getPerPage()) { |
|
200
|
|
|
$sql .= ' LIMIT :limit '; |
|
201
|
|
|
$params['limit'] = (int)$filter->getPerPage(); |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
if ($filter->getPage()) { |
|
205
|
|
|
$sql .= ' OFFSET :offset '; |
|
206
|
|
|
$params['offset'] = (int)($filter->getPerPage()*($filter->getPage()-1)); |
|
207
|
|
|
} |
|
208
|
|
|
return [$sql, $params]; |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* @inheritDoc |
|
213
|
|
|
* @param Xhgui_Storage_Filter $filter |
|
214
|
|
|
* @param $col |
|
215
|
|
|
* @param int $percentile |
|
216
|
|
|
* @return array |
|
217
|
|
|
* @throws Exception |
|
218
|
|
|
*/ |
|
219
|
|
|
public function aggregate(\Xhgui_Storage_Filter $filter, $col, $percentile = 1) |
|
220
|
|
|
{ |
|
221
|
|
|
$stmt = $this->connection->prepare('select |
|
222
|
|
|
* |
|
223
|
|
|
from |
|
224
|
|
|
profiles_info |
|
225
|
|
|
where |
|
226
|
|
|
url = :url |
|
227
|
|
|
'); |
|
228
|
|
|
$stmt->execute(['url'=> $filter->getUrl()]); |
|
229
|
|
|
$aggregatedData = []; |
|
230
|
|
|
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
|
231
|
|
|
$date = new \DateTime($row['request_time']); |
|
232
|
|
|
$formattedDate = $date->format('Y-m-d H:i'); |
|
233
|
|
|
if (empty($aggregatedData[$date->format('Y-m-d H:i')])) { |
|
234
|
|
|
$aggregatedData[$date->format('Y-m-d H:i')] = [ |
|
235
|
|
|
'wall_times' => [], |
|
236
|
|
|
'cpu_times' => [], |
|
237
|
|
|
'mu_times' => [], |
|
238
|
|
|
'pmu_times' => [], |
|
239
|
|
|
'row_count' => 0 |
|
240
|
|
|
]; |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
$aggregatedData[$formattedDate]['wall_times'][] = $row['main_wt']; |
|
244
|
|
|
$aggregatedData[$formattedDate]['cpu_times'][] = $row['main_ct']; |
|
245
|
|
|
$aggregatedData[$formattedDate]['mu_times'][] = $row['main_mu']; |
|
246
|
|
|
$aggregatedData[$formattedDate]['pmu_times'][] = $row['main_pmu']; |
|
247
|
|
|
$aggregatedData[$formattedDate]['row_count']++; |
|
248
|
|
|
$aggregatedData[$formattedDate]['_id'] = $date->format('Y-m-d H:i:s'); |
|
249
|
|
|
$aggregatedData[$formattedDate]['raw_index'] = |
|
250
|
|
|
$aggregatedData[$formattedDate]['row_count']*($percentile/100); |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
$return = [ |
|
254
|
|
|
'ok' => 1, |
|
255
|
|
|
'result'=> array_values($aggregatedData), |
|
256
|
|
|
]; |
|
257
|
|
|
return $return; |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
/** |
|
261
|
|
|
* @inheritDoc |
|
262
|
|
|
* @@param Xhgui_Storage_Filter $filter |
|
263
|
|
|
* @return int |
|
264
|
|
|
*/ |
|
265
|
|
|
public function count(\Xhgui_Storage_Filter $filter) |
|
266
|
|
|
{ |
|
267
|
|
|
list($query, $params) = $this->getQuery($filter, true); |
|
268
|
|
|
try { |
|
269
|
|
|
$stmt = $this->connection->prepare($query); |
|
270
|
|
|
$stmt->execute($params); |
|
271
|
|
|
} catch (\Exception $e) { |
|
272
|
|
|
print_r($e->getMessage()); |
|
273
|
|
|
exit; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
$ret = $stmt->fetch(\PDO::FETCH_ASSOC); |
|
277
|
|
|
|
|
278
|
|
|
if (!empty($ret['c'])) { |
|
279
|
|
|
return $ret['c']; |
|
280
|
|
|
} |
|
281
|
|
|
return 0; |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
/** |
|
285
|
|
|
* @inheritDoc |
|
286
|
|
|
* @param $id |
|
287
|
|
|
* @return mixed |
|
288
|
|
|
*/ |
|
289
|
|
|
public function findOne($id) |
|
290
|
|
|
{ |
|
291
|
|
|
$stmt = $this->connection->prepare(' |
|
292
|
|
|
select |
|
293
|
|
|
* |
|
294
|
|
|
from |
|
295
|
|
|
profiles as p left join |
|
296
|
|
|
profiles_info as i on (p.profile_id = i.id) LEFT JOIN |
|
297
|
|
|
profiles_meta as m on (p.profile_id = m.profile_id) |
|
298
|
|
|
where |
|
299
|
|
|
p.profile_id = :id |
|
300
|
|
|
'); |
|
301
|
|
|
|
|
302
|
|
|
$stmt->execute(['id'=>$id]); |
|
303
|
|
|
$row = $stmt->fetch(\PDO::FETCH_ASSOC); |
|
304
|
|
|
$row['profile'] = json_decode($row['profiles'], true); |
|
305
|
|
|
$row['meta'] = json_decode($row['meta'], true); |
|
306
|
|
|
$row['_id'] = $id; |
|
307
|
|
|
|
|
308
|
|
|
return $row; |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
/** |
|
312
|
|
|
* @inheritDoc |
|
313
|
|
|
* @param $id |
|
314
|
|
|
*/ |
|
315
|
|
|
public function remove($id) |
|
316
|
|
|
{ |
|
317
|
|
|
$this->connection->beginTransaction(); |
|
318
|
|
|
try { |
|
319
|
|
|
$profileStmt = $this->connection->prepare('delete from profiles where profile_id = :id'); |
|
320
|
|
|
$profileStmt->execute(['id'=>$id]); |
|
321
|
|
|
|
|
322
|
|
|
$metaStmt = $this->connection->prepare('delete from profiles_meta where profile_id = :id'); |
|
323
|
|
|
$metaStmt->execute(['id'=>$id]); |
|
324
|
|
|
|
|
325
|
|
|
$infoStmt = $this->connection->prepare('delete from profiles_info where id = :id'); |
|
326
|
|
|
$infoStmt->execute(['id'=>$id]); |
|
327
|
|
|
|
|
328
|
|
|
$this->connection->commit(); |
|
329
|
|
|
} catch (\Exception $e) { |
|
330
|
|
|
$this->connection->rollBack(); |
|
331
|
|
|
} |
|
332
|
|
|
} |
|
333
|
|
|
|
|
334
|
|
|
/** |
|
335
|
|
|
* @inheritDoc |
|
336
|
|
|
* Remove all data from profile tables |
|
337
|
|
|
*/ |
|
338
|
|
|
public function drop() |
|
339
|
|
|
{ |
|
340
|
|
|
$this->connection->exec('delete from profiles'); |
|
341
|
|
|
$this->connection->exec('delete from profiles_meta'); |
|
342
|
|
|
$this->connection->exec('delete from profiles_info'); |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
/** |
|
346
|
|
|
* @inheritDoc |
|
347
|
|
|
* @return array |
|
348
|
|
|
*/ |
|
349
|
|
|
public function getWatchedFunctions() |
|
350
|
|
|
{ |
|
351
|
|
|
$stmt = $this->connection->query('select * from watched order by name desc'); |
|
352
|
|
|
return $stmt->fetchAll(\PDO::FETCH_ASSOC); |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
/** |
|
356
|
|
|
* @inheritDoc |
|
357
|
|
|
* @param $name |
|
358
|
|
|
* @return bool |
|
359
|
|
|
*/ |
|
360
|
|
|
public function addWatchedFunction($name) |
|
361
|
|
|
{ |
|
362
|
|
|
$name = trim($name); |
|
363
|
|
|
if (empty($name)) { |
|
364
|
|
|
return false; |
|
365
|
|
|
} |
|
366
|
|
|
$stmt = $this->connection->prepare('INSERT INTO watched (name) VALUES (:name)'); |
|
367
|
|
|
$stmt->execute(['name'=>trim($name)]); |
|
368
|
|
|
return true; |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
/** |
|
372
|
|
|
* @inheritDoc |
|
373
|
|
|
* @param $id |
|
374
|
|
|
* @param $name |
|
375
|
|
|
*/ |
|
376
|
|
|
public function updateWatchedFunction($id, $name) |
|
377
|
|
|
{ |
|
378
|
|
|
$stmt = $this->connection->prepare('update watched set name=:name where id = :id'); |
|
379
|
|
|
$stmt->execute(['id'=>$id, 'name'=>$name]); |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
/** |
|
383
|
|
|
* @inheritDoc |
|
384
|
|
|
* @param $id |
|
385
|
|
|
*/ |
|
386
|
|
|
public function removeWatchedFunction($id) |
|
387
|
|
|
{ |
|
388
|
|
|
$stmt = $this->connection->prepare('delete from watched where id = :id'); |
|
389
|
|
|
$stmt->execute(['id'=>$id]); |
|
390
|
|
|
} |
|
391
|
|
|
} |
|
392
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.