commit 7941a8eb60f4907637fc1af6abcbb279933d1d1a Author: chris Date: Sat Jul 30 14:23:02 2016 +0200 initial commit. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..480e556 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:3.4 + +RUN apk add --no-cache python3 &&\ + pip3 install apscheduler &&\ + pip3 install requests + +COPY pagespeed.py pagespeed.conf / + +EXPOSE 9113 + +CMD python3 /pagespeed.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..bde0803 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# pagespeed_exporter for providing Google PageSpeed Insights as metrics + +## Requirements + +Software: + + * Python3 + * APScheduler + * requests + +Google PageSpeed API key. See here on how to [get a server API key for googles platform](https://developers.google.com/console/help/generating-dev-keys). + +## Configuration + +The exporter is currently able to check one URI, buffer the results +and serve them as metrics. + +Configuration is done via pagespeed.conf, see example file for documentation and defaults. +All config values can be overriden with environment variables given the same names. + +N.B.: Google caches it's results itself for 30 seconds, so a shorter fetch interval would +be useless. diff --git a/pagespeed.conf b/pagespeed.conf new file mode 100644 index 0000000..cf84a4b --- /dev/null +++ b/pagespeed.conf @@ -0,0 +1,25 @@ +[EXPORTER] +# URI to run tests against. Make sure it is reachable from the public internet. +# ENV override: PAGESPEED_TEST_URI +test_uri = https://www.test.de/ + +# Your PageSpeed Insights API key from Google. See README.md for documentation link. +# ENV override: PAGESPEED_API_KEY +api_key = + +# IP to bind the metrics server to. +# Default: empty, binds to all available interfaces. +# ENV override: PAGESPEED_BIND_IP +bind_ip = + +# TCP port to bind the metrics server to. +# Default: 9113 +# ENV override: PAGESPEED_BIND_PORT +bind_port = 9113 + +# Interval for instanciating pagespeed tests at google in seconds. +# Do not go below 30 seconds. +# Default: 300 +# ENV override: PAGESPEED_FETCH_INTERVAL +fetch_interval = 300 + diff --git a/pagespeed.py b/pagespeed.py new file mode 100644 index 0000000..8d17d1b --- /dev/null +++ b/pagespeed.py @@ -0,0 +1,118 @@ +import configparser +import json +import logging +import os +import sys +from http.server import HTTPServer, BaseHTTPRequestHandler +from time import sleep, time + +import requests +from pytz import utc +from apscheduler.schedulers.background import BackgroundScheduler + + +# try to read configfile +config = configparser.ConfigParser() +try: + config.read('pagespeed.conf') + exporter_config = config['EXPORTER'] +except: + exporter_config = {'EXPORTER': {}} + + +# try to get config from ENV +TEST_URI = os.environ.get('PAGESPEED_URI', exporter_config.get('TEST_URI')) +API_KEY = os.environ.get('PAGESPEED_API_KEY', exporter_config.get('API_KEY')) +BIND_IP = os.environ.get('PAGESPEED_HOST', exporter_config.get('BIND_IP', '')) +BIND_PORT = os.environ.get('PAGESPEED_PORT', exporter_config.get('BIND_PORT', 9113)) +FETCH_INTERVAL = os.environ.get('PAGESPEED_FETCH_INTERVAL', exporter_config.get('FETCH_INTERVAL', 300)) + + +# input validation :) +if not TEST_URI: + logger.error('TEST_URI needs to be defined either in config file or in ENV.') + sys.exit(1) +if not API_KEY: + logger.error('API_KEY needs to be definer either in config file or in ENV.') + sys.exit(1) +try: + FETCH_INTERVAL = int(FETCH_INTERVAL) + BIND_PORT = int(BIND_PORT) +except: + logger.exception('Value error in FETCH_INTERVAL or BIND_PORT.') + sys.exit(1) + + +metric_data = "" +error_instances = 0 +logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s') +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +def fetch_pagespeed(): + global metric_data + global error_instances + + logger.debug('fetching pagespeed') + + try: + req = requests.get('https://www.googleapis.com/pagespeedonline/v2/' + 'runPagespeed?url={url}&key={key}&' + 'prettyprint=false'.format( + url=TEST_URI, + key=API_KEY + )) + result = req.json() + except: + logger.exception('error while doing request') + error_instances += 1 + + translation_table = { + 'speed_score': result['ruleGroups']['SPEED']['score'], + 'stats_resources_total': result['pageStats']['numberResources'], + 'stats_hosts_total': result['pageStats']['numberHosts'], + 'stats_request_bytes': result['pageStats']['totalRequestBytes'], + 'stats_static_resources_total': result['pageStats']['numberStaticResources'], + 'stats_html_resources_bytes': result['pageStats']['htmlResponseBytes'], + 'stats_image_resources_bytes': result['pageStats']['imageResponseBytes'], + 'stats_javascript_resources_bytes': result['pageStats']['javascriptResponseBytes'], + 'stats_javascript_resources_total': result['pageStats']['numberJsResources'], + 'stats_css_resources_bytes': result['pageStats']['cssResponseBytes'], + 'stats_css_resources_total': result['pageStats']['numberCssResources'], + 'stats_other_resources_bytes': result['pageStats']['otherResponseBytes'], + } + metric_data = "" + for metric, value in translation_table.items(): + metric_data += 'pagespeed_{metric}{{site="{job}"}}' \ + ' {metric_value}\n'.format( + metric=metric, + job=result['id'], + metric_value=value, + ) + metric_data += 'pagespeed_last_update {}\n'.format(time()) + metric_data += 'pagespeed_metric_errors_total {}\n'.format(error_instances) + metric_data += 'up 1\n' + logger.debug('fetched pagespeed') + + +class AllGetHTTPRequestHandler(BaseHTTPRequestHandler): + def do_GET(s): + s.send_response(200) + s.send_header("Content-Type", "text/plain") + s.end_headers() + s.wfile.write(metric_data.encode('utf-8')) + +scheduler = BackgroundScheduler(timezone=utc) +scheduler.add_job(fetch_pagespeed, 'interval', seconds=FETCH_INTERVAL, max_instances=1) +scheduler.start() + + +if __name__=='__main__': + logger.info('starting pagespeed_exporter') + if metric_data == '': + fetch_pagespeed() + server_address = (BIND_IP, BIND_PORT) + httpd = HTTPServer(server_address, AllGetHTTPRequestHandler) + logger.info('binding to {}'.format(server_address)) + httpd.serve_forever()