This post will show you how to create a simple system resource monitor that sends WhatsApp alerts when CPU or memory utilization exceeds a threshold. We created this demo project with the sysinfo and the infobip_sdk crates. The complete code is in the sysmonitor-alert repository.
Rust is an efficient systems programming language. This makes it ideal for resource monitors, profilers, and other low-level, infrastructure-related software. When you deploy an application, you want it to perform at its best without overloading the computer it runs on. For that purpose, resource monitors are used to observe latency and scalability in a system. Ideally, you want your software to use enough processor and memory without saturating the computer. Usage numbers that are too high may mean you need to grow the system or check if something is wrong. This software could run in a company server, or you could simply monitor your laptop to identify inefficient, battery-draining processes.
We’ll guide you through the steps we followed to create this monitor, and we’ll show the most interesting code and how to run the project.
Overview
If you want to create a monitor of your own, you may follow the steps detailed in this guide:
- Create a new Rust project
- Install dependencies
- Create a
async main
function - Create a
sysinfo::System
- Read system details in a loop
- Check for usage beyond the threshold
- Send the alert if usage is high
Create a new Rust project
Create a new project using cargo or your IDE of choice.
cargo new <project-name>
Install Dependencies
We need a few crates for the monitor to work nicely:
[dependencies]
infobip_sdk = "0.5.2"
sysinfo = "0.30.5"
tokio = "1.35.1"
chrono = "0.4.33"
bytesize = "1.3.0"
clap = { version = "4.4.18", features = ["derive"] }
Particularly important are infobip_sdk
, which enables the program to send WhatsApp alerts, tokio
, which provides
the async runtime and the interval loop, and sysinfo
, which is our source of system information.
Create an async main function
Because the Infobip Rust SDK is async, we need to create an async main with the help of Tokio:
#[tokio::main]
async fn main() {
}
Create a sysinfo::System object
The System object will help us gather resource usage details. Make sure you check the sysinfo crate, which has a lot of system properties it can monitor.
let sys = System::new_all();
Read system details in a loop
We need to run the System.refresh()
function to update system properties and detect anomalies. To refresh at regular
intervals, we are using Tokio’s interval
, which runs async and can run while we send WhatsApp alerts over the network
without stopping the program execution.
let mut interval = time::interval(Duration::from_secs(args.refresh_interval_secs));
// ...
loop {
// Refresh CPU for accuracy.
sys.refresh_cpu();
interval.tick().await;
sys.refresh_all();
Check for usage beyond threshold and alert
After gathering system info, we check if all CPU cores are within limits. If they are outside limits, we send an alert with the send_alert() function.
for (i, cpu) in sys.cpus().iter().enumerate() {
let usage = cpu.cpu_usage();
if usage > args.cpu_usage_threshold {
cpus_high_cycles[i] += 1;
if cpus_high_cycles[i] >= args.cycles_for_alert
&& cpus_ok_cycles[i] >= args.cycles_between_alert
{
cpus_ok_cycles[i] = 0;
handles.push(send_alert(format!(
"{ts} {hostname}: High CPU{i} usage: {usage:.1}%",
)));
}
} else {
cpus_high_cycles[i] = 0;
cpus_ok_cycles[i] += 1;
}
}
To prevent flooding your phone with messages, we implemented a couple of simple counters, cpus_high_cycles
and cpus_ok_cycles
, that prevent sending alerts after every high usage spike. For an alert to trigger, a number of
consecutive high measures must have been taken, and there should be a number of consecutive non-high measures to
separate one anomaly from another. For checking memory, we follow the same logic.
Send the alert if usage is high
The special feature of this monitor is implemented in the send_alert()
function, which is called when there’s an
anomaly. Here’s the code for it:
async fn send_alert(message: String) {
let client = WhatsappClient::with_configuration(Configuration::from_env_api_key().unwrap());
let request_body = SendTextRequestBody::new(
env::var("WA_SENDER").unwrap().as_str(),
env::var("WA_DESTINATION").unwrap().as_str(),
TextContent::new(message.as_str()),
);
let response = client.send_text(request_body).await.unwrap();
println!("Alert: {message} => HTTP response: {:?}", response.status);
}
The above code creates a message and then sends it to the server. For this code to work correctly, we need to set four environment variables, which you can retrieve from your account:
IB_API_KEY=<your-api-key>
IB_BASE_URL=<your-base-url>
WA_SENDER=<your-whatsapp-sender-number>
WA_DESTINATION=<your-whatsapp-phone-number>
The IB_API_KEY
and IB_BASE_URL
variables are needed to initialize the Infobip SDK, while WA_SENDER
and
WA_DESTINATION
are the ones we use for our specific use case.
Run the project
Because we are using simple text WhatsApp messages, we need prior user engagement with the user to send messages. Send a message from the phone to the sender number with any text. Check the endpoint documentation for more information.
Once you send the message, run the project with cargo or with the command line:
cargo run
That’s it! The process will run forever until you kill it.
Customizing execution
The default command will run with the default parameters. We implemented a few command line options to customize the usage
thresholds, refresh frequency and the required positive cycles to trigger an alert. Run with -–help
to get a summary
of the available parameters.
cargo run -- --help
I hope you find this monitor helpful or that it helps you to create your own awesome one. In case you find any errors or you want to know more about response details, please check Infobip Response Status and Error Codes Reference.