857090bc7c
Ocassionally this exporter produces output which is not compatible with the telegraf prometheus input plugin. This change adds a filter to remove duplicated HELP and TYPE texts from the output eg: 021-09-13T19:40:00Z E! [inputs.prometheus] Error in plugin: error reading metrics for http://10.96.22.16:9103/metrics: reading text format failed: text format parsing error in line 46: second HELP line for metric name "openstack_services_neutron_neutron_sriov_nic_agent" Change-Id: I8c4c7739be86207de04bca4c1f9718794f4dda5f
184 lines
6.1 KiB
Python
184 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
# 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 argparse
|
|
import yaml
|
|
import os
|
|
import urllib.parse
|
|
import logging
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from socketserver import ForkingMixIn
|
|
|
|
from prometheus_client import CONTENT_TYPE_LATEST
|
|
|
|
from osclient import OSClient
|
|
from oscache import OSCache
|
|
from check_os_api import CheckOSApi
|
|
from neutron_agents import NeutronAgentStats
|
|
from nova_services import NovaServiceStats
|
|
from cinder_services import CinderServiceStats
|
|
from hypervisor_stats import HypervisorStats
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s:%(levelname)s: %(message)s")
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
collectors = []
|
|
|
|
|
|
class ForkingHTTPServer(ForkingMixIn, HTTPServer):
|
|
pass
|
|
|
|
class OpenstackExporterHandler(BaseHTTPRequestHandler):
|
|
def __init__(self, *args, **kwargs):
|
|
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
|
|
|
def do_GET(self):
|
|
url = urllib.parse.urlparse(self.path)
|
|
if url.path == '/metrics':
|
|
output = b''
|
|
for collector in collectors:
|
|
try:
|
|
stats = collector.get_stats()
|
|
if stats is not None:
|
|
output = output + remove_duplicate_help_text(stats)
|
|
except BaseException as inst:
|
|
logger.warning(
|
|
'Could not get stats for collector {}.'
|
|
'"{}" Exception "{}" occured.'
|
|
.format(
|
|
collector.get_cache_key(),
|
|
type(inst),
|
|
inst
|
|
))
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', CONTENT_TYPE_LATEST)
|
|
self.end_headers()
|
|
self.wfile.write(output)
|
|
elif url.path == '/':
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write("""<html>
|
|
<head><title>OpenStack Exporter</title></head>
|
|
<body>
|
|
<h1>OpenStack Exporter</h1>
|
|
<p>Visit <code>/metrics</code> to use.</p>
|
|
</body>
|
|
</html>""")
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
|
|
def handler(*args, **kwargs):
|
|
OpenstackExporterHandler(*args, **kwargs)
|
|
|
|
def remove_duplicate_help_text(stats):
|
|
""" Ocassionally this exporter produces output which is not compatible
|
|
with the telegraf prometheus input plugin. This function is a filter
|
|
to remove duplicated # HELP and # TYPE texts from the output """
|
|
|
|
seen = set()
|
|
clean_stats = []
|
|
|
|
for line in stats.decode().splitlines():
|
|
if line not in seen:
|
|
if line.startswith('#'):
|
|
seen.add(line)
|
|
clean_stats.append(line)
|
|
|
|
return '\n'.join(clean_stats).encode() + b'\n'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(
|
|
usage=__doc__,
|
|
description='Prometheus OpenStack exporter',
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
|
parser.add_argument('--config-file', nargs='?',
|
|
help='Configuration file path',
|
|
type=argparse.FileType('r'),
|
|
required=False)
|
|
args = parser.parse_args()
|
|
config = {}
|
|
if args.config_file:
|
|
config = yaml.safe_load(args.config_file.read())
|
|
|
|
os_keystone_url = config.get('OS_AUTH_URL', os.getenv('OS_AUTH_URL'))
|
|
os_password = config.get('OS_PASSWORD', os.getenv('OS_PASSWORD'))
|
|
os_tenant_name = config.get(
|
|
'OS_PROJECT_NAME',
|
|
os.getenv('OS_PROJECT_NAME'))
|
|
os_username = config.get('OS_USERNAME', os.getenv('OS_USERNAME'))
|
|
os_user_domain = config.get(
|
|
'OS_USER_DOMAIN_NAME',
|
|
os.getenv('OS_USER_DOMAIN_NAME'))
|
|
os_region = config.get('OS_REGION_NAME', os.getenv('OS_REGION_NAME'))
|
|
os_cacert = config.get('OS_CACERT', os.getenv('OS_CACERT'))
|
|
os_timeout = config.get(
|
|
'TIMEOUT_SECONDS', int(
|
|
os.getenv(
|
|
'TIMEOUT_SECONDS', 10)))
|
|
os_polling_interval = config.get(
|
|
'OS_POLLING_INTERVAL', int(
|
|
os.getenv(
|
|
'OS_POLLING_INTERVAL', 900)))
|
|
os_retries = config.get('OS_RETRIES', int(os.getenv('OS_RETRIES', 1)))
|
|
os_cpu_overcomit_ratio = config.get(
|
|
'OS_CPU_OC_RATIO', float(
|
|
os.getenv(
|
|
'OS_CPU_OC_RATIO', 1)))
|
|
os_ram_overcomit_ratio = config.get(
|
|
'OS_RAM_OC_RATIO', float(
|
|
os.getenv(
|
|
'OS_RAM_OC_RATIO', 1)))
|
|
|
|
osclient = OSClient(
|
|
os_keystone_url,
|
|
os_password,
|
|
os_tenant_name,
|
|
os_username,
|
|
os_user_domain,
|
|
os_region,
|
|
os_cacert,
|
|
os_timeout,
|
|
os_retries)
|
|
oscache = OSCache(os_polling_interval, os_region)
|
|
collectors.append(oscache)
|
|
|
|
check_os_api = CheckOSApi(oscache, osclient)
|
|
collectors.append(check_os_api)
|
|
neutron_agent_stats = NeutronAgentStats(oscache, osclient)
|
|
collectors.append(neutron_agent_stats)
|
|
cinder_service_stats = CinderServiceStats(oscache, osclient)
|
|
collectors.append(cinder_service_stats)
|
|
nova_service_stats = NovaServiceStats(oscache, osclient)
|
|
collectors.append(nova_service_stats)
|
|
hypervisor_stats = HypervisorStats(
|
|
oscache,
|
|
osclient,
|
|
os_cpu_overcomit_ratio,
|
|
os_ram_overcomit_ratio)
|
|
collectors.append(hypervisor_stats)
|
|
|
|
oscache.start()
|
|
|
|
listen_port = config.get(
|
|
'LISTEN_PORT', int(
|
|
os.getenv(
|
|
'LISTEN_PORT', 9103)))
|
|
server = ForkingHTTPServer(('', listen_port), handler)
|
|
server.serve_forever()
|