├── .prettierrc ├── .eslintignore ├── .prettierignore ├── doc ├── setting.png └── screencast.gif ├── jupyter_config.json ├── .lintstagedrc ├── jupyterlab-system-monitor ├── _version.py └── __init__.py ├── packages ├── system-monitor │ ├── style │ │ └── index.css │ ├── tsconfig.json │ ├── schema │ │ └── plugin.json │ ├── src │ │ └── index.ts │ └── package.json └── system-monitor-base │ ├── src │ ├── index.ts │ ├── cpuView.tsx │ ├── memoryView.tsx │ ├── indicator.tsx │ └── model.ts │ ├── tsconfig.json │ ├── style │ └── index.css │ └── package.json ├── lerna.json ├── pyproject.toml ├── binder └── environment.yml ├── install.json ├── MANIFEST.in ├── tsconfigbase.json ├── .github └── workflows │ ├── build.yml │ └── packaging.yml ├── package.json ├── .eslintrc.js ├── LICENSE ├── .gitignore ├── setup.py └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/build 5 | **/static 6 | -------------------------------------------------------------------------------- /doc/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtpio/jupyterlab-system-monitor/HEAD/doc/setting.png -------------------------------------------------------------------------------- /doc/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtpio/jupyterlab-system-monitor/HEAD/doc/screencast.gif -------------------------------------------------------------------------------- /jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResourceUseDisplay": { 3 | "track_cpu_percent": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}": [ 3 | "prettier --write" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /jupyterlab-system-monitor/_version.py: -------------------------------------------------------------------------------- 1 | version_info = (0, 8, 0) 2 | __version__ = ".".join(map(str, version_info)) 3 | -------------------------------------------------------------------------------- /packages/system-monitor/style/index.css: -------------------------------------------------------------------------------- 1 | .jp-TopBar-item .jp-IndicatorContainer { 2 | max-width: 500px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/system-monitor-base/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cpuView'; 2 | export * from './memoryView'; 3 | export * from './model'; 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "npmClient": "jlpm", 4 | "useWorkspaces": true, 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["jupyter_packaging~=0.7.9", "jupyterlab>=3.0.0rc10,==3.*", "setuptools>=40.8.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /packages/system-monitor-base/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlab-system-monitor 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python=3 6 | - jupyterlab=3 7 | - jupyter-resource-usage=0.5 8 | - jupyterlab-system-monitor=0.8 9 | - nodejs 10 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "jupyterlab-system-monitor", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab-system-monitor" 5 | } 6 | -------------------------------------------------------------------------------- /packages/system-monitor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../system-monitor-base" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /jupyterlab-system-monitor/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import os.path as osp 4 | 5 | from ._version import __version__ 6 | 7 | HERE = osp.abspath(osp.dirname(__file__)) 8 | 9 | with open(osp.join(HERE, 'labextension', 'package.json')) as fid: 10 | data = json.load(fid) 11 | 12 | def _jupyter_labextension_paths(): 13 | return [{ 14 | 'src': 'labextension', 15 | 'dest': data['name'] 16 | }] 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include pyproject.toml 4 | include jupyter-config/jupyterlab-system-monitor.json 5 | 6 | include package.json 7 | include install.json 8 | include ts*.json 9 | 10 | graft jupyterlab-system-monitor/labextension 11 | 12 | # Javascript files 13 | graft src 14 | graft style 15 | prune **/node_modules 16 | prune lib 17 | 18 | # Patterns to exclude from any directory 19 | global-exclude *~ 20 | global-exclude *.pyc 21 | global-exclude *.pyo 22 | global-exclude .git 23 | global-exclude .ipynb_checkpoints 24 | -------------------------------------------------------------------------------- /tsconfigbase.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "outDir": "lib", 15 | "rootDir": "src", 16 | "preserveWatchOutput": true, 17 | "resolveJsonModule": true, 18 | "strictNullChecks": false, 19 | "target": "esnext" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/system-monitor-base/style/index.css: -------------------------------------------------------------------------------- 1 | .jp-IndicatorContainer { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .jp-IndicatorFiller { 7 | height: 100%; 8 | } 9 | 10 | .jp-IndicatorText { 11 | display: flex; 12 | min-width: 35px; 13 | flex-direction: column; 14 | justify-content: center; 15 | text-align: right; 16 | white-space: nowrap; 17 | overflow: hidden; 18 | } 19 | 20 | .jp-IndicatorWrapper { 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: center; 24 | margin-left: 5px; 25 | margin-right: 5px; 26 | width: 75px; 27 | } 28 | 29 | .jp-IndicatorBar { 30 | height: 75%; 31 | outline: 1px solid black; 32 | } 33 | 34 | .jp-IndicatorBar svg { 35 | max-width: 100%; 36 | height: 100%; 37 | } 38 | -------------------------------------------------------------------------------- /packages/system-monitor/schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.setting-icon-class": "jp-BuildIcon", 3 | "jupyter.lab.setting-icon-label": "System Monitor", 4 | "title": "System Monitor", 5 | "description": "System Monitor", 6 | "properties": { 7 | "refreshRate": { 8 | "title": "Refresh Rate (ms)", 9 | "description": "Refresh Rate to sync metrics data", 10 | "default": 5000, 11 | "type": "number" 12 | }, 13 | "memory": { 14 | "title": "Memory Settings", 15 | "description": "Settings for the memory indicator", 16 | "default": { 17 | "label": "Mem: " 18 | }, 19 | "type": "object" 20 | }, 21 | "cpu": { 22 | "title": "CPU Settings", 23 | "description": "Settings for the CPU indicator", 24 | "default": { 25 | "label": "CPU: " 26 | }, 27 | "type": "object" 28 | } 29 | }, 30 | "additionalProperties": false, 31 | "type": "object" 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Install node 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: '12.x' 21 | 22 | - name: Install Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.8' 26 | 27 | - name: Install JupyterLab 28 | run: | 29 | python -m pip install jupyterlab --pre 30 | 31 | - name: Install the extension 32 | run: | 33 | jlpm && jlpm bootstrap 34 | python -m pip install -e . 35 | jupyter labextension list 2>&1 | grep -ie "jupyterlab-system-monitor.*OK" 36 | 37 | - name: Browser check 38 | run: python -m jupyterlab.browser_check 39 | 40 | - name: Lint 41 | run: jlpm run lint:check -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "bootstrap": "lerna bootstrap", 5 | "build": "lerna run build", 6 | "build:prod": "lerna run build:prod", 7 | "clean": "lerna run clean", 8 | "develop": "jupyter labextension develop . --overwrite", 9 | "lint": "jlpm run eslint && jlpm run prettier", 10 | "lint:check": "jlpm run eslint:check && jlpm run prettier:check", 11 | "eslint": "eslint . --ext .ts,.tsx --fix", 12 | "eslint:check": "eslint . --ext .ts,.tsx", 13 | "prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 14 | "prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"" 15 | }, 16 | "workspaces": { 17 | "packages": [ 18 | "packages/*" 19 | ] 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "lint-staged" 24 | } 25 | }, 26 | "resolutions": { 27 | "react": "^17", 28 | "@types/react": "^17" 29 | }, 30 | "devDependencies": { 31 | "@typescript-eslint/eslint-plugin": "^2.21.0", 32 | "@typescript-eslint/parser": "^2.21.0", 33 | "eslint": "^6.8.0", 34 | "eslint-config-prettier": "^6.10.0", 35 | "eslint-plugin-jsdoc": "^22.0.0", 36 | "eslint-plugin-prettier": "^3.1.2", 37 | "eslint-plugin-react": "^7.18.3", 38 | "husky": "^3.0.0", 39 | "lerna": "^3.20.2", 40 | "lint-staged": "^10.0.8", 41 | "prettier": "^2.0.2", 42 | "rimraf": "^3.0.2", 43 | "typescript": "~3.8.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:jsdoc/recommended', 7 | 'plugin:prettier/recommended', 8 | 'plugin:react/recommended', 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2017, 14 | }, 15 | plugins: ['@typescript-eslint', 'jsdoc'], 16 | rules: { 17 | '@typescript-eslint/interface-name-prefix': [ 18 | 'error', 19 | { prefixWithI: 'always' }, 20 | ], 21 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | '@typescript-eslint/camelcase': 'off', 24 | '@typescript-eslint/no-namespace': 'off', 25 | '@typescript-eslint/no-use-before-define': 'off', 26 | '@typescript-eslint/quotes': [ 27 | 'error', 28 | 'single', 29 | { avoidEscape: true, allowTemplateLiterals: false }, 30 | ], 31 | curly: ['error', 'all'], 32 | 'jsdoc/require-param-type': 'off', 33 | 'jsdoc/require-property-type': 'off', 34 | 'jsdoc/require-returns-type': 'off', 35 | 'jsdoc/require-returns': 'off', 36 | 'jsdoc/no-types': 'warn', 37 | 'prefer-arrow-callback': 'error', 38 | }, 39 | settings: { 40 | jsdoc: { 41 | mode: 'typescript', 42 | }, 43 | react: { 44 | version: 'detect', 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, jupyterlab-system-monitor contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/system-monitor-base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyterlab-system-monitor-base", 3 | "version": "0.8.0", 4 | "description": "Base components for the jupyterlab-system-monitor extension", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab" 8 | ], 9 | "homepage": "https://github.com/jtpio/jupyterlab-system-monitor", 10 | "bugs": { 11 | "url": "https://github.com/jtpio/jupyterlab-system-monitor/issues" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Jeremy Tuloup", 15 | "files": [ 16 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 17 | "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 18 | "schema/*.json" 19 | ], 20 | "main": "lib/index.js", 21 | "style:": "lib/index.css", 22 | "types": "lib/index.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/jtpio/jupyterlab-system-monitor.git" 26 | }, 27 | "scripts": { 28 | "build": "tsc", 29 | "build:prod": "tsc", 30 | "link": "jupyter labextension link . --no-build", 31 | "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo", 32 | "prepare": "npm run clean && npm run build", 33 | "watch": "tsc -w" 34 | }, 35 | "dependencies": { 36 | "@jupyterlab/apputils": "^3.0.0-rc.10", 37 | "@jupyterlab/services": "^6.0.0-rc.10", 38 | "@lumino/coreutils": "^1.5.3", 39 | "@lumino/polling": "^1.3.3", 40 | "react": "^17.0.1", 41 | "react-dom": "^17.0.1", 42 | "react-sparklines": "^1.7.0" 43 | }, 44 | "devDependencies": { 45 | "@types/react": "^17.0.0", 46 | "@types/react-dom": "^17.0.0", 47 | "@types/react-sparklines": "^1.7.0", 48 | "rimraf": "^3.0.2", 49 | "typescript": "~4.0.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/system-monitor-base/src/cpuView.tsx: -------------------------------------------------------------------------------- 1 | import { ReactWidget } from '@jupyterlab/apputils'; 2 | 3 | import React, { useEffect, useState, ReactElement } from 'react'; 4 | 5 | import { IndicatorComponent } from './indicator'; 6 | 7 | import { ResourceUsage } from './model'; 8 | 9 | /** 10 | * A CpuView component to display CPU usage. 11 | */ 12 | const CpuViewComponent = ({ 13 | model, 14 | label, 15 | }: { 16 | model: ResourceUsage.Model; 17 | label: string; 18 | }): ReactElement => { 19 | const [text, setText] = useState(''); 20 | const [values, setValues] = useState([]); 21 | 22 | const update = (): void => { 23 | const { cpuLimit, currentCpuPercent } = model; 24 | const newValues = model.values.map((value) => 25 | Math.min(1, value.cpuPercent / cpuLimit) 26 | ); 27 | const newText = `${(currentCpuPercent * 100).toFixed(0)}%`; 28 | setText(newText); 29 | setValues(newValues); 30 | }; 31 | 32 | useEffect(() => { 33 | model.changed.connect(update); 34 | return (): void => { 35 | model.changed.disconnect(update); 36 | }; 37 | }, [model]); 38 | 39 | return ( 40 | 47 | ); 48 | }; 49 | 50 | /** 51 | * A namespace for CpuView statics. 52 | */ 53 | export namespace CpuView { 54 | /** 55 | * Create a new CpuView React Widget. 56 | * 57 | * @param model The resource usage model. 58 | * @param label The label next to the component. 59 | */ 60 | export const createCpuView = ( 61 | model: ResourceUsage.Model, 62 | label: string 63 | ): ReactWidget => { 64 | return ReactWidget.create(); 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /packages/system-monitor-base/src/memoryView.tsx: -------------------------------------------------------------------------------- 1 | import { ReactWidget } from '@jupyterlab/apputils'; 2 | 3 | import React, { useState, useEffect, ReactElement } from 'react'; 4 | 5 | import { IndicatorComponent } from './indicator'; 6 | 7 | import { ResourceUsage } from './model'; 8 | 9 | const MemoryViewComponent = ({ 10 | model, 11 | label, 12 | }: { 13 | model: ResourceUsage.Model; 14 | label: string; 15 | }): ReactElement => { 16 | const [text, setText] = useState(''); 17 | const [values, setValues] = useState([]); 18 | 19 | const update = (): void => { 20 | const { memoryLimit, currentMemory, units } = model; 21 | const precision = ['B', 'KB', 'MB'].indexOf(units) > 0 ? 0 : 2; 22 | const newText = `${currentMemory.toFixed(precision)} ${ 23 | memoryLimit ? '/ ' + memoryLimit.toFixed(precision) : '' 24 | } ${units}`; 25 | const newValues = model.values.map((value) => value.memoryPercent); 26 | setText(newText); 27 | setValues(newValues); 28 | }; 29 | 30 | useEffect(() => { 31 | model.changed.connect(update); 32 | return (): void => { 33 | model.changed.disconnect(update); 34 | }; 35 | }, [model]); 36 | 37 | return ( 38 | 45 | ); 46 | }; 47 | 48 | export namespace MemoryView { 49 | /** 50 | * Create a new MemoryView React Widget. 51 | * 52 | * @param model The resource usage model. 53 | * @param label The label next to the component. 54 | */ 55 | export const createMemoryView = ( 56 | model: ResourceUsage.Model, 57 | label: string 58 | ): ReactWidget => { 59 | return ReactWidget.create( 60 | 61 | ); 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.egg-info/ 5 | .ipynb_checkpoints 6 | *.tsbuildinfo 7 | .vscode 8 | jupyterlab-system-monitor/labextension 9 | 10 | # Created by https://www.gitignore.io/api/python 11 | # Edit at https://www.gitignore.io/?templates=python 12 | 13 | ### Python ### 14 | # Byte-compiled / optimized / DLL files 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # C extensions 20 | *.so 21 | 22 | # Distribution / packaging 23 | .Python 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | wheels/ 36 | pip-wheel-metadata/ 37 | share/python-wheels/ 38 | .installed.cfg 39 | *.egg 40 | MANIFEST 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .nox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | .hypothesis/ 63 | .pytest_cache/ 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | .spyproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | # Mr Developer 95 | .mr.developer.cfg 96 | .project 97 | .pydevproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | .dmypy.json 105 | dmypy.json 106 | 107 | # Pyre type checker 108 | .pyre/ 109 | 110 | # End of https://www.gitignore.io/api/python -------------------------------------------------------------------------------- /packages/system-monitor/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin, 4 | } from '@jupyterlab/application'; 5 | 6 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 7 | 8 | import { JSONObject } from '@lumino/coreutils'; 9 | 10 | import { ITopBar } from 'jupyterlab-topbar'; 11 | 12 | import { 13 | MemoryView, 14 | ResourceUsage, 15 | CpuView, 16 | } from 'jupyterlab-system-monitor-base'; 17 | 18 | import 'jupyterlab-system-monitor-base/style/index.css'; 19 | 20 | import '../style/index.css'; 21 | 22 | /** 23 | * The default refresh rate. 24 | */ 25 | const DEFAULT_REFRESH_RATE = 5000; 26 | 27 | /** 28 | * The default memory label. 29 | */ 30 | const DEFAULT_MEMORY_LABEL = 'Mem: '; 31 | 32 | /** 33 | * The default CPU label. 34 | */ 35 | const DEFAULT_CPU_LABEL = 'CPU: '; 36 | 37 | /** 38 | * An interface for resource settings. 39 | */ 40 | interface IResourceSettings extends JSONObject { 41 | label: string; 42 | } 43 | 44 | /** 45 | * Initialization data for the jupyterlab-system-monitor extension. 46 | */ 47 | const extension: JupyterFrontEndPlugin = { 48 | id: 'jupyterlab-system-monitor:plugin', 49 | autoStart: true, 50 | requires: [ITopBar], 51 | optional: [ISettingRegistry], 52 | activate: async ( 53 | app: JupyterFrontEnd, 54 | topBar: ITopBar, 55 | settingRegistry: ISettingRegistry 56 | ) => { 57 | let refreshRate = DEFAULT_REFRESH_RATE; 58 | let cpuLabel = DEFAULT_CPU_LABEL; 59 | let memoryLabel = DEFAULT_MEMORY_LABEL; 60 | 61 | if (settingRegistry) { 62 | const settings = await settingRegistry.load(extension.id); 63 | refreshRate = settings.get('refreshRate').composite as number; 64 | const cpuSettings = settings.get('cpu').composite as IResourceSettings; 65 | cpuLabel = cpuSettings.label; 66 | const memorySettings = settings.get('memory') 67 | .composite as IResourceSettings; 68 | memoryLabel = memorySettings.label; 69 | } 70 | 71 | const model = new ResourceUsage.Model({ refreshRate }); 72 | await model.refresh(); 73 | if (model.cpuAvailable) { 74 | const cpu = CpuView.createCpuView(model, cpuLabel); 75 | topBar.addItem('cpu', cpu); 76 | } 77 | const memory = MemoryView.createMemoryView(model, memoryLabel); 78 | topBar.addItem('memory', memory); 79 | }, 80 | }; 81 | 82 | export default extension; 83 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupyterlab-system-monitor setup 3 | """ 4 | import os 5 | 6 | from jupyter_packaging import ( 7 | create_cmdclass, install_npm, ensure_targets, 8 | combine_commands, get_version, skip_if_exists 9 | ) 10 | import setuptools 11 | 12 | HERE = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | # The name of the project 15 | name="jupyterlab-system-monitor" 16 | 17 | version = get_version(os.path.join(name, "_version.py")) 18 | 19 | lab_path = os.path.join(HERE, name, "labextension") 20 | 21 | # Representative files that should exist after a successful build 22 | jstargets = [ 23 | os.path.join(lab_path, "package.json"), 24 | ] 25 | 26 | package_data_spec = { 27 | name: [ 28 | "*" 29 | ] 30 | } 31 | 32 | labext_name = "jupyterlab-system-monitor" 33 | 34 | data_files_spec = [ 35 | ("share/jupyter/labextensions/%s" % labext_name, lab_path, "**"), 36 | ("share/jupyter/labextensions/%s" % labext_name, HERE, "install.json"), 37 | ] 38 | 39 | cmdclass = create_cmdclass("jsdeps", 40 | package_data_spec=package_data_spec, 41 | data_files_spec=data_files_spec 42 | ) 43 | 44 | js_command = combine_commands( 45 | install_npm(HERE, build_cmd="build:prod", npm=["jlpm"]), 46 | ensure_targets(jstargets), 47 | ) 48 | 49 | is_repo = os.path.exists(os.path.join(HERE, ".git")) 50 | if is_repo: 51 | cmdclass["jsdeps"] = js_command 52 | else: 53 | cmdclass["jsdeps"] = skip_if_exists(jstargets, js_command) 54 | 55 | with open("README.md", "r") as fh: 56 | long_description = fh.read() 57 | 58 | setup_args = dict( 59 | name=name, 60 | version=version, 61 | url="https://github.com/jtpio/jupyterlab-system-monitor.git", 62 | author="Jeremy Tuloup", 63 | description="JupyterLab extension to display system information", 64 | long_description= long_description, 65 | long_description_content_type="text/markdown", 66 | cmdclass= cmdclass, 67 | packages=setuptools.find_packages(), 68 | install_requires=[ 69 | "jupyterlab~=3.0", 70 | "jupyterlab-topbar~=0.6", 71 | "jupyter-resource-usage~=0.5" 72 | ], 73 | zip_safe=False, 74 | include_package_data=True, 75 | python_requires=">=3.6", 76 | license="BSD-3-Clause", 77 | platforms="Linux, Mac OS X, Windows", 78 | keywords=["Jupyter", "JupyterLab"], 79 | classifiers=[ 80 | "License :: OSI Approved :: BSD License", 81 | "Programming Language :: Python", 82 | "Programming Language :: Python :: 3", 83 | "Programming Language :: Python :: 3.6", 84 | "Programming Language :: Python :: 3.7", 85 | "Programming Language :: Python :: 3.8", 86 | "Framework :: Jupyter", 87 | ], 88 | ) 89 | 90 | 91 | if __name__ == "__main__": 92 | setuptools.setup(**setup_args) 93 | -------------------------------------------------------------------------------- /packages/system-monitor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyterlab-system-monitor", 3 | "version": "0.8.0", 4 | "description": "JupyterLab extension to display system information", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/jtpio/jupyterlab-system-monitor", 11 | "bugs": { 12 | "url": "https://github.com/jtpio/jupyterlab-system-monitor/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Jeremy Tuloup", 16 | "files": [ 17 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 18 | "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 19 | "schema/*.json" 20 | ], 21 | "main": "lib/index.js", 22 | "types": "lib/index.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/jtpio/jupyterlab-system-monitor.git" 26 | }, 27 | "scripts": { 28 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 29 | "build:labextension": "jupyter labextension build .", 30 | "build:labextension:dev": "jupyter labextension build --development True .", 31 | "build:lib": "tsc", 32 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 33 | "clean": "jlpm run clean:lib", 34 | "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", 35 | "clean:labextension": "rimraf jupyterlab-system-monitor/labextension", 36 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 37 | "eslint": "eslint . --ext .ts,.tsx --fix", 38 | "eslint:check": "eslint . --ext .ts,.tsx", 39 | "prepare": "jlpm run clean && jlpm run build:prod", 40 | "watch": "run-p watch:src watch:labextension", 41 | "watch:labextension": "jupyter labextension watch .", 42 | "watch:src": "tsc -w" 43 | }, 44 | "dependencies": { 45 | "@jupyterlab/application": "^3.0.0-rc.10", 46 | "@jupyterlab/settingregistry": "^3.0.0-rc.10", 47 | "jupyterlab-system-monitor-base": "^0.8.0", 48 | "@lumino/coreutils": "^1.5.3", 49 | "@lumino/signaling": "^1.4.3", 50 | "jupyterlab-topbar": "^0.6.0" 51 | }, 52 | "devDependencies": { 53 | "@jupyterlab/builder": "^3.0.0-rc.10", 54 | "rimraf": "^3.0.2", 55 | "typescript": "~4.0.3" 56 | }, 57 | "jupyterlab": { 58 | "extension": true, 59 | "schemaDir": "schema", 60 | "discovery": { 61 | "server": { 62 | "managers": [ 63 | "pip", 64 | "conda" 65 | ], 66 | "base": { 67 | "name": "jupyter-resource-usage" 68 | } 69 | } 70 | }, 71 | "outputDir": "../../jupyterlab-system-monitor/labextension", 72 | "sharedPackages": { 73 | "jupyterlab-topbar": { 74 | "bundled": false, 75 | "singleton": true 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/system-monitor-base/src/indicator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, ReactElement } from 'react'; 2 | 3 | import { Sparklines, SparklinesLine, SparklinesSpots } from 'react-sparklines'; 4 | 5 | /** 6 | * A indicator fill component. 7 | */ 8 | const IndicatorFiller = ({ 9 | percentage, 10 | color, 11 | }: { 12 | percentage: number; 13 | color: string; 14 | }): ReactElement => { 15 | return ( 16 |
23 | ); 24 | }; 25 | 26 | /** 27 | * An indicator bar component. 28 | */ 29 | const IndicatorBar = ({ 30 | values, 31 | percentage, 32 | baseColor, 33 | }: { 34 | values: number[]; 35 | percentage: number; 36 | baseColor: string; 37 | }): ReactElement => { 38 | const [isSparklines, setIsSparklines] = useState(false); 39 | 40 | const toggleSparklines = (): void => { 41 | setIsSparklines(!isSparklines); 42 | }; 43 | 44 | const color = 45 | percentage > 0.5 ? (percentage > 0.8 ? 'red' : 'orange') : baseColor; 46 | 47 | return ( 48 |
toggleSparklines()}> 49 | {isSparklines && ( 50 | 57 | 65 | 66 | 67 | )} 68 | {!isSparklines && ( 69 | 70 | )} 71 |
72 | ); 73 | }; 74 | 75 | /** 76 | * An incicator component for displaying resource usage. 77 | * 78 | */ 79 | export const IndicatorComponent = ({ 80 | enabled, 81 | values, 82 | label, 83 | color, 84 | text, 85 | }: { 86 | enabled: boolean; 87 | values: number[]; 88 | label: string; 89 | color: string; 90 | text: string; 91 | }): ReactElement => { 92 | const percentage = values[values.length - 1]; 93 | return ( 94 | enabled && ( 95 |
96 |
{label}
97 | {percentage !== null && ( 98 |
99 | 104 |
105 | )} 106 |
{text}
107 |
108 | ) 109 | ); 110 | }; 111 | -------------------------------------------------------------------------------- /.github/workflows/packaging.yml: -------------------------------------------------------------------------------- 1 | name: Packaging 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: '*' 8 | 9 | env: 10 | PIP_DISABLE_PIP_VERSION_CHECK: 1 11 | 12 | defaults: 13 | run: 14 | shell: bash -l {0} 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | - name: Install node 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: '14.x' 26 | - name: Install Python 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: '3.9' 30 | architecture: 'x64' 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip wheel 34 | python -m pip install setuptools jupyter_packaging "jupyterlab>=3,<4" 35 | - name: Build pypi distributions 36 | run: | 37 | python setup.py sdist bdist_wheel 38 | - name: Build npm distributions 39 | run: | 40 | jlpm lerna exec -- npm pack 41 | cp packages/*/*.tgz dist 42 | - name: Build checksum file 43 | run: | 44 | cd dist 45 | sha256sum * | tee SHA256SUMS 46 | - name: Upload distributions 47 | uses: actions/upload-artifact@v2 48 | with: 49 | name: dist ${{ github.run_number }} 50 | path: ./dist 51 | 52 | install: 53 | runs-on: ${{ matrix.os }}-latest 54 | needs: [build] 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | os: [ubuntu, macos, windows] 59 | python: ['3.6', '3.9'] 60 | include: 61 | - python: '3.6' 62 | dist: 'jupyterlab-system-monitor*.tar.gz' 63 | - python: '3.9' 64 | dist: 'jupyterlab_system_monitor*.whl' 65 | - os: windows 66 | py_cmd: python 67 | - os: macos 68 | py_cmd: python3 69 | - os: ubuntu 70 | py_cmd: python 71 | steps: 72 | - name: Install Python 73 | uses: actions/setup-python@v2 74 | with: 75 | python-version: ${{ matrix.python }} 76 | architecture: 'x64' 77 | - uses: actions/download-artifact@v2 78 | with: 79 | name: dist ${{ github.run_number }} 80 | path: ./dist 81 | - name: Install the prerequisites 82 | run: | 83 | ${{ matrix.py_cmd }} -m pip install pip wheel 84 | - name: Install the package 85 | run: | 86 | cd dist 87 | ${{ matrix.py_cmd }} -m pip install -vv ${{ matrix.dist }} 88 | - name: Validate environment 89 | run: | 90 | ${{ matrix.py_cmd }} -m pip freeze 91 | ${{ matrix.py_cmd }} -m pip check 92 | - name: Validate the install 93 | run: | 94 | jupyter labextension list 95 | jupyter labextension list 2>&1 | grep -ie "jupyterlab-system-monitor.*enabled.*ok" - 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JupyterLab System Monitor 2 | 3 | ![Github Actions Status](https://github.com/jtpio/jupyterlab-system-monitor/workflows/Build/badge.svg) 4 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jtpio/jupyterlab-system-monitor/stable?urlpath=lab) 5 | [![PyPI](https://img.shields.io/pypi/v/jupyterlab-system-monitor.svg)](https://pypi.org/project/jupyterlab-system-monitor) 6 | 7 | ## ⚠️ Archived ⚠️ 8 | 9 | This repository is now **archived**. 10 | 11 | The Top Bar components have been moved to the Jupyter Resource Usage extension: https://github.com/jupyter-server/jupyter-resource-usage 12 | 13 | If you are using JupyterLab 4+, install with: 14 | 15 | ``` 16 | pip install jupyter-resource-usage 17 | ``` 18 | 19 | If you are using JupyterLab 3, you can still install `jupyterlab-system-monitor` but the extension is not maintained anymore. 20 | 21 | --- 22 | 23 | JupyterLab extension to display system information (memory and cpu usage). 24 | 25 | Provides an alternative frontend for the `jupyter-resource-usage` metrics: [https://github.com/jupyter-server/jupyter-resource-usage](https://github.com/jupyter-server/jupyter-resource-usage) 26 | 27 | ![screencast](./doc/screencast.gif) 28 | 29 | This extension was originally developed as part of the [jupyterlab-topbar](https://github.com/jtpio/jupyterlab-topbar) project, and extracted into its own repository later on. 30 | 31 | ## TODO 32 | 33 | - Add Network I/O 34 | - Expose more settings 35 | 36 | ## Prerequisites 37 | 38 | - JupyterLab 1.0+ 39 | - Node.js 40 | 41 | ## Installation 42 | 43 | This extension requires the `jupyter-resource-usage` package and the `jupyterlab-topbar-extension` extension for JupyterLab. 44 | 45 | **Note: This extension is not compatible with `nbresuse==0.3.4`**. 46 | 47 | Starting from JupyterLab 3.0, extensions can be distributed as a Python package. Installation instructions will differ depending on your version of JupyterLab: 48 | 49 | ### JupyterLab 3.x 50 | 51 | ```bash 52 | pip install jupyterlab-system-monitor 53 | ``` 54 | 55 | ### JupyterLab 2.x 56 | 57 | ```bash 58 | pip install nbresuse 59 | jupyter labextension install jupyterlab-topbar-extension jupyterlab-system-monitor 60 | ``` 61 | 62 | `nbresuse` can also be installed with `conda`: 63 | 64 | ```bash 65 | conda install -c conda-forge nbresuse 66 | ``` 67 | 68 | Note: Node.js is required to install JupyterLab extensions. It can be installed with `conda`: 69 | 70 | ```bash 71 | conda install -c conda-forge nodejs 72 | ``` 73 | 74 | ## Configuration 75 | 76 | ### Graphic Display 77 | 78 | You can set the memory and cpu limits (but not enforce it) to display the indicator in the top bar. 79 | 80 | For more info, check the [memory limit](https://github.com/jupyter-server/jupyter-resource-usage#memory-limit) in the [nbresuse](https://github.com/jupyter-server/jupyter-resource-usage) repository. 81 | 82 | Edit `~/.jupyter/jupyter_notebook_config.py` (note: see [here](https://jupyter-notebook.readthedocs.io/en/stable/config.html#config-file-and-command-line-options) if you do not have a config file: 83 | 84 | ```python 85 | c = get_config() 86 | 87 | # memory 88 | c.ResourceUseDisplay.mem_limit = *1024*1024*1024 89 | 90 | # cpu 91 | c.ResourceUseDisplay.track_cpu_percent = True 92 | c.ResourceUseDisplay.cpu_limit = 93 | ``` 94 | 95 | For example: 96 | 97 | ```python 98 | c.ResourceUseDisplay.mem_limit = 4294967296 99 | c.ResourceUseDisplay.track_cpu_percent = True 100 | c.ResourceUseDisplay.cpu_limit = 2 101 | ``` 102 | 103 | Or use the command line option: 104 | 105 | ```bash 106 | # POSIX shell 107 | jupyter lab --NotebookApp.ResourceUseDisplay.mem_limit=$(( size_in_GB *1024*1024*1024)) \ 108 | --NotebookApp.ResourceUseDisplay.track_cpu_percent=True \ 109 | --NotebookApp.ResourceUseDisplay.cpu_limit=$(( number_of_cpus )) 110 | ``` 111 | 112 | ### Advanced Settings 113 | 114 | You can change the label and refresh rate in JupyterLab's advanced settings editor: 115 | 116 | ![jupyterlab_setting](./doc/setting.png) 117 | 118 | ### Usage with Docker and containers 119 | 120 | When this extension is running when Jupyter is hosted in a Docker container you will need to make some small adjustments. By default `psutil` reads the resources available to the entire system e.g. `/proc/meminfo`. However Docker uses cgroups to limit what resources are available to the container. Running in a container does not have an impact on the memory or CPU usage numbers, only the limits. The extension looks at all the running processes and sums up their usage. As Docker can not see processes outside the container this number will be correct. 121 | 122 | If you are launching your Docker container with the `--cpus` and `--memory` options the easiest solution is to just pass these same values as environment variables and read them from inside the container and set limits as described in the previous configuration section. e.g. 123 | 124 | ``` 125 | docker run --cpus=4 --memory=16g -e CPU_LIMIT=4 -e MEM_LIMIT=16g 126 | ``` 127 | 128 | If you do not have access to these values you can get the memory limit via the cgroups file: `/sys/fs/cgroup/memory/memory.limit_in_bytes`. The CPU limit is much more difficult and highly depends on your use case. You will need to consult with the admin who is running your container in order to determine what CPU restrictions have been put in place. 129 | 130 | ## Troubleshooting 131 | 132 | If you are experiencing issues with the memory and cpu indicators not being displayed, make sure to check the [nbresuse changelog](https://github.com/jupyter-server/jupyter-resource-usage/blob/master/CHANGELOG.md) for any breaking changes from major releases. 133 | 134 | ## Development 135 | 136 | ```bash 137 | # create a new conda environment 138 | conda create -n jupyterlab-system-monitor -c conda-forge jupyterlab nodejs nbresuse 139 | conda activate jupyterlab-system-monitor 140 | 141 | # Install dependencies 142 | jlpm 143 | 144 | # Install the package in development mode 145 | pip install -e . 146 | 147 | # Link your development version of the extension with JupyterLab 148 | jlpm run develop 149 | 150 | # Rebuild extension TypeScript source after making changes 151 | jlpm run build 152 | ``` 153 | 154 | ### Uninstall 155 | 156 | ```bash 157 | pip uninstall jupyterlab-system-monitor 158 | ``` 159 | -------------------------------------------------------------------------------- /packages/system-monitor-base/src/model.ts: -------------------------------------------------------------------------------- 1 | // Some parts of this code is adapted from: 2 | // https://github.com/jupyterlab/jupyterlab/blob/22cbc926e59443c67a80fcd363bb2de653087910/packages/statusbar/src/defaults/memoryUsage.tsx 3 | // Copyright (c) Jupyter Development Team. 4 | // Distributed under the terms of the Modified BSD License. 5 | 6 | import { URLExt } from '@jupyterlab/coreutils'; 7 | 8 | import { ServerConnection } from '@jupyterlab/services'; 9 | 10 | import { Poll } from '@lumino/polling'; 11 | 12 | import { ISignal, Signal } from '@lumino/signaling'; 13 | 14 | /** 15 | * Number of values to keep in memory. 16 | */ 17 | const N_BUFFER = 20; 18 | 19 | /** 20 | * A namespace for ResourcUsage statics. 21 | */ 22 | export namespace ResourceUsage { 23 | /** 24 | * A model for the resource usage items. 25 | */ 26 | export class Model { 27 | /** 28 | * Construct a new resource usage model. 29 | * 30 | * @param options The options for creating the model. 31 | */ 32 | constructor(options: Model.IOptions) { 33 | for (let i = 0; i < N_BUFFER; i++) { 34 | this._values.push({ memoryPercent: 0, cpuPercent: 0 }); 35 | } 36 | this._poll = new Poll({ 37 | factory: (): Promise => Private.factory(), 38 | frequency: { 39 | interval: options.refreshRate, 40 | backoff: true, 41 | }, 42 | name: 'jupyterlab-system-monitor:ResourceUsage#metrics', 43 | }); 44 | this._poll.ticked.connect((poll) => { 45 | const { payload, phase } = poll.state; 46 | if (phase === 'resolved') { 47 | this._updateMetricsValues(payload); 48 | return; 49 | } 50 | if (phase === 'rejected') { 51 | const oldMemoryAvailable = this._memoryAvailable; 52 | const oldCpuAvailable = this._cpuAvailable; 53 | this._memoryAvailable = false; 54 | this._cpuAvailable = false; 55 | this._currentMemory = 0; 56 | this._memoryLimit = null; 57 | this._cpuLimit = null; 58 | this._units = 'B'; 59 | 60 | if (oldMemoryAvailable || oldCpuAvailable) { 61 | this._changed.emit(); 62 | } 63 | return; 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * A promise that resolves after the next request. 70 | */ 71 | async refresh(): Promise { 72 | await this._poll.refresh(); 73 | await this._poll.tick; 74 | } 75 | 76 | /** 77 | * Whether the memory metric is available. 78 | */ 79 | get memoryAvailable(): boolean { 80 | return this._memoryAvailable; 81 | } 82 | 83 | /** 84 | * Whether the cpu metric is available. 85 | */ 86 | get cpuAvailable(): boolean { 87 | return this._cpuAvailable; 88 | } 89 | 90 | /** 91 | * The current memory usage. 92 | */ 93 | get currentMemory(): number { 94 | return this._currentMemory; 95 | } 96 | 97 | /** 98 | * The current memory limit, or null if not specified. 99 | */ 100 | get memoryLimit(): number | null { 101 | return this._memoryLimit; 102 | } 103 | 104 | /** 105 | * The current cpu limit, or null if not specified. 106 | */ 107 | get cpuLimit(): number | null { 108 | return this._cpuLimit; 109 | } 110 | 111 | /** 112 | * The units for memory usages and limits. 113 | */ 114 | get units(): MemoryUnit { 115 | return this._units; 116 | } 117 | 118 | /** 119 | * The current cpu percent. 120 | */ 121 | get currentCpuPercent(): number { 122 | return this._currentCpuPercent; 123 | } 124 | 125 | /** 126 | * A signal emitted when the resource usage model changes. 127 | */ 128 | get changed(): ISignal { 129 | return this._changed; 130 | } 131 | 132 | /** 133 | * Get a list of the last metric values. 134 | */ 135 | get values(): Model.IMetricValue[] { 136 | return this._values; 137 | } 138 | 139 | /** 140 | * Dispose of the memory usage model. 141 | */ 142 | dispose(): void { 143 | this._poll.dispose(); 144 | } 145 | 146 | /** 147 | * Given the results of the metrics request, update model values. 148 | * 149 | * @param value The metric request result. 150 | */ 151 | private _updateMetricsValues( 152 | value: Private.IMetricRequestResult | null 153 | ): void { 154 | if (value === null) { 155 | this._memoryAvailable = false; 156 | this._cpuAvailable = false; 157 | this._currentMemory = 0; 158 | this._memoryLimit = null; 159 | this._units = 'B'; 160 | return; 161 | } 162 | 163 | const numBytes = value.rss; 164 | const memoryLimit = value.limits.memory ? value.limits.memory.rss : null; 165 | const [currentMemory, units] = Private.convertToLargestUnit(numBytes); 166 | this._memoryAvailable = numBytes !== undefined; 167 | this._currentMemory = currentMemory; 168 | this._units = units; 169 | this._memoryLimit = memoryLimit 170 | ? memoryLimit / Private.MEMORY_UNIT_LIMITS[units] 171 | : null; 172 | 173 | const memoryPercent = this.memoryLimit 174 | ? Math.min(this._currentMemory / this.memoryLimit, 1) 175 | : null; 176 | 177 | const cpuPercent = value.cpu_percent; 178 | this._cpuLimit = value.limits.cpu ? value.limits.cpu.cpu : 1; 179 | this._cpuAvailable = cpuPercent !== undefined; 180 | this._currentCpuPercent = this._cpuAvailable ? cpuPercent / 100 : 0; 181 | 182 | this._values.push({ memoryPercent, cpuPercent: this._currentCpuPercent }); 183 | this._values.shift(); 184 | this._changed.emit(void 0); 185 | } 186 | 187 | private _memoryAvailable = false; 188 | private _cpuAvailable = false; 189 | private _currentMemory = 0; 190 | private _currentCpuPercent = 0; 191 | private _memoryLimit: number | null = null; 192 | private _cpuLimit: number | null = null; 193 | private _poll: Poll; 194 | private _units: MemoryUnit = 'B'; 195 | private _changed = new Signal(this); 196 | private _values: Model.IMetricValue[] = []; 197 | } 198 | 199 | /** 200 | * A namespace for Model statics. 201 | */ 202 | export namespace Model { 203 | /** 204 | * Options for creating a ResourceUsage model. 205 | */ 206 | export interface IOptions { 207 | /** 208 | * The refresh rate (in ms) for querying the server. 209 | */ 210 | refreshRate: number; 211 | } 212 | 213 | /** 214 | * An interface for metric values. 215 | */ 216 | export interface IMetricValue { 217 | /** 218 | * The memory percentage. 219 | */ 220 | memoryPercent: number | null; 221 | 222 | /** 223 | * The cpu percentage. 224 | */ 225 | cpuPercent: number | null; 226 | } 227 | } 228 | 229 | /** 230 | * The type of unit used for reporting memory usage. 231 | */ 232 | export type MemoryUnit = 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB'; 233 | } 234 | 235 | /** 236 | * A namespace for module private statics. 237 | */ 238 | namespace Private { 239 | /** 240 | * The number of decimal places to use when rendering memory usage. 241 | */ 242 | export const DECIMAL_PLACES = 2; 243 | 244 | /** 245 | * The number of bytes in each memory unit. 246 | */ 247 | export const MEMORY_UNIT_LIMITS: { 248 | readonly [U in ResourceUsage.MemoryUnit]: number; 249 | } = { 250 | B: 1, 251 | KB: 1024, 252 | MB: 1048576, 253 | GB: 1073741824, 254 | TB: 1099511627776, 255 | PB: 1125899906842624, 256 | }; 257 | 258 | /** 259 | * Given a number of bytes, convert to the most human-readable 260 | * format, (GB, TB, etc). 261 | * 262 | * @param numBytes The number of bytes. 263 | */ 264 | export const convertToLargestUnit = ( 265 | numBytes: number 266 | ): [number, ResourceUsage.MemoryUnit] => { 267 | if (numBytes < MEMORY_UNIT_LIMITS.KB) { 268 | return [numBytes, 'B']; 269 | } else if ( 270 | MEMORY_UNIT_LIMITS.KB === numBytes || 271 | numBytes < MEMORY_UNIT_LIMITS.MB 272 | ) { 273 | return [numBytes / MEMORY_UNIT_LIMITS.KB, 'KB']; 274 | } else if ( 275 | MEMORY_UNIT_LIMITS.MB === numBytes || 276 | numBytes < MEMORY_UNIT_LIMITS.GB 277 | ) { 278 | return [numBytes / MEMORY_UNIT_LIMITS.MB, 'MB']; 279 | } else if ( 280 | MEMORY_UNIT_LIMITS.GB === numBytes || 281 | numBytes < MEMORY_UNIT_LIMITS.TB 282 | ) { 283 | return [numBytes / MEMORY_UNIT_LIMITS.GB, 'GB']; 284 | } else if ( 285 | MEMORY_UNIT_LIMITS.TB === numBytes || 286 | numBytes < MEMORY_UNIT_LIMITS.PB 287 | ) { 288 | return [numBytes / MEMORY_UNIT_LIMITS.TB, 'TB']; 289 | } else { 290 | return [numBytes / MEMORY_UNIT_LIMITS.PB, 'PB']; 291 | } 292 | }; 293 | 294 | /** 295 | * Settings for making requests to the server. 296 | */ 297 | const SERVER_CONNECTION_SETTINGS = ServerConnection.makeSettings(); 298 | 299 | /** 300 | * The url endpoint for making requests to the server. 301 | */ 302 | const METRIC_URL = URLExt.join( 303 | SERVER_CONNECTION_SETTINGS.baseUrl, 304 | 'api/metrics/v1' 305 | ); 306 | 307 | /** 308 | * The shape of a response from the metrics server extension. 309 | */ 310 | export interface IMetricRequestResult { 311 | rss: number; 312 | cpu_percent?: number; 313 | cpu_count?: number; 314 | limits: { 315 | memory?: { 316 | rss: number; 317 | warn?: number; 318 | }; 319 | cpu?: { 320 | cpu: number; 321 | warn?: number; 322 | }; 323 | }; 324 | } 325 | 326 | /** 327 | * Make a request to the backend. 328 | */ 329 | export const factory = async (): Promise => { 330 | const request = ServerConnection.makeRequest( 331 | METRIC_URL, 332 | {}, 333 | SERVER_CONNECTION_SETTINGS 334 | ); 335 | const response = await request; 336 | 337 | if (response.ok) { 338 | return await response.json(); 339 | } 340 | 341 | return null; 342 | }; 343 | } 344 | --------------------------------------------------------------------------------