Repository: james-atkinson/speedcomplainer Branch: master Commit: 0692520772c7 Files: 5 Total size: 10.0 KB Directory structure: gitextract_0i8yc2am/ ├── .gitignore ├── README.md ├── config.json ├── logger.py └── speedcomplainer.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea ================================================ FILE: README.md ================================================ # speedcomplainer A python app that will test your internet connection and then complain to your service provider (and log to a data store if you'd like) ## Configuration Configuration is handled by a basic JSON file. Things that can be configured are: * twitter * twitterToken: This is your app access token * twitterConsumerKey: This is your Consumer Key (API Key) * twitterTokenSecret: This is your Access Token Secret * TwitterConsumerSecret: This is your Consumer Secret (API Secret) * tweetTo: This is a account (or list of accounts) that will be @ mentioned (include the @!) * internetSpeed: This is the speed (in MB/sec) you're paying for (and presumably not getting). * tweetThresholds: This is a list of messages that will be tweeted when you hit a threshold of crappiness. Placeholders are: * {tweetTo} - The above tweetTo configuration. * {internetSpeed} - The above internetSpeed configuration. * {downloadResult} - The poor download speed you're getting Threshold Example (remember to limit your messages to 140 characters or less!): ``` "tweetThresholds": { "5": [ "Hey {tweetTo} I'm paying for {internetSpeed}Mb/s but getting only {downloadResult} Mb/s?!? Shame.", "Oi! {tweetTo} $100+/month for {internetSpeed}Mbit/s and I only get {downloadResult} Mbit/s? How does that seem fair?" ], "12.5": [ "Uhh {tweetTo} for $100+/month I expect better than {downloadResult}Mbit/s when I'm paying for {internetSpeed}Mbit/s. Fix your network!", "Hey {tweetTo} why am I only getting {downloadResult}Mb/s when I pay for {internetSpeed}Mb/s? $100+/month for this??" ], "25": [ "Well {tweetTo} I guess {downloadResult}Mb/s is better than nothing, still not worth $100/mnth when I expect {internetSpeed}Mb/s" ] } ``` Logging can be done to CSV files, with a log file for ping results and speed test results. CSV Logging config example: ``` "log": { "type": "csv", "files": { "ping": "pingresults.csv", "speed": "speedrestuls.csv" } } ``` ## Usage > python speedcomplainer.py Or to run in the background: > python speedcomplainer.py > /dev/null & ================================================ FILE: config.json ================================================ { "twitter": { "twitterToken": "", "twitterConsumerKey": "", "twitterTokenSecret": "", "twitterConsumerSecret": "" }, "tweetTo": "", "internetSpeed": "50", "tweetThresholds": { "5": [ "Hey {tweetTo} I'm paying for {internetSpeed}Mb/s but getting only {downloadResult} Mb/s?!? Shame.", "Oi! {tweetTo} $100+/month for {internetSpeed}Mbit/s and I only get {downloadResult} Mbit/s? How does that seem fair?" ], "12.5": [ "Uhh {tweetTo} for $100+/month I expect better than {downloadResult}Mbit/s when I'm paying for {internetSpeed}Mbit/s. Fix your network!", "Hey {tweetTo} why am I only getting {downloadResult}Mb/s when I pay for {internetSpeed}Mb/s? $100+/month for this??" ], "25": [ "Well {tweetTo} I guess {downloadResult}Mb/s is better than nothing, still not worth $100/mnth when I expect {internetSpeed}Mb/s" ] }, "log": { "type": "csv", "files": { "ping": "pingresults.csv", "speed": "speedrestuls.csv" } } } ================================================ FILE: logger.py ================================================ class Logger(object): def __init__(self, type, config): if type == 'csv': self.logger = CsvLogger(config['filename']) def log(self, logItems): self.logger.log(logItems) class CsvLogger(object): def __init__(self, filename): self.filename = filename def log(self, logItems): with open(self.filename, "a") as logfile: logfile.write("%s\n" % ','.join(logItems)) ================================================ FILE: speedcomplainer.py ================================================ import os import sys import time from datetime import datetime import daemon import signal import threading import twitter import json import random from logger import Logger shutdownFlag = False def main(filename, argv): print "======================================" print " Starting Speed Complainer! " print " Lets get noisy! " print "======================================" global shutdownFlag signal.signal(signal.SIGINT, shutdownHandler) monitor = Monitor() while not shutdownFlag: try: monitor.run() for i in range(0, 5): if shutdownFlag: break time.sleep(1) except Exception as e: print 'Error: %s' % e sys.exit(1) sys.exit() def shutdownHandler(signo, stack_frame): global shutdownFlag print 'Got shutdown signal (%s: %s).' % (signo, stack_frame) shutdownFlag = True class Monitor(): def __init__(self): self.lastPingCheck = None self.lastSpeedTest = None def run(self): if not self.lastPingCheck or (datetime.now() - self.lastPingCheck).total_seconds() >= 60: self.runPingTest() self.lastPingCheck = datetime.now() if not self.lastSpeedTest or (datetime.now() - self.lastSpeedTest).total_seconds() >= 3600: self.runSpeedTest() self.lastSpeedTest = datetime.now() def runPingTest(self): pingThread = PingTest() pingThread.start() def runSpeedTest(self): speedThread = SpeedTest() speedThread.start() class PingTest(threading.Thread): def __init__(self, numPings=3, pingTimeout=2, maxWaitTime=6): super(PingTest, self).__init__() self.numPings = numPings self.pingTimeout = pingTimeout self.maxWaitTime = maxWaitTime self.config = json.load(open('./config.json')) self.logger = Logger(self.config['log']['type'], { 'filename': self.config['log']['files']['ping'] }) def run(self): pingResults = self.doPingTest() self.logPingResults(pingResults) def doPingTest(self): response = os.system("ping -c %s -W %s -w %s 8.8.8.8 > /dev/null 2>&1" % (self.numPings, (self.pingTimeout * 1000), self.maxWaitTime)) success = 0 if response == 0: success = 1 return { 'date': datetime.now(), 'success': success } def logPingResults(self, pingResults): self.logger.log([ pingResults['date'].strftime('%Y-%m-%d %H:%M:%S'), str(pingResults['success'])]) class SpeedTest(threading.Thread): def __init__(self): super(SpeedTest, self).__init__() self.config = json.load(open('./config.json')) self.logger = Logger(self.config['log']['type'], { 'filename': self.config['log']['files']['speed'] }) def run(self): speedTestResults = self.doSpeedTest() self.logSpeedTestResults(speedTestResults) self.tweetResults(speedTestResults) def doSpeedTest(self): # run a speed test result = os.popen("/usr/local/bin/speedtest-cli --simple").read() if 'Cannot' in result: return { 'date': datetime.now(), 'uploadResult': 0, 'downloadResult': 0, 'ping': 0 } # Result: # Ping: 529.084 ms # Download: 0.52 Mbit/s # Upload: 1.79 Mbit/s resultSet = result.split('\n') pingResult = resultSet[0] downloadResult = resultSet[1] uploadResult = resultSet[2] pingResult = float(pingResult.replace('Ping: ', '').replace(' ms', '')) downloadResult = float(downloadResult.replace('Download: ', '').replace(' Mbit/s', '')) uploadResult = float(uploadResult.replace('Upload: ', '').replace(' Mbit/s', '')) return { 'date': datetime.now(), 'uploadResult': uploadResult, 'downloadResult': downloadResult, 'ping': pingResult } def logSpeedTestResults(self, speedTestResults): self.logger.log([ speedTestResults['date'].strftime('%Y-%m-%d %H:%M:%S'), str(speedTestResults['uploadResult']), str(speedTestResults['downloadResult']), str(speedTestResults['ping']) ]) def tweetResults(self, speedTestResults): thresholdMessages = self.config['tweetThresholds'] message = None for (threshold, messages) in thresholdMessages.items(): threshold = float(threshold) if speedTestResults['downloadResult'] < threshold: message = messages[random.randint(0, len(messages) - 1)].replace('{tweetTo}', self.config['tweetTo']).replace('{internetSpeed}', self.config['internetSpeed']).replace('{downloadResult}', str(speedTestResults['downloadResult'])) if message: api = twitter.Api(consumer_key=self.config['twitter']['twitterConsumerKey'], consumer_secret=self.config['twitter']['twitterConsumerSecret'], access_token_key=self.config['twitter']['twitterToken'], access_token_secret=self.config['twitter']['twitterTokenSecret']) if api: status = api.PostUpdate(message) class DaemonApp(): def __init__(self, pidFilePath, stdout_path='/dev/null', stderr_path='/dev/null'): self.stdin_path = '/dev/null' self.stdout_path = stdout_path self.stderr_path = stderr_path self.pidfile_path = pidFilePath self.pidfile_timeout = 1 def run(self): main(__file__, sys.argv[1:]) if __name__ == '__main__': main(__file__, sys.argv[1:]) workingDirectory = os.path.basename(os.path.realpath(__file__)) stdout_path = '/dev/null' stderr_path = '/dev/null' fileName, fileExt = os.path.split(os.path.realpath(__file__)) pidFilePath = os.path.join(workingDirectory, os.path.basename(fileName) + '.pid') from daemon import runner dRunner = runner.DaemonRunner(DaemonApp(pidFilePath, stdout_path, stderr_path)) dRunner.daemon_context.working_directory = workingDirectory dRunner.daemon_context.umask = 0o002 dRunner.daemon_context.signal_map = { signal.SIGTERM: 'terminate', signal.SIGUP: 'terminate' } dRunner.do_action()