diff --git a/.gitignore b/.gitignore index ecc3046..a52fe51 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ pnpm-debug.log* __pycache__ *egg-info *pyc +build diff --git a/README.rst b/README.rst index ea60403..af2712d 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ CODES Dashboard =============== -Initial dashboard for CODES digital twin +Dashboard for CODES digital twin * Free software: BSD License @@ -11,7 +11,7 @@ Initial dashboard for CODES digital twin Installing ---------- -Install the application +Install the application. All dependencies will be installed as well. .. code-block:: console @@ -22,7 +22,12 @@ Run the application .. code-block:: console - codes-dashboard + codes-dashboard --data + +Right now this takes in a dataset generated by the `ROSS Instrumentation layer`_. +There is a small sample dataset located in this repo at ``tests/data/ross-binary-data/ross-stats-gvt.bin``. +It was generated using the PHOLD model, which is pretty load balanced, so it's not particularly interesting. +More interesting sample data will be uploaded in the future. Features -------- diff --git a/codes_dashboard/app/CodesDashboard.py b/codes_dashboard/app/CodesDashboard.py new file mode 100644 index 0000000..331ccfd --- /dev/null +++ b/codes_dashboard/app/CodesDashboard.py @@ -0,0 +1,714 @@ +import os + +from trame.app import get_server, dev +from trame.app.file_upload import ClientFile +from trame.decorators import TrameApp, change, controller +from trame.ui.vuetify import SinglePageWithDrawerLayout +from trame.widgets import client, grid, html, vuetify, trame, router +from trame.ui.router import RouterViewLayout +from trame.ui.html import DivLayout +from trame.widgets import plotly +import plotly.express as px + +from .ui import ( + empty, + parallel_coords, + time_plot, + scatter_plot, + network_time, + heatmap +) +from .core.ross_binary_file import ROSSFile +from .core.event_trace_file import EventFile +from .core.model_file import ModelFile + +# The user can set this via an environment variable +DATA_PATH_ENV_NAME = "ROSS_DATA_PATH" + +DEFAULT_NB_ROWS = 8 + +def get_next_y_from_layout(layout): + next_y = 0 + for item in layout: + y, h = item.get("y", 0), item.get("h", 1) + if y + h > next_y: + next_y = y + h + return next_y + +@TrameApp() +class CodesDashboard: + def __init__(self, server_or_name=None) -> None: + self.server = get_server(server_or_name, client_type="vue2") + self.state.trame__title = "Visualization Dashboard" + self.state.setdefault("grid_options", []) + self.state.setdefault("model_grid_item_dirty_key", 0) + self.state.setdefault("engine_grid_item_dirty_key", 0) + + self._args = self._app_settings() + self._ross_file = None + self._event_file = None + self._model_file = None + + # only true when both event file and model file are uploaded + self.state.all_model_data_uploaded = False + self.state.sim_engine_data_uploaded = False + + # can be none, model-only, engine-only, both + self.state.view_mode = "none" + if self._args.data_file is not None: + self._ross_file = ROSSFile(self._args.data_file) + self._ross_file.read() + self.state.sim_engine_data_uploaded = True + + if self._args.event_data_file is not None: + self._event_file = EventFile(self._args.event_data_file) + self._event_file.read() + + if self._args.model_data_file is not None: + self._model_file = ModelFile(self._args.model_data_file) + self._model_file.read() + + if self._model_file is not None and self._event_file is not None: + self.state.all_model_data_uploaded = True + + # at this point, set up default layouts, which if nothing is loaded, is just + # empty. maybe it should be an empty vcontainer or something, that we can then + # change once data gets loaded + # so I think we actually need to have all the figures created in the beginning + # and use v_for to hide them until they're needed + # so maybe each vis type gets a function that just sets up the inital plotly figure + # but doesn't have to set up any of the other stuff, or maybe there's just a default + # function that sets up some empty figures and hides them, then that can be adapted + # to the correct type of figure when the data is loaded + self.init_all_visualizations() + + self.LAYOUTS = {} + self.set_up_model_vis_view() + self.set_up_sim_engine_vis_view() + + if self.server.hot_reload: + self.server.controller.on_server_reload.add(self._build_ui) + self.ui = self._build_ui() + + + @property + def ctrl(self): + return self.server.controller + + + @property + def state(self): + return self.server.state + + + # can choose to load any of the data files through the command line + def _app_settings(self): + data_kwargs = { + "help": "Sim Engine data file to load", + "dest": "data_file", + } + + event_data_kwargs = { + "help": "Event data file to load", + "dest": "event_data_file", + } + + model_data_kwargs = { + "help": "model data file to load", + "dest": "model_data_file", + } + + default = os.getenv(DATA_PATH_ENV_NAME) + if default is not None: + # If the environment variable has been provided, use that for the default + data_kwargs["default"] = default + event_data_kwargs["default"] = default + model_data_kwargs["default"] = default + + self.server.cli.add_argument("--data", **data_kwargs) + self.server.cli.add_argument("--event-data", **event_data_kwargs) + self.server.cli.add_argument("--model-data", **model_data_kwargs) + args, _ = self.server.cli.parse_known_args() + return args + + + def init_all_visualizations(self): + empty.init_options(self.server) + heatmap.init_options(self.server) + network_time.init_options(self.server) + parallel_coords.init_options(self.server) + scatter_plot.init_options(self.server) + time_plot.init_options(self.server) + + + def set_up_model_vis_view(self): + # this also gets added to the state.grid_options, do we need both? + self.state[f"model_grid_view_heatmap"] = heatmap.OPTION + self.state[f"model_grid_view_network_time_plot"] = network_time.OPTION + + name = 'model_layout' + layout = DivLayout(self.server, name, + width='1200px') + self.LAYOUTS[name] = layout + with layout: + with vuetify.VCard(style="height: 100%; width: 100%;", + classes="ma-1 position-absolute top-0 left-0", + #width=1200, height=800 + ): + vuetify.VCardTitle("Model Data Visualizations") + vuetify.VDivider() + layout.content = vuetify.VCardText() + layout.content.vis_views = {} + with layout.content: + with vuetify.VRow(v_if="all_model_data_uploaded == false"): + with vuetify.VCol(cols="12"): + html.Div("Load a model data file and event trace file to view visualizations") + self._create_model_vis_view(layout) + + + def set_up_sim_engine_vis_view(self): + self.state[f"engine_grid_view_parallel_coords"] = parallel_coords.OPTION + self.state[f"engine_grid_view_scatter_plot"] = scatter_plot.OPTION + self.state[f"engine_grid_view_time_plot"] = time_plot.OPTION + + name = 'sim_engine_layout' + layout = DivLayout(self.server, name) + self.LAYOUTS[name] = layout + with layout: + with vuetify.VCard(style="height: 100%; width: 100%;", + #width=1200, height=800 + ): + vuetify.VCardTitle("Simulation Engine Visualizations") + vuetify.VDivider() + layout.content = vuetify.VCardText() + layout.content.vis_views = {} + with layout.content: + with vuetify.VRow(v_if="sim_engine_data_uploaded == false"): + with vuetify.VCol(cols="12"): + html.Div("Load a simulation engine file to view visualizations") + self._create_sim_engine_vis_view(layout) + + + @change("sim_engine_file") + def sim_engine_file_uploaded(self, sim_engine_file, **kwargs): + if sim_engine_file is None: + return + + file = ClientFile(sim_engine_file) + self._ross_file = ROSSFile(file) + self._ross_file.read() + + if self.state.view_mode == "none" or self.state.view_mode == "engine-only": + self.state.view_mode = "engine-only" + else: + self.state.view_mode = "both" + + self.state.sim_engine_data_uploaded = True + + parallel_coords.initialize(self.server, self._ross_file) + scatter_plot.initialize(self.server, self._ross_file) + time_plot.initialize(self.server, self._ross_file) + + # TODO: update the sim engine layout + layout = self.LAYOUTS["sim_engine_layout"] + with layout: + with layout.content: + for name, view in layout.content.vis_views.items(): + view.clear() + with view: + client.ServerTemplate(name=name) + + self.ctrl.init_parallel_coords() + + + @change("model_data_file") + def model_data_file_uploaded(self, model_data_file, **kwargs): + if model_data_file is None: + return + + file = ClientFile(model_data_file) + self._model_file = ModelFile(file) + self._model_file.read() + + if self.state.view_mode == "none" or self.state.view_mode == "model-only": + self.state.view_mode = "model-only" + else: + self.state.view_mode = "both" + + + if self._event_file is not None: + self.state.all_model_data_uploaded = True + + + @change("event_data_file") + def event_data_file_uploaded(self, event_data_file, **kwargs): + if event_data_file is None: + return + + file = ClientFile(event_data_file) + self._event_file = EventFile(file) + self._event_file.read() + + + if self.state.view_mode == "none" or self.state.view_mode == "model-only": + self.state.view_mode = "model-only" + else: + self.state.view_mode = "both" + + if self._model_file is not None: + self.state.all_model_data_uploaded = True + + + @change("all_model_data_uploaded") + def model_data_uploaded(self, all_model_data_uploaded, **kwargs): + if not all_model_data_uploaded: + return + + heatmap.initialize(self.server, self._event_file) + network_time.initialize(self.server, self._model_file) + + # now we can update the model_layout + layout = self.LAYOUTS["model_layout"] + with layout: + with layout.content: + for name, view in layout.content.vis_views.items(): + view.clear() + with view: + client.ServerTemplate(name=name) + + + @controller.set("view_update") + def update_views_time(self): + self.ctrl.on_ross_time_range_changed() + + + def create_vis(self, layout, grid_view_name, template_name, key): + with vuetify.VCard(#style="height: 100%;", + key=key, + height=400, + width=600 + ): + with vuetify.VCardTitle(classes="py-1 px-1"): + with vuetify.VMenu(offset_y=True): + with vuetify.Template( + v_slot_activator="{ on, attrs }" + ): + with vuetify.VBtn( + icon=True, + small=True, + v_bind="attrs", + v_on="on", + ): + vuetify.VIcon( + v_text=f"get(`{grid_view_name}`).icon" + ) + html.Div( + f"{{{{ get(`{grid_view_name}`).label }}}}", + classes="ml-1 text-subtitle-2", + ) + with vuetify.VList(dense=True): + with vuetify.VListItem( + v_for="(option, index) in grid_options", + key="index", + click=f""" + set(`{grid_view_name}`, option); + {key}++; + """, + ): + with vuetify.VListItemIcon(): + vuetify.VIcon(v_text="option.icon") + vuetify.VListItemTitle("{{ option.label }}") + vuetify.VDivider() + style = "; ".join( + [ + "position: relative", + "height: calc(100% - 37px)", + "overflow: auto", + ] + ) + layout.content.vis_views[template_name] = vuetify.VCardText(style=style, classes="drag_ignore") + with layout.content.vis_views[template_name]: + client.ServerTemplate(name=f"{template_name}_init") + + + def _create_model_vis_view(self, layout): + with vuetify.VContainer(v_if="all_model_data_uploaded == true"): + with vuetify.VRow(): + with vuetify.VCol(cols="12"): + self.create_vis(layout, "model_grid_view_heatmap", "heatmap", "model_grid_item_dirty_key") + with vuetify.VRow(): + with vuetify.VCol(cols="20"): + self.create_vis(layout, "model_grid_view_network_time_plot", "network_time_plot", "model_grid_item_dirty_key") + + + def _create_sim_engine_vis_view(self, layout): + with vuetify.VContainer(v_if="sim_engine_data_uploaded == true"): + with vuetify.VRow(): + with vuetify.VCol(cols="12"): + self.create_vis(layout, "engine_grid_view_parallel_coords", "parallel_coords", "engine_grid_item_dirty_key") + with vuetify.VRow(): + with vuetify.VCol(cols="12"): + self.create_vis(layout, "engine_grid_view_scatter_plot", "scatter_plot", "engine_grid_item_dirty_key") + with vuetify.VRow(): + with vuetify.VCol(cols="12"): + self.create_vis(layout, "engine_grid_view_time_plot", "time_plot", "engine_grid_item_dirty_key") + + + def _build_ui(self, *args, **kwargs): + with RouterViewLayout(self.server, "/"): + with vuetify.VCard(): + vuetify.VCardTitle("Getting Started") + vuetify.VCardText( + """ + To get started, load a file for either simulation engine data + or model level data on the left. + """ + ) + + with RouterViewLayout(self.server, "/model"): + client.ServerTemplate(name="model_layout") + + with RouterViewLayout(self.server, "/engine"): + client.ServerTemplate(name="sim_engine_layout") + + with SinglePageWithDrawerLayout(self.server) as layout: + layout.title.set_text("Visualization Dashboard") + layout.root.classes = ("{ busy: trame__busy }",) + + with layout.toolbar as toolbar: + toolbar.clear() + + toolbar.height = 50 + + vuetify.VSpacer() + + with html.Div( + style="width: 25px", + classes="mr-2", + ): + vuetify.VProgressCircular( + indeterminate=True, + v_show=("trame__busy",), + style="background-color: lightgray; border-radius: 50%", + background_opacity=1, + bg_color="#01549b", + color="#04a94d", + size=16, + width=3, + ) + + vuetify.VSpacer() + + with vuetify.VBtn(value="home", to="/"): + vuetify.VIcon("mdi-home") + + + with layout.drawer as drawer: + drawer.width = 325 + with vuetify.VCard(): + vuetify.VCardTitle("Simulation Engine Data") + vuetify.VFileInput(v_model=("sim_engine_file", None), label="Simulation Engine File") + with vuetify.VCard(): + vuetify.VCardTitle("Model Data") + vuetify.VFileInput(v_model=("model_data_file", None), label="Model Data File") + vuetify.VFileInput(v_model=("event_data_file", None), label="Event Data File") + with vuetify.VCard(): + vuetify.VCardTitle("Choose Visualizations") + vuetify.VBtn("View Simulation Engine Visualizations", to="/engine") + vuetify.VBtn("View Model Visualizations", to="/model") + vuetify.VBtn("View Combined", to="/both") + + with layout.content: + with vuetify.VContainer( + style="user-select: none; width=100%;", + ): + router.RouterView() + + return layout + + + +# this was the original way of doing things, taken from VeraCore, but i couldn't get it +# to work correctly when uploading files (as opposed to providing files via the command line). +# I ended up not using the grid layout, but it's very clunky so I'm leaving this code here +# for now in case I want to go back to it. +# other methods after this are related to how it was done before, but not using them +# in the new way of setting things up + def _build_ui_old(self, *args, **kwargs): + _available_view_types = ["scatter_plot", "parallel_coordinates"] + + # Setup main layout + with SinglePageWithDrawerLayout(self.server) as layout: + layout.root.classes = ("{ busy: trame__busy }",) + + # Toolbar + with layout.toolbar as toolbar: + toolbar.clear() + + toolbar.height = 40 + + vuetify.VSpacer() + + with html.Div( + style="width: 25px", + classes="mr-2", + ): + vuetify.VProgressCircular( + indeterminate=True, + v_show=("trame__busy",), + style="background-color: lightgray; border-radius: 50%", + background_opacity=1, + bg_color="#01549b", + color="#04a94d", + size=16, + width=3, + ) + + #removing this messes up the parallel coords graph for some reason + # but it doesn't matter that we removed the ref to the ross_file for array names + vuetify.VSelect( + v_model=("selected_array", "events_processed"), + items=( + "available_arrays", + [ + { "test": "test"} + ], + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) + + # the following code was added for adding new views, but it doesn't fully work yet, + # so commenting out for now + #vuetify.VSelect( + # v_model=("selected_view", "scatter_plot"), + # items=( + # "available_views", + # [ + # dict(text=key.replace("_", " ").title(), value=key) + # for key in _available_view_types + # ], + # ), + # hide_details=True, + # dense=True, + # style="max-width: 220px", + #) + + #with vuetify.VBtn(icon=True, click=self.ctrl.grid_add_view): + # vuetify.VIcon("mdi-plus") + + # drawer components + with layout.drawer as drawer: + drawer.width = 325 + self.vis_selection() + vuetify.VDivider(classes="mb-2") + self.sim_engine_card() + self.model_vis_card() + + + # Main content + with layout.content: +# print(f'self.state.grid_layout : {self.state.grid_layout}') + layout.content.style = "overflow: auto; margin: 36px 0px 35px; padding: 0;" + with vuetify.VContainer( + fluid=True, + classes="pa-0 fill-height", + style="user-select: none;", + ): + # welcome page when no files have been loaded + with vuetify.VCard(v_if=("view_mode == 'none'"), classes = "ma-8"): + vuetify.VCardText("Getting Started") + vuetify.VCardText( + """ + To get started, load a file for either simulation engine data + or model level data on the left. + """ + ) + + # now we can create 3 views, sim engine only, + # model data only + # both together + # need some way to specify for each vis type what kind it is + # then the grid layout can loop through that? + + # model data vis only + with grid.GridLayout( + v_if="view_mode == 'model-only'", + layout=("grid_layout", []), + row_height=30, + #is_draggable="draggable", + vertical_compact=True, + style="width: 100%; height: 100%;", + ): + with grid.GridItem( + v_for="item in grid_layout", + key="item.i", + v_bind="item", + style="touch-action: none;", + drag_ignore_from=".drag_ignore", + ): + with vuetify.VCard( + style="height: 100%;", + key="grid_item_dirty_key", + ): + with vuetify.VCardTitle(classes="py-1 px-1"): + with vuetify.VMenu(offset_y=True): + with vuetify.Template( + v_slot_activator="{ on, attrs }" + ): + with vuetify.VBtn( + icon=True, + small=True, + v_bind="attrs", + v_on="on", + ): + vuetify.VIcon( + v_text="get(`grid_view_${item.i}`).icon" + ) + html.Div( + "{{ get(`grid_view_${item.i}`).label }}", + classes="ml-1 text-subtitle-2", + ) + with vuetify.VList(dense=True): + with vuetify.VListItem( + v_for="(option, index) in grid_options", + key="index", + click=""" + set(`grid_view_${item.i}`, option); + grid_item_dirty_key++; + """, + ): + with vuetify.VListItemIcon(): + vuetify.VIcon(v_text="option.icon") + vuetify.VListItemTitle("{{ option.label }}") + vuetify.VSpacer() + with vuetify.VBtn( + icon=True, + x_small=True, + click=(self.ctrl.grid_remove_view, "[item.i]"), + ): + vuetify.VIcon( + "mdi-delete-forever-outline", small=True + ) + vuetify.VDivider() + + style = "; ".join( + [ + "position: relative", + "height: calc(100% - 37px)", + "overflow: auto", + ] + ) + with vuetify.VCardText(style=style, classes="drag_ignore"): + # Add template for value of get(`grid_view_${item.i}`) + client.ServerTemplate( + name=("get(`grid_view_${item.i}`).name",) + ) + return layout + + + def __init_old(self, server_or_name=None) -> None: + + self.state.setdefault("grid_layout", []) + + self.state.sim_engine_layout = [ + {"x:": 0, "y": 0, "w": 8, "h": 10, "i": "0"}, + {"x:": 0, "y": 20, "w": 8, "h": 10, "i": "1"}, + {"x:": 0, "y": 10, "w": 4, "h": 10, "i": "2"}, + ] + self.state.model_layout = [ + #{"x:": 0, "y": 30, "w": 8, "h": 10, "i": 1}, + dict(x=4, y=10, w=4, h=10, i=1), + #{"x:": 4, "y": 10, "w": 4, "h": 10, "i": 1}, + ] + + self.state.current_layout = "none" + + # can be none, model-only, engine-only, both + self.state.view_mode = "none" + + if self._ross_file is not None and self._model_file is not None and self._event_file is not None: + self.state.view_mode = "both" + self.state.current_layout = "both" + elif self._ross_file is not None and self._model_file is None and self._event_file is None: + self.state.view_mode = "engine-only" + self.state.current_layout = "sim_engine" + elif self._ross_file is None and self._model_file is not None and self._event_file is not None: + self.state.view_mode = "model-only" + self.state.current_layout = "model" + + + self.state.setdefault("active_ui", None) + + + @controller.set("grid_remove_view") + def remove_view(self, view_id): + print(f'removing view id {view_id}') + self._available_view_ids.append(view_id) + print(f'avail view ids: {self._available_view_ids}') + # clear out the details of the previous view + self.state[f"grid_view_{view_id}"] = empty.OPTION + self.state.grid_layout = list( + filter(lambda item: item.get("i") != view_id, self.state.grid_layout) + ) + + + def actives_change(self, ids): + _id = ids[0] + if _id == "1": + self.state.active_ui = "engine" + elif _id == "2": + self.state.active_ui = "model" + else: + self.state.active_ui = "nothing" + + + def visibility_change(self, event): + _id = event["id"] + _visibility = event["visible"] + + if _id == "1": + # engine + pass + elif _id == "2": + #model + pass + + + def vis_selection(self): + trame.GitTree( + sources=( + "pipeline", + [ + {"id": "1", "parent": "0", "visible": 1, "name": "Simulation Engine"}, + {"id": "2", "parent": "1", "visible": 1, "name": "Network Model"}, + ], + ), + actives_change=(self.actives_change, "[$event]"), + visibility_change=(self.visibility_change, "[$event]"), + ) + + + def ui_card(self, title, ui_name): + with vuetify.VCard(v_show=f"active_ui == '{ui_name}'"): + vuetify.VCardTitle( + title, + classes="grey lighten-1 py-1 grey--text text--darken-3", + style="user-select: none; cursor: pointer", + hide_details=True, + dense=True, + ) + content = vuetify.VCardText(classes="py-2") + return content + + + def sim_engine_card(self): + with self.ui_card(title="Simulation Engine", ui_name="engine"): + vuetify.VFileInput(v_model=("sim_engine_file", None), label="Simulation Engine File") + + + def model_vis_card(self): + with self.ui_card(title="Network Model", ui_name="model"): + vuetify.VFileInput(v_model=("model_data_file", None), label="Model Data File") + vuetify.VFileInput(v_model=("event_data_file", None), label="Event Data File") \ No newline at end of file diff --git a/codes_dashboard/app/core.py b/codes_dashboard/app/core.py deleted file mode 100644 index 875ef20..0000000 --- a/codes_dashboard/app/core.py +++ /dev/null @@ -1,80 +0,0 @@ -from trame.app import get_server -from trame.decorators import TrameApp, change, controller -from trame.ui.vuetify3 import SinglePageLayout -from trame.widgets import vuetify3, vtk - - -# --------------------------------------------------------- -# Engine class -# --------------------------------------------------------- - - -@TrameApp() -class MyTrameApp: - def __init__(self, server=None): - self.server = get_server(server, client_type="vue3") - if self.server.hot_reload: - self.server.controller.on_server_reload.add(self._build_ui) - self.ui = self._build_ui() - - # Set state variable - self.state.trame__title = "CODES Dashboard" - self.state.resolution = 6 - - @property - def state(self): - return self.server.state - - @property - def ctrl(self): - return self.server.controller - - @controller.set("reset_resolution") - def reset_resolution(self): - self.state.resolution = 6 - - @change("resolution") - def on_resolution_change(self, resolution, **kwargs): - print(f">>> ENGINE(a): Slider updating resolution to {resolution}") - - def _build_ui(self, *args, **kwargs): - with SinglePageLayout(self.server) as layout: - # Toolbar - layout.title.set_text("CODES Dashboard") - with layout.toolbar: - vuetify3.VSpacer() - vuetify3.VSlider( # Add slider - v_model=( - "resolution", - 6, - ), # bind variable with an initial value of 6 - min=3, - max=60, - step=1, # slider range - dense=True, - hide_details=True, # presentation setup - ) - with vuetify3.VBtn(icon=True, click=self.ctrl.reset_camera): - vuetify3.VIcon("mdi-crop-free") - with vuetify3.VBtn(icon=True, click=self.reset_resolution): - vuetify3.VIcon("mdi-undo") - - # Main content - with layout.content: - with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"): - with vtk.VtkView() as vtk_view: # vtk.js view for local rendering - self.ctrl.reset_camera = ( - vtk_view.reset_camera - ) # Bind method to controller - with vtk.VtkGeometryRepresentation(): # Add representation to vtk.js view - vtk.VtkAlgorithm( # Add ConeSource to representation - vtk_class="vtkConeSource", # Set attribute value with no JS eval - state=( - "{ resolution }", - ), # Set attribute value with JS eval - ) - - # Footer - # layout.footer.hide() - - return layout diff --git a/codes_dashboard/app/core/__init__.py b/codes_dashboard/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codes_dashboard/app/core/event_trace_file.py b/codes_dashboard/app/core/event_trace_file.py new file mode 100644 index 0000000..f134485 --- /dev/null +++ b/codes_dashboard/app/core/event_trace_file.py @@ -0,0 +1,122 @@ + +import struct +from collections import namedtuple + +import pandas as pd + +from trame.app.file_upload import ClientFile + +class EventFile: + def __init__(self, filename): + if isinstance(filename, str): + self.f = open(filename, "rb") + self.content = self.f.read() + elif isinstance(filename, ClientFile): + self.content = filename.content + + self.md_format = "@IIfffI" + self.md_sz = struct.calcsize(self.md_format) + + # TODO: will need to figure out a way to not hardcode this + self.simplep2p_format = "@i" + self.simplep2p_size = struct.calcsize(self.simplep2p_format) + + self._use_send_time = True + self._time_variable = "virtual_send" + + def read(self): + sample_list = [] + + byte_pos = 0 + while True: + md_bytes = self.content[byte_pos: byte_pos+self.md_sz] + byte_pos += len(md_bytes) + if not md_bytes: + break + md_record = namedtuple("MD", "source_lp dest_lp virtual_send virtual_receive real_times sample_size") + md = md_record._make(struct.unpack(self.md_format, md_bytes)) + + if md.sample_size == self.simplep2p_size: + sp_bytes = self.content[byte_pos:byte_pos+self.simplep2p_size] + byte_pos += len(sp_bytes) + sp_record = namedtuple("SimpleP2P", "event_type") + sp_data = sp_record._make(struct.unpack(self.simplep2p_format, sp_bytes)) + df = pd.DataFrame([sp_data]) + df["source_lp"] = md.source_lp + df["dest_lp"] = md.dest_lp + df["virtual_send"] = md.virtual_send + df["virtual_receive"] = md.virtual_receive + sample_list.append(df) + elif md.sample_size == 0: + #print(f'source {md.source_lp}, dest {md.dest_lp}') + continue + else: + print(f'sample of size {md.sample_size} found') + + self._simplep2p_df = pd.concat(sample_list) + + self._min_time = self._simplep2p_df[self._time_variable].min() + self._max_time = self._simplep2p_df[self._time_variable].max() + + + def close(self): + self.f.close() + + #def read_file(self): + # self._df = pd.read_csv(self.filename, header=None, names=["LP ID", "Virtual Time", "Packets Sent", "Packets Received", "Bytes Sent", "Bytes Received"]) + + # self._min_time = self._df["Virtual Time"].min() + # self._max_time = self._df["Virtual Time"].max() + # print(f'min time is {self._min_time}') + # print(f'max time is {self._max_time}') + + @property + def max_time(self): + return self._max_time + + + @max_time.setter + def max_time(self, time): + self._max_time = time + print(f'max time is {self._max_time}') + + + @property + def min_time(self): + return self._min_time + + + @min_time.setter + def min_time(self, time): + self._min_time = time + print(f'min time is {self._min_time}') + + @property + def network_df(self): + return self._simplep2p_df[ + (self._simplep2p_df[self._time_variable] >= self._min_time) & + (self._simplep2p_df[self._time_variable] <= self._max_time)] #& + #(self._simplep2p_df['source_lp'].isin([1, 2, 4, 6, 8])) & + #(self._simplep2p_df['dest_lp'].isin([1, 2, 4, 6, 8]))] + + + def reset_time_range(self): + print("resetting time range") + self._min_time = self._simplep2p_df[self._time_variable].min() + self._max_time = self._simplep2p_df[self._time_variable].max() + print(f'\tmin time is {self._min_time}') + print(f'\tmax time is {self._max_time}') + + + @property + def use_send_time(self): + return self._use_send_time + + @use_send_time.setter + def use_send_time(self, flag): + self._use_send_time = flag + if self._use_send_time: + self._time_variable = "virtual_send" + else: + self._time_variable = "virtual_receive" + self.reset_time_range() \ No newline at end of file diff --git a/codes_dashboard/app/core/model_file.py b/codes_dashboard/app/core/model_file.py new file mode 100644 index 0000000..289d344 --- /dev/null +++ b/codes_dashboard/app/core/model_file.py @@ -0,0 +1,110 @@ +import struct +from collections import namedtuple + +import pandas as pd + +from trame.app.file_upload import ClientFile + +#note this actually reads the analysis lps files, which i believe would include engine data as well, if collected +class ModelFile: + def __init__(self, filename): + if isinstance(filename, str): + self.f = open(filename, "rb") + self.content = self.f.read() + elif isinstance(filename, ClientFile): + self.content = filename.content + + self.md_format = "@QLLddii" + self.md_sz = struct.calcsize(self.md_format) + + # TODO: will need to figure out a way to not hardcode this + self.simplep2p_format = "@Qlldlld" + self.simplep2p_size = struct.calcsize(self.simplep2p_format) + + self._use_virtual_time = True + self._time_variable = "virtual_time" + + def read(self): + sample_list = [] + + byte_pos = 0 + while True: + md_bytes = self.content[byte_pos:byte_pos+self.md_sz] + byte_pos += len(md_bytes) + if not md_bytes: + break + md_record = namedtuple("MD", "lp_id kp_id pe_id virtual_time real_time sample_size flag") + md = md_record._make(struct.unpack(self.md_format, md_bytes)) + + # flag == 3 is model data + if md.flag == 3 and md.sample_size == self.simplep2p_size: + sp_bytes = self.content[byte_pos:byte_pos+self.simplep2p_size] + byte_pos += len(sp_bytes) + sp_record = namedtuple("SimpleP2P", "component_id send_count send_bytes send_time receive_count receive_bytes receive_time") + sp_data = sp_record._make(struct.unpack(self.simplep2p_format, sp_bytes)) + df = pd.DataFrame([sp_data]) + df["lp_id"] = md.lp_id + df["virtual_time"] = md.virtual_time + df["real_time"] = md.real_time + sample_list.append(df) + else: + print(f'sample of size {md.sample_size} found') + + self._simplep2p_df = pd.concat(sample_list) + + self._min_time = self._simplep2p_df[self._time_variable].min() + self._max_time = self._simplep2p_df[self._time_variable].max() + + + def close(self): + self.f.close() + + + @property + def max_time(self): + return self._max_time + + + @max_time.setter + def max_time(self, time): + self._max_time = time + print(f'max time is {self._max_time}') + + + @property + def min_time(self): + return self._min_time + + + @min_time.setter + def min_time(self, time): + self._min_time = time + print(f'min time is {self._min_time}') + + @property + def network_df(self): + return self._simplep2p_df[ + (self._simplep2p_df[self._time_variable] >= self._min_time) & + (self._simplep2p_df[self._time_variable] <= self._max_time)] + + + def reset_time_range(self): + print("resetting time range") + self._min_time = self._simplep2p_df[self._time_variable].min() + self._max_time = self._simplep2p_df[self._time_variable].max() + print(f'\tmin time is {self._min_time}') + print(f'\tmax time is {self._max_time}') + + + @property + def use_virtual_time(self): + return self._use_virtual_time + + @use_virtual_time.setter + def use_virtual_time(self, flag): + self._use_virtual_time = flag + if self._use_virtual_time: + self._time_variable = "virtual_time" + else: + self._time_variable = "real_time" + self.reset_time_range() \ No newline at end of file diff --git a/codes_dashboard/app/core/ross_binary_file.py b/codes_dashboard/app/core/ross_binary_file.py new file mode 100644 index 0000000..6052203 --- /dev/null +++ b/codes_dashboard/app/core/ross_binary_file.py @@ -0,0 +1,156 @@ +import struct +from collections import namedtuple + +import numpy as np +import pandas as pd + +from trame.app.file_upload import ClientFile + +# class inefficiently reads in and stores data in a pandas dataframe +# matches the structs in ross/core/instrumentation/st-instrumentation.h +# with each row in the df being an instance of the struct +# TODO: need a way to pull in model data +class ROSSFile: + def __init__(self, filename): + print("init ROSSFile") + if isinstance(filename, str): + self.f = open(filename, "rb") + self.content = self.f.read() + elif isinstance(filename, ClientFile): + self.content = filename.content + + self.engine_md_format = "@2i2d" + self.engine_md_size = struct.calcsize(self.engine_md_format) + + self.engine_pe_format = "@13I13f" + self.engine_pe_size = struct.calcsize(self.engine_pe_format) + + self.engine_kp_format = "@9I2f" + self.engine_kp_size = struct.calcsize(self.engine_kp_format) + + self.engine_lp_format = "@8If" + self.engine_lp_size = struct.calcsize(self.engine_lp_format) + + self._use_virtual_time = True + self._time_variable = "virtual_time" + + + def read(self): + pe_list = [] + kp_list = [] + lp_list = [] + + byte_pos = 0 + while True: + print(f'byte pos {byte_pos}') + print(f'file len {len(self.content)}') + md_bytes = self.content[byte_pos:byte_pos+self.engine_md_size] + byte_pos += len(md_bytes) + print(f'byte pos {byte_pos}') + if not md_bytes: + break + md_record = namedtuple("MD", "flag sample_size virtual_time real_time") + md = md_record._make(struct.unpack(self.engine_md_format, md_bytes)) + #print(f'flag: {md[0]}, sample size: {md[1]}, virtual ts: {md[2]}, real ts: {md[3]}') + + if md.sample_size == self.engine_pe_size: + #print("found a pe struct") + pe_bytes = self.content[byte_pos:byte_pos+self.engine_pe_size] + byte_pos += len(pe_bytes) + print(f'byte pos {byte_pos}') + pe_record = namedtuple("PE", "PE_ID events_processed events_aborted events_rolled_back total_rollbacks secondary_rollbacks fossil_collection_attempts pq_queue_size network_sends network_reads number_gvt pe_event_ties all_reduce efficiency network_read_time network_other_time gvt_time fossil_collect_time event_abort_time event_process_time pq_time rollback_time cancel_q_time avl_time buddy_time lz4_time") + pe_data = pe_record._make(struct.unpack(self.engine_pe_format, pe_bytes)) + df = pd.DataFrame([pe_data]) + df["virtual_time"] = md.virtual_time + df["real_time"] = md.real_time + pe_list.append(df) + elif md.sample_size == self.engine_kp_size: + #print("found a kp struct") + kp_bytes = self.content[byte_pos:byte_pos+self.engine_kp_size] + byte_pos += len(kp_bytes) + print(f'byte pos {byte_pos}') + kp_record = namedtuple("KP", "PE_ID KP_ID events_processed events_abort events_rolled_back total_rollbacks secondary_rollbacks network_sends network_reads time_ahead_gvt efficiency") + kp_data = kp_record._make(struct.unpack(self.engine_kp_format, kp_bytes)) + df = pd.DataFrame([kp_data]) + df["virtual_time"] = md.virtual_time + df["real_time"] = md.real_time + kp_list.append(df) + elif md.sample_size == self.engine_lp_size: + #print("found a lp struct") + lp_bytes = self.content[byte_pos:byte_pos+self.engine_lp_size] + byte_pos += len(lp_bytes) + print(f'byte pos {byte_pos}') + lp_record = namedtuple("LP", "PE_ID KP_ID LP_ID events_processed events_abort events_rolled_back network_sends network_reads efficiency") + lp_data = lp_record._make(struct.unpack(self.engine_lp_format, lp_bytes)) + df = pd.DataFrame([lp_data]) + df["virtual_time"] = md.virtual_time + df["real_time"] = md.real_time + lp_list.append(df) + else: + print("ERROR: found incorrect struct size") + self._pe_engine_df = pd.concat(pe_list) + self._kp_engine_df = pd.concat(kp_list) + self._lp_engine_df = pd.concat(lp_list) + + self._min_time = self._pe_engine_df[self._time_variable].min() + self._max_time = self._pe_engine_df[self._time_variable].max() + print(f'min time is {self._min_time}') + print(f'max time is {self._max_time}') + + + def close(self): + self.f.close() + + + @property + def max_time(self): + return self._max_time + + + @max_time.setter + def max_time(self, time): + self._max_time = time + print(f'max time is {self._max_time}') + + + @property + def min_time(self): + return self._min_time + + + @min_time.setter + def min_time(self, time): + self._min_time = time + print(f'min time is {self._min_time}') + + + @property + def pe_engine_df(self): + print("pe_engine_df") + print(f'\tmin time is {self._min_time}') + print(f'\tmax time is {self._max_time}') + return self._pe_engine_df[ + (self._pe_engine_df[self._time_variable] >= self._min_time) & + (self._pe_engine_df[self._time_variable] <= self._max_time)] + + + def reset_time_range(self): + print("resetting time range") + self._min_time = self._pe_engine_df[self._time_variable].min() + self._max_time = self._pe_engine_df[self._time_variable].max() + print(f'\tmin time is {self._min_time}') + print(f'\tmax time is {self._max_time}') + + + @property + def use_virtual_time(self): + return self._use_virtual_time + + @use_virtual_time.setter + def use_virtual_time(self, flag): + self._use_virtual_time = flag + if self._use_virtual_time: + self._time_variable = "virtual_time" + else: + self._time_variable = "real_time" + self.reset_time_range() diff --git a/codes_dashboard/app/main.py b/codes_dashboard/app/main.py index 45df140..5f2e3ce 100644 --- a/codes_dashboard/app/main.py +++ b/codes_dashboard/app/main.py @@ -1,10 +1,8 @@ -from .core import MyTrameApp - +from .CodesDashboard import CodesDashboard def main(server=None, **kwargs): - app = MyTrameApp(server) + app = CodesDashboard(server) app.server.start(**kwargs) - if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/codes_dashboard/app/ui/__init__.py b/codes_dashboard/app/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codes_dashboard/app/ui/empty.py b/codes_dashboard/app/ui/empty.py new file mode 100644 index 0000000..05f2968 --- /dev/null +++ b/codes_dashboard/app/ui/empty.py @@ -0,0 +1,38 @@ +from trame.ui.html import DivLayout +from trame.widgets import plotly, vuetify + +OPTION = { + "name": "empty", + "label": "Undefined content", + "icon": "mdi-help-circle-outline", +} + + +def init_options(server): + state = server.state + + if OPTION not in state.grid_options: + state.grid_options.append(OPTION) + + +# Created this because it seems that I need to create some kind of initial +# plotly figure, so that way once data does get loaded, the figure will show up +# without doing this, the figure will never show up +def init_empty_vis(server, template_name): + state, ctrl = server.state, server.controller + + with DivLayout(server, template_name=f'{template_name}_init') as layout: + layout.root.style = "height: 100%; width: 100%;" + + style = "; ".join( + [ + "width: 100%", + "height: 80%", + "user-select: none", + ] + ) + layout.figure = plotly.Figure( + display_logo=False, + display_mode_bar=False, + style=style, + ) \ No newline at end of file diff --git a/codes_dashboard/app/ui/heatmap.py b/codes_dashboard/app/ui/heatmap.py new file mode 100644 index 0000000..2861d6a --- /dev/null +++ b/codes_dashboard/app/ui/heatmap.py @@ -0,0 +1,120 @@ +import plotly.express as px +import numpy as np + +from trame.ui.html import DivLayout +from trame.widgets import plotly, vuetify + +from . import empty + +OPTION = { + "name": "heatmap", + "label": "Heatmap", + "icon": "mdi-chart-box", +} + +TEMPLATE_NAME = "heatmap" + +def init_options(server): + if OPTION not in server.state.grid_options: + server.state.grid_options.append(OPTION) + + empty.init_empty_vis(server, TEMPLATE_NAME) + +# heatmap of the selected variable showing connections between the +# so when we don't preload data, this network_file is None and we can't do anything +# and it can't be updated later? +# maybe we should make this a class that can be updated as things are loaded +# this would also let us have multiple instances of a vis so that we could have multiple heatmaps +def initialize(server, network_file): + state, ctrl = server.state, server.controller + + # lets provide the ability to color by either the count of messages or the amount of + # data sent. right now this is the same thing (Because all messages are the same size). + # but this could be different in the future, so lets go ahead and support it because then it's set + # up the same way all the other graphs are + def create_heatmap(selected_heatmap_variable): + # so we have our event trace which has the source and dest, send and receive times, and event type + print("CREATE HEATMAP CALLED") + if not hasattr(network_file, "network_df") or network_file.network_df is None: + return None + df = network_file.network_df + matrix = df.groupby(['source_lp', 'dest_lp']).size().unstack(fill_value=0) + #for index in matrix.index: + # if index in matrix.columns: + # matrix.at[index, index] = 0 + + print(matrix) + + # TODO: make it so you can get the number of bytes sent + state.last_heatmap_variable = selected_heatmap_variable + + figure = px.imshow(matrix) + + figure.update_layout(margin=dict(t=0, b=0, l=0, r=0), + xaxis_title="Receiving LP ID", + yaxis_title="Sending LP ID", + coloraxis_colorbar=dict(title=selected_heatmap_variable)) + return figure + + @state.change( + "selected_heatmap_variable" + ) + @ctrl.add("on_ross_active_state_index_changed") + def on_cell_change( + selected_heatmap_variable, + **kwargs + ): + ctrl.update_heatmap(create_heatmap(selected_heatmap_variable)) + + @ctrl.add("on_ross_time_range_changed") + def on_time_change(): + print("updating heatmap for time") + ctrl.update_heatmap(create_heatmap(state.last_heatmap_variable)) + + @state.change("event_file_uploaded") + def on_event_file_uploaded(event_file_uploaded, **kwargs): + if event_file_uploaded: + ctrl.update_heatmap(create_heatmap(state.last_heatmap_variable)) + + @state.change("update_vis") + def on_update_vis(update_vis, **kwargs): + if update_vis: + ctrl.update_heatmap(create_heatmap(state.last_heatmap_variable)) + + with DivLayout(server, template_name=TEMPLATE_NAME) as layout: + layout.root.style = "height: 100%; width: 100%;" + + style = "; ".join( + [ + "width: 100%", + "height: 80%", + "user-select: none", + ] + ) + figure = plotly.Figure( + display_logo=False, + display_mode_bar=False, + style=style, + # selected=(on_event, "["selected", utils.safe($event)]"), + # hover=(on_event, "["hover", utils.safe($event)]"), + # selecting=(on_event, "["selecting", $event]"), + # unhover=(on_event, "["unhover", $event]"), + ) + ctrl.update_heatmap = figure.update + + with vuetify.VRow(classes="pt-2", dense=True): + with vuetify.VCol(cols="5"): + array_list = ["num_messages", "bytes_sent"] + arrays = [ + dict(text=key.replace("_", " ").title(), value=key) + for key in array_list + ] + vuetify.VSelect( + v_model=("selected_heatmap_variable", "num_messages"), + items=( + "available_heatmap_arrays", arrays + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) \ No newline at end of file diff --git a/codes_dashboard/app/ui/network_time.py b/codes_dashboard/app/ui/network_time.py new file mode 100644 index 0000000..6977324 --- /dev/null +++ b/codes_dashboard/app/ui/network_time.py @@ -0,0 +1,146 @@ +import plotly.express as px + +from trame.ui.html import DivLayout +from trame.widgets import html, plotly, vuetify + +from . import empty + +OPTION = { + "name": "network_time_plot", + "label": "Network Time Plot", + "icon": "mdi-chart-network", +} + +TEMPLATE_NAME = "network_time_plot" + +def init_options(server): + if OPTION not in server.state.grid_options: + server.state.grid_options.append(OPTION) + + empty.init_empty_vis(server, TEMPLATE_NAME) + +# This plots some PE array over network time. +# Zooming in on the graph will grab x-axis values so we can update +# other views based on the time selection +def initialize(server, model_file): + state, ctrl = server.state, server.controller + + def create_line(selected_network_time_array, selected_network_time_type_array): + if not hasattr(model_file, "network_df") or model_file.network_df is None: + return None + if selected_network_time_type_array == "virtual_time": + model_file.use_virtual_time = True + else: + model_file.use_virtual_time = False + + df = model_file.network_df + + # need to keep track of whether we're using real or virtual time, + # so we can filter the data appropriately + state.selected_network_time_type_array = selected_network_time_type_array + + kwargs = { + "x": selected_network_time_type_array, + "y": selected_network_time_array, + "color": "lp_id", + "labels": { + selected_network_time_type_array: selected_network_time_type_array.replace("_", " ").title(), + selected_network_time_array: selected_network_time_array.replace("_", " ").title(), + "lp_id": "LP ID" + }, + } + figure = px.line(df, **kwargs) + + figure.update_layout(margin=dict(t=0, b=0, l=0, r=0)) + return figure + + @state.change("selected_network_time_array", + "selected_network_time_type_array") + def on_cell_change( + selected_network_time_array, + selected_network_time_type_array, + **kwargs + ): + ctrl.update_network_time_plot(create_line(selected_network_time_array, selected_network_time_type_array)) + + # TODO: will want to update the event file time too + def on_layout_change(event): + print("network time plot: on_layout_change") + view_changed = False + if "xaxis.range[0]" in event: + model_file.min_time = event["xaxis.range[0]"] + view_changed = True + if "xaxis.range[1]" in event: + model_file.max_time = event["xaxis.range[1]"] + view_changed = True + if view_changed: + ctrl.view_update() + + def on_double_click(): + print("network time plot: on_double_click") + model_file.reset_time_range() + ctrl.view_update() + + def get_array_list(): + if not hasattr(model_file, "network_df") or model_file.network_df is None: + return [] + array_list = list(model_file.network_df.columns) + if "lp_id" in array_list: + array_list.remove("lp_id") + if "component_id" in array_list: + array_list.remove("component_id") + if "real_time" in array_list: + array_list.remove("real_time") + if "virtual_time" in array_list: + array_list.remove("virtual_time") + return array_list + + with DivLayout(server, template_name=TEMPLATE_NAME) as layout: + layout.root.style = "height: 100%; width: 100%;" + + style = "; ".join( + [ + "width: 100%", + "height: 80%", + "user-select: none", + ] + ) + figure = plotly.Figure( + display_logo=False, + display_mode_bar=False, + style=style, + relayout=(on_layout_change, "[$event]"), + double_click=(on_double_click, ""), + ) + ctrl.update_network_time_plot = figure.update + + with vuetify.VRow(classes="pt-2", dense=True): + with vuetify.VCol(cols="4"): + array_list = get_array_list() + arrays = [ + dict(text=key.replace("_", " ").title(), value=key) + for key in array_list + ] + vuetify.VSelect( + v_model=("selected_network_time_array", "send_count"), + items=("available_network_time_arrays", arrays), + hide_details=True, + dense=True, + style="max-width: 220px", + ) + + time_list = ["virtual_time", "real_time"] + with vuetify.VCol(cols="4"): + vuetify.VSelect( + v_model=("selected_network_time_type_array", "virtual_time"), + items=( + "available_network_time_type_arrays", + [ + dict(text=key.replace("_", " ").title(), value=key) + for key in time_list + ], + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) diff --git a/codes_dashboard/app/ui/parallel_coords.py b/codes_dashboard/app/ui/parallel_coords.py new file mode 100644 index 0000000..f43dd46 --- /dev/null +++ b/codes_dashboard/app/ui/parallel_coords.py @@ -0,0 +1,71 @@ +import plotly.express as px + +from trame.ui.html import DivLayout +from trame.widgets import plotly + +from . import empty + +OPTION = { + "name": "parallel_coords", + "label": "Parallel Coordinates", + "icon": "mdi-chart-line-stacked", +} + +TEMPLATE_NAME = "parallel_coords" + +def init_options(server): + if OPTION not in server.state.grid_options: + server.state.grid_options.append(OPTION) + + empty.init_empty_vis(server, TEMPLATE_NAME) + +def initialize(server, ross_file): + state, ctrl = server.state, server.controller + + def create_line(): + print("parcoords create_line called") + df = ross_file.pe_engine_df + + kwargs = { + "color": "PE_ID", + "dimensions": ["PE_ID", "events_processed", "events_rolled_back", "total_rollbacks", "secondary_rollbacks"], + "labels": {"PE_ID": "PE ID", "events_processed": "Events Processed", + "events_rolled_back": "Events Rolled Back", "total_rollbacks": "Total Rollbacks", + "secondary_rollbacks": "Secondary Rollbacks"} + } + figure = px.parallel_coordinates(df, **kwargs) + + figure.update_layout(margin=dict(t=40, b=20, l=25, r=20)) + return figure + + @ctrl.add("init_parallel_coords") + def on_cell_change(): + print("on_cell_change called") + ctrl.update_parallel_coords(create_line()) + + @ctrl.add("on_ross_time_range_changed") + def on_time_change(): + print("updating par coords for time") + ctrl.update_parallel_coords(create_line()) + + with DivLayout(server, template_name=TEMPLATE_NAME) as layout: + layout.root.style = "height: 100%; width: 100%;" + + style = "; ".join( + [ + "width: 100%", + "height: 100%", + "user-select: none", + ] + ) + figure = plotly.Figure( + display_logo=False, + display_mode_bar=False, + style=style, + # # selected=(on_event, "["selected", utils.safe($event)]"), + # # hover=(on_event, "["hover", utils.safe($event)]"), + # # selecting=(on_event, "["selecting", $event]"), + # # unhover=(on_event, "["unhover", $event]"), + ) + ctrl.update_parallel_coords = figure.update + diff --git a/codes_dashboard/app/ui/scatter_plot.py b/codes_dashboard/app/ui/scatter_plot.py new file mode 100644 index 0000000..06c3649 --- /dev/null +++ b/codes_dashboard/app/ui/scatter_plot.py @@ -0,0 +1,125 @@ +import plotly.express as px + +from trame.ui.html import DivLayout +from trame.widgets import plotly, vuetify + +from . import empty + +OPTION = { + "name": "scatter_plot", + "label": "Scatter Plot", + "icon": "mdi-chart-scatter-plot", +} + +TEMPLATE_NAME = "scatter_plot" + +def init_options(server): + if OPTION not in server.state.grid_options: + server.state.grid_options.append(OPTION) + + empty.init_empty_vis(server, TEMPLATE_NAME) + +# scatter plot of two variables +def initialize(server, ross_file): + state, ctrl = server.state, server.controller + + if OPTION not in state.grid_options: + state.grid_options.append(OPTION) + + def create_line(selected_scatter_array_x, selected_scatter_array_y): + df = ross_file.pe_engine_df + + # not sure if this is the best way to handle this. + # this is so we can call create_line, when the time changes, but + # we don't have the array selection. we save it in the state so + # we'll be able to access the values later when we call this + # on time change. + state.last_scatter_array_x = selected_scatter_array_x + state.last_scatter_array_y = selected_scatter_array_y + + kwargs = { + "x": selected_scatter_array_x, + "y": selected_scatter_array_y, + "color": "PE_ID", + "labels": { + selected_scatter_array_x: selected_scatter_array_x.replace("_", " ").title(), + selected_scatter_array_y: selected_scatter_array_y.replace("_", " ").title(), + "PE_ID": "PE ID" + }, + } + figure = px.scatter(df, **kwargs) + + figure.update_layout(margin=dict(t=0, b=0, l=0, r=0)) + return figure + + @state.change( + "selected_scatter_array_x", + "selected_scatter_array_y" + ) + @ctrl.add("on_ross_active_state_index_changed") + def on_cell_change( + selected_scatter_array_x, + selected_scatter_array_y, + **kwargs + ): + ctrl.update_scatter_plot(create_line(selected_scatter_array_x, selected_scatter_array_y)) + + @ctrl.add("on_ross_time_range_changed") + def on_time_change(): + print("updating scatter plot for time") + ctrl.update_scatter_plot(create_line(state.last_scatter_array_x, state.last_scatter_array_y)) + + with DivLayout(server, template_name=TEMPLATE_NAME) as layout: + layout.root.style = "height: 100%; width: 100%;" + + style = "; ".join( + [ + "width: 100%", + "height: 80%", + "user-select: none", + ] + ) + figure = plotly.Figure( + display_logo=False, + display_mode_bar=False, + style=style, + # selected=(on_event, "["selected", utils.safe($event)]"), + # hover=(on_event, "["hover", utils.safe($event)]"), + # selecting=(on_event, "["selecting", $event]"), + # unhover=(on_event, "["unhover", $event]"), + ) + ctrl.update_scatter_plot = figure.update + + with vuetify.VRow(classes="pt-2", dense=True): + with vuetify.VCol(cols="5"): + array_list = list(ross_file.pe_engine_df.columns) + if "PE_ID" in array_list: + array_list.remove("PE_ID") + if "real_time" in array_list: + array_list.remove("real_time") + if "virtual_time" in array_list: + array_list.remove("virtual_time") + arrays = [ + dict(text=key.replace("_", " ").title(), value=key) + for key in array_list + ] + vuetify.VSelect( + v_model=("selected_scatter_array_x", "events_processed"), + items=( + "available_scatter_arrays", arrays + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) + + with vuetify.VCol(cols="5"): + vuetify.VSelect( + v_model=("selected_scatter_array_y", "events_rolled_back"), + items=( + "available_scatter_arrays", arrays + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) diff --git a/codes_dashboard/app/ui/time_plot.py b/codes_dashboard/app/ui/time_plot.py new file mode 100644 index 0000000..cdd5508 --- /dev/null +++ b/codes_dashboard/app/ui/time_plot.py @@ -0,0 +1,145 @@ +import plotly.express as px + +from trame.ui.html import DivLayout +from trame.widgets import html, plotly, vuetify + +from . import empty + +OPTION = { + "name": "time_plot", + "label": "Time Plot", + "icon": "mdi-chart-line", +} + +TEMPLATE_NAME = "time_plot" + +def init_options(server): + if OPTION not in server.state.grid_options: + server.state.grid_options.append(OPTION) + + empty.init_empty_vis(server, TEMPLATE_NAME) + +# this plots the some PE array over either real or virtual time. +# zooming in on the graph will grab x-axis values so we can update +# other views based on the time selection +def initialize(server, ross_file): + state, ctrl = server.state, server.controller + + if OPTION not in state.grid_options: + state.grid_options.append(OPTION) + + def create_line(selected_time_array, selected_time_type_array): + if selected_time_type_array == "virtual_time": + ross_file.use_virtual_time = True + else: + ross_file.use_virtual_time = False + + df = ross_file.pe_engine_df + + # need to keep track of whether we're using real or virtual time, + # so we can filter the data appropriately + state.selected_time_type_array = selected_time_type_array + + kwargs = { + "x": selected_time_type_array, + "y": selected_time_array, + "color": "PE_ID", + "labels": { + selected_time_type_array: selected_time_type_array.replace("_", " ").title(), + selected_time_array: selected_time_array.replace("_", " ").title(), + "PE_ID": "PE ID" + }, + } + figure = px.line(df, **kwargs) + + figure.update_layout(margin=dict(t=0, b=0, l=0, r=0)) + return figure + + @state.change( + "selected_time_array", + "selected_time_type_array" + ) + def on_cell_change( + selected_time_array, + selected_time_type_array, + **kwargs + ): + ctrl.update_time_plot(create_line(selected_time_array, selected_time_type_array)) + + + def on_layout_change(event): + print("time plot: on_layout_change") + view_changed = False + if "xaxis.range[0]" in event: + ross_file.min_time = event["xaxis.range[0]"] + view_changed = True + if "xaxis.range[1]" in event: + ross_file.max_time = event["xaxis.range[1]"] + view_changed = True + if view_changed: + ctrl.view_update() + + + def on_double_click(): + print("time plot: on_double_click") + ross_file.reset_time_range() + ctrl.view_update() + + + with DivLayout(server, template_name=TEMPLATE_NAME) as layout: + layout.root.style = "height: 100%; width: 100%;" + + style = "; ".join( + [ + "width: 100%", + "height: 80%", + "user-select: none", + ] + ) + figure = plotly.Figure( + display_logo=False, + display_mode_bar=False, + style=style, + relayout=(on_layout_change, "[$event]"), + double_click=(on_double_click, ""), + ) + ctrl.update_time_plot = figure.update + + with vuetify.VRow(classes="pt-2", dense=True): + with vuetify.VCol(cols="4"): + array_list = list(ross_file.pe_engine_df.columns) + if "PE_ID" in array_list: + array_list.remove("PE_ID") + if "real_time" in array_list: + array_list.remove("real_time") + if "virtual_time" in array_list: + array_list.remove("virtual_time") + arrays = [ + dict(text=key.replace("_", " ").title(), value=key) + for key in array_list + ] + vuetify.VSelect( + v_model=("selected_time_array", "events_processed"), + items=( + "available_time_arrays", arrays + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) + + time_list = ["virtual_time", "real_time"] + with vuetify.VCol(cols="4"): + vuetify.VSelect( + v_model=("selected_time_type_array", "virtual_time"), + items=( + "available_time_type_arrays", + [ + dict(text=key.replace("_", " ").title(), value=key) + for key in time_list + ], + ), + hide_details=True, + dense=True, + style="max-width: 220px", + ) diff --git a/examples/jupyter/show.ipynb b/examples/jupyter/show.ipynb index 2113902..b5c78ef 100644 --- a/examples/jupyter/show.ipynb +++ b/examples/jupyter/show.ipynb @@ -1,44 +1,44 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "d2589707", - "metadata": {}, - "outputs": [], - "source": [ - "from codes_dashboard.app.core import MyTrameApp\n", - "\n", - "app = MyTrameApp()\n", - "await app.ui.ready\n", - "app.ui" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "d2589707", + "metadata": {}, + "outputs": [], + "source": [ + "from codes_dashboard.app.CodesDashboard import CodesDashboard\n", + "\n", + "app = CodesDashboard()\n", + "await app.ui.ready\n", + "app.ui" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.9.9" + }, + "vscode": { + "interpreter": { + "hash": "077b396a86bc6b27a58ed1155668631492bc59fe85a729995524234d32fbc2c2" + } + } }, - "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.9.9" - }, - "vscode": { - "interpreter": { - "hash": "077b396a86bc6b27a58ed1155668631492bc59fe85a729995524234d32fbc2c2" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/setup.cfg b/setup.cfg index 1a503cb..dc4e9a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,14 +29,11 @@ include_package_data = True install_requires = trame trame-vuetify - trame-vtk - # vtk - # pandas - # numpy - # altair - # mpld3 - # plotly - # pydeck + trame-grid-layout + trame-plotly + pandas + numpy + plotly [options.entry_points] console_scripts = diff --git a/tests/data/ross-binary-data/esnet-model-inst-analysis-lps-router-output.csv b/tests/data/ross-binary-data/esnet-model-inst-analysis-lps-router-output.csv new file mode 100644 index 0000000..88817bc --- /dev/null +++ b/tests/data/ross-binary-data/esnet-model-inst-analysis-lps-router-output.csv @@ -0,0 +1,501 @@ +LP,KP,PE,virtual_time,comp_id,send_cnt,send_bytes,send_time,recv_cnt,recv_bytes,recv_time +8,8,0,1000.0,4,4,2048,0.0,0,0,0.0 +4,4,0,1000.0,2,0,0,0.0,0,0,0.0 +6,6,0,1000.0,3,0,0,0.0,0,0,0.0 +1,1,0,1000.0,0,4,2048,0.0,0,0,0.0 +2,2,0,1000.0,1,0,0,0.0,0,0,0.0 +4,4,0,2000.0,2,4,2048,0.0,0,0,0.0 +2,2,0,2000.0,1,0,0,0.0,0,0,0.0 +1,1,0,2000.0,0,12,6144,0.0,0,0,0.0 +6,6,0,2000.0,3,4,2048,0.0,0,0,0.0 +8,8,0,2000.0,4,8,4096,0.0,0,0,0.0 +4,4,0,3000.0,2,8,4096,0.0,0,0,0.0 +6,6,0,3000.0,3,4,2048,0.0,0,0,0.0 +1,1,0,3000.0,0,28,14336,0.0,0,0,0.0 +2,2,0,3000.0,1,0,0,0.0,0,0,0.0 +8,8,0,3000.0,4,20,10240,0.0,0,0,0.0 +8,8,0,4000.0,4,24,12288,0.0,0,0,0.0 +6,6,0,4000.0,3,4,2048,0.0,0,0,0.0 +4,4,0,4000.0,2,12,6144,0.0,0,0,0.0 +2,2,0,4000.0,1,0,0,0.0,0,0,0.0 +1,1,0,4000.0,0,28,14336,0.0,0,0,0.0 +8,8,0,5000.0,4,28,14336,0.0,0,0,0.0 +6,6,0,5000.0,3,20,10240,0.0,0,0,0.0 +4,4,0,5000.0,2,12,6144,0.0,0,0,0.0 +2,2,0,5000.0,1,0,0,0.0,0,0,0.0 +1,1,0,5000.0,0,32,16384,0.0,0,0,0.0 +8,8,0,6000.0,4,32,16384,0.0,0,0,0.0 +1,1,0,6000.0,0,36,18432,0.0,0,0,0.0 +4,4,0,6000.0,2,12,6144,0.0,0,0,0.0 +2,2,0,6000.0,1,0,0,0.0,0,0,0.0 +6,6,0,6000.0,3,20,10240,0.0,0,0,0.0 +1,1,0,7000.0,0,36,18432,0.0,0,0,0.0 +6,6,0,7000.0,3,28,14336,0.0,0,0,0.0 +4,4,0,7000.0,2,12,6144,0.0,0,0,0.0 +2,2,0,7000.0,1,0,0,0.0,0,0,0.0 +8,8,0,7000.0,4,32,16384,0.0,0,0,0.0 +8,8,0,8000.0,4,32,16384,0.0,0,0,0.0 +4,4,0,8000.0,2,12,6144,0.0,0,0,0.0 +2,2,0,8000.0,1,0,0,0.0,0,0,0.0 +6,6,0,8000.0,3,28,14336,0.0,0,0,0.0 +1,1,0,8000.0,0,36,18432,0.0,0,0,0.0 +8,8,0,9000.0,4,36,18432,0.0,0,0,0.0 +2,2,0,9000.0,1,0,0,0.0,0,0,0.0 +6,6,0,9000.0,3,40,20480,0.0,0,0,0.0 +4,4,0,9000.0,2,16,8192,0.0,0,0,0.0 +1,1,0,9000.0,0,40,20480,0.0,0,0,0.0 +6,6,0,10000.0,3,40,20480,0.0,0,0,0.0 +1,1,0,10000.0,0,40,20480,0.0,0,0,0.0 +4,4,0,10000.0,2,16,8192,0.0,0,0,0.0 +2,2,0,10000.0,1,0,0,0.0,0,0,0.0 +8,8,0,10000.0,4,40,20480,0.0,0,0,0.0 +4,4,0,11000.0,2,16,8192,0.0,0,0,0.0 +8,8,0,11000.0,4,52,26624,0.0,0,0,0.0 +1,1,0,11000.0,0,53,27136,0.0,0,0,0.0 +6,6,0,11000.0,3,40,20480,0.0,0,0,0.0 +2,2,0,11000.0,1,2,1024,0.0,2,1024,0.0 +4,4,0,12000.0,2,20,10240,0.0,0,0,0.0 +6,6,0,12000.0,3,44,22528,0.0,0,0,0.0 +1,1,0,12000.0,0,56,28672,0.0,0,0,0.0 +2,2,0,12000.0,1,4,2048,0.0,4,2048,0.0 +8,8,0,12000.0,4,56,28672,0.0,0,0,0.0 +8,8,0,13000.0,4,60,30720,0.0,0,0,0.0 +2,2,0,13000.0,1,4,2048,0.0,4,2048,0.0 +4,4,0,13000.0,2,24,12288,0.0,0,0,0.0 +1,1,0,13000.0,0,72,36864,0.0,0,0,0.0 +6,6,0,13000.0,3,44,22528,0.0,0,0,0.0 +4,4,0,14000.0,2,28,14336,0.0,0,0,0.0 +2,2,0,14000.0,1,4,2048,0.0,4,2048,0.0 +8,8,0,14000.0,4,64,32768,0.0,0,0,0.0 +6,6,0,14000.0,3,52,26624,0.0,0,0,0.0 +1,1,0,14000.0,0,80,40960,0.0,0,0,0.0 +6,6,0,15000.0,3,56,28672,0.0,0,0,0.0 +4,4,0,15000.0,2,32,16384,0.0,0,0,0.0 +1,1,0,15000.0,0,84,43008,0.0,0,0,0.0 +8,8,0,15000.0,4,76,38912,0.0,0,0,0.0 +2,2,0,15000.0,1,4,2048,0.0,4,2048,0.0 +6,6,0,16000.0,3,56,28672,0.0,0,0,0.0 +1,1,0,16000.0,0,84,43008,0.0,0,0,0.0 +4,4,0,16000.0,2,40,20480,0.0,0,0,0.0 +2,2,0,16000.0,1,4,2048,0.0,4,2048,0.0 +8,8,0,16000.0,4,80,40960,0.0,0,0,0.0 +1,1,0,17000.0,0,88,45056,0.0,0,0,0.0 +8,8,0,17000.0,4,80,40960,0.0,0,0,0.0 +2,2,0,17000.0,1,4,2048,0.0,4,2048,0.0 +6,6,0,17000.0,3,56,28672,0.0,0,0,0.0 +4,4,0,17000.0,2,40,20480,0.0,0,0,0.0 +1,1,0,18000.0,0,92,47104,0.0,0,0,0.0 +8,8,0,18000.0,4,88,45056,0.0,0,0,0.0 +2,2,0,18000.0,1,4,2048,0.0,4,2048,0.0 +6,6,0,18000.0,3,60,30720,0.0,0,0,0.0 +4,4,0,18000.0,2,40,20480,0.0,0,0,0.0 +1,1,0,19000.0,0,92,47104,0.0,0,0,0.0 +6,6,0,19000.0,3,68,34816,0.0,0,0,0.0 +4,4,0,19000.0,2,48,24576,0.0,0,0,0.0 +2,2,0,19000.0,1,4,2048,0.0,4,2048,0.0 +8,8,0,19000.0,4,88,45056,0.0,0,0,0.0 +6,6,0,20000.0,3,68,34816,0.0,0,0,0.0 +8,8,0,20000.0,4,88,45056,0.0,0,0,0.0 +4,4,0,20000.0,2,48,24576,0.0,0,0,0.0 +2,2,0,20000.0,1,5,2560,0.0,5,2560,0.0 +1,1,0,20000.0,0,100,51200,0.0,0,0,0.0 +6,6,0,21000.0,3,68,34816,0.0,1,512,0.0 +4,4,0,21000.0,2,56,28672,0.0,1,512,0.0 +2,2,0,21000.0,1,7,3584,0.0,7,3584,0.0 +8,8,0,21000.0,4,100,51200,0.0,0,0,0.0 +1,1,0,21000.0,0,100,51200,0.0,0,0,0.0 +4,4,0,22000.0,2,56,28672,0.0,1,512,0.0 +6,6,0,22000.0,3,76,38912,0.0,1,512,0.0 +8,8,0,22000.0,4,104,53248,0.0,1,512,0.0 +2,2,0,22000.0,1,8,4096,0.0,8,4096,0.0 +1,1,0,22000.0,0,100,51200,0.0,0,0,0.0 +1,1,0,23000.0,0,104,53248,0.0,0,0,0.0 +8,8,0,23000.0,4,104,53248,0.0,1,512,0.0 +4,4,0,23000.0,2,56,28672,0.0,1,512,0.0 +6,6,0,23000.0,3,76,38912,0.0,1,512,0.0 +2,2,0,23000.0,1,8,4096,0.0,8,4096,0.0 +2,2,0,24000.0,1,8,4096,0.0,8,4096,0.0 +1,1,0,24000.0,0,104,53248,0.0,0,0,0.0 +4,4,0,24000.0,2,64,32768,0.0,1,512,0.0 +6,6,0,24000.0,3,84,43008,0.0,1,512,0.0 +8,8,0,24000.0,4,104,53248,0.0,1,512,0.0 +4,4,0,25000.0,2,68,34816,0.0,1,512,0.0 +1,1,0,25000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,25000.0,4,104,53248,0.0,1,512,0.0 +2,2,0,25000.0,1,8,4096,0.0,8,4096,0.0 +6,6,0,25000.0,3,92,47104,0.0,1,512,0.0 +1,1,0,26000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,26000.0,2,72,36864,0.0,1,512,0.0 +8,8,0,26000.0,4,112,57344,0.0,1,512,0.0 +6,6,0,26000.0,3,104,53248,0.0,1,512,0.0 +2,2,0,26000.0,1,8,4096,0.0,8,4096,0.0 +4,4,0,27000.0,2,84,43008,0.0,1,512,0.0 +8,8,0,27000.0,4,112,57344,0.0,1,512,0.0 +6,6,0,27000.0,3,108,55296,0.0,1,512,0.0 +2,2,0,27000.0,1,8,4096,0.0,8,4096,0.0 +1,1,0,27000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,28000.0,4,112,57344,0.0,1,512,0.0 +2,2,0,28000.0,1,8,4096,0.0,8,4096,0.0 +4,4,0,28000.0,2,96,49152,0.0,1,512,0.0 +6,6,0,28000.0,3,108,55296,0.0,1,512,0.0 +1,1,0,28000.0,0,120,61440,0.0,0,0,0.0 +1,1,0,29000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,29000.0,1,8,4096,0.0,8,4096,0.0 +4,4,0,29000.0,2,108,55296,0.0,1,512,0.0 +8,8,0,29000.0,4,116,59392,0.0,1,512,0.0 +6,6,0,29000.0,3,108,55296,0.0,1,512,0.0 +2,2,0,30000.0,1,9,4608,0.0,9,4608,0.0 +8,8,0,30000.0,4,116,59392,0.0,1,512,0.0 +6,6,0,30000.0,3,112,57344,0.0,1,512,0.0 +1,1,0,30000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,30000.0,2,108,55296,0.0,2,1024,0.0 +1,1,0,31000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,31000.0,4,120,61440,0.0,1,512,0.0 +2,2,0,31000.0,1,11,5632,0.0,11,5632,0.0 +6,6,0,31000.0,3,112,57344,0.0,2,1024,0.0 +4,4,0,31000.0,2,112,57344,0.0,2,1024,0.0 +4,4,0,32000.0,2,116,59392,0.0,2,1024,0.0 +8,8,0,32000.0,4,120,61440,0.0,2,1024,0.0 +2,2,0,32000.0,1,12,6144,0.0,12,6144,0.0 +6,6,0,32000.0,3,112,57344,0.0,2,1024,0.0 +1,1,0,32000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,33000.0,2,116,59392,0.0,2,1024,0.0 +1,1,0,33000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,33000.0,4,120,61440,0.0,2,1024,0.0 +6,6,0,33000.0,3,120,61440,0.0,2,1024,0.0 +2,2,0,33000.0,1,12,6144,0.0,12,6144,0.0 +8,8,0,34000.0,4,120,61440,0.0,2,1024,0.0 +1,1,0,34000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,34000.0,1,12,6144,0.0,12,6144,0.0 +6,6,0,34000.0,3,120,61440,0.0,2,1024,0.0 +4,4,0,34000.0,2,120,61440,0.0,2,1024,0.0 +8,8,0,35000.0,4,120,61440,0.0,2,1024,0.0 +2,2,0,35000.0,1,12,6144,0.0,12,6144,0.0 +6,6,0,35000.0,3,120,61440,0.0,2,1024,0.0 +1,1,0,35000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,35000.0,2,120,61440,0.0,2,1024,0.0 +6,6,0,36000.0,3,120,61440,0.0,2,1024,0.0 +1,1,0,36000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,36000.0,1,12,6144,0.0,12,6144,0.0 +4,4,0,36000.0,2,120,61440,0.0,2,1024,0.0 +8,8,0,36000.0,4,120,61440,0.0,2,1024,0.0 +1,1,0,37000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,37000.0,1,12,6144,0.0,12,6144,0.0 +6,6,0,37000.0,3,120,61440,0.0,2,1024,0.0 +8,8,0,37000.0,4,120,61440,0.0,2,1024,0.0 +4,4,0,37000.0,2,120,61440,0.0,2,1024,0.0 +4,4,0,38000.0,2,120,61440,0.0,2,1024,0.0 +8,8,0,38000.0,4,120,61440,0.0,2,1024,0.0 +2,2,0,38000.0,1,12,6144,0.0,12,6144,0.0 +1,1,0,38000.0,0,120,61440,0.0,0,0,0.0 +6,6,0,38000.0,3,120,61440,0.0,2,1024,0.0 +6,6,0,39000.0,3,120,61440,0.0,2,1024,0.0 +4,4,0,39000.0,2,120,61440,0.0,2,1024,0.0 +1,1,0,39000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,39000.0,1,12,6144,0.0,12,6144,0.0 +8,8,0,39000.0,4,120,61440,0.0,2,1024,0.0 +4,4,0,40000.0,2,120,61440,0.0,3,1536,0.0 +1,1,0,40000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,40000.0,4,120,61440,0.0,2,1024,0.0 +6,6,0,40000.0,3,120,61440,0.0,2,1024,0.0 +2,2,0,40000.0,1,13,6656,0.0,13,6656,0.0 +2,2,0,41000.0,1,16,8192,0.0,16,8192,0.0 +8,8,0,41000.0,4,120,61440,0.0,2,1024,0.0 +6,6,0,41000.0,3,120,61440,0.0,3,1536,0.0 +4,4,0,41000.0,2,120,61440,0.0,3,1536,0.0 +1,1,0,41000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,42000.0,2,120,61440,0.0,3,1536,0.0 +8,8,0,42000.0,4,120,61440,0.0,3,1536,0.0 +2,2,0,42000.0,1,16,8192,0.0,16,8192,0.0 +6,6,0,42000.0,3,120,61440,0.0,3,1536,0.0 +1,1,0,42000.0,0,120,61440,0.0,0,0,0.0 +1,1,0,43000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,43000.0,2,120,61440,0.0,3,1536,0.0 +2,2,0,43000.0,1,16,8192,0.0,16,8192,0.0 +8,8,0,43000.0,4,120,61440,0.0,3,1536,0.0 +6,6,0,43000.0,3,120,61440,0.0,3,1536,0.0 +4,4,0,44000.0,2,120,61440,0.0,3,1536,0.0 +6,6,0,44000.0,3,120,61440,0.0,3,1536,0.0 +2,2,0,44000.0,1,16,8192,0.0,16,8192,0.0 +1,1,0,44000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,44000.0,4,120,61440,0.0,3,1536,0.0 +6,6,0,45000.0,3,120,61440,0.0,3,1536,0.0 +1,1,0,45000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,45000.0,4,120,61440,0.0,3,1536,0.0 +2,2,0,45000.0,1,16,8192,0.0,16,8192,0.0 +4,4,0,45000.0,2,120,61440,0.0,3,1536,0.0 +4,4,0,46000.0,2,120,61440,0.0,3,1536,0.0 +2,2,0,46000.0,1,16,8192,0.0,16,8192,0.0 +1,1,0,46000.0,0,120,61440,0.0,0,0,0.0 +6,6,0,46000.0,3,120,61440,0.0,3,1536,0.0 +8,8,0,46000.0,4,120,61440,0.0,3,1536,0.0 +1,1,0,47000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,47000.0,1,16,8192,0.0,16,8192,0.0 +8,8,0,47000.0,4,120,61440,0.0,3,1536,0.0 +4,4,0,47000.0,2,120,61440,0.0,3,1536,0.0 +6,6,0,47000.0,3,120,61440,0.0,3,1536,0.0 +1,1,0,48000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,48000.0,1,16,8192,0.0,16,8192,0.0 +8,8,0,48000.0,4,120,61440,0.0,3,1536,0.0 +4,4,0,48000.0,2,120,61440,0.0,3,1536,0.0 +6,6,0,48000.0,3,120,61440,0.0,3,1536,0.0 +6,6,0,49000.0,3,120,61440,0.0,3,1536,0.0 +1,1,0,49000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,49000.0,1,16,8192,0.0,16,8192,0.0 +8,8,0,49000.0,4,120,61440,0.0,3,1536,0.0 +4,4,0,49000.0,2,120,61440,0.0,3,1536,0.0 +6,6,0,50000.0,3,120,61440,0.0,3,1536,0.0 +8,8,0,50000.0,4,120,61440,0.0,3,1536,0.0 +2,2,0,50000.0,1,18,9216,0.0,18,9216,0.0 +4,4,0,50000.0,2,120,61440,0.0,4,2048,0.0 +1,1,0,50000.0,0,120,61440,0.0,0,0,0.0 +6,6,0,51000.0,3,120,61440,0.0,4,2048,0.0 +1,1,0,51000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,51000.0,1,20,10240,0.0,20,10240,0.0 +8,8,0,51000.0,4,120,61440,0.0,4,2048,0.0 +4,4,0,51000.0,2,120,61440,0.0,4,2048,0.0 +2,2,0,52000.0,1,20,10240,0.0,20,10240,0.0 +1,1,0,52000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,52000.0,2,120,61440,0.0,4,2048,0.0 +8,8,0,52000.0,4,120,61440,0.0,4,2048,0.0 +6,6,0,52000.0,3,120,61440,0.0,4,2048,0.0 +4,4,0,53000.0,2,120,61440,0.0,4,2048,0.0 +2,2,0,53000.0,1,20,10240,0.0,20,10240,0.0 +8,8,0,53000.0,4,120,61440,0.0,4,2048,0.0 +6,6,0,53000.0,3,120,61440,0.0,4,2048,0.0 +1,1,0,53000.0,0,120,61440,0.0,0,0,0.0 +6,6,0,54000.0,3,120,61440,0.0,4,2048,0.0 +4,4,0,54000.0,2,120,61440,0.0,4,2048,0.0 +2,2,0,54000.0,1,20,10240,0.0,20,10240,0.0 +8,8,0,54000.0,4,120,61440,0.0,4,2048,0.0 +1,1,0,54000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,55000.0,2,120,61440,0.0,4,2048,0.0 +1,1,0,55000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,55000.0,1,20,10240,0.0,20,10240,0.0 +8,8,0,55000.0,4,120,61440,0.0,4,2048,0.0 +6,6,0,55000.0,3,120,61440,0.0,4,2048,0.0 +8,8,0,56000.0,4,120,61440,0.0,4,2048,0.0 +2,2,0,56000.0,1,20,10240,0.0,20,10240,0.0 +6,6,0,56000.0,3,120,61440,0.0,4,2048,0.0 +1,1,0,56000.0,0,120,61440,0.0,0,0,0.0 +4,4,0,56000.0,2,120,61440,0.0,4,2048,0.0 +1,1,0,57000.0,0,120,61440,0.0,0,0,0.0 +8,8,0,57000.0,4,120,61440,0.0,4,2048,0.0 +6,6,0,57000.0,3,120,61440,0.0,4,2048,0.0 +2,2,0,57000.0,1,20,10240,0.0,20,10240,0.0 +4,4,0,57000.0,2,120,61440,0.0,4,2048,0.0 +4,4,0,58000.0,2,120,61440,0.0,4,2048,0.0 +1,1,0,58000.0,0,120,61440,0.0,0,0,0.0 +6,6,0,58000.0,3,120,61440,0.0,4,2048,0.0 +8,8,0,58000.0,4,120,61440,0.0,4,2048,0.0 +2,2,0,58000.0,1,20,10240,0.0,20,10240,0.0 +6,6,0,59000.0,3,120,61440,0.0,4,2048,0.0 +4,4,0,59000.0,2,120,61440,0.0,4,2048,0.0 +8,8,0,59000.0,4,120,61440,0.0,4,2048,0.0 +1,1,0,59000.0,0,120,61440,0.0,0,0,0.0 +2,2,0,59000.0,1,21,10752,0.0,21,10752,0.0 +8,8,0,60000.0,4,120,61440,0.0,4,2048,0.0 +6,6,0,60000.0,3,120,61440,0.0,5,2560,0.0 +2,2,0,60000.0,1,23,11776,0.0,23,11776,0.0 +4,4,0,60000.0,2,120,61440,0.0,5,2560,0.0 +1,1,0,60000.0,0,120,61440,0.0,1,512,0.0 +8,8,0,61000.0,4,120,61440,0.0,5,2560,0.0 +4,4,0,61000.0,2,120,61440,0.0,5,2560,0.0 +2,2,0,61000.0,1,24,12288,0.0,24,12288,0.0 +1,1,0,61000.0,0,120,61440,0.0,1,512,0.0 +6,6,0,61000.0,3,120,61440,0.0,5,2560,0.0 +8,8,0,62000.0,4,120,61440,0.0,5,2560,0.0 +4,4,0,62000.0,2,120,61440,0.0,5,2560,0.0 +1,1,0,62000.0,0,120,61440,0.0,1,512,0.0 +6,6,0,62000.0,3,120,61440,0.0,5,2560,0.0 +2,2,0,62000.0,1,24,12288,0.0,24,12288,0.0 +6,6,0,63000.0,3,120,61440,0.0,5,2560,0.0 +2,2,0,63000.0,1,24,12288,0.0,24,12288,0.0 +1,1,0,63000.0,0,120,61440,0.0,1,512,0.0 +4,4,0,63000.0,2,120,61440,0.0,5,2560,0.0 +8,8,0,63000.0,4,120,61440,0.0,5,2560,0.0 +6,6,0,64000.0,3,120,61440,0.0,5,2560,0.0 +2,2,0,64000.0,1,24,12288,0.0,24,12288,0.0 +4,4,0,64000.0,2,120,61440,0.0,5,2560,0.0 +1,1,0,64000.0,0,120,61440,0.0,1,512,0.0 +8,8,0,64000.0,4,120,61440,0.0,5,2560,0.0 +6,6,0,65000.0,3,120,61440,0.0,5,2560,0.0 +1,1,0,65000.0,0,120,61440,0.0,1,512,0.0 +8,8,0,65000.0,4,120,61440,0.0,5,2560,0.0 +2,2,0,65000.0,1,24,12288,0.0,24,12288,0.0 +4,4,0,65000.0,2,120,61440,0.0,5,2560,0.0 +8,8,0,66000.0,4,120,61440,0.0,5,2560,0.0 +4,4,0,66000.0,2,120,61440,0.0,5,2560,0.0 +2,2,0,66000.0,1,24,12288,0.0,24,12288,0.0 +6,6,0,66000.0,3,120,61440,0.0,5,2560,0.0 +1,1,0,66000.0,0,120,61440,0.0,1,512,0.0 +8,8,0,67000.0,4,120,61440,0.0,5,2560,0.0 +4,4,0,67000.0,2,120,61440,0.0,5,2560,0.0 +2,2,0,67000.0,1,24,12288,0.0,24,12288,0.0 +1,1,0,67000.0,0,120,61440,0.0,1,512,0.0 +6,6,0,67000.0,3,120,61440,0.0,5,2560,0.0 +8,8,0,68000.0,4,120,61440,0.0,5,2560,0.0 +1,1,0,68000.0,0,120,61440,0.0,1,512,0.0 +4,4,0,68000.0,2,120,61440,0.0,5,2560,0.0 +6,6,0,68000.0,3,120,61440,0.0,5,2560,0.0 +2,2,0,68000.0,1,24,12288,0.0,24,12288,0.0 +6,6,0,69000.0,3,120,61440,0.0,5,2560,0.0 +1,1,0,69000.0,0,120,61440,0.0,1,512,0.0 +4,4,0,69000.0,2,120,61440,0.0,6,3072,0.0 +2,2,0,69000.0,1,25,12800,0.0,25,12800,0.0 +8,8,0,69000.0,4,120,61440,0.0,5,2560,0.0 +1,1,0,70000.0,0,120,61440,0.0,2,1024,0.0 +8,8,0,70000.0,4,120,61440,0.0,5,2560,0.0 +4,4,0,70000.0,2,120,61440,0.0,6,3072,0.0 +2,2,0,70000.0,1,27,13824,0.0,27,13824,0.0 +6,6,0,70000.0,3,120,61440,0.0,6,3072,0.0 +4,4,0,71000.0,2,120,61440,0.0,6,3072,0.0 +2,2,0,71000.0,1,28,14336,0.0,28,14336,0.0 +6,6,0,71000.0,3,120,61440,0.0,6,3072,0.0 +1,1,0,71000.0,0,120,61440,0.0,2,1024,0.0 +8,8,0,71000.0,4,120,61440,0.0,6,3072,0.0 +1,1,0,72000.0,0,120,61440,0.0,2,1024,0.0 +8,8,0,72000.0,4,120,61440,0.0,6,3072,0.0 +2,2,0,72000.0,1,28,14336,0.0,28,14336,0.0 +4,4,0,72000.0,2,120,61440,0.0,6,3072,0.0 +6,6,0,72000.0,3,120,61440,0.0,6,3072,0.0 +6,6,0,73000.0,3,120,61440,0.0,6,3072,0.0 +1,1,0,73000.0,0,120,61440,0.0,2,1024,0.0 +2,2,0,73000.0,1,28,14336,0.0,28,14336,0.0 +8,8,0,73000.0,4,120,61440,0.0,6,3072,0.0 +4,4,0,73000.0,2,120,61440,0.0,6,3072,0.0 +2,2,0,74000.0,1,28,14336,0.0,28,14336,0.0 +6,6,0,74000.0,3,120,61440,0.0,6,3072,0.0 +4,4,0,74000.0,2,120,61440,0.0,6,3072,0.0 +8,8,0,74000.0,4,120,61440,0.0,6,3072,0.0 +1,1,0,74000.0,0,120,61440,0.0,2,1024,0.0 +6,6,0,75000.0,3,120,61440,0.0,6,3072,0.0 +8,8,0,75000.0,4,120,61440,0.0,6,3072,0.0 +4,4,0,75000.0,2,120,61440,0.0,6,3072,0.0 +1,1,0,75000.0,0,120,61440,0.0,2,1024,0.0 +2,2,0,75000.0,1,28,14336,0.0,28,14336,0.0 +1,1,0,76000.0,0,120,61440,0.0,2,1024,0.0 +4,4,0,76000.0,2,120,61440,0.0,6,3072,0.0 +2,2,0,76000.0,1,28,14336,0.0,28,14336,0.0 +6,6,0,76000.0,3,120,61440,0.0,6,3072,0.0 +8,8,0,76000.0,4,120,61440,0.0,6,3072,0.0 +8,8,0,77000.0,4,120,61440,0.0,6,3072,0.0 +6,6,0,77000.0,3,120,61440,0.0,6,3072,0.0 +1,1,0,77000.0,0,120,61440,0.0,2,1024,0.0 +2,2,0,77000.0,1,28,14336,0.0,28,14336,0.0 +4,4,0,77000.0,2,120,61440,0.0,6,3072,0.0 +1,1,0,78000.0,0,120,61440,0.0,2,1024,0.0 +6,6,0,78000.0,3,120,61440,0.0,6,3072,0.0 +2,2,0,78000.0,1,28,14336,0.0,28,14336,0.0 +4,4,0,78000.0,2,120,61440,0.0,6,3072,0.0 +8,8,0,78000.0,4,120,61440,0.0,6,3072,0.0 +2,2,0,79000.0,1,29,14848,0.0,29,14848,0.0 +8,8,0,79000.0,4,120,61440,0.0,6,3072,0.0 +6,6,0,79000.0,3,120,61440,0.0,6,3072,0.0 +1,1,0,79000.0,0,120,61440,0.0,2,1024,0.0 +4,4,0,79000.0,2,120,61440,0.0,7,3584,0.0 +4,4,0,80000.0,2,120,61440,0.0,7,3584,0.0 +6,6,0,80000.0,3,120,61440,0.0,7,3584,0.0 +2,2,0,80000.0,1,32,16384,0.0,32,16384,0.0 +8,8,0,80000.0,4,120,61440,0.0,6,3072,0.0 +1,1,0,80000.0,0,120,61440,0.0,3,1536,0.0 +8,8,0,81000.0,4,120,61440,0.0,7,3584,0.0 +1,1,0,81000.0,0,120,61440,0.0,3,1536,0.0 +6,6,0,81000.0,3,120,61440,0.0,7,3584,0.0 +2,2,0,81000.0,1,32,16384,0.0,32,16384,0.0 +4,4,0,81000.0,2,120,61440,0.0,7,3584,0.0 +8,8,0,82000.0,4,120,61440,0.0,7,3584,0.0 +6,6,0,82000.0,3,120,61440,0.0,7,3584,0.0 +1,1,0,82000.0,0,120,61440,0.0,3,1536,0.0 +4,4,0,82000.0,2,120,61440,0.0,7,3584,0.0 +2,2,0,82000.0,1,32,16384,0.0,32,16384,0.0 +1,1,0,83000.0,0,120,61440,0.0,3,1536,0.0 +8,8,0,83000.0,4,120,61440,0.0,7,3584,0.0 +2,2,0,83000.0,1,32,16384,0.0,32,16384,0.0 +6,6,0,83000.0,3,120,61440,0.0,7,3584,0.0 +4,4,0,83000.0,2,120,61440,0.0,7,3584,0.0 +2,2,0,84000.0,1,32,16384,0.0,32,16384,0.0 +4,4,0,84000.0,2,120,61440,0.0,7,3584,0.0 +8,8,0,84000.0,4,120,61440,0.0,7,3584,0.0 +6,6,0,84000.0,3,120,61440,0.0,7,3584,0.0 +1,1,0,84000.0,0,120,61440,0.0,3,1536,0.0 +4,4,0,85000.0,2,120,61440,0.0,7,3584,0.0 +1,1,0,85000.0,0,120,61440,0.0,3,1536,0.0 +8,8,0,85000.0,4,120,61440,0.0,7,3584,0.0 +2,2,0,85000.0,1,32,16384,0.0,32,16384,0.0 +6,6,0,85000.0,3,120,61440,0.0,7,3584,0.0 +1,1,0,86000.0,0,120,61440,0.0,3,1536,0.0 +2,2,0,86000.0,1,32,16384,0.0,32,16384,0.0 +8,8,0,86000.0,4,120,61440,0.0,7,3584,0.0 +6,6,0,86000.0,3,120,61440,0.0,7,3584,0.0 +4,4,0,86000.0,2,120,61440,0.0,7,3584,0.0 +6,6,0,87000.0,3,120,61440,0.0,7,3584,0.0 +1,1,0,87000.0,0,120,61440,0.0,3,1536,0.0 +4,4,0,87000.0,2,120,61440,0.0,7,3584,0.0 +2,2,0,87000.0,1,32,16384,0.0,32,16384,0.0 +8,8,0,87000.0,4,120,61440,0.0,7,3584,0.0 +8,8,0,88000.0,4,120,61440,0.0,7,3584,0.0 +1,1,0,88000.0,0,120,61440,0.0,3,1536,0.0 +2,2,0,88000.0,1,32,16384,0.0,32,16384,0.0 +6,6,0,88000.0,3,120,61440,0.0,7,3584,0.0 +4,4,0,88000.0,2,120,61440,0.0,7,3584,0.0 +4,4,0,89000.0,2,120,61440,0.0,8,4096,0.0 +2,2,0,89000.0,1,34,17408,0.0,34,17408,0.0 +8,8,0,89000.0,4,120,61440,0.0,7,3584,0.0 +1,1,0,89000.0,0,120,61440,0.0,3,1536,0.0 +6,6,0,89000.0,3,120,61440,0.0,7,3584,0.0 +6,6,0,90000.0,3,120,61440,0.0,8,4096,0.0 +8,8,0,90000.0,4,120,61440,0.0,8,4096,0.0 +1,1,0,90000.0,0,120,61440,0.0,4,2048,0.0 +2,2,0,90000.0,1,36,18432,0.0,36,18432,0.0 +4,4,0,90000.0,2,120,61440,0.0,8,4096,0.0 +1,1,0,91000.0,0,120,61440,0.0,4,2048,0.0 +8,8,0,91000.0,4,120,61440,0.0,8,4096,0.0 +4,4,0,91000.0,2,120,61440,0.0,8,4096,0.0 +6,6,0,91000.0,3,120,61440,0.0,8,4096,0.0 +2,2,0,91000.0,1,36,18432,0.0,36,18432,0.0 +1,1,0,92000.0,0,120,61440,0.0,4,2048,0.0 +4,4,0,92000.0,2,120,61440,0.0,8,4096,0.0 +8,8,0,92000.0,4,120,61440,0.0,8,4096,0.0 +6,6,0,92000.0,3,120,61440,0.0,8,4096,0.0 +2,2,0,92000.0,1,36,18432,0.0,36,18432,0.0 +2,2,0,93000.0,1,36,18432,0.0,36,18432,0.0 +8,8,0,93000.0,4,120,61440,0.0,8,4096,0.0 +6,6,0,93000.0,3,120,61440,0.0,8,4096,0.0 +4,4,0,93000.0,2,120,61440,0.0,8,4096,0.0 +1,1,0,93000.0,0,120,61440,0.0,4,2048,0.0 +8,8,0,94000.0,4,120,61440,0.0,8,4096,0.0 +2,2,0,94000.0,1,36,18432,0.0,36,18432,0.0 +6,6,0,94000.0,3,120,61440,0.0,8,4096,0.0 +4,4,0,94000.0,2,120,61440,0.0,8,4096,0.0 +1,1,0,94000.0,0,120,61440,0.0,4,2048,0.0 +2,2,0,95000.0,1,36,18432,0.0,36,18432,0.0 +6,6,0,95000.0,3,120,61440,0.0,8,4096,0.0 +1,1,0,95000.0,0,120,61440,0.0,4,2048,0.0 +8,8,0,95000.0,4,120,61440,0.0,8,4096,0.0 +4,4,0,95000.0,2,120,61440,0.0,8,4096,0.0 +4,4,0,96000.0,2,120,61440,0.0,8,4096,0.0 +1,1,0,96000.0,0,120,61440,0.0,4,2048,0.0 +2,2,0,96000.0,1,36,18432,0.0,36,18432,0.0 +6,6,0,96000.0,3,120,61440,0.0,8,4096,0.0 +8,8,0,96000.0,4,120,61440,0.0,8,4096,0.0 +6,6,0,97000.0,3,120,61440,0.0,8,4096,0.0 +2,2,0,97000.0,1,36,18432,0.0,36,18432,0.0 +1,1,0,97000.0,0,120,61440,0.0,4,2048,0.0 +4,4,0,97000.0,2,120,61440,0.0,8,4096,0.0 +8,8,0,97000.0,4,120,61440,0.0,8,4096,0.0 +8,8,0,98000.0,4,120,61440,0.0,8,4096,0.0 +4,4,0,98000.0,2,120,61440,0.0,8,4096,0.0 +6,6,0,98000.0,3,120,61440,0.0,8,4096,0.0 +1,1,0,98000.0,0,120,61440,0.0,4,2048,0.0 +2,2,0,98000.0,1,36,18432,0.0,36,18432,0.0 +2,2,0,99000.0,1,39,19968,0.0,39,19968,0.0 +1,1,0,99000.0,0,120,61440,0.0,4,2048,0.0 +6,6,0,99000.0,3,120,61440,0.0,9,4608,0.0 +4,4,0,99000.0,2,120,61440,0.0,9,4608,0.0 +8,8,0,99000.0,4,120,61440,0.0,8,4096,0.0 +4,4,0,100000.0,2,120,61440,0.0,9,4608,0.0 +8,8,0,100000.0,4,120,61440,0.0,8,4096,0.0 +6,6,0,100000.0,3,120,61440,0.0,9,4608,0.0 +2,2,0,100000.0,1,40,20480,0.0,40,20480,0.0 +1,1,0,100000.0,0,120,61440,0.0,5,2560,0.0 diff --git a/tests/data/ross-binary-data/esnet-model-inst-analysis-lps.bin b/tests/data/ross-binary-data/esnet-model-inst-analysis-lps.bin new file mode 100644 index 0000000..6e6f955 Binary files /dev/null and b/tests/data/ross-binary-data/esnet-model-inst-analysis-lps.bin differ diff --git a/tests/data/ross-binary-data/esnet-model-inst-evtrace.bin b/tests/data/ross-binary-data/esnet-model-inst-evtrace.bin new file mode 100644 index 0000000..60354e5 Binary files /dev/null and b/tests/data/ross-binary-data/esnet-model-inst-evtrace.bin differ diff --git a/tests/data/ross-binary-data/ross-stats-gvt.bin b/tests/data/ross-binary-data/ross-stats-gvt.bin new file mode 100644 index 0000000..9769e4f Binary files /dev/null and b/tests/data/ross-binary-data/ross-stats-gvt.bin differ diff --git a/tests/test_import.py b/tests/test_import.py index 6fe27bf..d9124cc 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,2 +1,2 @@ def test_import(): - from codes_dashboard import main # noqa: F401 + from codes_dashboard.app import main # noqa: F401 diff --git a/tests/test_read_event_file.py b/tests/test_read_event_file.py new file mode 100644 index 0000000..8c94f51 --- /dev/null +++ b/tests/test_read_event_file.py @@ -0,0 +1,6 @@ +def test_read_event_file(): + from codes_dashboard.app.core.event_trace_file import EventFile + + ross_file = EventFile("tests/data/ross-binary-data/esnet-model-inst-evtrace.bin") + ross_file.read() + ross_file.close() \ No newline at end of file diff --git a/tests/test_read_model_file.py b/tests/test_read_model_file.py new file mode 100644 index 0000000..21e4906 --- /dev/null +++ b/tests/test_read_model_file.py @@ -0,0 +1,6 @@ +def test_read_model_file(): + from codes_dashboard.app.core.model_file import ModelFile + + ross_file = ModelFile("tests/data/ross-binary-data/esnet-model-inst-analysis-lps.bin") + ross_file.read() + ross_file.close() \ No newline at end of file diff --git a/tests/test_read_ross_file.py b/tests/test_read_ross_file.py new file mode 100644 index 0000000..962becb --- /dev/null +++ b/tests/test_read_ross_file.py @@ -0,0 +1,7 @@ +def test_read_ross_file(): + from codes_dashboard.app.core.ross_binary_file import ROSSFile + + ross_file = ROSSFile("tests/data/ross-binary-data/ross-stats-gvt.bin") + ross_file.read() + ross_file.close() +