├── Dockerfile ├── README.md ├── ping-exporter.py └── ping.png /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk update && apk add python2 py-pip fping 4 | COPY ./ping-exporter.py / 5 | EXPOSE 8085 6 | CMD ["python2", "/ping-exporter.py"] 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ping-exporter 2 | 3 | ## Introduction 4 | 5 | Prometheus Ping Exporter is a simple python script which utilize fping to probe endpoint through ICMP and parsing the output to Prometheus. The result can then be visualize through Grafana with ease. 6 | 7 | **Requirements** 8 | - Python 2.x 9 | - fping 4.x 10 | 11 | **Screenshots** 12 | ![alt text](https://raw.githubusercontent.com/frankiexyz/ping-exporter/master/ping.png) 13 | 14 | For Debian user, you can get the fping deb file from http://ftp.debian.org/debian/pool/main/f/fping/fping_4.0-1_armhf.deb 15 | 16 | For docker user, you can build the container with the Docker file (based on alpine around 54M). For arm user, you can change FROM to "armhf/alpine:edge" 17 | 18 | PS: The script is working fine with > 40 ping target in a PI 3B. 19 | 20 | ## Getting Started 21 | 22 | 1. Download ping-exporter.py and place it inside /opt/ 23 | ``` 24 | # cd /opt/ 25 | # curl -O https://raw.githubusercontent.com/frankiexyz/ping-exporter/master/ping-exporter.py 26 | ``` 27 | 28 | 2. Ensure Correct Permission on ping-exporter.py 29 | ``` 30 | # chmod 755 /opt/ping-exporter.py 31 | ``` 32 | 33 | 3. Running The Ping Exporter 34 | ``` 35 | # /usr/bin/python /opt/ping-exporter.py 36 | ``` 37 | 38 | 4. Testing The Script 39 | ``` 40 | # curl "127.0.0.1:8085/?target=8.8.8.8" 41 | ``` 42 | 43 | ### Running Script On System Startup 44 | 45 | **CentOS 7 (Using Systemd)** 46 | 47 | 1. Create a new ping_exporter.service file at /lib/systemd/system/ 48 | ``` 49 | vi /lib/systemd/system/ping_exporter.service 50 | ``` 51 | 52 | 2. Paste The Following into ping_exporter.service 53 | 54 | ``` 55 | [Unit] 56 | Description=Ping Exporter for Prometheus (Created By Frankie) 57 | After=multi-user.target 58 | 59 | [Service] 60 | Type=idle 61 | ExecStart=/usr/bin/python /opt/ping-exporter.py 62 | 63 | [Install] 64 | WantedBy=multi-user.target 65 | ``` 66 | 67 | 3. Save The File and Execute systemctl daemon-reload 68 | ``` 69 | # systemctl daemon-reload 70 | ``` 71 | 72 | 4. Start and Enable Ping Exporter Service 73 | ``` 74 | # systemctl start ping_exporter.service 75 | # systemctl enable ping_exporter.service 76 | ``` 77 | 78 | ## Configuration 79 | 80 | ### Prometheus Configuration 81 | 82 | Append the following in prometheus's config (Default is prometheus.yml) 83 | 84 | ``` 85 | - job_name: 'ping-exporter' 86 | scrape_interval: 60s 87 | metrics_path: /probe 88 | params: 89 | prot: ['4'] 90 | static_configs: 91 | - targets: 92 | - www.ifconfig.xyz 93 | - www.google.com 94 | relabel_configs: 95 | - source_labels: [__address__] 96 | target_label: __param_target 97 | replacement: ${1} 98 | - source_labels: [__param_target] 99 | regex: (.*) 100 | target_label: instance 101 | replacement: ${1} 102 | - source_labels: [] 103 | regex: .* 104 | target_label: __address__ 105 | replacement: :8085 106 | ``` 107 | 108 | You might want to add or change the following parameters in params's section to match your requirements 109 | 110 | ``` 111 | params: 112 | # Default Is IPv4, Can Be Changed To IPv6 113 | prot: ['4'] 114 | 115 | # Ping Packet Size (Default value is 56) 116 | size: ['56'] 117 | 118 | # Ping Count (Default value is 10 times) 119 | count: ['10'] 120 | 121 | # Ping Interval (Default value is 500ms) 122 | interval: ['500'] 123 | 124 | # Source address for ping (System default used if not specified) 125 | source: ['10.10.10.10'] 126 | ``` 127 | 128 | Prometheus configuration example where the job pings a single destination (params:target) but from multiple source addresses (static_config:targets) to determine the quality of each route / path: 129 | 130 | ``` 131 | - job_name: 'ping-exporter' 132 | scrape_interval: 60s 133 | metrics_path: /probe 134 | params: 135 | prot: ['4'] 136 | count: ['3'] 137 | target: ['207.225.112.9'] 138 | static_configs: 139 | - targets: 140 | - 192.168.101.2 141 | - 192.168.102.2 142 | - 192.168.103.2 143 | - 192.168.104.2 144 | relabel_configs: 145 | - source_labels: [__address__] 146 | target_label: __param_source 147 | replacement: ${1} 148 | - source_labels: [__param_target] 149 | regex: (.*) 150 | target_label: instance 151 | replacement: ${1} 152 | - source_labels: [__param_source] 153 | regex: (.*) 154 | target_label: source 155 | replacement: ${1} 156 | - source_labels: [] 157 | regex: .* 158 | target_label: __address__ 159 | replacement: localhost:8085 160 | ``` 161 | -------------------------------------------------------------------------------- /ping-exporter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 3 | from SocketServer import ThreadingMixIn 4 | import threading 5 | import sys 6 | import subprocess 7 | from urlparse import parse_qs, urlparse 8 | import logging 9 | import os 10 | 11 | def locate(file): 12 | #Find the path for fping 13 | for path in os.environ["PATH"].split(os.pathsep): 14 | if os.path.exists(os.path.join(path, file)): 15 | return os.path.join(path, file) 16 | return "{}".format(file) 17 | 18 | def ping(host, prot, interval, count, size, source): 19 | # Using source address? 20 | if source == '': 21 | ping_command = '{} -{} -b {} -i 1 -p {} -q -c {} {}'.format(filepath, prot, size, interval, count, host) 22 | else: 23 | ping_command = '{} -{} -b {} -i 1 -p {} -q -c {} -S {} {}'.format(filepath, prot, size, interval, count, source, host) 24 | 25 | output = [] 26 | #Log the actual ping command for debug purpose 27 | logger.info(ping_command) 28 | #Execute the ping 29 | cmd_output = subprocess.Popen(ping_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() 30 | #Parse the fping output 31 | try: 32 | loss = cmd_output[1].split("%")[1].split("/")[2] 33 | min = cmd_output[1].split("=")[2].split("/")[0] 34 | avg = cmd_output[1].split("=")[2].split("/")[1] 35 | max = cmd_output[1].split("=")[2].split("/")[2].split("\n")[0] 36 | except IndexError: 37 | loss = 100 38 | min = 0 39 | avg = 0 40 | max = 0 41 | #Prepare the metric 42 | output.append("ping_avg {}".format(avg)) 43 | output.append("ping_max {}".format(max)) 44 | output.append("ping_min {}".format(min)) 45 | output.append("ping_loss {}".format(loss)) 46 | output.append('') 47 | return output 48 | 49 | class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 50 | """Handle requests in a separate thread.""" 51 | 52 | class GetHandler(BaseHTTPRequestHandler): 53 | def do_GET(self): 54 | #Parse the url 55 | parsed_path = urlparse(self.path).query 56 | value = parse_qs(parsed_path) 57 | #Retrieve the ping target 58 | address = value['target'][0] 59 | #Retrieve source address 60 | if "source" in value: 61 | source = value['source'][0] 62 | else: 63 | source = '' 64 | #Retrieve prot 65 | if "prot" in value: 66 | prot = value['prot'][0] 67 | else: 68 | prot = 4 69 | #Retrieve ping count 70 | if "count" in value: 71 | count = value['count'][0] 72 | else: 73 | count = 10 74 | #Retrieve ping packet size 75 | if "size" in value and int(value['size'][0]) < 10240: 76 | size = value['size'][0] 77 | else: 78 | size = 56 79 | #Retrieve ping interval 80 | if "interval" in value and int(value['interval'][0]) > 1: 81 | interval = value['interval'][0] 82 | else: 83 | interval = 500 84 | 85 | message = '\n'.join(ping(address, prot, interval, count, size, source)) 86 | #Prepare HTTP status code 87 | self.send_response(200) 88 | self.end_headers() 89 | self.wfile.write(message) 90 | return 91 | 92 | if __name__ == '__main__': 93 | #Locate the path of fping 94 | global filepath 95 | filepath = locate("fping") 96 | logger = logging.getLogger() 97 | handler = logging.StreamHandler() 98 | formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') 99 | handler.setFormatter(formatter) 100 | logger.addHandler(handler) 101 | logger.setLevel(logging.DEBUG) 102 | #Check if there is a special port configured 103 | if len(sys.argv) >= 3: 104 | port = int(sys.argv[2]) 105 | else: 106 | port = 8085 107 | logger.info('Starting server port {}, use to stop'.format(port)) 108 | server = ThreadedHTTPServer(('0.0.0.0', port), GetHandler) 109 | server.serve_forever() 110 | -------------------------------------------------------------------------------- /ping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frankiexyz/ping-exporter/bc3ef923fbec509613777e138e55d210505b6323/ping.png --------------------------------------------------------------------------------