modules
¶
Top-level package for Internet Speed Test Logger.
cli
¶
Console script for netspeedlogger.
delete_database()
¶
Run a SQL querry on the netspeedlogger database and print a table of the results
Source code in netspeedlogger/cli.py
def delete_database():
"""Run a SQL querry on the netspeedlogger database and print a table of the results"""
db_path = get_database_path()
print(f"Deleting netspeedlogger database at path: `{db_path}`")
print(
"Are you sure you want to delete the whole database? Input 'y' for yes or 'n' for no"
)
for i in range(10):
confirmation = input("Please type 'y' for Yes or 'n' for No")
if confirmation == "n":
return "Not deleting database"
elif confirmation == "y":
delete_database_if_exists()
return "Database deleted"
results()
¶
Show all results from the netspeedlogger database
If there are more than 10000 results, will show the first 10000
Source code in netspeedlogger/cli.py
def results():
"""Show all results from the netspeedlogger database
If there are more than 10000 results, will show the first 10000
"""
sql_to_markdown(
"select substr(timestamp,1,19) as 'Date Time', "
" download_speed/(1024*1024) as 'Download Speed (Mb/s)', "
" upload_speed/(1024*1024) as 'Upload Speed (Mb/s)', "
" bytes_sent/(1024) as 'kB Sent', "
" bytes_received/(1024) as 'kB Recieved', "
" server_id as 'Server ID', "
" server_host as 'Server Host', "
" ping as 'Ping (ms)' "
" from netspeedlogger limit 10000"
)
speedtest()
¶
Run an internet speed test using speedtest-cli and save the results to a local sqlite database
Source code in netspeedlogger/cli.py
def speedtest():
"""Run an internet speed test using speedtest-cli and save the results to a local sqlite database"""
print("netspeedlogger speedtest")
print("=" * len("netspeedlogger speedtest"))
print("Starting to run an internet speed test, and logging the output")
results_dict = run_speedtest()
df = speedtest_dict_to_dataframe(results_dict)
write_speedtest_to_database(df)
print("Speedtest complete. Results:")
print(df.to_markdown(index=False))
sql_to_markdown(sql_query, showindex=False)
¶
Run a SQL querry on the netspeedlogger database and print a table of the results
Source code in netspeedlogger/cli.py
def sql_to_markdown(sql_query: str, showindex: bool = False):
"""Run a SQL querry on the netspeedlogger database and print a table of the results"""
if database_has_results():
df = query(sql_query)
print(df.to_markdown(index=showindex))
else:
print("No results - run `netspeedlogger run` first")
summary()
¶
Display summary of internet speed test results as a table
Source code in netspeedlogger/cli.py
def summary():
"""Display summary of internet speed test results as a table"""
if database_has_results():
df = query(
(
"select substr(timestamp,1,19) as 'Date Time', "
" download_speed/(1024*1024) as 'Download Speed (Mb/s)', "
" upload_speed/(1024*1024) as 'Upload Speed (Mb/s)', "
" ping as 'Ping (ms)' "
" from netspeedlogger "
)
)
print(df.describe().to_markdown(index=True))
else:
print("No results - run `netspeedlogger run` first")
netspeedlogger
¶
database_has_results()
¶
Returns True if netspeedlogger database has results, False otherwise
Source code in netspeedlogger/netspeedlogger.py
def database_has_results():
"""Returns True if netspeedlogger database has results, False otherwise"""
result = query("select count(*) from netspeedlogger")
if isinstance(result, pd.DataFrame):
return True
else:
return False
delete_database_if_exists()
¶
Delete netspeedlogger database if it exists
Source code in netspeedlogger/netspeedlogger.py
def delete_database_if_exists():
"""Delete netspeedlogger database if it exists"""
dbpath = get_database_path()
if os.path.isfile(dbpath):
os.remove(dbpath)
get_database_path()
¶
Returns the path to the netspeedlogger sqlite database
Set the env var NETSPEEDLOGGER to change the default folder location
If NETSPEEDLOGGER is not set, it is stored here: ~/.netspeedlogger/netspeedlogger.sqlite3 If the folder .netspeedlogger doesn't exist, it will be created
Source code in netspeedlogger/netspeedlogger.py
def get_database_path():
"""Returns the path to the netspeedlogger sqlite database
Set the env var NETSPEEDLOGGER to change the default folder location
If NETSPEEDLOGGER is not set, it is stored here: ~/.netspeedlogger/netspeedlogger.sqlite3
If the folder .netspeedlogger doesn't exist, it will be created
"""
USERHOME = os.path.expanduser("~")
database_folder = os.environ.get(
"NETSPEEDLOGGER", os.path.join(USERHOME, ".netspeedlogger")
)
if not os.path.isdir(database_folder):
os.makedirs(database_folder, exist_ok=True)
return os.path.join(database_folder, "netspeedlogger.sqlite3")
query(query)
¶
Run a SQL querry on the netspeedlogger database
Source code in netspeedlogger/netspeedlogger.py
def query(query: str):
"""Run a SQL querry on the netspeedlogger database"""
database_file = get_database_path()
if os.path.isfile(database_file):
con = sqlite3.connect(database_file)
df = pd.read_sql_query(query, con)
con.close()
return df
else:
return None
run_speedtest(retries=3, timeout=15, sleep_between_retries=10)
¶
Run internet speed test with speedtest-cli library
Source code in netspeedlogger/netspeedlogger.py
def run_speedtest(retries: int = 3, timeout: int = 15, sleep_between_retries: int = 10):
"""Run internet speed test with speedtest-cli library"""
for retry_count in range(retries):
try:
s = speedtest.Speedtest(timeout=timeout)
s.get_best_server()
s.download(threads=None)
s.upload(threads=None)
results_dict = s.results.dict()
validate_speedtest_result(results_dict)
return results_dict
except speedtest.ConfigRetrievalError as e:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
message = template.format(type(e).__name__, e.args)
mylogger.warning(message)
if (retry_count + 1) < retries:
sleep(sleep_between_retries)
except IndexError as e:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
message = template.format(type(e).__name__, e.args)
mylogger.warning(message)
if (retry_count + 1) < retries:
sleep(sleep_between_retries)
mylogger.error(f"Failed running speed test {retries} times; returning zero values")
return {
"download": 0,
"upload": 0,
"bytes_sent": 0,
"bytes_received": 0,
"ping": None,
"server": {"host": None, "id": None},
"timestamp": str(datetime.datetime.now()),
}
selectall()
¶
Select all records in the netspeedlogger database
Source code in netspeedlogger/netspeedlogger.py
def selectall():
"""Select all records in the netspeedlogger database"""
return query("SELECT * from netspeedlogger")
selectall_with_date_range(min_date, max_date)
¶
Select all from netspeedlogger database with date range
Source code in netspeedlogger/netspeedlogger.py
def selectall_with_date_range(min_date, max_date):
"""Select all from netspeedlogger database with date range"""
return query(
f"SELECT * FROM netspeedlogger where timestamp >= '{min_date}' and timestamp <= '{max_date}'"
)
speedtest_dict_to_dataframe(results_dict)
¶
Converts speedtest-cli results_dict to a pandas.DataFrame
Source code in netspeedlogger/netspeedlogger.py
def speedtest_dict_to_dataframe(results_dict):
"""Converts speedtest-cli results_dict to a pandas.DataFrame"""
return pd.DataFrame(
[
{
"download_speed": results_dict["download"],
"upload_speed": results_dict["upload"],
"bytes_sent": results_dict["bytes_sent"],
"bytes_received": results_dict["bytes_received"],
"ping": results_dict["ping"],
"server_host": results_dict["server"]["host"],
"server_id": results_dict["server"]["id"],
"timestamp": str(datetime.datetime.now()),
}
]
)
timeseries_chart(dat, variable, CHART_HEIGHT, CHART_WIDTH)
¶
Altair timeseries chart for netspeedlogger
Source code in netspeedlogger/netspeedlogger.py
def timeseries_chart(dat, variable, CHART_HEIGHT, CHART_WIDTH):
"""Altair timeseries chart for netspeedlogger"""
return (
alt.Chart(dat, title="")
.mark_line(interpolate="cardinal", point=alt.OverlayMarkDef())
.encode(
x="timestamp:T",
y=f"{variable}:Q",
tooltip=[
alt.Tooltip(variable, title=variable),
alt.Tooltip("timestamp:T", title="Date"),
alt.Tooltip("timestamp:T", title="Hour", timeUnit="hours"),
alt.Tooltip("timestamp:T", title="Minute", timeUnit="minutes"),
],
)
.properties(height=CHART_HEIGHT, width=CHART_WIDTH)
)
validate_speedtest_result(results_dict)
¶
Validates speedtest-cli results results_dict has required fields
Source code in netspeedlogger/netspeedlogger.py
def validate_speedtest_result(results_dict: dict):
"""Validates speedtest-cli results results_dict has required fields"""
mylogger.info("Validating response before inserting into database")
assert "download" in results_dict
assert "upload" in results_dict
assert "bytes_sent" in results_dict
assert "bytes_received" in results_dict
assert "ping" in results_dict
assert "server" in results_dict
assert "host" in results_dict["server"]
assert "id" in results_dict["server"]
assert isinstance(results_dict["download"], float)
assert isinstance(results_dict["upload"], float)
assert isinstance(results_dict["bytes_sent"], int)
assert isinstance(results_dict["bytes_received"], int)
assert isinstance(results_dict["ping"], float)
assert isinstance(results_dict["server"]["host"], str)
assert isinstance(results_dict["server"]["id"], str)
write_speedtest_to_database(df)
¶
Write speedtest result to netspeedlogger database
Source code in netspeedlogger/netspeedlogger.py
def write_speedtest_to_database(df: pd.DataFrame):
"""Write speedtest result to netspeedlogger database"""
database_path = get_database_path()
mylogger.info(f"Opening database: {database_path}")
con = sqlite3.connect(database_path)
mylogger.info("Inserting into database")
df.to_sql("netspeedlogger", con, if_exists="append")
mylogger.info(str(datetime.datetime.now()))
mylogger.info("Internet Speed Test script finished")
con.close()