├── .github └── workflows │ └── deploy-ui.yml ├── .gitignore ├── .gitmodules ├── .idea └── dictionaries │ └── varuna.xml ├── .labml.yaml ├── Makefile ├── cpp ├── .gitignore └── labml_fast_merge │ ├── labml_fast_merge.pyx │ └── setup.py ├── docs ├── installation.rst └── perfomance.md ├── images ├── cover.png ├── experiment.png └── labml-app.gif ├── license ├── package-lock.json ├── package.json ├── readme.md ├── server ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── gunicorn.conf.py ├── labml_app │ ├── .gitignore │ ├── __init__.py │ ├── analyses │ │ ├── __init__.py │ │ ├── analysis.py │ │ ├── computers │ │ │ ├── battery.py │ │ │ ├── cpu.py │ │ │ ├── disk.py │ │ │ ├── gpu.py │ │ │ ├── memory.py │ │ │ ├── network.py │ │ │ └── process.py │ │ ├── experiments │ │ │ ├── comparison.py │ │ │ ├── gradients.py │ │ │ ├── hyperparameters.py │ │ │ ├── metrics.py │ │ │ ├── outputs.py │ │ │ └── parameters.py │ │ ├── helper.py │ │ ├── preferences.py │ │ ├── series.py │ │ └── series_collection.py │ ├── analyses_settings.sample.py │ ├── auth │ │ └── __init__.py │ ├── block_uuids.sample.py │ ├── db │ │ ├── __init__.py │ │ ├── app_token.py │ │ ├── blocked_uuids.py │ │ ├── computer.py │ │ ├── job.py │ │ ├── project.py │ │ ├── run.py │ │ ├── session.py │ │ ├── status.py │ │ └── user.py │ ├── docs.py │ ├── enums.py │ ├── flask_app.py │ ├── handlers.py │ ├── logger │ │ ├── __init__.py │ │ └── logger.py │ ├── scripts │ │ ├── clean_ups.py │ │ ├── db_checks.py │ │ └── temp_cleans.py │ ├── settings.sample.py │ └── utils │ │ ├── __init__.py │ │ ├── mix_panel.py │ │ └── slack.py ├── setup.py └── unit_tests │ └── series.py ├── tsconfig.json └── ui ├── images ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── favicon.png ├── lab_logo.png ├── safari-pinned-tab.svg └── tf_Icon.png └── src ├── analyses ├── analyses.ts ├── experiments │ ├── activations │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── comaprison │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── configs │ │ ├── card.ts │ │ ├── components.ts │ │ ├── index.ts │ │ └── view.ts │ ├── grads │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── hyper_params │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── logger │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── metrics │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── params │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── run_header │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── stderror │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ └── stdout │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts ├── helpers.ts ├── sessions │ ├── battery │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── configs │ │ ├── card.ts │ │ ├── components.ts │ │ ├── index.ts │ │ └── view.ts │ ├── cpu │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── disk │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── gpu │ │ ├── cache.ts │ │ ├── index.ts │ │ ├── memory_card.ts │ │ ├── memory_view.ts │ │ ├── power_card.ts │ │ ├── power_view.ts │ │ ├── temp_card.ts │ │ ├── temp_view.ts │ │ ├── util_card.ts │ │ ├── util_view.ts │ │ └── utils.ts │ ├── memory │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── network │ │ ├── cache.ts │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts │ ├── process │ │ ├── cache.ts │ │ ├── cache_helper.ts │ │ ├── card.ts │ │ ├── detail_view.ts │ │ ├── index.ts │ │ ├── process_list.ts │ │ ├── types.ts │ │ └── view.ts │ └── session_header │ │ ├── card.ts │ │ ├── index.ts │ │ └── view.ts └── types.ts ├── app.ts ├── cache └── cache.ts ├── components ├── badge.ts ├── buttons.ts ├── charts │ ├── axis.ts │ ├── chart_colors.ts │ ├── chart_gradients.ts │ ├── compare_lines │ │ ├── chart.ts │ │ └── plot.ts │ ├── compare_spark_lines │ │ ├── chart.ts │ │ └── spark_line.ts │ ├── constants.ts │ ├── custom_lines │ │ ├── chart.ts │ │ └── plot.ts │ ├── editable_spark_lines │ │ ├── chart.ts │ │ └── editable_spark_line.ts │ ├── labels.ts │ ├── lines │ │ ├── chart.ts │ │ └── plot.ts │ ├── simple_lines │ │ ├── chart.ts │ │ └── plot.ts │ ├── spark_lines │ │ ├── chart.ts │ │ └── spark_line.ts │ ├── spark_time_lines │ │ ├── chart.ts │ │ └── spark_time_line.ts │ ├── timeseries │ │ ├── chart.ts │ │ ├── plot.ts │ │ └── single_scale_lines.ts │ ├── types.ts │ └── utils.ts ├── codes │ ├── keras.ts │ ├── pytorch.ts │ ├── pytorch_lightning.ts │ └── tabs.ts ├── error_message.ts ├── hamburger_menu.ts ├── input │ ├── editable_field.ts │ └── editable_select_field.ts ├── insights_list.ts ├── loader.ts ├── refresh_button.ts ├── runs_list_item.ts ├── search.ts ├── sessions_list_item.ts ├── status.ts └── user_messages.ts ├── d3.ts ├── env.sample.ts ├── index.html ├── main.ts ├── mix_panel.ts ├── models ├── config.ts ├── job.ts ├── preferences.ts ├── run.ts ├── run_list.ts ├── session.ts ├── session_list.ts ├── status.ts └── user.ts ├── network.ts ├── neumorphism.scss ├── screen.ts ├── screen_view.ts ├── sentry.ts ├── site.webmanifest ├── style.scss ├── types.ts ├── utils ├── ansi_to_html.js ├── document.ts ├── meta_tags.ts ├── mobile.ts ├── new_tab.ts ├── redirect.ts ├── render.ts ├── time.ts ├── value.ts └── window_dimentions.ts └── views ├── empty_runs_list.ts ├── empty_sessions_list.ts ├── errors ├── auth_error_view.ts ├── network_error_view.ts ├── other_error_view.ts └── page_not_found_view.ts ├── login_view.ts ├── run_picker_view.ts ├── run_view.ts ├── runs_list_view.ts ├── session_view.ts ├── sessions_list_view.ts └── settings_view.ts /.github/workflows/deploy-ui.yml: -------------------------------------------------------------------------------- 1 | name: Deploy UI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-20.04 11 | 12 | steps: 13 | - name: Cloning Repo 14 | uses: actions/checkout@v2 15 | with: 16 | submodules: 'recursive' 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: '14' 22 | 23 | - name: Setup Cache 24 | uses: actions/cache@v2 25 | with: 26 | path: ~/.npm 27 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 28 | restore-keys: | 29 | ${{ runner.os }}-node- 30 | 31 | - name: Install SSH key 32 | uses: shimataro/ssh-key-action@v2 33 | with: 34 | key: ${{ secrets.SSH_KEY }} 35 | name: id_rsa 36 | known_hosts: ${{ secrets.KNOWN_HOSTS }} 37 | 38 | - name: Prepare build environment 39 | run: | 40 | npm install 41 | echo "${{ secrets.ENV_TS }}" > ui/src/env.ts 42 | 43 | - name: Build 44 | run: make compile-prod 45 | 46 | - name: Deploy 47 | run: | 48 | rsync -zravKLt --perms --executability static/ ${{ secrets.DEPLOY_LOCATION }} 49 | 50 | - name: Create Sentry release 51 | uses: getsentry/action-release@v1 52 | env: 53 | SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} 54 | SENTRY_ORG: ${{ secrets.SENTRY_ORG }} 55 | SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} 56 | with: 57 | environment: production 58 | sourcemaps: './static/js' 59 | set_commits: skip 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.staging 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | notes.json 28 | node_modules/ 29 | .*.swp 30 | __pycache__ 31 | *.egg-info/ 32 | .sass-cache/ 33 | react-app-env.d.ts 34 | 35 | .idea/* 36 | !.idea/dictionaries 37 | 38 | data/ 39 | geckodriver 40 | geckodriver.log 41 | labml 42 | /server/app.log 43 | /server/labml_db 44 | /server/labml_app/analyses_settings.py 45 | /server/labml_app/block_uuids.py 46 | /server/labml_app/app_analyses 47 | /server/labml_app/static 48 | 49 | # labml 50 | /logs/ 51 | /static 52 | ui/src/env.ts 53 | 54 | dist/ 55 | 56 | *.log 57 | 58 | build/ 59 | *.so 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/io"] 2 | path = lib/io 3 | url = https://github.com/vpj/IO.git 4 | [submodule "lib/weya"] 5 | path = lib/weya 6 | url = https://github.com/vpj/weya.git 7 | -------------------------------------------------------------------------------- /.idea/dictionaries/varuna.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hyperparam 5 | labml 6 | 7 | 8 | -------------------------------------------------------------------------------- /.labml.yaml: -------------------------------------------------------------------------------- 1 | web_api: http://localhost:5000/api/v1/track? 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help 2 | .DEFAULT_GOAL := help 3 | 4 | setup: ## install server and ui dependencies 5 | pip install pipenv 6 | sudo apt-get update 7 | sudo apt-get install npm 8 | npm install 9 | cd server && pipenv install --ignore-pipfile 10 | 11 | server-dev: ## start and watch server 12 | cd server && pipenv run uvicorn labml_app.flask_app:app --reload --host 0.0.0.0 --port 5000 13 | 14 | server-prod: ## compile and start server in prod 15 | # pkill gunicorn 16 | # export PATH=~/miniconda/bin:$PATH 17 | cd server && pipenv install --ignore-pipfile && pipenv run gunicorn --bind 0.0.0.0:5000 -w 2 -k uvicorn.workers.UvicornWorker labml_app.flask_app:app --daemon 18 | 19 | clean-db: ## clean float-project and move samples to float-project 20 | cd server && pipenv run python -m labml_app.scripts.clean_ups 21 | 22 | check-db: ## db checks 23 | cd server && pipenv run python -m labml_app.scripts.db_checks 24 | 25 | compile: ## Compile JS 26 | rm -rf static 27 | mkdir -p static/js 28 | cp ui/src/index.html static/index.html 29 | cp ui/src/site.webmanifest static/site.webmanifest 30 | cp ui/images/favicon.ico static/favicon.ico 31 | cp -r ui/images static/ 32 | npm run build 33 | 34 | compile-prod: compile 35 | $(eval JS_CHECKSUM := $(shell md5sum static/js/bundle.min.js | cut -f 1 -d " ")) 36 | $(eval CSS_CHECKSUM := $(shell md5sum static/css/style.css | cut -f 1 -d " ")) 37 | sed -i 's/bundle.min.js/$(JS_CHECKSUM).min.js/g' static/index.html 38 | sed -i 's/bundle.min.js.map/$(JS_CHECKSUM).min.js.map/g' static/js/bundle.min.js 39 | sed -i 's/style.css/$(CSS_CHECKSUM).css/g' static/index.html 40 | sed -i 's/style.css.map/$(CSS_CHECKSUM).css.map/g' static/css/style.css 41 | mv static/js/bundle.min.js static/js/$(JS_CHECKSUM).min.js 42 | mv static/js/bundle.min.js.map static/js/$(JS_CHECKSUM).min.js.map 43 | mv static/css/style.css static/css/$(CSS_CHECKSUM).css 44 | mv static/css/style.css.map static/css/$(CSS_CHECKSUM).css.map 45 | 46 | 47 | watch-ui: compile ## Compile and Watch JS & CSS 48 | npm run watch 49 | 50 | build-ui: compile ## build production ui 51 | 52 | package: build-ui ## Build PIPy Package 53 | rm -rf server/labml_app/static 54 | cp -r static/ server/labml_app/static 55 | cd server && python setup.py sdist bdist_wheel 56 | 57 | check-package: ## List contents of PIPy Package 58 | cd server && tar -tvf dist/*.tar.gz 59 | 60 | install: compile ## Install from repo 61 | cd server && pip install -e . 62 | 63 | uninstall: ## Uninstall 64 | pip uninstall labml_app labml 65 | 66 | 67 | help: ## Show this help. 68 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' 69 | 70 | -------------------------------------------------------------------------------- /cpp/.gitignore: -------------------------------------------------------------------------------- 1 | labml_fast_merge.cpp -------------------------------------------------------------------------------- /cpp/labml_fast_merge/labml_fast_merge.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | 3 | import numpy as np 4 | cimport numpy as np 5 | cimport cython 6 | 7 | @cython.boundscheck(False) 8 | @cython.wraparound(False) 9 | def merge(np.ndarray[np.double_t, ndim=1] values, 10 | np.ndarray[np.double_t, ndim=1] last_step, 11 | np.ndarray[np.double_t, ndim=1] steps, 12 | double step_gap, 13 | double prev_last_step, 14 | int i, # from_step 15 | ): 16 | cdef int j = i + 1 17 | cdef int length = values.shape[0] 18 | cdef double iw, jw 19 | while j < length: 20 | if last_step[j] - prev_last_step < step_gap or last_step[j] - last_step[j - 1] < 1e-3: # merge 21 | iw = max(1., last_step[i] - prev_last_step) 22 | jw = max(1., last_step[j] - last_step[i]) 23 | steps[i] = (steps[i] * iw + steps[j] * jw) / (iw + jw) 24 | values[i] = (values[i] * iw + values[j] * jw) / (iw + jw) 25 | last_step[i] = last_step[j] 26 | j += 1 27 | else: # move to next 28 | prev_last_step = last_step[i] 29 | i += 1 30 | last_step[i] = last_step[j] 31 | steps[i] = steps[j] 32 | values[i] = values[j] 33 | j += 1 34 | 35 | return i + 1 # size after merging 36 | -------------------------------------------------------------------------------- /cpp/labml_fast_merge/setup.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from setuptools import setup 3 | 4 | from Cython.Build import cythonize 5 | 6 | setup(ext_modules=cythonize("labml_fast_merge.pyx"), 7 | include_dirs=[np.get_include()]) 8 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Running Your Own Server Instructions 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | 1. Requirements: Python 3.7 and npm installed in your machine. 5 | 6 | 2. Clone the repository 7 | 8 | .. code-block:: console 9 | 10 | git clone git@github.com:lab-ml/app.git 11 | 12 | 3. Install server and ui dependencies 13 | 14 | .. code-block:: console 15 | 16 | make setup 17 | 18 | 4. Create ``app/server/app/setting.py`` similar to ``app/server/app/setting.example.py`` and ``app/ui/src/.env`` similar to ``app/ui/src/.env.example`` files and change the parameters accordingly. 19 | 20 | 5. For UI and server dev 21 | 22 | .. code-block:: console 23 | 24 | make watch-ui 25 | make server-dev 26 | 27 | 6. For UI and server prod 28 | 29 | .. code-block:: console 30 | 31 | make build-ui 32 | make server-prod 33 | -------------------------------------------------------------------------------- /docs/perfomance.md: -------------------------------------------------------------------------------- 1 | ### Perfomance Improvement with Cython 2 | 3 | ##### Compile/install cython module cpp/labml_fast_merge with 4 | `` 5 | python setup.py build_ext install 6 | `` 7 | 8 | ##### pipenv --site-packages 9 | `` 10 | pipenv --site-packages 11 | `` 12 | -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/images/cover.png -------------------------------------------------------------------------------- /images/experiment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/images/experiment.png -------------------------------------------------------------------------------- /images/labml-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/images/labml-app.gif -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Nipun Wijerathne, Varuna Jayasiri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@types/d3": "^5.7.2", 7 | "@types/node": "latest", 8 | "browserify": "^17.0.0", 9 | "sass": "latest", 10 | "tsify": "^5.0.2", 11 | "typescript": "latest", 12 | "uglify-js": "^3.12.8", 13 | "uglifyify": "^5.0.2", 14 | "watchify": "^4.0.0" 15 | }, 16 | "dependencies": { 17 | "d3": "^5.16.0" 18 | }, 19 | "scripts": { 20 | "build": "$npm_execpath run build:ui && $npm_execpath run build:sass", 21 | "watch": "$npm_execpath run watch:ui & $npm_execpath run watch:sass", 22 | "build:ui": "browserify ui/src/main.ts -t uglifyify -d -p [ tsify -p tsconfig.json] | uglifyjs -cm -o static/js/bundle.min.js --source-map \"content=inline,url=bundle.min.js.map,includeSources\"", 23 | "watch:ui": "watchify ui/src/main.ts -t uglifyify -d -p [ tsify -p tsconfig.json] -v -o static/js/bundle.min.js", 24 | "build:sass": "sass ui/src/style.scss:static/css/style.css", 25 | "watch:sass": "sass --watch ui/src/style.scss:static/css/style.css", 26 | "clean": "rm -rf static/" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include labml_app * 2 | include readme.rst 3 | -------------------------------------------------------------------------------- /server/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | requests = "*" 10 | gunicorn = "*" 11 | labml="*" 12 | numpy = "*" 13 | sentry-sdk = {extras = ["flask"], version = "*"} 14 | labml-db = "*" 15 | redis = "*" 16 | mixpanel = "*" 17 | slack-sdk = "*" 18 | fastapi = "*" 19 | uvicorn = "*" 20 | aiofiles = "*" 21 | 22 | [requires] 23 | python_version = "3.7" 24 | -------------------------------------------------------------------------------- /server/gunicorn.conf.py: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/benoitc/gunicorn/blob/master/examples/example_config.py 2 | # import os 3 | import multiprocessing 4 | 5 | # _ROOT = os.path.dirname(os.path.abspath("__file__")) 6 | # _ETC = os.path.join(_ROOT, 'etc') 7 | 8 | loglevel = 'info' 9 | 10 | errorlog = '../logs/api-error.log' 11 | accesslog = '../logs/api-access.log' 12 | 13 | bind = '0.0.0.0:5000' 14 | workers = 2 # multiprocessing.cpu_count() * 2 + 1 15 | threads = 8 16 | 17 | timeout = 3 * 60 # 3 minutes 18 | keepalive = 24 * 60 * 60 # 1 day 19 | 20 | capture_output = True 21 | -------------------------------------------------------------------------------- /server/labml_app/.gitignore: -------------------------------------------------------------------------------- 1 | settings.py 2 | analyses_settings.py -------------------------------------------------------------------------------- /server/labml_app/__init__.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def start_server(): 5 | try: 6 | subprocess.run( 7 | ["gunicorn --bind 0.0.0.0:5000 -w 2 -k uvicorn.workers.UvicornWorker labml_app.flask_app:app"], 8 | shell=True, 9 | ) 10 | except KeyboardInterrupt: 11 | pass 12 | -------------------------------------------------------------------------------- /server/labml_app/analyses/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Tuple, Callable 2 | 3 | from labml import monit 4 | from . import analysis 5 | from .series import SeriesModel 6 | from ..analyses_settings import experiment_analyses, computer_analyses 7 | 8 | EXPERIMENT_ANALYSES = {} 9 | for ans in experiment_analyses: 10 | EXPERIMENT_ANALYSES[ans.__name__] = ans 11 | 12 | 13 | class AnalysisManager: 14 | @staticmethod 15 | def track(run_uuid: str, data: Dict[str, SeriesModel]) -> None: 16 | for ans in experiment_analyses: 17 | ans.get_or_create(run_uuid).track(data) 18 | 19 | @staticmethod 20 | def track_computer(session_uuid: str, data: Dict[str, SeriesModel]) -> None: 21 | for ans in computer_analyses: 22 | ans.get_or_create(session_uuid).track(data) 23 | 24 | @staticmethod 25 | def delete_run(run_uuid: str) -> None: 26 | for ans in experiment_analyses: 27 | ans.delete(run_uuid) 28 | 29 | @staticmethod 30 | def delete_computer(computer_uuid: str) -> None: 31 | for ans in computer_analyses: 32 | ans.delete(computer_uuid) 33 | 34 | @staticmethod 35 | def get_handlers() -> List[Tuple[str, Callable, str, bool]]: 36 | return analysis.URLS 37 | 38 | @staticmethod 39 | def get_db_indexes() -> List[Tuple[any, str]]: 40 | return analysis.DB_INDEXES 41 | 42 | @staticmethod 43 | def get_db_models() -> List[Tuple[any, str]]: 44 | return analysis.DB_MODELS 45 | 46 | @staticmethod 47 | def get_experiment_analysis(name: str, run_uuid: str) -> any: 48 | assert name in EXPERIMENT_ANALYSES 49 | 50 | return EXPERIMENT_ANALYSES[name].get_or_create(run_uuid) 51 | -------------------------------------------------------------------------------- /server/labml_app/analyses/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from .series import SeriesModel 4 | 5 | URLS = [] 6 | DB_MODELS = [] 7 | DB_INDEXES = [] 8 | 9 | 10 | class Analysis: 11 | def track(self, data: Dict[str, SeriesModel]) -> None: 12 | raise NotImplementedError 13 | 14 | @staticmethod 15 | def get_or_create(run_uuid: str): 16 | raise NotImplementedError 17 | 18 | @staticmethod 19 | def delete(run_uuid: str): 20 | raise NotImplementedError 21 | 22 | @staticmethod 23 | def route(method: str, url: str, login_required: bool = False): 24 | def decorator(f): 25 | URLS.append((method, f, url, login_required)) 26 | return f 27 | 28 | return decorator 29 | 30 | @staticmethod 31 | def db_model(serializer: any, path: str): 32 | def decorator(cls): 33 | DB_MODELS.append((serializer, cls, path)) 34 | 35 | return cls 36 | 37 | return decorator 38 | 39 | @staticmethod 40 | def db_index(serializer: any, path: str): 41 | def decorator(cls): 42 | DB_INDEXES.append((serializer, cls, path)) 43 | 44 | return cls 45 | 46 | return decorator 47 | -------------------------------------------------------------------------------- /server/labml_app/analyses/experiments/comparison.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from fastapi import Request 4 | from labml_db import Model, Index 5 | from labml_db.serializer.pickle import PickleSerializer 6 | from labml_db.serializer.yaml import YamlSerializer 7 | 8 | from labml_app.logger import logger 9 | from ..analysis import Analysis 10 | from .. import preferences 11 | 12 | 13 | class ComparisonPreferences(preferences.Preferences): 14 | base_series_preferences: preferences.SeriesPreferences 15 | base_experiment: str 16 | 17 | @classmethod 18 | def defaults(cls): 19 | return dict(base_series_preferences=[], 20 | base_experiment=str, 21 | ) 22 | 23 | def update_preferences(self, data: preferences.PreferencesData) -> None: 24 | if 'base_series_preferences' in data: 25 | self.update_base_series_preferences(data['base_series_preferences']) 26 | 27 | if 'base_experiment' in data: 28 | self.base_experiment = data['base_experiment'] 29 | 30 | if 'series_preferences' in data: 31 | self.update_series_preferences(data['series_preferences']) 32 | 33 | if 'chart_type' in data: 34 | self.chart_type = data['chart_type'] 35 | 36 | self.save() 37 | 38 | def update_base_series_preferences(self, data: preferences.SeriesPreferences) -> None: 39 | self.base_series_preferences = data 40 | 41 | def get_data(self) -> Dict[str, Any]: 42 | return { 43 | 'base_series_preferences': self.base_series_preferences, 44 | 'series_preferences': self.series_preferences, 45 | 'base_experiment': self.base_experiment, 46 | 'chart_type': self.chart_type, 47 | } 48 | 49 | 50 | @Analysis.db_model(PickleSerializer, 'comparison_preferences') 51 | class ComparisonPreferencesModel(Model['ComparisonPreferencesModel'], ComparisonPreferences): 52 | pass 53 | 54 | 55 | @Analysis.db_index(YamlSerializer, 'comparison_preferences_index.yaml') 56 | class ComparisonPreferencesIndex(Index['ComparisonPreferences']): 57 | pass 58 | 59 | 60 | @Analysis.route('GET', 'compare/preferences/{run_uuid}') 61 | def get_comparison_preferences(request: Request, run_uuid: str) -> Any: 62 | preferences_data = {} 63 | 64 | preferences_key = ComparisonPreferencesIndex.get(run_uuid) 65 | if not preferences_key: 66 | return preferences_data 67 | 68 | cp: ComparisonPreferences = preferences_key.load() 69 | preferences_data = cp.get_data() 70 | 71 | return preferences_data 72 | 73 | 74 | @Analysis.route('POST', 'compare/preferences/{run_uuid}') 75 | async def set_comparison_preferences(request: Request, run_uuid: str) -> Any: 76 | preferences_key = ComparisonPreferencesIndex.get(run_uuid) 77 | 78 | if not preferences_key: 79 | cp = ComparisonPreferencesModel() 80 | ComparisonPreferencesIndex.set(run_uuid, cp.key) 81 | else: 82 | cp = preferences_key.load() 83 | 84 | json = await request.json() 85 | cp.update_preferences(json) 86 | 87 | logger.debug(f'update comparison preferences: {cp.key}') 88 | 89 | return {'errors': cp.errors} 90 | -------------------------------------------------------------------------------- /server/labml_app/analyses/helper.py: -------------------------------------------------------------------------------- 1 | import math 2 | from typing import List, Dict, Any 3 | 4 | 5 | def find_common_prefix(names: List[str]) -> str: 6 | shortest = min(names, key=len) 7 | 8 | for i, word in enumerate(shortest): 9 | for name in names: 10 | if name[i] != word: 11 | return shortest[:i] 12 | 13 | return '' 14 | 15 | 16 | def remove_common_prefix(series: List[Dict[str, Any]], key: str) -> None: 17 | if not series: 18 | return 19 | 20 | names = [] 21 | for s in series: 22 | s[key] = s[key].split('.') 23 | 24 | names.append(s[key]) 25 | 26 | common_prefix = find_common_prefix(names) 27 | 28 | if common_prefix: 29 | len_removed = len(common_prefix) 30 | else: 31 | len_removed = 0 32 | 33 | for s in series: 34 | name = s[key][len_removed:] 35 | 36 | s[key] = '.'.join(name) 37 | 38 | 39 | def replace_nans(series: List[Dict[str, Any]], keys: List[str]) -> None: 40 | for s in series: 41 | for key in keys: 42 | if isinstance(s[key], list): 43 | s[key] = [0 if math.isnan(x) else x for x in s[key]] 44 | else: 45 | s[key] = 0 if math.isnan(s[key]) else s[key] 46 | 47 | 48 | def get_mean_series(res: List[Dict[str, Any]]) -> Dict[str, Any]: 49 | mean_value = [sum(x) / len(x) for x in zip(*[s['value'] for s in res])] 50 | mean_smoothed = [sum(x) / len(x) for x in zip(*[s['smoothed'] for s in res])] 51 | step = res[0]['step'] 52 | 53 | return {'step': step, 'value': mean_value, 'smoothed': mean_smoothed, 'name': 'mean'} 54 | -------------------------------------------------------------------------------- /server/labml_app/analyses/preferences.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any 2 | 3 | PreferencesData = Dict[str, Any] 4 | SeriesPreferences = List[int] 5 | 6 | 7 | class Preferences: 8 | series_preferences: SeriesPreferences 9 | chart_type: int 10 | errors: List[Dict[str, str]] 11 | 12 | @classmethod 13 | def defaults(cls): 14 | return dict(series_preferences=[], 15 | chart_type=0, 16 | errors=[] 17 | ) 18 | 19 | def update_preferences(self, data: PreferencesData) -> None: 20 | if 'series_preferences' in data: 21 | self.update_series_preferences(data['series_preferences']) 22 | 23 | if 'chart_type' in data: 24 | self.chart_type = data['chart_type'] 25 | 26 | self.save() 27 | 28 | def update_series_preferences(self, data: SeriesPreferences) -> None: 29 | self.series_preferences = data 30 | 31 | def get_data(self) -> Dict[str, Any]: 32 | return { 33 | 'series_preferences': self.series_preferences, 34 | 'chart_type': self.chart_type, 35 | } 36 | -------------------------------------------------------------------------------- /server/labml_app/analyses/series_collection.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, List 2 | 3 | from ..analyses.series import SeriesModel, Series 4 | 5 | 6 | class SeriesCollection: 7 | tracking: Dict[str, SeriesModel] 8 | indicators: set 9 | step: int 10 | max_buffer_length: int 11 | 12 | @classmethod 13 | def defaults(cls): 14 | return dict(tracking={}, 15 | step=0, 16 | indicators=set(), 17 | max_buffer_length=None, 18 | ) 19 | 20 | def get_tracks(self) -> List[SeriesModel]: 21 | res = [] 22 | is_series_updated = False 23 | for ind, track in self.tracking.items(): 24 | name = ind.split('.') 25 | 26 | s = Series().load(track) 27 | series: Dict[str, Any] = s.detail 28 | series['name'] = '.'.join(name[1:]) 29 | 30 | if s.is_smoothed_updated: 31 | self.tracking[ind] = s.to_data() 32 | is_series_updated = True 33 | 34 | res.append(series) 35 | 36 | if is_series_updated: 37 | self.save() 38 | 39 | return res 40 | 41 | def track(self, data: Dict[str, SeriesModel]) -> None: 42 | for ind, series in data.items(): 43 | self.step = max(self.step, series['step'][-1]) 44 | self._update_series(ind, series) 45 | 46 | self.save() 47 | 48 | def _update_series(self, ind: str, series: SeriesModel) -> None: 49 | if ind not in self.tracking: 50 | self.tracking[ind] = Series(self.max_buffer_length).to_data() 51 | 52 | s = Series(self.max_buffer_length).load(self.tracking[ind]) 53 | s.update(series['step'], series['value']) 54 | 55 | self.tracking[ind] = s.to_data() 56 | 57 | def save(self): 58 | raise NotImplementedError 59 | -------------------------------------------------------------------------------- /server/labml_app/analyses_settings.sample.py: -------------------------------------------------------------------------------- 1 | from .analyses.experiments.parameters import ParametersAnalysis 2 | from .analyses.experiments.gradients import GradientsAnalysis 3 | from .analyses.experiments.metrics import MetricsAnalysis 4 | from .analyses.experiments.outputs import OutputsAnalysis 5 | from .analyses.experiments.hyperparameters import HyperParamsAnalysis 6 | 7 | from .analyses.computers.cpu import CPUAnalysis 8 | from .analyses.computers.gpu import GPUAnalysis 9 | from .analyses.computers.memory import MemoryAnalysis 10 | from .analyses.computers.network import NetworkAnalysis 11 | from .analyses.computers.disk import DiskAnalysis 12 | from .analyses.computers.process import ProcessAnalysis 13 | 14 | experiment_analyses = [GradientsAnalysis, 15 | OutputsAnalysis, 16 | ParametersAnalysis, 17 | HyperParamsAnalysis, 18 | MetricsAnalysis] 19 | 20 | computer_analyses = [CPUAnalysis, 21 | GPUAnalysis, 22 | MemoryAnalysis, 23 | NetworkAnalysis, 24 | DiskAnalysis, 25 | ProcessAnalysis] 26 | 27 | INDICATORS_LIMIT = 100 28 | -------------------------------------------------------------------------------- /server/labml_app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import inspect 3 | from typing import Optional 4 | 5 | from fastapi import Request 6 | from fastapi.responses import JSONResponse 7 | 8 | from ..db import app_token 9 | from ..db import project 10 | from ..db import user 11 | from .. import settings 12 | 13 | 14 | def get_app_token(request: Request) -> 'app_token.AppToken': 15 | token_id = request.headers.get('Authorization', '') 16 | 17 | return app_token.get_or_create(token_id) 18 | 19 | 20 | def check_labml_token_permission(func) -> functools.wraps: 21 | @functools.wraps(func) 22 | def wrapper(*args, **kwargs): 23 | labml_token = kwargs.get('labml_token', '') 24 | 25 | p = project.get_project(labml_token) 26 | if p and p.is_sharable: 27 | return func(*args, **kwargs) 28 | 29 | kwargs['labml_token'] = None 30 | 31 | return func(*args, **kwargs) 32 | 33 | return wrapper 34 | 35 | 36 | def login_required(func) -> functools.wraps: 37 | @functools.wraps(func) 38 | async def wrapper(request: Request, *args, **kwargs): 39 | token_id = request.headers.get('Authorization', '') 40 | at = app_token.get_or_create(token_id) 41 | if at.is_auth or not settings.IS_LOGIN_REQUIRED: 42 | if inspect.iscoroutinefunction(func): 43 | return await func(request, *args, **kwargs) 44 | else: 45 | return func(request, *args, **kwargs) 46 | else: 47 | response = JSONResponse() 48 | response.status_code = 403 49 | 50 | return response 51 | 52 | return wrapper 53 | 54 | 55 | def get_auth_user(request: Request) -> Optional['user.User']: 56 | s = get_app_token(request) 57 | 58 | u = None 59 | if s.user: 60 | u = s.user.load() 61 | 62 | if not settings.IS_LOGIN_REQUIRED: 63 | u = user.get_or_create_user(user.AuthOInfo( 64 | **{k: '' for k in ('name', 'email', 'sub', 'email_verified', 'picture')})) 65 | 66 | return u 67 | 68 | 69 | def get_is_user_logged(request: Request) -> bool: 70 | s = get_app_token(request) 71 | 72 | if s.is_auth or not settings.IS_LOGIN_REQUIRED: 73 | return True 74 | 75 | return False 76 | -------------------------------------------------------------------------------- /server/labml_app/block_uuids.sample.py: -------------------------------------------------------------------------------- 1 | delete_run_uuids = [] 2 | -------------------------------------------------------------------------------- /server/labml_app/db/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | from labml_db import Model, Index 5 | from labml_db.driver.redis import RedisDbDriver 6 | from labml_db.driver.file import FileDbDriver 7 | from labml_db.index_driver.redis import RedisIndexDbDriver 8 | from labml_db.index_driver.file import FileIndexDbDriver 9 | from labml_db.serializer.json import JsonSerializer 10 | from labml_db.serializer.yaml import YamlSerializer 11 | from labml_db.serializer.pickle import PickleSerializer 12 | 13 | from . import project 14 | from . import user 15 | from . import status 16 | from . import app_token 17 | from . import run 18 | from . import session 19 | from . import computer 20 | from . import job 21 | from . import blocked_uuids 22 | from .. import analyses 23 | from .. import settings 24 | 25 | Models = [(YamlSerializer(), user.User), 26 | (YamlSerializer(), project.Project), 27 | (JsonSerializer(), status.Status), 28 | (JsonSerializer(), status.RunStatus), 29 | (JsonSerializer(), app_token.AppToken), 30 | (JsonSerializer(), run.Run), 31 | (JsonSerializer(), session.Session), 32 | (PickleSerializer(), job.Job), 33 | (PickleSerializer(), computer.Computer)] + [(s(), m) for s, m, p in analyses.AnalysisManager.get_db_models()] 34 | 35 | Indexes = [project.ProjectIndex, 36 | user.UserIndex, 37 | blocked_uuids.BlockedRunIndex, 38 | blocked_uuids.BlockedSessionIndex, 39 | user.TokenOwnerIndex, 40 | app_token.AppTokenIndex, 41 | run.RunIndex, 42 | session.SessionIndex, 43 | job.JobIndex, 44 | computer.ComputerIndex] + [m for s, m, p in analyses.AnalysisManager.get_db_indexes()] 45 | 46 | 47 | def get_data_path(): 48 | package_path = Path(os.path.dirname(os.path.abspath(__file__))).parent 49 | 50 | data_path = package_path / 'data' 51 | if not data_path.exists(): 52 | raise RuntimeError(f'Data folder not found. Package path: {str(package_path)}') 53 | 54 | return data_path 55 | 56 | 57 | def init_db(): 58 | data_path = get_data_path() 59 | 60 | if settings.IS_LOCAL_SETUP: 61 | Model.set_db_drivers( 62 | [FileDbDriver(PickleSerializer(), m, Path(f'{data_path}/{m.__name__}')) for s, m in Models]) 63 | Index.set_db_drivers( 64 | [FileIndexDbDriver(YamlSerializer(), m, Path(f'{data_path}/{m.__name__}.yaml')) for m in Indexes]) 65 | else: 66 | import redis 67 | db = redis.Redis(host='localhost', port=6379, db=0) 68 | 69 | Model.set_db_drivers([RedisDbDriver(s, m, db) for s, m in Models]) 70 | Index.set_db_drivers([RedisIndexDbDriver(m, db) for m in Indexes]) 71 | 72 | project.create_project(settings.FLOAT_PROJECT_TOKEN, 'float project') 73 | project.create_project(settings.SAMPLES_PROJECT_TOKEN, 'samples project') 74 | -------------------------------------------------------------------------------- /server/labml_app/db/app_token.py: -------------------------------------------------------------------------------- 1 | import time 2 | from uuid import uuid4 3 | 4 | from labml_db import Model, Key, Index 5 | 6 | from . import user 7 | 8 | EXPIRATION_DELAY = 60 * 60 * 24 * 30 9 | 10 | 11 | def gen_token_id() -> str: 12 | return uuid4().hex 13 | 14 | 15 | def gen_expiration() -> float: 16 | return time.time() + EXPIRATION_DELAY 17 | 18 | 19 | class AppToken(Model['Session']): 20 | token_id: str 21 | expiration: float 22 | user: Key['user.User'] 23 | 24 | @classmethod 25 | def defaults(cls): 26 | return dict(token_id='', 27 | expiration='', 28 | user=None 29 | ) 30 | 31 | @property 32 | def is_auth(self) -> bool: 33 | return self.user is not None and self.expiration > time.time() 34 | 35 | 36 | class AppTokenIndex(Index['AppToken']): 37 | pass 38 | 39 | 40 | def get_or_create(token_id: str) -> AppToken: 41 | if not token_id: 42 | token_id = gen_token_id() 43 | 44 | app_token_key = AppTokenIndex.get(token_id) 45 | 46 | if not app_token_key: 47 | app_token = AppToken(token_id=token_id, 48 | expiration=gen_expiration() 49 | ) 50 | app_token.save() 51 | AppTokenIndex.set(app_token.token_id, app_token.key) 52 | 53 | return app_token 54 | 55 | return app_token_key.load() 56 | 57 | 58 | def delete(app_token: AppToken) -> None: 59 | AppTokenIndex.delete(app_token.token_id) 60 | app_token.delete() 61 | -------------------------------------------------------------------------------- /server/labml_app/db/blocked_uuids.py: -------------------------------------------------------------------------------- 1 | from labml_db import Index 2 | 3 | from . import run 4 | from . import session 5 | 6 | 7 | class BlockedRunIndex(Index['BlockedRun']): 8 | pass 9 | 10 | 11 | class BlockedSessionIndex(Index['BlockedSession']): 12 | pass 13 | 14 | 15 | def add_blocked_run(r: 'run.Run') -> None: 16 | BlockedRunIndex.set(r.run_uuid, r.key) 17 | 18 | 19 | def add_blocked_session(s: 'session.Session') -> None: 20 | BlockedSessionIndex.set(s.session_uuid, s.key) 21 | 22 | 23 | def is_run_blocked(run_uuid: str) -> bool: 24 | run_key = BlockedRunIndex.get(run_uuid) 25 | 26 | return run_key is not None 27 | 28 | 29 | def is_session_blocked(session_uuid: str) -> bool: 30 | session_key = BlockedSessionIndex.get(session_uuid) 31 | 32 | return session_key is not None 33 | 34 | 35 | def remove_blocked_run(run_uuid: str) -> None: 36 | BlockedRunIndex.delete(run_uuid) 37 | 38 | 39 | def remove_blocked_session(session_uuid: str) -> None: 40 | BlockedSessionIndex.delete(session_uuid) 41 | -------------------------------------------------------------------------------- /server/labml_app/db/job.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Optional, Dict, Union, Any 3 | 4 | from labml_db import Model, Index 5 | 6 | from labml_app import utils 7 | 8 | 9 | class JobStatuses: 10 | INITIATED = 'initiated' 11 | FAIL = 'fail' 12 | SUCCESS = 'success' 13 | TIMEOUT = 'timeout' 14 | COMPUTER_OFFLINE = 'computer_offline' 15 | 16 | 17 | class JobMethods: 18 | START_TENSORBOARD = 'start_tensorboard' 19 | DELETE_RUNS = 'delete_runs' 20 | CLEAR_CHECKPOINTS = 'clear_checkpoints' 21 | CALL_SYNC = 'call_sync' 22 | 23 | 24 | NON_REPEATED_METHODS = [JobMethods.CALL_SYNC] 25 | 26 | JobDict = Dict[str, Union[str, float]] 27 | 28 | 29 | class Job(Model['Job']): 30 | job_uuid: str 31 | method: str 32 | status: str 33 | created_time: float 34 | completed_time: float 35 | data: Dict[str, Any] 36 | 37 | @classmethod 38 | def defaults(cls): 39 | return dict(job_uuid='', 40 | method='', 41 | status='', 42 | created_time=None, 43 | completed_time=None, 44 | data={}, 45 | ) 46 | 47 | @property 48 | def is_success(self) -> bool: 49 | return self.status == JobStatuses.SUCCESS 50 | 51 | @property 52 | def is_error(self) -> bool: 53 | return self.status == JobStatuses.FAIL 54 | 55 | @property 56 | def is_completed(self) -> bool: 57 | return self.status == JobStatuses.FAIL or self.status == JobStatuses.SUCCESS 58 | 59 | @property 60 | def is_non_repeated(self) -> bool: 61 | return self.method in NON_REPEATED_METHODS 62 | 63 | def to_data(self) -> JobDict: 64 | return { 65 | 'uuid': self.job_uuid, 66 | 'method': self.method, 67 | 'status': self.status, 68 | 'created_time': self.created_time, 69 | 'completed_time': self.completed_time, 70 | 'data': self.data 71 | } 72 | 73 | def update_job(self, status: str, data: Dict[str, Any]) -> None: 74 | self.status = status 75 | 76 | if self.status in [JobStatuses.SUCCESS, JobStatuses.FAIL]: 77 | self.completed_time = time.time() 78 | 79 | if type(data) is dict: 80 | self.data.update(data) 81 | 82 | self.save() 83 | 84 | 85 | class JobIndex(Index['Job']): 86 | pass 87 | 88 | 89 | def create(method: str, data: Dict[str, Any]) -> Job: 90 | job = Job(job_uuid=utils.gen_token(), 91 | method=method, 92 | created_time=time.time(), 93 | data=data, 94 | status=JobStatuses.INITIATED, 95 | ) 96 | job.save() 97 | JobIndex.set(job.job_uuid, job.key) 98 | 99 | return job 100 | 101 | 102 | def get(job_uuid: str) -> Optional[Job]: 103 | job_key = JobIndex.get(job_uuid) 104 | 105 | if job_key: 106 | return job_key.load() 107 | 108 | return None 109 | 110 | 111 | def delete(job_uuid: str) -> None: 112 | job_key = JobIndex.get(job_uuid) 113 | 114 | if job_key: 115 | job_key.delete() 116 | JobIndex.delete(job_uuid) 117 | -------------------------------------------------------------------------------- /server/labml_app/db/status.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Dict 3 | 4 | from labml_db import Model, Key 5 | 6 | from ..enums import RunEnums 7 | 8 | 9 | class RunStatus(Model['RunStatusModel']): 10 | status: str 11 | details: object 12 | time: float 13 | 14 | @classmethod 15 | def defaults(cls): 16 | return dict(status='', 17 | details=None, 18 | time=None 19 | ) 20 | 21 | 22 | class Status(Model['Status']): 23 | last_updated_time: float 24 | run_status: Key[RunStatus] 25 | 26 | @classmethod 27 | def defaults(cls): 28 | return dict(last_updated_time=None, 29 | run_status=None 30 | ) 31 | 32 | def get_data(self) -> Dict[str, any]: 33 | run_status = self.run_status.load().to_dict() 34 | run_status['status'] = self.get_true_status(run_status.get('status', '')) 35 | 36 | return { 37 | 'last_updated_time': self.last_updated_time, 38 | 'run_status': run_status 39 | } 40 | 41 | def update_time_status(self, data: Dict[str, any]) -> None: 42 | self.last_updated_time = time.time() 43 | 44 | s = data.get('status', {}) 45 | if s: 46 | run_status = self.run_status.load() 47 | 48 | run_status.status = s.get('status', run_status.status) 49 | run_status.details = s.get('details', run_status.details) 50 | run_status.time = s.get('time', run_status.time) 51 | 52 | run_status.save() 53 | 54 | self.save() 55 | 56 | def get_true_status(self, status: str = None) -> str: 57 | if not status: 58 | status = self.run_status.load().status 59 | not_responding = False 60 | 61 | if status == RunEnums.RUN_IN_PROGRESS: 62 | if self.last_updated_time is not None: 63 | time_diff = (time.time() - self.last_updated_time) / 60 64 | if time_diff > 15: 65 | not_responding = True 66 | 67 | if not_responding: 68 | return RunEnums.RUN_NOT_RESPONDING 69 | elif status == '': 70 | return RunEnums.RUN_UNKNOWN 71 | else: 72 | return status 73 | 74 | 75 | def create_status() -> Status: 76 | time_now = time.time() 77 | 78 | run_status = RunStatus(status=RunEnums.RUN_IN_PROGRESS, 79 | time=time_now 80 | ) 81 | status = Status(last_updated_time=time_now, 82 | run_status=run_status.key 83 | ) 84 | status.save() 85 | run_status.save() 86 | 87 | return status 88 | -------------------------------------------------------------------------------- /server/labml_app/db/user.py: -------------------------------------------------------------------------------- 1 | from typing import List, NamedTuple, Dict, Optional 2 | 3 | from labml_db import Model, Key, Index 4 | 5 | from . import project 6 | from .. import utils 7 | 8 | 9 | class User(Model['User']): 10 | name: str 11 | sub: str 12 | email: str 13 | picture: str 14 | theme: str 15 | is_dev: bool 16 | email_verified: bool 17 | projects: List[Key['project.Project']] 18 | 19 | @classmethod 20 | def defaults(cls): 21 | return dict(name='', 22 | sub='', 23 | email='', 24 | picture='', 25 | theme='light', 26 | is_dev=False, 27 | email_verified=False, 28 | projects=[] 29 | ) 30 | 31 | @property 32 | def default_project(self) -> 'project.Project': 33 | return self.projects[0].load() 34 | 35 | def get_data(self) -> Dict[str, any]: 36 | return { 37 | 'name': self.name, 38 | 'email': self.email, 39 | 'picture': self.picture, 40 | 'theme': self.theme, 41 | 'projects': [p.load().labml_token for p in self.projects], 42 | 'default_project': self.default_project.labml_token 43 | } 44 | 45 | def set_user(self, data) -> None: 46 | if 'theme' in data: 47 | self.theme = data['theme'] 48 | self.save() 49 | 50 | 51 | class UserIndex(Index['User']): 52 | pass 53 | 54 | 55 | class TokenOwnerIndex(Index['TokenOwner']): 56 | pass 57 | 58 | 59 | def get_token_owner(labml_token: str) -> Optional[str]: 60 | user_key = TokenOwnerIndex.get(labml_token) 61 | 62 | if user_key: 63 | user = user_key.load() 64 | return user.email 65 | 66 | return '' 67 | 68 | 69 | class AuthOInfo(NamedTuple): 70 | name: str 71 | sub: str 72 | email: str 73 | picture: str 74 | email_verified: bool 75 | 76 | 77 | def get_or_create_user(info: AuthOInfo) -> User: 78 | user_key = UserIndex.get(info.email) 79 | 80 | if not user_key: 81 | p = project.Project(labml_token=utils.gen_token()) 82 | user = User(name=info.name, 83 | sub=info.sub, 84 | email=info.email, 85 | picture=info.picture, 86 | email_verified=info.email_verified, 87 | projects=[p.key] 88 | ) 89 | 90 | user.save() 91 | p.save() 92 | 93 | UserIndex.set(user.email, user.key) 94 | project.ProjectIndex.set(p.labml_token, p.key) 95 | TokenOwnerIndex.set(p.labml_token, user.key) 96 | 97 | return user 98 | 99 | return user_key.load() 100 | -------------------------------------------------------------------------------- /server/labml_app/enums.py: -------------------------------------------------------------------------------- 1 | class RunEnums: 2 | RUN_COMPLETED = 'completed' 3 | RUN_CRASHED = 'crashed' 4 | RUN_INTERRUPTED = 'interrupted' 5 | RUN_IN_PROGRESS = 'in progress' 6 | RUN_UNKNOWN = 'unknown' 7 | RUN_NOT_RESPONDING = 'no response' 8 | 9 | 10 | class SeriesEnums: 11 | GRAD = 'grad' 12 | PARAM = 'param' 13 | MODULE = 'module' 14 | TIME = 'time' 15 | METRIC = 'metric' 16 | HYPERPARAMS = 'hp' 17 | 18 | 19 | class COMPUTEREnums: 20 | CPU = 'cpu' 21 | GPU = 'gpu' 22 | DISK = 'disk' 23 | MEMORY = 'memory' 24 | NETWORK = 'net' 25 | PROCESS = 'process' 26 | BATTERY = 'battery' 27 | 28 | 29 | class InsightEnums: 30 | DANGER = 'danger' 31 | WARNING = 'warning' 32 | SUCCESS = 'success' 33 | 34 | 35 | INDICATORS = [SeriesEnums.GRAD, 36 | SeriesEnums.PARAM, 37 | SeriesEnums.TIME, 38 | SeriesEnums.MODULE, 39 | SeriesEnums.METRIC, 40 | SeriesEnums.HYPERPARAMS] 41 | -------------------------------------------------------------------------------- /server/labml_app/logger/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import logger 2 | 3 | _all__ = ["logger"] 4 | -------------------------------------------------------------------------------- /server/labml_app/logger/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging.handlers import RotatingFileHandler 3 | from logging import StreamHandler 4 | 5 | _LOG_PATH = 'labml_app.log' 6 | _MAX_BYTES = 100000 7 | 8 | 9 | class CustomFormatter(logging.Formatter): 10 | grey = "\x1b[38;21m" 11 | blue = "\x1b[36;21m" 12 | yellow = "\x1b[33;21m" 13 | red = "\x1b[31;21m" 14 | bold_red = "\x1b[31;1m" 15 | reset = "\x1b[0m" 16 | format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" 17 | 18 | FORMATS = { 19 | logging.DEBUG: grey + format + reset, 20 | logging.INFO: blue + format + reset, 21 | logging.WARNING: yellow + format + reset, 22 | logging.ERROR: red + format + reset, 23 | logging.CRITICAL: bold_red + format + reset 24 | } 25 | 26 | def format(self, record): 27 | log_fmt = self.FORMATS.get(record.levelno) 28 | formatter = logging.Formatter(log_fmt) 29 | return formatter.format(record) 30 | 31 | 32 | def _init_streaming_handler(): 33 | streaming = StreamHandler() 34 | streaming.setFormatter(CustomFormatter()) 35 | 36 | return streaming 37 | 38 | 39 | def _init_file_handler(): 40 | file_handler = RotatingFileHandler(filename=_LOG_PATH, maxBytes=_MAX_BYTES) 41 | file_handler.setFormatter(CustomFormatter()) 42 | 43 | return file_handler 44 | 45 | 46 | logger = logging.getLogger('LabML logger') 47 | logger.setLevel(logging.INFO) 48 | 49 | logger.addHandler(_init_streaming_handler()) 50 | logger.addHandler(_init_file_handler()) 51 | -------------------------------------------------------------------------------- /server/labml_app/scripts/clean_ups.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from labml_app import settings 4 | from labml_app import block_uuids 5 | from labml_app.logger import logger 6 | from labml_app.db import project, run, session, blocked_uuids, init_db 7 | 8 | 9 | def clean_float_project() -> None: 10 | p = project.get_project(settings.FLOAT_PROJECT_TOKEN) 11 | 12 | logger.info('Cleaning runs started') 13 | 14 | delete_run_list = [] 15 | for run_uuid, run_key in p.runs.items(): 16 | try: 17 | r = run_key.load() 18 | s = r.status.load() 19 | 20 | if not r.is_claimed and (time.time() - 86400) > s.last_updated_time: 21 | run.delete(run_uuid) 22 | blocked_uuids.remove_blocked_run(run_uuid) 23 | delete_run_list.append(run_uuid) 24 | logger.log(str(r.run_uuid)) 25 | elif (time.time() - 86400) > s.last_updated_time: 26 | delete_run_list.append(run_uuid) 27 | except TypeError: 28 | logger.error(f'error while deleting the run {run_uuid}') 29 | 30 | for run_uuid in delete_run_list: 31 | p.runs.pop(run_uuid) 32 | p.save() 33 | 34 | logger.info('......Done.........') 35 | logger.info('Cleaning sessions started') 36 | 37 | delete_session_list = [] 38 | for session_uuid, session_key in p.sessions.items(): 39 | try: 40 | ss = session_key.load() 41 | s = ss.status.load() 42 | 43 | if not ss.is_claimed and (time.time() - 86400) > s.last_updated_time: 44 | session.delete(session_uuid) 45 | blocked_uuids.remove_blocked_session(session_uuid) 46 | delete_session_list.append(session_uuid) 47 | elif (time.time() - 86400) > s.last_updated_time: 48 | delete_session_list.append(session_uuid) 49 | except TypeError: 50 | logger.error(f'error while deleting the session {session_uuid}') 51 | 52 | for session_uuid in delete_session_list: 53 | p.sessions.pop(session_uuid) 54 | p.save() 55 | 56 | logger.info('......Done.........') 57 | 58 | 59 | def move_to_samples(): 60 | logger.info('Samples moving started') 61 | p = project.get_project(settings.SAMPLES_PROJECT_TOKEN) 62 | for run_uuid in block_uuids.delete_run_uuids: 63 | r = run.get(run_uuid) 64 | 65 | if r.owner == 'samples': 66 | continue 67 | 68 | if r and r.owner != 'samples': 69 | r.owner = 'samples' 70 | p.add_run(run_uuid) 71 | 72 | r.save() 73 | p.save() 74 | 75 | logger.info('......Done.........') 76 | 77 | 78 | def add_block_uuids(): 79 | logger.info('add_block_uuids started') 80 | 81 | for run_uuid in block_uuids.update_run_uuids: 82 | r = run.get(run_uuid) 83 | if r: 84 | logger.info(r.run_uuid) 85 | blocked_uuids.add_blocked_run(r) 86 | 87 | logger.info('......Done.........') 88 | 89 | 90 | if __name__ == "__main__": 91 | init_db() 92 | clean_float_project() 93 | move_to_samples() 94 | add_block_uuids() 95 | -------------------------------------------------------------------------------- /server/labml_app/scripts/db_checks.py: -------------------------------------------------------------------------------- 1 | from labml_app.logger import logger 2 | from labml_app.db import Models 3 | 4 | for s, m in Models: 5 | logger.info('checking: ' + str(m)) 6 | model_keys = m.get_all() 7 | 8 | for model_key in model_keys: 9 | try: 10 | m = model_key.load() 11 | except Exception as e: 12 | logger.error(e) 13 | 14 | # TODO add more checks 15 | -------------------------------------------------------------------------------- /server/labml_app/scripts/temp_cleans.py: -------------------------------------------------------------------------------- 1 | from labml_app.db import run, init_db, project 2 | 3 | init_db() 4 | 5 | run_keys = run.Run.get_all() 6 | for run_key in run_keys: 7 | r = run_key.load() 8 | if r.is_claimed and not r.owner: 9 | run.delete(r.run_uuid) 10 | 11 | print(len(run_keys)) 12 | -------------------------------------------------------------------------------- /server/labml_app/settings.sample.py: -------------------------------------------------------------------------------- 1 | from pathlib import PurePath 2 | 3 | SERVER_URL = 'http://localhost:5000' 4 | WEB_URL = 'http://localhost:5000' 5 | SLACK_BOT_TOKEN = 'XXX' 6 | SLACK_CHANNEL = 'XXX' 7 | AUTH0_DOMAIN = 'XXX' 8 | DATA_PATH = PurePath('/xxx/xxx/data') 9 | SENTRY_DSN = 'XXX' 10 | FLOAT_PROJECT_TOKEN = 'XXXX' 11 | SAMPLES_PROJECT_TOKEN = 'XXX' 12 | LABML_VERSION = 'XXX' 13 | IS_MIX_PANEL = False 14 | IS_LOCAL_SETUP = True 15 | INDICATOR_LIMIT = 100 16 | IS_LOGIN_REQUIRED = True 17 | -------------------------------------------------------------------------------- /server/labml_app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Callable 3 | from uuid import uuid4 4 | from functools import wraps 5 | 6 | from . import mix_panel 7 | 8 | 9 | def check_version(user_v, new_v) -> bool: 10 | for uv, nw in zip(user_v.split('.'), new_v.split('.')): 11 | if int(nw) == int(uv): 12 | continue 13 | elif int(nw) > int(uv): 14 | return True 15 | else: 16 | return False 17 | 18 | 19 | def gen_token() -> str: 20 | return uuid4().hex 21 | 22 | 23 | def time_this(function) -> Callable: 24 | @wraps(function) 25 | def time_wrapper(*args, **kwargs): 26 | start = time.time() 27 | r = function(*args, **kwargs) 28 | end = time.time() 29 | 30 | total_time = end - start 31 | print(function.__name__, total_time) 32 | 33 | return r 34 | 35 | return time_wrapper 36 | -------------------------------------------------------------------------------- /server/labml_app/utils/slack.py: -------------------------------------------------------------------------------- 1 | try: 2 | from slack_sdk import WebClient 3 | from slack_sdk.errors import SlackApiError 4 | except ImportError as e: 5 | WebClient = None 6 | SlackApiError = None 7 | 8 | from labml_app import settings 9 | 10 | 11 | class SlackMessage: 12 | def __init__(self): 13 | self._client = WebClient(settings.SLACK_BOT_TOKEN) 14 | 15 | def send(self, text): 16 | res = {'error': '', 'success': False, 'ts': ''} 17 | 18 | if settings.SLACK_BOT_TOKEN and settings.SLACK_CHANNEL: 19 | try: 20 | ret = self._client.chat_postMessage( 21 | channel=settings.SLACK_CHANNEL, 22 | text=text, 23 | ) 24 | res['ts'] = ret['ts'] 25 | res['success'] = True 26 | except SlackApiError as e: 27 | res['error'] = e.response["error"] 28 | 29 | return res 30 | 31 | 32 | class DummySlackMessage: 33 | def send(self, text): 34 | return {'error': '', 'success': False, 'ts': ''} 35 | 36 | 37 | if WebClient is not None: 38 | client = SlackMessage() 39 | else: 40 | client = DummySlackMessage() 41 | -------------------------------------------------------------------------------- /server/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("../readme.md", "r") as f: 4 | long_description = f.read() 5 | 6 | setuptools.setup( 7 | name='labml_app', 8 | version='0.0.0', 9 | author="Varuna Jayasiri, Nipun, Aditya", 10 | author_email="vpjayasiri@gmail.com", 11 | description="", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/lab-ml/app", 15 | project_urls={ 16 | 'Documentation': 'https://lab-ml.com/' 17 | }, 18 | install_requires=['labml>=0.4.87', 19 | 'gunicorn', 20 | 'numpy', 21 | 'labml-db', 22 | 'fastapi', 23 | 'uvicorn', 24 | 'aiofiles', 25 | ], 26 | packages=['labml_app'], 27 | include_package_data=True, 28 | classifiers=[ 29 | "Programming Language :: Python :: 3", 30 | "License :: OSI Approved :: MIT License", 31 | 'Intended Audience :: Developers', 32 | 'Intended Audience :: Science/Research', 33 | 'Topic :: Scientific/Engineering', 34 | 'Topic :: Scientific/Engineering :: Mathematics', 35 | 'Topic :: Scientific/Engineering :: Artificial Intelligence', 36 | 'Topic :: Software Development', 37 | 'Topic :: Software Development :: Libraries', 38 | 'Topic :: Software Development :: Libraries :: Python Modules', 39 | ], 40 | keywords='machine learning', 41 | ) 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "outDir": "dist", 8 | "allowJs": true, 9 | "downlevelIteration": true 10 | }, 11 | "include": [ 12 | "ui/src", 13 | "lib/io/io.ts", 14 | "lib/io/ajax.ts", 15 | "lib/weya/weya.ts", 16 | "lib/weya/router.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ui/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /ui/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /ui/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/apple-touch-icon.png -------------------------------------------------------------------------------- /ui/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/favicon-16x16.png -------------------------------------------------------------------------------- /ui/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/favicon-32x32.png -------------------------------------------------------------------------------- /ui/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/favicon.ico -------------------------------------------------------------------------------- /ui/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/favicon.png -------------------------------------------------------------------------------- /ui/images/lab_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/lab_logo.png -------------------------------------------------------------------------------- /ui/images/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ui/images/tf_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labmlai/app/d525baa516f60658595cf9308207ea71421d7f76/ui/images/tf_Icon.png -------------------------------------------------------------------------------- /ui/src/analyses/analyses.ts: -------------------------------------------------------------------------------- 1 | import {Analysis} from "./types" 2 | 3 | import metricAnalysis from "./experiments/metrics" 4 | import hyperPramsAnalysis from "./experiments/hyper_params" 5 | import gradientAnalysis from "./experiments/grads" 6 | import parameterAnalysis from "./experiments/params" 7 | import moduleAnalysis from "./experiments/activations" 8 | import stdOutAnalysis from "./experiments/stdout" 9 | import stderrAnalysis from "./experiments/stderror" 10 | import loggerAnalysis from "./experiments/logger" 11 | import runConfigsAnalysis from "./experiments/configs" 12 | 13 | import cpuAnalysis from './sessions/cpu' 14 | import diskAnalysis from './sessions/disk' 15 | import {gpuMemoryAnalysis, gpuPowerAnalysis, gpuTempAnalysis, gpuUtilAnalysis} from './sessions/gpu' 16 | import memoryAnalysis from './sessions/memory' 17 | import networkAnalysis from './sessions/network' 18 | import processAnalysis from './sessions/process' 19 | import batteryAnalysis from './sessions/battery' 20 | import sessionConfigsAnalysis from "./sessions/configs" 21 | import comparisonAnalysis from './experiments/comaprison' 22 | 23 | let experimentAnalyses: Analysis[] = [ 24 | metricAnalysis, 25 | comparisonAnalysis, 26 | runConfigsAnalysis, 27 | hyperPramsAnalysis, 28 | gradientAnalysis, 29 | parameterAnalysis, 30 | moduleAnalysis, 31 | stdOutAnalysis, 32 | stderrAnalysis, 33 | loggerAnalysis 34 | ] 35 | 36 | let sessionAnalyses: Analysis[] = [ 37 | cpuAnalysis, 38 | processAnalysis, 39 | memoryAnalysis, 40 | diskAnalysis, 41 | gpuUtilAnalysis, 42 | gpuTempAnalysis, 43 | gpuMemoryAnalysis, 44 | gpuPowerAnalysis, 45 | batteryAnalysis, 46 | networkAnalysis, 47 | sessionConfigsAnalysis, 48 | ] 49 | 50 | export { 51 | experimentAnalyses, 52 | sessionAnalyses 53 | } 54 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/activations/cache.ts: -------------------------------------------------------------------------------- 1 | import {RunStatusCache, AnalysisDataCache, AnalysisPreferenceCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class ActivationsAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: RunStatusCache) { 6 | super(uuid, 'outputs', statusCache) 7 | } 8 | } 9 | 10 | class ActivationsPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'outputs') 13 | } 14 | } 15 | 16 | let activationsCache = new AnalysisCache('run', ActivationsAnalysisCache, ActivationsPreferenceCache) 17 | 18 | export default activationsCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/activations/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction} from '../../../../../lib/weya/weya' 2 | import {AnalysisDataModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {SimpleLinesChart} from "../../../components/charts/simple_lines/chart" 6 | import activationsCache from "./cache" 7 | import {DataLoader} from "../../../components/loader" 8 | import {ROUTER} from '../../../app' 9 | 10 | export class ActivationsCard extends Card { 11 | uuid: string 12 | width: number 13 | analysisData: AnalysisDataModel 14 | analysisCache: AnalysisDataCache 15 | lineChartContainer: HTMLDivElement 16 | elem: HTMLDivElement 17 | private loader: DataLoader 18 | 19 | constructor(opt: CardOptions) { 20 | super(opt) 21 | 22 | this.uuid = opt.uuid 23 | this.width = opt.width 24 | this.analysisCache = activationsCache.getAnalysis(this.uuid) 25 | this.loader = new DataLoader(async (force) => { 26 | this.analysisData = await this.analysisCache.get(force) 27 | }) 28 | } 29 | 30 | getLastUpdated(): number { 31 | return this.analysisCache.lastUpdated 32 | } 33 | 34 | async render($: WeyaElementFunction) { 35 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 36 | $('h3', '.header', 'Activations') 37 | this.loader.render($) 38 | this.lineChartContainer = $('div', '') 39 | }) 40 | 41 | try { 42 | await this.loader.load() 43 | 44 | if (this.analysisData.summary.length > 0) { 45 | this.renderLineChart() 46 | } else { 47 | this.elem.classList.add('hide') 48 | } 49 | } catch (e) { 50 | 51 | } 52 | } 53 | 54 | renderLineChart() { 55 | this.lineChartContainer.innerHTML = '' 56 | $(this.lineChartContainer, $ => { 57 | new SimpleLinesChart({series: this.analysisData.summary, width: this.width}).render($) 58 | }) 59 | } 60 | 61 | async refresh() { 62 | try { 63 | await this.loader.load(true) 64 | if (this.analysisData.summary.length > 0) { 65 | this.renderLineChart() 66 | this.elem.classList.remove('hide') 67 | } 68 | } catch (e) { 69 | 70 | } 71 | } 72 | 73 | onClick = () => { 74 | ROUTER.navigate(`/run/${this.uuid}/outputs`) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/activations/index.ts: -------------------------------------------------------------------------------- 1 | import {ActivationsCard} from "./card" 2 | import {ActivationsHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let activationsAnalysis: Analysis = { 7 | card: ActivationsCard, 8 | viewHandler: ActivationsHandler, 9 | route: 'outputs' 10 | } 11 | 12 | export default activationsAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/comaprison/cache.ts: -------------------------------------------------------------------------------- 1 | import {RunStatusCache, AnalysisDataCache, AnalysisPreferenceCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class ComparisonAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: RunStatusCache) { 6 | super(uuid, 'metrics', statusCache) 7 | } 8 | } 9 | 10 | class ComparisonPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'compare') 13 | } 14 | } 15 | 16 | let comparisonCache = new AnalysisCache('run', ComparisonAnalysisCache, ComparisonPreferenceCache) 17 | 18 | export default comparisonCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/comaprison/index.ts: -------------------------------------------------------------------------------- 1 | import {ComparisonCard} from "./card" 2 | import {ComparisonHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let comparisonAnalysis: Analysis = { 7 | card: ComparisonCard, 8 | viewHandler: ComparisonHandler, 9 | route: 'compare' 10 | } 11 | 12 | export default comparisonAnalysis 13 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/configs/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction} from '../../../../../lib/weya/weya' 2 | import {Run} from "../../../models/run" 3 | import CACHE, {RunCache} from "../../../cache/cache" 4 | import {Card, CardOptions} from "../../types" 5 | import {DataLoader} from "../../../components/loader" 6 | import {Configs} from "./components" 7 | import {ROUTER} from '../../../app' 8 | 9 | export class RunConfigsCard extends Card { 10 | run: Run 11 | uuid: string 12 | width: number 13 | runCache: RunCache 14 | elem: HTMLDivElement 15 | configsContainer: HTMLDivElement 16 | private loader: DataLoader 17 | 18 | constructor(opt: CardOptions) { 19 | super(opt) 20 | 21 | this.uuid = opt.uuid 22 | this.width = opt.width 23 | this.runCache = CACHE.getRun(this.uuid) 24 | this.loader = new DataLoader(async (force) => { 25 | this.run = await this.runCache.get(force) 26 | }) 27 | } 28 | 29 | getLastUpdated(): number { 30 | return this.runCache.lastUpdated 31 | } 32 | 33 | async render($: WeyaElementFunction) { 34 | this.elem = $('div','.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 35 | $('h3','.header', 'Configurations') 36 | this.loader.render($) 37 | this.configsContainer = $('div') 38 | }) 39 | 40 | try { 41 | await this.loader.load() 42 | 43 | if (this.run.configs.length > 0) { 44 | this.renderConfigs() 45 | } else { 46 | this.elem.classList.add('hide') 47 | } 48 | } catch (e) { 49 | 50 | } 51 | } 52 | 53 | async refresh() { 54 | try { 55 | await this.loader.load(true) 56 | if (this.run.configs.length > 0) { 57 | this.renderConfigs() 58 | this.elem.classList.remove('hide') 59 | } 60 | } catch (e) { 61 | 62 | } 63 | } 64 | 65 | renderConfigs() { 66 | this.configsContainer.innerHTML = '' 67 | $(this.configsContainer, $ => { 68 | new Configs({configs: this.run.configs, width: this.width, isHyperParamOnly: true}).render($) 69 | }) 70 | } 71 | 72 | onClick = () => { 73 | ROUTER.navigate(`/run/${this.uuid}/configs`) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/configs/index.ts: -------------------------------------------------------------------------------- 1 | import {RunConfigsCard} from "./card" 2 | import {RunConfigsHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let runConfigsAnalysis: Analysis = { 7 | card: RunConfigsCard, 8 | viewHandler: RunConfigsHandler, 9 | route: 'configs' 10 | } 11 | 12 | export default runConfigsAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/grads/cache.ts: -------------------------------------------------------------------------------- 1 | import {RunStatusCache, AnalysisDataCache, AnalysisPreferenceCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class GradientAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: RunStatusCache) { 6 | super(uuid, 'gradients', statusCache) 7 | } 8 | } 9 | 10 | class GradientPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'gradients') 13 | } 14 | } 15 | 16 | let gradientsCache = new AnalysisCache('run', GradientAnalysisCache, GradientPreferenceCache) 17 | 18 | export default gradientsCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/grads/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {AnalysisDataModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {SimpleLinesChart} from "../../../components/charts/simple_lines/chart" 6 | import gradientsCache from "./cache" 7 | import {DataLoader} from "../../../components/loader" 8 | import {ROUTER} from '../../../app' 9 | 10 | export class GradientsCard extends Card { 11 | uuid: string 12 | width: number 13 | analysisData: AnalysisDataModel 14 | analysisCache: AnalysisDataCache 15 | lineChartContainer: HTMLDivElement 16 | elem: HTMLDivElement 17 | private loader: DataLoader 18 | 19 | constructor(opt: CardOptions) { 20 | super(opt) 21 | 22 | this.uuid = opt.uuid 23 | this.width = opt.width 24 | this.analysisCache = gradientsCache.getAnalysis(this.uuid) 25 | this.loader = new DataLoader(async (force) => { 26 | this.analysisData = await this.analysisCache.get(force) 27 | }) 28 | } 29 | 30 | getLastUpdated(): number { 31 | return this.analysisCache.lastUpdated 32 | } 33 | 34 | async render($: WeyaElementFunction) { 35 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 36 | $('h3', '.header', 'Gradients') 37 | this.loader.render($) 38 | this.lineChartContainer = $('div', '') 39 | }) 40 | 41 | try { 42 | await this.loader.load() 43 | 44 | if (this.analysisData.summary.length > 0) { 45 | this.renderLineChart() 46 | } else { 47 | this.elem.classList.add('hide') 48 | } 49 | } catch (e) { 50 | 51 | } 52 | } 53 | 54 | renderLineChart() { 55 | this.lineChartContainer.innerHTML = '' 56 | $(this.lineChartContainer, $ => { 57 | new SimpleLinesChart({series: this.analysisData.summary, width: this.width}).render($) 58 | }) 59 | } 60 | 61 | async refresh() { 62 | try { 63 | await this.loader.load(true) 64 | if (this.analysisData.summary.length > 0) { 65 | this.renderLineChart() 66 | this.elem.classList.remove('hide') 67 | } 68 | } catch (e) { 69 | 70 | } 71 | } 72 | 73 | onClick = () => { 74 | ROUTER.navigate(`/run/${this.uuid}/grads`) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/grads/index.ts: -------------------------------------------------------------------------------- 1 | import {GradientsCard} from "./card" 2 | import {GradientsHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let gradientsAnalysis: Analysis = { 7 | card: GradientsCard, 8 | viewHandler: GradientsHandler, 9 | route: 'grads' 10 | } 11 | 12 | export default gradientsAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/hyper_params/cache.ts: -------------------------------------------------------------------------------- 1 | import {RunStatusCache, AnalysisDataCache, AnalysisPreferenceCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class HyperPramsAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: RunStatusCache) { 6 | super(uuid, 'hyper_params', statusCache) 7 | } 8 | } 9 | 10 | class HyperPramsPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'hyper_params') 13 | } 14 | } 15 | 16 | let hyperParamsCache = new AnalysisCache('run', HyperPramsAnalysisCache, HyperPramsPreferenceCache) 17 | 18 | export default hyperParamsCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/hyper_params/index.ts: -------------------------------------------------------------------------------- 1 | import {HyperParamsCard} from "./card" 2 | import {HyperParamsHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let hyperPramsAnalysis: Analysis = { 7 | card: HyperParamsCard, 8 | viewHandler: HyperParamsHandler, 9 | route: 'hyper_params' 10 | } 11 | 12 | export default hyperPramsAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/logger/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction} from '../../../../../lib/weya/weya' 2 | import {Run} from "../../../models/run" 3 | import CACHE, {RunCache} from "../../../cache/cache" 4 | import {Card, CardOptions} from "../../types" 5 | import Filter from "../../../utils/ansi_to_html" 6 | import {DataLoader} from "../../../components/loader" 7 | import {ROUTER} from '../../../app' 8 | 9 | export class LoggerCard extends Card { 10 | run: Run 11 | uuid: string 12 | runCache: RunCache 13 | outputContainer: HTMLPreElement 14 | elem: HTMLDivElement 15 | filter: Filter 16 | private loader: DataLoader 17 | 18 | constructor(opt: CardOptions) { 19 | super(opt) 20 | 21 | this.uuid = opt.uuid 22 | this.runCache = CACHE.getRun(this.uuid) 23 | this.filter = new Filter({}) 24 | this.loader = new DataLoader(async (force) => { 25 | this.run = await this.runCache.get(force) 26 | }) 27 | } 28 | 29 | getLastTenLines(inputStr: string) { 30 | let split = inputStr.split("\n") 31 | 32 | let last10Lines 33 | if (split.length > 10) { 34 | last10Lines = split.slice(Math.max(split.length - 10, 1)) 35 | } else { 36 | last10Lines = split 37 | } 38 | 39 | return last10Lines.join("\n") 40 | } 41 | 42 | getLastUpdated(): number { 43 | return this.runCache.lastUpdated 44 | } 45 | 46 | async render($: WeyaElementFunction) { 47 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 48 | $('h3', '.header', 'Standard Logger') 49 | this.loader.render($) 50 | $('div', '.terminal-card.no-scroll', $ => { 51 | this.outputContainer = $('pre', '') 52 | }) 53 | }) 54 | 55 | try { 56 | await this.loader.load() 57 | 58 | if (this.run.logger) { 59 | this.renderOutput() 60 | } else { 61 | this.elem.classList.add('hide') 62 | } 63 | } catch (e) { 64 | 65 | } 66 | } 67 | 68 | renderOutput() { 69 | this.outputContainer.innerHTML = '' 70 | $(this.outputContainer, $ => { 71 | let output = $('div', '') 72 | output.innerHTML = this.filter.toHtml(this.getLastTenLines(this.run.logger)) 73 | }) 74 | } 75 | 76 | async refresh() { 77 | try { 78 | await this.loader.load(true) 79 | if (this.run.logger) { 80 | this.renderOutput() 81 | this.elem.classList.remove('hide') 82 | } 83 | } catch (e) { 84 | 85 | } 86 | } 87 | 88 | onClick = () => { 89 | ROUTER.navigate(`/run/${this.uuid}/logger`) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/logger/index.ts: -------------------------------------------------------------------------------- 1 | import {LoggerCard} from "./card" 2 | import {LoggerHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let loggerAnalysis: Analysis = { 7 | card: LoggerCard, 8 | viewHandler: LoggerHandler, 9 | route: 'logger' 10 | } 11 | 12 | export default loggerAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/metrics/cache.ts: -------------------------------------------------------------------------------- 1 | import {RunStatusCache, AnalysisDataCache, AnalysisPreferenceCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class MetricsAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: RunStatusCache) { 6 | super(uuid, 'metrics', statusCache) 7 | } 8 | } 9 | 10 | class MetricsPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'metrics') 13 | } 14 | } 15 | 16 | let metricsCache = new AnalysisCache('run', MetricsAnalysisCache, MetricsPreferenceCache) 17 | 18 | export default metricsCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/metrics/index.ts: -------------------------------------------------------------------------------- 1 | import {MetricsCard} from "./card" 2 | import {MetricsHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let metricsAnalysis: Analysis = { 7 | card: MetricsCard, 8 | viewHandler: MetricsHandler, 9 | route: 'metrics' 10 | } 11 | 12 | export default metricsAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/params/cache.ts: -------------------------------------------------------------------------------- 1 | import {RunStatusCache, AnalysisDataCache, AnalysisPreferenceCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class ParameterAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: RunStatusCache) { 6 | super(uuid, 'parameters', statusCache) 7 | } 8 | } 9 | 10 | class ParameterPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'parameters') 13 | } 14 | } 15 | 16 | let parametersCache = new AnalysisCache('run', ParameterAnalysisCache, ParameterPreferenceCache) 17 | 18 | export default parametersCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/params/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {AnalysisDataModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {SimpleLinesChart} from "../../../components/charts/simple_lines/chart" 6 | import parametersCache from "./cache" 7 | import {DataLoader} from "../../../components/loader" 8 | import {ROUTER} from '../../../app' 9 | 10 | export class ParametersCard extends Card { 11 | uuid: string 12 | width: number 13 | analysisData: AnalysisDataModel 14 | analysisCache: AnalysisDataCache 15 | elem: HTMLDivElement 16 | lineChartContainer: HTMLDivElement 17 | private loader: DataLoader 18 | 19 | constructor(opt: CardOptions) { 20 | super(opt) 21 | 22 | this.uuid = opt.uuid 23 | this.width = opt.width 24 | this.analysisCache = parametersCache.getAnalysis(this.uuid) 25 | this.loader = new DataLoader(async (force) => { 26 | this.analysisData = await this.analysisCache.get(force) 27 | }) 28 | } 29 | 30 | getLastUpdated(): number { 31 | return this.analysisCache.lastUpdated 32 | } 33 | 34 | async render($: WeyaElementFunction) { 35 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 36 | $('h3', '.header', 'Parameters') 37 | this.loader.render($) 38 | this.lineChartContainer = $('div', '') 39 | }) 40 | 41 | try { 42 | await this.loader.load() 43 | 44 | if (this.analysisData.summary.length > 0) { 45 | this.renderLineChart() 46 | } else { 47 | this.elem.classList.add('hide') 48 | } 49 | } catch (e) { 50 | 51 | } 52 | } 53 | 54 | renderLineChart() { 55 | this.lineChartContainer.innerHTML = '' 56 | $(this.lineChartContainer, $ => { 57 | new SimpleLinesChart({series: this.analysisData.summary, width: this.width}).render($) 58 | }) 59 | } 60 | 61 | async refresh() { 62 | try { 63 | await this.loader.load(true) 64 | 65 | if (this.analysisData.summary.length > 0) { 66 | this.renderLineChart() 67 | this.elem.classList.remove('hide') 68 | } 69 | } catch (e) { 70 | 71 | } 72 | } 73 | 74 | onClick = () => { 75 | ROUTER.navigate(`/run/${this.uuid}/params`) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/params/index.ts: -------------------------------------------------------------------------------- 1 | import {ParametersCard} from "./card" 2 | import {ParametersHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let parametersAnalysis: Analysis = { 7 | card: ParametersCard, 8 | viewHandler: ParametersHandler, 9 | route: 'params' 10 | } 11 | 12 | export default parametersAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/run_header/index.ts: -------------------------------------------------------------------------------- 1 | import {RunHeaderCard} from "./card" 2 | import {RunHeaderHandler} from "./view" 3 | 4 | 5 | let runHeaderAnalysis = { 6 | card: RunHeaderCard, 7 | viewHandler: RunHeaderHandler, 8 | route: 'header' 9 | } 10 | 11 | export default runHeaderAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/stderror/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction} from '../../../../../lib/weya/weya' 2 | import {Run} from "../../../models/run" 3 | import CACHE, {RunCache} from "../../../cache/cache" 4 | import {Card, CardOptions} from "../../types" 5 | import Filter from "../../../utils/ansi_to_html" 6 | import {DataLoader} from "../../../components/loader" 7 | import {ROUTER} from '../../../app'; 8 | 9 | export class StdErrorCard extends Card { 10 | run: Run 11 | uuid: string 12 | runCache: RunCache 13 | outputContainer: HTMLPreElement 14 | elem: HTMLDivElement 15 | filter: Filter 16 | private loader: DataLoader 17 | 18 | constructor(opt: CardOptions) { 19 | super(opt) 20 | 21 | this.uuid = opt.uuid 22 | this.runCache = CACHE.getRun(this.uuid) 23 | this.filter = new Filter({}) 24 | this.loader = new DataLoader(async (force) => { 25 | this.run = await this.runCache.get(force) 26 | }) 27 | } 28 | 29 | getLastTenLines(inputStr: string) { 30 | let split = inputStr.split("\n") 31 | 32 | let last10Lines 33 | if (split.length > 10) { 34 | last10Lines = split.slice(Math.max(split.length - 10, 1)) 35 | } else { 36 | last10Lines = split 37 | } 38 | 39 | return last10Lines.join("\n") 40 | } 41 | 42 | getLastUpdated(): number { 43 | return this.runCache.lastUpdated 44 | } 45 | 46 | async render($: WeyaElementFunction) { 47 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 48 | $('h3', '.header', 'Standard Error') 49 | this.loader.render($) 50 | $('div', '.terminal-card.no-scroll', $ => { 51 | this.outputContainer = $('pre', '') 52 | }) 53 | }) 54 | 55 | try { 56 | await this.loader.load() 57 | 58 | if (this.run.stderr) { 59 | this.renderOutput() 60 | } else { 61 | this.elem.classList.add('hide') 62 | } 63 | } catch (e) { 64 | 65 | } 66 | } 67 | 68 | renderOutput() { 69 | this.outputContainer.innerHTML = '' 70 | $(this.outputContainer, $ => { 71 | let output = $('div', '') 72 | output.innerHTML = this.filter.toHtml(this.getLastTenLines(this.run.stderr)) 73 | }) 74 | } 75 | 76 | async refresh() { 77 | try { 78 | await this.loader.load(true) 79 | if (this.run.stderr) { 80 | this.renderOutput() 81 | this.elem.classList.remove('hide') 82 | } 83 | } catch (e) { 84 | 85 | } 86 | } 87 | 88 | onClick = () => { 89 | ROUTER.navigate(`/run/${this.uuid}/stderr`) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/stderror/index.ts: -------------------------------------------------------------------------------- 1 | import {StdErrorCard} from "./card" 2 | import {StdErrorHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let stdErrorAnalysis: Analysis = { 7 | card: StdErrorCard, 8 | viewHandler: StdErrorHandler, 9 | route: 'stderr' 10 | } 11 | 12 | export default stdErrorAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/experiments/stdout/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction} from '../../../../../lib/weya/weya' 2 | import {Run} from "../../../models/run" 3 | import CACHE, {RunCache} from "../../../cache/cache" 4 | import {Card, CardOptions} from "../../types" 5 | import Filter from "../../../utils/ansi_to_html" 6 | import {DataLoader} from "../../../components/loader" 7 | import {ROUTER} from '../../../app' 8 | 9 | export class StdOutCard extends Card { 10 | run: Run 11 | uuid: string 12 | runCache: RunCache 13 | outputContainer: HTMLPreElement 14 | elem: HTMLDivElement 15 | filter: Filter 16 | private loader: DataLoader 17 | 18 | constructor(opt: CardOptions) { 19 | super(opt) 20 | 21 | this.uuid = opt.uuid 22 | this.runCache = CACHE.getRun(this.uuid) 23 | this.filter = new Filter({}) 24 | this.loader = new DataLoader(async (force) => { 25 | this.run = await this.runCache.get(force) 26 | }) 27 | } 28 | 29 | getLastTenLines(inputStr: string) { 30 | let split = inputStr.split("\n") 31 | 32 | let last10Lines 33 | if (split.length > 10) { 34 | last10Lines = split.slice(Math.max(split.length - 10, 1)) 35 | } else { 36 | last10Lines = split 37 | } 38 | 39 | return last10Lines.join("\n") 40 | } 41 | 42 | getLastUpdated(): number { 43 | return this.runCache.lastUpdated 44 | } 45 | 46 | async render($: WeyaElementFunction) { 47 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 48 | $('h3', '.header', 'Standard Output') 49 | this.loader.render($) 50 | $('div', '.terminal-card.no-scroll', $ => { 51 | this.outputContainer = $('pre', '') 52 | }) 53 | }) 54 | 55 | try { 56 | await this.loader.load() 57 | 58 | if (this.run.stdout) { 59 | this.renderOutput() 60 | } else { 61 | this.elem.classList.add('hide') 62 | } 63 | } catch (e) { 64 | 65 | } 66 | } 67 | 68 | renderOutput() { 69 | this.outputContainer.innerHTML = '' 70 | $(this.outputContainer, $ => { 71 | let output = $('div', '') 72 | output.innerHTML = this.filter.toHtml(this.getLastTenLines(this.run.stdout)) 73 | }) 74 | } 75 | 76 | async refresh() { 77 | try { 78 | await this.loader.load(true) 79 | if (this.run.stdout) { 80 | this.renderOutput() 81 | this.elem.classList.remove('hide') 82 | } 83 | } catch (e) { 84 | 85 | } 86 | } 87 | 88 | onClick = () => { 89 | ROUTER.navigate(`/run/${this.uuid}/stdout`) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/src/analyses/experiments/stdout/index.ts: -------------------------------------------------------------------------------- 1 | import {StdOutCard} from "./card" 2 | import {StdOutHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let stdOutAnalysis: Analysis = { 7 | card: StdOutCard, 8 | viewHandler: StdOutHandler, 9 | route: 'stdout' 10 | } 11 | 12 | export default stdOutAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/helpers.ts: -------------------------------------------------------------------------------- 1 | import CACHE, {AnalysisDataCache, AnalysisPreferenceCache, RunStatusCache, SessionStatusCache} from "../cache/cache" 2 | import {ContentType} from '../types' 3 | 4 | export class AnalysisCache { 5 | private readonly type: ContentType 6 | private readonly series: new (uuid: string, status: RunStatusCache | SessionStatusCache) => TA 7 | private readonly seriesCaches: { [uuid: string]: AnalysisDataCache } 8 | private readonly preferences: new (uuid: string) => TAP 9 | private readonly preferencesCaches: { [uuid: string]: AnalysisPreferenceCache } 10 | 11 | constructor(type: ContentType, series: new (uuid: string, status: RunStatusCache | SessionStatusCache) => TA, preferences: new (uuid: string) => TAP) { 12 | this.type = type 13 | this.seriesCaches = {} 14 | this.preferencesCaches = {} 15 | this.series = series 16 | this.preferences = preferences 17 | } 18 | 19 | getAnalysis(uuid: string) { 20 | if (this.seriesCaches[uuid] == null) { 21 | this.seriesCaches[uuid] = new this.series(uuid, this.getStatus(uuid)) 22 | } 23 | 24 | return this.seriesCaches[uuid] 25 | } 26 | 27 | getPreferences(uuid: string) { 28 | if (this.preferencesCaches[uuid] == null) { 29 | this.preferencesCaches[uuid] = new this.preferences(uuid) 30 | } 31 | 32 | return this.preferencesCaches[uuid] 33 | } 34 | 35 | private getStatus(uuid: string) { 36 | if (this.type === 'run') { 37 | return CACHE.getRunStatus(uuid) 38 | } else if (this.type === 'session') { 39 | return CACHE.getSessionStatus(uuid) 40 | } 41 | 42 | return null 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/battery/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class BatteryAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: SessionStatusCache) { 6 | super(uuid, 'battery', statusCache) 7 | } 8 | } 9 | 10 | class BatteryPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'battery') 13 | } 14 | } 15 | 16 | let batteryCache = new AnalysisCache('session', BatteryAnalysisCache, BatteryPreferenceCache) 17 | 18 | export default batteryCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/battery/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {toPointValues} from "../../../components/charts/utils" 6 | import {DataLoader} from "../../../components/loader" 7 | import batteryCache from './cache' 8 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 9 | import {Labels} from "../../../components/charts/labels" 10 | import {ROUTER} from '../../../app' 11 | 12 | export class BatteryCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | private loader: DataLoader 20 | private labelsContainer: HTMLDivElement 21 | 22 | constructor(opt: CardOptions) { 23 | super(opt) 24 | 25 | this.uuid = opt.uuid 26 | this.width = opt.width 27 | this.analysisCache = batteryCache.getAnalysis(this.uuid) 28 | this.loader = new DataLoader(async (force) => { 29 | let data = (await this.analysisCache.get(force)).summary 30 | this.series = toPointValues(data) 31 | }) 32 | } 33 | 34 | getLastUpdated(): number { 35 | return this.analysisCache.lastUpdated 36 | } 37 | 38 | async render($: WeyaElementFunction) { 39 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 40 | $('h3', '.header', 'Battery') 41 | this.loader.render($) 42 | this.lineChartContainer = $('div', '') 43 | this.labelsContainer = $('div', '') 44 | }) 45 | 46 | try { 47 | await this.loader.load() 48 | 49 | if (this.series.length > 0) { 50 | this.renderLineChart() 51 | } else { 52 | this.elem.classList.add('hide') 53 | } 54 | } catch (e) { 55 | 56 | } 57 | } 58 | 59 | renderLineChart() { 60 | this.lineChartContainer.innerHTML = '' 61 | $(this.lineChartContainer, $ => { 62 | new TimeSeriesChart({ 63 | series: this.series, 64 | width: this.width, 65 | plotIdx: [], 66 | yExtend: [0, 100], 67 | chartHeightFraction: 4, 68 | isDivergent: true 69 | }).render($) 70 | }) 71 | 72 | this.labelsContainer.innerHTML = '' 73 | $(this.labelsContainer, $ => { 74 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 75 | }) 76 | } 77 | 78 | async refresh() { 79 | try { 80 | await this.loader.load(true) 81 | if (this.series.length > 0) { 82 | this.renderLineChart() 83 | this.elem.classList.remove('hide') 84 | } 85 | } catch (e) { 86 | 87 | } 88 | } 89 | 90 | onClick = () => { 91 | ROUTER.navigate(`/session/${this.uuid}/battery`) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/battery/index.ts: -------------------------------------------------------------------------------- 1 | import {BatteryCard} from "./card" 2 | import {BatteryHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | let batteryAnalysis: Analysis = { 6 | card: BatteryCard, 7 | viewHandler: BatteryHandler, 8 | route: 'battery' 9 | } 10 | 11 | export default batteryAnalysis 12 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/configs/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction} from '../../../../../lib/weya/weya' 2 | import CACHE, {SessionCache} from "../../../cache/cache" 3 | import {Card, CardOptions} from "../../types" 4 | import {DataLoader} from "../../../components/loader" 5 | import {Configs} from "./components" 6 | import {ROUTER} from '../../../app' 7 | import {Config} from "../../../models/config" 8 | 9 | export class SessionConfigsCard extends Card { 10 | configs: Config[] 11 | uuid: string 12 | width: number 13 | sessionCache: SessionCache 14 | elem: HTMLDivElement 15 | configsContainer: HTMLDivElement 16 | private loader: DataLoader 17 | 18 | constructor(opt: CardOptions) { 19 | super(opt) 20 | 21 | this.uuid = opt.uuid 22 | this.width = opt.width 23 | this.sessionCache = CACHE.getSession(this.uuid) 24 | this.loader = new DataLoader(async (force) => { 25 | let session = await this.sessionCache.get(force) 26 | this.configs = session.configs.filter(conf => !conf['key'].includes('sensor')) 27 | }) 28 | } 29 | 30 | getLastUpdated(): number { 31 | return this.sessionCache.lastUpdated 32 | } 33 | 34 | async render($: WeyaElementFunction) { 35 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 36 | $('h3', '.header', 'Configurations') 37 | this.loader.render($) 38 | this.configsContainer = $('div') 39 | }) 40 | 41 | try { 42 | await this.loader.load() 43 | 44 | if (this.configs.length > 0) { 45 | this.renderConfigs() 46 | } else { 47 | this.elem.classList.add('hide') 48 | } 49 | } catch (e) { 50 | 51 | } 52 | } 53 | 54 | async refresh() { 55 | try { 56 | await this.loader.load(true) 57 | if (this.configs.length > 0) { 58 | this.renderConfigs() 59 | this.elem.classList.remove('hide') 60 | } 61 | } catch (e) { 62 | 63 | } 64 | } 65 | 66 | renderConfigs() { 67 | this.configsContainer.innerHTML = '' 68 | $(this.configsContainer, $ => { 69 | new Configs({configs: this.configs, width: this.width}).render($) 70 | }) 71 | } 72 | 73 | onClick = () => { 74 | ROUTER.navigate(`/session/${this.uuid}/configs`) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/configs/components.ts: -------------------------------------------------------------------------------- 1 | import {Config} from "../../../models/config" 2 | import {WeyaElementFunction} from "../../../../../lib/weya/weya" 3 | import {ComputedValue, KEY_WIDTH, PADDING} from "../../experiments/configs/components" 4 | 5 | 6 | interface ConfigsOptions { 7 | configs: Config[] 8 | width: number 9 | } 10 | 11 | export class Configs { 12 | configs: Config[] 13 | width: number 14 | count: number 15 | 16 | constructor(opt: ConfigsOptions) { 17 | this.configs = opt.configs 18 | this.width = opt.width 19 | } 20 | 21 | render($: WeyaElementFunction) { 22 | $('div', '.configs.block.collapsed', {style: {width: `${this.width}px`}}, $ => { 23 | this.configs.map((c) => 24 | $('div', '.info_list.config', $ => { 25 | $('span.key', c.key) 26 | $('span.combined', {style: {width: `${this.width - KEY_WIDTH - 2 * PADDING}px`}}, $ => { 27 | new ComputedValue({computed: c.value}).render($) 28 | }) 29 | }) 30 | ) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/configs/index.ts: -------------------------------------------------------------------------------- 1 | import {SessionConfigsCard} from "./card" 2 | import {SessionConfigsHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | 6 | let sessionConfigsAnalysis: Analysis = { 7 | card: SessionConfigsCard, 8 | viewHandler: SessionConfigsHandler, 9 | route: 'configs' 10 | } 11 | 12 | export default sessionConfigsAnalysis -------------------------------------------------------------------------------- /ui/src/analyses/sessions/cpu/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class CPUAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: SessionStatusCache) { 6 | super(uuid, 'cpu', statusCache) 7 | } 8 | } 9 | 10 | class CPUPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'cpu') 13 | } 14 | } 15 | 16 | let cpuCache = new AnalysisCache('session', CPUAnalysisCache, CPUPreferenceCache) 17 | 18 | export default cpuCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/cpu/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {toPointValues} from "../../../components/charts/utils" 6 | import {DataLoader} from "../../../components/loader" 7 | import cpuCache from './cache' 8 | import {TimeSeriesChart} from '../../../components/charts/timeseries/chart' 9 | import {Labels} from "../../../components/charts/labels" 10 | import {ROUTER} from '../../../app' 11 | 12 | export class CPUCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | private loader: DataLoader 20 | private labelsContainer: HTMLDivElement 21 | 22 | constructor(opt: CardOptions) { 23 | super(opt) 24 | 25 | this.uuid = opt.uuid 26 | this.width = opt.width 27 | this.analysisCache = cpuCache.getAnalysis(this.uuid) 28 | this.loader = new DataLoader(async (force) => { 29 | this.series = toPointValues((await this.analysisCache.get(force)).summary) 30 | }) 31 | } 32 | 33 | getLastUpdated(): number { 34 | return this.analysisCache.lastUpdated 35 | } 36 | 37 | async render($: WeyaElementFunction) { 38 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 39 | $('h3', '.header', 'CPU') 40 | this.loader.render($) 41 | this.lineChartContainer = $('div', '') 42 | this.labelsContainer = $('div', '') 43 | }) 44 | 45 | try { 46 | await this.loader.load() 47 | 48 | if (this.series.length > 0) { 49 | this.renderLineChart() 50 | } else { 51 | this.elem.classList.add('hide') 52 | } 53 | } catch (e) { 54 | 55 | } 56 | } 57 | 58 | renderLineChart() { 59 | this.lineChartContainer.innerHTML = '' 60 | $(this.lineChartContainer, $ => { 61 | new TimeSeriesChart({ 62 | series: this.series, 63 | width: this.width, 64 | plotIdx: [], 65 | yExtend: [0, 100], 66 | chartHeightFraction: 4, 67 | isDivergent: true 68 | }).render($) 69 | }) 70 | 71 | this.labelsContainer.innerHTML = '' 72 | $(this.labelsContainer, $ => { 73 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 74 | }) 75 | } 76 | 77 | async refresh() { 78 | try { 79 | await this.loader.load(true) 80 | if (this.series.length > 0) { 81 | this.renderLineChart() 82 | this.elem.classList.remove('hide') 83 | } 84 | } catch (e) { 85 | 86 | } 87 | } 88 | 89 | onClick = () => { 90 | ROUTER.navigate(`/session/${this.uuid}/cpu`) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/cpu/index.ts: -------------------------------------------------------------------------------- 1 | import {CPUCard} from "./card" 2 | import {CPUHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | let cpuAnalysis: Analysis = { 6 | card: CPUCard, 7 | viewHandler: CPUHandler, 8 | route: 'cpu' 9 | } 10 | 11 | export default cpuAnalysis 12 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/disk/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class DiskAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: SessionStatusCache) { 6 | super(uuid, 'disk', statusCache) 7 | } 8 | } 9 | 10 | class DiskPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'disk') 13 | } 14 | } 15 | 16 | let diskCache = new AnalysisCache('session', DiskAnalysisCache, DiskPreferenceCache) 17 | 18 | export default diskCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/disk/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {toPointValues} from "../../../components/charts/utils" 6 | import {DataLoader} from "../../../components/loader" 7 | import diskCache from './cache' 8 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 9 | import {Labels} from "../../../components/charts/labels" 10 | import {ROUTER} from '../../../app' 11 | 12 | export class DiskCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | private loader: DataLoader 20 | private labelsContainer: HTMLDivElement 21 | 22 | constructor(opt: CardOptions) { 23 | super(opt) 24 | 25 | this.uuid = opt.uuid 26 | this.width = opt.width 27 | this.analysisCache = diskCache.getAnalysis(this.uuid) 28 | this.loader = new DataLoader(async (force) => { 29 | this.series = toPointValues((await this.analysisCache.get(force)).series) 30 | }) 31 | } 32 | 33 | getLastUpdated(): number { 34 | return this.analysisCache.lastUpdated 35 | } 36 | 37 | async render($: WeyaElementFunction) { 38 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 39 | $('h3', '.header', 'Disk') 40 | this.loader.render($) 41 | this.lineChartContainer = $('div', '') 42 | this.labelsContainer = $('div', '') 43 | }) 44 | 45 | try { 46 | await this.loader.load() 47 | 48 | if (this.series.length > 0) { 49 | this.renderLineChart() 50 | } else { 51 | this.elem.classList.add('hide') 52 | } 53 | } catch (e) { 54 | 55 | } 56 | } 57 | 58 | renderLineChart() { 59 | this.lineChartContainer.innerHTML = '' 60 | $(this.lineChartContainer, $ => { 61 | new TimeSeriesChart({ 62 | series: this.series, 63 | width: this.width, 64 | plotIdx: [0, 1], 65 | chartHeightFraction: 4, 66 | isDivergent: true 67 | }).render($) 68 | }) 69 | 70 | this.labelsContainer.innerHTML = '' 71 | $(this.labelsContainer, $ => { 72 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 73 | }) 74 | } 75 | 76 | async refresh() { 77 | try { 78 | await this.loader.load(true) 79 | if (this.series.length > 0) { 80 | this.renderLineChart() 81 | this.elem.classList.remove('hide') 82 | } 83 | } catch (e) { 84 | 85 | } 86 | } 87 | 88 | onClick = () => { 89 | ROUTER.navigate(`/session/${this.uuid}/disk`) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/disk/index.ts: -------------------------------------------------------------------------------- 1 | import {DiskCard} from "./card" 2 | import {DiskHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | let diskAnalysis: Analysis = { 6 | card: DiskCard, 7 | viewHandler: DiskHandler, 8 | route: 'disk' 9 | } 10 | 11 | export default diskAnalysis 12 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class GPUAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: SessionStatusCache) { 6 | super(uuid, 'gpu', statusCache) 7 | } 8 | } 9 | 10 | class GPUPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'gpu') 13 | } 14 | } 15 | 16 | let gpuCache = new AnalysisCache('session', GPUAnalysisCache, GPUPreferenceCache) 17 | 18 | export default gpuCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/index.ts: -------------------------------------------------------------------------------- 1 | import {GPUUtilCard} from "./util_card" 2 | import {GPUTempCard} from "./temp_card" 3 | import {GPUMemoryCard} from "./memory_card" 4 | import {GPUPowerCard} from "./power_card" 5 | import {GPUUtilHandler} from "./util_view" 6 | import {GPUTempHandler} from "./temp_view" 7 | import {GPUMemoryHandler} from "./memory_view" 8 | import {GPUPowerHandler} from "./power_view" 9 | import {Analysis} from "../../types" 10 | 11 | let gpuUtilAnalysis: Analysis = { 12 | card: GPUUtilCard, 13 | viewHandler: GPUUtilHandler, 14 | route: 'gpu_util' 15 | } 16 | 17 | let gpuTempAnalysis: Analysis = { 18 | card: GPUTempCard, 19 | viewHandler: GPUTempHandler, 20 | route: 'gpu_temp' 21 | } 22 | 23 | let gpuMemoryAnalysis: Analysis = { 24 | card: GPUMemoryCard, 25 | viewHandler: GPUMemoryHandler, 26 | route: 'gpu_memory' 27 | } 28 | 29 | let gpuPowerAnalysis: Analysis = { 30 | card: GPUPowerCard, 31 | viewHandler: GPUPowerHandler, 32 | route: 'gpu_power' 33 | } 34 | 35 | export { 36 | gpuUtilAnalysis, 37 | gpuTempAnalysis, 38 | gpuMemoryAnalysis, 39 | gpuPowerAnalysis 40 | } 41 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/memory_card.ts: -------------------------------------------------------------------------------- 1 | import {SeriesModel} from "../../../models/run" 2 | import {AnalysisDataCache} from "../../../cache/cache" 3 | import {Weya as $, WeyaElementFunction} from "../../../../../lib/weya/weya" 4 | import {DataLoader} from "../../../components/loader" 5 | import {Card, CardOptions} from "../../types" 6 | import gpuCache from "./cache" 7 | import {getSeriesData} from "./utils" 8 | import {Labels} from "../../../components/charts/labels" 9 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 10 | import {ROUTER} from "../../../app" 11 | 12 | export class GPUMemoryCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | plotIdx: number[] = [] 20 | private loader: DataLoader 21 | private labelsContainer: HTMLDivElement 22 | 23 | constructor(opt: CardOptions) { 24 | super(opt) 25 | 26 | this.uuid = opt.uuid 27 | this.width = opt.width 28 | this.analysisCache = gpuCache.getAnalysis(this.uuid) 29 | this.loader = new DataLoader(async (force) => { 30 | this.series = getSeriesData((await this.analysisCache.get(force)).series, 'memory') 31 | }) 32 | } 33 | 34 | getLastUpdated(): number { 35 | return this.analysisCache.lastUpdated 36 | } 37 | 38 | async render($: WeyaElementFunction) { 39 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 40 | $('h3', '.header', 'GPU - Memory') 41 | this.loader.render($) 42 | this.lineChartContainer = $('div', '') 43 | this.labelsContainer = $('div', '') 44 | }) 45 | 46 | try { 47 | await this.loader.load() 48 | 49 | if (this.series.length > 0) { 50 | this.renderLineChart() 51 | } else { 52 | this.elem.classList.add('hide') 53 | } 54 | } catch (e) { 55 | 56 | } 57 | } 58 | 59 | renderLineChart() { 60 | let res: number[] = [] 61 | for (let i = 0; i < this.series.length; i++) { 62 | res.push(i) 63 | } 64 | this.plotIdx = res 65 | 66 | this.lineChartContainer.innerHTML = '' 67 | $(this.lineChartContainer, $ => { 68 | new TimeSeriesChart({ 69 | series: this.series, 70 | width: this.width, 71 | plotIdx: this.plotIdx, 72 | chartHeightFraction: 4, 73 | isDivergent: true 74 | }).render($) 75 | }) 76 | 77 | this.labelsContainer.innerHTML = '' 78 | $(this.labelsContainer, $ => { 79 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 80 | }) 81 | } 82 | 83 | async refresh() { 84 | try { 85 | await this.loader.load(true) 86 | if (this.series.length > 0) { 87 | this.renderLineChart() 88 | this.elem.classList.remove('hide') 89 | } 90 | } catch (e) { 91 | 92 | } 93 | } 94 | 95 | onClick = () => { 96 | ROUTER.navigate(`/session/${this.uuid}/gpu_memory`) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/power_card.ts: -------------------------------------------------------------------------------- 1 | import {SeriesModel} from "../../../models/run" 2 | import {AnalysisDataCache} from "../../../cache/cache" 3 | import {Weya as $, WeyaElementFunction} from "../../../../../lib/weya/weya" 4 | import {DataLoader} from "../../../components/loader" 5 | import {Card, CardOptions} from "../../types" 6 | import gpuCache from "./cache" 7 | import {getSeriesData} from "./utils" 8 | import {Labels} from "../../../components/charts/labels" 9 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 10 | import {ROUTER} from "../../../app" 11 | 12 | export class GPUPowerCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | plotIdx: number[] = [] 20 | private loader: DataLoader 21 | private labelsContainer: HTMLDivElement 22 | 23 | constructor(opt: CardOptions) { 24 | super(opt) 25 | 26 | this.uuid = opt.uuid 27 | this.width = opt.width 28 | this.analysisCache = gpuCache.getAnalysis(this.uuid) 29 | this.loader = new DataLoader(async (force) => { 30 | this.series = getSeriesData((await this.analysisCache.get(force)).series, 'power') 31 | }) 32 | } 33 | 34 | getLastUpdated(): number { 35 | return this.analysisCache.lastUpdated 36 | } 37 | 38 | async render($: WeyaElementFunction) { 39 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 40 | $('h3', '.header', 'GPU - Power') 41 | this.loader.render($) 42 | this.lineChartContainer = $('div', '') 43 | this.labelsContainer = $('div', '') 44 | }) 45 | 46 | try { 47 | await this.loader.load() 48 | 49 | if (this.series.length > 0) { 50 | this.renderLineChart() 51 | } else { 52 | this.elem.classList.add('hide') 53 | } 54 | } catch (e) { 55 | 56 | } 57 | } 58 | 59 | renderLineChart() { 60 | let res: number[] = [] 61 | for (let i = 0; i < this.series.length; i++) { 62 | res.push(i) 63 | } 64 | this.plotIdx = res 65 | 66 | this.lineChartContainer.innerHTML = '' 67 | $(this.lineChartContainer, $ => { 68 | new TimeSeriesChart({ 69 | series: this.series, 70 | width: this.width, 71 | plotIdx: this.plotIdx, 72 | chartHeightFraction: 4, 73 | isDivergent: true 74 | }).render($) 75 | }) 76 | 77 | this.labelsContainer.innerHTML = '' 78 | $(this.labelsContainer, $ => { 79 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 80 | }) 81 | } 82 | 83 | async refresh() { 84 | try { 85 | await this.loader.load(true) 86 | if (this.series.length > 0) { 87 | this.renderLineChart() 88 | this.elem.classList.remove('hide') 89 | } 90 | } catch (e) { 91 | 92 | } 93 | } 94 | 95 | onClick = () => { 96 | ROUTER.navigate(`/session/${this.uuid}/gpu_power`) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/temp_card.ts: -------------------------------------------------------------------------------- 1 | import {SeriesModel} from "../../../models/run" 2 | import {AnalysisDataCache} from "../../../cache/cache" 3 | import {Weya as $, WeyaElementFunction} from "../../../../../lib/weya/weya" 4 | import {DataLoader} from "../../../components/loader" 5 | import {Card, CardOptions} from "../../types" 6 | import gpuCache from "./cache" 7 | import {getSeriesData} from "./utils" 8 | import {Labels} from "../../../components/charts/labels" 9 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 10 | import {ROUTER} from "../../../app" 11 | 12 | export class GPUTempCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | plotIdx: number[] = [] 20 | private loader: DataLoader 21 | private labelsContainer: HTMLDivElement 22 | 23 | constructor(opt: CardOptions) { 24 | super(opt) 25 | 26 | this.uuid = opt.uuid 27 | this.width = opt.width 28 | this.analysisCache = gpuCache.getAnalysis(this.uuid) 29 | this.loader = new DataLoader(async (force) => { 30 | this.series = getSeriesData((await this.analysisCache.get(force)).series, 'temperature') 31 | }) 32 | } 33 | 34 | getLastUpdated(): number { 35 | return this.analysisCache.lastUpdated 36 | } 37 | 38 | async render($: WeyaElementFunction) { 39 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 40 | $('h3', '.header', 'GPU - Temperature') 41 | this.loader.render($) 42 | this.lineChartContainer = $('div', '') 43 | this.labelsContainer = $('div', '') 44 | }) 45 | 46 | try { 47 | await this.loader.load() 48 | 49 | if (this.series.length > 0) { 50 | this.renderLineChart() 51 | } else { 52 | this.elem.classList.add('hide') 53 | } 54 | } catch (e) { 55 | 56 | } 57 | } 58 | 59 | renderLineChart() { 60 | let res: number[] = [] 61 | for (let i = 0; i < this.series.length; i++) { 62 | res.push(i) 63 | } 64 | this.plotIdx = res 65 | 66 | this.lineChartContainer.innerHTML = '' 67 | $(this.lineChartContainer, $ => { 68 | new TimeSeriesChart({ 69 | series: this.series, 70 | width: this.width, 71 | plotIdx: this.plotIdx, 72 | chartHeightFraction: 4, 73 | isDivergent: true 74 | }).render($) 75 | }) 76 | 77 | this.labelsContainer.innerHTML = '' 78 | $(this.labelsContainer, $ => { 79 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 80 | }) 81 | } 82 | 83 | async refresh() { 84 | try { 85 | await this.loader.load(true) 86 | if (this.series.length > 0) { 87 | this.renderLineChart() 88 | this.elem.classList.remove('hide') 89 | } 90 | } catch (e) { 91 | 92 | } 93 | } 94 | 95 | onClick = () => { 96 | ROUTER.navigate(`/session/${this.uuid}/gpu_temp`) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/util_card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {DataLoader} from "../../../components/loader" 6 | import gpuCache from './cache' 7 | import {getSeriesData} from './utils' 8 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 9 | import {Labels} from "../../../components/charts/labels" 10 | import {ROUTER} from '../../../app' 11 | 12 | export class GPUUtilCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | plotIdx: number[] = [] 20 | private loader: DataLoader 21 | private labelsContainer: HTMLDivElement 22 | 23 | constructor(opt: CardOptions) { 24 | super(opt) 25 | 26 | this.uuid = opt.uuid 27 | this.width = opt.width 28 | this.analysisCache = gpuCache.getAnalysis(this.uuid) 29 | this.loader = new DataLoader(async (force) => { 30 | this.series = getSeriesData((await this.analysisCache.get(force)).series, 'utilization') 31 | }) 32 | } 33 | 34 | getLastUpdated(): number { 35 | return this.analysisCache.lastUpdated 36 | } 37 | 38 | async render($: WeyaElementFunction) { 39 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 40 | $('h3', '.header', 'GPU - Utilization') 41 | this.loader.render($) 42 | this.lineChartContainer = $('div', '') 43 | this.labelsContainer = $('div', '') 44 | }) 45 | 46 | try { 47 | await this.loader.load() 48 | 49 | if (this.series.length > 0) { 50 | this.renderLineChart() 51 | } else { 52 | this.elem.classList.add('hide') 53 | } 54 | } catch (e) { 55 | 56 | } 57 | } 58 | 59 | renderLineChart() { 60 | let res: number[] = [] 61 | for (let i = 0; i < this.series.length; i++) { 62 | res.push(i) 63 | } 64 | this.plotIdx = res 65 | 66 | this.lineChartContainer.innerHTML = '' 67 | $(this.lineChartContainer, $ => { 68 | new TimeSeriesChart({ 69 | series: this.series, 70 | width: this.width, 71 | plotIdx: this.plotIdx, 72 | yExtend: [0, 100], 73 | chartHeightFraction: 4, 74 | isDivergent: true 75 | }).render($) 76 | }) 77 | 78 | this.labelsContainer.innerHTML = '' 79 | $(this.labelsContainer, $ => { 80 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 81 | }) 82 | } 83 | 84 | async refresh() { 85 | try { 86 | await this.loader.load(true) 87 | if (this.series.length > 0) { 88 | this.renderLineChart() 89 | this.elem.classList.remove('hide') 90 | } 91 | } catch (e) { 92 | 93 | } 94 | } 95 | 96 | onClick = () => { 97 | ROUTER.navigate(`/session/${this.uuid}/gpu_util`) 98 | } 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/gpu/utils.ts: -------------------------------------------------------------------------------- 1 | import {SeriesModel} from "../../../models/run" 2 | import {toPointValues} from "../../../components/charts/utils" 3 | 4 | export function getSeriesData(series: SeriesModel[], analysis: string, isMean: boolean = false) { 5 | let res = [] 6 | for (let r of series) { 7 | let s = {...r} 8 | if (s.name.includes(analysis)) { 9 | if (s.name.includes('mean')) { 10 | if (isMean) { 11 | s.name = 'mean' 12 | return toPointValues([s]) 13 | } else { 14 | continue 15 | } 16 | } 17 | s.name = s.name.replace(/\D/g, '') 18 | res.push(s) 19 | } 20 | } 21 | 22 | return toPointValues(res) 23 | } 24 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/memory/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class MemoryAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: SessionStatusCache) { 6 | super(uuid, 'memory', statusCache) 7 | } 8 | } 9 | 10 | class MemoryPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'memory') 13 | } 14 | } 15 | 16 | let memoryCache = new AnalysisCache('session', MemoryAnalysisCache, MemoryPreferenceCache) 17 | 18 | export default memoryCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/memory/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {toPointValues} from "../../../components/charts/utils" 6 | import {DataLoader} from "../../../components/loader" 7 | import memoryCache from './cache' 8 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 9 | import {Labels} from "../../../components/charts/labels" 10 | import {ROUTER} from '../../../app' 11 | 12 | export class MemoryCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | private loader: DataLoader 20 | private labelsContainer: HTMLDivElement 21 | 22 | constructor(opt: CardOptions) { 23 | super(opt) 24 | 25 | this.uuid = opt.uuid 26 | this.width = opt.width 27 | this.analysisCache = memoryCache.getAnalysis(this.uuid) 28 | this.loader = new DataLoader(async (force) => { 29 | this.series = toPointValues((await this.analysisCache.get(force)).series) 30 | }) 31 | } 32 | 33 | getLastUpdated(): number { 34 | return this.analysisCache.lastUpdated 35 | } 36 | 37 | async render($: WeyaElementFunction) { 38 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 39 | $('h3', '.header', 'Memory') 40 | this.loader.render($) 41 | this.lineChartContainer = $('div', '') 42 | this.labelsContainer = $('div', '') 43 | }) 44 | 45 | try { 46 | await this.loader.load() 47 | 48 | if (this.series.length > 0) { 49 | this.renderLineChart() 50 | } else { 51 | this.elem.classList.add('hide') 52 | } 53 | } catch (e) { 54 | 55 | } 56 | } 57 | 58 | renderLineChart() { 59 | this.lineChartContainer.innerHTML = '' 60 | $(this.lineChartContainer, $ => { 61 | new TimeSeriesChart({ 62 | series: this.series, 63 | width: this.width, 64 | plotIdx: [0, 1], 65 | chartHeightFraction: 4, 66 | isDivergent: true 67 | }).render($) 68 | }) 69 | 70 | this.labelsContainer.innerHTML = '' 71 | $(this.labelsContainer, $ => { 72 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 73 | }) 74 | } 75 | 76 | async refresh() { 77 | try { 78 | await this.loader.load(true) 79 | if (this.series.length > 0) { 80 | this.renderLineChart() 81 | this.elem.classList.remove('hide') 82 | } 83 | } catch (e) { 84 | 85 | } 86 | } 87 | 88 | onClick = () => { 89 | ROUTER.navigate(`/session/${this.uuid}/memory`) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/memory/index.ts: -------------------------------------------------------------------------------- 1 | import {MemoryCard} from "./card" 2 | import {MemoryHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | let memoryAnalysis: Analysis = { 6 | card: MemoryCard, 7 | viewHandler: MemoryHandler, 8 | route: 'memory' 9 | } 10 | 11 | export default memoryAnalysis 12 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/network/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | 4 | class NetworkAnalysisCache extends AnalysisDataCache { 5 | constructor(uuid: string, statusCache: SessionStatusCache) { 6 | super(uuid, 'network', statusCache) 7 | } 8 | } 9 | 10 | class NetworkPreferenceCache extends AnalysisPreferenceCache { 11 | constructor(uuid: string) { 12 | super(uuid, 'network') 13 | } 14 | } 15 | 16 | let networkCache = new AnalysisCache('session', NetworkAnalysisCache, NetworkPreferenceCache) 17 | 18 | export default networkCache 19 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/network/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {toPointValues} from "../../../components/charts/utils" 6 | import {DataLoader} from "../../../components/loader" 7 | import networkCache from './cache' 8 | import {TimeSeriesChart} from "../../../components/charts/timeseries/chart" 9 | import {Labels} from "../../../components/charts/labels" 10 | import {ROUTER} from '../../../app' 11 | 12 | export class NetworkCard extends Card { 13 | uuid: string 14 | width: number 15 | series: SeriesModel[] 16 | analysisCache: AnalysisDataCache 17 | lineChartContainer: HTMLDivElement 18 | elem: HTMLDivElement 19 | private loader: DataLoader 20 | private labelsContainer: HTMLDivElement 21 | 22 | constructor(opt: CardOptions) { 23 | super(opt) 24 | 25 | this.uuid = opt.uuid 26 | this.width = opt.width 27 | this.analysisCache = networkCache.getAnalysis(this.uuid) 28 | this.loader = new DataLoader(async (force) => { 29 | this.series = toPointValues((await this.analysisCache.get(force)).series) 30 | }) 31 | } 32 | 33 | getLastUpdated(): number { 34 | return this.analysisCache.lastUpdated 35 | } 36 | 37 | async render($: WeyaElementFunction) { 38 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 39 | $('h3', '.header', 'Network') 40 | this.loader.render($) 41 | this.lineChartContainer = $('div', '') 42 | this.labelsContainer = $('div', '') 43 | }) 44 | 45 | try { 46 | await this.loader.load() 47 | 48 | if (this.series.length > 0) { 49 | this.renderLineChart() 50 | } else { 51 | this.elem.classList.add('hide') 52 | } 53 | } catch (e) { 54 | 55 | } 56 | } 57 | 58 | renderLineChart() { 59 | this.lineChartContainer.innerHTML = '' 60 | $(this.lineChartContainer, $ => { 61 | new TimeSeriesChart({ 62 | series: this.series, 63 | width: this.width, 64 | plotIdx: [0], 65 | chartHeightFraction: 4, 66 | isDivergent: true 67 | }).render($) 68 | }) 69 | 70 | this.labelsContainer.innerHTML = '' 71 | $(this.labelsContainer, $ => { 72 | new Labels({labels: Array.from(this.series, x => x['name']), isDivergent: true}).render($) 73 | }) 74 | } 75 | 76 | async refresh() { 77 | try { 78 | await this.loader.load(true) 79 | if (this.series.length > 0) { 80 | this.renderLineChart() 81 | this.elem.classList.remove('hide') 82 | } 83 | } catch (e) { 84 | 85 | } 86 | } 87 | 88 | onClick = () => { 89 | ROUTER.navigate(`/session/${this.uuid}/network`) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/network/index.ts: -------------------------------------------------------------------------------- 1 | import {NetworkCard} from "./card" 2 | import {NetworkHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | let networkAnalysis: Analysis = { 6 | card: NetworkCard, 7 | viewHandler: NetworkHandler, 8 | route: 'network' 9 | } 10 | 11 | export default networkAnalysis 12 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/process/cache.ts: -------------------------------------------------------------------------------- 1 | import {AnalysisDataCache, AnalysisPreferenceCache, SessionStatusCache} from "../../../cache/cache" 2 | import {AnalysisCache} from "../../helpers" 3 | import {DetailsCache, DetailsDataCache} from "./cache_helper" 4 | 5 | class ProcessAnalysisCache extends AnalysisDataCache { 6 | constructor(uuid: string, statusCache: SessionStatusCache) { 7 | super(uuid, 'process', statusCache) 8 | } 9 | } 10 | 11 | class ProcessPreferenceCache extends AnalysisPreferenceCache { 12 | constructor(uuid: string) { 13 | super(uuid, 'process') 14 | } 15 | } 16 | 17 | let processCache = new AnalysisCache('session', ProcessAnalysisCache, ProcessPreferenceCache) 18 | 19 | class ProcessDetailsCache extends DetailsDataCache { 20 | constructor(uuid: string, processId: string, statusCache: SessionStatusCache) { 21 | super(uuid, processId, statusCache) 22 | } 23 | } 24 | 25 | let processDetailsCache = new DetailsCache(ProcessDetailsCache) 26 | 27 | export { 28 | processCache, 29 | processDetailsCache 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/process/cache_helper.ts: -------------------------------------------------------------------------------- 1 | import NETWORK from "../../../network" 2 | import CACHE, {CacheObject, isReloadTimeout, SessionStatusCache, StatusCache} from "../../../cache/cache" 3 | import {ProcessDetailsModel} from "./types" 4 | 5 | export class DetailsDataCache extends CacheObject { 6 | private readonly uuid: string 7 | private readonly processId: string 8 | private statusCache: StatusCache 9 | 10 | constructor(uuid: string, processId: string, statusCache: StatusCache) { 11 | super() 12 | this.uuid = uuid 13 | this.processId = processId 14 | this.statusCache = statusCache 15 | } 16 | 17 | async load(): Promise { 18 | return this.broadcastPromise.create(async () => { 19 | return await NETWORK.getCustomAnalysis(`process/${this.uuid}/details/${this.processId}`) 20 | }) 21 | } 22 | 23 | async get(isRefresh = false): Promise { 24 | let status = await this.statusCache.get() 25 | 26 | if (this.data == null || (status.isRunning && isReloadTimeout(this.lastUpdated)) || isRefresh) { 27 | this.data = await this.load() 28 | this.lastUpdated = (new Date()).getTime() 29 | 30 | if ((status.isRunning && isReloadTimeout(this.lastUpdated)) || isRefresh) { 31 | await this.statusCache.get(true) 32 | } 33 | } 34 | 35 | return this.data 36 | } 37 | } 38 | 39 | export class DetailsCache { 40 | private readonly series: new (uuid: string, processId: string, status: SessionStatusCache) => TA 41 | private readonly processCache: { [uuid: string]: { [processId: string]: DetailsDataCache } } 42 | 43 | constructor(series: new (uuid: string, processId: string, status: SessionStatusCache) => TA) { 44 | this.processCache = {} 45 | this.series = series 46 | } 47 | 48 | getAnalysis(uuid: string, processId: string) { 49 | if (this.processCache[uuid] == null) { 50 | let seriesCaches = {} 51 | seriesCaches[processId] = new this.series(uuid, processId, CACHE.getSessionStatus(uuid)) 52 | this.processCache[uuid] = seriesCaches 53 | } else if (this.processCache[uuid][processId] == null) { 54 | this.processCache[uuid][processId] = new this.series(uuid, processId, CACHE.getSessionStatus(uuid)) 55 | } 56 | 57 | return this.processCache[uuid][processId] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/process/card.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $, WeyaElementFunction,} from '../../../../../lib/weya/weya' 2 | import {SeriesModel} from "../../../models/run" 3 | import {Card, CardOptions} from "../../types" 4 | import {AnalysisDataCache} from "../../../cache/cache" 5 | import {toPointValues} from "../../../components/charts/utils" 6 | import {DataLoader} from "../../../components/loader" 7 | import {processCache} from './cache' 8 | import {ROUTER} from '../../../app' 9 | import {SparkTimeLines} from "../../../components/charts/spark_time_lines/chart" 10 | 11 | export class ProcessCard extends Card { 12 | uuid: string 13 | width: number 14 | series: SeriesModel[] 15 | analysisCache: AnalysisDataCache 16 | sparkLinesContainer: HTMLDivElement 17 | sparkTimeLines: SparkTimeLines 18 | elem: HTMLDivElement 19 | private loader: DataLoader 20 | 21 | constructor(opt: CardOptions) { 22 | super(opt) 23 | 24 | this.uuid = opt.uuid 25 | this.width = opt.width 26 | this.analysisCache = processCache.getAnalysis(this.uuid) 27 | this.loader = new DataLoader(async (force) => { 28 | this.series = toPointValues((await this.analysisCache.get(force)).summary) 29 | }) 30 | } 31 | 32 | getLastUpdated(): number { 33 | return this.analysisCache.lastUpdated 34 | } 35 | 36 | async render($: WeyaElementFunction) { 37 | this.elem = $('div', '.labml-card.labml-card-action', {on: {click: this.onClick}}, $ => { 38 | $('h3', '.header', 'Processes') 39 | this.loader.render($) 40 | this.sparkLinesContainer = $('div', '') 41 | }) 42 | 43 | try { 44 | await this.loader.load() 45 | 46 | if (this.series.length > 0) { 47 | this.renderSparkLines() 48 | } else { 49 | this.elem.classList.add('hide') 50 | } 51 | } catch (e) { 52 | 53 | } 54 | } 55 | 56 | renderSparkLines() { 57 | this.sparkLinesContainer.innerHTML = '' 58 | $(this.sparkLinesContainer, $ => { 59 | this.sparkTimeLines = new SparkTimeLines({ 60 | series: this.series, 61 | plotIdx: [1, 2, 3, 4, 5], 62 | width: this.width, 63 | isColorless: true 64 | }) 65 | this.sparkTimeLines.render($) 66 | }) 67 | } 68 | 69 | async refresh() { 70 | try { 71 | await this.loader.load(true) 72 | if (this.series.length > 0) { 73 | this.renderSparkLines() 74 | this.elem.classList.remove('hide') 75 | } 76 | } catch (e) { 77 | 78 | } 79 | } 80 | 81 | onClick = () => { 82 | ROUTER.navigate(`/session/${this.uuid}/process`) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/process/index.ts: -------------------------------------------------------------------------------- 1 | import {ProcessCard} from "./card" 2 | import {ProcessHandler} from "./view" 3 | import {Analysis} from "../../types" 4 | 5 | let processAnalysis: Analysis = { 6 | card: ProcessCard, 7 | viewHandler: ProcessHandler, 8 | route: 'process' 9 | } 10 | 11 | export default processAnalysis 12 | -------------------------------------------------------------------------------- /ui/src/analyses/sessions/process/types.ts: -------------------------------------------------------------------------------- 1 | import {SeriesModel} from "../../../models/run" 2 | 3 | export interface ProcessModel { 4 | process_id: string 5 | name: string 6 | cpu: SeriesModel 7 | rss: SeriesModel 8 | dead: number 9 | pid: number 10 | } 11 | 12 | export interface ProcessDetailsModel extends ProcessModel { 13 | create_time: number, 14 | cmdline: string, 15 | exe: string 16 | ppid: number 17 | series: SeriesModel[] 18 | } -------------------------------------------------------------------------------- /ui/src/analyses/sessions/session_header/index.ts: -------------------------------------------------------------------------------- 1 | import {SessionHeaderCard} from "./card" 2 | import {SessionHeaderHandler} from "./view" 3 | 4 | let sessionHeaderAnalysis = { 5 | card: SessionHeaderCard, 6 | viewHandler: SessionHeaderHandler, 7 | route: 'header' 8 | } 9 | 10 | export default sessionHeaderAnalysis 11 | -------------------------------------------------------------------------------- /ui/src/analyses/types.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from "../../../lib/weya/weya" 2 | 3 | export interface CardOptions { 4 | uuid: string 5 | width: number 6 | } 7 | 8 | export abstract class Card { 9 | protected constructor(opt: CardOptions) { 10 | } 11 | 12 | abstract render($: WeyaElementFunction) 13 | 14 | abstract refresh() 15 | 16 | abstract getLastUpdated(): number 17 | } 18 | 19 | export abstract class ViewHandler { 20 | 21 | } 22 | 23 | export interface Analysis { 24 | card: new (opt: CardOptions) => Card 25 | viewHandler: new () => ViewHandler 26 | route: string 27 | } 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ui/src/app.ts: -------------------------------------------------------------------------------- 1 | import {ScreenContainer} from './screen' 2 | import {Router} from '../../lib/weya/router' 3 | 4 | 5 | export let ROUTER = new Router({ 6 | emulateState: false, 7 | hashChange: false, 8 | pushState: true, 9 | root: '/', 10 | onerror: e => { 11 | console.error('Error', e) 12 | } 13 | }) 14 | 15 | export let SCREEN = new ScreenContainer() -------------------------------------------------------------------------------- /ui/src/components/badge.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from "../../../lib/weya/weya" 2 | 3 | interface BadgeViewOptions { 4 | text: string 5 | } 6 | 7 | export class BadgeView { 8 | text: string 9 | 10 | constructor(opt: BadgeViewOptions) { 11 | this.text = opt.text 12 | } 13 | 14 | render($: WeyaElementFunction) { 15 | $('div.label.label-info', this.text) 16 | } 17 | } -------------------------------------------------------------------------------- /ui/src/components/charts/axis.ts: -------------------------------------------------------------------------------- 1 | import d3 from "../../d3" 2 | import {WeyaElementFunction} from "../../../../lib/weya/weya" 3 | 4 | interface AxisOptions { 5 | chartId: string 6 | scale: d3.ScaleLinear 7 | specifier?: string 8 | numTicks?: number 9 | } 10 | 11 | export class RightAxis { 12 | scale: d3.ScaleLinear 13 | specifier?: string 14 | numTicks?: number 15 | id: string 16 | axis 17 | 18 | constructor(opt: AxisOptions) { 19 | this.specifier = opt.specifier !== undefined ? opt.specifier : "" 20 | this.numTicks = opt.numTicks !== undefined ? opt.numTicks : 5 21 | this.id = `${opt.chartId}_axis_right` 22 | this.axis = d3.axisRight(opt.scale as d3.AxisScale).ticks(this.numTicks, this.specifier) 23 | } 24 | 25 | render($: WeyaElementFunction) { 26 | $('g', {id: this.id}) 27 | 28 | let layer = d3.select(`#${this.id}`) 29 | layer.selectAll('g').remove() 30 | layer.append('g').call(this.axis) 31 | } 32 | } 33 | 34 | export class BottomAxis { 35 | scale: d3.ScaleLinear 36 | specifier?: string 37 | id: string 38 | axis 39 | 40 | constructor(opt: AxisOptions) { 41 | this.specifier = opt.specifier !== undefined ? opt.specifier : ".2s" 42 | this.id = `${opt.chartId}_axis_bottom` 43 | this.axis = d3.axisBottom(opt.scale as d3.AxisScale).ticks(5, this.specifier) 44 | } 45 | 46 | render($: WeyaElementFunction) { 47 | $('g', {id: this.id}) 48 | 49 | let layer = d3.select(`#${this.id}`) 50 | layer.selectAll('g').remove() 51 | layer.append('g').call(this.axis) 52 | } 53 | } 54 | 55 | interface TimeAxisOptions { 56 | chartId: string 57 | scale: d3.ScaleTime 58 | numTicks?: number 59 | } 60 | 61 | export class BottomTimeAxis { 62 | scale: d3.ScaleTime 63 | numTicks?: number 64 | id: string 65 | axis 66 | 67 | constructor(opt: TimeAxisOptions) { 68 | this.numTicks = opt.numTicks | 5 69 | this.id = `${opt.chartId}_axis_bottom` 70 | this.axis = d3.axisBottom(opt.scale as d3.AxisScale).ticks(this.numTicks, d3.timeFormat("%b-%d:%H:%M")) 71 | } 72 | 73 | render($: WeyaElementFunction) { 74 | $('g', {id: this.id}) 75 | 76 | let layer = d3.select(`#${this.id}`) 77 | layer.selectAll('g').remove() 78 | layer.append('g').call(this.axis) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ui/src/components/charts/chart_colors.ts: -------------------------------------------------------------------------------- 1 | import d3 from "../../d3" 2 | 3 | const LIGHT_SINGLE_HUE = d3.piecewise(d3.interpolateHsl, ["#004c6d", "#c1e7ff"]) 4 | const DARK_SINGLE_HUE = d3.piecewise(d3.interpolateHsl, ["#c1e7ff", "#004c6d"]) 5 | const DIVERGENT = d3.piecewise(d3.interpolateHcl, ["#ffa600", "#bc5090", "#003f5d"]) 6 | const DIVERGENT_SECOND = d3.piecewise(d3.interpolateHcl, ["#dd8400", "#9a3070", "#001d3b"]) 7 | 8 | interface ChartColorsOptions { 9 | nColors: number 10 | secondNColors?: number 11 | isDivergent?: boolean 12 | } 13 | 14 | export default class ChartColors { 15 | nColors: number 16 | secondNColors: number 17 | isDivergent: boolean 18 | colorScale: d3.ScaleLinear 19 | secondColorScale: d3.ScaleLinear 20 | colors: string[] = [] 21 | secondColors: string[] = [] 22 | 23 | constructor(opt: ChartColorsOptions) { 24 | this.nColors = opt.nColors 25 | this.secondNColors = opt.secondNColors ?? 0 26 | this.isDivergent = opt.isDivergent 27 | 28 | this.colorScale = DIVERGENT 29 | this.secondColorScale = DIVERGENT_SECOND 30 | if (!this.isDivergent) { 31 | if (document.body.classList.contains('light')) { 32 | this.colorScale = LIGHT_SINGLE_HUE 33 | } else { 34 | this.colorScale = DARK_SINGLE_HUE 35 | } 36 | } 37 | 38 | for (let n = 0; n < this.nColors; ++n) { 39 | this.colors.push(this.colorScale(n / (Math.max(1, this.nColors - 1)))) 40 | } 41 | for (let n = 0; n < this.secondNColors; ++n) { 42 | this.secondColors.push(this.secondColorScale(n / (Math.max(1, this.secondNColors - 1)))) 43 | } 44 | } 45 | 46 | getColor(i: number) { 47 | return this.colors[i] 48 | } 49 | getSecondColor(i: number) { 50 | return this.secondColors[i] 51 | } 52 | 53 | getColors() { 54 | return this.colors 55 | } 56 | getSecondColors() { 57 | return this.secondColors 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ui/src/components/charts/chart_gradients.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from "../../../../lib/weya/weya" 2 | import ChartColors from "./chart_colors" 3 | 4 | interface LineGradientsOptions { 5 | chartColors: ChartColors 6 | chartId: string 7 | } 8 | 9 | export class LineGradients { 10 | chartColors: ChartColors 11 | chartId: string 12 | 13 | constructor(opt: LineGradientsOptions) { 14 | this.chartColors = opt.chartColors 15 | this.chartId = opt.chartId 16 | } 17 | 18 | render($: WeyaElementFunction) { 19 | $('defs', $ => { 20 | this.chartColors.getColors().map((c, i) => { 21 | $('linearGradient', { 22 | id: `gradient-${i}-${this.chartId}`, 23 | x1: '0%', 24 | x2: '0%', 25 | y1: '0%', 26 | y2: '100%' 27 | }, $ => { 28 | $('stop', {offset: '0%', 'stop-color': c, 'stop-opacity': 1.0}) 29 | $('stop', {offset: '100%', 'stop-color': c, 'stop-opacity': 0.0}) 30 | }) 31 | }) 32 | }) 33 | } 34 | } 35 | 36 | export class DefaultLineGradient { 37 | constructor() { 38 | } 39 | 40 | render($: WeyaElementFunction) { 41 | $('defs', $ => { 42 | $('linearGradient', {id: `gradient-grey`, x1: '0%', x2: '0%', y1: '0%', y2: '100%'}, $ => { 43 | $('stop', {offset: '0%', 'stop-color': '#7f8c8d', 'stop-opacity': 1.0}) 44 | $('stop', {offset: '100%', 'stop-color': '#7f8c8d', 'stop-opacity': 0.0}) 45 | }) 46 | }) 47 | } 48 | } 49 | 50 | export class DropShadow { 51 | constructor() { 52 | } 53 | 54 | render($: WeyaElementFunction) { 55 | $('defs', $ => { 56 | $('filter', {id: 'dropshadow'}, $ => { 57 | $('feGaussianBlur', {in: 'SourceAlpha', stdDeviation: "3"}) 58 | $('feOffset', {dx: '0', dy: "0", result: "offsetblur"}) 59 | $('feComponentTransfer', $ => { 60 | $('feFuncA', {slope: '0.2', type: "linear"}) 61 | }) 62 | $('feMerge', $ => { 63 | $('feMergeNode') 64 | $('feMergeNode', {in: "SourceGraphic"}) 65 | }) 66 | }) 67 | }) 68 | } 69 | } -------------------------------------------------------------------------------- /ui/src/components/charts/constants.ts: -------------------------------------------------------------------------------- 1 | export const OUTLIER_MARGIN = 0.04 2 | export const BASE_COLOR = '#34495e' 3 | 4 | 5 | export function getColor(index: number) { 6 | return CHART_COLORS[index % 10] 7 | } 8 | 9 | export function getBaseColor() { 10 | if (document.body.classList.contains('light')) { 11 | return '#34495e' 12 | } else { 13 | return '#ccc' 14 | } 15 | } 16 | 17 | export const CHART_COLORS = [ 18 | '#4E79A7', 19 | '#F28E2C', 20 | '#76B7B2', 21 | '#E15759', 22 | '#59A14F', 23 | '#EDC949', 24 | '#AF7AA1', 25 | '#FF9DA7', 26 | '#9C755F', 27 | '#BAB0AB' 28 | ] 29 | 30 | -------------------------------------------------------------------------------- /ui/src/components/charts/labels.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from "../../../../lib/weya/weya" 2 | import ChartColors from "./chart_colors" 3 | 4 | interface LabelsOptions { 5 | labels: string[] 6 | isDivergent?: boolean 7 | chartColors?: ChartColors 8 | } 9 | 10 | export class Labels { 11 | labels: string[] 12 | chartColors: ChartColors 13 | 14 | constructor(opt: LabelsOptions) { 15 | this.labels = opt.labels 16 | this.chartColors = opt.chartColors ? opt.chartColors : new ChartColors({ 17 | nColors: this.labels.length, 18 | isDivergent: opt.isDivergent 19 | }) 20 | } 21 | 22 | render($: WeyaElementFunction) { 23 | $('div.text-center.labels.text-secondary', 24 | $ => { 25 | this.labels.map((label, i) => { 26 | $('span', $ => { 27 | $('div.box', {style: {background: this.chartColors.getColor(i)}}) 28 | }) 29 | $('span', label) 30 | }) 31 | }) 32 | } 33 | } -------------------------------------------------------------------------------- /ui/src/components/charts/simple_lines/plot.ts: -------------------------------------------------------------------------------- 1 | import d3 from "../../../d3" 2 | import {WeyaElementFunction} from '../../../../../lib/weya/weya' 3 | import {FillOptions, PlotOptions} from '../types' 4 | 5 | interface SimpleLinePlotOptions extends PlotOptions { 6 | xScale: d3.ScaleLinear 7 | series: number[] 8 | } 9 | 10 | export class SimpleLinePlot { 11 | series: number[] 12 | xScale: d3.ScaleLinear 13 | yScale: d3.ScaleLinear 14 | color: string 15 | smoothedLine: d3.Line 16 | 17 | constructor(opt: SimpleLinePlotOptions) { 18 | this.series = opt.series 19 | this.xScale = opt.xScale 20 | this.yScale = opt.yScale 21 | this.color = opt.color 22 | 23 | this.smoothedLine = d3.line() 24 | .curve(d3.curveMonotoneX) 25 | .x((d, i) => { 26 | return this.xScale(i) 27 | }) 28 | .y((d) => { 29 | return this.yScale(d) 30 | }) 31 | } 32 | 33 | 34 | render($: WeyaElementFunction) { 35 | $('g', $ => { 36 | $('path.smoothed-line.dropshadow', 37 | { 38 | fill: 'none', 39 | stroke: this.color, 40 | d: this.smoothedLine(this.series) as string 41 | }) 42 | }) 43 | } 44 | } 45 | 46 | interface SimpleLineFillOptions extends FillOptions { 47 | xScale: d3.ScaleLinear 48 | series: number[] 49 | chartId: string 50 | } 51 | 52 | export class SimpleLineFill { 53 | series: number[] 54 | chartId: string 55 | xScale: d3.ScaleLinear 56 | yScale: d3.ScaleLinear 57 | color: string 58 | colorIdx: number 59 | smoothedLine 60 | dFill: string 61 | 62 | constructor(opt: SimpleLineFillOptions) { 63 | this.series = opt.series 64 | this.xScale = opt.xScale 65 | this.yScale = opt.yScale 66 | this.color = opt.color 67 | this.colorIdx = opt.colorIdx 68 | this.chartId = opt.chartId 69 | 70 | this.smoothedLine = d3.line() 71 | .curve(d3.curveMonotoneX) 72 | .x((d, i) => { 73 | return this.xScale(i) 74 | }) 75 | .y((d) => { 76 | return this.yScale(d) 77 | }) 78 | 79 | let d = this.smoothedLine(this.series) as string 80 | this.dFill = `M${this.xScale(0)},0L` + d.substr(1) + `L${this.xScale(this.series.length - 1)},0` 81 | } 82 | 83 | render($: WeyaElementFunction) { 84 | $('g', $ => { 85 | $('path.line-fill', 86 | { 87 | fill: this.color, 88 | stroke: 'none', 89 | style: {fill: `url(#gradient-${this.colorIdx}-${this.chartId}`}, 90 | d: this.dFill 91 | }) 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ui/src/components/charts/types.ts: -------------------------------------------------------------------------------- 1 | import d3 from "../../d3" 2 | import {SeriesModel} from "../../models/run" 3 | 4 | export interface ChartOptions { 5 | series: SeriesModel[] 6 | width: number 7 | } 8 | 9 | export interface PlotOptions { 10 | yScale: d3.ScaleLinear 11 | color: string 12 | } 13 | 14 | export interface FillOptions extends PlotOptions { 15 | colorIdx: number 16 | } 17 | -------------------------------------------------------------------------------- /ui/src/components/codes/pytorch_lightning.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../../lib/weya/weya' 2 | import {Tab} from "./tabs" 3 | 4 | 5 | export default class PyTorchLightningCode { 6 | constructor() { 7 | } 8 | 9 | render($) { 10 | $('div.code-sample.bg-dark.px-1.py-2.my-3', $ => { 11 | $('pre.text-white', $ => { 12 | $('div', $ => { 13 | $('span.key-word', 'from') 14 | $('span', ' labml') 15 | $('span.key-word', ' import') 16 | $('span', ' experiment ') 17 | $('span.key-word', 'as ') 18 | $('span', 'exp') 19 | }) 20 | $('div', $ => { 21 | $('span.key-word', 'from') 22 | $('span', ' labml.utils.lightning ') 23 | $('span.key-word', 'import') 24 | $('span', ' LabMLLightningLogger ') 25 | }) 26 | $('br') 27 | 28 | $('div', $ => { 29 | $('span', 'trainer = pl.Trainer(') 30 | $('span.param', 'gpus') 31 | $('span', '=1,') 32 | }) 33 | $('div', $ => { 34 | new Tab().render($) 35 | new Tab().render($) 36 | new Tab().render($) 37 | new Tab().render($) 38 | $('span.param', 'max_epochs') 39 | $('span', '=5,') 40 | }) 41 | $('div', $ => { 42 | new Tab().render($) 43 | new Tab().render($) 44 | new Tab().render($) 45 | new Tab().render($) 46 | $('span.param', 'progress_bar_refresh_rate') 47 | $('span', '=20,') 48 | }) 49 | $('div.labml-api', $ => { 50 | new Tab().render($) 51 | new Tab().render($) 52 | new Tab().render($) 53 | new Tab().render($) 54 | $('span.param', 'logger') 55 | $('span', '=LabMLLighteningLogger())') 56 | }) 57 | $('br') 58 | 59 | $('div', $ => { 60 | $('div.labml-api', $ => { 61 | $('span.key-word', 'with') 62 | $('span', ' exp.record(') 63 | $('span.param', 'name=') 64 | $('span.string', "'sample'") 65 | $('span', ', ') 66 | $('span.param', 'exp_conf') 67 | $('span', '=conf, ') 68 | $('span.param', 'disable_screen') 69 | $('span', '=True):') 70 | }) 71 | }) 72 | $('div.labml-api', $ => { 73 | new Tab().render($) 74 | new Tab().render($) 75 | $('span', 'trainer.fit(model, data_loader)') 76 | }) 77 | }) 78 | }) 79 | } 80 | } -------------------------------------------------------------------------------- /ui/src/components/codes/tabs.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction,} from '../../../../lib/weya/weya' 2 | 3 | export class Tab { 4 | constructor() { 5 | } 6 | 7 | render($: WeyaElementFunction) { 8 | $('span.tab-space') 9 | } 10 | } 11 | 12 | 13 | export class HalfTab { 14 | constructor() { 15 | } 16 | 17 | render($: WeyaElementFunction) { 18 | $('span.tab-space') 19 | } 20 | } 21 | 22 | export class QuarterTab { 23 | constructor() { 24 | } 25 | 26 | render($: WeyaElementFunction) { 27 | $('span.tab-space') 28 | } 29 | } -------------------------------------------------------------------------------- /ui/src/components/error_message.ts: -------------------------------------------------------------------------------- 1 | import {Weya} from "../../../lib/weya/weya" 2 | 3 | export class ErrorMessage { 4 | elem: HTMLDivElement 5 | 6 | constructor() { 7 | this.elem = null 8 | } 9 | 10 | render(parent: HTMLDivElement) { 11 | this.remove() 12 | Weya(parent, $ => { 13 | this.elem = $('div', '.error.text-center.warning', $ => { 14 | $('span', '.fas.fa-exclamation-triangle', '') 15 | $('h4', '.text-uppercase', 'Network error') 16 | }) 17 | }) 18 | } 19 | 20 | remove() { 21 | if (this.elem == null) { 22 | return 23 | } 24 | this.elem.remove() 25 | this.elem = null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/src/components/input/editable_field.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../../lib/weya/weya' 2 | 3 | interface EditableFieldOptions { 4 | name: string 5 | value: any 6 | placeholder?: string 7 | isEditable?: boolean 8 | numEditRows?: number 9 | } 10 | 11 | export default class EditableField { 12 | name: string 13 | value: any 14 | placeholder: string 15 | isEditable: boolean 16 | numEditRows: number 17 | inputElem: HTMLInputElement | HTMLTextAreaElement 18 | valueElem: HTMLSpanElement 19 | 20 | constructor(opt: EditableFieldOptions) { 21 | this.name = opt.name 22 | this.value = opt.value 23 | this.placeholder = opt.placeholder 24 | this.isEditable = opt.isEditable 25 | this.numEditRows = opt.numEditRows 26 | } 27 | 28 | getInput() { 29 | return this.inputElem.value 30 | } 31 | 32 | updateValue(value: string) { 33 | this.valueElem.textContent = value 34 | } 35 | 36 | render($: WeyaElementFunction) { 37 | $(`li`, $ => { 38 | $('span.item-key', this.name) 39 | if (this.isEditable) { 40 | $('div.input-container.mt-2', $ => { 41 | $('div.input-content', $ => { 42 | if (this.numEditRows) { 43 | this.inputElem = $('textarea', { 44 | rows: this.numEditRows, 45 | placeholder: this.placeholder, 46 | value: this.value 47 | } 48 | ) 49 | } else { 50 | this.inputElem = $('input', { 51 | placeholder: this.placeholder, 52 | value: this.value 53 | } 54 | ) 55 | } 56 | }) 57 | }) 58 | } else { 59 | this.valueElem = $('span', '.item-value') 60 | this.updateValue(this.value) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ui/src/components/input/editable_select_field.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../../lib/weya/weya' 2 | 3 | interface EditableSelectFieldOptions { 4 | name: string 5 | value: any 6 | isEditable?: boolean 7 | onEdit: () => void 8 | onClick?: () => void 9 | } 10 | 11 | export default class EditableSelectField { 12 | name: string 13 | value: any 14 | isEditable: boolean 15 | private readonly onEdit: () => void 16 | private readonly _onClick?: () => void 17 | 18 | constructor(opt: EditableSelectFieldOptions) { 19 | this.name = opt.name 20 | this.value = opt.value 21 | this.isEditable = opt.isEditable 22 | this.onEdit = opt.onEdit 23 | this._onClick = opt.onClick 24 | } 25 | 26 | onClick() { 27 | if (!this.isEditable) { 28 | this._onClick() 29 | } 30 | } 31 | 32 | render($: WeyaElementFunction) { 33 | $(`li`, {on: {click: this.onClick.bind(this)}}, $ => { 34 | $('span.item-key', this.name) 35 | if (this.isEditable) { 36 | $('div.input-container.mt-2', $ => { 37 | $('div', '.input-content', {on: {click: this.onEdit}}, $ => { 38 | $('input', { 39 | value: this.value, 40 | readonly: 'readonly' 41 | } 42 | ) 43 | }) 44 | }) 45 | } else { 46 | $('span.item-value', this.value) 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ui/src/components/insights_list.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from "../../../lib/weya/weya" 2 | import {InsightModel} from "../models/run" 3 | 4 | class Insight { 5 | className: string 6 | message: string 7 | 8 | constructor(opt: InsightModel) { 9 | this.className = '.insight-container' 10 | 11 | if (opt.type === 'danger') { 12 | this.className += '.danger' 13 | } else if (opt.type === 'warning') { 14 | this.className += '.warning' 15 | } else { 16 | this.className += '.success' 17 | } 18 | 19 | this.message = opt.message 20 | } 21 | 22 | render($: WeyaElementFunction) { 23 | $('div', this.className, $ => { 24 | $('span', '.fas.fa-lightbulb.icon', '') 25 | $('span', '.info', this.message) 26 | }) 27 | } 28 | } 29 | 30 | interface InsightsListOptions { 31 | insightList: InsightModel[] 32 | } 33 | 34 | 35 | export default class InsightsList { 36 | insightList: InsightModel[] 37 | 38 | constructor(opt: InsightsListOptions) { 39 | this.insightList = opt.insightList 40 | } 41 | 42 | render($) { 43 | this.insightList.map((insight, idx) => ( 44 | new Insight({message: insight.message, type: insight.type, time: insight.time}).render($) 45 | )) 46 | } 47 | } -------------------------------------------------------------------------------- /ui/src/components/loader.ts: -------------------------------------------------------------------------------- 1 | import {Weya, WeyaElement, WeyaElementFunction} from '../../../lib/weya/weya' 2 | import {ErrorMessage} from './error_message' 3 | import {waitForFrame} from '../utils/render' 4 | 5 | export class Loader { 6 | elem: WeyaElement 7 | isScreenLoader: boolean 8 | 9 | constructor(isScreenLoader?: boolean) { 10 | this.isScreenLoader = isScreenLoader 11 | this.elem = null 12 | } 13 | 14 | render($: WeyaElementFunction) { 15 | if (this.isScreenLoader) { 16 | this.elem = $('div', '.loader-container', $ => { 17 | $('div', '.text-center.mt-5', $ => { 18 | $('img', '.logo-style', {src: '/images/lab_logo.png'}) 19 | }) 20 | $('div', '.text-center', $ => { 21 | $('div.loader', '') 22 | }) 23 | }) 24 | } else { 25 | this.elem = $('div', '.text-center', $ => { 26 | $('div', '.loader', '') 27 | }) 28 | } 29 | 30 | return this.elem 31 | } 32 | 33 | remove() { 34 | if (this.elem == null) { 35 | return 36 | } 37 | this.elem.remove() 38 | this.elem = null 39 | } 40 | } 41 | 42 | export class DataLoader { 43 | private _load: (force: boolean) => Promise 44 | private loaded: boolean 45 | private loader: Loader 46 | private elem: HTMLDivElement 47 | private errorMessage: ErrorMessage 48 | 49 | constructor(load: (force: boolean) => Promise) { 50 | this._load = load 51 | this.loaded = false 52 | this.loader = new Loader() 53 | this.errorMessage = new ErrorMessage() 54 | } 55 | 56 | render($: WeyaElementFunction) { 57 | this.elem = $('div', '.data-loader') 58 | } 59 | 60 | async load(force: boolean = false) { 61 | this.errorMessage.remove() 62 | if (!this.loaded) { 63 | this.elem.appendChild(this.loader.render(Weya)) 64 | await waitForFrame() 65 | } 66 | 67 | try { 68 | await this._load(force) 69 | this.loaded = true 70 | } catch (e) { 71 | this.loaded = false 72 | this.errorMessage.render(this.elem) 73 | throw e 74 | } finally { 75 | this.loader.remove() 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ui/src/components/runs_list_item.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../lib/weya/weya' 2 | import {RunListItemModel} from '../models/run_list' 3 | import {StatusView} from './status' 4 | import {formatTime} from '../utils/time' 5 | 6 | export interface RunsListItemOptions { 7 | item: RunListItemModel 8 | onClick: (elem: RunsListItemView) => void 9 | } 10 | 11 | export class RunsListItemView { 12 | item: RunListItemModel 13 | elem: HTMLAnchorElement 14 | onClick: (evt: Event) => void 15 | 16 | constructor(opt: RunsListItemOptions) { 17 | this.item = opt.item 18 | this.onClick = (e: Event) => { 19 | e.preventDefault() 20 | opt.onClick(this) 21 | } 22 | } 23 | 24 | render($: WeyaElementFunction) { 25 | this.elem = $('a', '.list-item.list-group-item.list-group-item-action', 26 | {href: `/run/${this.item.run_uuid}`, on: {click: this.onClick}}, 27 | $ => { 28 | $('div', $ => { 29 | new StatusView({status: this.item.run_status}).render($) 30 | $('p', `Started on ${formatTime(this.item.start_time)}`) 31 | $('h5', this.item.name) 32 | $('h6', this.item.comment) 33 | }) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ui/src/components/search.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../lib/weya/weya' 2 | import Timeout = NodeJS.Timeout 3 | 4 | export interface SearchOptions { 5 | onSearch: (query: string) => void 6 | } 7 | 8 | export class SearchView { 9 | onSearch: () => void 10 | textbox: HTMLInputElement 11 | inputTimeout: Timeout 12 | 13 | constructor(opt: SearchOptions) { 14 | this.onSearch = () => { 15 | clearTimeout(this.inputTimeout) 16 | this.inputTimeout = setTimeout(() => { 17 | opt.onSearch(this.textbox.value) 18 | }, 250) 19 | } 20 | } 21 | 22 | render($: WeyaElementFunction) { 23 | $('div', '.search-container.mt-3.mb-3.px-2', $ => { 24 | $('div', '.search-content', $ => { 25 | $('span', '.icon', $=>{ 26 | $('span', '.fas.fa-search', '') 27 | }) 28 | this.textbox = $('input', '.search-input', { 29 | type: 'search', 30 | placeholder: 'Search', 31 | 'aria-label': 'Search', 32 | on: { 33 | input: this.onSearch 34 | } 35 | }) 36 | }) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ui/src/components/sessions_list_item.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../lib/weya/weya' 2 | import {StatusView} from './status' 3 | import {formatTime} from '../utils/time' 4 | import {SessionsListItemModel} from '../models/session_list'; 5 | 6 | export interface SessionListItemOptions { 7 | item: SessionsListItemModel 8 | onClick: (elem: SessionsListItemView) => void 9 | } 10 | 11 | export class SessionsListItemView { 12 | item: SessionsListItemModel 13 | elem: HTMLAnchorElement 14 | onClick: (evt: Event) => void 15 | 16 | constructor(opt: SessionListItemOptions) { 17 | this.item = opt.item 18 | this.onClick = (e: Event) => { 19 | e.preventDefault() 20 | opt.onClick(this) 21 | } 22 | } 23 | 24 | render($: WeyaElementFunction) { 25 | this.elem = $('a', '.list-item.list-group-item.list-group-item-action', 26 | {href: `/session/${this.item.session_uuid}`, on: {click: this.onClick}}, 27 | $ => { 28 | $('div', $ => { 29 | new StatusView({status: this.item.run_status, type: 'session'}).render($) 30 | $('p', `Started ${formatTime(this.item.start_time)}`) 31 | $('h5', this.item.name) 32 | $('h6', this.item.comment) 33 | }) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ui/src/components/status.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../lib/weya/weya' 2 | import {RunStatusModel} from "../models/status" 3 | import {ContentType} from '../types'; 4 | 5 | export interface StatusOptions { 6 | status: RunStatusModel 7 | type?: ContentType 8 | } 9 | 10 | export class StatusView { 11 | status: RunStatusModel 12 | type: ContentType 13 | 14 | constructor(opt: StatusOptions) { 15 | this.status = opt.status 16 | this.type = opt.type || 'run' 17 | } 18 | 19 | render($: WeyaElementFunction) { 20 | if (this.status.status === 'in progress') { 21 | if (this.type === 'session') { 22 | $('div.status.text-info.text-uppercase', 'monitoring') 23 | return 24 | } 25 | $('div.status.text-info.text-uppercase', 'experiment is running') 26 | } else if (this.status.status === 'no response') { 27 | $('div.status.text-warning.text-uppercase', 'no response') 28 | } else if (this.status.status === 'completed') { 29 | $('div.status.text-success.text-uppercase', 'completed') 30 | } else if (this.status.status === 'crashed') { 31 | $('div.status.text-danger.text-uppercase', 'crashed') 32 | } else if (this.status.status === 'unknown') { 33 | $('div.status.text-info.text-uppercase', 'unknown status') 34 | } else { 35 | $('div.status', this.status.status) 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /ui/src/components/user_messages.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction, Weya as $,} from '../../../lib/weya/weya' 2 | 3 | export class UserMessages { 4 | message: string 5 | elem: HTMLDivElement 6 | 7 | constructor() { 8 | } 9 | 10 | render($: WeyaElementFunction) { 11 | this.elem = $('div', '.pointer-cursor.mt-1') 12 | } 13 | 14 | hide(isHidden: boolean) { 15 | if (isHidden) { 16 | this.elem.classList.add('hide') 17 | } else { 18 | this.elem.classList.remove('hide') 19 | } 20 | } 21 | 22 | networkError() { 23 | this.message = 'An unexpected network error occurred. Please try again later' 24 | this.elem.innerHTML = '' 25 | $(this.elem, $ => { 26 | $('div', '.message.alert', $ => { 27 | $('span', this.message) 28 | $('span', '.close-btn', 29 | String.fromCharCode(215), 30 | {on: {click: this.hide.bind(this, true)}} 31 | ) 32 | }) 33 | }) 34 | this.hide(false) 35 | } 36 | 37 | success(message: string) { 38 | this.message = message 39 | this.elem.innerHTML = '' 40 | $(this.elem, $ => { 41 | $('div', '.message.success', $ => { 42 | $('span', this.message) 43 | $('span', '.close-btn', 44 | String.fromCharCode(215), 45 | {on: {click: this.hide.bind(this, true)}} 46 | ) 47 | }) 48 | }) 49 | this.hide(false) 50 | } 51 | 52 | warning(message: string) { 53 | this.message = message 54 | this.elem.innerHTML = '' 55 | $(this.elem, $ => { 56 | $('div', '.message.alert', $ => { 57 | $('span', this.message) 58 | $('span', '.close-btn', 59 | String.fromCharCode(215), 60 | {on: {click: this.hide.bind(this, true)}} 61 | ) 62 | }) 63 | }) 64 | this.hide(false) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ui/src/d3.ts: -------------------------------------------------------------------------------- 1 | let d3 = (window).d3 2 | 3 | export default d3 -------------------------------------------------------------------------------- /ui/src/env.sample.ts: -------------------------------------------------------------------------------- 1 | export const AUTH0_DOMAIN = 'auth0.com' 2 | export const AUTH0_CLIENT_ID = '' 3 | export const APP_BASE_URL = 'http://localhost:5000' 4 | export const API_BASE_URL = 'http://localhost:5000/api/v1' 5 | export const MOBILE_APP_NAMESPACE = '' 6 | export const SENTRY_DSN = '' 7 | -------------------------------------------------------------------------------- /ui/src/main.ts: -------------------------------------------------------------------------------- 1 | import {Integrations, Sentry} from './sentry' 2 | 3 | import {ROUTER} from './app' 4 | import {RunHandler} from './views/run_view' 5 | import {PageNotFoundHandler} from './views/errors/page_not_found_view' 6 | import {RunsListHandler} from './views/runs_list_view' 7 | import {SessionsListHandler} from './views/sessions_list_view' 8 | import {LoginHandler} from './views/login_view' 9 | import {SettingsHandler} from './views/settings_view' 10 | 11 | import {experimentAnalyses, sessionAnalyses} from "./analyses/analyses" 12 | import {ProcessDetailsHandler} from "./analyses/sessions/process/detail_view" 13 | import {RunHeaderHandler} from "./analyses/experiments/run_header/view" 14 | import {SessionHeaderHandler} from "./analyses/sessions/session_header/view" 15 | import {SessionHandler} from './views/session_view' 16 | import {SENTRY_DSN} from './env' 17 | import {AuthErrorHandler} from './views/errors/auth_error_view' 18 | import {OtherErrorHandler} from './views/errors/other_error_view' 19 | import {NetworkErrorHandler} from './views/errors/network_error_view' 20 | 21 | ROUTER.route(/^(.*)$/g, [() => { 22 | ROUTER.navigate('/404') 23 | }]) 24 | 25 | new LoginHandler() 26 | 27 | new PageNotFoundHandler() 28 | new AuthErrorHandler() 29 | new OtherErrorHandler() 30 | new NetworkErrorHandler() 31 | 32 | new RunHandler() 33 | new SessionHandler() 34 | new RunsListHandler() 35 | new SessionsListHandler() 36 | new SettingsHandler() 37 | 38 | new RunHeaderHandler() 39 | new SessionHeaderHandler() 40 | 41 | //TODO properly import this later 42 | new ProcessDetailsHandler() 43 | 44 | ROUTER.route('', [() => { 45 | ROUTER.navigate('/runs') 46 | }]) 47 | 48 | ROUTER.route('cordova', [() => { 49 | window.localStorage.setItem('platform', 'cordova') 50 | ROUTER.navigate('/runs') 51 | }]) 52 | 53 | experimentAnalyses.map((analysis, i) => { 54 | new analysis.viewHandler() 55 | }) 56 | 57 | sessionAnalyses.map((analysis, i) => { 58 | new analysis.viewHandler() 59 | }) 60 | 61 | if ( 62 | document.readyState === 'complete' || 63 | document.readyState === 'interactive' 64 | ) { 65 | ROUTER.start(null, false) 66 | } else { 67 | document.addEventListener('DOMContentLoaded', () => { 68 | ROUTER.start(null, false) 69 | }) 70 | } 71 | 72 | // To make sure that :active is triggered in safari 73 | // Ref: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/AdjustingtheTextSize/AdjustingtheTextSize.html 74 | document.addEventListener("touchstart", () => { 75 | }, true); 76 | 77 | if (SENTRY_DSN) { 78 | Sentry.init({ 79 | dsn: SENTRY_DSN, 80 | integrations: [ 81 | new Integrations.BrowserTracing(), 82 | ], 83 | tracesSampleRate: 1.0, 84 | }) 85 | } 86 | 87 | (window as any).handleOpenURL = function (url) { 88 | window.location.hash = `#${url.split('#')[1]}` 89 | window.location.reload() 90 | } 91 | -------------------------------------------------------------------------------- /ui/src/mix_panel.ts: -------------------------------------------------------------------------------- 1 | let mix_panel = (window).mixpanel 2 | 3 | mix_panel.init("7e19de9c3c68ba5a897f19837042a826") 4 | mix_panel = (window).mixpanel 5 | 6 | export default mix_panel 7 | 8 | -------------------------------------------------------------------------------- /ui/src/models/config.ts: -------------------------------------------------------------------------------- 1 | export interface ConfigModel { 2 | key: string 3 | name: string 4 | computed: any 5 | value: any 6 | options: string[] 7 | order: number 8 | type: string 9 | is_hyperparam?: boolean 10 | is_meta?: boolean 11 | is_explicitly_specified?: boolean 12 | } 13 | 14 | export class Config { 15 | key: string 16 | name: string 17 | computed: any 18 | value: any 19 | options: string[] 20 | order: number 21 | type: string 22 | isHyperparam?: boolean 23 | isMeta?: boolean 24 | isExplicitlySpecified?: boolean 25 | 26 | isCustom: boolean 27 | isOnlyOption: boolean 28 | isDefault: boolean 29 | otherOptions: Set 30 | 31 | constructor(config: ConfigModel) { 32 | this.key = config.key 33 | this.name = config.name 34 | this.computed = config.computed 35 | this.value = config.value 36 | this.options = config.options ? config.options: [] 37 | this.order = config.order 38 | this.type = config.type 39 | this.isHyperparam = config.is_hyperparam 40 | this.isMeta = config.is_meta 41 | this.isExplicitlySpecified = config.is_explicitly_specified 42 | 43 | let options = new Set() 44 | for (let opt of this.options) { 45 | options.add(opt) 46 | } 47 | 48 | this.isCustom = false 49 | this.isOnlyOption = false 50 | this.isDefault = false 51 | 52 | if (options.has(this.value)) { 53 | options.delete(this.value) 54 | if (options.size === 0) { 55 | this.isOnlyOption = true 56 | this.isDefault = true 57 | } 58 | } else { 59 | this.isCustom = true 60 | if (this.isExplicitlySpecified !== true) { 61 | this.isDefault = true 62 | } 63 | } 64 | 65 | this.otherOptions = options 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ui/src/models/job.ts: -------------------------------------------------------------------------------- 1 | export interface JobModel { 2 | job_uuid: string 3 | method: string 4 | status: string 5 | created_time: number 6 | completed_time: number 7 | data: object 8 | } 9 | 10 | 11 | export class Job { 12 | job_uuid: string 13 | method: string 14 | status: string 15 | created_time: number 16 | completed_time: number 17 | data: object 18 | 19 | constructor(job: JobModel) { 20 | this.job_uuid = job.job_uuid 21 | this.method = job.method 22 | this.status = job.status 23 | this.created_time = job.created_time 24 | this.completed_time = job.completed_time 25 | this.data = job.data 26 | } 27 | 28 | get isSuccessful() { 29 | return this.status === 'success' 30 | } 31 | 32 | get isFailed() { 33 | return this.status === 'fail' 34 | } 35 | 36 | get isTimeOut() { 37 | return this.status === 'timeout' 38 | } 39 | 40 | get isComputerOffline() { 41 | return this.status === 'computer_offline' 42 | } 43 | } -------------------------------------------------------------------------------- /ui/src/models/preferences.ts: -------------------------------------------------------------------------------- 1 | export interface AnalysisPreferenceModel { 2 | series_preferences: number[] 3 | sub_series_preferences: Object 4 | chart_type: number 5 | } 6 | 7 | export class AnalysisPreference { 8 | series_preferences: number[] 9 | sub_series_preferences: object 10 | chart_type: number 11 | 12 | constructor(preference: AnalysisPreferenceModel) { 13 | if (preference.series_preferences) { 14 | this.series_preferences = preference.series_preferences 15 | } else { 16 | this.series_preferences = [] 17 | } 18 | this.chart_type = preference.chart_type 19 | this.sub_series_preferences = preference.sub_series_preferences 20 | } 21 | } 22 | 23 | export interface ComparisonPreferenceModel extends AnalysisPreferenceModel { 24 | base_series_preferences: number[] 25 | base_experiment: string 26 | } 27 | 28 | export class ComparisonPreference extends AnalysisPreference { 29 | base_series_preferences: number[] 30 | compared_with: string 31 | 32 | constructor(preference: ComparisonPreferenceModel) { 33 | super(preference) 34 | if (preference.base_series_preferences) { 35 | this.base_series_preferences = preference.base_series_preferences 36 | } else { 37 | this.base_series_preferences = [] 38 | } 39 | this.compared_with = preference.base_experiment 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ui/src/models/run.ts: -------------------------------------------------------------------------------- 1 | import {Config, ConfigModel} from "./config" 2 | 3 | export interface RunModel { 4 | run_uuid: string 5 | name: string 6 | comment: string 7 | tags: string[] 8 | note: string 9 | start_time: number 10 | python_file: string 11 | repo_remotes: string 12 | commit: string 13 | commit_message: string 14 | start_step: number 15 | is_claimed: boolean 16 | is_project_run: boolean 17 | size: number 18 | size_checkpoints: number 19 | size_tensorboard: number 20 | computer_uuid: string 21 | configs: ConfigModel[] 22 | stdout: string 23 | logger: string 24 | stderr: string 25 | } 26 | 27 | export interface PointValue { 28 | step: number 29 | value: number 30 | smoothed: number 31 | } 32 | 33 | export interface SeriesModel { 34 | name: string 35 | step: number[] 36 | value: number[] 37 | smoothed: number[] 38 | mean: number 39 | series: PointValue[] 40 | dynamic_type: string 41 | range: [number, number] 42 | is_editable: boolean 43 | sub: SeriesModel 44 | } 45 | 46 | export interface InsightModel { 47 | message: string 48 | type: string 49 | time: number 50 | } 51 | 52 | export interface AnalysisDataModel { 53 | series: any[] 54 | insights: InsightModel[] 55 | summary: any[] 56 | } 57 | 58 | export class Run { 59 | run_uuid: string 60 | name: string 61 | comment: string 62 | note: string 63 | tags: string[] 64 | start_time: number 65 | python_file: string 66 | repo_remotes: string 67 | commit: string 68 | commit_message: string 69 | start_step: number 70 | is_claimed: boolean 71 | is_project_run: boolean 72 | size: number 73 | size_checkpoints: number 74 | size_tensorboard: number 75 | computer_uuid: string 76 | configs: Config[] 77 | dynamic: object 78 | stdout: string 79 | logger: string 80 | stderr: string 81 | 82 | constructor(run: RunModel) { 83 | this.run_uuid = run.run_uuid 84 | this.name = run.name 85 | this.comment = run.comment 86 | this.note = run.note 87 | this.tags = run.tags 88 | this.start_time = run.start_time 89 | this.python_file = run.python_file 90 | this.repo_remotes = run.repo_remotes 91 | this.commit = run.commit 92 | this.commit_message = run.commit_message 93 | this.start_step = run.start_step 94 | this.is_claimed = run.is_claimed 95 | this.is_project_run = run.is_project_run 96 | this.size = run.size 97 | this.size_checkpoints = run.size_checkpoints 98 | this.size_tensorboard = run.size_tensorboard 99 | this.computer_uuid = run.computer_uuid 100 | this.configs = [] 101 | for (let c of run.configs) { 102 | this.configs.push(new Config(c)) 103 | } 104 | this.stdout = run.stdout 105 | this.logger = run.logger 106 | this.stderr = run.stderr 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /ui/src/models/run_list.ts: -------------------------------------------------------------------------------- 1 | import {RunStatus} from "./status" 2 | 3 | export interface RunListItemModel { 4 | run_uuid: string 5 | computer_uuid : string 6 | run_status: RunStatus 7 | last_updated_time: number 8 | name: string 9 | comment: string 10 | start_time: number 11 | } 12 | 13 | export interface RunsListModel { 14 | runs: RunListItemModel[] 15 | labml_token: string 16 | } 17 | 18 | export class RunListItem { 19 | run_uuid: string 20 | computer_uuid : string 21 | run_status: RunStatus 22 | last_updated_time: number 23 | name: string 24 | comment: string 25 | start_time: number 26 | 27 | constructor(run_list_item: RunListItemModel) { 28 | this.run_uuid = run_list_item.run_uuid 29 | this.computer_uuid = run_list_item.computer_uuid 30 | this.name = run_list_item.name 31 | this.comment = run_list_item.comment 32 | this.start_time = run_list_item.start_time 33 | this.last_updated_time = run_list_item.last_updated_time 34 | this.run_status = new RunStatus(run_list_item.run_status) 35 | } 36 | } 37 | 38 | export class RunsList { 39 | runs: RunListItemModel[] 40 | labml_token: string 41 | 42 | constructor(runs_list: RunsListModel) { 43 | this.runs = [] 44 | for (let r of runs_list.runs) { 45 | this.runs.push(new RunListItem(r)) 46 | } 47 | this.labml_token = runs_list.labml_token 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ui/src/models/session.ts: -------------------------------------------------------------------------------- 1 | import {Config, ConfigModel} from "./config" 2 | 3 | export interface SessionModel { 4 | computer_uuid: string 5 | session_uuid: string 6 | name: string 7 | comment: string 8 | start_time: number 9 | is_claimed: boolean 10 | is_project_session: boolean 11 | configs: ConfigModel[] 12 | } 13 | 14 | export class Session { 15 | computer_uuid: string 16 | session_uuid: string 17 | name: string 18 | comment: string 19 | is_claimed: boolean 20 | is_project_session: boolean 21 | start_time: number 22 | configs: Config[] 23 | 24 | constructor(session: SessionModel) { 25 | this.computer_uuid = session.computer_uuid 26 | this.session_uuid = session.session_uuid 27 | this.name = session.name 28 | this.comment = session.comment 29 | this.start_time = session.start_time 30 | this.is_claimed = session.is_claimed 31 | this.is_project_session = session.is_project_session 32 | this.configs = [] 33 | for (let c of session.configs) { 34 | this.configs.push(new Config(c)) 35 | } 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /ui/src/models/session_list.ts: -------------------------------------------------------------------------------- 1 | import {RunStatus} from "./status" 2 | 3 | export interface SessionsListItemModel { 4 | computer_uuid: string 5 | session_uuid: string 6 | run_status: RunStatus 7 | last_updated_time: number 8 | name: string 9 | comment: string 10 | start_time: number 11 | } 12 | 13 | export interface SessionsListModel { 14 | sessions: SessionsListItemModel[] 15 | labml_token: string 16 | } 17 | 18 | export class SessionListItem { 19 | computer_uuid: string 20 | session_uuid: string 21 | run_status: RunStatus 22 | last_updated_time: number 23 | name: string 24 | comment: string 25 | start_time: number 26 | 27 | constructor(sessionListItem: SessionsListItemModel) { 28 | this.computer_uuid = sessionListItem.computer_uuid 29 | this.session_uuid = sessionListItem.session_uuid 30 | this.name = sessionListItem.name 31 | this.comment = sessionListItem.comment 32 | this.start_time = sessionListItem.start_time 33 | this.last_updated_time = sessionListItem.last_updated_time 34 | this.run_status = new RunStatus(sessionListItem.run_status) 35 | } 36 | } 37 | 38 | export class SessionsList { 39 | sessions: SessionsListItemModel[] 40 | labml_token: string 41 | 42 | constructor(sessionsList: SessionsListModel) { 43 | this.sessions = [] 44 | for (let c of sessionsList.sessions) { 45 | this.sessions.push(new SessionListItem(c)) 46 | } 47 | this.labml_token = sessionsList.labml_token 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ui/src/models/status.ts: -------------------------------------------------------------------------------- 1 | export interface RunStatusModel { 2 | status: string 3 | details: string 4 | time: number 5 | } 6 | 7 | export interface StatusModel { 8 | run_uuid: string 9 | last_updated_time: number 10 | run_status: RunStatusModel 11 | } 12 | 13 | export class RunStatus { 14 | status: string 15 | details: string 16 | time: number 17 | 18 | constructor(runStatus: RunStatusModel) { 19 | this.status = runStatus.status 20 | this.details = runStatus.details 21 | this.time = runStatus.time 22 | } 23 | } 24 | 25 | export class Status { 26 | uuid: string 27 | last_updated_time: number 28 | run_status: RunStatus 29 | 30 | constructor(status: StatusModel) { 31 | this.uuid = status.run_uuid 32 | this.last_updated_time = status.last_updated_time 33 | this.run_status = new RunStatus(status.run_status) 34 | } 35 | 36 | get isRunning() { 37 | if (this.run_status.status === 'in progress') { 38 | let timeDiff = (Date.now() / 1000 - this.last_updated_time) / 60 39 | return timeDiff <= 15 40 | } else { 41 | return false 42 | } 43 | } 44 | 45 | get isStatusInProgress() { 46 | return this.run_status.status === 'in progress' 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /ui/src/models/user.ts: -------------------------------------------------------------------------------- 1 | export interface UserModel { 2 | sub: string 3 | email: string 4 | name: string 5 | picture: string 6 | theme: string 7 | email_verified: boolean 8 | projects: object 9 | default_project: object 10 | } 11 | 12 | export class User { 13 | sub: string 14 | email: string 15 | name: string 16 | picture: string 17 | theme: string 18 | email_verified: boolean 19 | projects: object 20 | default_project: object 21 | 22 | constructor(user: UserModel) { 23 | this.sub = user.sub 24 | this.email = user.email 25 | this.name = user.name 26 | this.picture = user.picture 27 | this.theme = user.theme 28 | this.email_verified = user.email_verified 29 | this.projects = user.projects 30 | this.default_project = user.default_project 31 | } 32 | } 33 | 34 | export interface IsUserLoggedModel { 35 | is_user_logged: boolean 36 | } 37 | 38 | export class IsUserLogged { 39 | is_user_logged: boolean 40 | 41 | constructor(isUserLogged: IsUserLoggedModel) { 42 | this.is_user_logged = isUserLogged.is_user_logged 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ui/src/screen.ts: -------------------------------------------------------------------------------- 1 | import {Weya as $} from '../../lib/weya/weya' 2 | import {getWindowDimensions} from "./utils/window_dimentions" 3 | import CACHE, {IsUserLoggedCache, UserCache} from './cache/cache' 4 | import {Loader} from './components/loader' 5 | import {ROUTER} from './app' 6 | import {setTitle} from './utils/document' 7 | import {ScreenView} from './screen_view' 8 | import {handleNetworkErrorInplace} from './utils/redirect' 9 | 10 | class ScreenContainer { 11 | view?: ScreenView 12 | private isUserLoggedCache: IsUserLoggedCache 13 | private isUserLogged: boolean 14 | private userCache: UserCache 15 | private loader: Loader 16 | private windowWidth: number 17 | 18 | constructor() { 19 | this.view = null 20 | this.isUserLoggedCache = CACHE.getIsUserLogged() 21 | this.userCache = CACHE.getUser() 22 | this.loader = new Loader(true) 23 | window.addEventListener('resize', this.onResize.bind(this)) 24 | document.addEventListener('visibilitychange', this.onVisibilityChange.bind(this)) 25 | } 26 | 27 | onResize = () => { 28 | let windowWidth = getWindowDimensions().width 29 | // Prevent mobile browser addressBar visibility from triggering a resize event 30 | if (this.windowWidth !== windowWidth && this.view) { 31 | this.windowWidth = windowWidth 32 | this.view.onResize(windowWidth) 33 | } 34 | } 35 | 36 | onVisibilityChange() { 37 | if (this.view) { 38 | this.view.onVisibilityChange() 39 | } 40 | } 41 | 42 | async updateTheme() { 43 | let theme = localStorage.getItem('theme') || 'light' 44 | if (document.body.className !== theme) { 45 | document.body.className = theme 46 | } 47 | try { 48 | this.isUserLogged = (await this.isUserLoggedCache.get()).is_user_logged 49 | if (this.isUserLogged) { 50 | theme = (await this.userCache.get()).theme 51 | localStorage.setItem('theme', theme) 52 | } 53 | } catch (e) { 54 | //Let the view handle network failures 55 | } 56 | if (document.body.className !== theme) { 57 | document.body.className = theme || 'light' 58 | } 59 | } 60 | 61 | setView(view: ScreenView) { 62 | if (this.view) { 63 | this.view.destroy() 64 | setTitle({}) 65 | } 66 | this.view = view 67 | document.body.innerHTML = '' 68 | this.updateTheme().then() 69 | this.loader.render($) 70 | if (!this.view.requiresAuth) { 71 | document.body.innerHTML = '' 72 | this.windowWidth = null 73 | this.onResize() 74 | document.body.append(this.view.render()) 75 | return 76 | } 77 | this.isUserLoggedCache.get().then(value => { 78 | this.isUserLogged = value.is_user_logged 79 | if (this.view.requiresAuth && !value.is_user_logged) { 80 | ROUTER.navigate(`/login#return_url=${window.location.pathname}`) 81 | return 82 | } 83 | document.body.innerHTML = '' 84 | this.windowWidth = null 85 | this.onResize() 86 | document.body.append(this.view.render()) 87 | }).catch(e => { 88 | handleNetworkErrorInplace(e) 89 | }) 90 | } 91 | } 92 | 93 | export {ScreenContainer} 94 | -------------------------------------------------------------------------------- /ui/src/screen_view.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElement} from '../../lib/weya/weya' 2 | 3 | abstract class ScreenView { 4 | get requiresAuth() { 5 | return true 6 | } 7 | 8 | abstract render(): WeyaElement 9 | 10 | onResize(width: number) { 11 | } 12 | 13 | destroy() { 14 | } 15 | 16 | onRefresh() { 17 | } 18 | 19 | onVisibilityChange() { 20 | } 21 | } 22 | 23 | export {ScreenView} 24 | -------------------------------------------------------------------------------- /ui/src/sentry.ts: -------------------------------------------------------------------------------- 1 | let Sentry = (window).Sentry 2 | let Integrations = (window).Sentry.Integrations 3 | 4 | export { 5 | Sentry, 6 | Integrations 7 | } -------------------------------------------------------------------------------- /ui/src/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labml.ai", 3 | "icons": [ 4 | { 5 | "src": "/images/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/images/android-chrome-512x512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "background_color": "#ffffff", 17 | "description": "Monitor PyTorch & TensorFlow model training on mobile phones", 18 | "display": "standalone", 19 | "shortcuts": [ 20 | { 21 | "name": "Runs", 22 | "url": "/runs", 23 | "description": "Machine learning experiments" 24 | }, 25 | { 26 | "name": "Computers", 27 | "url": "/computers", 28 | "description": "Computer monitoring" 29 | }, 30 | { 31 | "name": "Settings", 32 | "url": "/settings" 33 | } 34 | ], 35 | "start_url": "/" 36 | } 37 | -------------------------------------------------------------------------------- /ui/src/types.ts: -------------------------------------------------------------------------------- 1 | export type ContentType = 'run' | 'session' 2 | -------------------------------------------------------------------------------- /ui/src/utils/document.ts: -------------------------------------------------------------------------------- 1 | export function setTitle(opt: { section?: string, item?: string }) { 2 | if (opt.section != null && opt.item != null) { 3 | document.title = `${opt.section} - ${opt.item} - labml.ai` 4 | } else if (opt.section != null || opt.item != null) { 5 | document.title = `${opt.section || opt.item} - labml.ai` 6 | } else { 7 | document.title = 'labml.ai' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ui/src/utils/meta_tags.ts: -------------------------------------------------------------------------------- 1 | import {Run} from '../models/run' 2 | 3 | const metaTag = `Monitor PyTorch & TensorFlow model training on mobile phones` 4 | 5 | function setMetaDes(des: string) { 6 | document.getElementsByTagName('meta') 7 | .namedItem('description') 8 | .setAttribute('content', des) 9 | } 10 | 11 | 12 | export function changeRunDec(run: Run) { 13 | let metaStr = `${metaTag}\n${run.name};${run.comment}\n${run.run_uuid}` 14 | 15 | setMetaDes(metaStr) 16 | } -------------------------------------------------------------------------------- /ui/src/utils/mobile.ts: -------------------------------------------------------------------------------- 1 | export function detectMobile(): boolean { 2 | return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) 3 | } 4 | 5 | let isMobile = detectMobile() 6 | 7 | export default isMobile -------------------------------------------------------------------------------- /ui/src/utils/new_tab.ts: -------------------------------------------------------------------------------- 1 | import {UserMessages} from "../components/user_messages" 2 | 3 | 4 | export function openInNewTab(url, userMessages?: UserMessages) { 5 | let newWindow = window.open(url, '_blank') 6 | 7 | if (!newWindow || newWindow.closed || typeof newWindow.closed == 'undefined') { 8 | let error = `cannot open ${url}: blocked pop up windows` 9 | if (userMessages) { 10 | userMessages.warning(error) 11 | } else { 12 | throw new Error(error) 13 | } 14 | } else { 15 | newWindow.focus() 16 | } 17 | } -------------------------------------------------------------------------------- /ui/src/utils/redirect.ts: -------------------------------------------------------------------------------- 1 | import {NetworkError} from '../network'; 2 | import {ROUTER} from '../app'; 3 | import {Sentry} from '../sentry'; 4 | import {PageNotFoundHandler} from '../views/errors/page_not_found_view' 5 | import {AuthErrorHandler} from '../views/errors/auth_error_view' 6 | import {OtherErrorHandler} from '../views/errors/other_error_view' 7 | import {NetworkErrorHandler} from '../views/errors/network_error_view' 8 | 9 | export function handleNetworkError(error: Error | NetworkError) { 10 | if (error instanceof NetworkError) { 11 | if (error.statusCode === 404 || error.statusCode === 400) { 12 | ROUTER.navigate('/404') 13 | } else if (error.statusCode === 401 || error.statusCode === 403) { 14 | ROUTER.navigate('/401') 15 | } else { 16 | ROUTER.navigate('/500') 17 | } 18 | } else { 19 | ROUTER.navigate('/network_error') 20 | } 21 | Sentry.setExtra('error', error) 22 | Sentry.captureException(error) 23 | } 24 | 25 | export function handleNetworkErrorInplace(error: Error | NetworkError) { 26 | if (error instanceof NetworkError) { 27 | if (error.statusCode === 404 || error.statusCode === 400) { 28 | PageNotFoundHandler.handlePageNotFound() 29 | } else if (error.statusCode === 401 || error.statusCode === 403) { 30 | AuthErrorHandler.handleAuthError() 31 | } else { 32 | OtherErrorHandler.handleOtherError() 33 | } 34 | } else { 35 | NetworkErrorHandler.handleNetworkError() 36 | } 37 | 38 | Sentry.setExtra('error', error) 39 | Sentry.captureException(error) 40 | } 41 | -------------------------------------------------------------------------------- /ui/src/utils/render.ts: -------------------------------------------------------------------------------- 1 | export async function waitForFrame() { 2 | return new Promise((resolve) => { 3 | window.requestAnimationFrame(() => { 4 | resolve() 5 | }) 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /ui/src/utils/time.ts: -------------------------------------------------------------------------------- 1 | export function formatTime(time: number): string { 2 | let date = new Date(time * 1000) 3 | let timeStr = date.toTimeString().substr(0, 8) 4 | let dateStr = date.toDateString() 5 | 6 | return `${dateStr} at ${timeStr}` 7 | } 8 | 9 | 10 | export function getTimeDiff(timestamp: number): string { 11 | let timeDiff = (Date.now() / 1000 - timestamp / 1000) 12 | 13 | if (timeDiff < 60) { 14 | return `${Math.round(timeDiff)}s ago` 15 | } else if (timeDiff < 600) { 16 | return `${Math.floor(timeDiff / 60)}m and ${Math.round(timeDiff % 60)}s ago` 17 | } else { 18 | return formatTime(timestamp / 1000) 19 | } 20 | } 21 | 22 | const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", 23 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] 24 | 25 | 26 | export function formatDateTime(dateTime: Date) { 27 | let date = dateTime.getDate() 28 | let month = monthNames[dateTime.getMonth()] 29 | let timeStr = dateTime.toTimeString().substr(0, 8) 30 | 31 | return `${month} ${date},${timeStr}` 32 | } -------------------------------------------------------------------------------- /ui/src/utils/window_dimentions.ts: -------------------------------------------------------------------------------- 1 | export function getWindowDimensions() { 2 | const {innerWidth: width, innerHeight: height} = window 3 | 4 | return { 5 | width, 6 | height 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /ui/src/views/empty_runs_list.ts: -------------------------------------------------------------------------------- 1 | import {Weya, WeyaElementFunction, WeyaElement} from '../../../lib/weya/weya' 2 | import PyTorchCode from '../components/codes/pytorch' 3 | import KerasCode from '../components/codes/keras' 4 | import PyTorchLightningCode from '../components/codes/pytorch_lightning' 5 | 6 | export default class EmptyRunsList { 7 | currentTab: string 8 | codeContainer: WeyaElement 9 | 10 | constructor() { 11 | this.currentTab = 'pytoch' 12 | } 13 | 14 | clickHandle(tab: string) { 15 | this.currentTab = tab 16 | this.renderCode() 17 | } 18 | 19 | render($: WeyaElementFunction) { 20 | $('div.text-center', $ => { 21 | $('h5.mt-4.px-1', 'You will see your runs here') 22 | $('p.px-1', 'Start monitoring your models by adding just two lines of code:') 23 | }) 24 | 25 | $('div.text-center', $ => { 26 | $('nav.nav-link.d-inline.tab', 'PyTorch', {on: {click: () => this.clickHandle('pytoch')}}) 27 | $('nav.nav-link.d-inline.tab', 'PyTorch Lightning', {on: {click: () => this.clickHandle('lightning')}}) 28 | $('nav.nav-link.d-inline.tab', 'Keras', {on: {click: () => this.clickHandle('keras')}}) 29 | }) 30 | 31 | this.codeContainer = $('div') 32 | this.renderCode() 33 | } 34 | 35 | renderCode() { 36 | this.codeContainer.innerHTML = '' 37 | 38 | Weya(this.codeContainer, $ => { 39 | if (this.currentTab === 'pytoch') { 40 | new PyTorchCode().render($) 41 | } else if (this.currentTab === 'keras') { 42 | new KerasCode().render($) 43 | } else if (this.currentTab === 'lightning') { 44 | new PyTorchLightningCode().render($) 45 | } 46 | }) 47 | } 48 | } -------------------------------------------------------------------------------- /ui/src/views/empty_sessions_list.ts: -------------------------------------------------------------------------------- 1 | import {WeyaElementFunction} from '../../../lib/weya/weya' 2 | 3 | export default class EmptySessionsList { 4 | constructor() { 5 | } 6 | 7 | render($: WeyaElementFunction) { 8 | $('div.text-center', $ => { 9 | $('h5.mt-4.px-1', 'You will see your computers here') 10 | $('p.px-1', 'Run the following command to start monitoring:') 11 | }) 12 | 13 | $('div', $ => { 14 | $('div.code-sample.bg-dark.px-1.py-2.my-3.code-container', $ => { 15 | $('pre.text-white', $ => { 16 | $('div.labml-api', $ => { 17 | $('span.key-word', 'pip') 18 | $('span', ' install labml psutil py3nvml') 19 | $('br') 20 | $('span.key-word', 'labml') 21 | $('span', ' monitor') 22 | }) 23 | }) 24 | }) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/src/views/errors/auth_error_view.ts: -------------------------------------------------------------------------------- 1 | import {ROUTER, SCREEN} from '../../app' 2 | import {Weya as $} from '../../../../lib/weya/weya' 3 | import mix_panel from "../../mix_panel" 4 | import {setTitle} from '../../utils/document' 5 | import {ScreenView} from '../../screen_view' 6 | 7 | function wrapEvent(eventName: string, func: Function) { 8 | function wrapper() { 9 | let e: Event = arguments[arguments.length - 1] 10 | if (eventName[eventName.length - 1] !== '_') { 11 | e.preventDefault() 12 | e.stopPropagation() 13 | } 14 | 15 | func.apply(null, arguments) 16 | } 17 | 18 | return wrapper 19 | } 20 | 21 | class AuthErrorView extends ScreenView { 22 | elem: HTMLDivElement 23 | private events = { 24 | back: () => { 25 | if (ROUTER.canBack()) { 26 | ROUTER.back() 27 | } else { 28 | ROUTER.navigate('/') 29 | } 30 | }, 31 | login: () => { 32 | ROUTER.navigate(`/login`) 33 | }, 34 | slack: () => { 35 | window.open('https://join.slack.com/t/labforml/shared_invite/zt-egj9zvq9-Dl3hhZqobexgT7aVKnD14g/') 36 | }, 37 | } 38 | 39 | constructor() { 40 | super() 41 | let events = [] 42 | for (let k in this.events) { 43 | events.push(k) 44 | } 45 | 46 | for (let k of events) { 47 | let func = this.events[k] 48 | this.events[k] = wrapEvent(k, func) 49 | } 50 | 51 | mix_panel.track('401 View') 52 | } 53 | 54 | get requiresAuth(): boolean { 55 | return false 56 | } 57 | 58 | render() { 59 | setTitle({section: '401'}) 60 | this.elem = $('div', '.error-container', $ => { 61 | $('h2', '.mt-5', 'Ooops! Authentication Failure.' + '') 62 | $('h1', '401') 63 | $('p', 'We are having trouble authenticating your request' + '') 64 | $('div', '.btn-container.mt-3', $ => { 65 | $('button', '.btn.nav-link', 66 | {on: {click: this.events.back}}, 67 | $ => { 68 | $('span', '.fas.fa-redo', '') 69 | $('span', '.m-1', 'Retry') 70 | }) 71 | $('button', '.btn.nav-link', 72 | {on: {click: this.events.login}}, 73 | $ => { 74 | $('span', '.fas.fa-user', '') 75 | $('span', '.m-1', 'Login Again') 76 | }) 77 | $('button', '.btn.nav-link', 78 | {on: {click: this.events.slack}}, 79 | $ => { 80 | $('span', '.fas.fa-comments', '') 81 | $('span', '.m-1', 'Reach us on Slack') 82 | }) 83 | }) 84 | 85 | }) 86 | 87 | return this.elem 88 | } 89 | 90 | destroy() { 91 | } 92 | } 93 | 94 | export class AuthErrorHandler { 95 | constructor() { 96 | ROUTER.route('401', [AuthErrorHandler.handleAuthError]) 97 | } 98 | 99 | static handleAuthError = () => { 100 | SCREEN.setView(new AuthErrorView()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ui/src/views/errors/network_error_view.ts: -------------------------------------------------------------------------------- 1 | import {ROUTER, SCREEN} from '../../app' 2 | import {Weya as $} from '../../../../lib/weya/weya' 3 | import mix_panel from "../../mix_panel" 4 | import {setTitle} from '../../utils/document' 5 | import {ScreenView} from '../../screen_view' 6 | 7 | function wrapEvent(eventName: string, func: Function) { 8 | function wrapper() { 9 | let e: Event = arguments[arguments.length - 1] 10 | if (eventName[eventName.length - 1] !== '_') { 11 | e.preventDefault() 12 | e.stopPropagation() 13 | } 14 | 15 | func.apply(null, arguments) 16 | } 17 | 18 | return wrapper 19 | } 20 | 21 | class NetworkErrorView extends ScreenView { 22 | elem: HTMLDivElement 23 | private events = { 24 | retry: () => { 25 | if (ROUTER.canBack()) { 26 | ROUTER.back() 27 | } 28 | }, 29 | } 30 | 31 | constructor() { 32 | super() 33 | let events = [] 34 | for (let k in this.events) { 35 | events.push(k) 36 | } 37 | 38 | for (let k of events) { 39 | let func = this.events[k] 40 | this.events[k] = wrapEvent(k, func) 41 | } 42 | 43 | mix_panel.track('Network Error View') 44 | } 45 | 46 | get requiresAuth(): boolean { 47 | return false 48 | } 49 | 50 | render() { 51 | setTitle({section: 'Network Error'}) 52 | this.elem = $('div', '.error-container', $ => { 53 | $('h2', '.mt-5', 'Ooops!' + '') 54 | $('p', 'There\'s a problem with the connection between you and us' + '') 55 | $('div', '.btn-container.mt-3', $ => { 56 | $('button', '.btn.nav-link', 57 | {on: {click: this.events.retry}}, 58 | $ => { 59 | $('span', '.fas.fa-redo', '') 60 | $('span', '.m-1', 'Retry') 61 | }) 62 | }) 63 | }) 64 | 65 | return this.elem 66 | } 67 | 68 | destroy() { 69 | } 70 | } 71 | 72 | export class NetworkErrorHandler { 73 | constructor() { 74 | ROUTER.route('network_error', [NetworkErrorHandler.handleNetworkError]) 75 | } 76 | 77 | static handleNetworkError = () => { 78 | SCREEN.setView(new NetworkErrorView()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ui/src/views/errors/other_error_view.ts: -------------------------------------------------------------------------------- 1 | import {ROUTER, SCREEN} from '../../app' 2 | import {Weya as $} from '../../../../lib/weya/weya' 3 | import mix_panel from "../../mix_panel" 4 | import {setTitle} from '../../utils/document' 5 | import {ScreenView} from '../../screen_view' 6 | 7 | function wrapEvent(eventName: string, func: Function) { 8 | function wrapper() { 9 | let e: Event = arguments[arguments.length - 1] 10 | if (eventName[eventName.length - 1] !== '_') { 11 | e.preventDefault() 12 | e.stopPropagation() 13 | } 14 | 15 | func.apply(null, arguments) 16 | } 17 | 18 | return wrapper 19 | } 20 | 21 | class OtherErrorView extends ScreenView { 22 | elem: HTMLDivElement 23 | private events = { 24 | back: () => { 25 | if (ROUTER.canBack()) { 26 | ROUTER.back() 27 | } else { 28 | ROUTER.navigate('/') 29 | } 30 | }, 31 | slack: () => { 32 | window.open('https://join.slack.com/t/labforml/shared_invite/zt-egj9zvq9-Dl3hhZqobexgT7aVKnD14g/') 33 | }, 34 | } 35 | 36 | constructor() { 37 | super() 38 | let events = [] 39 | for (let k in this.events) { 40 | events.push(k) 41 | } 42 | 43 | for (let k of events) { 44 | let func = this.events[k] 45 | this.events[k] = wrapEvent(k, func) 46 | } 47 | 48 | mix_panel.track('500 View') 49 | } 50 | 51 | get requiresAuth(): boolean { 52 | return false 53 | } 54 | 55 | render() { 56 | setTitle({section: '500'}) 57 | this.elem = $('div', '.error-container', $ => { 58 | $('h2', '.mt-5', 'Ooops! Something went wrong' + '') 59 | $('h1', '500') 60 | $('p', 'Seems like we are having issues right now' + '') 61 | $('div', '.btn-container.mt-3', $ => { 62 | $('button', '.btn.nav-link', 63 | {on: {click: this.events.back}}, 64 | $ => { 65 | $('span', '.fas.fa-redo', '') 66 | $('span', '.m-1', 'Retry') 67 | }) 68 | $('button', '.btn.nav-link', 69 | {on: {click: this.events.slack}}, 70 | $ => { 71 | $('span', '.fas.fa-comments', '') 72 | $('span', '.m-1', 'Reach us on Slack') 73 | }) 74 | }) 75 | 76 | }) 77 | 78 | return this.elem 79 | } 80 | 81 | destroy() { 82 | } 83 | } 84 | 85 | export class OtherErrorHandler { 86 | constructor() { 87 | ROUTER.route('500', [OtherErrorHandler.handleOtherError]) 88 | } 89 | 90 | static handleOtherError = () => { 91 | SCREEN.setView(new OtherErrorView()) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ui/src/views/errors/page_not_found_view.ts: -------------------------------------------------------------------------------- 1 | import {ROUTER, SCREEN} from '../../app' 2 | import {Weya as $} from '../../../../lib/weya/weya' 3 | import mix_panel from "../../mix_panel" 4 | import {setTitle} from '../../utils/document' 5 | import {ScreenView} from '../../screen_view' 6 | 7 | function wrapEvent(eventName: string, func: Function) { 8 | function wrapper() { 9 | let e: Event = arguments[arguments.length - 1] 10 | if (eventName[eventName.length - 1] !== '_') { 11 | e.preventDefault() 12 | e.stopPropagation() 13 | } 14 | 15 | func.apply(null, arguments) 16 | } 17 | 18 | return wrapper 19 | } 20 | 21 | class PageNotFoundView extends ScreenView { 22 | elem: HTMLDivElement 23 | private events = { 24 | home: () => { 25 | ROUTER.navigate(`/`) 26 | }, 27 | } 28 | 29 | constructor() { 30 | super() 31 | let events = [] 32 | for (let k in this.events) { 33 | events.push(k) 34 | } 35 | 36 | for (let k of events) { 37 | let func = this.events[k] 38 | this.events[k] = wrapEvent(k, func) 39 | } 40 | 41 | mix_panel.track('404 View') 42 | } 43 | 44 | get requiresAuth(): boolean { 45 | return false 46 | } 47 | 48 | render() { 49 | setTitle({section: '404'}) 50 | this.elem = $('div', '.error-container', $ => { 51 | $('h2', '.mt-5', 'Ooops! Page not found.' + '') 52 | $('h1', '404') 53 | $('p', 'We can\'t find the page.' + '') 54 | $('div', '.btn-container.mt-3', $ => { 55 | $('button', '.btn.nav-link', 56 | {on: {click: this.events.home}}, 57 | $ => { 58 | $('span', '.fas.fa-home', '') 59 | $('span', '.m-1', 'Home') 60 | }) 61 | }) 62 | }) 63 | 64 | return this.elem 65 | } 66 | 67 | destroy() { 68 | } 69 | } 70 | 71 | export class PageNotFoundHandler { 72 | constructor() { 73 | ROUTER.route('404', [PageNotFoundHandler.handlePageNotFound]) 74 | } 75 | 76 | static handlePageNotFound = () => { 77 | SCREEN.setView(new PageNotFoundView()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ui/src/views/login_view.ts: -------------------------------------------------------------------------------- 1 | import {IsUserLogged} from '../models/user' 2 | import {ROUTER, SCREEN} from '../app' 3 | import {Weya as $, WeyaElement} from '../../../lib/weya/weya' 4 | import {Loader} from "../components/loader" 5 | import CACHE, {IsUserLoggedCache} from "../cache/cache" 6 | import NETWORK from '../network' 7 | import {handleNetworkError} from '../utils/redirect'; 8 | import {setTitle} from '../utils/document' 9 | import {ScreenView} from '../screen_view' 10 | 11 | class LoginView extends ScreenView { 12 | isUserLogged: IsUserLogged 13 | isUserLoggedCache: IsUserLoggedCache 14 | elem: WeyaElement 15 | loader: Loader 16 | token: string 17 | returnUrl: string 18 | 19 | constructor(token?: string, returnUrl: string = '/runs') { 20 | super() 21 | 22 | this.isUserLoggedCache = CACHE.getIsUserLogged() 23 | this.loader = new Loader(true) 24 | this.token = token 25 | this.returnUrl = returnUrl 26 | } 27 | 28 | get requiresAuth(): boolean { 29 | return false; 30 | } 31 | 32 | render() { 33 | this.elem = $('div', $ => { 34 | this.loader.render($) 35 | }) 36 | 37 | this.handleLogin().then() 38 | setTitle({section: 'Login'}) 39 | 40 | return this.elem 41 | } 42 | 43 | private async handleLogin() { 44 | this.isUserLogged = await this.isUserLoggedCache.get() 45 | 46 | if (this.token) { 47 | try { 48 | let res = await NETWORK.signIn(this.token) 49 | if (!res.is_successful) { 50 | ROUTER.navigate('/401') 51 | } 52 | localStorage.setItem('app_token', res.app_token) 53 | this.isUserLogged = await this.isUserLoggedCache.get(true) 54 | } catch (e) { 55 | handleNetworkError(e) 56 | return 57 | } 58 | await SCREEN.updateTheme().then() 59 | } 60 | 61 | if (!this.isUserLogged.is_user_logged) { 62 | NETWORK.redirectLogin() 63 | return 64 | } 65 | 66 | ROUTER.navigate(this.returnUrl) 67 | this.loader.remove() 68 | } 69 | } 70 | 71 | export class LoginHandler { 72 | constructor() { 73 | ROUTER.route('login', [this.handleLogin]) 74 | } 75 | 76 | handleLogin = () => { 77 | let params = (window.location.hash.substr(1)).split("&") 78 | let token = undefined 79 | let returnUrl = sessionStorage.getItem('return_url') || '/runs' 80 | 81 | for (let i = 0; i < params.length; i++) { 82 | let val = params[i].split('=') 83 | switch (val[0]) { 84 | case 'access_token': 85 | token = val[1] 86 | break 87 | case 'return_url': 88 | returnUrl = val[1] 89 | break 90 | } 91 | } 92 | sessionStorage.setItem('return_url', returnUrl) 93 | SCREEN.setView(new LoginView(token, returnUrl)) 94 | } 95 | } 96 | --------------------------------------------------------------------------------