1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Dtc\QueueBundle\Controller; |
4
|
|
|
|
5
|
|
|
use Doctrine\ODM\MongoDB\DocumentManager; |
6
|
|
|
use Doctrine\ORM\EntityManager; |
7
|
|
|
use Dtc\QueueBundle\Doctrine\DoctrineJobTimingManager; |
8
|
|
|
use Dtc\QueueBundle\Model\JobTiming; |
9
|
|
|
use Dtc\QueueBundle\ODM\JobManager; |
10
|
|
|
use Dtc\QueueBundle\ODM\JobTimingManager; |
11
|
|
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; |
12
|
|
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; |
13
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
14
|
|
|
use Symfony\Component\HttpFoundation\JsonResponse; |
15
|
|
|
use Symfony\Component\HttpFoundation\Request; |
16
|
|
|
|
17
|
|
|
class TrendsController extends Controller |
18
|
|
|
{ |
19
|
|
|
use ControllerTrait; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Show a graph of job trends. |
23
|
|
|
* |
24
|
|
|
* @Route("/trends", name="dtc_queue_trends") |
25
|
|
|
* @Template("@DtcQueue/Queue/trends.html.twig") |
26
|
|
|
*/ |
27
|
1 |
|
public function trendsAction() |
28
|
|
|
{ |
29
|
1 |
|
$recordTimings = $this->container->getParameter('dtc_queue.timings.record'); |
30
|
1 |
|
$params = ['record_timings' => $recordTimings, 'states' => JobTiming::getStates()]; |
31
|
1 |
|
$this->addCssJs($params); |
32
|
|
|
|
33
|
1 |
|
return $params; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @Route("/timings", name="dtc_queue_timings") |
38
|
|
|
*/ |
39
|
1 |
|
public function getTimingsAction(Request $request) |
40
|
|
|
{ |
41
|
1 |
|
$begin = $request->query->get('begin'); |
42
|
1 |
|
$end = $request->query->get('end'); |
43
|
1 |
|
$type = $request->query->get('type', 'HOUR'); |
44
|
1 |
|
$beginDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $begin) ?: null; |
45
|
1 |
|
$endDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.uO', $end) ?: \Dtc\QueueBundle\Util\Util::getMicrotimeDateTime(); |
46
|
|
|
|
47
|
1 |
|
$recordTimings = $this->container->getParameter('dtc_queue.timings.record'); |
48
|
1 |
|
$params = []; |
49
|
1 |
|
if ($recordTimings) { |
50
|
1 |
|
$params = $this->calculateTimings($type, $beginDate, $endDate); |
51
|
|
|
} |
52
|
|
|
|
53
|
1 |
|
return new JsonResponse($params); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @param \DateTime|null $beginDate |
58
|
|
|
* @param \DateTime $endDate |
59
|
|
|
*/ |
60
|
1 |
|
protected function calculateTimings($type, $beginDate, $endDate) |
61
|
|
|
{ |
62
|
1 |
|
$params = []; |
63
|
1 |
|
$this->validateJobTimingManager(); |
64
|
|
|
|
65
|
|
|
/** @var DoctrineJobTimingManager $jobTimingManager */ |
66
|
1 |
|
$jobTimingManager = $this->get('dtc_queue.manager.job_timing'); |
67
|
1 |
|
if ($jobTimingManager instanceof JobTimingManager) { |
68
|
1 |
|
$timings = $this->getJobTimingsOdm($type, $endDate, $beginDate); |
69
|
|
|
} else { |
70
|
1 |
|
$timings = $this->getJobTimingsOrm($type, $endDate, $beginDate); |
71
|
|
|
} |
72
|
|
|
|
73
|
1 |
|
$timingStates = JobTiming::getStates(); |
74
|
1 |
|
$timingsDates = []; |
75
|
1 |
|
foreach (array_keys($timingStates) as $state) { |
76
|
1 |
|
if (!isset($timings[$state])) { |
77
|
1 |
|
continue; |
78
|
|
|
} |
79
|
1 |
|
$timingsData = $timings[$state]; |
80
|
1 |
|
$timingsDates = array_unique(array_merge(array_keys($timingsData), $timingsDates)); |
81
|
|
|
} |
82
|
|
|
|
83
|
1 |
|
$format = $this->getDateFormat($type); |
84
|
1 |
|
usort($timingsDates, function($date1str, $date2str) use ($format) { |
85
|
|
|
$date1 = \DateTime::createFromFormat($format, $date1str); |
86
|
|
|
$date2 = \DateTime::createFromFormat($format, $date2str); |
87
|
|
|
if (!$date2) { |
88
|
|
|
return false; |
89
|
|
|
} |
90
|
|
|
if (!$date1) { |
91
|
|
|
return false; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return $date1 > $date2; |
95
|
1 |
|
}); |
96
|
|
|
|
97
|
1 |
|
$timingsDatesAdjusted = $this->getTimingsDatesAdjusted($timingsDates, $format); |
98
|
1 |
|
$this->setTimingsData($timingStates, $timings, $timingsDates, $params); |
99
|
1 |
|
$params['timings_dates'] = $timingsDates; |
100
|
1 |
|
$params['timings_dates_rfc3339'] = $timingsDatesAdjusted; |
101
|
|
|
|
102
|
1 |
|
return $params; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Timings offset by timezone if necessary. |
107
|
|
|
* |
108
|
|
|
* @param array $timingsDates |
109
|
|
|
* @param string $format |
110
|
|
|
* |
111
|
|
|
* @return array |
112
|
|
|
*/ |
113
|
1 |
|
protected function getTimingsDatesAdjusted(array $timingsDates, $format) |
114
|
|
|
{ |
115
|
1 |
|
$timezoneOffset = $this->container->getParameter('dtc_queue.timings.timezone_offset'); |
116
|
1 |
|
$timingsDatesAdjusted = []; |
117
|
1 |
|
foreach ($timingsDates as $dateStr) { |
118
|
1 |
|
$date = \DateTime::createFromFormat($format, $dateStr); |
119
|
1 |
|
if (0 !== $timezoneOffset) { |
120
|
|
|
// This may too simplistic in areas that observe DST - does the database or PHP code observe DST? |
121
|
|
|
$date->setTimestamp($date->getTimestamp() + ($timezoneOffset * 3600)); |
122
|
|
|
} |
123
|
1 |
|
if (false === $date) { |
124
|
|
|
throw new \InvalidArgumentException("'$date' is not in the right format: ".DATE_RFC3339); |
125
|
|
|
} |
126
|
1 |
|
$timingsDatesAdjusted[] = $date->format(DATE_RFC3339); |
127
|
|
|
} |
128
|
|
|
|
129
|
1 |
|
return $timingsDatesAdjusted; |
130
|
|
|
} |
131
|
|
|
|
132
|
1 |
|
protected function setTimingsData(array $timingStates, array $timings, array $timingsDates, array &$params) |
133
|
|
|
{ |
134
|
1 |
|
foreach (array_keys($timingStates) as $state) { |
135
|
1 |
|
if (!isset($timings[$state])) { |
136
|
1 |
|
continue; |
137
|
|
|
} |
138
|
|
|
|
139
|
1 |
|
$timingsData = $timings[$state]; |
140
|
1 |
|
foreach ($timingsDates as $date) { |
141
|
1 |
|
$params['timings_data_'.$state][] = isset($timingsData[$date]) ? $timingsData[$date] : 0; |
142
|
|
|
} |
143
|
|
|
} |
144
|
1 |
|
} |
145
|
|
|
|
146
|
1 |
|
protected function getJobTimingsOdm($type, \DateTime $end, \DateTime $begin = null) |
147
|
|
|
{ |
148
|
|
|
/** @var JobTimingManager $runManager */ |
149
|
1 |
|
$jobTimingManager = $this->get('dtc_queue.manager.job_timing'); |
150
|
1 |
|
$jobTimingClass = $jobTimingManager->getJobTimingClass(); |
151
|
|
|
|
152
|
|
|
/** @var DocumentManager $documentManager */ |
153
|
1 |
|
$documentManager = $jobTimingManager->getObjectManager(); |
154
|
|
|
|
155
|
1 |
|
$regexInfo = $this->getRegexDate($type); |
156
|
1 |
|
if (!$begin) { |
157
|
1 |
|
$begin = clone $end; |
158
|
1 |
|
$begin->sub($regexInfo['interval']); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// Run a map reduce function get worker and status break down |
162
|
|
|
$mapFunc = 'function() { |
163
|
|
|
var dateStr = this.finishedAt.toISOString(); |
164
|
1 |
|
dateStr = dateStr.replace(/'.$regexInfo['replace']['regex']."/,'".$regexInfo['replace']['replacement']."'); |
165
|
1 |
|
var dateBegin = new Date('".$begin->format('c')."'); |
166
|
1 |
|
var dateEnd = new Date('".$end->format('c')."'); |
167
|
|
|
if (this.finishedAt >= dateBegin && this.finishedAt <= dateEnd) { |
168
|
|
|
var result = {}; |
169
|
|
|
result[dateStr] = 1; |
170
|
|
|
emit(this.status, result); |
171
|
|
|
} |
172
|
|
|
}"; |
173
|
1 |
|
$reduceFunc = JobManager::REDUCE_FUNCTION; |
174
|
1 |
|
$builder = $documentManager->createQueryBuilder($jobTimingClass); |
175
|
1 |
|
$builder->map($mapFunc) |
176
|
1 |
|
->reduce($reduceFunc); |
177
|
1 |
|
$query = $builder->getQuery(); |
178
|
1 |
|
$results = $query->execute(); |
179
|
1 |
|
$resultHash = []; |
180
|
1 |
|
foreach ($results as $info) { |
181
|
1 |
|
$resultHash[$info['_id']] = $info['value']; |
182
|
|
|
} |
183
|
|
|
|
184
|
1 |
|
return $resultHash; |
185
|
|
|
} |
186
|
|
|
|
187
|
1 |
|
protected function getDateFormat($type) |
188
|
|
|
{ |
189
|
|
|
switch ($type) { |
190
|
1 |
|
case 'YEAR': |
191
|
1 |
|
return 'Y'; |
192
|
1 |
|
case 'MONTH': |
193
|
1 |
|
return 'Y-m'; |
194
|
1 |
|
case 'DAY': |
195
|
1 |
|
return 'Y-m-d'; |
196
|
1 |
|
case 'HOUR': |
197
|
1 |
|
return 'Y-m-d H'; |
198
|
1 |
|
case 'MINUTE': |
199
|
1 |
|
return 'Y-m-d H:i'; |
200
|
|
|
default: |
201
|
|
|
throw new \InvalidArgumentException("Invalid date format type '$type''"); |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
|
205
|
1 |
|
protected function getRegexDate($type) |
206
|
|
|
{ |
207
|
|
|
switch ($type) { |
208
|
1 |
View Code Duplication |
case 'YEAR': |
|
|
|
|
209
|
1 |
|
return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1'], |
210
|
1 |
|
'interval' => new \DateInterval('P10Y'), ]; |
211
|
1 |
View Code Duplication |
case 'MONTH': |
|
|
|
|
212
|
1 |
|
return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2'], |
213
|
1 |
|
'interval' => new \DateInterval('P12M'), ]; |
214
|
1 |
View Code Duplication |
case 'DAY': |
|
|
|
|
215
|
1 |
|
return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3'], |
216
|
1 |
|
'interval' => new \DateInterval('P31D'), ]; |
217
|
1 |
View Code Duplication |
case 'HOUR': |
|
|
|
|
218
|
1 |
|
return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4'], |
219
|
1 |
|
'interval' => new \DateInterval('PT24H'), ]; |
220
|
1 |
View Code Duplication |
case 'MINUTE': |
|
|
|
|
221
|
1 |
|
return ['replace' => ['regex' => '(\d+)\-(\d+)\-(\d+)T(\d+):(\d+):(\d+).+$', 'replacement' => '$1-$2-$3 $4:$5'], |
222
|
1 |
|
'interval' => new \DateInterval('PT3600S'), ]; |
223
|
|
|
} |
224
|
|
|
throw new \InvalidArgumentException("Invalid type $type"); |
225
|
|
|
} |
226
|
|
|
|
227
|
1 |
|
protected function getOrmGroupBy($type) |
228
|
|
|
{ |
229
|
|
|
switch ($type) { |
230
|
1 |
|
case 'YEAR': |
231
|
1 |
|
return ['groupby' => 'YEAR(j.finishedAt)', |
232
|
1 |
|
'interval' => new \DateInterval('P10Y'), ]; |
233
|
1 |
|
case 'MONTH': |
234
|
1 |
|
return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt))', |
235
|
1 |
|
'interval' => new \DateInterval('P12M'), ]; |
236
|
1 |
|
case 'DAY': |
237
|
1 |
|
return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt))', |
238
|
1 |
|
'interval' => new \DateInterval('P31D'), ]; |
239
|
1 |
|
case 'HOUR': |
240
|
1 |
|
return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt))', |
241
|
1 |
|
'interval' => new \DateInterval('PT24H'), ]; |
242
|
1 |
|
case 'MINUTE': |
243
|
1 |
|
return ['groupby' => 'CONCAT(YEAR(j.finishedAt),\'-\',MONTH(j.finishedAt),\'-\',DAY(j.finishedAt),\' \',HOUR(j.finishedAt),\':\',MINUTE(j.finishedAt))', |
244
|
1 |
|
'interval' => new \DateInterval('PT3600S'), ]; |
245
|
|
|
} |
246
|
|
|
throw new \InvalidArgumentException("Invalid type $type"); |
247
|
|
|
} |
248
|
|
|
|
249
|
1 |
|
protected function getJobTimingsOrm($type, \DateTime $end, \DateTime $begin = null) |
250
|
|
|
{ |
251
|
|
|
/** @var JobTimingManager $jobTimingManager */ |
252
|
1 |
|
$jobTimingManager = $this->get('dtc_queue.manager.job_timing'); |
253
|
1 |
|
$jobTimingClass = $jobTimingManager->getJobTimingClass(); |
254
|
|
|
/** @var EntityManager $entityManager */ |
255
|
1 |
|
$entityManager = $jobTimingManager->getObjectManager(); |
256
|
|
|
|
257
|
1 |
|
$groupByInfo = $this->getOrmGroupBy($type); |
258
|
|
|
|
259
|
1 |
|
if (!$begin) { |
260
|
1 |
|
$begin = clone $end; |
261
|
1 |
|
$begin->sub($groupByInfo['interval']); |
262
|
|
|
} |
263
|
|
|
|
264
|
1 |
|
$queryBuilder = $entityManager->createQueryBuilder()->select("j.status as status, count(j.finishedAt) as thecount, {$groupByInfo['groupby']} as thedate") |
265
|
1 |
|
->from($jobTimingClass, 'j') |
266
|
1 |
|
->where('j.finishedAt <= :end') |
267
|
1 |
|
->andWhere('j.finishedAt >= :begin') |
268
|
1 |
|
->setParameter(':end', $end) |
269
|
1 |
|
->setParameter(':begin', $begin) |
270
|
1 |
|
->groupBy('status') |
271
|
1 |
|
->addGroupBy('thedate'); |
272
|
|
|
|
273
|
|
|
$result = $queryBuilder |
274
|
1 |
|
->getQuery()->getArrayResult(); |
275
|
|
|
|
276
|
1 |
|
$resultHash = []; |
277
|
1 |
|
foreach ($result as $row) { |
278
|
1 |
|
$date = $this->formatOrmDateTime($type, $row['thedate']); |
279
|
1 |
|
$resultHash[$row['status']][$date] = intval($row['thecount']); |
280
|
|
|
} |
281
|
|
|
|
282
|
1 |
|
return $resultHash; |
283
|
|
|
} |
284
|
|
|
|
285
|
1 |
|
protected function strPadLeft($str) |
286
|
|
|
{ |
287
|
1 |
|
return str_pad($str, 2, '0', STR_PAD_LEFT); |
288
|
|
|
} |
289
|
|
|
|
290
|
1 |
|
protected function formatOrmDateTime($type, $str) |
291
|
|
|
{ |
292
|
|
|
switch ($type) { |
293
|
1 |
|
case 'MONTH': |
294
|
1 |
|
$parts = explode('-', $str); |
295
|
|
|
|
296
|
1 |
|
return $parts[0].'-'.$this->strPadLeft($parts[1]); |
297
|
1 |
|
case 'DAY': |
298
|
1 |
|
$parts = explode('-', $str); |
299
|
|
|
|
300
|
1 |
|
return $parts[0].'-'.$this->strPadLeft($parts[1]).'-'.$this->strPadLeft($parts[2]); |
301
|
1 |
|
case 'HOUR': |
302
|
1 |
|
$parts = explode(' ', $str); |
303
|
1 |
|
$dateParts = explode('-', $parts[0]); |
304
|
|
|
|
305
|
1 |
|
return $dateParts[0].'-'.$this->strPadLeft($dateParts[1]).'-'.$this->strPadLeft($dateParts[2]).' '.$this->strPadLeft($parts[1]); |
306
|
1 |
|
case 'MINUTE': |
307
|
1 |
|
$parts = explode(' ', $str); |
308
|
1 |
|
$dateParts = explode('-', $parts[0]); |
309
|
1 |
|
$timeParts = explode(':', $parts[1]); |
310
|
|
|
|
311
|
1 |
|
return $dateParts[0].'-'.$this->strPadLeft($dateParts[1]).'-'.$this->strPadLeft($dateParts[2]).' '.$this->strPadLeft($timeParts[0]).':'.$this->strPadLeft($timeParts[1]); |
312
|
|
|
} |
313
|
|
|
|
314
|
1 |
|
return $str; |
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
|
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.