Advertisement
Open Source Projects by Phil Schwartz

Writing a Python Script That Monitors Process Memory Usage and Sends Alerts

A process that quietly consumes available memory can slow a Linux host, trigger the OOM killer, and interrupt services without producing an obvious application error. A small Python monitor can detect that growth early, record useful context, and notify an administrator before the system becomes unstable.

The psutil library provides a practical interface for reading resident memory, virtual memory, CPU activity, process names, and system-wide statistics. Combined with Python’s standard email and logging modules, it is enough to build a lightweight memory usage alerting tool without adding a full monitoring platform.

This approach suits development servers, small business systems, self-hosted applications, and open-source projects running on VPS instances. It can also complement established tools such as systemd, cron, Prometheus, or a cloud provider’s alerting service.

For Australian teams, alert timing and data handling deserve attention. A server in Sydney may be administered from Melbourne or Perth, so timestamps should include the timezone and daylight-saving offset. Logs containing usernames, command lines, or host details should also be handled in line with the Privacy Act 1988 and the organisation’s security policy.

Define What Memory Usage Means

A process can expose several memory figures. Resident Set Size, or RSS, represents the memory currently held in RAM and is usually the most useful signal for operational alerts. Virtual Memory Size, or VMS, includes mapped address space and can appear large even when physical memory pressure is modest.

A percentage threshold can be easier to reuse across different hosts. For example, an alert might trigger when a named process uses more than 15 per cent of total RAM. An absolute limit, such as 1.5 GB, is often clearer for a known workload. Recording both the RSS value and the percentage gives administrators enough context to decide whether an increase is serious.

Single readings can be misleading. A compiler, backup job, or web request may briefly consume extra memory and then release it. Requiring several consecutive readings above the limit reduces false positives. A cooldown period prevents a growing process from sending a new email every 30 seconds.

Prepare the Python Environment

Install the dependency in a virtual environment so the monitor’s packages remain separate from the operating system’s Python installation:

python3 -m venv /opt/memory-monitor/venv
/opt/memory-monitor/venv/bin/pip install psutil

The script needs permission to inspect the target process. Ordinary processes are generally visible to the same user, while services owned by another account may expose limited details. Running the monitor under a dedicated service account is preferable to using root, provided the account can read the required process information.

A configuration file or environment variables should hold the process name, threshold, email destination, and SMTP credentials. Avoid placing passwords directly in source code. Australian organisations using hosted mail should check whether their provider requires authenticated SMTP, an app password, or an approved relay from a local office or cloud network.

Build the Memory Usage Monitor

The following example searches for a process by name, samples its RSS usage, and sends an email after three consecutive readings above the limit. It uses an environment variable for the SMTP password and includes the host name and measured value in the alert.

#!/usr/bin/env python3
import os
import socket
import smtplib
import time
from email.message import EmailMessage

import psutil

PROCESS_NAME = os.getenv("MONITOR_PROCESS", "myapp")
LIMIT_PERCENT = float(os.getenv("MEMORY_LIMIT_PERCENT", "15"))
INTERVAL_SECONDS = int(os.getenv("CHECK_INTERVAL", "60"))
REQUIRED_BREACHES = 3
COOLDOWN_SECONDS = 1800

SMTP_HOST = os.environ["SMTP_HOST"]
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USER = os.environ["SMTP_USER"]
SMTP_PASSWORD = os.environ["SMTP_PASSWORD"]
ALERT_TO = os.environ["ALERT_TO"]

breaches = 0
last_alert = 0

def process_memory():
    matches = []
    for process in psutil.process_iter(["name", "cmdline", "memory_info"]):
        try:
            if process.info["name"] == PROCESS_NAME:
                matches.append(process.info["memory_info"].rss)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue
    return sum(matches)

def send_alert(rss, percent):
    message = EmailMessage()
    message["Subject"] = f"Memory alert on {socket.gethostname()}"
    message["From"] = SMTP_USER
    message["To"] = ALERT_TO
    message.set_content(
        f"{PROCESS_NAME} is using {rss / 1024**3:.2f} GiB "
        f"({percent:.1f}% of system RAM)."
    )

    with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=20) as server:
        server.starttls()
        server.login(SMTP_USER, SMTP_PASSWORD)
        server.send_message(message)

while True:
    try:
        rss = process_memory()
        total_ram = psutil.virtual_memory().total
        percent = rss / total_ram * 100

        if percent >= LIMIT_PERCENT:
            breaches += 1
        else:
            breaches = 0

        now = time.time()
        if breaches >= REQUIRED_BREACHES and now - last_alert >= COOLDOWN_SECONDS:
            send_alert(rss, percent)
            last_alert = now
            breaches = 0

    except Exception as error:
        print(f"Monitor error: {error}", flush=True)

    time.sleep(INTERVAL_SECONDS)

If several worker processes share the same name, the script adds their RSS values together. That is useful for a multi-worker application, although shared libraries can make the total differ from a detailed proportional-set-size measurement. For a single service, identifying the process by PID, systemd unit, or command-line argument may provide greater accuracy.

Make Alerts Useful and Secure

Email is convenient, but it is not the only notification route. A webhook can send structured data to Microsoft Teams, Slack, PagerDuty, or an internal incident system. SMS may be appropriate for a critical service, although message costs and escalation rules should be considered. A local Australian business might route alerts to an on-call engineer in Brisbane during business hours and to a separate after-hours roster.

Every alert should explain what happened, where it happened, when it happened, and what threshold was crossed. Include the hostname, process name, RSS in MiB or GiB, total memory, and the number of consecutive breaches. Do not include secrets, full user data, or unrestricted command-line arguments in messages sent through third-party systems.

Use TLS for SMTP and restrict the credentials to the minimum required account. Store them in a root-readable environment file or a secret manager. If alerts contain personal information or customer identifiers, consider where the mail provider stores and processes that information under Australian privacy obligations and any contractual data-residency requirements.

Run It Reliably on Linux

A systemd service is more dependable than leaving the script in a terminal or relying on a user’s desktop session. Create a unit such as /etc/systemd/system/memory-monitor.service:

[Unit]
Description=Process memory monitor
After=network-online.target

[Service]
User=memory-monitor
ExecStart=/opt/memory-monitor/venv/bin/python /opt/memory-monitor/monitor.py
EnvironmentFile=/etc/memory-monitor.conf
Restart=always
RestartSec=10
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

After creating the service account and protecting the configuration file, enable it with systemctl enable --now memory-monitor. Review output using journalctl -u memory-monitor. Add log rotation or rely on journald limits so repeated errors cannot fill the disk.

Use timestamps with an explicit zone, particularly when servers span Sydney, Melbourne, Adelaide, Perth, and Darwin. A scheduled maintenance window that looks consistent in Australian Eastern Standard Time can shift by an hour during daylight saving, while Perth remains on Western Standard Time throughout the year.

Operational Practices for Safer Monitoring

Memory alerts work best when they support diagnosis rather than simply announcing a failure. Compare process RSS with system-wide available memory, swap activity, restart counts, and application-level metrics. A process using 20 per cent of RAM may be harmless on a 64 GB host but urgent on a small 2 GB VPS.

Review the threshold after deployments, dependency changes, and changes in workload. A retail application serving customers in Sydney may have a different weekend pattern from a software service used during Melbourne business hours. Treat the monitor as a small operational component: version it, test it, and keep its failure behaviour simple and visible.