import pandas as pd import dash import dash_core_components as dcc import dash_html_components as html import dash_bootstrap_components as dbc from Application.Simulation import Simulation import os import numpy as np from dash.dependencies import Input, Output, State from Application.DistributionFactory import * import base64 import sys import traceback import threading import plotly.graph_objects as go from plotly.subplots import make_subplots import Application.config as config pd.options.plotting.backend = "plotly" app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SLATE, { 'href': 'https://use.fontawesome.com/releases/v5.8.1/css/all.css', 'rel': 'stylesheet', 'integrity': 'sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf', 'crossorigin': 'anonymous' }]) sys.setrecursionlimit(10 ** 6) dfs = dict() graphs = [] schemaPath = os.path.join(os.path.dirname(__file__), "./Application/files/profile_schema.json") serviceSchemaPath = os.path.join(os.path.dirname(__file__), "./Application/files/service_schema.json") schema = DistributionFactory.getContentFromFile(schemaPath) serviceSchema = DistributionFactory.getContentFromFile(serviceSchemaPath) profile = None services = None mapping = {} seed = None lastDropDown = dict() expectedFig = None distRequest = None # [(np.random.triangular(0, 10E9, 60E9, 1), 1)] @app.callback( Output("errors", "children"), Input('input_go', 'n_clicks')) def startSimulation(value): global profile global services global mapping global seed global distRequest np.random.seed(1) if value > 0: if profile is not None and services is not None and mapping is not None: try: sim = Simulation(schema, serviceSchema) mapping2 = DistributionFactory.genMapping(profile, mapping) sim.main(profile, mapping2, services, distRequest) tmp = readFromQueue(sim) saveSimResult(tmp) createLayout() return html.Div(["Simulation complete"]) except Exception as e: traceback.print_exc() print(e) return html.Div(str(e)), else: return html.Div("One or more inputs missing!") return html.Div([""]) def saveSimResult(observationDict, savePath="SimResults.json"): savePath = os.path.join(os.path.dirname(__file__), savePath) print(savePath) with open(savePath, 'w') as fp: json.dump(observationDict, fp) def readFromQueue(sim): global dfs dfs = dict() # TODO: auto update the dfs dict properly tmp = sim.observationQueueToDict() for value, df in tmp.items():# x = pd.DataFrame.from_dict(df) x["t"] = pd.to_datetime(x["t"], unit='ns') x.set_index("t", inplace=True) if value in dfs: dfs[value].append(x) else: dfs[value] = x return tmp def createLayout(): uploadStyle = { 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' } global app app.layout = html.Div( [ html.Div([ dcc.Interval( id='interval-component', interval=int(config.refreshTime) * 1000, # in milliseconds n_intervals=0 ), dcc.Interval( id='interval2', interval=int(config.refreshTime/2) * 1000, # in milliseconds n_intervals=0 ), html.Div([ html.Div([ dcc.Upload( id='input_profile', children=html.Div([ 'Select Application Profile' ]), style=uploadStyle, multiple=False ) ], className="col-2"), html.Div([ dcc.Upload( id='input_services', children=html.Div([ 'Select Service Definition' ]), style=uploadStyle, multiple=False ) ], className="col-2"), html.Div([ dcc.Upload( id='input_mapping', children=html.Div([ 'Select Service Mapping' ]), style=uploadStyle, multiple=False ) ], className="col-2"), html.Div([ dcc.Upload( id='input_dist', children=html.Div([ 'Select Distribution Request' ]), style=uploadStyle, multiple=False ) ], className="col-2"), html.Div([ dcc.Input(id="input_seed", placeholder='seed', style=uploadStyle) ], className="col-1"), html.Div([ html.Button(children=html.I(className="far fa-play-circle fa-3x", style={"display": "inline-block", "margin": "-8px auto auto -8px", "padding": "0"}), id='input_go', className="btn btn-outline-success", style={'lineHeight': '60px', 'margin': '10px', 'width': '60px', 'height': '60px', "display": "inline-block", }, n_clicks=0 ) ], className="col-1"), html.Div([ ], className="col-2", id="errors") ], className="row g-2 pt-3") ]), html.Div(getPlots() , className="container-fluid px-2 overflow-hidden", id="plots"), ] , style={"overflow": "hidden"}) def getPlots(): plots = [ html.Div([ html.Div([ html.Div([ html.Div([ getDropdown("dones") ], id="dones-dropdown" ), html.Div([ ], id="dones-plot" ) ], id="done", className="col-6"), html.Div([ html.Div([ getDropdown("service_util") ], id="service_util-dropdown" ), html.Div([ ], id="service_util-plot" ) ], id="service_util", className="col-6") ], className="row g-2 pt-3"), html.Div([ html.Div([ html.Div([ getDropdown("response_time") ], id="response_time-dropdown" ), html.Div([ ], id="response_time-plot" ) ], id="response_time", className="col-6"), html.Div([ html.Div([ getDropdown("sim_events") ], id="sim_events-dropdown" ), html.Div([ ], id="sim_events-plot" ) ], id="sim_events", className="col-6") ], className="row g-2 pt-3") ]) ] return plots @app.callback(Output('input_profile', 'children'), Input('input_profile', 'contents')) def setProfileInput(content): global profile try: if content is None and profile is not None: return html.Div(['Application Profile ✔']) if content is not None: content_type, content_string = content.split(',') p = json.loads(base64.b64decode(content_string)) suc, e = DistributionFactory.validateContentvsSchema(p, schema) if not suc: return html.Div([str(e)]) profile = p return html.Div(['Application Profile ✔']) else: return html.Div(['Select Application Profile']) except Exception as e: print(e) return html.Div(['Application Profile ❌']) @app.callback(Output('input_services', 'children'), Input('input_services', 'contents')) def setServicesInput(content): try: global services if content is None and services is not None: return html.Div(['Service Definition ✔']) if content is not None: content_type, content_string = content.split(',') p = json.loads(base64.b64decode(content_string)) suc, e = DistributionFactory.validateContentvsSchema(p, serviceSchema) if not suc: return html.Div([str(e)]) services = copy.deepcopy(p) return html.Div(['Service Definition ✔']) else: return html.Div(['Select Service Definition']) except Exception as e: print(e) return html.Div(['Service Definition ❌']) @app.callback(Output('input_mapping', 'children'), Input('input_mapping', 'contents')) def setMappingInput(content): try: global mapping global profile if content is None and mapping is not None: return html.Div(['Service Mapping ✔']) if content is not None: content_type, content_string = content.split(',') p = json.loads(base64.b64decode(content_string)) if profile is None: return html.Div(['Select Application Profile first']) mapping = copy.deepcopy(p) return html.Div(['Service Mapping ✔']) else: return html.Div(['Select Service Mapping']) except Exception as e: print(e) return html.Div(['Service Mapping ❌']) @app.callback(Output('input_dist', 'children'), Input('input_dist', 'contents')) def setDistInput(content): try: global distRequest if content is None and distRequest is not None: return html.Div(['Distributions ✔']) if content is not None: distRequest = [] content_type, content_string = content.split(',') p = json.loads(base64.b64decode(content_string)) # TODO: validate that requested scenarioids are present in profile for req in p: if req["kind"] == "triangle": dist = (np.random.triangular(req["start"]*1E9, req["highpoint"]*1E9, req["end"]*1E9, req["volume"]), req["scenarioID"]) distRequest.append(dist) return html.Div(['Distributions ✔']) else: return html.Div(['Select Distribution Request']) except Exception as e: print(e) return html.Div(['Distributions ❌']) @app.callback(Output('input_seed', 'value'), Input('input_seed', 'value')) def setSeed(value): if value is not None: global seed seed = int(value) return value def getDropdown(keyword): if keyword not in dfs: return dcc.Dropdown( id=keyword + '-dropdown-dd', options=[], value=[] ) options = [] for i in sorted(dfs[keyword].identifier.unique()): options.append({"label": i, "value": i}) return dcc.Dropdown( id=keyword + '-dropdown-dd', options=options, value=dfs[keyword].identifier.unique()[0] ) @app.callback( Output(component_id='dones-dropdown', component_property='children'), Output(component_id='response_time-dropdown', component_property='children'), Output(component_id='service_util-dropdown', component_property='children'), Output(component_id='sim_events-dropdown', component_property='children'), Input('interval2', 'n_intervals')) def getDropdown2(n_intervals): returns = [] global lastDropDown titles = ["sim_events","dones", "response_time", "service_util"] if not dfs: for i in titles: returns.append(dcc.Dropdown( id=str(i) + '-dropdown-dd', options=[], value=[]) ) return returns else: for keyword in sorted(list(dfs.keys())): options = [] for i in sorted(dfs[keyword].identifier.unique()): options.append({"label": i, "value": i}) value = lastDropDown[keyword] if lastDropDown[keyword] != [] else dfs[keyword].identifier.unique()[0] returns.append( dcc.Dropdown( id=keyword + '-dropdown-dd', options=options, value=value) ) return returns @app.callback( Output(component_id='dones-plot', component_property='children'), Input(component_id='dones-dropdown-dd', component_property='value'), Input('interval-component', 'n_intervals') ) def update_output_div(input_value, n_intervals): global lastDropDown if input_value is not None: lastDropDown["dones"] = input_value else: input_value = lastDropDown["dones"] return getPlot("dones", input_value) @app.callback( Output(component_id='response_time-plot', component_property='children'), Input(component_id='response_time-dropdown-dd', component_property='value'), Input('interval-component', 'n_intervals') ) def update_output_div(input_value, n_intervals): global lastDropDown if input_value is not None: lastDropDown["response_time"] = input_value else: input_value = lastDropDown["response_time"] return getPlot("response_time", input_value) @app.callback( Output(component_id='service_util-plot', component_property='children'), Input(component_id='service_util-dropdown-dd', component_property='value'), Input('interval-component', 'n_intervals') ) def update_output_div(input_value, n_intervals): global lastDropDown if input_value is not None: lastDropDown["service_util"] = input_value else: input_value = lastDropDown["service_util"] return getPlot("service_util", input_value) @app.callback( Output(component_id='sim_events-plot', component_property='children'), Input(component_id='sim_events-dropdown-dd', component_property='value'), Input('interval-component', 'n_intervals') ) def update_output_div(input_value, n_intervals): global lastDropDown if input_value is not None: lastDropDown["sim_events"] = input_value else: input_value = lastDropDown["sim_events"] return getPlot("sim_events", input_value) def loadSimResult(savePath="SimResults.json"): global dfs savePath = os.path.join(os.path.dirname(__file__), savePath) if not os.path.isfile(savePath): print(savePath) return dfs with open(savePath, "r") as fp: dfDicts = json.load(fp) for value, df in dfDicts.items(): dfs[value] = pd.DataFrame.from_dict(df) dfs[value]["t"] = pd.to_datetime(dfs[value]["t"], unit='ns') dfs[value].set_index("t", inplace=True) return dfs def getHistPlot(fig, title, changes): global distRequest fig2 = make_subplots() hist = pd.DataFrame() if distRequest is not None: dists = sorted([pd.to_datetime(y/1E9, unit="s") for x,_ in distRequest for y in x]) integratedCurve = [i for i in range(len(dists))] expectedFig =go.Scatter( x=dists, y=integratedCurve, mode="lines", line=go.scatter.Line(color="black"), name="expected" ) fig2.add_trace(expectedFig) hist["t"] = dists hist["t2"] = integratedCurve hist.set_index("t", inplace=True) hist = hist.resample(rule=config.bucketSize).last().interpolate() fig2.add_trace(go.Bar(x=hist.index, y=hist["t2"].diff(), name="input")) fig2.add_trace(fig) #hist = go.Histogram(x=dists, name="input") changes = changes.resample(rule=config.bucketSize).last().interpolate() fig2.add_trace(go.Bar(x=changes.index, y=changes["completed"].diff(), name="output")) fig2.update_layout({"title": title}) return fig2 def getPlot(keyword, identifier=None): global dfs if keyword not in dfs: return html.P(keyword + ' graph') titles = { "sim_events": "events in queue", "dones": "completed interactions", "response_time": "response time", "service_util": "service utilization", } df = dfs[keyword] if identifier is None: identifier = df.identifier.unique()[0] if keyword == "sim_events": fig = df.loc[df["identifier"] == identifier].resample(rule=config.average).sum().interpolate().plot(kind='bar', title=f"{titles[keyword]}: {identifier}") return dcc.Graph(id=f"{keyword} {identifier}", figure=fig, style={"height": "24rem"}, animate=True) else: if keyword == "dones": data = df.loc[df["identifier"] == identifier].resample(rule = config.average).last().interpolate("pad") fig = go.Scatter( x=data.index, y=data["completed"], mode="lines", line=go.scatter.Line(color="blue"), name="completed" ) fig = getHistPlot(fig, f"{titles[keyword]}: {identifier}", data) else: data = df.loc[df["identifier"] == identifier].resample(rule=config.average).mean().interpolate("pad") fig = data.plot(kind='line', title=f"{titles[keyword]}: {identifier}") return dcc.Graph(id=f"{keyword} {identifier}", figure=fig, style={"height": "24rem"}, animate=True) if __name__ == '__main__': dfs = loadSimResult() createLayout() app.run_server(debug=True)