openstack-helm-images/prometheus-openstack-exporter/exporter/main.py
Steven Fitzpatrick 857090bc7c Add a function to remove duplicate HELP/TYPE text from output
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
2021-09-15 21:13:20 +00:00

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()