├── visual ├── munin │ ├── 1opcodes.jpg │ ├── 2queries-in.jpg │ ├── 3queries-out.jpg │ ├── 5server_stats.jpg │ └── 4resolver-stats.jpg └── grafana │ ├── bind9-grafana1.png │ ├── bind9-grafana2.png │ └── bind9-grafana3.png ├── sysconfig-options ├── systemd-service ├── README.md ├── bind9stats-munin.py ├── bind9stats-graphite.py └── Grafana-BIND.json /visual/munin/1opcodes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/munin/1opcodes.jpg -------------------------------------------------------------------------------- /visual/munin/2queries-in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/munin/2queries-in.jpg -------------------------------------------------------------------------------- /visual/munin/3queries-out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/munin/3queries-out.jpg -------------------------------------------------------------------------------- /visual/munin/5server_stats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/munin/5server_stats.jpg -------------------------------------------------------------------------------- /visual/munin/4resolver-stats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/munin/4resolver-stats.jpg -------------------------------------------------------------------------------- /visual/grafana/bind9-grafana1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/grafana/bind9-grafana1.png -------------------------------------------------------------------------------- /visual/grafana/bind9-grafana2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/grafana/bind9-grafana2.png -------------------------------------------------------------------------------- /visual/grafana/bind9-grafana3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuque/bind9stats/HEAD/visual/grafana/bind9-grafana3.png -------------------------------------------------------------------------------- /sysconfig-options: -------------------------------------------------------------------------------- 1 | # 2 | # This file should be installed as: /etc/sysconfig/bind9stats-graphite 3 | # 4 | # Options given to bind9stats-graphite.py 5 | # -d: print timing related debug info 6 | # -s : graphite server address to send metrics to 7 | # -r: really send to graphite server 8 | # 9 | 10 | OPTIONS="-d -s 127.0.0.1 -r" 11 | -------------------------------------------------------------------------------- /systemd-service: -------------------------------------------------------------------------------- 1 | # 2 | # systemd service script for bind9stats-graphite.py 3 | # 4 | # Installation: 5 | # - Install as /usr/local/lib/systemd/system/bind9stats-graphite.service 6 | # - Symlink: 7 | # cd /usr/lib/systemd/system 8 | # sudo ln -s /usr/local/lib/systemd/system/bind9stats-graphite.service . 9 | # - Run "sudo systemctl daemon-reload" 10 | # - Enable: sudo systemctl enable bind9stats-graphite 11 | # - Start: sudo systemctl start bind9stats-graphite 12 | # 13 | 14 | [Unit] 15 | Description=BIND9 DNS graphite statistics exporter 16 | After=network.target 17 | After=named.service 18 | Wants=named.service 19 | 20 | [Service] 21 | Type=forking 22 | User=named 23 | Group=named 24 | EnvironmentFile=-/etc/sysconfig/bind9stats-graphite 25 | ExecStart=/usr/local/sbin/bind9stats-graphite.py $OPTIONS 26 | Restart=always 27 | RestartSec=1s 28 | TimeoutSec=10 29 | 30 | [Install] 31 | WantedBy=multi-user.target 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bind9stats.py 2 | 3 | Programs to obtain data from the statistics channel of a BIND9 4 | DNS server, and send it to some graphing and data visualization 5 | tools. The original program, bind9stats.py, was written to 6 | be a plugin for Munin, and has recently been renamed to 7 | bind9stats-munin.py. There is also a newer version, called 8 | bind9stats-graphite.py, that works with Graphite and Grafana. 9 | 10 | 11 | ## bind9stats-munin.py: Munin plugin 12 | 13 | version 0.31 14 | 15 | A munin plugin to obtain data from a BIND9 statistics server, written 16 | in Python. Tested with BIND 9.10, 9.11, and 9.12's statistics server 17 | exporting version 3 of the statistics. In earlier versions of BIND 9.9, 18 | the v3 schema of statistics can be specified using the 'newstats' 19 | configuration directive. The newstats option was introduced in BIND 9.9.3. 20 | 21 | If you are using older versions of BIND 9.9 that only support version 22 | 2 of the XML statistics, you'll need to use the 0.1x version of this 23 | program, which can be obtained from: 24 | 25 | https://github.com/shuque/bind9stats/archive/v0.12.tar.gz 26 | 27 | Software needed to use this: 28 | * Python 2.7 or later, or Python 3.x. 29 | * BIND: BIND DNS server from isc.org. https://www.isc.org/software/bind 30 | * Munin: a resource monitoring tool that does pretty graphs. 31 | See http://munin-monitoring.org/ for details.) 32 | 33 | Some notes: 34 | * BIND can be configured to provide per-zone query statistics also. This 35 | plugin currently doesn't process that data, and only does the aggregate 36 | statistics for the entire server. 37 | * Only the _default view is used. Servers configured to use multiple 38 | views that want per view statistics will have to extend this program 39 | a bit. 40 | 41 | Instructions for using this: 42 | - Have a DNS server running BIND9, with the statistics server enabled. 43 | 44 | On my BIND servers, I usually have something like the following in the 45 | configuration file: 46 | 47 | statistics-channels { 48 | inet 127.0.0.1 port 8053 allow { 127.0.0.1; }; 49 | }; 50 | 51 | - Have a munin-node running on it, install bind9stats.py in its plugins 52 | directory and restart the node. 53 | You can also run the plugin on another machine, if the statistics 54 | server allows queries remotely. Set the HOST and PORT environment 55 | variables appropriately in that case before invoking bind9stats.py. 56 | 57 | Sample munin graphs produced by this plugin: 58 | 59 | ![Muning Graph 1](visual/munin/1opcodes.jpg) 60 | ![Muning Graph 1](visual/munin/2queries-in.jpg) 61 | 62 | 63 | ## bind9stats-graphite.py 64 | 65 | This version of the program collects BIND9 statistics and sends them 66 | to a [Graphite](https://graphite.readthedocs.io/en/latest/) server, 67 | another monitoring tool and time series data store. This runs as a long 68 | lived daemon, collects statistics at regular intervals (default is every 69 | minute), and then sends them to a Graphite server. 70 | 71 | ``` 72 | Usage: bind9stats-graphite.py [Options] 73 | 74 | Options: 75 | -h Print this usage message 76 | -d Generate some diagnostic messages 77 | -f Stay in foreground (default: become daemon) 78 | -m metrics Comma separated metric types 79 | (default: auth,res,bind,zone,memory) 80 | (supported: auth,res,bind,zone,memory,socket) 81 | -n name Specify server name (default: 1st component of hostname) 82 | -i interval Polling interval in seconds (default: 60 sec) 83 | -s server Graphite server IP address (default: 127.0.0.1) 84 | -p port Graphite server port (default: 2003) 85 | -r Really send data to Graphite (default: don't) 86 | 87 | -o options Other comma separated options (default: none) 88 | (supported: derive) 89 | ``` 90 | 91 | Installation: 92 | 93 | * Install the program into a suitable location on your system, e.g. 94 | sudo cp bind9stats-graphite.py /usr/local/sbin/ 95 | * Arrange for the program to be started up on system boot. A sample 96 | [Systemd service file](systemd-service) and [Startup options file] 97 | (sysconfig-options) is provided. The OPTIONS variable in the latter 98 | specifies that command line options to start the program with, which 99 | by default is set to: OPTIONS="-d -s 127.0.0.1 -r" 100 | 101 | Graphite is commonly the default backend for 102 | [Grafana](https://grafana.com/), a popular data visualization tool 103 | and dashboard. The included sample [Grafana dashboard configuration file](Grafana-BIND.json) 104 | produces output like the following: 105 | 106 | ![Grafana Screenshot 1](visual/grafana/bind9-grafana1.png) 107 | ![Grafana Screenshot 1](visual/grafana/bind9-grafana2.png) 108 | ![Grafana Screenshot 1](visual/grafana/bind9-grafana3.png) 109 | 110 | Author: Shumon Huque 111 | 112 | Copyright (c) 2013-2015 - Shumon Huque. All rights reserved. 113 | This program is free software; you can redistribute it and/or modify 114 | it under the same terms as Python itself. 115 | -------------------------------------------------------------------------------- /bind9stats-munin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Munin monitoring plug-in for BIND9 DNS statistics server. Tested 5 | with BIND 9.10, 9.11, and 9.12, exporting version 3.x of the XML 6 | statistics. 7 | 8 | Copyright (c) 2013-2015, Shumon Huque. All rights reserved. 9 | This program is free software; you can redistribute it and/or modify 10 | it under the same terms as Python itself. 11 | """ 12 | 13 | import os, sys 14 | import xml.etree.ElementTree as et 15 | try: 16 | from urllib2 import urlopen # for Python 2 17 | except ImportError: 18 | from urllib.request import urlopen # for Python 3 19 | 20 | VERSION = "0.31" 21 | 22 | HOST = os.environ.get('HOST', "127.0.0.1") 23 | PORT = os.environ.get('PORT', "8053") 24 | INSTANCE = os.environ.get('INSTANCE', "") 25 | SUBTITLE = os.environ.get('SUBTITLE', "") 26 | 27 | STATS_TYPE = "xml" # will support json later 28 | BINDSTATS_URL = "http://%s:%s/%s" % (HOST, PORT, STATS_TYPE) 29 | 30 | if SUBTITLE != '': 31 | SUBTITLE = ' ' + SUBTITLE 32 | 33 | GraphCategoryName = "dns_bind" 34 | 35 | # Note: munin displays these graphs ordered alphabetically by graph title 36 | 37 | GraphConfig = ( 38 | 39 | ('dns_opcode_in' + INSTANCE, 40 | dict(title='BIND [00] Opcodes In', 41 | enable=True, 42 | stattype='counter', 43 | args='-l 0', 44 | vlabel='Queries/sec', 45 | location="server/counters[@type='opcode']/counter", 46 | config=dict(type='DERIVE', min=0, draw='AREASTACK'))), 47 | 48 | ('dns_qtypes_in' + INSTANCE, 49 | dict(title='BIND [01] Query Types In', 50 | enable=True, 51 | stattype='counter', 52 | args='-l 0', 53 | vlabel='Queries/sec', 54 | location="server/counters[@type='qtype']/counter", 55 | config=dict(type='DERIVE', min=0, draw='AREASTACK'))), 56 | 57 | ('dns_server_stats' + INSTANCE, 58 | dict(title='BIND [02] Server Stats', 59 | enable=True, 60 | stattype='counter', 61 | args='-l 0', 62 | vlabel='Queries/sec', 63 | location="server/counters[@type='nsstat']/counter", 64 | fields=("Requestv4", "Requestv6", "ReqEdns0", "ReqTCP", "ReqTSIG", 65 | "Response", "TruncatedResp", "RespEDNS0", "RespTSIG", 66 | "QrySuccess", "QryAuthAns", "QryNoauthAns", "QryReferral", 67 | "QryNxrrset", "QrySERVFAIL", "QryFORMERR", "QryNXDOMAIN", 68 | "QryRecursion", "QryDuplicate", "QryDropped", "QryFailure", 69 | "XfrReqDone", "UpdateDone", "QryUDP", "QryTCP"), 70 | config=dict(type='DERIVE', min=0))), 71 | 72 | ('dns_cachedb' + INSTANCE, 73 | dict(title='BIND [03] CacheDB RRsets', 74 | enable=True, 75 | stattype='cachedb', 76 | args='-l 0', 77 | vlabel='Count', 78 | location="views/view[@name='_default']/cache[@name='_default']/rrset", 79 | config=dict(type='GAUGE', min=0))), 80 | 81 | ('dns_resolver_stats' + INSTANCE, 82 | dict(title='BIND [04] Resolver Stats', 83 | enable=False, # appears to be empty 84 | stattype='counter', 85 | args='-l 0', 86 | vlabel='Count/sec', 87 | location="server/counters[@type='resstat']/counter", 88 | config=dict(type='DERIVE', min=0))), 89 | 90 | ('dns_resolver_stats_qtype' + INSTANCE, 91 | dict(title='BIND [05] Resolver Outgoing Queries', 92 | enable=True, 93 | stattype='counter', 94 | args='-l 0', 95 | vlabel='Count/sec', 96 | location="views/view[@name='_default']/counters[@type='resqtype']/counter", 97 | config=dict(type='DERIVE', min=0))), 98 | 99 | ('dns_resolver_stats_view' + INSTANCE, 100 | dict(title='BIND [06] Resolver Stats', 101 | enable=True, 102 | stattype='counter', 103 | args='-l 0', 104 | vlabel='Count/sec', 105 | location="views/view[@name='_default']/counters[@type='resstats']/counter", 106 | config=dict(type='DERIVE', min=0))), 107 | 108 | ('dns_cachestats' + INSTANCE, 109 | dict(title='BIND [07] Resolver Cache Stats', 110 | enable=True, 111 | stattype='counter', 112 | args='-l 0', 113 | vlabel='Count/sec', 114 | location="views/view[@name='_default']/counters[@type='cachestats']/counter", 115 | fields=("CacheHits", "CacheMisses", "QueryHits", "QueryMisses", 116 | "DeleteLRU", "DeleteTTL"), 117 | config=dict(type='DERIVE', min=0))), 118 | 119 | ('dns_cache_mem' + INSTANCE, 120 | dict(title='BIND [08] Resolver Cache Memory Stats', 121 | enable=True, 122 | stattype='counter', 123 | args='-l 0 --base 1024', 124 | vlabel='Memory In-Use', 125 | location="views/view[@name='_default']/counters[@type='cachestats']/counter", 126 | fields=("TreeMemInUse", "HeapMemInUse"), 127 | config=dict(type='GAUGE', min=0))), 128 | 129 | ('dns_socket_activity' + INSTANCE, 130 | dict(title='BIND [09] Socket Activity', 131 | enable=True, 132 | stattype='counter', 133 | args='-l 0', 134 | vlabel='Active', 135 | location="server/counters[@type='sockstat']/counter", 136 | fields=("UDP4Active", "UDP6Active", 137 | "TCP4Active", "TCP6Active", 138 | "UnixActive", "RawActive"), 139 | config=dict(type='GAUGE', min=0))), 140 | 141 | ('dns_socket_stats' + INSTANCE, 142 | dict(title='BIND [10] Socket Rates', 143 | enable=True, 144 | stattype='counter', 145 | args='-l 0', 146 | vlabel='Count/sec', 147 | location="server/counters[@type='sockstat']/counter", 148 | fields=("UDP4Open", "UDP6Open", 149 | "TCP4Open", "TCP6Open", 150 | "UDP4OpenFail", "UDP6OpenFail", 151 | "TCP4OpenFail", "TCP6OpenFail", 152 | "UDP4Close", "UDP6Close", 153 | "TCP4Close", "TCP6Close", 154 | "UDP4BindFail", "UDP6BindFail", 155 | "TCP4BindFail", "TCP6BindFail", 156 | "UDP4ConnFail", "UDP6ConnFail", 157 | "TCP4ConnFail", "TCP6ConnFail", 158 | "UDP4Conn", "UDP6Conn", 159 | "TCP4Conn", "TCP6Conn", 160 | "TCP4AcceptFail", "TCP6AcceptFail", 161 | "TCP4Accept", "TCP6Accept", 162 | "UDP4SendErr", "UDP6SendErr", 163 | "TCP4SendErr", "TCP6SendErr", 164 | "UDP4RecvErr", "UDP6RecvErr", 165 | "TCP4RecvErr", "TCP6RecvErr"), 166 | config=dict(type='DERIVE', min=0))), 167 | 168 | ('dns_zone_stats' + INSTANCE, 169 | dict(title='BIND [11] Zone Maintenance', 170 | enable=False, 171 | stattype='counter', 172 | args='-l 0', 173 | vlabel='Count/sec', 174 | location="server/counters[@type='zonestat']/counter", 175 | config=dict(type='DERIVE', min=0))), 176 | 177 | ('dns_memory_usage' + INSTANCE, 178 | dict(title='BIND [12] Memory Usage', 179 | enable=True, 180 | stattype='memory', 181 | args='-l 0 --base 1024', 182 | vlabel='Memory In-Use', 183 | location='memory/summary', 184 | fields=("ContextSize", "BlockSize", "Lost", "InUse"), 185 | config=dict(type='GAUGE', min=0))), 186 | 187 | ('dns_adbstat' + INSTANCE, 188 | dict(title='BIND [13] adbstat', 189 | enable=True, 190 | stattype='counter', 191 | args='-l 0', 192 | vlabel='Count', 193 | location="views/view[@name='_default']/counters[@type='adbstat']/counter", 194 | config=dict(type='GAUGE', min=0))), 195 | 196 | ) 197 | 198 | 199 | def unsetenvproxy(): 200 | """Unset HTTP Proxy environment variables that might interfere""" 201 | for proxyvar in [ 'http_proxy', 'HTTP_PROXY' ]: 202 | os.unsetenv(proxyvar) 203 | return 204 | 205 | 206 | def getstatsversion(etree): 207 | """return version of BIND statistics""" 208 | return etree.attrib['version'] 209 | 210 | 211 | def getdata(graph, etree, getvals=False): 212 | 213 | stattype = graph[1]['stattype'] 214 | location = graph[1]['location'] 215 | 216 | if stattype == 'memory': 217 | return getdata_memory(graph, etree, getvals) 218 | elif stattype == 'cachedb': 219 | return getdata_cachedb(graph, etree, getvals) 220 | 221 | results = [] 222 | counters = etree.findall(location) 223 | 224 | if counters is None: # empty result 225 | return results 226 | 227 | for c in counters: 228 | key = c.attrib['name'] 229 | val = c.text 230 | if getvals: 231 | results.append((key, val)) 232 | else: 233 | results.append(key) 234 | return results 235 | 236 | 237 | def getdata_memory(graph, etree, getvals=False): 238 | 239 | location = graph[1]['location'] 240 | 241 | results = [] 242 | counters = etree.find(location) 243 | 244 | if counters is None: # empty result 245 | return results 246 | 247 | for c in counters: 248 | key = c.tag 249 | val = c.text 250 | if getvals: 251 | results.append((key, val)) 252 | else: 253 | results.append(key) 254 | return results 255 | 256 | 257 | def getdata_cachedb(graph, etree, getvals=False): 258 | 259 | location = graph[1]['location'] 260 | 261 | results = [] 262 | counters = etree.findall(location) 263 | 264 | if counters is None: # empty result 265 | return results 266 | 267 | for c in counters: 268 | key = c.find('name').text 269 | val = c.find('counter').text 270 | if getvals: 271 | results.append((key, val)) 272 | else: 273 | results.append(key) 274 | return results 275 | 276 | 277 | def validkey(graph, key): 278 | fieldlist = graph[1].get('fields', None) 279 | if fieldlist and (key not in fieldlist): 280 | return False 281 | else: 282 | return True 283 | 284 | 285 | def get_etree_root(url): 286 | """Return the root of an ElementTree structure populated by 287 | parsing BIND9 statistics obtained at the given URL""" 288 | 289 | data = urlopen(url) 290 | return et.parse(data).getroot() 291 | 292 | 293 | def muninconfig(etree): 294 | """Generate munin config for the BIND stats plugin""" 295 | 296 | for g in GraphConfig: 297 | if not g[1]['enable']: 298 | continue 299 | print("multigraph %s" % g[0]) 300 | print("graph_title %s" % g[1]['title'] + SUBTITLE) 301 | print("graph_args %s" % g[1]['args']) 302 | print("graph_vlabel %s" % g[1]['vlabel']) 303 | print("graph_category %s" % GraphCategoryName) 304 | 305 | data = getdata(g, etree, getvals=False) 306 | if data != None: 307 | for key in data: 308 | if validkey(g, key): 309 | print("%s.label %s" % (key, key)) 310 | if 'draw' in g[1]['config']: 311 | print("%s.draw %s" % (key, g[1]['config']['draw'])) 312 | print("%s.min %s" % (key, g[1]['config']['min'])) 313 | print("%s.type %s" % (key, g[1]['config']['type'])) 314 | print('') 315 | 316 | 317 | def munindata(etree): 318 | """Generate munin data for the BIND stats plugin""" 319 | 320 | for g in GraphConfig: 321 | if not g[1]['enable']: 322 | continue 323 | print("multigraph %s" % g[0]) 324 | data = getdata(g, etree, getvals=True) 325 | if data != None: 326 | for (key, value) in data: 327 | if validkey(g, key): 328 | print("%s.value %s" % (key, value)) 329 | print('') 330 | 331 | 332 | def usage(): 333 | """Print plugin usage""" 334 | print("""\ 335 | \nUsage: bind9stats.py [config|statsversion]\n""") 336 | sys.exit(1) 337 | 338 | 339 | if __name__ == '__main__': 340 | 341 | tree = get_etree_root(BINDSTATS_URL) 342 | 343 | args = sys.argv[1:] 344 | argslen = len(args) 345 | unsetenvproxy() 346 | 347 | if argslen == 0: 348 | munindata(tree) 349 | elif argslen == 1: 350 | if args[0] == "config": 351 | muninconfig(tree) 352 | elif args[0] == "statsversion": 353 | print("bind9stats %s version %s" % (STATS_TYPE, getstatsversion(tree))) 354 | else: 355 | usage() 356 | else: 357 | usage() 358 | -------------------------------------------------------------------------------- /bind9stats-graphite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | bind9stats-graphite.py 5 | 6 | A version of bind9stats that works with Graphite+Grafana. 7 | 8 | Query the XML/HTTP statistics channel for a BIND9 server, at regular 9 | intervals, pick out useful statistics, and send them to a Graphite/Carbon 10 | server (the default backend for the Graphana visualization dashboard among 11 | other things). 12 | 13 | Author: Shumon Huque 14 | 15 | """ 16 | 17 | import os 18 | import sys 19 | import time 20 | import calendar 21 | import socket 22 | import getopt 23 | import syslog 24 | from datetime import datetime 25 | try: 26 | import lxml.etree as et 27 | except ImportError: 28 | import xml.etree.ElementTree as et 29 | from urllib.request import urlopen 30 | from urllib.error import URLError 31 | 32 | 33 | PROGNAME = os.path.basename(sys.argv[0]) 34 | VERSION = "0.20" 35 | 36 | DEFAULT_BIND9_HOST = '127.0.0.1' 37 | DEFAULT_BIND9_PORT = '8053' 38 | DEFAULT_GRAPHITE_HOST = '127.0.0.1' 39 | DEFAULT_GRAPHITE_PORT = '2003' 40 | 41 | # Hash table specifying which metric types to export. 42 | METRICS = { 43 | 'auth': False, 44 | 'res': False, 45 | 'bind': False, 46 | 'zone': False, 47 | 'memory': False, 48 | 'socket': False, 49 | } 50 | 51 | class Prefs: 52 | """General Preferences""" 53 | DEBUG = False # -d: True 54 | DAEMON = True # -f: foreground 55 | WORKDIR = "/" # Fixed 56 | SEND = False # -s: Send to Graphite 57 | METRICS = "auth,res,bind,zone,memory" # -m: metric types 58 | HOSTNAME = socket.gethostname().split('.')[0] # -n to change 59 | SYSLOG_FAC = syslog.LOG_DAEMON # Syslog facility 60 | SYSLOG_PRI = syslog.LOG_INFO # Syslog priority 61 | POLL_INTERVAL = 60 # in secs (-p) 62 | BIND9_HOST = os.environ.get('BIND9_HOST', DEFAULT_BIND9_HOST) 63 | BIND9_PORT = os.environ.get('BIND9_PORT', DEFAULT_BIND9_PORT) 64 | GRAPHITE_HOST = os.environ.get('GRAPHITE_HOST', DEFAULT_GRAPHITE_HOST) 65 | GRAPHITE_PORT = int(os.environ.get('GRAPHITE_PORT', DEFAULT_GRAPHITE_PORT)) 66 | TIMEOUT = 5 67 | DERIVE = False # -o derive 68 | 69 | 70 | def usage(msg=None): 71 | """Print Usage string""" 72 | if msg is not None: 73 | print(msg) 74 | print("""\ 75 | \nUsage: {0} [Options] 76 | 77 | Options: 78 | -h Print this usage message 79 | -d Generate some diagnostic messages 80 | -f Stay in foreground (default: become daemon) 81 | -m metrics Comma separated metric types 82 | (default: {1}) 83 | (supported: {2}) 84 | -n name Specify server name (default: 1st component of hostname) 85 | -i interval Polling interval in seconds (default: {3} sec) 86 | -s server Graphite server IP address (default: {4}) 87 | -p port Graphite server port (default: {5}) 88 | -r Really send data to Graphite (default: don't) 89 | 90 | -o options Other comma separated options (default: none) 91 | (supported: derive) 92 | """.format(PROGNAME, 93 | Prefs.METRICS, 94 | ",".join(METRICS.keys()), 95 | Prefs.POLL_INTERVAL, 96 | DEFAULT_GRAPHITE_HOST, 97 | DEFAULT_GRAPHITE_PORT)) 98 | sys.exit(1) 99 | 100 | 101 | def set_other_options(args): 102 | """Set other options from a comma separated list specified with -o""" 103 | for opt in args.split(','): 104 | if opt == "derive": 105 | Prefs.DERIVE = True 106 | else: 107 | usage("Unrecognized option: {}".format(opt)) 108 | 109 | 110 | def process_args(arguments): 111 | """Process command line arguments""" 112 | try: 113 | (options, args) = getopt.getopt(arguments, 'hdfm:n:i:s:p:ro:') 114 | except getopt.GetoptError: 115 | usage("Argument processing error.") 116 | if args: 117 | usage("Too many arguments provided.") 118 | 119 | for (opt, optval) in options: 120 | if opt == "-h": 121 | usage() 122 | elif opt == "-d": 123 | Prefs.DEBUG = True 124 | elif opt == "-f": 125 | Prefs.DAEMON = False 126 | elif opt == "-m": 127 | Prefs.METRICS = optval 128 | elif opt == "-n": 129 | Prefs.HOSTNAME = dot2underscore(optval) 130 | elif opt == "-i": 131 | Prefs.POLL_INTERVAL = int(optval) 132 | elif opt == "-s": 133 | Prefs.GRAPHITE_HOST = optval 134 | elif opt == "-p": 135 | Prefs.GRAPHITE_PORT = int(optval) 136 | elif opt == "-r": 137 | Prefs.SEND = True 138 | elif opt == "-o": 139 | set_other_options(optval) 140 | 141 | for metric in Prefs.METRICS.split(','): 142 | if metric in METRICS: 143 | METRICS[metric] = True 144 | else: 145 | usage("{} is not a valid metric.".format(metric)) 146 | return 147 | 148 | 149 | class Graphs: 150 | 151 | """Class to encapsulate graphable metric types and parameters""" 152 | 153 | def __init__(self, metrics_dict): 154 | self.metrics = metrics_dict 155 | 156 | self.params = [ 157 | 158 | ('dns_opcode_in', 159 | dict(enable=self.metrics['auth'] or self.metrics['res'], 160 | stattype='counter', 161 | metrictype='DERIVE', 162 | location="server/counters[@type='opcode']/counter")), 163 | 164 | ('dns_qtypes_in', 165 | dict(enable=self.metrics['auth'] or self.metrics['res'], 166 | stattype='counter', 167 | metrictype='DERIVE', 168 | location="server/counters[@type='qtype']/counter")), 169 | 170 | ('dns_server_stats', 171 | dict(enable=self.metrics['auth'] or self.metrics['res'], 172 | stattype='counter', 173 | metrictype='DERIVE', 174 | location="server/counters[@type='nsstat']/counter")), 175 | 176 | ('dns_cachedb', 177 | dict(enable=self.metrics['res'], 178 | stattype='cachedb', 179 | metrictype='GAUGE', 180 | location="views/view[@name='_default']/cache[@name='_default']/rrset")), 181 | 182 | ('dns_resolver_stats', 183 | dict(enable=False, # appears to be empty 184 | stattype='counter', 185 | metrictype='DERIVE', 186 | location="server/counters[@type='resstat']/counter")), 187 | 188 | ('dns_resolver_stats_qtype', 189 | dict(enable=self.metrics['res'], 190 | stattype='counter', 191 | metrictype='DERIVE', 192 | location="views/view[@name='_default']/counters[@type='resqtype']/counter")), 193 | 194 | ('dns_resolver_stats_defview', 195 | dict(enable=self.metrics['res'], 196 | stattype='counter', 197 | metrictype='DERIVE', 198 | location="views/view[@name='_default']/counters[@type='resstats']/counter")), 199 | 200 | ('dns_cachestats', 201 | dict(enable=self.metrics['res'] and self.metrics['memory'], 202 | stattype='counter', 203 | metrictype='DERIVE', 204 | location="views/view[@name='_default']/counters[@type='cachestats']/counter")), 205 | 206 | ('dns_socket_activity', 207 | dict(enable=self.metrics['socket'], 208 | stattype='counter', 209 | metrictype='GAUGE', 210 | location="server/counters[@type='sockstat']/counter")), 211 | 212 | ('dns_socket_stats', 213 | dict(enable=self.metrics['socket'], 214 | stattype='counter', 215 | metrictype='DERIVE', 216 | location="server/counters[@type='sockstat']/counter")), 217 | 218 | ('dns_zone_stats', 219 | dict(enable=self.metrics['auth'], 220 | stattype='counter', 221 | metrictype='DERIVE', 222 | location="server/counters[@type='zonestat']/counter")), 223 | 224 | ('dns_memory_usage', 225 | dict(enable=(self.metrics['auth'] or self.metrics['res']) \ 226 | and self.metrics['memory'], 227 | stattype='memory', 228 | metrictype='GAUGE', 229 | location='memory/summary', 230 | fields=("ContextSize", "BlockSize", "Lost", "InUse"))), 231 | 232 | ('dns_adbstat', 233 | dict(enable=False, 234 | stattype='counter', 235 | metrictype='GAUGE', 236 | location="views/view[@name='_default']/counters[@type='adbstat']/counter")), 237 | 238 | ] 239 | 240 | 241 | def daemon(dirname=None, syslog_fac=syslog.LOG_DAEMON, umask=0o022): 242 | 243 | """Become daemon: fork, go into background, create new session""" 244 | 245 | try: 246 | pid = os.fork() 247 | if pid > 0: 248 | sys.exit(0) 249 | except OSError as einfo: 250 | print("fork() failed: {}".format(einfo)) 251 | sys.exit(1) 252 | else: 253 | if dirname: 254 | os.chdir(dirname) 255 | os.umask(umask) 256 | os.setsid() 257 | 258 | for fd in range(0, os.sysconf("SC_OPEN_MAX")): 259 | try: 260 | os.close(fd) 261 | except OSError: 262 | pass 263 | syslog.openlog(PROGNAME, syslog.LOG_PID, syslog_fac) 264 | 265 | return 266 | 267 | 268 | def log_message(msg): 269 | """log message to syslog if daemon, otherwise print""" 270 | if Prefs.DAEMON: 271 | syslog.syslog(Prefs.SYSLOG_PRI, msg) 272 | else: 273 | print(msg) 274 | 275 | 276 | def validkey(graphconfig, key): 277 | """Are we interested in this key?""" 278 | fieldlist = graphconfig.get('fields', None) 279 | if fieldlist and (key not in fieldlist): 280 | return False 281 | return True 282 | 283 | 284 | def dot2underscore(instring): 285 | """replace periods with underscores in given string""" 286 | return instring.replace('.', '_') 287 | 288 | 289 | def get_xml_etree_root(url, timeout): 290 | 291 | """Return the root of an ElementTree structure populated by 292 | parsing XML statistics obtained at the given URL. And also 293 | the elapsed time.""" 294 | 295 | time_start = time.time() 296 | try: 297 | rawdata = urlopen(url, timeout=timeout) 298 | except URLError as einfo: 299 | log_message("ERROR: Error reading {}: {}".format(url, einfo)) 300 | return None, None 301 | outdata = et.parse(rawdata).getroot() 302 | elapsed = time.time() - time_start 303 | return outdata, elapsed 304 | 305 | 306 | def connect_host(ipaddr, port, timeout): 307 | 308 | """Connect with TCP to given host, port and return socket""" 309 | 310 | family = socket.AF_INET6 if ipaddr.find(':') != -1 else socket.AF_INET 311 | 312 | sock = socket.socket(family, socket.SOCK_STREAM) 313 | sock.settimeout(timeout) 314 | try: 315 | sock.connect((ipaddr, port)) 316 | except OSError as einfo: 317 | log_message("WARN: connect() to {},{} failed: {}".format( 318 | ipaddr, port, einfo)) 319 | return None 320 | return sock 321 | 322 | 323 | def send_socket(sock, message): 324 | """Send message on a connected socket""" 325 | try: 326 | octets_sent = 0 327 | while octets_sent < len(message): 328 | sentn = sock.send(message[octets_sent:]) 329 | if sentn == 0: 330 | log_message("WARN: Broken connection. send() returned 0") 331 | return False 332 | octets_sent += sentn 333 | except OSError as einfo: 334 | log_message("WARN: send_socket exception: {}".format(einfo)) 335 | return False 336 | else: 337 | return True 338 | 339 | 340 | class Bind9Stats: 341 | 342 | """Class to poll BIND9 Statistics server and parse its data""" 343 | 344 | def __init__(self, host, port, timeout, poll_interval=60): 345 | self.host = host 346 | self.port = port 347 | self.timeout = timeout 348 | self.poll_interval = poll_interval 349 | self.url = "http://{}:{}/xml".format(host, port) 350 | self.tree = None 351 | self.poll_duration = None 352 | self.timestamp = None 353 | self.g_timestamp = None 354 | self.g_timestamp_last = None 355 | self.adjust = '' 356 | self.last_poll = None 357 | self.time_delta = None 358 | 359 | def poll(self): 360 | """Poll BIND stats and record timestamp and time delta""" 361 | self.timestamp = time.time() 362 | self.compute_graphite_timestamp() 363 | self.tree, self.poll_duration = get_xml_etree_root(self.url, self.timeout) 364 | if self.tree is not None: 365 | if self.last_poll is not None: 366 | self.time_delta = self.timestamp - self.last_poll 367 | self.last_poll = self.timestamp 368 | 369 | def compute_graphite_timestamp(self): 370 | """Graphite timestamp computation function""" 371 | self.adjust = '' 372 | self.g_timestamp = round(self.timestamp/self.poll_interval) \ 373 | * self.poll_interval 374 | if self.g_timestamp_last is not None: 375 | difference = self.g_timestamp - self.g_timestamp_last 376 | if difference == 0: 377 | self.g_timestamp += self.poll_interval 378 | self.adjust = '+' 379 | elif difference == self.poll_interval: 380 | pass 381 | elif difference == (2 * self.poll_interval): 382 | self.g_timestamp -= self.poll_interval 383 | self.adjust = '-' 384 | else: 385 | self.adjust = '?' 386 | self.g_timestamp_last = self.g_timestamp 387 | 388 | def timestamp2string(self): 389 | """Convert timestamp into human readable string""" 390 | return datetime.fromtimestamp(self.timestamp).strftime( 391 | "%Y-%m-%dT%H:%M:%S.%f")[:-3] 392 | 393 | def timestring2since(self, tstring): 394 | """Convert bind9 stats time string to seconds since current time""" 395 | try: 396 | return self.timestamp - calendar.timegm(time.strptime( 397 | tstring.split('.')[0], "%Y-%m-%dT%H:%M:%S")) 398 | except ValueError: 399 | return 'nan' 400 | 401 | def getdata(self, graphconfig): 402 | """Obtain data from XML stats location""" 403 | 404 | stattype = graphconfig['stattype'] 405 | location = graphconfig['location'] 406 | 407 | if stattype == 'memory': 408 | return self.getdata_memory(graphconfig) 409 | elif stattype == 'cachedb': 410 | return self.getdata_cachedb(graphconfig) 411 | 412 | results = [] 413 | counters = self.tree.findall(location) 414 | 415 | if counters is None: 416 | return results 417 | 418 | for c in counters: 419 | key = c.attrib['name'] 420 | val = c.text 421 | results.append((key, val)) 422 | return results 423 | 424 | def getdata_memory(self, graphconfig): 425 | """Obtain memory type XML stats""" 426 | 427 | location = graphconfig['location'] 428 | 429 | results = [] 430 | counters = self.tree.find(location) 431 | 432 | if counters is None: 433 | return results 434 | 435 | for c in counters: 436 | key = c.tag 437 | val = c.text 438 | results.append((key, val)) 439 | return results 440 | 441 | def getdata_cachedb(self, graphconfig): 442 | """Obtain cachedb type XML stats""" 443 | 444 | location = graphconfig['location'] 445 | 446 | results = [] 447 | counters = self.tree.findall(location) 448 | 449 | if counters is None: 450 | return results 451 | 452 | for c in counters: 453 | key = c.find('name').text 454 | val = c.find('counter').text 455 | results.append((key, val)) 456 | return results 457 | 458 | 459 | class Bind2Graphite: 460 | 461 | """Functions to communicate BIND9 stats to a Graphite server""" 462 | 463 | def __init__(self, stats, host, port, name=None, timeout=5, 464 | poll_interval=None, debug=False): 465 | self.stats = stats 466 | self.host = host 467 | self.port = port 468 | self.name = name 469 | self.timeout = timeout 470 | self.poll_interval = poll_interval 471 | self.debug = debug 472 | self.statsdb = {} # stores (derive) stats from previous run 473 | self.graphite_data = b'' 474 | self.socket = None 475 | 476 | def reset(self): 477 | """Empty graphite_data byte string""" 478 | self.graphite_data = b'' 479 | 480 | def compute_statvalue(self, name, val): 481 | """Compute metric value for DERIVE types""" 482 | if name not in self.statsdb: 483 | gvalue = 'nan' 484 | else: 485 | gvalue = (float(val) - float(self.statsdb[name])) / self.stats.time_delta 486 | if gvalue < 0: 487 | ## negative increment. Probably BIND server restart 488 | gvalue = 'nan' 489 | self.statsdb[name] = val 490 | return gvalue 491 | 492 | def add_metric(self, category, stat, value): 493 | """Add graphite metrics line""" 494 | 495 | metricpath = '{}.{}.{}'.format(self.name, category, stat) 496 | out = '{} {} {}\r\n'.format(metricpath, value, self.stats.g_timestamp) 497 | self.graphite_data += out.encode() 498 | 499 | def generate_bind_data(self): 500 | """bind_info data: boot-time and config-time""" 501 | 502 | category = 'bind_info' 503 | self.add_metric(category, 'boot-time', 504 | self.stats.timestring2since( 505 | self.stats.tree.find('server/boot-time').text)) 506 | self.add_metric(category, 'config-time', 507 | self.stats.timestring2since( 508 | self.stats.tree.find('server/config-time').text)) 509 | 510 | def generate_zone_data(self): 511 | """bind zone data: zonename and serial number or delta""" 512 | 513 | category = "bind_zones" 514 | for zone in self.stats.tree.find("views/view[@name='_default']/zones"): 515 | ztype = zone.find('type').text 516 | if ztype != 'builtin': 517 | zonename = dot2underscore(zone.attrib['name']) 518 | zserial = zone.find('serial').text 519 | statname = "{}.{}".format(category, zonename) 520 | if Prefs.DERIVE: 521 | serial_increment = self.compute_statvalue(statname, zserial) 522 | self.add_metric(category, zonename, serial_increment) 523 | else: 524 | self.add_metric(category, zonename, zserial) 525 | 526 | def generate_graph_data(self): 527 | """Generate all the graphable metrics data""" 528 | 529 | for (graphname, graphconfig) in graphs.params: 530 | if not graphconfig['enable']: 531 | continue 532 | data = self.stats.getdata(graphconfig) 533 | if data is None: 534 | continue 535 | for (key, value) in data: 536 | if not validkey(graphconfig, key): 537 | continue 538 | statname = "{}.{}".format(graphname, key) 539 | if Prefs.DERIVE and graphconfig['metrictype'] == 'DERIVE': 540 | gvalue = self.compute_statvalue(statname, value) 541 | else: 542 | gvalue = value 543 | self.add_metric(graphname, key, gvalue) 544 | 545 | def generate_all_data(self): 546 | """Generate all metrics data""" 547 | self.reset() 548 | if graphs.metrics['bind']: 549 | self.generate_bind_data() 550 | if graphs.metrics['zone']: 551 | self.generate_zone_data() 552 | self.generate_graph_data() 553 | 554 | def connect_graphite(self): 555 | """Connect to Graphite server and record socket info""" 556 | self.socket = connect_host(self.host, self.port, self.timeout) 557 | 558 | def send_graphite(self): 559 | """Send metrics data to Graphite server""" 560 | 561 | if self.socket is None: 562 | self.connect_graphite() 563 | if self.socket is None: 564 | return 565 | if not send_socket(self.socket, self.graphite_data): 566 | log_message("WARN: reconnecting socket ..") 567 | self.socket.close() 568 | time.sleep(0.2) 569 | self.connect_graphite() 570 | if self.socket is None: 571 | return 572 | if not send_socket(self.socket, self.graphite_data): 573 | log_message("WARN: send() failed. Sleeping till next poll.") 574 | 575 | def single_run(self): 576 | """A single run of polling stats data and sending it out""" 577 | self.stats.poll() 578 | if self.stats.tree is None: 579 | log_message("WARN: No statistics found. Sleeping till next poll.") 580 | return 581 | self.generate_all_data() 582 | if Prefs.SEND: 583 | self.send_graphite() 584 | else: 585 | print(self.graphite_data.decode()) 586 | 587 | def sleep_time(self, elapsed): 588 | """Compute amount of time we have to sleep till next run""" 589 | compensation_time = 0 590 | if self.stats.time_delta is not None: 591 | if self.stats.time_delta > self.poll_interval: 592 | compensation_time = 2 * (self.stats.time_delta % self.poll_interval) 593 | if elapsed <= self.poll_interval: 594 | base_value = self.poll_interval - elapsed 595 | else: 596 | base_value = self.poll_interval - (elapsed % self.poll_interval) 597 | return base_value - compensation_time 598 | 599 | def run(self): 600 | """Run loop""" 601 | while True: 602 | time_start = time.time() 603 | self.single_run() 604 | elapsed = time.time() - time_start 605 | if self.debug: 606 | time_delta = "{:.3f}".format(self.stats.time_delta) \ 607 | if self.stats.time_delta is not None else "null" 608 | log_message("{} {:.3f} {} elapsed={:.3f} delta={} adj={}".format( 609 | self.stats.timestamp2string(), 610 | self.stats.timestamp, 611 | self.stats.g_timestamp, 612 | elapsed, 613 | time_delta, 614 | self.stats.adjust)) 615 | elapsed = time.time() - time_start 616 | time.sleep(self.sleep_time(elapsed)) 617 | 618 | 619 | if __name__ == '__main__': 620 | 621 | process_args(sys.argv[1:]) 622 | 623 | if Prefs.DAEMON: 624 | daemon(dirname=Prefs.WORKDIR) 625 | log_message("starting with host {}, graphite server: {},{}".format( 626 | Prefs.HOSTNAME, Prefs.GRAPHITE_HOST, Prefs.GRAPHITE_PORT)) 627 | 628 | graphs = Graphs(METRICS) 629 | 630 | b9_stats = Bind9Stats(Prefs.BIND9_HOST, Prefs.BIND9_PORT, Prefs.TIMEOUT, 631 | poll_interval=Prefs.POLL_INTERVAL) 632 | 633 | Bind2Graphite(b9_stats, 634 | Prefs.GRAPHITE_HOST, Prefs.GRAPHITE_PORT, 635 | name=Prefs.HOSTNAME, 636 | timeout=Prefs.TIMEOUT, 637 | poll_interval=Prefs.POLL_INTERVAL, 638 | debug=Prefs.DEBUG).run() 639 | -------------------------------------------------------------------------------- /Grafana-BIND.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_GRAPHITE", 5 | "label": "Graphite", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "graphite", 9 | "pluginName": "Graphite" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "6.4.2" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "graphite", 28 | "name": "Graphite", 29 | "version": "1.0.0" 30 | }, 31 | { 32 | "type": "panel", 33 | "id": "singlestat", 34 | "name": "Singlestat", 35 | "version": "" 36 | }, 37 | { 38 | "type": "panel", 39 | "id": "table", 40 | "name": "Table", 41 | "version": "" 42 | } 43 | ], 44 | "annotations": { 45 | "list": [ 46 | { 47 | "builtIn": 1, 48 | "datasource": "-- Grafana --", 49 | "enable": true, 50 | "hide": true, 51 | "iconColor": "rgba(0, 211, 255, 1)", 52 | "name": "Annotations & Alerts", 53 | "type": "dashboard" 54 | } 55 | ] 56 | }, 57 | "editable": true, 58 | "gnetId": null, 59 | "graphTooltip": 0, 60 | "id": null, 61 | "links": [], 62 | "panels": [ 63 | { 64 | "cacheTimeout": null, 65 | "colorBackground": false, 66 | "colorValue": false, 67 | "colors": [ 68 | "#299c46", 69 | "rgba(237, 129, 40, 0.89)", 70 | "#d44a3a" 71 | ], 72 | "datasource": "${DS_GRAPHITE}", 73 | "format": "s", 74 | "gauge": { 75 | "maxValue": 100, 76 | "minValue": 0, 77 | "show": false, 78 | "thresholdLabels": false, 79 | "thresholdMarkers": true 80 | }, 81 | "id": 20, 82 | "interval": null, 83 | "links": [], 84 | "mappingType": 1, 85 | "mappingTypes": [ 86 | { 87 | "name": "value to text", 88 | "value": 1 89 | }, 90 | { 91 | "name": "range to text", 92 | "value": 2 93 | } 94 | ], 95 | "maxDataPoints": 100, 96 | "nullPointMode": "connected", 97 | "nullText": null, 98 | "options": {}, 99 | "postfix": " ago", 100 | "postfixFontSize": "50%", 101 | "prefix": "", 102 | "prefixFontSize": "50%", 103 | "rangeMaps": [ 104 | { 105 | "from": "null", 106 | "text": "N/A", 107 | "to": "null" 108 | } 109 | ], 110 | "sparkline": { 111 | "fillColor": "rgba(31, 118, 189, 0.18)", 112 | "full": false, 113 | "lineColor": "rgb(31, 120, 193)", 114 | "show": false 115 | }, 116 | "tableColumn": "", 117 | "targets": [ 118 | { 119 | "refId": "A", 120 | "target": "hostname.bind_info.boot-time" 121 | } 122 | ], 123 | "thresholds": "", 124 | "title": "BIND Start Time", 125 | "type": "singlestat", 126 | "valueFontSize": "80%", 127 | "valueMaps": [ 128 | { 129 | "op": "=", 130 | "text": "N/A", 131 | "value": "null" 132 | } 133 | ], 134 | "valueName": "current" 135 | }, 136 | { 137 | "cacheTimeout": null, 138 | "colorBackground": false, 139 | "colorValue": false, 140 | "colors": [ 141 | "#d44a3a", 142 | "rgba(237, 129, 40, 0.89)", 143 | "#299c46" 144 | ], 145 | "datasource": "${DS_GRAPHITE}", 146 | "description": "", 147 | "format": "none", 148 | "gauge": { 149 | "maxValue": 1, 150 | "minValue": 0, 151 | "show": true, 152 | "thresholdLabels": false, 153 | "thresholdMarkers": true 154 | }, 155 | "gridPos": { 156 | "h": 6, 157 | "w": 6, 158 | "x": 6, 159 | "y": 0 160 | }, 161 | "id": 28, 162 | "interval": null, 163 | "links": [], 164 | "mappingType": 1, 165 | "mappingTypes": [ 166 | { 167 | "name": "value to text", 168 | "value": 1 169 | }, 170 | { 171 | "name": "range to text", 172 | "value": 2 173 | } 174 | ], 175 | "maxDataPoints": 100, 176 | "nullPointMode": "connected", 177 | "nullText": null, 178 | "options": {}, 179 | "postfix": "", 180 | "postfixFontSize": "50%", 181 | "prefix": "", 182 | "prefixFontSize": "50%", 183 | "rangeMaps": [ 184 | { 185 | "from": "null", 186 | "text": "N/A", 187 | "to": "null" 188 | } 189 | ], 190 | "sparkline": { 191 | "fillColor": "rgba(31, 118, 189, 0.18)", 192 | "full": false, 193 | "lineColor": "rgb(31, 120, 193)", 194 | "show": false 195 | }, 196 | "tableColumn": "", 197 | "targets": [ 198 | { 199 | "hide": true, 200 | "refCount": -1, 201 | "refId": "A", 202 | "target": "hostname.dns_cachestats.QueryHits" 203 | }, 204 | { 205 | "hide": true, 206 | "refCount": 0, 207 | "refId": "B", 208 | "target": "hostname.dns_cachestats.QueryMisses" 209 | }, 210 | { 211 | "hide": true, 212 | "refCount": -1, 213 | "refId": "C", 214 | "target": "sumSeries(#A, #B)", 215 | "targetFull": "sumSeries(hostname.dns_cachestats.QueryHits, hostname.dns_cachestats.QueryMisses)" 216 | }, 217 | { 218 | "refCount": 0, 219 | "refId": "D", 220 | "target": "divideSeries(#A, #C)", 221 | "targetFull": "divideSeries(hostname.dns_cachestats.QueryHits, sumSeries(hostname.dns_cachestats.QueryHits, hostname.dns_cachestats.QueryMisses))" 222 | } 223 | ], 224 | "thresholds": "40,80", 225 | "title": "Cache Hit Ratio", 226 | "type": "singlestat", 227 | "valueFontSize": "80%", 228 | "valueMaps": [ 229 | { 230 | "op": "=", 231 | "text": "N/A", 232 | "value": "null" 233 | } 234 | ], 235 | "valueName": "avg" 236 | }, 237 | { 238 | "aliasColors": {}, 239 | "bars": false, 240 | "dashLength": 10, 241 | "dashes": false, 242 | "datasource": "${DS_GRAPHITE}", 243 | "fill": 1, 244 | "fillGradient": 0, 245 | "gridPos": { 246 | "h": 6, 247 | "w": 12, 248 | "x": 12, 249 | "y": 0 250 | }, 251 | "id": 34, 252 | "legend": { 253 | "alignAsTable": true, 254 | "avg": true, 255 | "current": true, 256 | "max": true, 257 | "min": true, 258 | "show": true, 259 | "total": false, 260 | "values": true 261 | }, 262 | "lines": true, 263 | "linewidth": 1, 264 | "links": [], 265 | "nullPointMode": "null", 266 | "options": { 267 | "dataLinks": [] 268 | }, 269 | "percentage": false, 270 | "pointradius": 5, 271 | "points": false, 272 | "renderer": "flot", 273 | "seriesOverrides": [], 274 | "spaceLength": 10, 275 | "stack": false, 276 | "steppedLine": false, 277 | "targets": [ 278 | { 279 | "refCount": 0, 280 | "refId": "A", 281 | "target": "aliasByNode(perSecond(hostname.dns_cachestats.QueryHits), 2)" 282 | }, 283 | { 284 | "refCount": 0, 285 | "refId": "B", 286 | "target": "aliasByNode(perSecond(hostname.dns_cachestats.QueryMisses), 2)" 287 | } 288 | ], 289 | "thresholds": [], 290 | "timeFrom": null, 291 | "timeRegions": [], 292 | "timeShift": null, 293 | "title": "Cache Hits", 294 | "tooltip": { 295 | "shared": true, 296 | "sort": 0, 297 | "value_type": "individual" 298 | }, 299 | "type": "graph", 300 | "xaxis": { 301 | "buckets": null, 302 | "mode": "time", 303 | "name": null, 304 | "show": true, 305 | "values": [] 306 | }, 307 | "yaxes": [ 308 | { 309 | "format": "short", 310 | "label": null, 311 | "logBase": 1, 312 | "max": null, 313 | "min": null, 314 | "show": true 315 | }, 316 | { 317 | "format": "short", 318 | "label": null, 319 | "logBase": 1, 320 | "max": null, 321 | "min": null, 322 | "show": true 323 | } 324 | ], 325 | "yaxis": { 326 | "align": false, 327 | "alignLevel": null 328 | } 329 | }, 330 | { 331 | "cacheTimeout": null, 332 | "colorBackground": false, 333 | "colorValue": false, 334 | "colors": [ 335 | "#299c46", 336 | "rgba(237, 129, 40, 0.89)", 337 | "#d44a3a" 338 | ], 339 | "datasource": "${DS_GRAPHITE}", 340 | "format": "s", 341 | "gauge": { 342 | "maxValue": 100, 343 | "minValue": 0, 344 | "show": false, 345 | "thresholdLabels": false, 346 | "thresholdMarkers": true 347 | }, 348 | "gridPos": { 349 | "h": 3, 350 | "w": 6, 351 | "x": 0, 352 | "y": 3 353 | }, 354 | "id": 18, 355 | "interval": null, 356 | "links": [], 357 | "mappingType": 1, 358 | "mappingTypes": [ 359 | { 360 | "name": "value to text", 361 | "value": 1 362 | }, 363 | { 364 | "name": "range to text", 365 | "value": 2 366 | } 367 | ], 368 | "maxDataPoints": 100, 369 | "nullPointMode": "connected", 370 | "nullText": null, 371 | "options": {}, 372 | "postfix": " ago", 373 | "postfixFontSize": "50%", 374 | "prefix": "", 375 | "prefixFontSize": "50%", 376 | "rangeMaps": [ 377 | { 378 | "from": "null", 379 | "text": "N/A", 380 | "to": "null" 381 | } 382 | ], 383 | "sparkline": { 384 | "fillColor": "rgba(31, 118, 189, 0.18)", 385 | "full": false, 386 | "lineColor": "rgb(31, 120, 193)", 387 | "show": false 388 | }, 389 | "tableColumn": "", 390 | "targets": [ 391 | { 392 | "refId": "A", 393 | "target": "hostname.bind_info.config-time" 394 | } 395 | ], 396 | "thresholds": "", 397 | "title": "BIND Config Time", 398 | "type": "singlestat", 399 | "valueFontSize": "80%", 400 | "valueMaps": [ 401 | { 402 | "op": "=", 403 | "text": "N/A", 404 | "value": "null" 405 | } 406 | ], 407 | "valueName": "current" 408 | }, 409 | { 410 | "aliasColors": {}, 411 | "bars": false, 412 | "dashLength": 10, 413 | "dashes": false, 414 | "datasource": "${DS_GRAPHITE}", 415 | "fill": 1, 416 | "fillGradient": 0, 417 | "gridPos": { 418 | "h": 9, 419 | "w": 12, 420 | "x": 0, 421 | "y": 6 422 | }, 423 | "id": 2, 424 | "legend": { 425 | "alignAsTable": true, 426 | "avg": true, 427 | "current": true, 428 | "hideEmpty": true, 429 | "hideZero": true, 430 | "max": true, 431 | "min": true, 432 | "show": true, 433 | "sort": "avg", 434 | "sortDesc": true, 435 | "total": false, 436 | "values": true 437 | }, 438 | "lines": true, 439 | "linewidth": 1, 440 | "links": [], 441 | "nullPointMode": "null", 442 | "options": { 443 | "dataLinks": [] 444 | }, 445 | "percentage": false, 446 | "pointradius": 5, 447 | "points": false, 448 | "renderer": "flot", 449 | "seriesOverrides": [], 450 | "spaceLength": 10, 451 | "stack": false, 452 | "steppedLine": false, 453 | "targets": [ 454 | { 455 | "refId": "A", 456 | "target": "aliasByNode(perSecond(hostname.dns_opcode_in.*), 2)" 457 | } 458 | ], 459 | "thresholds": [], 460 | "timeFrom": null, 461 | "timeRegions": [], 462 | "timeShift": null, 463 | "title": "opcodes_in", 464 | "tooltip": { 465 | "shared": true, 466 | "sort": 0, 467 | "value_type": "individual" 468 | }, 469 | "type": "graph", 470 | "xaxis": { 471 | "buckets": null, 472 | "mode": "time", 473 | "name": null, 474 | "show": true, 475 | "values": [] 476 | }, 477 | "yaxes": [ 478 | { 479 | "format": "short", 480 | "label": "Ops/sec", 481 | "logBase": 1, 482 | "max": null, 483 | "min": null, 484 | "show": true 485 | }, 486 | { 487 | "format": "short", 488 | "label": null, 489 | "logBase": 1, 490 | "max": null, 491 | "min": null, 492 | "show": true 493 | } 494 | ], 495 | "yaxis": { 496 | "align": false, 497 | "alignLevel": null 498 | } 499 | }, 500 | { 501 | "aliasColors": {}, 502 | "bars": false, 503 | "dashLength": 10, 504 | "dashes": false, 505 | "datasource": "${DS_GRAPHITE}", 506 | "fill": 1, 507 | "fillGradient": 0, 508 | "gridPos": { 509 | "h": 9, 510 | "w": 12, 511 | "x": 12, 512 | "y": 6 513 | }, 514 | "id": 4, 515 | "legend": { 516 | "alignAsTable": true, 517 | "avg": true, 518 | "current": true, 519 | "hideEmpty": true, 520 | "hideZero": true, 521 | "max": true, 522 | "min": true, 523 | "show": true, 524 | "sort": "avg", 525 | "sortDesc": true, 526 | "total": false, 527 | "values": true 528 | }, 529 | "lines": true, 530 | "linewidth": 1, 531 | "links": [], 532 | "nullPointMode": "null", 533 | "options": { 534 | "dataLinks": [] 535 | }, 536 | "percentage": false, 537 | "pointradius": 5, 538 | "points": false, 539 | "renderer": "flot", 540 | "seriesOverrides": [], 541 | "spaceLength": 10, 542 | "stack": false, 543 | "steppedLine": false, 544 | "targets": [ 545 | { 546 | "refId": "A", 547 | "target": "aliasByNode(perSecond(hostname.dns_qtypes_in.*), 2)" 548 | } 549 | ], 550 | "thresholds": [], 551 | "timeFrom": null, 552 | "timeRegions": [], 553 | "timeShift": null, 554 | "title": "qtypes_in", 555 | "tooltip": { 556 | "shared": true, 557 | "sort": 0, 558 | "value_type": "individual" 559 | }, 560 | "type": "graph", 561 | "xaxis": { 562 | "buckets": null, 563 | "mode": "time", 564 | "name": null, 565 | "show": true, 566 | "values": [] 567 | }, 568 | "yaxes": [ 569 | { 570 | "format": "short", 571 | "label": "Queries/sec", 572 | "logBase": 1, 573 | "max": null, 574 | "min": null, 575 | "show": true 576 | }, 577 | { 578 | "format": "short", 579 | "label": null, 580 | "logBase": 1, 581 | "max": null, 582 | "min": null, 583 | "show": true 584 | } 585 | ], 586 | "yaxis": { 587 | "align": false, 588 | "alignLevel": null 589 | } 590 | }, 591 | { 592 | "aliasColors": {}, 593 | "bars": false, 594 | "dashLength": 10, 595 | "dashes": false, 596 | "datasource": "${DS_GRAPHITE}", 597 | "fill": 1, 598 | "fillGradient": 0, 599 | "gridPos": { 600 | "h": 9, 601 | "w": 12, 602 | "x": 0, 603 | "y": 15 604 | }, 605 | "id": 6, 606 | "legend": { 607 | "alignAsTable": true, 608 | "avg": true, 609 | "current": true, 610 | "hideEmpty": true, 611 | "hideZero": true, 612 | "max": true, 613 | "min": true, 614 | "show": true, 615 | "sort": "avg", 616 | "sortDesc": true, 617 | "total": false, 618 | "values": true 619 | }, 620 | "lines": true, 621 | "linewidth": 1, 622 | "links": [], 623 | "nullPointMode": "null", 624 | "options": { 625 | "dataLinks": [] 626 | }, 627 | "percentage": false, 628 | "pointradius": 5, 629 | "points": false, 630 | "renderer": "flot", 631 | "seriesOverrides": [], 632 | "spaceLength": 10, 633 | "stack": false, 634 | "steppedLine": false, 635 | "targets": [ 636 | { 637 | "refId": "A", 638 | "target": "aliasByNode(perSecond(hostname.dns_resolver_stats_qtype.*), 2)" 639 | } 640 | ], 641 | "thresholds": [], 642 | "timeFrom": null, 643 | "timeRegions": [], 644 | "timeShift": null, 645 | "title": "resolver_stats_qtype", 646 | "tooltip": { 647 | "shared": true, 648 | "sort": 0, 649 | "value_type": "individual" 650 | }, 651 | "type": "graph", 652 | "xaxis": { 653 | "buckets": null, 654 | "mode": "time", 655 | "name": null, 656 | "show": true, 657 | "values": [] 658 | }, 659 | "yaxes": [ 660 | { 661 | "format": "short", 662 | "label": "Queries/sec", 663 | "logBase": 1, 664 | "max": null, 665 | "min": null, 666 | "show": true 667 | }, 668 | { 669 | "format": "short", 670 | "label": null, 671 | "logBase": 1, 672 | "max": null, 673 | "min": null, 674 | "show": true 675 | } 676 | ], 677 | "yaxis": { 678 | "align": false, 679 | "alignLevel": null 680 | } 681 | }, 682 | { 683 | "aliasColors": {}, 684 | "bars": false, 685 | "dashLength": 10, 686 | "dashes": false, 687 | "datasource": "${DS_GRAPHITE}", 688 | "fill": 1, 689 | "fillGradient": 0, 690 | "gridPos": { 691 | "h": 9, 692 | "w": 12, 693 | "x": 12, 694 | "y": 15 695 | }, 696 | "id": 16, 697 | "legend": { 698 | "alignAsTable": true, 699 | "avg": true, 700 | "current": true, 701 | "hideEmpty": true, 702 | "hideZero": true, 703 | "max": true, 704 | "min": true, 705 | "show": true, 706 | "sort": "avg", 707 | "sortDesc": true, 708 | "total": false, 709 | "values": true 710 | }, 711 | "lines": true, 712 | "linewidth": 1, 713 | "links": [], 714 | "nullPointMode": "null", 715 | "options": { 716 | "dataLinks": [] 717 | }, 718 | "percentage": false, 719 | "pointradius": 5, 720 | "points": false, 721 | "renderer": "flot", 722 | "seriesOverrides": [], 723 | "spaceLength": 10, 724 | "stack": false, 725 | "steppedLine": false, 726 | "targets": [ 727 | { 728 | "refId": "A", 729 | "target": "aliasByNode(perSecond(hostname.dns_resolver_stats_defview.*), 2)" 730 | } 731 | ], 732 | "thresholds": [], 733 | "timeFrom": null, 734 | "timeRegions": [], 735 | "timeShift": null, 736 | "title": "resolver_stats", 737 | "tooltip": { 738 | "shared": true, 739 | "sort": 0, 740 | "value_type": "individual" 741 | }, 742 | "type": "graph", 743 | "xaxis": { 744 | "buckets": null, 745 | "mode": "time", 746 | "name": null, 747 | "show": true, 748 | "values": [] 749 | }, 750 | "yaxes": [ 751 | { 752 | "format": "short", 753 | "label": null, 754 | "logBase": 1, 755 | "max": null, 756 | "min": null, 757 | "show": true 758 | }, 759 | { 760 | "format": "short", 761 | "label": null, 762 | "logBase": 1, 763 | "max": null, 764 | "min": null, 765 | "show": true 766 | } 767 | ], 768 | "yaxis": { 769 | "align": false, 770 | "alignLevel": null 771 | } 772 | }, 773 | { 774 | "aliasColors": {}, 775 | "bars": false, 776 | "dashLength": 10, 777 | "dashes": false, 778 | "datasource": "${DS_GRAPHITE}", 779 | "fill": 1, 780 | "fillGradient": 0, 781 | "gridPos": { 782 | "h": 9, 783 | "w": 12, 784 | "x": 0, 785 | "y": 24 786 | }, 787 | "id": 14, 788 | "legend": { 789 | "alignAsTable": true, 790 | "avg": true, 791 | "current": true, 792 | "hideEmpty": true, 793 | "hideZero": true, 794 | "max": true, 795 | "min": true, 796 | "show": true, 797 | "sort": "avg", 798 | "sortDesc": true, 799 | "total": false, 800 | "values": true 801 | }, 802 | "lines": true, 803 | "linewidth": 1, 804 | "links": [], 805 | "nullPointMode": "null", 806 | "options": { 807 | "dataLinks": [] 808 | }, 809 | "percentage": false, 810 | "pointradius": 5, 811 | "points": false, 812 | "renderer": "flot", 813 | "seriesOverrides": [], 814 | "spaceLength": 10, 815 | "stack": false, 816 | "steppedLine": false, 817 | "targets": [ 818 | { 819 | "refId": "A", 820 | "target": "aliasByNode(perSecond(hostname.dns_zone_stats.*), 2)" 821 | } 822 | ], 823 | "thresholds": [], 824 | "timeFrom": null, 825 | "timeRegions": [], 826 | "timeShift": null, 827 | "title": "zone_maint", 828 | "tooltip": { 829 | "shared": true, 830 | "sort": 0, 831 | "value_type": "individual" 832 | }, 833 | "type": "graph", 834 | "xaxis": { 835 | "buckets": null, 836 | "mode": "time", 837 | "name": null, 838 | "show": true, 839 | "values": [] 840 | }, 841 | "yaxes": [ 842 | { 843 | "format": "short", 844 | "label": "Ops/sec", 845 | "logBase": 1, 846 | "max": null, 847 | "min": null, 848 | "show": true 849 | }, 850 | { 851 | "format": "short", 852 | "label": null, 853 | "logBase": 1, 854 | "max": null, 855 | "min": null, 856 | "show": true 857 | } 858 | ], 859 | "yaxis": { 860 | "align": false, 861 | "alignLevel": null 862 | } 863 | }, 864 | { 865 | "aliasColors": {}, 866 | "bars": false, 867 | "dashLength": 10, 868 | "dashes": false, 869 | "datasource": "${DS_GRAPHITE}", 870 | "fill": 1, 871 | "fillGradient": 0, 872 | "gridPos": { 873 | "h": 9, 874 | "w": 12, 875 | "x": 12, 876 | "y": 24 877 | }, 878 | "id": 12, 879 | "legend": { 880 | "alignAsTable": true, 881 | "avg": true, 882 | "current": true, 883 | "hideEmpty": true, 884 | "hideZero": true, 885 | "max": true, 886 | "min": true, 887 | "show": true, 888 | "sort": "avg", 889 | "sortDesc": true, 890 | "total": false, 891 | "values": true 892 | }, 893 | "lines": true, 894 | "linewidth": 1, 895 | "links": [], 896 | "nullPointMode": "null", 897 | "options": { 898 | "dataLinks": [] 899 | }, 900 | "percentage": false, 901 | "pointradius": 5, 902 | "points": false, 903 | "renderer": "flot", 904 | "seriesOverrides": [], 905 | "spaceLength": 10, 906 | "stack": false, 907 | "steppedLine": false, 908 | "targets": [ 909 | { 910 | "refId": "A", 911 | "target": "aliasByNode(perSecond(hostname.dns_server_stats.*), 2)" 912 | } 913 | ], 914 | "thresholds": [], 915 | "timeFrom": null, 916 | "timeRegions": [], 917 | "timeShift": null, 918 | "title": "server_stats", 919 | "tooltip": { 920 | "shared": true, 921 | "sort": 0, 922 | "value_type": "individual" 923 | }, 924 | "type": "graph", 925 | "xaxis": { 926 | "buckets": null, 927 | "mode": "time", 928 | "name": null, 929 | "show": true, 930 | "values": [] 931 | }, 932 | "yaxes": [ 933 | { 934 | "format": "short", 935 | "label": "Ops/sec", 936 | "logBase": 1, 937 | "max": null, 938 | "min": null, 939 | "show": true 940 | }, 941 | { 942 | "format": "short", 943 | "label": null, 944 | "logBase": 1, 945 | "max": null, 946 | "min": null, 947 | "show": true 948 | } 949 | ], 950 | "yaxis": { 951 | "align": false, 952 | "alignLevel": null 953 | } 954 | }, 955 | { 956 | "aliasColors": {}, 957 | "bars": false, 958 | "dashLength": 10, 959 | "dashes": false, 960 | "datasource": "${DS_GRAPHITE}", 961 | "fill": 1, 962 | "fillGradient": 0, 963 | "gridPos": { 964 | "h": 9, 965 | "w": 12, 966 | "x": 0, 967 | "y": 33 968 | }, 969 | "id": 36, 970 | "legend": { 971 | "alignAsTable": true, 972 | "avg": true, 973 | "current": true, 974 | "max": true, 975 | "min": true, 976 | "show": true, 977 | "sort": "avg", 978 | "sortDesc": true, 979 | "total": false, 980 | "values": true 981 | }, 982 | "lines": true, 983 | "linewidth": 1, 984 | "links": [], 985 | "nullPointMode": "null", 986 | "options": { 987 | "dataLinks": [] 988 | }, 989 | "percentage": false, 990 | "pointradius": 5, 991 | "points": false, 992 | "renderer": "flot", 993 | "seriesOverrides": [], 994 | "spaceLength": 10, 995 | "stack": false, 996 | "steppedLine": false, 997 | "targets": [ 998 | { 999 | "refId": "A", 1000 | "target": "aliasByNode(perSecond(hostname.dns_server_stats.Qry*), 2)" 1001 | } 1002 | ], 1003 | "thresholds": [], 1004 | "timeFrom": null, 1005 | "timeRegions": [], 1006 | "timeShift": null, 1007 | "title": "QueryStats", 1008 | "tooltip": { 1009 | "shared": true, 1010 | "sort": 0, 1011 | "value_type": "individual" 1012 | }, 1013 | "type": "graph", 1014 | "xaxis": { 1015 | "buckets": null, 1016 | "mode": "time", 1017 | "name": null, 1018 | "show": true, 1019 | "values": [] 1020 | }, 1021 | "yaxes": [ 1022 | { 1023 | "format": "short", 1024 | "label": null, 1025 | "logBase": 1, 1026 | "max": null, 1027 | "min": null, 1028 | "show": true 1029 | }, 1030 | { 1031 | "format": "short", 1032 | "label": null, 1033 | "logBase": 1, 1034 | "max": null, 1035 | "min": null, 1036 | "show": true 1037 | } 1038 | ], 1039 | "yaxis": { 1040 | "align": false, 1041 | "alignLevel": null 1042 | } 1043 | }, 1044 | { 1045 | "aliasColors": {}, 1046 | "bars": true, 1047 | "dashLength": 10, 1048 | "dashes": false, 1049 | "datasource": "${DS_GRAPHITE}", 1050 | "fill": 1, 1051 | "fillGradient": 0, 1052 | "gridPos": { 1053 | "h": 9, 1054 | "w": 12, 1055 | "x": 12, 1056 | "y": 33 1057 | }, 1058 | "id": 38, 1059 | "legend": { 1060 | "alignAsTable": true, 1061 | "avg": true, 1062 | "current": true, 1063 | "max": true, 1064 | "min": true, 1065 | "show": true, 1066 | "sort": "avg", 1067 | "sortDesc": true, 1068 | "total": false, 1069 | "values": true 1070 | }, 1071 | "lines": true, 1072 | "linewidth": 1, 1073 | "links": [], 1074 | "nullPointMode": "null", 1075 | "options": { 1076 | "dataLinks": [] 1077 | }, 1078 | "percentage": false, 1079 | "pointradius": 5, 1080 | "points": false, 1081 | "renderer": "flot", 1082 | "seriesOverrides": [ 1083 | { 1084 | "alias": "ValAttempt", 1085 | "hideTooltip": true, 1086 | "legend": false, 1087 | "lines": false, 1088 | "points": false 1089 | } 1090 | ], 1091 | "spaceLength": 10, 1092 | "stack": false, 1093 | "steppedLine": false, 1094 | "targets": [ 1095 | { 1096 | "refId": "A", 1097 | "target": "aliasByNode(perSecond(hostname.dns_resolver_stats_defview.Val*), 2)" 1098 | } 1099 | ], 1100 | "thresholds": [], 1101 | "timeFrom": null, 1102 | "timeRegions": [], 1103 | "timeShift": null, 1104 | "title": "Validation Stats", 1105 | "tooltip": { 1106 | "shared": true, 1107 | "sort": 0, 1108 | "value_type": "individual" 1109 | }, 1110 | "type": "graph", 1111 | "xaxis": { 1112 | "buckets": null, 1113 | "mode": "time", 1114 | "name": null, 1115 | "show": true, 1116 | "values": [] 1117 | }, 1118 | "yaxes": [ 1119 | { 1120 | "format": "short", 1121 | "label": null, 1122 | "logBase": 1, 1123 | "max": null, 1124 | "min": null, 1125 | "show": true 1126 | }, 1127 | { 1128 | "format": "short", 1129 | "label": null, 1130 | "logBase": 1, 1131 | "max": null, 1132 | "min": null, 1133 | "show": true 1134 | } 1135 | ], 1136 | "yaxis": { 1137 | "align": false, 1138 | "alignLevel": null 1139 | } 1140 | }, 1141 | { 1142 | "aliasColors": {}, 1143 | "bars": false, 1144 | "dashLength": 10, 1145 | "dashes": false, 1146 | "datasource": "${DS_GRAPHITE}", 1147 | "fill": 1, 1148 | "fillGradient": 0, 1149 | "gridPos": { 1150 | "h": 9, 1151 | "w": 12, 1152 | "x": 0, 1153 | "y": 42 1154 | }, 1155 | "id": 22, 1156 | "legend": { 1157 | "alignAsTable": true, 1158 | "avg": true, 1159 | "current": true, 1160 | "max": true, 1161 | "min": true, 1162 | "show": true, 1163 | "sort": "avg", 1164 | "sortDesc": true, 1165 | "total": false, 1166 | "values": true 1167 | }, 1168 | "lines": true, 1169 | "linewidth": 1, 1170 | "links": [], 1171 | "nullPointMode": "null", 1172 | "options": { 1173 | "dataLinks": [] 1174 | }, 1175 | "percentage": false, 1176 | "pointradius": 5, 1177 | "points": false, 1178 | "renderer": "flot", 1179 | "seriesOverrides": [], 1180 | "spaceLength": 10, 1181 | "stack": false, 1182 | "steppedLine": false, 1183 | "targets": [ 1184 | { 1185 | "refId": "A", 1186 | "target": "aliasByNode(perSecond(hostname.bind_zones.*), 2)" 1187 | } 1188 | ], 1189 | "thresholds": [], 1190 | "timeFrom": null, 1191 | "timeRegions": [], 1192 | "timeShift": null, 1193 | "title": "Serial Increments", 1194 | "tooltip": { 1195 | "shared": true, 1196 | "sort": 0, 1197 | "value_type": "individual" 1198 | }, 1199 | "type": "graph", 1200 | "xaxis": { 1201 | "buckets": null, 1202 | "mode": "time", 1203 | "name": null, 1204 | "show": true, 1205 | "values": [] 1206 | }, 1207 | "yaxes": [ 1208 | { 1209 | "format": "short", 1210 | "label": null, 1211 | "logBase": 1, 1212 | "max": null, 1213 | "min": null, 1214 | "show": true 1215 | }, 1216 | { 1217 | "format": "short", 1218 | "label": null, 1219 | "logBase": 1, 1220 | "max": null, 1221 | "min": null, 1222 | "show": true 1223 | } 1224 | ], 1225 | "yaxis": { 1226 | "align": false, 1227 | "alignLevel": null 1228 | } 1229 | }, 1230 | { 1231 | "aliasColors": {}, 1232 | "bars": false, 1233 | "dashLength": 10, 1234 | "dashes": false, 1235 | "datasource": "${DS_GRAPHITE}", 1236 | "fill": 1, 1237 | "fillGradient": 0, 1238 | "gridPos": { 1239 | "h": 9, 1240 | "w": 12, 1241 | "x": 12, 1242 | "y": 42 1243 | }, 1244 | "id": 26, 1245 | "legend": { 1246 | "alignAsTable": true, 1247 | "avg": true, 1248 | "current": true, 1249 | "max": true, 1250 | "min": true, 1251 | "show": true, 1252 | "sort": "current", 1253 | "sortDesc": true, 1254 | "total": false, 1255 | "values": true 1256 | }, 1257 | "lines": true, 1258 | "linewidth": 1, 1259 | "links": [], 1260 | "nullPointMode": "null", 1261 | "options": { 1262 | "dataLinks": [] 1263 | }, 1264 | "percentage": false, 1265 | "pointradius": 5, 1266 | "points": false, 1267 | "renderer": "flot", 1268 | "seriesOverrides": [], 1269 | "spaceLength": 10, 1270 | "stack": false, 1271 | "steppedLine": false, 1272 | "targets": [ 1273 | { 1274 | "refId": "A", 1275 | "target": "aliasByNode(hostname.dns_memory_usage.*, 2)" 1276 | } 1277 | ], 1278 | "thresholds": [], 1279 | "timeFrom": null, 1280 | "timeRegions": [], 1281 | "timeShift": null, 1282 | "title": "memory_usage", 1283 | "tooltip": { 1284 | "shared": true, 1285 | "sort": 0, 1286 | "value_type": "individual" 1287 | }, 1288 | "type": "graph", 1289 | "xaxis": { 1290 | "buckets": null, 1291 | "mode": "time", 1292 | "name": null, 1293 | "show": true, 1294 | "values": [] 1295 | }, 1296 | "yaxes": [ 1297 | { 1298 | "format": "decbytes", 1299 | "label": null, 1300 | "logBase": 1, 1301 | "max": null, 1302 | "min": null, 1303 | "show": true 1304 | }, 1305 | { 1306 | "format": "short", 1307 | "label": null, 1308 | "logBase": 1, 1309 | "max": null, 1310 | "min": null, 1311 | "show": true 1312 | } 1313 | ], 1314 | "yaxis": { 1315 | "align": false, 1316 | "alignLevel": null 1317 | } 1318 | }, 1319 | { 1320 | "columns": [ 1321 | { 1322 | "text": "Current", 1323 | "value": "current" 1324 | } 1325 | ], 1326 | "datasource": "${DS_GRAPHITE}", 1327 | "fontSize": "100%", 1328 | "gridPos": { 1329 | "h": 9, 1330 | "w": 12, 1331 | "x": 0, 1332 | "y": 51 1333 | }, 1334 | "id": 32, 1335 | "links": [], 1336 | "options": {}, 1337 | "pageSize": null, 1338 | "scroll": true, 1339 | "showHeader": true, 1340 | "sort": { 1341 | "col": 0, 1342 | "desc": false 1343 | }, 1344 | "styles": [ 1345 | { 1346 | "alias": "Time", 1347 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 1348 | "pattern": "Time", 1349 | "type": "date" 1350 | }, 1351 | { 1352 | "alias": "", 1353 | "colorMode": null, 1354 | "colors": [ 1355 | "rgba(245, 54, 54, 0.9)", 1356 | "rgba(237, 129, 40, 0.89)", 1357 | "rgba(50, 172, 45, 0.97)" 1358 | ], 1359 | "decimals": 2, 1360 | "pattern": "/.*/", 1361 | "thresholds": [], 1362 | "type": "string", 1363 | "unit": "short" 1364 | } 1365 | ], 1366 | "targets": [ 1367 | { 1368 | "refId": "A", 1369 | "target": "aliasByNode(hostname.bind_zones.*, 2)" 1370 | } 1371 | ], 1372 | "title": "Zones List", 1373 | "transform": "timeseries_aggregations", 1374 | "type": "table" 1375 | } 1376 | ], 1377 | "schemaVersion": 20, 1378 | "style": "dark", 1379 | "tags": [], 1380 | "templating": { 1381 | "list": [] 1382 | }, 1383 | "time": { 1384 | "from": "now-6h", 1385 | "to": "now" 1386 | }, 1387 | "timepicker": { 1388 | "refresh_intervals": [ 1389 | "5s", 1390 | "10s", 1391 | "30s", 1392 | "1m", 1393 | "5m", 1394 | "15m", 1395 | "30m", 1396 | "1h", 1397 | "2h", 1398 | "1d" 1399 | ], 1400 | "time_options": [ 1401 | "5m", 1402 | "15m", 1403 | "1h", 1404 | "6h", 1405 | "12h", 1406 | "24h", 1407 | "2d", 1408 | "7d", 1409 | "30d" 1410 | ] 1411 | }, 1412 | "timezone": "", 1413 | "title": "Hostname - BIND", 1414 | "uid": "_hUr1lpWz", 1415 | "version": 49 1416 | } 1417 | --------------------------------------------------------------------------------