A simple javascript-all-the-way-down application for reporting and visualizing system load data in realtime.
If you aren't familiar with the tools used herein, feel free to peruse the loose assembly of reading & viewing materials at the bottom of the readme.
You might be wondering to yourself:
🐐 ?
Well, this is a load monitoring application, okay?
It has nothing to do with goats whatsoever. Don't be a silly-goat. Stop asking about the goats.
🐐 .
Big assumption here: You should build and run this app on a unixy system.
Since load average is a unixy concept, that seems reasonable, right?
Correct! 🎊
(Optionally, you may want to grab yourself a 🐐, but like I said, that's not what this application is about. Okay?)
From the project root, run:
cd backend && npm i
From the project root, run:
cd frontend && npm i
From the project root, run:
cd frontend && npm test
If all the tests pass, you should see a colorful and friendly message in the console. With that, you're good to goat! 🐐
Patience! this might take a few seconds.
From the project root, run:
cd frontend && make
From the project root, run:
node .
You deserve it. 🍕
The index.js
file in the project root connects the frontend
and the backend
. It does this by giving the backend server the following information:
pathToIndex
: Where the root html document is locatedpathToAssets
: Where the assets for the frontend application are located
Our backend happens to serve the index.html
file in the project root. This file expects that it can grab a tada.js
file (served from the folder frontend/dist
, our static asset directory). tada.js
should in turn load the frontend
build that consumes our backend
.
The server uses node's os
module in conjunction with socket.io
to broadcast system load data to interested parties over websockets via "loadavg update"
events.
Speaking of those interested parties, how about that frontend
, eh?
The frontend is a standard React + Redux app built with webpack. The action that you'll probably want to check out is named "ADD_LOAD_DATUM"
. I used the c3
charting library to create the visualization.
Psssst there's a secret little easter-egg action that has nothing to do with goats, but don't tell anyone shhhhh 🙊 🐐.
The production build listens for "loadavg update"
events over websockets. When new data are received, an "ADD_LOAD_DATUM"
action is dispatched with the new loadavg data. This re-renders the interface with our new data and any new alerts (if we crossed the stated loadavg
threshold.)
The alert logic is handled inside frontend/src/js/reducers/app-actions-alerts.js
.
- Is a thin
node/express
server - Takes a configuration object with
- the path to the root html document
- the path to the static assets folder
- Makes use of
os
andsocket.io
to broadcast system load information over websockets - Prints a cute and colorful
"server listening"
message
The "loadavg update"
event emits some data over websockets. That data looks like this:
const loadavgUpdateData = {
loadavg1, // One minute load average
loadavg5, // Five minute load average
loadavg15, // Fifteen minute load average
timestamp, // Timestamp (ms since Jan. 1, 1970 +0000 UTC)
};
- A standard
React
andRedux
application built withwebpack
- Uses the
expect
library for testing, as well as some daunting mocks - In production, listens for
"loadavg update"
events over websockets - In development, mocks a websocket connection and uses
webpack-dev-server
with hot reloading
The entire state of the application is held inside the Redux store. The state object includes the following fields:
const state = {
alerts, // Array of `alert` objects
maxAlertHistory, // The maximum number of alerts to remember
chartDataBuffer, // A buffer of data we have received but not graphed yet
chartUpdateInterval, // How long we should wait before adding data to chart
latestDatumTimestamp, // Cached timestamp of the latest loadDatum we've seen
latestChartTimestamp, // The time at which we last
loadData, // Array of `loadDatum` objects
loadAlertThreshold, // The minimum avg number that does not trigger an alert
loadInterval, // Interval between `loadData` objects (ms) - weakpoint of app
loadSpan, // Amt of time over which to viz loadDatum objects (ms)
themeName, // It's a surprise!
};
A loadDatum
object has the following form:
const loadDatum = {
loadavg, // Exactly what you thing it is
timestamp, // The timestamp (provided by the server) of loadavg observation
};
An alert
object has the following form:
const alert = {
type, // Either "warning" or "success"
message,
loadAlertThreshold, // Boundary that we crossed to trigger the alert
twoMinuteAvg, // Two-minute loadavg that triggered the alert
timestamp, // Time at which alert was triggered
};
The frontend also makes use of feature flags to help mock out the backend in development. (The production build removes these chunks of code for us.)
Thus, we can develop the frontend separately from the backend. (_I investigated a few different approaches here, and using feature flags seemed to be the simplest for the scope of this project.__) Furthermore, we can take advantage of 🔥 hot module replacement while we work on the interface. (Thanks be to webpack-dev-server
.)
To check out the 🔥 hot reloading, run the following from the project root:
cd frontend && npm i && npm start
The frontend build uses Babel's Stage2 preset, which gives us some of javascript's awesome forthcoming syntactic features. (:heart: object splats ...
.)
Calculating a two-minute loadavg.
Seeing as I keep one-minute loadavg measurements in one-second intervals, I calculated the two-minute loadavg by adding a one-minute loadavg measurement to another taken 60 seconds prior and then dividing by two. Mathematically, this made the most sense to me. I have no idea how it pans out in practice. (We can go over the assumptions at play here in person if you want.)
The current version of the application could benefit from one or more of these features.
The visualization is janky, but there are probably some other reasons for jank too. Want to profile the app with me???? It'd be fun! Let's do it!! 📊
The server could (should) cache its most recent data so that the client would not have to wait around before seeing a nontrivial amount of data.
It wasn't exactly clear who the end-user was for this application. With more clarity on our audience, we might consider providing more context to the data that we present.
The user should be able to adjust certain parts of the app to their liking. E.g.,
the alert threshold🎉- the maximum number alerts to display
the timespan between data samples(sort of 🎉 ?)- the total window of time in the visualization
The app could provide sensible defaults and a simple way to toy with those defaults.
E.g., it's important to know how many cores the machine has alongside its loadavg.
Alerts should be actionable. A better version of this application would suggest courses of action in the face of high load. E.g., we could report on what process(es) were eating up system resources.
There are several inefficiencies in the Redux code. At the current scale of the app, these inefficiencies are probably just noise. However, if more charts and data got added, the frontend code would beg a good performance tune-up
Error handling. Let's do it. E.g., what happens if the websocket connection goes kaput? What happens if... other stuff. You get the point.
Also, I am certain that sneaky depedencies abound. Ya know, little areas where code assumes that data are structured a certain way, etc. Can you find them?! I am tired.
Alright. I realize that I may have used a lot of tools with which you might not be familiar.
Personally, I would be 😡 if I had to review this code, and I hadn't ever toyed with React, Redux, webpack, etc.
In that instance, the best I can do is give you some light reading. This might not help mitigate the 😡, but I really, really, really have come to love these tools. (I also felt like they fit this project particularly well.) After some fiddling, I think you might love these tools as well.
- Getting started with React
- A Cartoon introduction to Redux
- Pete Hunt's webpack howto (wayyy better intro than the webpack docs)
- Dan Abramov's Redux video tutorial
For reference.
Create a simple web application that monitors load average on your machine:
- Collect the machine load (using “uptime” for example)
- Display in the application the key statistics as well as a history of load over the past 10 minutes in 10s intervals. We’d suggest a graphical representation using D3.js, but feel free to use another tool or representation if you prefer. Make it easy for the end-user to picture the situation!
- Make sure a user can keep the web page open and monitor the load on their machine.
- Whenever the load for the past 2 minutes exceeds 1 on average, add a message saying that “High load generated an alert - load = {value}, triggered at {time}”
- Whenever the load average drops again below 1 on average for the past 2 minutes, Add another message explaining when the alert recovered.
- Make sure all messages showing when alerting thresholds are crossed remain visible on the page for historical reasons.
- Write a test for the alerting logic
- Explain how you’d improve on this application design