1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import os
23
24 from xml.dom import Node
25
26 from flumotion.configure import configure
27 from flumotion.common import common, config, connection
28 from flumotion.common.errors import ConfigError
29
30
31 """
32 RRD monitor configuration
33
34 The format of the configuration file is as follows. *, +, and ? have
35 their normal meanings: 0 or more, 1 or more, and 0 or 1, respectively.
36
37 <rrdmon>
38
39 <!-- normal -->
40 <debug>*:4</debug> ?
41
42 <!-- implementation note: the name of the source is used as the DS
43 name in the RRD file -->
44 <source name="http-streamer"> +
45
46 <!-- how we connect to the manager; parsed with
47 L{flumotion.common.connection.parsePBConnectionInfo} -->
48 <manager>user:test@localhost:7531</manager>
49
50 <!-- the L{flumotion.common.common.componentId} of the component we
51 will poll -->
52 <component-id>/default/http-audio-video</component-id>
53
54 <!-- the key of the L{flumotion.common.componentui} UIState that we
55 will poll; should be numeric in value -->
56 <ui-state-key>stream-totalbytes-raw</ui-state-key>
57
58 <!-- boolean; examples of gauge values would be number of users,
59 temperature, signal strength, precomputed bitrate. The most
60 common non-gauge values are bitrate values, where you poll e.g.
61 the number of bytes sent, not the rate itself -->
62 <is-gauge>False</is-gauge> ?
63
64 <!-- sample frequency in seconds, defaults to 5 minutes -->
65 <sample-frequency>300</sample-frequency> ?
66
67 <!-- Normally we generate the RRD DS spec from the answers above,
68 but if you want to you can specify one directly here. The DS
69 name should be the source name -->
70 <rrd-ds-spec>DS-SPEC</rrd-ds-spec> ?
71
72 <!-- file will be created if necessary -->
73 <rrd-file>/tmp/stream-bitrate.rrd</rrd-file>
74
75 <!-- set of archives to store in the rrd file
76 <archive> +
77 <!-- Would be nice to break this down as we did above for the DS
78 spec, but for now you have to specify the RRA specs manually.
79 Bummer dude! In this example, the meaning is that we should
80 archive a sample every 1*stepsize=1*300s=5 minutes, for 1200
81 samples = 5 min*1200=100h.-->
82 <rra-spec>AVERAGE:0.5:1:1200</rra-spec>
83 </archive>
84 </source>
85
86 </rrdmon>
87 """
88
89
91 """
92 RRD monitor configuration file parser.
93
94 Create a parser via passing the name of the file to parse to
95 __init__. Parse into a dict of properly-typed options by calling
96 parse() on the parser.
97 """
98 logCategory = 'rrdmon-config'
99
106
108 def strparser(parser):
109 def parsestr(node):
110 return self.parseTextNode(node, parser)
111 return parsestr
112 def ressetter(k):
113 def setter(v):
114 res[k] = v
115 return setter
116
117 res = {}
118 table = {}
119 basicOptions = (('rra-spec', True, str, None),)
120 for k, required, parser, default in basicOptions:
121 table[k] = strparser(parser), ressetter(k)
122 if not required:
123 res[k] = default
124
125 self.parseFromTable(node, table)
126
127 for k, required, parser, default in basicOptions:
128 if required and k not in res:
129 raise config.ConfigError('missing required node %s' % k)
130 return res
131
133 def strparser(parser):
134 def parsestr(node):
135 return self.parseTextNode(node, parser)
136 return parsestr
137 def ressetter(k):
138 def setter(v):
139 res[k] = v
140 return setter
141 def filename(v):
142 if v[0] != os.sep:
143 raise config.ConfigError('rrdfile paths should be absolute')
144 return str(v)
145
146 name, = self.parseAttributes(node, ('name',))
147
148 res = {'name': name}
149 table = {}
150
151 basicOptions = (('manager', True,
152 connection.parsePBConnectionInfo, None),
153 ('component-id', True, str, None),
154 ('ui-state-key', True, str, None),
155 ('sample-frequency', False, int, 300),
156 ('is-gauge', False, common.strToBool, True),
157 ('rrd-ds-spec', False, str, None),
158 ('rrd-file', True, filename, None))
159 for k, required, parser, default in basicOptions:
160 table[k] = strparser(parser), ressetter(k)
161 if not required:
162 res[k] = default
163
164 res['archives'] = []
165 table['archive'] = (self._parseArchive, res['archives'].append)
166
167 self.parseFromTable(node, table)
168
169 for k, required, parser, default in basicOptions:
170 if required and k not in res:
171 raise config.ConfigError('missing required node %s' % k)
172 if not res['archives']:
173 raise config.ConfigError('must specify at least one '
174 '<archive> per <source>')
175
176 return res
177
179
180
181 root = self.doc.documentElement
182 if not root.nodeName == 'rrdmon':
183 raise ConfigError("unexpected root node: %s" % root.nodeName)
184
185 def strparser(parser):
186 def parsestr(node):
187 return self.parseTextNode(node, parser)
188 return parsestr
189 def ressetter(k):
190 def setter(v):
191 res[k] = v
192 return setter
193
194 res = {'debug': None,
195 'sources': []}
196 table = {'debug': (strparser(str), ressetter('debug')),
197 'source': (self._parseSource, res['sources'].append)}
198
199 self.parseFromTable(root, table)
200
201 if not res['sources']:
202 raise config.ConfigError('must specify at least one '
203 '<source>')
204
205 return res
206