Merge lp:~qa-dashboard/qa-dashboard/idle-power into lp:qa-dashboard
- idle-power
- Merge into dev
Status: | Merged |
---|---|
Approved by: | Chris Johnston |
Approved revision: | 341 |
Merged at revision: | 341 |
Proposed branch: | lp:~qa-dashboard/qa-dashboard/idle-power |
Merge into: | lp:qa-dashboard |
Diff against target: |
1509 lines (+1285/-4) 25 files modified
common/management/__init__.py (+32/-0) common/management/commands/clear_items.py (+13/-0) common/management/commands/list_items.py (+12/-0) common/static/css/style.css (+40/-2) common/utils.py (+3/-0) idle_power/__init__.py (+14/-0) idle_power/admin.py (+77/-0) idle_power/management/__init__.py (+14/-0) idle_power/management/commands/__init__.py (+14/-0) idle_power/management/commands/jenkins_pull_idlepower.py (+208/-0) idle_power/migrations/0001_initial.py (+201/-0) idle_power/migrations/__init__.py (+14/-0) idle_power/models.py (+85/-0) idle_power/tables.py (+72/-0) idle_power/templates/idle_power/machine_raw_data.html (+37/-0) idle_power/templates/idle_power/machine_table.html (+30/-0) idle_power/templates/idle_power/metrics.html (+28/-0) idle_power/urls.py (+33/-0) idle_power/utah_utils.py (+112/-0) idle_power/views.py (+104/-0) power/templates/power/power_layout.html (+4/-1) qa_dashboard/settings.py (+1/-0) qa_dashboard/urls.py (+1/-0) scripts/fakeup_idle_power.py (+134/-0) smoke/utah_utils.py (+2/-1) |
To merge this branch: | bzr merge lp:~qa-dashboard/qa-dashboard/idle-power |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andy Doan (community) | Approve | ||
Review via email: mp+158831@code.launchpad.net |
Commit message
Adds power idle to the dashboard
Description of the change
- 336. By Chris Johnston
-
[r=Joe Talbott] Move power urls to power/urls.py, change /power/ and /api/power/ links to /power/software/ and /api/power/
software/ from Chris Johnston - 337. By Chris Johnston
-
[r=Andy Doan] Only run bzr pull, syncdb, migrate, collectstatic if there is a branch update. Also graceful apache from Chris Johnston
- 338. By Chris Johnston
-
Release version 2013.04.15
- 339. By Joe Talbott
-
[r=Andy Doan] Fix duplicate metrics due to recent utah log fix. from Joe Talbott
Andy Doan (doanac) wrote : | # |
Andy Doan (doanac) wrote : | # |
872 + def job_bugs(self):
guessing that's a copy/paste thing, but I don't think we have a use for this at the time, so should probably remove it.
1066 === added file 'idle_power/
that file is useless, i'd just kill it until there's something useful for it.
1202 + logging.error("{} {}".format(
1206 + logging.
bad logging form
def process_
This function is too large. You could probably break out the logic in the for loops that builds the detail object into its own function.
- 340. By Chris Johnston
-
[r=Joe Talbott] Fix issue where power url's show up as api/power/software from Chris Johnston
- 341. By Chris Johnston
-
resolve conflict
Andy Doan (doanac) : | # |
Preview Diff
1 | === modified file 'common/management/__init__.py' |
2 | --- common/management/__init__.py 2013-04-02 19:30:47 +0000 |
3 | +++ common/management/__init__.py 2013-04-16 20:51:23 +0000 |
4 | @@ -18,6 +18,7 @@ |
5 | import logging |
6 | import re |
7 | import urllib2 |
8 | +import yaml |
9 | |
10 | from django.core.management.base import BaseCommand |
11 | from optparse import make_option |
12 | @@ -113,6 +114,13 @@ |
13 | 'MemoryResult', |
14 | 'MemoryUpgrade', |
15 | ] |
16 | + elif arg == 'idlepower' or arg == 'idle_power': |
17 | + klass_list += [ |
18 | + 'IdlePowerImage', |
19 | + 'IdlePowerLog', |
20 | + 'IdlePowerMachine', |
21 | + 'IdlePowerMetric', |
22 | + ] |
23 | else: |
24 | klass_list.append(arg) |
25 | |
26 | @@ -170,6 +178,30 @@ |
27 | ) |
28 | |
29 | |
30 | +def get_kernel(build_url, artifact): |
31 | + """ Try to find a kernel in a utah log artifact. """ |
32 | + retval = "unknown" |
33 | + |
34 | + if artifact is None: |
35 | + return retval |
36 | + |
37 | + log_path = "{}artifact/{}".format( |
38 | + build_url, |
39 | + artifact['relativePath'], |
40 | + ) |
41 | + |
42 | + data = jenkins_get(log_path, as_json=False) |
43 | + try: |
44 | + data = yaml.load(data) |
45 | + except yaml.YAMLError: |
46 | + logging.warn("failed to parse data: %s", data) |
47 | + raise |
48 | + |
49 | + kernel = data['uname'][2] |
50 | + |
51 | + return kernel |
52 | + |
53 | + |
54 | def profile(fn): |
55 | """ Decorator for time profiling function calls. |
56 | |
57 | |
58 | === modified file 'common/management/commands/clear_items.py' |
59 | --- common/management/commands/clear_items.py 2013-03-01 22:09:19 +0000 |
60 | +++ common/management/commands/clear_items.py 2013-04-16 20:51:23 +0000 |
61 | @@ -51,6 +51,14 @@ |
62 | PowerMetric, |
63 | ) |
64 | |
65 | +from idle_power.models import ( |
66 | + IdlePowerBuild, |
67 | + IdlePowerDetail, |
68 | + IdlePowerImage, |
69 | + IdlePowerLog, |
70 | + IdlePowerMachine, |
71 | +) |
72 | + |
73 | from memory.models import ( |
74 | MemoryBuild, |
75 | MemoryDetail, |
76 | @@ -103,6 +111,11 @@ |
77 | MemoryDetail, |
78 | MemoryLog, |
79 | MemoryProcess, |
80 | + IdlePowerImage, |
81 | + IdlePowerLog, |
82 | + IdlePowerMachine, |
83 | + IdlePowerBuild, |
84 | + IdlePowerDetail, |
85 | ] |
86 | |
87 | verbosity = int(options.get('verbosity')) |
88 | |
89 | === modified file 'common/management/commands/list_items.py' |
90 | --- common/management/commands/list_items.py 2013-03-01 22:09:19 +0000 |
91 | +++ common/management/commands/list_items.py 2013-04-16 20:51:23 +0000 |
92 | @@ -64,6 +64,13 @@ |
93 | MemoryUpgrade, |
94 | ) |
95 | |
96 | +from idle_power.models import ( |
97 | + IdlePowerBuild, |
98 | + IdlePowerDetail, |
99 | + IdlePowerImage, |
100 | + IdlePowerLog, |
101 | + IdlePowerMachine, |
102 | +) |
103 | from common.management import process_class_list |
104 | |
105 | |
106 | @@ -103,6 +110,11 @@ |
107 | MemoryResult, |
108 | MemoryUpgrade, |
109 | MemoryProcess, |
110 | + IdlePowerImage, |
111 | + IdlePowerLog, |
112 | + IdlePowerMachine, |
113 | + IdlePowerBuild, |
114 | + IdlePowerDetail, |
115 | ] |
116 | |
117 | verbosity = int(options.get('verbosity')) |
118 | |
119 | === modified file 'common/static/css/style.css' |
120 | --- common/static/css/style.css 2013-04-11 15:40:42 +0000 |
121 | +++ common/static/css/style.css 2013-04-16 20:51:23 +0000 |
122 | @@ -400,6 +400,11 @@ |
123 | margin-top: 1.2em; /** 18px (desired value) / 15px (computed font-size value) */ |
124 | } |
125 | |
126 | +h5 { |
127 | + font-size : 1em; |
128 | + margin-bottom : 0.75em; |
129 | +} |
130 | + |
131 | p { |
132 | font-size: 1em; |
133 | line-height: 1.4; |
134 | @@ -415,8 +420,12 @@ |
135 | font-weight:bold; |
136 | } |
137 | |
138 | -table.basic thead a , table.basic tfoot a { |
139 | - text-decoration:underline; |
140 | +table.basic thead a, table.basic tfoot a { |
141 | + text-decoration: none; |
142 | +} |
143 | + |
144 | +table.basic thead a:hover, table.basic tfoot a:hover { |
145 | + text-decoration: underline; |
146 | } |
147 | |
148 | table.basic { |
149 | @@ -724,3 +733,32 @@ |
150 | width: 100%; |
151 | background-position:center; |
152 | } |
153 | + |
154 | +.box-padded { |
155 | + border-radius : 4px; |
156 | + background : #efefef; |
157 | + border : 0; |
158 | + margin-bottom : 20px; |
159 | + padding : 6px 5px 6px; |
160 | +} |
161 | + |
162 | +.box-padded h3 { |
163 | + font-size : 1.219em; |
164 | + margin-bottom : 0.615em; |
165 | + margin-left : 5px; |
166 | + margin-top : 5px; |
167 | +} |
168 | + |
169 | +.box-padded li h3 { |
170 | + font-size : 1.219em; |
171 | + margin-bottom : 0.615em; |
172 | + margin : 0; |
173 | +} |
174 | + |
175 | +.box-padded div { |
176 | + border-radius : 4px; |
177 | + background : #fff; |
178 | + overflow : hidden; |
179 | + padding : 8px 8px 2px; |
180 | +} |
181 | + |
182 | |
183 | === renamed file 'smoke/utah_parser.py' => 'common/utah_parser.py' |
184 | === modified file 'common/utils.py' |
185 | --- common/utils.py 2013-03-21 18:59:36 +0000 |
186 | +++ common/utils.py 2013-04-16 20:51:23 +0000 |
187 | @@ -28,6 +28,9 @@ |
188 | 'power': re.compile( |
189 | ur"^power-(milestone|backfill|raring)-(desktop)-([^-]*)-(.*)" |
190 | ), |
191 | + 'idlepower': re.compile( |
192 | + ur"^poweridle-(milestone|backfill|raring)-(desktop)-([^-]*)-(.*)" |
193 | + ), |
194 | 'upgrade': re.compile( |
195 | ur".*-upgrade-.*" |
196 | ), |
197 | |
198 | === added directory 'idle_power' |
199 | === added file 'idle_power/__init__.py' |
200 | --- idle_power/__init__.py 1970-01-01 00:00:00 +0000 |
201 | +++ idle_power/__init__.py 2013-04-16 20:51:23 +0000 |
202 | @@ -0,0 +1,14 @@ |
203 | +# QA Dashboard |
204 | +# Copyright 2012-2013 Canonical Ltd. |
205 | + |
206 | +# This program is free software: you can redistribute it and/or modify it |
207 | +# under the terms of the GNU Affero General Public License version 3, as |
208 | +# published by the Free Software Foundation. |
209 | + |
210 | +# This program is distributed in the hope that it will be useful, but |
211 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
212 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
213 | +# PURPOSE. See the GNU Affero General Public License for more details. |
214 | + |
215 | +# You should have received a copy of the GNU Affero General Public License |
216 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
217 | |
218 | === added file 'idle_power/admin.py' |
219 | --- idle_power/admin.py 1970-01-01 00:00:00 +0000 |
220 | +++ idle_power/admin.py 2013-04-16 20:51:23 +0000 |
221 | @@ -0,0 +1,77 @@ |
222 | +# QA Dashboard |
223 | +# Copyright 2013 Canonical Ltd. |
224 | + |
225 | +# This program is free software: you can redistribute it and/or modify it |
226 | +# under the terms of the GNU Affero General Public License version 3, as |
227 | +# published by the Free Software Foundation. |
228 | + |
229 | +# This program is distributed in the hope that it will be useful, but |
230 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
231 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
232 | +# PURPOSE. See the GNU Affero General Public License for more details. |
233 | + |
234 | +# You should have received a copy of the GNU Affero General Public License |
235 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
236 | + |
237 | +from .models import ( |
238 | + IdlePowerImage, |
239 | + IdlePowerMachine, |
240 | + IdlePowerLog, |
241 | + IdlePowerDetail, |
242 | + IdlePowerBuild, |
243 | +) |
244 | + |
245 | +from django.contrib import admin |
246 | + |
247 | + |
248 | +class IdlePowerMachineAdmin(admin.ModelAdmin): |
249 | + list_display = ('name', 'mac_address') |
250 | + search_fields = ['name', 'mac_address'] |
251 | + |
252 | + |
253 | +class IdlePowerLogAdmin(admin.ModelAdmin): |
254 | + list_display = ('name', 'path') |
255 | + search_fields = ['name', 'path'] |
256 | + |
257 | + |
258 | +class IdlePowerImageAdmin(admin.ModelAdmin): |
259 | + list_display = ( |
260 | + 'release', |
261 | + 'variant', |
262 | + 'arch', |
263 | + 'build_number', |
264 | + 'image_type', |
265 | + ) |
266 | + list_filter = [ |
267 | + 'release', |
268 | + 'variant', |
269 | + 'arch', |
270 | + 'build_number', |
271 | + ] |
272 | + search_fields = [ |
273 | + 'release', |
274 | + 'variant', |
275 | + 'arch', |
276 | + 'build_number', |
277 | + ] |
278 | + |
279 | + |
280 | +class IdlePowerDetailAdmin(admin.ModelAdmin): |
281 | + list_display = ( |
282 | + 'image', |
283 | + 'testcase', |
284 | + ) |
285 | + |
286 | + |
287 | +class IdlePowerBuildAdmin(admin.ModelAdmin): |
288 | + list_display = ( |
289 | + 'image', |
290 | + 'build', |
291 | + ) |
292 | + |
293 | + |
294 | +admin.site.register(IdlePowerMachine, IdlePowerMachineAdmin) |
295 | +admin.site.register(IdlePowerLog, IdlePowerLogAdmin) |
296 | +admin.site.register(IdlePowerImage, IdlePowerImageAdmin) |
297 | +admin.site.register(IdlePowerDetail, IdlePowerDetailAdmin) |
298 | +admin.site.register(IdlePowerBuild, IdlePowerBuildAdmin) |
299 | |
300 | === added directory 'idle_power/management' |
301 | === added file 'idle_power/management/__init__.py' |
302 | --- idle_power/management/__init__.py 1970-01-01 00:00:00 +0000 |
303 | +++ idle_power/management/__init__.py 2013-04-16 20:51:23 +0000 |
304 | @@ -0,0 +1,14 @@ |
305 | +# QA Dashboard |
306 | +# Copyright 2012-2013 Canonical Ltd. |
307 | + |
308 | +# This program is free software: you can redistribute it and/or modify it |
309 | +# under the terms of the GNU Affero General Public License version 3, as |
310 | +# published by the Free Software Foundation. |
311 | + |
312 | +# This program is distributed in the hope that it will be useful, but |
313 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
314 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
315 | +# PURPOSE. See the GNU Affero General Public License for more details. |
316 | + |
317 | +# You should have received a copy of the GNU Affero General Public License |
318 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
319 | |
320 | === added directory 'idle_power/management/commands' |
321 | === added file 'idle_power/management/commands/__init__.py' |
322 | --- idle_power/management/commands/__init__.py 1970-01-01 00:00:00 +0000 |
323 | +++ idle_power/management/commands/__init__.py 2013-04-16 20:51:23 +0000 |
324 | @@ -0,0 +1,14 @@ |
325 | +# QA Dashboard |
326 | +# Copyright 2012-2013 Canonical Ltd. |
327 | + |
328 | +# This program is free software: you can redistribute it and/or modify it |
329 | +# under the terms of the GNU Affero General Public License version 3, as |
330 | +# published by the Free Software Foundation. |
331 | + |
332 | +# This program is distributed in the hope that it will be useful, but |
333 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
334 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
335 | +# PURPOSE. See the GNU Affero General Public License for more details. |
336 | + |
337 | +# You should have received a copy of the GNU Affero General Public License |
338 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
339 | |
340 | === added file 'idle_power/management/commands/jenkins_pull_idlepower.py' |
341 | --- idle_power/management/commands/jenkins_pull_idlepower.py 1970-01-01 00:00:00 +0000 |
342 | +++ idle_power/management/commands/jenkins_pull_idlepower.py 2013-04-16 20:51:23 +0000 |
343 | @@ -0,0 +1,208 @@ |
344 | +# QA Dashboard |
345 | +# Copyright 2013 Canonical Ltd. |
346 | + |
347 | +# This program is free software: you can redistribute it and/or modify |
348 | +# it under the terms of the GNU Affero General Public License version |
349 | +# 3, as published by the Free Software Foundation. |
350 | + |
351 | +# This program is distributed in the hope that it will be useful, but |
352 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
353 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
354 | +# PURPOSE. See the GNU Affero General Public License for more details. |
355 | + |
356 | +# You should have received a copy of the GNU Affero General Public |
357 | +# License along with this program. If not, see |
358 | +# <http://www.gnu.org/licenses/>. |
359 | + |
360 | + |
361 | +import logging |
362 | +import re |
363 | + |
364 | +from common.management import ( |
365 | + JenkinsBaseCommand, |
366 | + get_kernel, |
367 | +) |
368 | + |
369 | +from common.utils import regexes |
370 | + |
371 | +from performance.management import get_image_build_number |
372 | + |
373 | +from idle_power.models import ( |
374 | + IdlePowerImage, |
375 | + IdlePowerLog, |
376 | + IdlePowerMachine, |
377 | + IdlePowerBuild, |
378 | +) |
379 | + |
380 | +from idle_power.utah_utils import process_idle_power_log |
381 | + |
382 | + |
383 | +def _get_power_logs(artifacts): |
384 | + """ Check for valid power results. |
385 | + |
386 | + :returns: a list of power logs. |
387 | + |
388 | + """ |
389 | + logs = [] |
390 | + utah_log_regex = re.compile(ur'^clientlogs\/utah.*.yaml') |
391 | + |
392 | + for artifact in artifacts: |
393 | + logging.debug(artifact['relativePath']) |
394 | + path = artifact['relativePath'] |
395 | + logging.debug("found %s", path) |
396 | + if utah_log_regex.match(path): |
397 | + logs.append(artifact) |
398 | + |
399 | + return logs |
400 | + |
401 | + |
402 | +def _process_utah_logs( |
403 | + logs, |
404 | + image, |
405 | + machine, |
406 | + jenkins_url=None, |
407 | + name=None, |
408 | +): |
409 | + |
410 | + details = [] |
411 | + |
412 | + for log in logs: |
413 | + logging.debug("log: %s", log) |
414 | + detail = process_idle_power_log( |
415 | + log, |
416 | + image, |
417 | + machine, |
418 | + jenkins_url=jenkins_url, |
419 | + name=name, |
420 | + ) |
421 | + |
422 | + if detail: |
423 | + details.append(detail) |
424 | + |
425 | + return details |
426 | + |
427 | + |
428 | +class Command(JenkinsBaseCommand): |
429 | + """ The command. """ |
430 | + |
431 | + job_regex = regexes['idlepower'] |
432 | + job_types = ['milestone', 'backfill'] |
433 | + |
434 | + def extract_data(self, name): |
435 | + m = self.job_regex.match(name) |
436 | + |
437 | + if m: |
438 | + self.release = m.group(1) |
439 | + self.variant = m.group(2) |
440 | + self.arch = m.group(3) |
441 | + self.machine_name = m.group(4) |
442 | + |
443 | + self.install_data = dict( |
444 | + release=self.release, |
445 | + variant=self.variant, |
446 | + arch=self.arch, |
447 | + machine=self.machine_name, |
448 | + ) |
449 | + |
450 | + def process_job(self, job): |
451 | + self.machine, new_machine = IdlePowerMachine.objects.get_or_create( |
452 | + name=self.machine_name, |
453 | + ) |
454 | + |
455 | + if new_machine: |
456 | + logging.debug("Added new machine: %s", self.machine_name) |
457 | + |
458 | + def _process_logs(self, build, build_date, install_data, func): |
459 | + |
460 | + if not hasattr(func, '__call__'): |
461 | + raise Exception("invalid callable: %s", func) |
462 | + |
463 | + logs = _get_power_logs(build['artifacts']) |
464 | + self.image_build_number = get_image_build_number(build) |
465 | + self.build_url = build['url'] |
466 | + for log in logs: |
467 | + kernel = get_kernel(build['url'], log) |
468 | + log_path = "{}artifact/{}".format( |
469 | + build['url'], |
470 | + log['relativePath'], |
471 | + ) |
472 | + dashboard_data = dict( |
473 | + build_date=build_date, |
474 | + build_number=self.image_build_number, |
475 | + log=log, |
476 | + log_path=log_path, |
477 | + release=install_data['release'], |
478 | + variant=install_data['variant'], |
479 | + arch=install_data['arch'], |
480 | + kernel=kernel, |
481 | + ) |
482 | + if 'jenkins_build' in install_data: |
483 | + dashboard_data['jenkins_build'] = install_data['jenkins_build'] |
484 | + |
485 | + func(build, dashboard_data) |
486 | + |
487 | + def add_result(self, build, dashboard_data): |
488 | + build_logs = [] |
489 | + for l in dashboard_data['log']: |
490 | + log, new_log = IdlePowerLog.objects.get_or_create( |
491 | + path=dashboard_data['log_path'], |
492 | + name=dashboard_data['log']['relativePath'], |
493 | + ) |
494 | + |
495 | + if new_log: |
496 | + logging.info("Adding new log: %s", log) |
497 | + |
498 | + build_logs.append(log) |
499 | + |
500 | + self.image, new_image = IdlePowerImage.objects.get_or_create( |
501 | + release=dashboard_data['release'], |
502 | + variant=dashboard_data['variant'], |
503 | + arch=dashboard_data['arch'], |
504 | + build_number=dashboard_data['build_number'], |
505 | + # fake an md5 since we don't have the data |
506 | + md5="{}{}{}{}".format( |
507 | + self.arch, |
508 | + dashboard_data['release'], |
509 | + self.variant, |
510 | + dashboard_data['build_number'], |
511 | + ) |
512 | + ) |
513 | + |
514 | + if new_image: |
515 | + logging.debug("Added new image: %s", self.image) |
516 | + |
517 | + build, new_build = IdlePowerBuild.objects.get_or_create( |
518 | + image=self.image, |
519 | + machine=self.machine, |
520 | + build=dashboard_data['jenkins_build'] |
521 | + ) |
522 | + |
523 | + if len(build_logs) > 0: |
524 | + _process_utah_logs( |
525 | + build_logs, |
526 | + image=self.image, |
527 | + machine=self.machine, |
528 | + jenkins_url=self.build_url, |
529 | + ) |
530 | + |
531 | + def remove_result(self, build, dashboard_data): |
532 | + """ Remove power data from the database """ |
533 | + logging.info("Removing power result") |
534 | + |
535 | + try: |
536 | + image = IdlePowerImage.objects.get( |
537 | + arch=self.arch, |
538 | + release=self.release, |
539 | + variant=self.variant, |
540 | + build_number=self.image_build_number, |
541 | + md5="{}{}{}{}".format( |
542 | + self.arch, |
543 | + self.release, |
544 | + self.variant, |
545 | + self.image_build_number, |
546 | + ) |
547 | + ) |
548 | + |
549 | + image.delete() |
550 | + except IdlePowerImage.DoesNotExist: |
551 | + pass |
552 | |
553 | === added directory 'idle_power/migrations' |
554 | === added file 'idle_power/migrations/0001_initial.py' |
555 | --- idle_power/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 |
556 | +++ idle_power/migrations/0001_initial.py 2013-04-16 20:51:23 +0000 |
557 | @@ -0,0 +1,201 @@ |
558 | +# -*- coding: utf-8 -*- |
559 | +import datetime |
560 | +from south.db import db |
561 | +from south.v2 import SchemaMigration |
562 | +from django.db import models |
563 | + |
564 | + |
565 | +class Migration(SchemaMigration): |
566 | + |
567 | + def forwards(self, orm): |
568 | + # Adding model 'IdlePowerImage' |
569 | + db.create_table('idle_power_images', ( |
570 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
571 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
572 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
573 | + ('internal', self.gf('django.db.models.fields.BooleanField')(default=True)), |
574 | + ('publish', self.gf('django.db.models.fields.BooleanField')(default=True)), |
575 | + ('release', self.gf('django.db.models.fields.CharField')(max_length=200)), |
576 | + ('variant', self.gf('django.db.models.fields.CharField')(max_length=200)), |
577 | + ('arch', self.gf('django.db.models.fields.CharField')(max_length=200)), |
578 | + ('md5', self.gf('django.db.models.fields.CharField')(unique=True, max_length=200)), |
579 | + ('build_number', self.gf('django.db.models.fields.CharField')(max_length=50)), |
580 | + ('image_type', self.gf('django.db.models.fields.CharField')(default=u'daily', max_length=10)), |
581 | + )) |
582 | + db.send_create_signal('idle_power', ['IdlePowerImage']) |
583 | + |
584 | + # Adding model 'IdlePowerMachine' |
585 | + db.create_table('idle_power_machines', ( |
586 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
587 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
588 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
589 | + ('internal', self.gf('django.db.models.fields.BooleanField')(default=True)), |
590 | + ('publish', self.gf('django.db.models.fields.BooleanField')(default=True)), |
591 | + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=200)), |
592 | + ('mac_address', self.gf('django.db.models.fields.CharField')(max_length=200)), |
593 | + )) |
594 | + db.send_create_signal('idle_power', ['IdlePowerMachine']) |
595 | + |
596 | + # Adding model 'IdlePowerLog' |
597 | + db.create_table('idle_power_logs', ( |
598 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
599 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
600 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
601 | + ('internal', self.gf('django.db.models.fields.BooleanField')(default=True)), |
602 | + ('publish', self.gf('django.db.models.fields.BooleanField')(default=True)), |
603 | + ('path', self.gf('django.db.models.fields.CharField')(max_length=4096)), |
604 | + ('name', self.gf('django.db.models.fields.CharField')(max_length=4096)), |
605 | + )) |
606 | + db.send_create_signal('idle_power', ['IdlePowerLog']) |
607 | + |
608 | + # Adding model 'IdlePowerDetail' |
609 | + db.create_table('idle_power_details', ( |
610 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
611 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
612 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
613 | + ('internal', self.gf('django.db.models.fields.BooleanField')(default=True)), |
614 | + ('publish', self.gf('django.db.models.fields.BooleanField')(default=True)), |
615 | + ('image', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['idle_power.IdlePowerImage'], null=True)), |
616 | + ('machine', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['idle_power.IdlePowerMachine'])), |
617 | + ('testcase', self.gf('django.db.models.fields.CharField')(max_length=4096)), |
618 | + ('calculated_energy', self.gf('django.db.models.fields.PositiveIntegerField')()), |
619 | + ('charge_start', self.gf('django.db.models.fields.PositiveIntegerField')()), |
620 | + ('charge_used', self.gf('django.db.models.fields.PositiveIntegerField')()), |
621 | + ('energy_start', self.gf('django.db.models.fields.PositiveIntegerField')()), |
622 | + ('energy_used', self.gf('django.db.models.fields.PositiveIntegerField')()), |
623 | + ('ran_at', self.gf('django.db.models.fields.DateTimeField')()), |
624 | + ('jenkins_url', self.gf('django.db.models.fields.URLField')(max_length=200)), |
625 | + )) |
626 | + db.send_create_signal('idle_power', ['IdlePowerDetail']) |
627 | + |
628 | + # Adding model 'IdlePowerBuild' |
629 | + db.create_table('idle_power_builds', ( |
630 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
631 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
632 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
633 | + ('internal', self.gf('django.db.models.fields.BooleanField')(default=True)), |
634 | + ('publish', self.gf('django.db.models.fields.BooleanField')(default=True)), |
635 | + ('image', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['idle_power.IdlePowerImage'], null=True)), |
636 | + ('machine', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['idle_power.IdlePowerMachine'])), |
637 | + ('build', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.JenkinsBuild'])), |
638 | + )) |
639 | + db.send_create_signal('idle_power', ['IdlePowerBuild']) |
640 | + |
641 | + |
642 | + def backwards(self, orm): |
643 | + # Deleting model 'IdlePowerImage' |
644 | + db.delete_table('idle_power_images') |
645 | + |
646 | + # Deleting model 'IdlePowerMachine' |
647 | + db.delete_table('idle_power_machines') |
648 | + |
649 | + # Deleting model 'IdlePowerLog' |
650 | + db.delete_table('idle_power_logs') |
651 | + |
652 | + # Deleting model 'IdlePowerDetail' |
653 | + db.delete_table('idle_power_details') |
654 | + |
655 | + # Deleting model 'IdlePowerBuild' |
656 | + db.delete_table('idle_power_builds') |
657 | + |
658 | + |
659 | + models = { |
660 | + 'common.bug': { |
661 | + 'Meta': {'object_name': 'Bug', 'db_table': "'bugs'"}, |
662 | + 'bug_no': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
663 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
664 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
665 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
666 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
667 | + 'status': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
668 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
669 | + }, |
670 | + 'common.jenkinsbuild': { |
671 | + 'Meta': {'unique_together': "(('job', 'build_number'),)", 'object_name': 'JenkinsBuild', 'db_table': "'jenkins_builds'"}, |
672 | + 'bugs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'build_bugs'", 'symmetrical': 'False', 'to': "orm['common.Bug']"}), |
673 | + 'build_number': ('django.db.models.fields.CharField', [], {'max_length': '50'}), |
674 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
675 | + 'failed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
676 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
677 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
678 | + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['common.JenkinsJob']"}), |
679 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
680 | + 'ran_at': ('django.db.models.fields.DateTimeField', [], {}), |
681 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
682 | + }, |
683 | + 'common.jenkinsjob': { |
684 | + 'Meta': {'object_name': 'JenkinsJob', 'db_table': "'jenkins_jobs'"}, |
685 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
686 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
687 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
688 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
689 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
690 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
691 | + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) |
692 | + }, |
693 | + 'idle_power.idlepowerbuild': { |
694 | + 'Meta': {'object_name': 'IdlePowerBuild', 'db_table': "'idle_power_builds'"}, |
695 | + 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['common.JenkinsBuild']"}), |
696 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
697 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
698 | + 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idle_power.IdlePowerImage']", 'null': 'True'}), |
699 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
700 | + 'machine': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idle_power.IdlePowerMachine']"}), |
701 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
702 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
703 | + }, |
704 | + 'idle_power.idlepowerdetail': { |
705 | + 'Meta': {'object_name': 'IdlePowerDetail', 'db_table': "'idle_power_details'"}, |
706 | + 'calculated_energy': ('django.db.models.fields.PositiveIntegerField', [], {}), |
707 | + 'charge_start': ('django.db.models.fields.PositiveIntegerField', [], {}), |
708 | + 'charge_used': ('django.db.models.fields.PositiveIntegerField', [], {}), |
709 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
710 | + 'energy_start': ('django.db.models.fields.PositiveIntegerField', [], {}), |
711 | + 'energy_used': ('django.db.models.fields.PositiveIntegerField', [], {}), |
712 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
713 | + 'image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idle_power.IdlePowerImage']", 'null': 'True'}), |
714 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
715 | + 'jenkins_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), |
716 | + 'machine': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idle_power.IdlePowerMachine']"}), |
717 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
718 | + 'ran_at': ('django.db.models.fields.DateTimeField', [], {}), |
719 | + 'testcase': ('django.db.models.fields.CharField', [], {'max_length': '4096'}), |
720 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
721 | + }, |
722 | + 'idle_power.idlepowerimage': { |
723 | + 'Meta': {'object_name': 'IdlePowerImage', 'db_table': "'idle_power_images'"}, |
724 | + 'arch': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
725 | + 'build_number': ('django.db.models.fields.CharField', [], {'max_length': '50'}), |
726 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
727 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
728 | + 'image_type': ('django.db.models.fields.CharField', [], {'default': "u'daily'", 'max_length': '10'}), |
729 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
730 | + 'md5': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
731 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
732 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
733 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
734 | + 'variant': ('django.db.models.fields.CharField', [], {'max_length': '200'}) |
735 | + }, |
736 | + 'idle_power.idlepowerlog': { |
737 | + 'Meta': {'object_name': 'IdlePowerLog', 'db_table': "'idle_power_logs'"}, |
738 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
739 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
740 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
741 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}), |
742 | + 'path': ('django.db.models.fields.CharField', [], {'max_length': '4096'}), |
743 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
744 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
745 | + }, |
746 | + 'idle_power.idlepowermachine': { |
747 | + 'Meta': {'object_name': 'IdlePowerMachine', 'db_table': "'idle_power_machines'"}, |
748 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
749 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
750 | + 'internal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
751 | + 'mac_address': ('django.db.models.fields.CharField', [], {'max_length': '200'}), |
752 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
753 | + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
754 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
755 | + } |
756 | + } |
757 | + |
758 | + complete_apps = ['idle_power'] |
759 | \ No newline at end of file |
760 | |
761 | === added file 'idle_power/migrations/__init__.py' |
762 | --- idle_power/migrations/__init__.py 1970-01-01 00:00:00 +0000 |
763 | +++ idle_power/migrations/__init__.py 2013-04-16 20:51:23 +0000 |
764 | @@ -0,0 +1,14 @@ |
765 | +# QA Dashboard |
766 | +# Copyright 2012-2013 Canonical Ltd. |
767 | + |
768 | +# This program is free software: you can redistribute it and/or modify it |
769 | +# under the terms of the GNU Affero General Public License version 3, as |
770 | +# published by the Free Software Foundation. |
771 | + |
772 | +# This program is distributed in the hope that it will be useful, but |
773 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
774 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
775 | +# PURPOSE. See the GNU Affero General Public License for more details. |
776 | + |
777 | +# You should have received a copy of the GNU Affero General Public License |
778 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
779 | |
780 | === added file 'idle_power/models.py' |
781 | --- idle_power/models.py 1970-01-01 00:00:00 +0000 |
782 | +++ idle_power/models.py 2013-04-16 20:51:23 +0000 |
783 | @@ -0,0 +1,85 @@ |
784 | +# QA Dashboard |
785 | +# Copyright 2012-2013 Canonical Ltd. |
786 | + |
787 | +# This program is free software: you can redistribute it and/or modify it |
788 | +# under the terms of the GNU Affero General Public License version 3, as |
789 | +# published by the Free Software Foundation. |
790 | + |
791 | +# This program is distributed in the hope that it will be useful, but |
792 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
793 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
794 | +# PURPOSE. See the GNU Affero General Public License for more details. |
795 | + |
796 | +# You should have received a copy of the GNU Affero General Public License |
797 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
798 | + |
799 | +from django.db import models |
800 | + |
801 | +from common.models import ( |
802 | + DashboardBaseModel, |
803 | + JenkinsBuild, |
804 | +) |
805 | + |
806 | +from performance.models import ( |
807 | + ImageBase, |
808 | + MachineBase, |
809 | +) |
810 | + |
811 | + |
812 | +class IdlePowerImage(ImageBase): |
813 | + """ Idle Power Image model. """ |
814 | + |
815 | + class Meta: |
816 | + db_table = "idle_power_images" |
817 | + |
818 | + |
819 | +class IdlePowerMachine(MachineBase): |
820 | + """ Idle Power Machine model. """ |
821 | + |
822 | + class Meta: |
823 | + db_table = "idle_power_machines" |
824 | + |
825 | + |
826 | +class IdlePowerLog(DashboardBaseModel): |
827 | + """ Idle Power logs for a build. """ |
828 | + |
829 | + class Meta: |
830 | + db_table = "idle_power_logs" |
831 | + |
832 | + path = models.CharField(max_length=4096) |
833 | + name = models.CharField(max_length=4096) |
834 | + |
835 | + def __unicode__(self): |
836 | + return "{}".format(self.name) |
837 | + |
838 | + |
839 | +class IdlePowerDetail(DashboardBaseModel): |
840 | + """ Idle Power raw data. """ |
841 | + |
842 | + class Meta: |
843 | + db_table = "idle_power_details" |
844 | + |
845 | + image = models.ForeignKey(IdlePowerImage, null=True) |
846 | + machine = models.ForeignKey(IdlePowerMachine) |
847 | + testcase = models.CharField(max_length=4096) |
848 | + calculated_energy = models.PositiveIntegerField() |
849 | + charge_start = models.PositiveIntegerField() |
850 | + charge_used = models.PositiveIntegerField() |
851 | + energy_start = models.PositiveIntegerField() |
852 | + energy_used = models.PositiveIntegerField() |
853 | + ran_at = models.DateTimeField("date run") |
854 | + jenkins_url = models.URLField() |
855 | + |
856 | + def __unicode__(self): |
857 | + return "{}".format(self.testcase) |
858 | + |
859 | + |
860 | +class IdlePowerBuild(DashboardBaseModel): |
861 | + """ Jenkins Build image. """ |
862 | + |
863 | + class Meta: |
864 | + db_table = "idle_power_builds" |
865 | + |
866 | + image = models.ForeignKey(IdlePowerImage, null=True) |
867 | + machine = models.ForeignKey(IdlePowerMachine) |
868 | + build = models.ForeignKey(JenkinsBuild) |
869 | |
870 | === added file 'idle_power/tables.py' |
871 | --- idle_power/tables.py 1970-01-01 00:00:00 +0000 |
872 | +++ idle_power/tables.py 2013-04-16 20:51:23 +0000 |
873 | @@ -0,0 +1,72 @@ |
874 | +# QA Dashboard |
875 | +# Copyright 2012-2013 Canonical Ltd. |
876 | + |
877 | +# This program is free software: you can redistribute it and/or modify it |
878 | +# under the terms of the GNU Affero General Public License version 3, as |
879 | +# published by the Free Software Foundation. |
880 | + |
881 | +# This program is distributed in the hope that it will be useful, but |
882 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
883 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
884 | +# PURPOSE. See the GNU Affero General Public License for more details. |
885 | + |
886 | +# You should have received a copy of the GNU Affero General Public License |
887 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
888 | + |
889 | +import django_tables2 as tables |
890 | +from django_tables2.utils import Accessor as A |
891 | + |
892 | + |
893 | +class MachineTable(tables.Table): |
894 | + build_number = tables.LinkColumn( |
895 | + 'idle_machine_raw_data', |
896 | + accessor='image__build_number', |
897 | + kwargs={ |
898 | + 'machine_id': A('machine__id'), |
899 | + 'arch': A('image__arch'), |
900 | + 'build_number': A('image__build_number'), |
901 | + }, |
902 | + verbose_name="Build Number", |
903 | + ) |
904 | + testcase = tables.Column(verbose_name="Test Case") |
905 | + energy_avg = tables.TemplateColumn( |
906 | + '{{ record.energy_avg|floatformat:2 }}', |
907 | + verbose_name="Average Energy used (mW)", |
908 | + attrs={'td': {'class': 'num'}}, |
909 | + ) |
910 | + energy_stddev = tables.TemplateColumn( |
911 | + '{{ record.energy_stddev|floatformat:2 }}', |
912 | + verbose_name="Standard Deviation", |
913 | + attrs={'td': {'class': 'num'}}, |
914 | + ) |
915 | + |
916 | + class Meta: |
917 | + attrs = {'class': 'basic'} |
918 | + |
919 | + |
920 | +class DataTable(tables.Table): |
921 | + testcase = tables.Column(verbose_name="Test Case") |
922 | + energy_start = tables.Column( |
923 | + verbose_name="Energy Start (mW)", |
924 | + attrs={'td': {'class': 'num'}}, |
925 | + ) |
926 | + energy_used = tables.Column( |
927 | + verbose_name="Energy used (mW)", |
928 | + attrs={'td': {'class': 'num'}}, |
929 | + ) |
930 | + calculated_energy = tables.Column( |
931 | + verbose_name="Calculated Energy (mW)", |
932 | + attrs={'td': {'class': 'num'}}, |
933 | + ) |
934 | + charge_start = tables.Column( |
935 | + verbose_name="Charge Start (mA)", |
936 | + attrs={'td': {'class': 'num'}}, |
937 | + ) |
938 | + charge_used = tables.Column( |
939 | + verbose_name="Charge Used (mA)", |
940 | + attrs={'td': {'class': 'num'}}, |
941 | + ) |
942 | + |
943 | + class Meta: |
944 | + attrs = {'class': 'basic'} |
945 | + order_by = ('testcase',) |
946 | |
947 | === added directory 'idle_power/templates' |
948 | === added directory 'idle_power/templates/idle_power' |
949 | === added file 'idle_power/templates/idle_power/machine_raw_data.html' |
950 | --- idle_power/templates/idle_power/machine_raw_data.html 1970-01-01 00:00:00 +0000 |
951 | +++ idle_power/templates/idle_power/machine_raw_data.html 2013-04-16 20:51:23 +0000 |
952 | @@ -0,0 +1,37 @@ |
953 | +{% extends "power/power_layout.html" %} |
954 | +{% load dashboard_extras %} |
955 | +{% load render_table from django_tables2 %} |
956 | + |
957 | +{% block page_name %}{{ name }} - {{ arch }} - raw data{% endblock %} |
958 | +{% block extra_headers %} |
959 | +<style> |
960 | +td { |
961 | + font-size: 0.8em; |
962 | +} |
963 | +th { |
964 | + font-size: 0.9em; |
965 | +} |
966 | +.box-padded { |
967 | + margin: 20px; |
968 | +} |
969 | +</style> |
970 | +{% endblock extra_headers %} |
971 | + |
972 | +{% block content %} |
973 | +<div class='grid_15'> |
974 | + <h2>{{ name }} raw data</h2> |
975 | + <p> |
976 | + <strong>Build #:</strong> <a href="{{ jenkins_url }}">{{ build_number }}</a><br /> |
977 | + <strong>Ran at:</strong> {{ ran_at }}<br /> |
978 | + <strong>Arch:</strong> {{ arch }}<br /> |
979 | + <strong>Release:</strong> {{ release }}<br /> |
980 | + <strong>Variant:</strong> {{ variant }} |
981 | + </p> |
982 | +</div> |
983 | +<div class='grid_8'> |
984 | +{% render_table table %} |
985 | +</div> |
986 | +<div class='grid_7'> |
987 | + {% include "idle_power/metrics.html" %} |
988 | +</div> |
989 | +{% endblock %} |
990 | |
991 | === added file 'idle_power/templates/idle_power/machine_table.html' |
992 | --- idle_power/templates/idle_power/machine_table.html 1970-01-01 00:00:00 +0000 |
993 | +++ idle_power/templates/idle_power/machine_table.html 2013-04-16 20:51:23 +0000 |
994 | @@ -0,0 +1,30 @@ |
995 | +{% extends "power/power_layout.html" %} |
996 | +{% load dashboard_extras %} |
997 | +{% load render_table from django_tables2 %} |
998 | + |
999 | +{% block page_name %}{{ name }}{% endblock %} |
1000 | +{% block extra_headers %} |
1001 | +<style> |
1002 | +td { |
1003 | + font-size: 0.8em; |
1004 | +} |
1005 | +th { |
1006 | + font-size: 0.9em; |
1007 | +} |
1008 | +.box-padded { |
1009 | + margin: 20px; |
1010 | +} |
1011 | +</style> |
1012 | +{% endblock extra_headers %} |
1013 | + |
1014 | +{% block content %} |
1015 | +<div class='grid_15'> |
1016 | + <h2>{{ name }} Data Overview</h2> |
1017 | + <p> |
1018 | + <strong>Arch:</strong> {{ arch }}<br /> |
1019 | + </p> |
1020 | +</div> |
1021 | +<div class='grid_8'> |
1022 | +{% render_table table %} |
1023 | +</div> |
1024 | +{% endblock %} |
1025 | |
1026 | === added file 'idle_power/templates/idle_power/metrics.html' |
1027 | --- idle_power/templates/idle_power/metrics.html 1970-01-01 00:00:00 +0000 |
1028 | +++ idle_power/templates/idle_power/metrics.html 2013-04-16 20:51:23 +0000 |
1029 | @@ -0,0 +1,28 @@ |
1030 | +<div class="box box-padded"> |
1031 | +<h3>Metrics Explained</h3> |
1032 | +<div> |
1033 | +The metrics in this chart come from the <a href="https://www.kernel.org/doc/Documentation/power/power_supply_class.txt">power supply</a> driver in the kernel. |
1034 | + |
1035 | + <dl> |
1036 | + <dt>Energy Start</dt> |
1037 | + <dd>The amount of energy in mW the battery contained.</dd> |
1038 | + <dt>Energy Used</dt> |
1039 | + <dd>This is difference between the "energy_now" measurements at the |
1040 | + start and end of the test. It represents the actual energy |
1041 | + that was consumed.</dd> |
1042 | + <dt>Computed Energy</dt> |
1043 | + <dd>We can multiple the "charge used" by an estimate of the |
1044 | + voltage: |
1045 | + <pre>end_voltage + 0.5 * abs(start_voltage - end_voltage)</pre> |
1046 | + to make a sanity check on the "energy used" metric. If this |
1047 | + value has a >1% difference, then we mark it so you'll know |
1048 | + something is inconsistent.</dd> |
1049 | + <dt>Charge Start</dt> |
1050 | + <dd>The amount of charge (in mA) reported by the battery at the |
1051 | + beginning of the test.</dd> |
1052 | + <dt>Charge Used</dt> |
1053 | + <dd>The difference of the "charge_now" metric at the start and end |
1054 | + the test.</dd> |
1055 | + </dl> |
1056 | +</div> |
1057 | +</div> |
1058 | |
1059 | === added file 'idle_power/urls.py' |
1060 | --- idle_power/urls.py 1970-01-01 00:00:00 +0000 |
1061 | +++ idle_power/urls.py 2013-04-16 20:51:23 +0000 |
1062 | @@ -0,0 +1,33 @@ |
1063 | +# QA Dashboard |
1064 | +# Copyright 2012-2013 Canonical Ltd. |
1065 | + |
1066 | +# This program is free software: you can redistribute it and/or modify it |
1067 | +# under the terms of the GNU Affero General Public License version 3, as |
1068 | +# published by the Free Software Foundation. |
1069 | + |
1070 | +# This program is distributed in the hope that it will be useful, but |
1071 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1072 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1073 | +# PURPOSE. See the GNU Affero General Public License for more details. |
1074 | + |
1075 | +# You should have received a copy of the GNU Affero General Public License |
1076 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1077 | + |
1078 | +from django.conf.urls.defaults import ( |
1079 | + patterns, |
1080 | + url, |
1081 | +) |
1082 | + |
1083 | +urlpatterns = patterns( |
1084 | + 'idle_power.views', |
1085 | + url( |
1086 | + r'^machine/(?P<machine_id>\d+)/(?P<arch>\w+)/$', |
1087 | + 'machine_detail', |
1088 | + name='idle_machine_detail' |
1089 | + ), |
1090 | + url( |
1091 | + r'^machine/(?P<machine_id>\d+)/(?P<arch>\w+)/(?P<build_number>\d+)/$', |
1092 | + 'machine_raw_data', |
1093 | + name='idle_machine_raw_data' |
1094 | + ), |
1095 | +) |
1096 | |
1097 | === added file 'idle_power/utah_utils.py' |
1098 | --- idle_power/utah_utils.py 1970-01-01 00:00:00 +0000 |
1099 | +++ idle_power/utah_utils.py 2013-04-16 20:51:23 +0000 |
1100 | @@ -0,0 +1,112 @@ |
1101 | +# QA Dashboard |
1102 | +# Copyright 2012-2013 Canonical Ltd. |
1103 | + |
1104 | +# This program is free software: you can redistribute it and/or modify it |
1105 | +# under the terms of the GNU Affero General Public License version 3, as |
1106 | +# published by the Free Software Foundation. |
1107 | + |
1108 | +# This program is distributed in the hope that it will be useful, but |
1109 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1110 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1111 | +# PURPOSE. See the GNU Affero General Public License for more details. |
1112 | + |
1113 | +# You should have received a copy of the GNU Affero General Public License |
1114 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1115 | + |
1116 | +""" |
1117 | +UTAH result parsing code. |
1118 | +""" |
1119 | + |
1120 | +import logging |
1121 | + |
1122 | +from common.utah_parser import UTAHParser, ParserError |
1123 | + |
1124 | +from .models import ( |
1125 | + IdlePowerDetail, |
1126 | +) |
1127 | + |
1128 | +FLAVOR = 'ubuntu' |
1129 | + |
1130 | + |
1131 | +def process_idle_power_log( |
1132 | + logfile, |
1133 | + image, |
1134 | + machine, |
1135 | + jenkins_url=None, |
1136 | + name=None |
1137 | +): |
1138 | + """ |
1139 | + Parse a utah client log for smoke test results. |
1140 | + """ |
1141 | + details = [] |
1142 | + |
1143 | + logfile_path = logfile |
1144 | + if jenkins_url is not None: |
1145 | + logfile_path = "{}/artifact/{}".format( |
1146 | + jenkins_url, |
1147 | + logfile, |
1148 | + ) |
1149 | + |
1150 | + if jenkins_url is None: |
1151 | + jenkins_url = "http://jenkins.qa.ubuntu.com/" |
1152 | + |
1153 | + parser = UTAHParser() |
1154 | + |
1155 | + try: |
1156 | + data = parser.parse(logfile_path) |
1157 | + except ParserError as e: |
1158 | + if not e.zero_entry: |
1159 | + logging.error("%s %s", logfile_path, e) |
1160 | + return details |
1161 | + |
1162 | + if data is None: |
1163 | + logging.warn("Unable to parse %s", logfile_path) |
1164 | + return details |
1165 | + |
1166 | + # Use the name from the logs if there is one, if there isn't one |
1167 | + # and one is passed in use that. |
1168 | + if 'name' not in data: |
1169 | + data['name'] = 'unnamed' |
1170 | + |
1171 | + if name is None or data['name'] != 'unnamed': |
1172 | + name = data['name'] |
1173 | + data['machine'] = machine |
1174 | + data['image'] = image |
1175 | + data['jenkins_url'] = jenkins_url |
1176 | + |
1177 | + _add_details(details, data) |
1178 | + |
1179 | + |
1180 | +def _add_details(details, data): |
1181 | + for command in data['commands']: |
1182 | + if command['cmd_type'] != 'testcase_test': |
1183 | + continue |
1184 | + testcase = command['testcase'] |
1185 | + start_voltage = command['battery']['start']['voltage_now'] / 1000 |
1186 | + end_voltage = command['battery']['end']['voltage_now'] / 1000 |
1187 | + energy_start = command['battery']['start']['energy_now'] / 1000 |
1188 | + energy_end = command['battery']['end']['energy_now'] / 1000 |
1189 | + energy_used = energy_start - energy_end |
1190 | + charge_start = command['battery']['start']['charge_now'] / 1000 |
1191 | + charge_end = command['battery']['end']['charge_now'] / 1000 |
1192 | + charge_used = charge_start - charge_end |
1193 | + ran_at = command['start_time'] |
1194 | + voltage_est = ( |
1195 | + end_voltage + 0.5 * abs(start_voltage - end_voltage) |
1196 | + ) / 1000 |
1197 | + calculated_energy = charge_used * voltage_est |
1198 | + detail, new_detail = IdlePowerDetail.objects.get_or_create( |
1199 | + image=data['image'], |
1200 | + machine=data['machine'], |
1201 | + jenkins_url=data['jenkins_url'], |
1202 | + ran_at=ran_at, |
1203 | + testcase=testcase, |
1204 | + calculated_energy=calculated_energy, |
1205 | + charge_start=charge_start, |
1206 | + charge_used=charge_used, |
1207 | + energy_start=energy_start, |
1208 | + energy_used=energy_used, |
1209 | + ) |
1210 | + details.append(detail) |
1211 | + |
1212 | + return details |
1213 | |
1214 | === added file 'idle_power/views.py' |
1215 | --- idle_power/views.py 1970-01-01 00:00:00 +0000 |
1216 | +++ idle_power/views.py 2013-04-16 20:51:23 +0000 |
1217 | @@ -0,0 +1,104 @@ |
1218 | +# QA Dashboard |
1219 | +# Copyright 2012-2013 Canonical Ltd. |
1220 | + |
1221 | +# This program is free software: you can redistribute it and/or modify it |
1222 | +# under the terms of the GNU Affero General Public License version 3, as |
1223 | +# published by the Free Software Foundation. |
1224 | + |
1225 | +# This program is distributed in the hope that it will be useful, but |
1226 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1227 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1228 | +# PURPOSE. See the GNU Affero General Public License for more details. |
1229 | + |
1230 | +# You should have received a copy of the GNU Affero General Public License |
1231 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1232 | + |
1233 | +from django.views.decorators.http import require_GET |
1234 | +from django.template import RequestContext |
1235 | +from django.db.models import Avg, StdDev |
1236 | + |
1237 | +from django.shortcuts import ( |
1238 | + render_to_response, |
1239 | + get_object_or_404, |
1240 | +) |
1241 | + |
1242 | +from django_tables2 import RequestConfig |
1243 | + |
1244 | +from idle_power.models import ( |
1245 | + IdlePowerMachine, |
1246 | + IdlePowerDetail, |
1247 | +) |
1248 | + |
1249 | +from idle_power.tables import DataTable, MachineTable |
1250 | + |
1251 | + |
1252 | +@require_GET |
1253 | +def machine_detail(request, machine_id, arch): |
1254 | + machine = get_object_or_404(IdlePowerMachine, id=machine_id) |
1255 | + qs = IdlePowerDetail.objects.filter( |
1256 | + machine__id=machine_id |
1257 | + ).values( |
1258 | + 'image__build_number', |
1259 | + 'image__arch', |
1260 | + 'testcase', |
1261 | + 'machine__id', |
1262 | + ).annotate( |
1263 | + energy_avg=Avg('energy_used'), |
1264 | + energy_stddev=StdDev('energy_used'), |
1265 | + ).order_by('-image__build_number') |
1266 | + table = MachineTable(qs) |
1267 | + RequestConfig(request, paginate={"per_page": 100}).configure(table) |
1268 | + data = { |
1269 | + 'name': machine.name, |
1270 | + 'arch': arch, |
1271 | + 'table': table, |
1272 | + } |
1273 | + |
1274 | + return render_to_response( |
1275 | + 'idle_power/machine_table.html', |
1276 | + data, |
1277 | + RequestContext(request) |
1278 | + ) |
1279 | + |
1280 | + |
1281 | +@require_GET |
1282 | +def machine_raw_data(request, machine_id, arch, build_number): |
1283 | + machine = get_object_or_404(IdlePowerMachine, id=machine_id) |
1284 | + |
1285 | + qs = IdlePowerDetail.objects.filter( |
1286 | + machine__id=machine_id, |
1287 | + image__build_number=build_number, |
1288 | + image__arch=arch, |
1289 | + ).values( |
1290 | + 'image__build_number', |
1291 | + 'image__arch', |
1292 | + 'image__release', |
1293 | + 'image__variant', |
1294 | + 'ran_at', |
1295 | + 'testcase', |
1296 | + 'energy_start', |
1297 | + 'energy_used', |
1298 | + 'calculated_energy', |
1299 | + 'charge_start', |
1300 | + 'charge_used', |
1301 | + 'jenkins_url', |
1302 | + ) |
1303 | + table = DataTable(qs) |
1304 | + RequestConfig(request, paginate={"per_page": 100}).configure(table) |
1305 | + details = qs[0] |
1306 | + data = { |
1307 | + 'name': machine.name, |
1308 | + 'id': machine.id, |
1309 | + 'arch': details['image__arch'], |
1310 | + 'table': table, |
1311 | + 'build_number': build_number, |
1312 | + 'release': details['image__release'], |
1313 | + 'ran_at': details['ran_at'], |
1314 | + 'variant': details['image__variant'], |
1315 | + 'jenkins_url': details['jenkins_url'], |
1316 | + } |
1317 | + return render_to_response( |
1318 | + "idle_power/machine_raw_data.html", |
1319 | + data, |
1320 | + RequestContext(request) |
1321 | + ) |
1322 | |
1323 | === modified file 'power/templates/power/power_layout.html' |
1324 | --- power/templates/power/power_layout.html 2013-04-02 21:09:05 +0000 |
1325 | +++ power/templates/power/power_layout.html 2013-04-16 20:51:23 +0000 |
1326 | @@ -1,2 +1,5 @@ |
1327 | {% extends "layout.html" %} |
1328 | -{% block sub_nav %}{% endblock %} |
1329 | +{% block sub_nav_links %} |
1330 | +<li {% ifequal url.1 'software' %}class="active"{% endifequal %}><a class="sub-nav-item" href="{% url power_arch_overview 'amd64' %}">Software Testing</a></li> |
1331 | +<li {% ifequal url.1 'idle' %}class="active"{% endifequal %}><a class="sub-nav-item" href="{% url idle_machine_detail 1 'armhf' %}">Idle Testing</a></li> |
1332 | +{% endblock %} |
1333 | |
1334 | === modified file 'qa_dashboard/settings.py' |
1335 | --- qa_dashboard/settings.py 2013-04-05 00:37:07 +0000 |
1336 | +++ qa_dashboard/settings.py 2013-04-16 20:51:23 +0000 |
1337 | @@ -147,6 +147,7 @@ |
1338 | 'power', |
1339 | 'smoke', |
1340 | 'sru', |
1341 | + 'idle_power', |
1342 | ) |
1343 | |
1344 | INSTALLED_APPS = ( |
1345 | |
1346 | === modified file 'qa_dashboard/urls.py' |
1347 | --- qa_dashboard/urls.py 2013-04-16 11:57:48 +0000 |
1348 | +++ qa_dashboard/urls.py 2013-04-16 20:51:23 +0000 |
1349 | @@ -28,6 +28,7 @@ |
1350 | '', |
1351 | url(r'^power/software/', include('power.urls')), |
1352 | url(r'^api/power/software/', include('power.urls_api')), |
1353 | + url(r'^power/idle/', include('idle_power.urls')), |
1354 | ) |
1355 | |
1356 | urlpatterns += patterns( |
1357 | |
1358 | === added file 'scripts/fakeup_idle_power.py' |
1359 | --- scripts/fakeup_idle_power.py 1970-01-01 00:00:00 +0000 |
1360 | +++ scripts/fakeup_idle_power.py 2013-04-16 20:51:23 +0000 |
1361 | @@ -0,0 +1,134 @@ |
1362 | +import datetime |
1363 | +import logging |
1364 | +import os |
1365 | +import random |
1366 | +import time |
1367 | +import sys |
1368 | + |
1369 | +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) |
1370 | +os.environ['DJANGO_SETTINGS_MODULE'] = 'qa_dashboard.settings' |
1371 | + |
1372 | +logging.basicConfig(level=logging.INFO) |
1373 | + |
1374 | +from idle_power.models import ( |
1375 | + IdlePowerDetail, |
1376 | + IdlePowerImage, |
1377 | + IdlePowerMachine, |
1378 | + IdlePowerResult, |
1379 | + IdlePowerLog, |
1380 | +) |
1381 | + |
1382 | + |
1383 | +def add_machines(count=3): |
1384 | + |
1385 | + machines = [] |
1386 | + |
1387 | + for i in range(count): |
1388 | + machines.append( |
1389 | + IdlePowerMachine.objects.create( |
1390 | + name='idle-power-test-{}'.format(i), |
1391 | + ) |
1392 | + ) |
1393 | + |
1394 | + return machines |
1395 | + |
1396 | + |
1397 | +def add_log(): |
1398 | + log = IdlePowerLog.objects.create( |
1399 | + path='idle-power-test-path', |
1400 | + name='idle-power-test-name', |
1401 | + ) |
1402 | + return log |
1403 | + |
1404 | + |
1405 | +def add_images(count=1): |
1406 | + |
1407 | + images = [] |
1408 | + |
1409 | + for i in range(count): |
1410 | + images.append( |
1411 | + IdlePowerImage.objects.create( |
1412 | + arch='amd64', |
1413 | + variant='desktop', |
1414 | + release='raring', |
1415 | + build_number='{}'.format(i), |
1416 | + md5='fake{}'.format(i), |
1417 | + ) |
1418 | + ) |
1419 | + images.append( |
1420 | + IdlePowerImage.objects.create( |
1421 | + arch='i386', |
1422 | + variant='desktop', |
1423 | + release='raring', |
1424 | + build_number='{}'.format(i), |
1425 | + md5='fake1{}'.format(i), |
1426 | + ) |
1427 | + ) |
1428 | + |
1429 | + return images |
1430 | + |
1431 | + |
1432 | +def add_details(machine, log, image=None, upgrade=None, count=3): |
1433 | + |
1434 | + details = [] |
1435 | + |
1436 | + for j in range(count): |
1437 | + date = datetime.datetime.now() |
1438 | + result = IdlePowerResult.objects.create( |
1439 | + image=image, |
1440 | + machine=machine, |
1441 | + value=0, |
1442 | + ran_at=date, |
1443 | + name=m, |
1444 | + log=log, |
1445 | + ) |
1446 | + for i in range(count): |
1447 | + details.append( |
1448 | + IdlePowerDetail.objects.create( |
1449 | + image=image, |
1450 | + machine=machine, |
1451 | + command="idle", |
1452 | + voltage_avg=random.uniform(0.1, 1) * 100, |
1453 | + energy_used=random.uniform(0.1, 5) * 100, |
1454 | + energy_start=random.uniform(0.1, 1) * 100, |
1455 | + charge_start=random.uniform(0.1, 1) * 100, |
1456 | + charge_used=random.uniform(1.1, 2) * 100, |
1457 | + ran_at=date, |
1458 | + result=result, |
1459 | + ) |
1460 | + ) |
1461 | + time.sleep(2) |
1462 | + |
1463 | + |
1464 | +def cleanup(): |
1465 | + |
1466 | + for klass in [ |
1467 | + IdlePowerDetail, |
1468 | + IdlePowerResult, |
1469 | + IdlePowerMachine, |
1470 | + IdlePowerImage, |
1471 | + IdlePowerLog |
1472 | + ]: |
1473 | + logging.debug("klass: {}".format(klass)) |
1474 | + klass.objects.all().delete() |
1475 | + |
1476 | + |
1477 | +def list_stuff(): |
1478 | + |
1479 | + for klass in [ |
1480 | + IdlePowerImage, |
1481 | + IdlePowerMachine, |
1482 | + IdlePowerDetail, |
1483 | + IdlePowerLog, |
1484 | + ]: |
1485 | + for item in klass.objects.all(): |
1486 | + logging.debug(item) |
1487 | + |
1488 | +if __name__ == "__main__": |
1489 | + cleanup() |
1490 | + machines = add_machines(count=3) |
1491 | + images = add_images(count=10) |
1492 | + log = add_log() |
1493 | + for image in images: |
1494 | + for machine in machines: |
1495 | + add_details(machine, log, image=image) |
1496 | |
1497 | === modified file 'smoke/utah_utils.py' |
1498 | --- smoke/utah_utils.py 2013-03-22 20:05:56 +0000 |
1499 | +++ smoke/utah_utils.py 2013-04-16 20:51:23 +0000 |
1500 | @@ -19,7 +19,8 @@ |
1501 | |
1502 | import logging |
1503 | |
1504 | -from smoke.utah_parser import UTAHParser, ParserError |
1505 | +from common.utah_parser import UTAHParser, ParserError |
1506 | + |
1507 | from smoke.models import ( |
1508 | Build, |
1509 | Run, |
A couple of things:
237 +from .models import (
We stopped using relative imports in lp:utah, might be good to not continue the pattern here.
496 + logging. info("Adding new log: {}".format(log)) info("Adding new log: %s", log)
logging.
830 + path = models. CharField( max_length= 512) CharField( max_length= 100)
831 + name = models.
I'd make those arbitrarily large since postgres doesn't care
847 + command = models. CharField( max_length= 4096)
We should probably name this "testcase" so that it stays inline with the YAML and our general terminology