Guest User

Untitled

a guest
May 10th, 2023
31,429
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.87 KB | Source Code | 0 0
  1. #!/usr/bin/python3
  2.  
  3. import logging
  4. import logging.handlers
  5.  
  6. import os
  7. from pathlib import Path
  8.  
  9. import csv
  10. from datetime import datetime, timedelta
  11. import time
  12. import dateutil.parser as dp
  13.  
  14. import pandas as pd
  15. import numpy as np
  16.  
  17. import matplotlib.pyplot as plt
  18. import matplotlib.cbook as cbook
  19. import matplotlib.dates as mdates
  20. from matplotlib.ticker import FuncFormatter
  21. from matplotlib import rcParams
  22.  
  23. rcParams["font.family"] = "sans-serif"
  24. rcParams["font.sans-serif"] = ["DejaVu Sans"]
  25. rcParams["font.weight"] = "medium"
  26. rcParams["font.size"] = 10.0
  27. rcParams["axes.linewidth"] = 0.4
  28. rcParams["legend.fontsize"] = 8.0
  29.  
  30. from scipy.interpolate import splev, splrep
  31.  
  32. import tweepy
  33. from instabot import Bot
  34. from mastodon import Mastodon
  35.  
  36. MAINTENANCE = "/home/ubuntu/bndwdth.maintenance"
  37.  
  38. CSV_FILE = "/home/ubuntu/bndwdth.csv"
  39.  
  40. SPEED_MAX_DOWN = 1184890880         # 1,130 Mbps
  41. SPEED_MIN_DOWN = SPEED_MAX_DOWN / 2 #   565 Mbps
  42. SPEED_MAX_UP   = 54525952           #    52 Mbps
  43. SPEED_MIN_UP   = SPEED_MAX_UP / 2   #    26 Mbps
  44.  
  45. DOWN_LINE_COLOUR = "blue"
  46. UP_LINE_COLOUR   = "magenta"
  47. PING_LINE_COLOUR = "orange"
  48. TTFB_LINE_COLOUR = "red"
  49.  
  50. logger = logging.getLogger('bndwdth-log')
  51. logger.setLevel(logging.DEBUG)
  52. logger.addHandler(logging.handlers.SysLogHandler(address = '/dev/log'))
  53.  
  54. def notice(s):
  55.     logger.debug(s)
  56.     print(s)
  57.  
  58. def mean(numbers):
  59.     return float(sum(numbers)) / max(len(numbers), 3)
  60.  
  61. #
  62. ## Get bandwidth in human readable form
  63. #
  64.  
  65. def ghr(b, u=True):
  66.     return get_human_readable(b, u)
  67.  
  68. def get_human_readable(bps, units=True):
  69.     K = 1024
  70.     M = K * K
  71.     G = M * K
  72.     Bps = bps / 8
  73.     if bps < K:
  74.         return "%d%s" % (bps, "bps" if units else "")
  75.     if bps < M:
  76.         return "%d%s" % ((bps / K), "Kbps" if units else "")
  77.     return "%d%s" % ((bps / M), "Mbps" if units else "")
  78.  
  79. def sufficiently_fast_up(speed):
  80.     return speed["up"] > SPEED_MIN_UP
  81.  
  82. def sufficiently_fast_down(speed):
  83.     return speed["down"] > SPEED_MIN_DOWN
  84.  
  85. def host_missing(speed):
  86.     return speed["up"] == 0 and speed["down"] == 0
  87.  
  88. def insufficiently_fast(speed):
  89.     return speed["up"] < SPEED_MIN_UP and speed["down"] < SPEED_MIN_DOWN
  90.  
  91. def sufficiently_fast(speed):
  92.     return sufficiently_fast_up(speed) and sufficiently_fast_down(speed)
  93.  
  94. def extra_fast(speed):
  95.     return speed["up"] > SPEED_MAX_UP and speed["down"] > SPEED_MAX_DOWN
  96.  
  97. #
  98. ## Generate a graph of the previous 24 hours bandwidth
  99. #
  100.  
  101. def parse_bandwidth(duration=24):
  102.     # datetime,down speed,up speed,ping,latency/ttfb,host up,latency up,host down,latency down
  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.                             if len(row) > 8:
  122.                                 ttfb.append(min(float(row[6]), float(row[8])))
  123.                             else:
  124.                                 ttfb.append(float(row[4]))
  125.                         else:
  126.                             ttfb.append(0)
  127.                     else:
  128.                         ping.append(0)
  129.                         ttfb.append(0)
  130.                 else:
  131.                     down.append(0)
  132.                     up.append(0)
  133.                     ping.append(0)
  134.                     ttfb.append(0)
  135.     return { "date": date, "down": down, "up": up, "ping": ping, "ttfb": ttfb }
  136.  
  137. def average_bandwidth(data, avg=2):
  138.     date = []
  139.     down = []
  140.     up = []
  141.     ping = []
  142.     ttfb = []
  143.     for d in range(int(len(data["date"]) / avg)):
  144.         t = d * avg
  145.         date.append(data["date"][t])
  146.  
  147.         ad = 0
  148.         for i in range(avg):
  149.             ad = ad + data["down"][t + i]
  150.         ad = ad / avg
  151.         down.append(ad)
  152.  
  153.         au = 0
  154.         for i in range(avg):
  155.             au = au + data["up"][t + i]
  156.         au = au / avg
  157.         up.append(au)
  158.  
  159.         ap = 0
  160.         for i in range(avg):
  161.             ap = ap + data["ping"][t + i]
  162.         ap = ap / avg
  163.         ping.append(ap)
  164.  
  165.         ac = 0
  166.         for i in range(avg):
  167.             ac = ac + data["ttfb"][t + i]
  168.         ac = ac / avg
  169.         ttfb.append(ac)
  170.  
  171.     return { "date": date, "down": down, "up": up, "ping": ping, "ttfb": ttfb }
  172.  
  173. def generate_graph(data, scale="log", xkcd=False):
  174.     if xkcd:
  175.         plt.xkcd()
  176.     fig, speed_plot = plt.subplots()
  177.     duration_plot = speed_plot.twinx()
  178.  
  179.     timestamp = []
  180.     down = []
  181.     up = []
  182.     ping = []
  183.     ttfb = []
  184.  
  185.     data_points = len(data["date"])
  186.  
  187.     for i in range(data_points):
  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.     # calculate (and draw) a pretty looking line
  201.     timestamp_min = np.array(timestamp).min()
  202.     timestamp_max = np.array(timestamp).max()
  203.  
  204.     time_continuous = np.linspace(int(timestamp_min), int(timestamp_max), int(timestamp_max - timestamp_min))
  205.     time = np.linspace(int(timestamp_min), int(timestamp_max), len(timestamp))
  206.  
  207.     down_continuous = splev(time_continuous, splrep(time, down))
  208.     up_continuous = splev(time_continuous, splrep(time, up))
  209.     ping_continuous = splev(time_continuous, splrep(time, ping))
  210.     ttfb_continuous = splev(time_continuous, splrep(time, ttfb))
  211.  
  212.     time_scale = []
  213.     time_continuous_scale = []
  214.     for i in time:
  215.         time_scale.append(datetime.fromtimestamp(i))
  216.     down_expected = []
  217.     up_expected = []
  218.     for i in time_continuous:
  219.         time_continuous_scale.append(datetime.fromtimestamp(i))
  220.         down_expected.append(float(ghr(SPEED_MAX_DOWN, False)))
  221.         up_expected.append(float(ghr(SPEED_MAX_UP, False)))
  222.  
  223.     ln =      speed_plot.plot(time_continuous_scale, down_continuous, color=DOWN_LINE_COLOUR, markersize=2, linewidth=1, label="Down")
  224.     ln = ln + speed_plot.plot(time_continuous_scale,   up_continuous, color=  UP_LINE_COLOUR, markersize=2, linewidth=1, label="Up")
  225.     ln = ln + speed_plot.plot(time_continuous_scale, ping_continuous, color=PING_LINE_COLOUR, markersize=2, linewidth=1, label="Ping")
  226.     ln = ln + speed_plot.plot(time_continuous_scale, ttfb_continuous, color=TTFB_LINE_COLOUR, markersize=2, linewidth=1, label="TTFB")
  227.     ln = ln + speed_plot.plot(time_continuous_scale,   down_expected, color=DOWN_LINE_COLOUR, markersize=2, linewidth=1, linestyle="dashed", label="Expected: " + str(int(SPEED_MAX_DOWN / 1024 / 1024)) + "Mbps")
  228.     ln = ln + speed_plot.plot(time_continuous_scale,     up_expected, color=  UP_LINE_COLOUR, markersize=2, linewidth=1, linestyle="dashed", label="Expected: " + str(int(SPEED_MAX_UP   / 1024 / 1024)) + "Mbps")
  229.  
  230.     # finish up
  231.     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)
  232.  
  233.     speed_plot.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d %H:%M"))
  234.  
  235.     plt.xlim([datetime.fromtimestamp(time_continuous[0]), datetime.now()])
  236.  
  237.     speed_plot.set_yscale(scale)
  238.     speed_plot.set_ylim(1, 2000)
  239.     speed_plot.yaxis.set_major_formatter(FuncFormatter(lambda y, p: format(int(y), ',')))
  240.  
  241.     duration_plot.set_yscale(scale)
  242.     duration_plot.set_ylim(1, max(data["ping"] + data["ttfb"]) * 1.5)
  243.     duration_plot.yaxis.set_major_formatter(FuncFormatter(lambda y, p: format(int(y), ',')))
  244.     fig.autofmt_xdate()
  245.     fig.set_size_inches(10, 8)
  246.  
  247.     out = "%s-bw-%s.png" % (datetime.now().strftime("%Y%m%d%H%M"), scale)
  248.     plt.savefig(out, dpi=320)
  249.  
  250.     return out
  251.  
  252. #
  253. ## Send the latest results to Twitter
  254. #
  255.  
  256. TWITTER_ACCESS_TOKEN        = ""
  257. TWITTER_ACCESS_TOKEN_SECRET = ""
  258. TWITTER_CONSUMER_KEY        = ""
  259. TWITTER_CONSUMER_SECRET     = ""
  260.  
  261. def get_twitter():
  262.     twitter_auth_keys = {
  263.         "consumer_key"        : TWITTER_CONSUMER_KEY,
  264.         "consumer_secret"     : TWITTER_CONSUMER_SECRET,
  265.         "access_token"        : TWITTER_ACCESS_TOKEN,
  266.         "access_token_secret" : TWITTER_ACCESS_TOKEN_SECRET
  267.         }
  268.     auth = tweepy.OAuthHandler(
  269.         twitter_auth_keys['consumer_key'],
  270.         twitter_auth_keys['consumer_secret']
  271.         )
  272.     auth.set_access_token(
  273.         twitter_auth_keys['access_token'],
  274.         twitter_auth_keys['access_token_secret']
  275.         )
  276.     return tweepy.API(auth)
  277.  
  278. MASTODON_CLIENT_KEY    = ""
  279. MASTODON_CLIENT_SECRET = ""
  280. MASTODON_ACCESS_TOKEN  = ""
  281. MASTODON_HOST          = ""
  282. MASTODON_USERNAME      = ""
  283. MASTODON_PASSWORD      = ""
  284.  
  285. def get_mastodon():
  286.     mastodon = Mastodon(client_id = MASTODON_CLIENT_KEY,
  287.                     client_secret = MASTODON_CLIENT_SECRET,
  288.                     access_token  = MASTODON_ACCESS_TOKEN,
  289.                     api_base_url  = MASTODON_HOST)
  290.     mastodon.log_in(username  = MASTODON_USERNAME,
  291.                     password  = MASTODON_PASSWORD,
  292.                     scopes    = ["read", "write"])
  293.     return mastodon
  294.  
  295. def alert_network_down(graph):
  296.     t = get_twitter()
  297.     s = "@virginmedia it looks like there might be network trouble in #Northants! My #awscloud EC2 instance has lost its connection with my home PC."
  298.     if t is not None:
  299.         t.statuses.update(status=s)
  300.     m = get_mastodon()
  301.     if m is not None:
  302.         m.toot(s)
  303.  
  304. def send_update(speed, graph):
  305.     # upload graph to twitter/mastodon
  306.     t = get_twitter()
  307.     m = get_mastodon()
  308.     twit_img_ids = []
  309.     mast_img_ids = []
  310.     if t is not None:
  311.         twit_img_ids.append(t.media_upload(graph).media_id)
  312.     if m is not None:
  313.         mast_img_ids.append(m.media_post(graph).id)
  314.  
  315.     # send tweet
  316.     t = get_twitter()
  317.     s = "Average #bandwidth in #Northants for the last hour was %s down and %s up" % (ghr(speed["down"]), ghr(speed["up"]))
  318.     if os.path.exists(MAINTENANCE):
  319.         s = "Down for scheduled maintenance"
  320.     else:
  321.         if insufficiently_fast(speed):
  322.             s = "@virginmedia Why's my hourly average #bandwidth in #Northants 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))
  323.         elif not sufficiently_fast_down(speed):
  324.             s = "@virginmedia Why's my hourly average download #bandwidth in #Northants only %s? Nowhere close to %s! (%s up)" % (ghr(speed["down"]), ghr(SPEED_MAX_DOWN, False), ghr(speed["up"]))
  325.         elif not sufficiently_fast_up(speed):
  326.             s = "@virginmedia Why's my hourly average upload #bandwidth in #Northants only %s? Not even close to %s! (%s down)" % (ghr(speed["up"]), ghr(SPEED_MAX_UP, False), ghr(speed["down"]))
  327.         elif extra_fast(speed):
  328.             s = "@virginmedia Excellent: my broadband #bandwidth in #Northants is %s down and %s up! #M600Fibre #superfast" % (ghr(speed["down"]), ghr(speed["up"]))
  329.  
  330.     if t is not None:
  331.         t.update_status(status=s, media_ids=twit_img_ids)
  332.     if m is not None:
  333.         m.status_post(status=s, media_ids=mast_img_ids)
  334.     notice(s)
  335.  
  336. if __name__ == "__main__":
  337.     s = parse_bandwidth(duration=1)
  338.     s = average_bandwidth(s, len(s["date"]))
  339.     s["up"] = s["up"][0]
  340.     s["down"] = s["down"][0]
  341.  
  342.     graph = generate_graph(average_bandwidth(parse_bandwidth(), 4), scale="log")
  343.     alert = Path("bndwdth-alert")
  344.     if int(s["up"]) > 0 and int(s["down"]) > 0:
  345.         if alert.is_file():
  346.             alert.unlink()
  347.         send_update(s, graph)
  348.     else:
  349.         if not alert.is_file():
  350.             alert.open("a")
  351.             alert_network_down(graph)
  352.  
  353.     g = Path(graph)
  354.     if g.is_file():
  355.         g.unlink()
Advertisement
Add Comment
Please, Sign In to add comment