{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Ping Modeller\n", "### Ewan Miles - 06/05/2020\n", "\n", "**This code is entirely open-source and thus editable by any user.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This script is essentially a simple make-up of two functions: **ping()** and **pingmodel()**, with the latter requiring the former.\n", "\n", "**ping()** uses subprocess to ping a user-input host address (typically a URL, e.g. \"google.com\", entered as a string). This means it sends a single arbitrary packet to the address and measures the time from departure to return, as a representation of connection strength. The data returned includes alot of information, but for the purposes of this script, **ping()** stringifies it and slices to the ping time only, returning it as a float.\n", "\n", "**pingmodel()** uses **ping()** repeatedly over a user-defined period, appending arrays with ping values in conjunction with the time at which the ping was sent, plotting the variation in ping (ms) across a time interval (s). Due to the structure of the function, _the graph does not plot until the time has passed_. It also features a break in the ping loop if packets do not return; it does this by checking if the ping array is filled with _None_ values only. A packet will not return if the connection is very slow, but likelihood is that, if no packets return over a few seconds, the host is not reachable (i.e. the user has likely input an incorrect URL).\n", "\n", "After running the module imports and defined functions, all that you need to do is run the last cell with **pingmodel()**, where the two arguments are the host name and the time interval over which to ping, respectively, e.g. `pingmodel(\"google.com\",30)`, which would ping google.com over 30 seconds and plot the graph.\n", "\n", "* Great connections have ping averages in the range of 10-20ms, with low standard deviations.\n", "* Good connections have ping averages in the range of 40-60ms, with low to medium standard deviations (medium deviations usually caused by occasional \"spikes\" in ping).\n", "* Poor connections typically average over 150-200ms, or have high standard deviations, or both.\n", "* **Virtually non-existent connections typically average 1000ms+.** I have been unfortunate enough to be on these connections many a time.\n", "\n", "To give an idea of what the ping represents, playing a game on 1000ms ping means that any controls by the player will be registered in-game after (around) a one second delay." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### IMPORTING MODULES ###\n", "\n", "%matplotlib notebook\n", "\n", "import subprocess\n", "import matplotlib.pyplot as plt\n", "from time import time, sleep\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def ping(host):\n", " \"\"\"\n", " Function which uses subprocess module to ping host website of choice, input:\n", " - host: URL of website to ping, e.g. \"youtube.com\"\n", " Outputs ping to website from computer in ms, one count\n", " \"\"\"\n", "\n", " # Ping the host website, output \"out\" variable as byte response for packet return\n", " ping = subprocess.Popen(\n", " [\"ping\", \"-c\", \"1\", host],\n", " stdout = subprocess.PIPE,\n", " )\n", " out = ping.communicate()\n", " \n", " # Try to stringify the ping output and find and slice the return time (ms)\n", " try:\n", " out = str(out)\n", " startms = out.find(\"time=\")\n", " endms = out.find(\"ms\")\n", " \n", " time = float(out[(startms + 5):(endms-1)])\n", " \n", " # Except lack of packet return, set time (ms) to None\n", " except ValueError:\n", " time = None\n", "\n", " return time" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def pingmodel(host, length):\n", " \"\"\"\n", " Function which pings host website of choice over certain length of time, inputs:\n", " - host: URL of website to ping, e.g. \"youtube.com\"\n", " - length: length of time over which to ping IN SECONDS, e.g. 300 secs\n", " Outputs plot of ping(ms) vs. time(s) with average and st.dev\n", " NOTE the function sleeps for 0.05s between pings\n", " GRAPH WILL PRINT AFTER THE TIME HAS PASSED, E.G. IF length = 30, GRAPH WILL APPEAR AFTER 30s\n", " \"\"\"\n", " \n", " # Take time at beginning of execution, to have a start point to check length of time passed\n", " start_time = current_time = time()\n", " \n", " # Empty arrays to fill with ping over time\n", " pingarray = []\n", " timearray = []\n", " \n", " # \"broken\" variable checks if packets have not returned, True in if statements below\n", " broken1 = broken2 = False\n", " \n", " while (current_time - start_time) < length:\n", " \n", " # Current time value of pinging host (x value), ping in ms (y value)\n", " ping_time = time() - start_time\n", " value = ping(host)\n", " \n", " # Append to lists\n", " timearray.append(ping_time)\n", " pingarray.append(value)\n", " \n", " # Check time to see if length has been met\n", " current_time = time()\n", " \n", " # Statements to break ping loop if packets do not return (no connection)\n", " if length/4 < 10:\n", " if (ping_time > length/4) and (set(pingarray) == {None}):\n", " print(\"{0}s have passed and no packets have returned yet, so the connection has been broken.\".format(length/4))\n", " print(\"Are you sure '{0}' is the correct host URL? Perhaps it is not possible to ping this address.\".format(host))\n", " broken1 = True\n", " break\n", " else:\n", " if (ping_time > 10) and (set(pingarray) == {None}):\n", " print(\"10s have passed and no packets have returned yet, so the connection has been broken.\")\n", " print(\"Are you sure '{0}' is the correct host URL? Perhaps it is not possible to ping this address.\".format(host))\n", " broken2 = True\n", " break\n", " \n", " # Plot data\n", " plt.figure()\n", " plt.xlabel(\"Time (s)\")\n", " plt.ylabel(\"Ping (ms)\")\n", " plt.grid()\n", " plt.plot(timearray,pingarray,\"k-\",label=\"Packet Return Time\")\n", " \n", " if broken1 == True:\n", " plt.title(\"Pinging host '{0}',\\nBroken after {1}s\".format(host,length/4))\n", "\n", " elif broken2 == True:\n", " plt.title(\"Pinging host '{0}',\\nBroken after 10s\".format(host,length))\n", " \n", " else: \n", " plt.title(\"Pinging host '{0}',\\nlength approx. {1}s\".format(host,length))\n", " \n", " pingarray = [i for i in pingarray if i != None] # Remove None values to take mean and standard deviation\n", " \n", " # Calculate and plot mean, standard deviation\n", " x0 = np.mean(pingarray)\n", " sigma = np.std(pingarray)\n", " \n", " plt.axhline(y=x0,c=\"b\",label=\"Mean, $x_0$\")\n", " plt.axhline(y=x0+sigma,c=\"r\",ls=\"-.\",label=\"± St. Dev, $1\\sigma$\")\n", " \n", " # Only plot mean - sigma if above 0ms, otherwise raises graph pointlessly\n", " if (x0-sigma) > 0:\n", " plt.axhline(y=x0-sigma,c=\"r\",ls=\"-.\")\n", " \n", " print(\"\\nAverage ping time: {0:0.2f}ms\".format(x0))\n", " print(\"Standard Deviation: {0:0.2f}ms\".format(sigma))\n", " \n", " plt.legend(loc=\"best\") " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "### RUN THIS CELL TO PRODUCE A GRAPH ###\n", "\n", "# Graph will not appear until time has passed\n", "pingmodel(\"\", )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.8" } }, "nbformat": 4, "nbformat_minor": 2 }