Skip to content

Commit dd38938

Browse files
committed
Experiment with resource monitor
1 parent f601be7 commit dd38938

File tree

4 files changed

+322
-9
lines changed

4 files changed

+322
-9
lines changed

site/components/ResourceMonitor.tsx

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import React from "react"
2+
import { Bar, Line } from "react-chartjs-2"
3+
import { Chart, ChartOptions } from "chart.js"
4+
5+
const multiply = {
6+
beforeDraw: function (chart: Chart, options: ChartOptions) {
7+
if (chart && chart.ctx) {
8+
chart.ctx.globalCompositeOperation = "multiply"
9+
}
10+
},
11+
afterDatasetsDraw: function (chart: Chart, options: ChartOptions) {
12+
if (chart && chart.ctx) {
13+
chart.ctx.globalCompositeOperation = "source-over"
14+
}
15+
},
16+
}
17+
18+
function formatBytes(bytes: number, decimals = 2) {
19+
if (bytes === 0) return "0 Bytes"
20+
21+
const k = 1024
22+
const dm = decimals < 0 ? 0 : decimals
23+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
24+
25+
const i = Math.floor(Math.log(bytes) / Math.log(k))
26+
27+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]
28+
}
29+
30+
const padding = 64
31+
32+
const opts: ChartOptions = {
33+
responsive: true,
34+
maintainAspectRatio: false,
35+
legend: {
36+
fullWidth: true,
37+
display: false,
38+
},
39+
elements: {
40+
point: {
41+
radius: 0,
42+
hitRadius: 8,
43+
hoverRadius: 8,
44+
},
45+
rectangle: {
46+
borderWidth: 0,
47+
},
48+
},
49+
layout: {
50+
padding: {
51+
top: padding,
52+
bottom: padding,
53+
},
54+
},
55+
tooltips: {
56+
mode: "index",
57+
axis: "y",
58+
cornerRadius: 8,
59+
borderWidth: 0,
60+
titleFontStyle: "normal",
61+
callbacks: {
62+
label: (item: any, data: any) => {
63+
const dataset = data.datasets[item.datasetIndex]
64+
const num: number = dataset.data[item.index] as number
65+
if (num) {
66+
return dataset.label + ": " + num.toFixed(2) + "%"
67+
}
68+
},
69+
labelColor: (item: any, data: any) => {
70+
const dataset = data.data.datasets[item.datasetIndex]
71+
return {
72+
// Trim off the transparent hex code.
73+
backgroundColor: (dataset.pointBackgroundColor as string).substr(0, 7),
74+
borderColor: "#000000",
75+
}
76+
},
77+
title: (item) => {
78+
console.log(item[0])
79+
80+
return "Resources: " + item[0].label
81+
},
82+
},
83+
},
84+
plugins: {
85+
tooltip: {
86+
callbacks: {
87+
beforeTitle: (item: any) => {
88+
console.log("BEFORE TITLE: " + item)
89+
return "Resources"
90+
},
91+
},
92+
},
93+
legend: {
94+
display: false,
95+
},
96+
},
97+
scales: {
98+
xAxes: [
99+
{
100+
display: false,
101+
ticks: {
102+
stepSize: 10,
103+
maxTicksLimit: 4,
104+
maxRotation: 0,
105+
},
106+
},
107+
],
108+
yAxes: [
109+
{
110+
gridLines: {
111+
color: "rgba(0, 0, 0, 0.09)",
112+
zeroLineColor: "rgba(0, 0, 0, 0.09)",
113+
},
114+
ticks: {
115+
callback: (v) => v + "%",
116+
max: 100,
117+
maxTicksLimit: 2,
118+
min: 0,
119+
padding: 4,
120+
},
121+
},
122+
],
123+
},
124+
}
125+
126+
export interface ResourceUsageSnapshot {
127+
cpuPercentage: number
128+
memoryUsedBytes: number
129+
diskUsedBytes: number
130+
}
131+
132+
export interface ResourceMonitorProps {
133+
readonly diskTotalBytes: number
134+
readonly memoryTotalBytes: number
135+
readonly resources: ReadonlyArray<ResourceUsageSnapshot>
136+
}
137+
138+
export const ResourceMonitor: React.FC<ResourceMonitorProps> = (props) => {
139+
const dataF = React.useMemo(() => {
140+
return (canvas: any) => {
141+
// Store gradients inside the canvas object for easy access.
142+
// This function is called everytime resources values change...
143+
// we don't want to allocate a new gradient everytime.
144+
if (!canvas["cpuGradient"]) {
145+
const cpuGradient = canvas.getContext("2d").createLinearGradient(0, 0, 0, canvas.height)
146+
cpuGradient.addColorStop(1, "#9787FF32")
147+
cpuGradient.addColorStop(0, "#5555FFC4")
148+
canvas["cpuGradient"] = cpuGradient
149+
}
150+
151+
if (!canvas["memGradient"]) {
152+
const memGradient = canvas.getContext("2d").createLinearGradient(0, 0, 0, canvas.height)
153+
memGradient.addColorStop(1, "#55FF8532")
154+
memGradient.addColorStop(0, "#42B863C4")
155+
canvas["memGradient"] = memGradient
156+
}
157+
158+
if (!canvas["diskGradient"]) {
159+
const diskGradient = canvas.getContext("2d").createLinearGradient(0, 0, 0, canvas.height)
160+
diskGradient.addColorStop(1, "#97979700")
161+
diskGradient.addColorStop(0, "#797979C4")
162+
canvas["diskGradient"] = diskGradient
163+
}
164+
165+
const cpuPercentages = []
166+
//const cpuPercentages = Array(20 - props.resources.length).fill(null)
167+
cpuPercentages.push(...props.resources.map((r) => r.cpuPercentage))
168+
169+
//const memPercentages = Array(20 - props.resources.length).fill(null)
170+
const memPercentages = []
171+
memPercentages.push(...props.resources.map((r) => (r.memoryUsedBytes / props.memoryTotalBytes) * 100))
172+
173+
const diskPercentages = []
174+
//const diskPercentages = Array(20 - props.resources.length).fill(null)
175+
diskPercentages.push(...props.resources.map((r) => (r.diskUsedBytes / props.diskTotalBytes) * 100))
176+
177+
return {
178+
labels: Array(20)
179+
.fill(0)
180+
.map((_, index) => (20 - index) * 3 + "s ago"),
181+
datasets: [
182+
{
183+
label: "CPU",
184+
data: cpuPercentages,
185+
backgroundColor: canvas["cpuGradient"],
186+
borderColor: "transparent",
187+
pointBackgroundColor: "#9787FF32",
188+
pointBorderColor: "#FFFFFF",
189+
lineTension: 0.4,
190+
fill: true,
191+
},
192+
{
193+
label: "Memory",
194+
data: memPercentages,
195+
backgroundColor: canvas["memGradient"],
196+
borderColor: "transparent",
197+
pointBackgroundColor: "#55FF8532",
198+
pointBorderColor: "#FFFFFF",
199+
lineTension: 0.4,
200+
fill: true,
201+
},
202+
{
203+
label: "Disk",
204+
data: diskPercentages,
205+
backgroundColor: canvas["diskGradient"],
206+
borderColor: "transparent",
207+
pointBackgroundColor: "#97979732",
208+
pointBorderColor: "#FFFFFF",
209+
lineTension: 0.4,
210+
fill: true,
211+
},
212+
],
213+
}
214+
}
215+
}, [props.resources])
216+
217+
return (
218+
<Line
219+
type="line"
220+
height={40 + padding * 2}
221+
data={dataF}
222+
options={opts}
223+
plugins={[multiply]}
224+
ref={(ref) => {
225+
window.Chart.defaults.global.defaultFontFamily = "'Fira Code', Inter"
226+
}}
227+
/>
228+
)
229+
}

