Gentoo Archives: gentoo-commits

From: "André Erdmann" <dywi@×××××××.de>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/R_overlay:master commit in: roverlay/stats/
Date: Mon, 29 Jul 2013 14:57:31
Message-Id: 1375109585.d1300fc92439e26b6941eec62ec888329835ee77.dywi@gentoo
1 commit: d1300fc92439e26b6941eec62ec888329835ee77
2 Author: André Erdmann <dywi <AT> mailerd <DOT> de>
3 AuthorDate: Mon Jul 29 14:53:05 2013 +0000
4 Commit: André Erdmann <dywi <AT> mailerd <DOT> de>
5 CommitDate: Mon Jul 29 14:53:05 2013 +0000
6 URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=d1300fc9
7
8 write stats to round-robin database (rrdtool)
9
10 This commit makes (/allows to/) stats data persistent by adding them to a db,
11 which can later be used to draw graphs etc.
12
13 ---
14 roverlay/stats/abstract.py | 37 +-----
15 roverlay/stats/collector.py | 156 +++++---------------------
16 roverlay/stats/dbcollector.py | 74 ++++++++++++
17 roverlay/stats/rrd.py | 63 +++++++++++
18 roverlay/stats/{collector.py => visualize.py} | 91 +++------------
19 5 files changed, 189 insertions(+), 232 deletions(-)
20
21 diff --git a/roverlay/stats/abstract.py b/roverlay/stats/abstract.py
22 index 6d0575a..68728f4 100644
23 --- a/roverlay/stats/abstract.py
24 +++ b/roverlay/stats/abstract.py
25 @@ -9,34 +9,7 @@ from __future__ import division
26 import collections
27 import time
28
29 -class MethodNotImplemented ( NotImplementedError ):
30 - def __init__ ( self, obj, method ):
31 - super ( MethodNotImplemented, self ).__init__ (
32 - "{n}.{f}()".format ( n=obj.__class__.__name__, f=method )
33 - )
34 - # --- end of __init__ (...) ---
35 -
36 -# --- end of MethodNotImplemented ---
37 -
38 -class StatsVisualizer ( object ):
39 -
40 - def __init__ ( self, stats ):
41 - super ( StatsVisualizer, self ).__init__()
42 - self.stats = stats
43 - self.lines = None
44 -
45 - self.prepare()
46 - # --- end of __init__ (...) ---
47 -
48 - def prepare ( self ):
49 - raise MethodNotImplemented ( self, 'prepare' )
50 - # --- end of prepare (...) ---
51 -
52 - def __str__ ( self ):
53 - return '\n'.join ( self.lines )
54 - # --- end of __str__ (...) ---
55 -
56 -# --- end of StatsVisualizer ---
57 +from roverlay.util.objects import MethodNotImplementedError
58
59
60 class RoverlayStatsBase ( object ):
61 @@ -66,7 +39,7 @@ class RoverlayStatsBase ( object ):
62 self.merge_members ( other, my_cls._MEMBERS )
63
64 else:
65 - raise MethodNotImplemented ( self, 'merge_with' )
66 + raise MethodNotImplementedError ( self, 'merge_with' )
67 # --- end of merge_with (...) ---
68
69 def merge_members ( self, other, members ):
70 @@ -86,7 +59,7 @@ class RoverlayStatsBase ( object ):
71 if int ( member ) != 0:
72 return member
73 else:
74 - raise MethodNotImplemented ( self, 'has_nonzero' )
75 + raise MethodNotImplementedError ( self, 'has_nonzero' )
76 # --- end of has_nonzero (...) ---
77
78 def reset_members ( self ):
79 @@ -98,7 +71,7 @@ class RoverlayStatsBase ( object ):
80 if hasattr ( self.__class__, '_MEMBERS' ):
81 self.reset_members()
82 else:
83 - raise MethodNotImplemented ( self, 'reset' )
84 + raise MethodNotImplementedError ( self, 'reset' )
85 # --- end of reset (...) ---
86
87 def get_description_str ( self ):
88 @@ -122,7 +95,7 @@ class RoverlayStatsBase ( object ):
89 if ret:
90 return ret
91 else:
92 - raise MethodNotImplemented ( self, '__str__' )
93 + raise MethodNotImplementedError ( self, '__str__' )
94 # --- end of __str__ (...) ---
95
96 # --- end of RoverlayStatsBase ---
97
98 diff --git a/roverlay/stats/collector.py b/roverlay/stats/collector.py
99 index 3e3dee7..30b0e76 100644
100 --- a/roverlay/stats/collector.py
101 +++ b/roverlay/stats/collector.py
102 @@ -4,10 +4,15 @@
103 # Distributed under the terms of the GNU General Public License;
104 # either version 2 of the License, or (at your option) any later version.
105
106 -import collections
107 +import time
108 +
109 +import roverlay.config.static
110
111 from . import abstract
112 from . import base
113 +from . import dbcollector
114 +from . import visualize
115 +from . import rrd
116
117
118 class StatsCollector ( abstract.RoverlayStatsBase ):
119 @@ -57,13 +62,29 @@ class StatsCollector ( abstract.RoverlayStatsBase ):
120 # --- end of get_net_gain (...) ---
121
122 def __init__ ( self ):
123 + super ( StatsCollector, self ).__init__()
124 +
125 self.time = abstract.TimeStats ( "misc time stats" )
126 self.distmap = base.DistmapStats()
127 self.overlay = base.OverlayStats()
128 self.overlay_creation = base.OverlayCreationStats()
129 self.repo = base.RepoStats()
130 + self.db_collector = None
131 + self._database = None
132 # --- end of __init__ (...) ---
133
134 + def setup_database ( self, config=None ):
135 + conf = (
136 + config if config is not None else roverlay.config.static.access()
137 + )
138 +
139 + self.db_collector = dbcollector.StatsDBCollector ( self )
140 + self._database = rrd.StatsDB (
141 + conf.get_or_fail ( "RRD_DB.file" ), self.db_collector
142 + )
143 + self._database.create_if_missing()
144 + # --- end of setup_database (...) ---
145 +
146 def gen_str ( self ):
147 yield "{success}, overall {osuccess}".format (
148 success = self.get_success_ratio(),
149 @@ -77,136 +98,17 @@ class StatsCollector ( abstract.RoverlayStatsBase ):
150 # --- end of gen_str (...) ---
151
152 def get_creation_str ( self ):
153 - return str ( CreationStatsVisualizer ( self ) )
154 + return str ( visualize.CreationStatsVisualizer ( self ) )
155 # --- end of to_creation_str (...) ---
156
157 -# --- end of StatsCollector ---
158 -
159 -
160 -class CreationStatsVisualizer ( abstract.StatsVisualizer ):
161 -
162 - def prepare ( self ):
163 - EMPTY_LINE = ""
164 -
165 - pkg_count = self.stats.repo.pkg_count
166 - pkg_queued = self.stats.overlay_creation.pkg_queued
167 - pkg_fail = self.stats.overlay_creation.pkg_fail
168 - pkg_success = self.stats.overlay_creation.pkg_success
169 - ebuild_delta = self.stats.get_net_gain()
170 - revbumps = self.stats.overlay.revbump_count
171 -
172 - max_number_len = min (
173 - len ( str ( int ( k ) ) ) for k in (
174 - pkg_queued, pkg_fail, pkg_success
175 - )
176 - )
177 - max_number_len = min ( 5, max_number_len )
178 -
179 - success_ratio = self.stats.get_success_ratio()
180 - overall_success_ratio = self.stats.get_overall_success_ratio()
181 -
182 + def write_db ( self ):
183 + self.db_collector.update()
184 + self._database.update()
185 + self._database.commit()
186 + # --- end of write_db (...) ---
187
188 - timestats = (
189 - ( 'scan_overlay', self.stats.overlay.scan_time.get_total_str() ),
190 - ( 'add_packages', self.stats.repo.queue_time.get_total_str() ),
191 - (
192 - 'ebuild_creation',
193 - self.stats.overlay_creation.creation_time.get_total_str()
194 - ),
195 - ( 'write_overlay', self.stats.overlay.write_time.get_total_str() ),
196 - )
197 -
198 - try:
199 - max_time_len = max ( len(k) for k, v in timestats if v is not None )
200 - except ValueError:
201 - # empty sequence -> no timestats
202 - max_time_len = -1
203 - else:
204 - # necessary?
205 - max_time_len = min ( 39, max_time_len )
206 -
207 -
208 - # create lines
209 - lines = collections.deque()
210 - unshift = lines.appendleft
211 - append = lines.append
212 -
213 - numstats = lambda k, s: "{num:<{l}d} {s}".format (
214 - num=int ( k ), s=s, l=max_number_len
215 - )
216 -
217 -
218 - append (
219 - 'success ratio {s_i:.2%} (overall {s_o:.2%})'.format (
220 - s_i = success_ratio.get_ratio(),
221 - s_o = overall_success_ratio.get_ratio()
222 - )
223 - )
224 - append ( EMPTY_LINE )
225 -
226 - append (
227 - "{e:+d} ebuilds ({r:d} revbumps)".format (
228 - e=ebuild_delta, r=int ( revbumps )
229 - )
230 - )
231 - append ( EMPTY_LINE )
232 -
233 - if int ( pkg_count ) != int ( pkg_queued ):
234 - append ( numstats (
235 - pkg_queued,
236 - '/ {n:d} packages added to the ebuild creation queue'.format (
237 - n=int ( pkg_count )
238 - )
239 - ) )
240 - else:
241 - append ( numstats (
242 - pkg_queued, 'packages added to the ebuild creation queue'
243 - ) )
244 -
245 - append ( numstats (
246 - pkg_success, 'packages passed ebuild creation'
247 - ) )
248 -
249 - append ( numstats (
250 - pkg_fail, 'packages failed ebuild creation'
251 - ) )
252 -
253 - if pkg_fail.has_details() and int ( pkg_fail ) != 0:
254 - append ( EMPTY_LINE )
255 - append ( "Details for ebuild creation failure:" )
256 - details = sorted (
257 - ( ( k, int(v) ) for k, v in pkg_fail.iter_details() ),
258 - key=lambda kv: kv[1]
259 - )
260 - dlen = len ( str ( max ( details, key=lambda kv: kv[1] ) [1] ) )
261 -
262 - for key, value in details:
263 - append ( "* {v:>{l}d}: {k}".format ( k=key, v=value, l=dlen ) )
264 - # -- end if <have pkg_fail details>
265 -
266 - if max_time_len > 0:
267 - # or >= 0
268 - append ( EMPTY_LINE )
269 - for k, v in timestats:
270 - if v is not None:
271 - append (
272 - "time for {0:<{l}} : {1}".format ( k, v, l=max_time_len )
273 - )
274 - # -- end if timestats
275 -
276 -
277 - append ( EMPTY_LINE )
278 -
279 - # add header/footer line(s)
280 - max_line_len = 2 + min ( 78, max ( len(s) for s in lines ) )
281 - unshift (
282 - "{0:-^{1}}\n".format ( " Overlay creation stats ", max_line_len )
283 - )
284 - append ( max_line_len * '-' )
285 +# --- end of StatsCollector ---
286
287 - self.lines = lines
288 - # --- end of gen_str (...) ---
289 -# --- end of CreationStatsVisualizer ---
290
291 static = StatsCollector()
292 StatsCollector._instance = static
293
294 diff --git a/roverlay/stats/dbcollector.py b/roverlay/stats/dbcollector.py
295 new file mode 100644
296 index 0000000..402e64d
297 --- /dev/null
298 +++ b/roverlay/stats/dbcollector.py
299 @@ -0,0 +1,74 @@
300 +# R overlay -- stats collection, prepare data for db storage
301 +# -*- coding: utf-8 -*-
302 +# Copyright (C) 2013 André Erdmann <dywi@×××××××.de>
303 +# Distributed under the terms of the GNU General Public License;
304 +# either version 2 of the License, or (at your option) any later version.
305 +
306 +import collections
307 +import weakref
308 +
309 +class StatsDBCollector ( object ):
310 + VERSION = 0
311 +
312 + # 'pc' := package count(er), 'ec' := ebuild _
313 + NUMSTATS_KEYS = (
314 + 'pc_repo', 'pc_distmap', 'pc_filtered', 'pc_queued', 'pc_success',
315 + 'pc_fail', 'pc_fail_empty', 'pc_fail_dep', 'pc_fail_selfdep',
316 + 'pc_fail_err',
317 + 'ec_pre', 'ec_post', 'ec_written', 'ec_revbump',
318 + )
319 + NUMSTATS = collections.namedtuple (
320 + "numstats", ' '.join ( NUMSTATS_KEYS )
321 + )
322 +
323 + TIMESTATS_KEYS = ()
324 + TIMESTATS = collections.namedtuple (
325 + "timestats", ' '.join ( TIMESTATS_KEYS )
326 + )
327 +
328 + def __init__ ( self, stats ):
329 + super ( StatsDBCollector, self ).__init__()
330 + self.stats = weakref.ref ( stats )
331 + self._collected_stats = None
332 + # --- end of __init__ (...) ---
333 +
334 + def update ( self ):
335 + self._collected_stats = self.make_all()
336 + # --- end of update (...) ---
337 +
338 + def make_numstats ( self ):
339 + stats = self.stats()
340 + ov_create = stats.overlay_creation
341 + ov = stats.overlay
342 +
343 + return self.__class__.NUMSTATS (
344 + pc_repo = int ( stats.repo.pkg_count ),
345 + pc_distmap = int ( stats.distmap.pkg_count ),
346 + pc_filtered = int ( ov_create.pkg_filtered ),
347 + pc_queued = int ( ov_create.pkg_queued ),
348 + pc_success = int ( ov_create.pkg_success ),
349 + pc_fail = int ( ov_create.pkg_fail ),
350 + pc_fail_empty = ov_create.pkg_fail.get ( 'empty_desc' ),
351 + pc_fail_dep = ov_create.pkg_fail.get ( 'unresolved_deps' ),
352 + pc_fail_selfdep = ov_create.pkg_fail.get ( 'bad_selfdeps' ),
353 + pc_fail_err = ov_create.pkg_fail.get ( 'exception' ),
354 + ec_pre = int ( ov.ebuilds_scanned ),
355 + ec_post = int ( ov.ebuild_count ),
356 + ec_written = int ( ov.ebuilds_written ),
357 + ec_revbump = int ( ov.revbump_count ),
358 + )
359 + # --- end of make_numstats (...) ---
360 +
361 + def make_timestats ( self ):
362 + return ()
363 + # --- end of make_timestats (...) ---
364 +
365 + def make_all ( self ):
366 + return self.make_numstats() + self.make_timestats()
367 + # --- end of make_all (...) ---
368 +
369 + def get_all ( self ):
370 + return self._collected_stats
371 + # --- end of get_all (...) ---
372 +
373 +# --- end of StatsDBCollector #v0 ---
374
375 diff --git a/roverlay/stats/rrd.py b/roverlay/stats/rrd.py
376 new file mode 100644
377 index 0000000..66da434
378 --- /dev/null
379 +++ b/roverlay/stats/rrd.py
380 @@ -0,0 +1,63 @@
381 +# R overlay -- stats collection, rrd database (using rrdtool)
382 +# -*- coding: utf-8 -*-
383 +# Copyright (C) 2013 André Erdmann <dywi@×××××××.de>
384 +# Distributed under the terms of the GNU General Public License;
385 +# either version 2 of the License, or (at your option) any later version.
386 +
387 +
388 +# NOT using rrdtool's python bindings as they're available for python 2 only
389 +
390 +import roverlay.db.rrdtool
391 +from roverlay.db.rrdtool import RRDVariable, RRDArchive
392 +
393 +
394 +class StatsDB ( roverlay.db.rrdtool.RRD ):
395 +
396 + # default step
397 + STEP = 300
398 +
399 + def __init__ ( self, filepath, collector, step=None ):
400 + # COULDFIX:
401 + # vars / RRA creation is only necessary when creating a new database
402 + #
403 + self.collector = collector
404 + self.rrd_vars = self.make_vars()
405 + self.rrd_archives = self.make_rra()
406 + self.step = step if step is not None else self.__class__.STEP
407 + super ( StatsDB, self ).__init__ ( filepath )
408 + # --- end of __init__ (...) ---
409 +
410 + def _do_create ( self, filepath ):
411 + return self._call_rrdtool (
412 + (
413 + 'create', filepath,
414 + '--start', str ( self.INIT_TIME ),
415 + '--step', str ( self.step ),
416 + ) + tuple (
417 + v.get_key() for v in self.rrd_vars
418 + ) + tuple (
419 + v.get_key() for v in self.rrd_archives
420 + )
421 + )
422 + # --- end of _do_create (...) ---
423 +
424 + def update ( self ):
425 + self.add ( self.collector.get_all() )
426 + # --- end of update (...) ---
427 +
428 + def make_vars ( self ):
429 + return tuple (
430 + RRDVariable ( k, 'DERIVE', val_max=0 )
431 + for k in self.collector.NUMSTATS_KEYS
432 + )
433 + # --- end of make_vars (...) ---
434 +
435 + def make_rra ( self ):
436 + return (
437 + RRDArchive.new_day ( 'LAST', 0.7 ),
438 + RRDArchive.new_week ( 'AVERAGE', 0.7 ),
439 + RRDArchive.new_month ( 'AVERAGE', 0.7 ),
440 + )
441 + # --- end of make_rra (...) ---
442 +
443 +# --- end of StatsDB ---
444
445 diff --git a/roverlay/stats/collector.py b/roverlay/stats/visualize.py
446 similarity index 61%
447 copy from roverlay/stats/collector.py
448 copy to roverlay/stats/visualize.py
449 index 3e3dee7..09ca48e 100644
450 --- a/roverlay/stats/collector.py
451 +++ b/roverlay/stats/visualize.py
452 @@ -1,4 +1,4 @@
453 -# R overlay -- stats collection, stats collector
454 +# R overlay -- stats collection, print stats
455 # -*- coding: utf-8 -*-
456 # Copyright (C) 2013 André Erdmann <dywi@×××××××.de>
457 # Distributed under the terms of the GNU General Public License;
458 @@ -6,84 +6,31 @@
459
460 import collections
461
462 -from . import abstract
463 -from . import base
464 +import roverlay.util.objects
465
466
467 -class StatsCollector ( abstract.RoverlayStatsBase ):
468 +class StatsVisualizer ( object ):
469
470 - _instance = None
471 + def __init__ ( self, stats ):
472 + super ( StatsVisualizer, self ).__init__()
473 + self.stats = stats
474 + self.lines = None
475
476 - _MEMBERS = ( 'time', 'repo', 'distmap', 'overlay_creation', 'overlay', )
477 -
478 - @classmethod
479 - def get_instance ( cls ):
480 - return cls._instance
481 - # --- end of instance (...) ---
482 -
483 - def get_success_ratio ( self ):
484 - # success ratio for "this" run:
485 - # new ebuilds / relevant package count (new packages - unsuitable,
486 - # where unsuitable is e.g. "OS_Type not supported")
487 - #
488 - return abstract.SuccessRatio (
489 - num_ebuilds = self.overlay_creation.pkg_success,
490 - num_pkg = self.overlay_creation.get_relevant_package_count(),
491 - )
492 - # --- end of get_success_ratio (...) ---
493 -
494 - def get_overall_success_ratio ( self ):
495 - # overall success ratio:
496 - # "relevant ebuild count" / "guessed package count"
497 - # ratio := <all ebuilds> / (<all ebuilds> + <failed packages>)
498 - #
499 - #
500 - # *Not* accurate as it includes imported ebuilds
501 - # (Still better than using the repo package count since that may
502 - # not include old package files)
503 - #
504 - return abstract.SuccessRatio (
505 - num_ebuilds = self.overlay.ebuild_count,
506 - num_pkg = (
507 - self.overlay.ebuild_count + self.overlay_creation.pkg_fail
508 - ),
509 - )
510 - # --- end of get_overall_success_ratio (...) ---
511 -
512 - def get_net_gain ( self ):
513 - return (
514 - self.overlay.ebuild_count - self.overlay.ebuilds_scanned
515 - )
516 - # --- end of get_net_gain (...) ---
517 -
518 - def __init__ ( self ):
519 - self.time = abstract.TimeStats ( "misc time stats" )
520 - self.distmap = base.DistmapStats()
521 - self.overlay = base.OverlayStats()
522 - self.overlay_creation = base.OverlayCreationStats()
523 - self.repo = base.RepoStats()
524 + self.prepare()
525 # --- end of __init__ (...) ---
526
527 - def gen_str ( self ):
528 - yield "{success}, overall {osuccess}".format (
529 - success = self.get_success_ratio(),
530 - osuccess = self.get_overall_success_ratio(),
531 - )
532 - yield ""
533 -
534 - for s in super ( StatsCollector, self ).gen_str():
535 - yield s
536 - yield ""
537 - # --- end of gen_str (...) ---
538 -
539 - def get_creation_str ( self ):
540 - return str ( CreationStatsVisualizer ( self ) )
541 - # --- end of to_creation_str (...) ---
542 + @roverlay.util.objects.not_implemented
543 + def prepare ( self ):
544 + pass
545 + # --- end of prepare (...) ---
546
547 -# --- end of StatsCollector ---
548 + def __str__ ( self ):
549 + return '\n'.join ( self.lines )
550 + # --- end of __str__ (...) ---
551
552 +# --- end of StatsVisualizer ---
553
554 -class CreationStatsVisualizer ( abstract.StatsVisualizer ):
555 +class CreationStatsVisualizer ( StatsVisualizer ):
556
557 def prepare ( self ):
558 EMPTY_LINE = ""
559 @@ -206,7 +153,5 @@ class CreationStatsVisualizer ( abstract.StatsVisualizer ):
560
561 self.lines = lines
562 # --- end of gen_str (...) ---
563 -# --- end of CreationStatsVisualizer ---
564
565 -static = StatsCollector()
566 -StatsCollector._instance = static
567 +# --- end of CreationStatsVisualizer ---