Advertisement
Guest User

Untitled

a guest
Jul 22nd, 2018
143
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.27 KB | None | 0 0
  1. #!/usr/bin/python3
  2.  
  3. import logging
  4. import logging.handlers
  5.  
  6. import os
  7. from subprocess import call
  8. from pathlib import Path
  9.  
  10. import csv
  11. from datetime import datetime, timedelta
  12. import time
  13. import dateutil.parser as dp
  14.  
  15. import pandas as pd
  16. import numpy as np
  17.  
  18. import matplotlib.pyplot as plt
  19. import matplotlib.cbook as cbook
  20. import matplotlib.dates as mdates
  21. from matplotlib.ticker import FuncFormatter
  22. from matplotlib import rcParams
  23.  
  24. rcParams["font.family"] = "sans-serif"
  25. rcParams["font.sans-serif"] = ["DejaVu Sans"]
  26. rcParams["font.weight"] = "medium"
  27. rcParams["font.size"] = 10.0
  28. rcParams["axes.linewidth"] = 0.4
  29. rcParams["legend.fontsize"] = 8.0
  30.  
  31. from scipy.interpolate import spline
  32. from scipy.interpolate import interp1d
  33. from scipy.interpolate import UnivariateSpline
  34.  
  35. from twitter import *
  36.  
  37. CSV_FILE = "/home/ubuntu/bndwdth.csv"
  38.  
  39. SPEED_MAX_DOWN = 367001600          # 350 Mbps
  40. SPEED_MIN_DOWN = SPEED_MAX_DOWN / 2 # 175 Mbps
  41. SPEED_MAX_UP   = 20971520           #  20 Mbps
  42. SPEED_MIN_UP   = SPEED_MAX_UP / 2   #  10 Mbps
  43.  
  44. DOWN_LINE_COLOUR  = "blue"
  45. UP_LINE_COLOUR    = "magenta"
  46. PING_LINE_COLOUR  = "orange"
  47. TTFB_LINE_COLOUR = "red"
  48.  
  49. my_logger = logging.getLogger('bndwdth-log')
  50. my_logger.setLevel(logging.DEBUG)
  51. my_logger.addHandler(logging.handlers.SysLogHandler(address = '/dev/log'))
  52.  
  53. def notice(s):
  54.     my_logger.debug(s)
  55.     print(s)
  56.  
  57. def mean(numbers):
  58.     return float(sum(numbers)) / max(len(numbers), 3)
  59.  
  60. #
  61. ## Get bandwidth in human readable form
  62. #
  63.  
  64. def ghr(b, u=True):
  65.     return get_human_readable(b, u)
  66.  
  67. def get_human_readable(bps, units=True):
  68.     K = 1024
  69.     M = K * K
  70.     G = M * K
  71.     Bps = bps / 8
  72.     if bps < K:
  73.         return "%d%s" % (bps, "bps" if units else "")
  74.     if bps < M:
  75.         return "%d%s" % ((bps / K), "Kbps" if units else "")
  76.     if bps < G:
  77.         return "%d%s" % ((bps / M), "Mbps" if units else "")
  78.     return "%d%s" % ((bps / G), "Gbps" if units else "")
  79.  
  80. def sufficiently_fast_up(speed):
  81.     return speed["up"] > SPEED_MIN_UP
  82.  
  83. def sufficiently_fast_down(speed):
  84.     return speed["down"] > SPEED_MIN_DOWN
  85.  
  86. def host_missing(speed):
  87.     return speed["up"] == 0 and speed["down"] == 0
  88.  
  89. def insufficiently_fast(speed):
  90.     return speed["up"] < SPEED_MIN_UP and speed["down"] < SPEED_MIN_DOWN
  91.  
  92. def sufficiently_fast(speed):
  93.     return sufficiently_fast_up(speed) and sufficiently_fast_down(speed)
  94.  
  95. def extra_fast(speed):
  96.     return speed["up"] > SPEED_MAX_UP and speed["down"] > SPEED_MAX_DOWN
  97.  
  98. #
  99. ## Generate a graph of the previous 24 hours bandwidth
  100. #
  101.  
  102. def parse_bandwidth(duration=24):
  103.     date = []
  104.     down = []
  105.     up = []
  106.     ping = []
  107.     ttfb = []
  108.     with open(CSV_FILE, "r") as c:
  109.         reader = csv.reader(c)
  110.         now = datetime.now()
  111.         for row in reader:
  112.             d = dp.parse(row[0])
  113.             if now - d < timedelta(hours=duration):
  114.                 date.append(d)
  115.                 if len(row) > 1:
  116.                     down.append(int(row[1]))
  117.                     up.append(int(row[2]))
  118.                     if len(row) > 3:
  119.                         ping.append(float(row[3]))
  120.                         if len(row) > 4:
  121.                             ttfb.append(float(row[4]))
  122.                         else:
  123.                             ttfb.append(0)
  124.                     else:
  125.                         ping.append(0)
  126.                         ttfb.append(0)
  127.                 else:
  128.                     down.append(0)
  129.                     up.append(0)
  130.                     ping.append(0)
  131.                     ttfb.append(0)
  132.  
  133.     return { "date": date, "down": down, "up": up, "ping": ping, "ttfb": ttfb }
  134.  
  135. def average_bandwidth(data, avg=2):
  136.     date = []
  137.     down = []
  138.     up = []
  139.     ping = []
  140.     ttfb = []
  141.     for d in range(int(len(data["date"]) / avg)):
  142.         t = d * avg
  143.         date.append(data["date"][t])
  144.  
  145.         ad = 0
  146.         for i in range(avg):
  147.             ad = ad + data["down"][t + i]
  148.         ad = ad / avg
  149.         down.append(ad)
  150.  
  151.         au = 0
  152.         for i in range(avg):
  153.             au = au + data["up"][t + i]
  154.         au = au / avg
  155.         up.append(au)
  156.  
  157.         ap = 0
  158.         for i in range(avg):
  159.             ap = ap + data["ping"][t + i]
  160.         ap = ap / avg
  161.         ping.append(ap)
  162.  
  163.         ac = 0
  164.         for i in range(avg):
  165.             ac = ac + data["ttfb"][t + i]
  166.         ac = ac / avg
  167.         ttfb.append(ac)
  168.  
  169.     return { "date": date, "down": down, "up": up, "ping": ping, "ttfb": ttfb }
  170.  
  171. def generate_graph(data, scale="log", xkcd=False):
  172.     if xkcd:
  173.         plt.xkcd()
  174.     fig, speed_plot = plt.subplots()
  175.     duration_plot = speed_plot.twinx()
  176.  
  177.     time = []
  178.     timestamp = []
  179.     down = []
  180.     up = []
  181.     ping = []
  182.     ttfb = []
  183.  
  184.     data_points = len(data["date"])
  185.  
  186.     for i in range(data_points):
  187.         time.append(data["date"][i])
  188.         timestamp.append(float(data["date"][i].timestamp()))
  189.         down.append(float(data["down"][i]) / 1000000)
  190.         up.append(float(data["up"][i]) / 1000000)
  191.         ping.append(float(data["ping"][i]))
  192.         ttfb.append(float(data["ttfb"][i]))
  193.  
  194.     now = datetime.now()
  195.     plt.suptitle("Bandwidth [" + now.strftime("%Y-%m-%d %-I%p") + "]")
  196.     speed_plot.set_xlabel("Date / Time")
  197.     speed_plot.set_ylabel("Bandwidth (Mbps)")
  198.     duration_plot.set_ylabel("Delay (ms)")
  199.  
  200.     # plot the original data points
  201.     #speed_plot.plot(time, down, DOWN_LINE_COLOUR, linewidth=0, marker=".")
  202.     #speed_plot.plot(time, up, UP_LINE_COLOUR, linewidth=0, marker=".")
  203.     #duration_plot.plot(time, ping, PING_LINE_COLOUR, linewidth=0, marker=".")
  204.     #duration_plot.plot(time, ttfb, TTFB_LINE_COLOUR, linewidth=0, marker=".")
  205.  
  206.     # calculate (and draw) a pretty looking line
  207.     timestamp_min = np.array(timestamp).min()
  208.     timestamp_max = np.array(timestamp).max()
  209.  
  210.     time_continuous = np.linspace(timestamp_min, timestamp_max, (timestamp_max - timestamp_min) / 60)
  211.     down_continuous = interp1d(timestamp, down, kind="cubic")(time_continuous)
  212.     up_continuous = interp1d(timestamp, up, kind="cubic")(time_continuous)
  213.     ping_continuous = interp1d(timestamp, ping, kind="cubic")(time_continuous)
  214.     #ttfb_continuous_i = interp1d(timestamp, ttfb, kind="cubic")(time_continuous)
  215.  
  216.     ttfb_weight = []
  217.     tm = max(ttfb)
  218.     for i in range(len(ttfb)):
  219.         ttfb_weight.append(2.75 / ttfb[i])
  220.     ttfb_continuous = UnivariateSpline(timestamp, ttfb, w=ttfb_weight, k=3)(time_continuous)
  221.  
  222.     time_extra = []
  223.     down_expected = []
  224.     up_expected = []
  225.     for i in time_continuous:
  226.         time_extra.append(datetime.fromtimestamp(i))
  227.         down_expected.append(float(ghr(SPEED_MAX_DOWN, False)))
  228.         up_expected.append(float(ghr(SPEED_MAX_UP, False)))
  229.  
  230.     for i in range(len(ping_continuous)):
  231.         if ping_continuous[i] < 1:
  232.             ping_continuous[i] = np.NaN
  233.  
  234.     for i in range(len(ttfb_continuous)):
  235.         #print(datetime.fromtimestamp(time_continuous[i]).strftime('%Y-%m-%d %H:%M:%S') + "," + str(ttfb_continuous[i]))
  236.         if ttfb_continuous[i] < 1:
  237.             ttfb_continuous[i] = np.NaN
  238.  
  239.     ln = speed_plot.plot(time_extra, down_continuous, DOWN_LINE_COLOUR, linewidth=1, label="Down")
  240.     ln = ln + speed_plot.plot(time_extra, down_expected, DOWN_LINE_COLOUR, linewidth=1, linestyle="dashed", label="Expected: 350Mbps")
  241.     ln = ln + speed_plot.plot(time_extra, up_continuous, UP_LINE_COLOUR, linewidth=1, label="Up")
  242.     ln = ln + speed_plot.plot(time_extra, up_expected, UP_LINE_COLOUR, linewidth=1, linestyle="dashed", label="Expected: 20Mbps")
  243.  
  244.     ln = ln + duration_plot.plot(time_extra, ttfb_continuous, TTFB_LINE_COLOUR, linewidth=1, label="TTFB")
  245.     ln = ln + duration_plot.plot(time_extra, ping_continuous, PING_LINE_COLOUR, linewidth=1, label="Ping")
  246.  
  247.     # finish up
  248.     plt.legend(ln, [l.get_label() for l in ln], loc= "upper center", ncol=4, bbox_to_anchor=(0.5, 1.06), framealpha=1).get_frame().set_linewidth(0.4)
  249.  
  250.     speed_plot.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d %H:%M"))
  251.  
  252.     plt.xlim([time_extra[0], datetime.now()])
  253.  
  254.     speed_plot.set_yscale(scale)
  255.     speed_plot.set_ylim(1, 1000)
  256.     speed_plot.yaxis.set_major_formatter(FuncFormatter(lambda y, p: format(int(y), ',')))
  257.  
  258.     duration_plot.set_yscale(scale)
  259.     duration_plot.set_ylim(1, max(data["ping"] + data["ttfb"]) * 1.5)
  260.     duration_plot.yaxis.set_major_formatter(FuncFormatter(lambda y, p: format(int(y), ',')))
  261.     fig.autofmt_xdate()
  262.     fig.set_size_inches(10, 8)
  263.  
  264.     out = "%s-bw-%s.png" % (datetime.now().strftime("%Y%m%d%H%M"), scale)
  265.     plt.savefig(out, dpi=320)
  266.     #plt.show()
  267.     return out
  268.  
  269. #
  270. ## Send the latest results to Twitter
  271. #
  272.  
  273. def get_twitter(up=False):
  274.     # token, token_secret, consumer_key, consumer_secret
  275.     oa = OAuth("", "", "", "")
  276.     if up:
  277.         return Twitter(domain="upload.twitter.com", auth=oa)
  278.     else:
  279.         return Twitter(auth=oa)
  280.  
  281. def alert_network_down():
  282.     t = get_twitter()
  283.     s = "@virginmedia it looks like there might be network trouble in #Watford! My #awscloud EC2 instance has lost its connection with my home PC."
  284.     t.statuses.update(status=s);
  285.     call(["/home/ubuntu/bin/bndwdth-notify", "down"])
  286.  
  287. def send_tweet(speed, graphs):
  288.     # upload graph to twitter
  289.     img_ids = []
  290.     for graph in graphs:
  291.         img = None
  292.         with open(graph, "rb") as i:
  293.             img = i.read()
  294.         t = get_twitter(True)
  295.         img_ids.append(t.media.upload(media=img)["media_id_string"])
  296.  
  297.     # send tweet
  298.     t = get_twitter()
  299.     s = "Average #bandwidth in #Watford for the last hour was %s down and %s up." % (ghr(speed["down"]), ghr(speed["up"]))
  300.     if insufficiently_fast(speed):
  301.         s = "@virginmedia Why's my hourly average #bandwidth in #Watford only %s down / %s up? Not even close to %s/%s!" % (ghr(speed["down"]), ghr(speed["up"]), ghr(SPEED_MAX_DOWN, False), ghr(SPEED_MAX_UP, False))
  302.         #call(["/home/ubuntu/bin/bndwdth-notify", "slow"])
  303.     elif not sufficiently_fast_down(speed):
  304.         s = "@virginmedia Why's my hourly average download #bandwidth in #Watford only %s? Nowhere close to %s! (%s up)" % (ghr(speed["down"]), ghr(SPEED_MAX_DOWN, False), ghr(speed["up"]))
  305.         #call(["/home/ubuntu/bin/bndwdth-notify", "slow/down"])
  306.     elif not sufficiently_fast_up(speed):
  307.         s = "@virginmedia Why's my hourly average upload #bandwidth in #Watford only %s? Not even close to %s! (%s down)" % (ghr(speed["up"]), ghr(SPEED_MAX_UP, False), ghr(speed["down"]))
  308.         #call(["/home/ubuntu/bin/bndwdth-notify", "slow/up"])
  309.     elif extra_fast(speed):
  310.         s = "@virginmedia Excellent: my broadband #bandwidth in #Watford is %s down and %s up! #VIVID350 #superfast" % (ghr(speed["down"]), ghr(speed["up"]))
  311.         call(["/home/ubuntu/bin/bndwdth-notify", "fast"])
  312.     t.statuses.update(status=s, media_ids=",".join(img_ids));
  313.     notice(s)
  314.  
  315. if __name__ == "__main__":
  316.     s = parse_bandwidth(duration=1)
  317.     s = average_bandwidth(s, len(s["date"]))
  318.     s["up"] = s["up"][0]
  319.     s["down"] = s["down"][0]
  320.  
  321.     alert = Path("bndwdth-alert")
  322.     if int(s["up"]) > 0 and int(s["down"]) > 0:
  323.         if alert.is_file():
  324.             alert.unlink()
  325.         d = parse_bandwidth()
  326.         d = average_bandwidth(d)
  327.         g = []
  328.         g.append(generate_graph(d, scale="log"))
  329.         send_tweet(s, g)
  330.     else:
  331.         if not alert.is_file():
  332.             alert.open("a")
  333.             alert_network_down()
  334.  
  335.     for f in g:
  336.         os.remove(f)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement