|
| 1 | +# Redistribution and use in source and binary forms, with or without |
| 2 | +# modification, are permitted provided that the following conditions are met: |
| 3 | +# * Redistributions of source code must retain the above copyright |
| 4 | +# notice, this list of conditions and the following disclaimer. |
| 5 | +# * Redistributions in binary form must reproduce the above copyright |
| 6 | +# notice, this list of conditions and the following disclaimer in the |
| 7 | +# documentation and/or other materials provided with the distribution. |
| 8 | +# |
| 9 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 10 | +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 11 | +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 12 | +# DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE FOR ANY |
| 13 | +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 14 | +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 15 | +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 16 | +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 17 | +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 18 | +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 19 | + |
| 20 | + |
| 21 | +import abc |
| 22 | +import copy |
| 23 | +import logging |
| 24 | +import logging.handlers |
| 25 | +import optparse |
| 26 | +import time |
| 27 | +import sys |
| 28 | + |
| 29 | +import pybindxml.reader |
| 30 | + |
| 31 | +log = None |
| 32 | + |
| 33 | +QUERY_TYPES = ['A', 'SOA', 'DS' 'UPDATE', 'MX', 'AAAA', 'DNSKEY', 'QUERY', 'TXT', 'PTR'] |
| 34 | + |
| 35 | +METRIC_PREFIX = 'bind_' |
| 36 | + |
| 37 | +DESCRIPTION_SKELETON = { |
| 38 | + 'name' : 'XXX', |
| 39 | + 'time_max' : 60, |
| 40 | + 'value_type' : 'uint', # (string, uint, float, double) |
| 41 | + 'format' : '%d', #String formatting ('%s', '%d','%f') |
| 42 | + 'units' : 'XXX', |
| 43 | + 'slope' : 'both', |
| 44 | + 'description' : 'XXX', |
| 45 | + 'groups' : 'bind_xml', |
| 46 | + 'calc' : 'scalar' # scalar |
| 47 | + } |
| 48 | + |
| 49 | + |
| 50 | +METRICS = [ |
| 51 | + {'name': 'mem_BlockSize', |
| 52 | + 'description': '', |
| 53 | + 'value_type': 'double', |
| 54 | + 'format': '%f', |
| 55 | + 'units': 'bytes'}, |
| 56 | + {'name': 'mem_ContextSize', |
| 57 | + 'description': '', |
| 58 | + 'value_type': 'double', |
| 59 | + 'format': '%f', |
| 60 | + 'units': 'bytes'}, |
| 61 | + {'name': 'mem_InUse', |
| 62 | + 'description': '', |
| 63 | + 'value_type': 'double', |
| 64 | + 'format': '%f', |
| 65 | + 'units': 'bytes'}, |
| 66 | + {'name': 'mem_TotalUse', |
| 67 | + 'description': '', |
| 68 | + 'units': 'bytes', |
| 69 | + 'value_type': 'double', |
| 70 | + 'format': '%f'}, |
| 71 | + ] |
| 72 | + |
| 73 | + |
| 74 | +#### Data Acces |
| 75 | + |
| 76 | +class BindStats(object): |
| 77 | + |
| 78 | + bind_reader = None |
| 79 | + |
| 80 | + stats = None |
| 81 | + stats_last = None |
| 82 | + now_ts = -1 |
| 83 | + last_ts = -1 |
| 84 | + |
| 85 | + def __init__(self, host, port, min_poll_seconds): |
| 86 | + self.host = host |
| 87 | + self.port = int(port) |
| 88 | + self.min_poll_seconds = int(min_poll_seconds) |
| 89 | + |
| 90 | + def short_name(self, name): |
| 91 | + return name.split('bind_')[1] |
| 92 | + |
| 93 | + def get_bind_reader(self): |
| 94 | + if self.bind_reader is None: |
| 95 | + self.bind_reader = pybindxml.reader.BindXmlReader(host=self.host, port=self.port) |
| 96 | + return self.bind_reader |
| 97 | + |
| 98 | + def should_update(self): |
| 99 | + return (self.now_ts == -1 or time.time() - self.now_ts > self.min_poll_seconds) |
| 100 | + |
| 101 | + def update_stats(self): |
| 102 | + self.stats_last = self.stats |
| 103 | + self.last_ts = self.now_ts |
| 104 | + self.stats = {} |
| 105 | + |
| 106 | + self.get_bind_reader().get_stats() |
| 107 | + for element, value in self.get_bind_reader().stats.memory_stats.items(): |
| 108 | + self.stats['mem_' + element] = value |
| 109 | + |
| 110 | + # Report queries as a rate of zero if none are reported |
| 111 | + for qtype in QUERY_TYPES: |
| 112 | + self.stats['query_' + qtype] = 0 |
| 113 | + for element, value in self.get_bind_reader().stats.query_stats.items(): |
| 114 | + self.stats['query_' + element] = value |
| 115 | + |
| 116 | + self.now_ts = int(time.time()) |
| 117 | + |
| 118 | + |
| 119 | + def get_metric_value(self, name): |
| 120 | + if self.should_update() is True: |
| 121 | + self.update_stats() |
| 122 | + if self.stats is None or self.stats_last is None: |
| 123 | + log.debug('Not enough stat data has been collected yet now_ts:%r last_ts:%r' % (self.now_ts, self.last_ts)) |
| 124 | + return None |
| 125 | + descriptor = NAME_2_DESCRIPTOR[name] |
| 126 | + if descriptor['calc'] == 'scalar': |
| 127 | + val = self.stats[self.short_name(name)] |
| 128 | + elif descriptor['calc'] == 'rate': |
| 129 | + val = (self.stats[self.short_name(name)] - self.stats_last[self.short_name(name)]) / (self.now_ts - self.last_ts) |
| 130 | + else: |
| 131 | + log.warn('unknokwn memtric calc type %s' % descriptor['calc']) |
| 132 | + return None |
| 133 | + log.debug('on call_back got %s = %r' % (self.short_name(name), val)) |
| 134 | + if descriptor['value_type'] == 'uint': |
| 135 | + return long(val) |
| 136 | + else: |
| 137 | + return float(val) |
| 138 | + |
| 139 | + |
| 140 | +#### module functions |
| 141 | + |
| 142 | + |
| 143 | +def metric_init(params): |
| 144 | + global BIND_STATS, NAME_2_DESCRIPTOR |
| 145 | + if log is None: |
| 146 | + setup_logging('syslog', params['syslog_facility'], params['log_level']) |
| 147 | + log.debug('metric_init: %r' % params) |
| 148 | + BIND_STATS = BindStats(params['host'], params['port'], params['min_poll_seconds']) |
| 149 | + descriptors = [] |
| 150 | + for qtype in QUERY_TYPES: |
| 151 | + METRICS.append({'name': 'query_' + qtype, |
| 152 | + 'description': '%s queries per second', |
| 153 | + 'value_type': 'double', 'format': '%f', |
| 154 | + 'units': 'req/s', 'calc': 'rate'}) |
| 155 | + for metric in METRICS: |
| 156 | + d = copy.copy(DESCRIPTION_SKELETON) |
| 157 | + d.update(metric) |
| 158 | + d['name'] = METRIC_PREFIX + d['name'] |
| 159 | + d['call_back'] = BIND_STATS.get_metric_value |
| 160 | + descriptors.append(d) |
| 161 | + log.debug('descriptors: %r' % descriptors) |
| 162 | + for d in descriptors: |
| 163 | + for key in ['name', 'units', 'description']: |
| 164 | + if d[key] == 'XXX': |
| 165 | + log.warn('incomplete descriptor definition: %r' % d) |
| 166 | + if d['value_type'] == 'uint' and d['format'] != '%d': |
| 167 | + log.warn('value/type format mismatch: %r' % d) |
| 168 | + NAME_2_DESCRIPTOR = {} |
| 169 | + for d in descriptors: |
| 170 | + NAME_2_DESCRIPTOR[d['name']] = d |
| 171 | + return descriptors |
| 172 | + |
| 173 | + |
| 174 | + |
| 175 | +def metric_cleanup(): |
| 176 | + logging.shutdown() |
| 177 | + |
| 178 | + |
| 179 | +#### Main and Friends |
| 180 | + |
| 181 | +def setup_logging(handlers, facility, level): |
| 182 | + global log |
| 183 | + |
| 184 | + log = logging.getLogger('gmond_python_bind_xml') |
| 185 | + formatter = logging.Formatter(' | '.join(['%(asctime)s', '%(name)s', '%(levelname)s', '%(message)s'])) |
| 186 | + if handlers in ['syslog', 'both']: |
| 187 | + sh = logging.handlers.SysLogHandler(address='/dev/log', facility=facility) |
| 188 | + sh.setFormatter(formatter) |
| 189 | + log.addHandler(sh) |
| 190 | + if handlers in ['stdout', 'both']: |
| 191 | + ch = logging.StreamHandler() |
| 192 | + ch.setFormatter(formatter) |
| 193 | + log.addHandler(ch) |
| 194 | + lmap = { |
| 195 | + 'CRITICAL': logging.CRITICAL, |
| 196 | + 'ERROR': logging.ERROR, |
| 197 | + 'WARNING': logging.WARNING, |
| 198 | + 'INFO': logging.INFO, |
| 199 | + 'DEBUG': logging.DEBUG, |
| 200 | + 'NOTSET': logging.NOTSET |
| 201 | + } |
| 202 | + log.setLevel(lmap[level]) |
| 203 | + |
| 204 | + |
| 205 | +def parse_args(argv): |
| 206 | + parser = optparse.OptionParser() |
| 207 | + parser.add_option('--log', |
| 208 | + action='store', dest='log', default='stdout', choices=['stdout', 'syslog', 'both'], |
| 209 | + help='log to stdout and/or syslog') |
| 210 | + parser.add_option('--log-level', |
| 211 | + action='store', dest='log_level', default='WARNING', |
| 212 | + choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'], |
| 213 | + help='log to stdout and/or syslog') |
| 214 | + parser.add_option('--log-facility', |
| 215 | + action='store', dest='log_facility', default='user', |
| 216 | + help='facility to use when using syslog') |
| 217 | + |
| 218 | + return parser.parse_args(argv) |
| 219 | + |
| 220 | + |
| 221 | +def main(argv): |
| 222 | + """ used for testing """ |
| 223 | + (opts, args) = parse_args(argv) |
| 224 | + setup_logging(opts.log, opts.log_facility, opts.log_level) |
| 225 | + params = {'min_poll_seconds': 5, 'host': 'asu101', 'port': 8053} |
| 226 | + descriptors = metric_init(params) |
| 227 | + try: |
| 228 | + while True: |
| 229 | + for d in descriptors: |
| 230 | + v = d['call_back'](d['name']) |
| 231 | + if v is None: |
| 232 | + print 'got None for %s' % d['name'] |
| 233 | + else: |
| 234 | + print 'value for %s is %r' % (d['name'], v) |
| 235 | + time.sleep(5) |
| 236 | + print '----------------------------' |
| 237 | + except KeyboardInterrupt: |
| 238 | + log.debug('KeyboardInterrupt, shutting down...') |
| 239 | + metric_cleanup() |
| 240 | + |
| 241 | +if __name__ == '__main__': |
| 242 | + main(sys.argv[1:]) |
| 243 | + |
0 commit comments