monasca-agent/monasca_agent/collector/checks_d/varnish.py

175 lines
6.6 KiB
Python

# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
import subprocess
import xml.parsers.expat # python 2.4 compatible
from monasca_agent.collector.checks import AgentCheck
class Varnish(AgentCheck):
# XML parsing bits, a.k.a. Kafka in Code
def _reset(self):
self._current_element = ""
self._current_metric = "varnish"
self._current_value = 0
self._current_str = ""
self._current_type = ""
def _start_element(self, name, attrs):
self._current_element = name
def _end_element(self, name):
if name == "stat":
m_name = self.normalize(self._current_metric)
if self._current_type in ("a", "c"):
self.rate(m_name, long(self._current_value))
elif self._current_type in ("i", "g"):
self.gauge(m_name, long(self._current_value))
else:
# Unsupported data type, ignore
self._reset()
return # don't save
# reset for next stat element
self._reset()
elif name in ("type", "ident", "name"):
self._current_metric += "." + self._current_str
def _char_data(self, data):
self.log.debug("Data %s [%s]" % (data, self._current_element))
data = data.strip()
if len(data) > 0 and self._current_element != "":
if self._current_element == "value":
self._current_value = long(data)
elif self._current_element == "flag":
self._current_type = data
else:
self._current_str = data
def check(self, instance):
"""Extract stats from varnishstat -x
The text option (-1) is not reliable enough when counters get large.
VBE.media_video_prd_services_01(10.93.67.16,,8080).happy18446744073709551615
2 types of data, "a" for counter ("c" in newer versions of varnish), "i" for gauge ("g")
https://github.com/varnish/Varnish-Cache/blob/master/include/tbl/vsc_fields.h
Bitmaps are not supported.
<varnishstat>
<stat>
<name>fetch_304</name>
<value>0</value>
<flag>a</flag>
<description>Fetch no body (304)</description>
</stat>
<stat>
<name>n_sess_mem</name>
<value>334</value>
<flag>i</flag>
<description>N struct sess_mem</description>
</stat>
<stat>
<type>LCK</type>
<ident>vcl</ident>
<name>creat</name>
<value>1</value>
<flag>a</flag>
<description>Created locks</description>
</stat>
</varnishstat>
"""
# Not configured? Not a problem.
if instance.get("varnishstat", None) is None:
raise Exception("varnishstat is not configured")
dimensions = self._set_dimensions(None, instance)
name = instance.get('name')
# Get the varnish version from varnishstat
output, error = subprocess.Popen([instance.get("varnishstat"), "-V"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
# Assumptions regarding varnish's version
use_xml = True
arg = "-x" # varnishstat argument
version = 3
m1 = re.search(r"varnish-(\d+)", output, re.MULTILINE)
# v2 prints the version on stderr, v3 on stdout
m2 = re.search(r"varnish-(\d+)", error, re.MULTILINE)
if m1 is None and m2 is None:
self.log.warn("Cannot determine the version of varnishstat, assuming 3 or greater")
self.log.warn("Cannot determine the version of varnishstat, assuming 3 or greater")
else:
if m1 is not None:
version = int(m1.group(1))
elif m2 is not None:
version = int(m2.group(1))
self.log.debug("Varnish version: %d" % version)
# Location of varnishstat
if version <= 2:
use_xml = False
arg = "-1"
cmd = [instance.get("varnishstat"), arg]
if name is not None:
cmd.extend(['-n', name])
dimensions.update({'varnish_name': name})
else:
dimensions.update({'varnish_name': 'default'})
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
output, error = proc.communicate()
except Exception:
self.log.error(u"Failed to run %s" % repr(cmd))
raise
if error and len(error) > 0:
self.log.error(error)
self._parse_varnishstat(output, use_xml, dimensions)
def _parse_varnishstat(self, output, use_xml, dimensions):
if use_xml:
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = self._start_element
p.EndElementHandler = self._end_element
p.CharacterDataHandler = self._char_data
self._reset()
p.Parse(output, True)
else:
for line in output.split("\n"):
self.log.debug("Parsing varnish results: %s" % line)
fields = line.split()
if len(fields) < 3:
break
name, gauge_val, rate_val = fields[0], fields[1], fields[2]
metric_name = self.normalize(name, prefix="varnish")
# Now figure out which value to pick
if rate_val.lower() in ("nan", "."):
# col 2 matters
self.log.debug("Varnish (gauge) %s %d" % (metric_name, int(gauge_val)))
self.gauge(metric_name, int(gauge_val), dimensions=dimensions)
else:
# col 3 has a rate (since restart)
self.log.debug("Varnish (rate) %s %d" % (metric_name, int(gauge_val)))
self.rate(metric_name, float(gauge_val), dimensions=dimensions)