site/components/Workspace/Workspace.tsx

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import Paper from "@material-ui/core/Paper"
33
import { makeStyles } from "@material-ui/core/styles"
44
import Typography from "@material-ui/core/Typography"
55
import OpenInNewIcon from "@material-ui/icons/OpenInNew"
6-
import React, { useState } from "react"
6+
import React, { useEffect, useState } from "react"
77
import MoreVertIcon from "@material-ui/icons/MoreVert"
88
import { QuestionHelp } from "../QuestionHelp"
99
import { CircularProgress, IconButton, Link, Menu, MenuItem } from "@material-ui/core"
10-
10+
import { ResourceMonitor, ResourceMonitorProps, ResourceUsageSnapshot } from "../ResourceMonitor"
1111
import { Timeline as TestTimeline } from "../Timeline"
1212

1313
import * as API from "../../api"
14+
import { getAllByTestId } from "@testing-library/react"
1415

1516
export interface WorkspaceProps {
1617
workspace: API.Workspace
@@ -94,17 +95,17 @@ export const ResourceRow: React.FC<ResourceRowProps> = ({ icon, href, name, stat
9495
<MoreVertIcon fontSize="inherit" />
9596
</IconButton>
9697

97-
<Menu anchorEl={menuAnchorEl} open={!!menuAnchorEl} onClose={() => setMenuAnchorEl(undefined)}>
98+
<Menu anchorEl={menuAnchorEl} open={!!menuAnchorEl} onClose={() => setMenuAnchorEl(null)}>
9899
<MenuItem
99100
onClick={() => {
100-
setMenuAnchorEl(undefined)
101+
setMenuAnchorEl(null)
101102
}}
102103
>
103104
SSH
104105
</MenuItem>
105106
<MenuItem
106107
onClick={() => {
107-
setMenuAnchorEl(undefined)
108+
setMenuAnchorEl(null)
108109
}}
109110
>
110111
Remote Desktop
@@ -177,6 +178,36 @@ const TitleIconSize = 48
177178
export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => {
178179
const styles = useStyles()
179180

181+
const [resources, setResources] = useState<ResourceUsageSnapshot[]>([
182+
{
183+
cpuPercentage: 50,
184+
memoryUsedBytes: 8 * 1024 * 1024,
185+
diskUsedBytes: 24 * 1024 * 1024,
186+
},
187+
])
188+
189+
useEffect(() => {
190+
const rand = (range: number) => {
191+
return range * 2 * (Math.random() - 0.5)
192+
}
193+
const timeout = window.setTimeout(() => {
194+
setResources((res: ResourceUsageSnapshot[]) => {
195+
const latest = res[0]
196+
const newEntry = {
197+
cpuPercentage: latest.cpuPercentage + rand(5),
198+
memoryUsedBytes: latest.memoryUsedBytes + rand(256 * 1024),
199+
diskUsedBytes: latest.diskUsedBytes + rand(512 * 1024),
200+
}
201+
const ret: ResourceUsageSnapshot[] = [newEntry, ...res]
202+
return ret
203+
})
204+
}, 1000)
205+
206+
return () => {
207+
window.clearTimeout(timeout)
208+
}
209+
})
210+
180211
return (
181212
<div className={styles.root}>
182213
<Paper elevation={0} className={styles.section}>
@@ -193,6 +224,13 @@ export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => {
193224
</Typography>
194225
</div>
195226
</div>
227+
<div style={{ height: "200px", position: "relative" }}>
228+
<ResourceMonitor
229+
diskTotalBytes={256 * 1024 * 1024}
230+
memoryTotalBytes={16 * 1024 * 1024}
231+
resources={resources}
232+
/>
233+
</div>
196234
</Paper>
197235
<div className={styles.horizontal}>
198236
<div className={styles.sideBar}>
@@ -210,7 +248,7 @@ export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => {
210248
<ResourceRow name={"React App"} icon={"/static/react-icon.svg"} href={"placeholder"} status={"active"} />
211249
</div>
212250
</Paper>
213-
251+
214252
<Paper elevation={0} className={styles.section}>
215253
<Title>
216254
<Typography variant="h6">Resources</Typography>

site/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@material-ui/icons": "4.5.1",
2121
"@material-ui/lab": "4.0.0-alpha.60",
2222
"@testing-library/react": "12.1.2",
23+
"@types/chart.js": "^2.9.35",
2324
"@types/express": "4.17.13",
2425
"@types/jest": "27.4.0",
2526
"@types/node": "14.18.10",
@@ -28,6 +29,7 @@
2829
"@types/superagent": "4.1.15",
2930
"@typescript-eslint/eslint-plugin": "4.33.0",
3031
"@typescript-eslint/parser": "4.33.0",
32+
"chart.js": "2.9.4",
3133
"eslint": "7.32.0",
3234
"eslint-config-prettier": "8.3.0",
3335
"eslint-import-resolver-alias": "1.1.2",
@@ -48,6 +50,7 @@
4850
"next-router-mock": "^0.6.5",
4951
"prettier": "2.5.1",
5052
"react": "17.0.2",
53+
"react-chartjs-2": "2.11.2",
5154
"react-dom": "17.0.2",
5255
"sql-formatter": "^4.0.2",
5356
"swr": "1.2.0",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy