19 Commits

Author SHA1 Message Date
user901423
68ddf9d239 Fixing logic and regex bugs in Mac 2017-03-13 04:19:24 -07:00
a36bbeadaa Add TODO 2017-03-13 04:13:29 -07:00
af5dfb34e0 Merge branch 'master' of github.com:zyphlar/pinger 2017-03-13 04:13:21 -07:00
155b25cb4a Improving regex and output detail 2017-03-13 04:09:56 -07:00
user901423
fafe321788 Mac compatibility achieved 2017-03-08 02:09:18 -08:00
9f20431887 Implementing platform-agnostic packet loss and ssid detection 2017-03-08 01:50:06 -08:00
user901423
dee07c0e92 Adding basic mac-compatible 2017-03-08 00:09:16 -08:00
3c2b39dafc Adding copyright 2016-01-13 15:56:36 -07:00
10bdd5aef2 Handling null router cases 2015-07-25 14:26:06 -07:00
4c62692936 Readme fix 2015-07-21 18:57:00 -07:00
fbfe9e0afe Updating default gateway if there are failures 2015-07-21 18:53:11 -07:00
610c61f9d1 Fixing automatic router detection 2015-05-19 10:18:10 -07:00
b8f6422323 Updating screenshot 2015-05-17 22:22:57 -07:00
2858f01f0e Don't hide bottom pixel 2015-05-17 21:21:06 -07:00
53ac0233ca Detect gateway dynamically 2015-05-17 17:57:25 -07:00
73fdf47d35 Adding second graph for LAN 2015-05-17 16:51:02 -07:00
1e81f2ab42 Add screenshot 2015-04-06 20:37:52 -07:00
b7e6a0c958 Let's use 8.8.8.8 as default instead of google.com 2014-11-13 15:52:54 -07:00
dac924b10e Changing 4.2.2.2 default target to google.com target 2014-11-13 15:44:07 -07:00
6 changed files with 284 additions and 38 deletions

15
COPYING.txt Normal file
View File

@@ -0,0 +1,15 @@
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

View File

@@ -1,3 +1,5 @@
![Screenshot](https://github.com/zyphlar/pinger/raw/master/pinger.png)
Pinger.py
=========
A ping tool that sits in your system tray
@@ -19,6 +21,6 @@ Open the Terminal program and enter the following commands:
git clone https://github.com/zyphlar/pinger.git
python pinger/pinger.py &
Pinger should open in your system tray (It just looks like "XX.X ms"). To set Pinger to start automatically (in Ubuntu) click it and choose Start Automatically.
Pinger should open in your system tray. To set Pinger to start automatically (in Ubuntu) click it and choose Start Automatically.
Report bugs or feature requests at https://github.com/zyphlar/pinger/issues

5
TODO.md Normal file
View File

@@ -0,0 +1,5 @@
- local caching
- remote submission
- centralized reporting
- packaging/distribution
- cross-platform gui

146
ping.inc.py Executable file
View File

@@ -0,0 +1,146 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Pinger.py -- A ping tool that sits in your system tray
# Copyright 2013 Will Bradley
#
# Contributors: Will Bradley <bradley.will@gmail.com>
# AltF4 <altf4@phx2600.org>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of either or both of the following licenses:
#
# 1) the GNU Lesser General Public License version 3, as published by the
# Free Software Foundation; and/or
# 2) the GNU Lesser General Public License version 2.1, as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the applicable version of the GNU Lesser General Public
# License for more details.
#
# You should have received a copy of both the GNU Lesser General Public
# License version 3 and version 2.1 along with this program. If not, see
# <http://www.gnu.org/licenses/>
#
#
# Dependencies
#
# System Tray Icon
# Timer
# Date
import datetime
# Pinging
import subprocess
# Regex
import re
# Ctrl-c
import signal
# File paths
import os
# OS naming
import platform
# Argument parsing
import argparse
# For exit
import sys
# For graphing
# For IP addresses
import socket, struct
# For API
import json
#
# Main Class
#
class Pinger:
def ping(self, target, log=None, widget=None, data=None):
pingResult = Util.doPing(target)
ssidResult = Util.getSsid()
print json.dumps({
'target': target,
'ssid': ssidResult,
'loss': float(pingResult['loss']),
'rtt_avg': float(pingResult['rtt_avg']),
})
def __init__(self):
# Print welcome message
print "Starting Pinger..."
self.ping("4.2.2.2")
# Print started message
print "Finished."
#
# Utility class for platform-agnosticism and reusability
#
class Util:
@staticmethod
def getSsid():
if platform.system() == "Linux":
ssid = subprocess.Popen(["nmcli","-t","-f","active,ssid","dev","wifi"],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
) # | egrep '^yes(.*)' | cut -d\' -f2
out, error = ssid.communicate()
m = re.search(r'yes:(.*)', out)
result = m.group(1)
elif platform.system() == "Darwin":
ssid = subprocess.Popen(["/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport","-I"],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
out, error = ssid.communicate()
result = out
return result
@staticmethod
def doPing(target):
if platform.system() == "Linux":
ping = subprocess.Popen(
["ping", "-c", "5", target],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
pingOut, pingError = ping.communicate()
# Doing two regexes because Python doesn't like doing both in one
lossResult = re.search(r' ([\d\.]+)% packet loss', pingOut, re.MULTILINE)
rttAvgResult = re.search(r' = [\d\.]+/([\d\.]+)/[\d\.]+/[\d\.]+', pingOut, re.MULTILINE)
if pingError or lossResult is None or rttAvgResult is None:
output = {'loss':-1, 'rtt_avg':-1};
else:
output = {'loss':lossResult.group(1), 'rtt_avg':rttAvgResult.group(1)};
elif platform.system() == "Darwin": # Macintosh
ping = subprocess.Popen(
["ping", "-c", "5", target],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
pingOut, pingError = ping.communicate()
# print out
lossResult = re.search(r' ([\d\.]+)% packet loss', pingOut, re.MULTILINE)
rttAvgResult = re.search(r' = [\d\.]+/([\d\.]+)/[\d\.]+/[\d\.]+', pingOut, re.MULTILINE)
if pingError or lossResult is None or rttAvgResult is None:
output = {'loss':-1, 'rtt_avg':-1};
else:
output = {'loss':lossResult.group(1), 'rtt_avg':rttAvgResult.group(1)};
return output
#
# Runtime
#
if __name__ == "__main__":
pinger = Pinger()

BIN
pinger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

152
pinger.py
View File

@@ -35,6 +35,7 @@ from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
# Timer
from gi.repository import GObject as gobject
import datetime
# Pinging
import subprocess
# Regex
@@ -49,6 +50,8 @@ import argparse
import sys
# For graphing
import cairo
# For IP addresses
import socket, struct
# Vars
startup_active_label = "✓ Start Automatically"
@@ -60,7 +63,7 @@ startup_path = home_path+'/.config/autostart/pinger.desktop'
startup_dir = home_path+'/.config/autostart/'
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", help="Target to PING against. (IP / Hostname / Domain name). Defaults to 4.2.2.2")
parser.add_argument("-t", "--target", help="Target to PING against. (IP / Hostname / Domain name). Defaults to 8.8.8.8")
parser.add_argument("-f", "--freq", help="Timeout between pings, in seconds. Defaults to 5")
parser.add_argument("-m", "--maxlog", help="Maximum amount of pings to log. Defaults to 40")
parser.add_argument("-c", "--color", help="Color scheme ('dark' or 'light'). Defaults to dark.")
@@ -69,7 +72,9 @@ args = parser.parse_args()
ubuntu_mono_dark_rgba = [0xdf, 0xd8, 0xc8, 0xff]
ubuntu_mono_light_rgba = [0x3a, 0x39, 0x35, 0xff]
black = [0, 0, 0, 0xff]
red = [0xff, 0, 0, 0xff]
red = [0xdf, 0x38, 0x2c, 0xff]
orange = [0xdd, 0x48, 0x14, 0xff]
yellow = [0xef, 0xb7, 0x3e, 0xff]
white = [0xff, 0xff, 0xff, 0xff]
dark_bg = [0, 0, 0, 0x3f]
light_bg = [0xff, 0xff, 0xff, 0x3f]
@@ -81,10 +86,12 @@ for arg in sys.argv[1:]:
# User-editable variables
if args.target:
host = args.target
default_host = args.target
else:
host = "4.2.2.2" # IP or hostname
print "Using default target IP of 4.2.2.2"
print "Using default Internet target of 8.8.8.8"
default_host = "8.8.8.8" # IP or hostname of WAN
default_router = "192.168.1.1" # IP or hostname of router
if args.freq:
try:
@@ -106,10 +113,14 @@ else:
if args.color == "light":
graph_color = ubuntu_mono_light_rgba
danger_color = red
warning_color = yellow
graph_highlight = ubuntu_mono_dark_rgba
graph_background = light_bg
else:
graph_color = ubuntu_mono_dark_rgba
danger_color = red
warning_color = yellow
graph_highlight = ubuntu_mono_light_rgba
graph_background = dark_bg
@@ -118,15 +129,19 @@ else:
#
class Pinger:
ping_log = []
host = None
router = None
host_log = []
router_log = []
paused = False
autostart = False
icon_height = 22
def ping(self, widget=None, data=None):
def ping(self, target, log, widget=None, data=None):
if not self.paused:
#print "Pinging "+str(target)
ping = subprocess.Popen(
["ping", "-c", "1", host],
["ping", "-c", "1", target],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
@@ -134,21 +149,44 @@ class Pinger:
m = re.search('time=(.*) ms', out)
if error or m == None:
label = "PING FAIL"
self.log_ping(-1)
self.log_ping(log, -1)
else:
latency = "%.2f" % float(m.group(1))
label = latency+" ms"
self.log_ping(latency)
#self.ind.set_label(label, "100.0 ms")
gobject.timeout_add_seconds(self.timeout, self.ping)
self.log_ping(log, latency)
def log_ping(self, value):
self.ping_log.append(float(value))
#self.ind.set_label(label, "100.0 ms")
def ping_both(self):
self.ping(self.host, self.host_log)
self.ping(self.router, self.router_log)
self.update_log_menu()
# If we have 5 router failures, try getting the default GW again
if (self.routerLastUpdated != None
and (datetime.datetime.now()-self.routerLastUpdated).seconds > 60):
if (len(self.router_log) > 5
and self.router_log[-1] == -1
and self.router_log[-2] == -1
and self.router_log[-3] == -1):
new_router = self.get_default_gateway_linux()
# Only update router if it's not blank
if new_router != None:
self.router = new_router
print "Updated router target to "+str(self.router)
self.routerLastUpdated = datetime.datetime.now()
gobject.timeout_add_seconds(self.timeout, self.ping_both)
def log_ping(self, log, value):
log.append(float(value))
# limit the size of the log
if len(self.ping_log) >= ping_log_max_size:
if len(log) >= ping_log_max_size:
# remove the earliest ping, not the latest
self.ping_log.pop(0)
log.pop(0)
def create_menu_item(self, text, callback):
menu_item = Gtk.MenuItem(text)
@@ -195,24 +233,14 @@ class Pinger:
# draw semitransparent box
self.draw_rect( ctx, [0,0], [ping_log_max_size,self.icon_height], graph_background )
if(max(self.ping_log) < 100):
max_ping = 100
else:
max_ping = max(self.ping_log)
for index, ping in enumerate(self.ping_log):
if float(ping) == -1: # Ping error
# Draw full-height error bar
self.draw_rect( ctx, [index,self.icon_height], [1,-self.icon_height-1], red )
else:
# draw normal bar
bar_height = -int(self.scale(ping, (0,max_ping), (0,self.icon_height)))
if bar_height > -1:
bar_height = -1
self.draw_rect( ctx, [index,self.icon_height], [1,bar_height], graph_color )
if len(self.host_log) > 0:
self.draw_log(ctx, self.host_log, 0)
host_avg = sum(self.host_log)/len(self.host_log)
self.ping_menu.set_label("Internet: "+str(int(round(self.host_log[-1])))+" ms "+str(int(round(host_avg)))+" avg")
if len(self.router_log) > 0:
self.draw_log(ctx, self.router_log, (self.icon_height/2))
router_avg = sum(self.router_log)/len(self.router_log)
self.router_menu.set_label("Router: "+str(int(round(self.router_log[-1])))+" ms "+str(int(round(router_avg)))+" avg")
try:
os.remove("/tmp/graph.png")
@@ -221,8 +249,37 @@ class Pinger:
surface.write_to_png("/tmp/graph.png")
self.ind.set_icon("") # gotta set it to nothing in order to update
self.ind.set_icon("graph")
self.ping_menu.set_label("Ping: "+str(self.ping_log[-1])+" ms")
def draw_log(self, ctx, log, yOffset):
if(max(log) < 100):
max_ping = 100
elif(max(log) > 1000):
max_ping = 1000
else:
max_ping = max(log)
for index, ping in enumerate(log):
if float(ping) == -1: # Ping error
# Draw full-height error bar
self.draw_rect( ctx, [index,(self.icon_height/2)+yOffset], [1,(-self.icon_height/2)-1], danger_color )
else:
# draw normal bar
bar_height = -int(self.scale(ping, (0,max_ping), (0,(self.icon_height/2)-1)))
if bar_height > -1:
bar_height = -1
if ping > 100:
color = warning_color
else:
color = graph_color
self.draw_rect( ctx, [index,self.icon_height/2+yOffset], [1,bar_height], color )
def draw_rect(self, ctx, point, size, rgba):
ctx.rectangle( point[0], point[1], size[0], size[1] )
ctx.set_source_rgba(rgba[0]/float(255), rgba[1]/float(255), rgba[2]/float(255), rgba[3]/float(255))
@@ -235,6 +292,16 @@ class Pinger:
scale = ((val - src[0]) / (src[1]-src[0])) * (dst[1]-dst[0]) + dst[0]
return scale
def get_default_gateway_linux(self):
# Read the default gateway directly from /proc.
with open("/proc/net/route") as fh:
for line in fh:
fields = line.strip().split()
if fields[1] != '00000000' or not int(fields[3], 16) & 2:
continue
return str(socket.inet_ntoa(struct.pack("<L", int(fields[2], 16))))
def __init__(self):
# Handle ctrl-c
signal.signal(signal.SIGINT, self.destroy)
@@ -255,6 +322,7 @@ class Pinger:
self.menu = Gtk.Menu()
# with ping numbers
self.ping_menu = self.create_menu_item("", None)
self.router_menu = self.create_menu_item("", None)
# with pause option
self.pause_menu = self.create_menu_item(pause_label, self.toggle_pause)
# with autostart option
@@ -269,10 +337,20 @@ class Pinger:
self.create_menu_item("Exit", self.destroy)
self.ind.set_menu(self.menu)
# load host/router vars
self.host = default_host
# set router / gateway dynamically
self.router = self.get_default_gateway_linux()
if self.router == None:
self.router = default_router
print "Set router target to "+str(self.router)
self.routerLastUpdated = datetime.datetime.now()
# start the ping process
self.counter = 0
self.timeout = ping_frequency
self.ping()
self.ping_both()
# Print started message
print "Started."