1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__ = """RRDUtil
15
16 Wrapper routines around the rrdtool library.
17 """
18
19 import logging
20 log = logging.getLogger("zen.RRDUtil")
21
22 import os
23 import re
24
25 from Products.ZenUtils.Utils import zenPath, rrd_daemon_running
26
27
28 EMPTY_RRD = zenPath('perf', 'empty.rrd')
29
30
32 """
33 Sanity check on the min, max values
34
35 @param x: RRD min or max value
36 @type x: number
37 @return: Either the number or 'U' (for undefined)
38 @rtype: number or string
39 """
40 if x is None or x == '' or x == -1 or x == '-1':
41 return 'U'
42 return x
43
44
46 """
47 Convert any value that is passed in to a string that is acceptable to use
48 for RRDtool's start and end parameters. Raises ValueError if this is not
49 possible.
50
51 See the AT-STYLE TIME SPECIFICATION and TIME REFERENCE SPECIFICATION
52 sections of the following document.
53
54 http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html
55
56 Note: Currently this method is only fixing floats by turning them into
57 strings with no decimal places.
58 """
59
60 try:
61 result = int(val)
62 return str(result)
63 except ValueError:
64 pass
65
66 return str(val)
67
68
70 """
71 Parses a list of RRDtool gopts for DEFs. Runs all of the filenames
72 referenced by those DEFs through the fixRRDFilename method to make sure
73 that they will exist and not cause the rendering to fail.
74 """
75 fixed_gopts = []
76
77 def_match = re.compile(r'^DEF:([^=]+)=([^:]+)').match
78 for gopt in gopts:
79 match = def_match(gopt)
80 if not match:
81 fixed_gopts.append(gopt)
82 continue
83
84 rrd_filename = match.group(2)
85 fixed_gopts.append(gopt.replace(
86 rrd_filename, fixRRDFilename(rrd_filename)))
87
88 return fixed_gopts
89
90
92 """
93 Attempting to render a graph containing a DEF referencing a non-existent
94 filename will cause the entire graph to fail to render. This method is a
95 helper to verify existence of an RRD file. If the file doesn't exist, a
96 placeholder RRD filename with no values in it will be returned instead.
97 """
98 if os.path.isfile(filename):
99 return filename
100
101 if not os.path.isfile(EMPTY_RRD):
102 import rrdtool
103 rrdtool.create(EMPTY_RRD, "--step", '300', 'DS:ds0:GAUGE:900:U:U',
104 'RRA:AVERAGE:0.5:1:1', 'RRA:MAX:0.5:1:1')
105
106 return EMPTY_RRD
107
108 -def read(path, consolidationFunction, start, end):
109 import rrdtool
110 try:
111 return rrdtool.fetch(path, consolidationFunction, start, end)
112 except rrdtool.error, err:
113 import sys
114 err_str = '%s: %s' % (err.__class__.__name__, err)
115 msg = 'Failed to read RRD file %s. %s' % (path, err_str)
116 raise StandardError(msg), None, sys.exc_info()[2]
117
119 """
120 Wrapper class around rrdtool
121 """
122
123 - def __init__(self, defaultRrdCreateCommand, defaultCycleTime):
124 """
125 Initializer
126
127 The RRD creation command is only used if the RRD file doesn't
128 exist and no rrdCommand was specified with the save() method.
129
130 @param defaultRrdCreateCommand: RRD creation command
131 @type defaultRrdCreateCommand: string
132 @param defaultCycleTime: expected time to periodically collect data
133 @type defaultCycleTime: integer
134 """
135 self.defaultRrdCreateCommand = defaultRrdCreateCommand
136 self.defaultCycleTime = defaultCycleTime
137 self.dataPoints = 0
138 self.cycleDataPoints = 0
139
140
142 """
143 Report on the number of data points collected in a cycle,
144 and reset the counter for a new cycle.
145
146 @return: number of data points collected during the cycle
147 @rtype: number
148 """
149 result = self.cycleDataPoints
150 self.cycleDataPoints = 0
151 return result
152
153
166
167
169 """
170 Return the step value for the provided cycleTime. This is a hook for
171 altering the default step calculation.
172 """
173 return int(cycleTime)
174
175
177 """
178 Return the heartbeat value for the provided cycleTime. This is a hook
179 for altering the default heartbeat calculation.
180 """
181 return int(cycleTime) * 3
182
183
184 - def save(self, path, value, rrdType, rrdCommand=None, cycleTime=None,
185 min='U', max='U', useRRDDaemon=True):
186 """
187 Save the value provided in the command to the RRD file specified in path.
188
189 If the RRD file does not exist, use the rrdType, rrdCommand, min and
190 max parameters to create the file.
191
192 @param path: name for a datapoint in a path (eg device/component/datasource_datapoint)
193 @type path: string
194 @param value: value to store into the RRD file
195 @type value: number
196 @param rrdType: RRD data type (eg ABSOLUTE, DERIVE, COUNTER)
197 @type rrdType: string
198 @param rrdCommand: RRD file creation command
199 @type rrdCommand: string
200 @param cycleTime: length of a cycle
201 @type cycleTime: number
202 @param min: minimum value acceptable for this metric
203 @type min: number
204 @param max: maximum value acceptable for this metric
205 @type max: number
206 @return: the parameter value converted to a number
207 @rtype: number or None
208 """
209 import rrdtool, os
210
211 daemon_args = ()
212 if useRRDDaemon:
213 daemon = rrd_daemon_running()
214 if daemon:
215 daemon_args = ('--daemon', daemon)
216
217 if value is None: return None
218
219 self.dataPoints += 1
220 self.cycleDataPoints += 1
221
222 if cycleTime is None:
223 cycleTime = self.defaultCycleTime
224
225 filename = self.performancePath(path) + '.rrd'
226 if not rrdCommand:
227 rrdCommand = self.defaultRrdCreateCommand
228 if not os.path.exists(filename):
229 log.debug("Creating new RRD file %s", filename)
230 dirname = os.path.dirname(filename)
231 if not os.path.exists(dirname):
232 os.makedirs(dirname, 0750)
233
234 min, max = map(_checkUndefined, (min, max))
235 dataSource = 'DS:%s:%s:%d:%s:%s' % (
236 'ds0', rrdType, self.getHeartbeat(cycleTime), min, max)
237 rrdtool.create(str(filename), "--step",
238 str(self.getStep(cycleTime)),
239 str(dataSource), *rrdCommand.split())
240
241 if rrdType in ('COUNTER', 'DERIVE'):
242 try:
243 value = long(value)
244 except (TypeError, ValueError):
245 return None
246 else:
247 try:
248 value = float(value)
249 except (TypeError, ValueError):
250 return None
251 try:
252 rrdtool.update(str(filename), *(daemon_args + ('N:%s' % value,)))
253 log.debug('%s: %r', str(filename), value)
254 except rrdtool.error, err:
255
256 log.error('rrdtool reported error %s %s', err, path)
257
258 if rrdType in ('COUNTER', 'DERIVE'):
259 startStop, names, values = \
260 rrdtool.fetch(filename, 'AVERAGE',
261 '-s', 'now-%d' % (cycleTime*2),
262 '-e', 'now', *daemon_args)
263 values = [ v[0] for v in values if v[0] is not None ]
264 if values: value = values[-1]
265 else: value = None
266 return value
267