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
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
110 """
111 Wrapper class around rrdtool
112 """
113
114 - def __init__(self, defaultRrdCreateCommand, defaultCycleTime):
115 """
116 Initializer
117
118 The RRD creation command is only used if the RRD file doesn't
119 exist and no rrdCommand was specified with the save() method.
120
121 @param defaultRrdCreateCommand: RRD creation command
122 @type defaultRrdCreateCommand: string
123 @param defaultCycleTime: expected time to periodically collect data
124 @type defaultCycleTime: integer
125 """
126 self.defaultRrdCreateCommand = defaultRrdCreateCommand
127 self.defaultCycleTime = defaultCycleTime
128 self.dataPoints = 0
129 self.cycleDataPoints = 0
130
131
133 """
134 Report on the number of data points collected in a cycle,
135 and reset the counter for a new cycle.
136
137 @return: number of data points collected during the cycle
138 @rtype: number
139 """
140 result = self.cycleDataPoints
141 self.cycleDataPoints = 0
142 return result
143
144
157
158
160 """
161 Return the step value for the provided cycleTime. This is a hook for
162 altering the default step calculation.
163 """
164 return int(cycleTime)
165
166
168 """
169 Return the heartbeat value for the provided cycleTime. This is a hook
170 for altering the default heartbeat calculation.
171 """
172 return int(cycleTime) * 3
173
174
175 - def save(self, path, value, rrdType, rrdCommand=None, cycleTime=None,
176 min='U', max='U'):
177 """
178 Save the value provided in the command to the RRD file specified in path.
179
180 If the RRD file does not exist, use the rrdType, rrdCommand, min and
181 max parameters to create the file.
182
183 @param path: name for a datapoint in a path (eg device/component/datasource_datapoint)
184 @type path: string
185 @param value: value to store into the RRD file
186 @type value: number
187 @param rrdType: RRD data type (eg ABSOLUTE, DERIVE, COUNTER)
188 @type rrdType: string
189 @param rrdCommand: RRD file creation command
190 @type rrdCommand: string
191 @param cycleTime: length of a cycle
192 @type cycleTime: number
193 @param min: minimum value acceptable for this metric
194 @type min: number
195 @param max: maximum value acceptable for this metric
196 @type max: number
197 @return: the parameter value converted to a number
198 @rtype: number or None
199 """
200 import rrdtool, os
201
202 if value is None: return None
203
204 self.dataPoints += 1
205 self.cycleDataPoints += 1
206
207 if cycleTime is None:
208 cycleTime = self.defaultCycleTime
209
210 filename = self.performancePath(path) + '.rrd'
211 if not rrdCommand:
212 rrdCommand = self.defaultRrdCreateCommand
213 if not os.path.exists(filename):
214 log.debug("Creating new RRD file %s", filename)
215 dirname = os.path.dirname(filename)
216 if not os.path.exists(dirname):
217 os.makedirs(dirname, 0750)
218
219 min, max = map(_checkUndefined, (min, max))
220 dataSource = 'DS:%s:%s:%d:%s:%s' % (
221 'ds0', rrdType, self.getHeartbeat(cycleTime), min, max)
222 rrdtool.create(str(filename), "--step",
223 str(self.getStep(cycleTime)),
224 str(dataSource), *rrdCommand.split())
225
226 if rrdType in ('COUNTER', 'DERIVE'):
227 try:
228 value = long(value)
229 except (TypeError, ValueError):
230 return None
231 else:
232 try:
233 value = float(value)
234 except (TypeError, ValueError):
235 return None
236 try:
237 rrdtool.update(str(filename), 'N:%s' % value)
238 log.debug('%s: %r', str(filename), value)
239 except rrdtool.error, err:
240
241 log.error('rrdtool reported error %s %s', err, path)
242
243 if rrdType in ('COUNTER', 'DERIVE'):
244 startStop, names, values = \
245 rrdtool.fetch(filename, 'AVERAGE',
246 '-s', 'now-%d' % (cycleTime*2),
247 '-e', 'now')
248 values = [ v[0] for v in values if v[0] is not None ]
249 if values: value = values[-1]
250 else: value = None
251 return value
252