diff --git a/package-lock.json b/package-lock.json index fcf2c41cb2..dedc21969c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39264,6 +39264,7 @@ "version": "0.15.5", "license": "Apache-2.0", "dependencies": { + "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", "@deephaven/icons": "file:../icons", "@deephaven/jsapi-shim": "file:../jsapi-shim", @@ -41095,6 +41096,7 @@ "@deephaven/console": { "version": "file:packages/console", "requires": { + "@deephaven/chart": "file:../chart", "@deephaven/components": "file:../components", "@deephaven/icons": "file:../icons", "@deephaven/jsapi-shim": "file:../jsapi-shim", diff --git a/packages/chart/src/index.ts b/packages/chart/src/index.ts index 6881d48b4d..a5d794f7bb 100644 --- a/packages/chart/src/index.ts +++ b/packages/chart/src/index.ts @@ -5,4 +5,5 @@ export { default as ChartUtils } from './ChartUtils'; export { default as FigureChartModel } from './FigureChartModel'; export { default as MockChartModel } from './MockChartModel'; export { default as Plot } from './plotly/Plot'; +export { default as ChartTheme } from './ChartTheme'; export { default as isFigureChartModel } from './isFigureChartModel'; diff --git a/packages/components/src/popper/Tooltip.tsx b/packages/components/src/popper/Tooltip.tsx index 3ca824ee81..34c1a9605e 100644 --- a/packages/components/src/popper/Tooltip.tsx +++ b/packages/components/src/popper/Tooltip.tsx @@ -13,6 +13,7 @@ type TooltipProps = typeof Tooltip.defaultProps & { reshowTimeout?: number; timeout?: number; referenceObject?: ReferenceObject | null; + onEntered?: () => void; onExited?: () => void; 'data-testid'?: string; }; @@ -49,6 +50,7 @@ class Tooltip extends Component { popperClassName: '', reshowTimeout: Tooltip.defaultReshowTimeout, timeout: Tooltip.defaultTimeout, + onEntered: (): void => undefined, onExited: (): void => undefined, 'data-testid': undefined, }; @@ -293,6 +295,7 @@ class Tooltip extends Component { referenceObject, popperClassName, 'data-testid': dataTestId, + onEntered, } = this.props; const { isShown } = this.state; return ( @@ -305,6 +308,7 @@ class Tooltip extends Component { className={classNames(popperClassName)} options={options} ref={this.popper} + onEntered={onEntered} onExited={this.handleExited} interactive={interactive} referenceObject={referenceObject} diff --git a/packages/console/package.json b/packages/console/package.json index 02c9c6db5c..1e7f04bc89 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -36,6 +36,7 @@ "@deephaven/log": "file:../log", "@deephaven/storage": "file:../storage", "@deephaven/utils": "file:../utils", + "@deephaven/chart": "file:../chart", "@fortawesome/react-fontawesome": "^0.1.18", "classnames": "^2.3.1", "lodash.debounce": "^4.0.8", diff --git a/packages/console/src/HeapUsage.scss b/packages/console/src/HeapUsage.scss new file mode 100644 index 0000000000..ce8f23fb46 --- /dev/null +++ b/packages/console/src/HeapUsage.scss @@ -0,0 +1,60 @@ +@import '../../components/scss/custom.scss'; + +.max-memory { + position: relative; + color: $gray-300; + background-color: $gray-900; + border: $black; + width: 4.25rem; + vertical-align: middle; + text-align: center; + user-select: none; + z-index: 1; +} + +.total-memory, +.used-memory { + position: absolute; + inset: 0; + margin: 1px; + z-index: -1; +} + +.total-memory { + background-color: $gray-700; +} + +.used-memory { + background-color: $gray-500; +} + +.heap-overflow { + background-color: $red; +} + +.heap-tooltip { + padding: $spacer-1; + width: 200px; +} + +.heap-usage-info-row { + display: flex; + color: $foreground; + padding: $spacer-1; + justify-content: space-between; +} + +.heap-plot { + display: flex; + align-items: center; +} + +.heading-bottom-border { + border-bottom: 1px solid $gray-900; + margin-bottom: $spacer-1; +} + +.heap-utilisation-text { + margin-top: $spacer-1; + color: $gray-400; +} diff --git a/packages/console/src/HeapUsage.tsx b/packages/console/src/HeapUsage.tsx new file mode 100644 index 0000000000..83a4877951 --- /dev/null +++ b/packages/console/src/HeapUsage.tsx @@ -0,0 +1,202 @@ +import { Tooltip } from '@deephaven/components'; +import { QueryConnectable } from '@deephaven/jsapi-shim'; +import React, { useEffect, useState, ReactElement, useRef } from 'react'; +import { Plot, ChartTheme } from '@deephaven/chart'; +import './HeapUsage.scss'; +import classNames from 'classnames'; + +interface HeapUsageProps { + connection: QueryConnectable; + defaultUpdateInterval: number; + hoverUpdateInterval: number; + bgMonitoring?: boolean; + + // in millis + monitorDuration: number; +} + +const HeapUsage = ({ + connection, + defaultUpdateInterval, + hoverUpdateInterval, + bgMonitoring = true, + monitorDuration, +}: HeapUsageProps): ReactElement => { + const [memoryUsage, setMemoryUsage] = useState({ + freeMemory: 0, + maximumHeapSize: 999, + totalHeapSize: 0, + }); + + const [isOpen, setIsOpen] = useState(false); + + const historyUsage = useRef<{ timestamps: number[]; usages: number[] }>({ + timestamps: [], + usages: [], + }); + + useEffect( + function setUsageUpdateInterval() { + const fetchAndUpdate = async () => { + const newUsage = await connection.getWorkerHeapInfo(); + setMemoryUsage(newUsage); + + if (bgMonitoring || isOpen) { + const currentUsage = + (newUsage.totalHeapSize - newUsage.freeMemory) / + newUsage.maximumHeapSize; + const currentTime = Date.now(); + + const { timestamps, usages } = historyUsage.current; + while ( + timestamps.length !== 0 && + currentTime - timestamps[0] > monitorDuration * 1.5 + ) { + timestamps.shift(); + usages.shift(); + } + + timestamps.push(currentTime); + usages.push(currentUsage); + } else { + historyUsage.current = { timestamps: [], usages: [] }; + } + }; + fetchAndUpdate(); + + const updateUsage = setInterval( + fetchAndUpdate, + isOpen ? hoverUpdateInterval : defaultUpdateInterval + ); + return () => { + clearInterval(updateUsage); + }; + }, + [ + isOpen, + hoverUpdateInterval, + connection, + defaultUpdateInterval, + monitorDuration, + bgMonitoring, + ] + ); + + const toDecimalPlace = (num: number, dec: number) => + Math.round(num * 10 ** dec) / 10 ** dec; + + const decimalPlace = 2; + const GbToByte = 1024 ** 3; + + const { freeMemory, totalHeapSize, maximumHeapSize } = memoryUsage; + + const freeMemoryGB = toDecimalPlace(freeMemory / GbToByte, decimalPlace); + const totalHeapGB = toDecimalPlace(totalHeapSize / GbToByte, decimalPlace); + const maxHeapGB = toDecimalPlace(maximumHeapSize / GbToByte, decimalPlace); + const inUseGB = totalHeapGB - freeMemoryGB; + + const getRow = (text: string, size: string, bottomBorder = false) => ( +
+
{text}
+
{size}
+
+ ); + + const { timestamps, usages } = historyUsage.current; + + const lastTimestamp = timestamps[timestamps.length - 1] ?? 0; + + const totalPercentage = totalHeapSize / maximumHeapSize; + const usedPercentage = (totalHeapSize - freeMemory) / maximumHeapSize; + + return ( + <> +
+
+
0.95, + })} + style={{ + width: `calc(${usedPercentage * 100}% - ${usedPercentage * 2}px`, + }} + /> +
{maxHeapGB.toFixed(1)} GB
+
+ + setIsOpen(true)} + onExited={() => setIsOpen(false)} + interactive + > +
+ {getRow( + 'In use:', + `${inUseGB.toFixed(decimalPlace)} of ${maxHeapGB.toFixed( + decimalPlace + )} GB`, + true + )} + {getRow('Free:', `${freeMemoryGB.toFixed(decimalPlace)} GB`)} + {getRow('Total:', `${totalHeapGB.toFixed(decimalPlace)} GB`)} + {getRow('Max:', `${maxHeapGB.toFixed(decimalPlace)} GB`)} +
+ +
+
+ % utilization over {Math.round(monitorDuration / 1000 / 60)} min. +
+
+
+ + ); +}; + +export default HeapUsage; diff --git a/packages/console/src/index.ts b/packages/console/src/index.ts index 4ba3dd1feb..14792cffda 100644 --- a/packages/console/src/index.ts +++ b/packages/console/src/index.ts @@ -11,5 +11,6 @@ export * from './common'; export * from './command-history'; export * from './console-history'; export { default as LogView } from './log/LogView'; +export { default as HeapUsage } from './HeapUsage'; export { default } from './Console'; diff --git a/packages/console/tsconfig.json b/packages/console/tsconfig.json index f505b4009a..b6e06cced6 100644 --- a/packages/console/tsconfig.json +++ b/packages/console/tsconfig.json @@ -21,6 +21,9 @@ }, { "path": "../utils" + }, + { + "path": "../chart" } ] } diff --git a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx index 8b3fd86c11..73677fc9e7 100644 --- a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx @@ -8,6 +8,7 @@ import { CommandHistoryStorage, Console, ConsoleConstants, + HeapUsage, } from '@deephaven/console'; import { PanelEvent } from '@deephaven/dashboard'; import { IdeSession, VariableDefinition } from '@deephaven/jsapi-shim'; @@ -321,7 +322,7 @@ export class ConsolePanel extends PureComponent< unzip, } = this.props; const { consoleSettings, error, objectMap } = this.state; - const { config, session } = sessionWrapper; + const { config, session, connection } = sessionWrapper; const { id: sessionId, type: language } = config; return ( @@ -350,6 +351,16 @@ export class ConsolePanel extends PureComponent< <>
 
{ConsoleConstants.LANGUAGE_MAP.get(language)}
+
 
+
+ +
+
 
} scope={sessionId} diff --git a/packages/jsapi-shim/src/dh.types.ts b/packages/jsapi-shim/src/dh.types.ts index b1489d2c65..4d553c333c 100644 --- a/packages/jsapi-shim/src/dh.types.ts +++ b/packages/jsapi-shim/src/dh.types.ts @@ -876,7 +876,14 @@ export interface TableMap extends Evented { getTable(key: object): Promise; } +export interface WorkerHeapInfo { + readonly maximumHeapSize: number; + readonly freeMemory: number; + readonly totalHeapSize: number; +} + export interface QueryConnectable extends Evented { + getWorkerHeapInfo(): Promise; getConsoleTypes(): Promise; startSession(type: string): Promise; } diff --git a/packages/redux/src/actions.ts b/packages/redux/src/actions.ts index a67c40753f..c20b819dd0 100644 --- a/packages/redux/src/actions.ts +++ b/packages/redux/src/actions.ts @@ -1,6 +1,5 @@ import type { Action } from 'redux'; import type { ThunkAction } from 'redux-thunk'; -import type { CommandHistoryStorage } from '@deephaven/console'; import type { FileStorage } from '@deephaven/file-explorer'; import { SET_PLUGINS, @@ -42,7 +41,7 @@ export const setWorkspaceStorage: PayloadActionCreator = works payload: workspaceStorage, }); -export const setCommandHistoryStorage: PayloadActionCreator = commandHistoryStorage => ({ +export const setCommandHistoryStorage: PayloadActionCreator = commandHistoryStorage => ({ type: SET_COMMAND_HISTORY_STORAGE, payload: commandHistoryStorage, }); diff --git a/packages/redux/tsconfig.json b/packages/redux/tsconfig.json index fefd4491a1..aeb10cd324 100644 --- a/packages/redux/tsconfig.json +++ b/packages/redux/tsconfig.json @@ -10,9 +10,6 @@ { "path": "../components" }, - { - "path": "../console" - }, { "path": "../file-explorer" },