Initial POC
This commit is contained in:
commit
98061e95c3
10
default.nix
Normal file
10
default.nix
Normal file
@ -0,0 +1,10 @@
|
||||
{ python3 }:
|
||||
with python3.pkgs;
|
||||
buildPythonPackage rec {
|
||||
name = "tsplot";
|
||||
src = ./.;
|
||||
propagatedBuildInputs = [ pyyaml pandas dash ];
|
||||
buildInputs = [];
|
||||
doCheck = false;
|
||||
shellHook = "";
|
||||
}
|
11
setup.py
Normal file
11
setup.py
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(name='tsplot',
|
||||
version='0.1',
|
||||
description='Simple time series plotting',
|
||||
author='0xee',
|
||||
author_email='tsplot@0xee.eu',
|
||||
packages=[],
|
||||
scripts=['tsplot'])
|
2
shell.nix
Normal file
2
shell.nix
Normal file
@ -0,0 +1,2 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.callPackage ./. {}
|
137
tsplot
Executable file
137
tsplot
Executable file
@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from dash.dependencies import Input, Output
|
||||
import dash
|
||||
import dash_core_components as dcc
|
||||
import dash_html_components as html
|
||||
|
||||
import pandas as pd
|
||||
from functools import lru_cache
|
||||
|
||||
import argparse
|
||||
import sqlite3
|
||||
import time
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
|
||||
|
||||
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
|
||||
|
||||
|
||||
def get_topics(db):
|
||||
cursor = db.cursor()
|
||||
query = 'SELECT name FROM sqlite_master WHERE type="table"'
|
||||
q = cursor.execute(query)
|
||||
topics = [t[0] for t in q.fetchall()]
|
||||
return topics
|
||||
|
||||
|
||||
update_interval_s = 4
|
||||
|
||||
# Since we're adding callbacks to elements that don't exist in the app.layout,
|
||||
# Dash will raise an exception to warn us that we might be
|
||||
# doing something wrong.
|
||||
# In this case, we're adding the elements through a callback, so we can ignore
|
||||
# the exception.
|
||||
app.config.suppress_callback_exceptions = True
|
||||
|
||||
app.layout = html.Div([
|
||||
dcc.Location(id='url', refresh=False),
|
||||
html.Div(id='page-content')
|
||||
])
|
||||
|
||||
|
||||
@lru_cache(maxsize=10)
|
||||
def get_data(topic, timerange, ts):
|
||||
conn = sqlite3.connect(db)
|
||||
table = topic
|
||||
|
||||
earliest = datetime.datetime.now() - timerange
|
||||
query = f"SELECT * FROM {table} WHERE timestamp > '{earliest}'"
|
||||
|
||||
df = pd.read_sql(query, conn, parse_dates=[
|
||||
'timestamp'], index_col='timestamp')
|
||||
return df
|
||||
|
||||
|
||||
plot_page = html.Div([
|
||||
dcc.Interval(
|
||||
id='interval-component',
|
||||
interval=4*1000, # in milliseconds
|
||||
n_intervals=0),
|
||||
dcc.Dropdown(
|
||||
id='timerange',
|
||||
options=[
|
||||
{'label': 'Last hour', 'value': 'h'},
|
||||
{'label': 'Last day', 'value': 'd'},
|
||||
{'label': 'Last week', 'value': 'w'},
|
||||
{'label': 'All', 'value': 'all'}
|
||||
],
|
||||
value='d'
|
||||
),
|
||||
dcc.Graph(id='graph', animate=False),
|
||||
dcc.Link('Back', href='/')
|
||||
])
|
||||
|
||||
def get_timedelta(range_str):
|
||||
range_to_delta = {
|
||||
'h': datetime.timedelta(hours=1),
|
||||
'd': datetime.timedelta(days=1),
|
||||
'w': datetime.timedelta(weeks=1),
|
||||
'all': datetime.timedelta(weeks=10000),
|
||||
}
|
||||
return range_to_delta[range_str]
|
||||
|
||||
|
||||
@app.callback(Output('graph', 'figure'),
|
||||
[Input('interval-component', 'n_intervals'),
|
||||
Input('timerange', 'value'),
|
||||
Input('url', 'pathname')])
|
||||
def update_graph(interval, timerange, pathname):
|
||||
topics = pathname.strip('/').split('+')
|
||||
if len(topics):
|
||||
delta = get_timedelta(timerange)
|
||||
now = int(time.time()) // update_interval_s
|
||||
def make_data(df, name):
|
||||
return {
|
||||
'x': df.index,
|
||||
'y': df['value'],
|
||||
'type': 'scatter',
|
||||
'mode': 'lines',
|
||||
'name': name
|
||||
}
|
||||
return {
|
||||
'data': [make_data(get_data(topic, delta, now), topic) for topic in topics]}
|
||||
else:
|
||||
return {}
|
||||
|
||||
# Update the index
|
||||
@app.callback(dash.dependencies.Output('page-content', 'children'),
|
||||
[dash.dependencies.Input('url', 'pathname')])
|
||||
def display_page(pathname):
|
||||
if pathname == '/':
|
||||
return index_page
|
||||
else:
|
||||
return plot_page
|
||||
# You could also return a 404 "URL not found" page here
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
options = argparse.ArgumentParser()
|
||||
options.add_argument('--debug', '-d', action='store_true')
|
||||
options.add_argument('db')
|
||||
|
||||
args = options.parse_args()
|
||||
|
||||
db = args.db
|
||||
|
||||
conn = sqlite3.connect(db)
|
||||
topics = get_topics(conn)
|
||||
|
||||
index_page = html.Div(sum([[
|
||||
dcc.Link(f'Go to {page}', href=f'/{page}'),
|
||||
html.Br()] for page in topics], []))
|
||||
|
||||
app.run_server(debug=args.debug, host='0.0.0.0')
|
Loading…
Reference in New Issue
Block a user