1 |
commit: 3b92458a280855f69a6aabc8a6c84e4868562444 |
2 |
Author: André Erdmann <dywi <AT> mailerd <DOT> de> |
3 |
AuthorDate: Tue Jul 30 16:02:12 2013 +0000 |
4 |
Commit: André Erdmann <dywi <AT> mailerd <DOT> de> |
5 |
CommitDate: Tue Jul 30 16:02:12 2013 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=3b92458a |
7 |
|
8 |
roverlay/db: rrdtool.py |
9 |
|
10 |
--- |
11 |
roverlay/db/rrdtool.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++++ |
12 |
1 file changed, 176 insertions(+) |
13 |
|
14 |
diff --git a/roverlay/db/rrdtool.py b/roverlay/db/rrdtool.py |
15 |
new file mode 100644 |
16 |
index 0000000..6d450fe |
17 |
--- /dev/null |
18 |
+++ b/roverlay/db/rrdtool.py |
19 |
@@ -0,0 +1,176 @@ |
20 |
+# R overlay -- stats collection, wrapper for writing rrd databases |
21 |
+# -*- coding: utf-8 -*- |
22 |
+# Copyright (C) 2013 André Erdmann <dywi@×××××××.de> |
23 |
+# Distributed under the terms of the GNU General Public License; |
24 |
+# either version 2 of the License, or (at your option) any later version. |
25 |
+ |
26 |
+from __future__ import division |
27 |
+ |
28 |
+# NOT using rrdtool's python bindings as they're available for python 2 only |
29 |
+ |
30 |
+import logging |
31 |
+import os |
32 |
+import time |
33 |
+ |
34 |
+import roverlay.tools.runcmd |
35 |
+from roverlay.tools.runcmd import run_command |
36 |
+ |
37 |
+import roverlay.util |
38 |
+ |
39 |
+import roverlay.util.objects |
40 |
+ |
41 |
+class RRDVariable ( object ): |
42 |
+ |
43 |
+ DST = frozenset ({ 'GAUGE', 'COUNTER', 'DERIVE', 'ABSOLUTE', }) |
44 |
+ |
45 |
+ def __init__ ( self, |
46 |
+ name, val_type, val_min=None, val_max=None, heartbeat=None, step=300 |
47 |
+ ): |
48 |
+ if val_type not in self.DST: |
49 |
+ raise ValueError ( "invalid DS type: {!r}".format ( val_type ) ) |
50 |
+ |
51 |
+ self.name = name |
52 |
+ self.val_type = val_type |
53 |
+ self.val_min = val_min |
54 |
+ self.val_max = val_max |
55 |
+ self.heartbeat = heartbeat or ( 2 * step ) |
56 |
+ # --- end of __init__ (...) --- |
57 |
+ |
58 |
+ def get_key ( self ): |
59 |
+ mstr = lambda k: 'U' if k is None else str ( k ) |
60 |
+ |
61 |
+ return "DS:{ds_name}:{DST}:{heartbeat}:{vmin}:{vmax}".format ( |
62 |
+ ds_name=self.name, DST=self.val_type, heartbeat=self.heartbeat, |
63 |
+ vmin=mstr ( self.val_min ), vmax=mstr ( self.val_max ) |
64 |
+ ) |
65 |
+ # --- end of get_key (...) --- |
66 |
+ |
67 |
+ __str__ = get_key |
68 |
+ |
69 |
+# --- end of RRDVariable --- |
70 |
+ |
71 |
+class RRDArchive ( object ): |
72 |
+ |
73 |
+ CF_TYPES = frozenset ({ 'AVERAGE', 'MIN', 'MAX', 'LAST', }) |
74 |
+ |
75 |
+ def __init__ ( self, cf, xff, steps, rows ): |
76 |
+ if cf not in self.CF_TYPES: |
77 |
+ raise ValueError ( "unknown CF: {!r}".format ( cf ) ) |
78 |
+ elif not isinstance ( xff, float ): |
79 |
+ raise TypeError ( "xff must be a float." ) |
80 |
+ elif xff < 0.0 or xff >= 1.0: |
81 |
+ raise ValueError ( |
82 |
+ "xff not in range: 0.0 <= {:f} <= 1.0?".format ( xff ) |
83 |
+ ) |
84 |
+ elif not isinstance ( steps, int ) or steps <= 0: |
85 |
+ raise ValueError ( "steps must be an integer > 0." ) |
86 |
+ |
87 |
+ elif not isinstance ( rows, int ) or rows <= 0: |
88 |
+ raise ValueError ( "rows must be an integer > 0." ) |
89 |
+ |
90 |
+ self.cf = cf |
91 |
+ self.xff = float ( xff ) |
92 |
+ self.steps = int ( steps ) |
93 |
+ self.rows = int ( rows ) |
94 |
+ # --- end of __init__ (...) --- |
95 |
+ |
96 |
+ def get_key ( self ): |
97 |
+ return "RRA:{cf}:{xff}:{steps}:{rows}".format ( |
98 |
+ cf=self.cf, xff=self.xff, steps=self.steps, rows=self.rows |
99 |
+ ) |
100 |
+ # --- end of get_key (...) --- |
101 |
+ |
102 |
+ __str__ = get_key |
103 |
+ |
104 |
+ @classmethod |
105 |
+ def new_day ( cls, cf, xff, step=300 ): |
106 |
+ # one CDP per hour (24 rows) |
107 |
+ return cls ( cf, xff, 3600 // step, 24 ) |
108 |
+ # --- end of new_day (...) --- |
109 |
+ |
110 |
+ @classmethod |
111 |
+ def new_week ( cls, cf, xff, step=300 ): |
112 |
+ # one CDP per 6h (28 rows) |
113 |
+ return cls ( cf, xff, 21600 // step, 42 ) |
114 |
+ # --- end of new_week (...) --- |
115 |
+ |
116 |
+ @classmethod |
117 |
+ def new_month ( cls, cf, xff, step=300 ): |
118 |
+ # one CDP per day (31 rows) |
119 |
+ return cls ( cf, xff, (24*3600) // step, 31 ) |
120 |
+ # --- end of new_month (...) --- |
121 |
+ |
122 |
+# --- end of RRDArchive --- |
123 |
+ |
124 |
+class RRD ( object ): |
125 |
+ # should be subclassed 'cause _do_create() is not implemented here |
126 |
+ |
127 |
+ RRDTOOL_CMDV_HEAD = ( 'rrdtool', ) |
128 |
+ |
129 |
+ LOGGER = logging.getLogger ( 'RRD' ) |
130 |
+ |
131 |
+ def __init__ ( self, filepath ): |
132 |
+ self.filepath = filepath |
133 |
+ self._commit_buffer = [] |
134 |
+ self._dbfile_exists = False |
135 |
+ self.logger = self.__class__.LOGGER |
136 |
+ self.INIT_TIME = self.time_now() - 10 |
137 |
+ # --- end of __init__ (...) --- |
138 |
+ |
139 |
+ def time_now ( self ): |
140 |
+ return int ( time.time() ) |
141 |
+ # --- end of time_now (...) --- |
142 |
+ |
143 |
+ def _call_rrdtool ( self, args, return_success=True ): |
144 |
+ return run_command ( |
145 |
+ self.RRDTOOL_CMDV_HEAD + args, None, self.logger, |
146 |
+ return_success=return_success |
147 |
+ ) |
148 |
+ # --- end of _call_rrdtool (...) --- |
149 |
+ |
150 |
+ @roverlay.util.objects.abstractmethod |
151 |
+ def _do_create ( self, filepath ): |
152 |
+ pass |
153 |
+ # --- end of _do_create (...) --- |
154 |
+ |
155 |
+ def create_if_missing ( self ): |
156 |
+ if self._dbfile_exists or not os.access ( self.filepath, os.F_OK ): |
157 |
+ return self.create() |
158 |
+ # --- end of create_if_missing (...) --- |
159 |
+ |
160 |
+ def create ( self ): |
161 |
+ roverlay.util.dodir_for_file ( self.filepath ) |
162 |
+ self._do_create ( self.filepath ) |
163 |
+ if os.access ( self.filepath, os.F_OK ): |
164 |
+ self._dbfile_exists = True |
165 |
+ else: |
166 |
+ raise Exception ( "database file does not exist." ) |
167 |
+ # --- end of create (...) --- |
168 |
+ |
169 |
+ def add ( self, values, timestamp=None ): |
170 |
+ if timestamp is False: |
171 |
+ t = 'N' |
172 |
+ elif timestamp is None: |
173 |
+ t = str ( self.time_now() ) |
174 |
+ else: |
175 |
+ t = str ( timestamp ) |
176 |
+ |
177 |
+ self._commit_buffer.append ( |
178 |
+ t + ':' + ':'.join ( str(v) for v in values ) |
179 |
+ ) |
180 |
+ # --- end of add (...) --- |
181 |
+ |
182 |
+ def clear ( self ): |
183 |
+ self._commit_buffer = [] |
184 |
+ # --- end of clear (...) --- |
185 |
+ |
186 |
+ def commit ( self ): |
187 |
+ if self._commit_buffer: |
188 |
+ self.create_if_missing() |
189 |
+ self._call_rrdtool ( |
190 |
+ ( 'update', self.filepath ) + tuple ( self._commit_buffer ) |
191 |
+ ) |
192 |
+ self.clear() |
193 |
+ # --- end of commit (...) --- |
194 |
+ |
195 |
+# --- end of RRD --- |