DEV Community

Cover image for Building a Real-time Laptop Monitoring System with Flask and Redis
Tandap Noel Bansikah
Tandap Noel Bansikah

Posted on

3 1 1 1 1

Building a Real-time Laptop Monitoring System with Flask and Redis

Introduction

After a considerable break from Python Flask development, I decided to refresh my skills by creating a practical project - a real-time laptop monitoring system. This project combines the simplicity of Flask with the power of Redis for real-time data handling, creating a responsive dashboard that displays crucial system metrics like CPU, memory, and disk usage.

Why Redis?

Redis, an in-memory data structure store, plays a crucial role in this project. It serves as a perfect solution for:

  • Real-time Data Storage: Storing rapidly changing system metrics efficiently
  • Fast Data Retrieval: Providing instant access to historical performance data
  • Time-Series Data: Managing temporal data for trend analysis and visualization
  • Lightweight: Minimal overhead compared to traditional databases
  • Built-in Data Structures: Utilizing Redis lists for storing time-series metrics

Prerequisites

Before diving into the project, ensure you have the following installed:

  • Docker and Docker Compose
  • Python 3.9 (included in the Docker image)
  • Modern web browser for viewing the dashboard
  • Basic understanding of Python, Flask, and Redis

Project Structure

laptop-monitor/
├── app/
│   ├── __init__.py
│   ├── monitor.py
│   ├── redis_client.py
│   ├── routes.py
│   └── templates/
│       └── index.html
├── static/
│   ├── css/
│   │   └── style.css
│   └── js/
│       └── script.js
├── Dockerfile
├── docker-compose.yml
└── requirements.txt
Enter fullscreen mode Exit fullscreen mode

Code Implementation

1. Flask Application Initialization (app/init.py)

import sys
import os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from flask import Flask
from routes import init_routes

app = Flask(__name__,
           static_folder='../static',
           static_url_path='')

init_routes(app)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
Enter fullscreen mode Exit fullscreen mode

2. System Monitoring (app/monitor.py)

import psutil

def get_metrics():
    return {
        'cpu_percent': psutil.cpu_percent(),
        'memory_percent': psutil.virtual_memory().percent,
        'disk_percent': psutil.disk_usage('/').percent
    }
Enter fullscreen mode Exit fullscreen mode

3. Redis Client (app/redis_client.py)

import redis
import json
from datetime import datetime

redis_client = redis.Redis(host='redis', port=6379, db=0)

def store_metrics(metrics):
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    metrics['timestamp'] = timestamp
    redis_client.lpush('metrics', json.dumps(metrics))
    redis_client.ltrim('metrics', 0, 99)  # Keep last 100 entries

def get_metrics_history():
    metrics_history = redis_client.lrange('metrics', 0, -1)
    return [json.loads(m) for m in metrics_history]
Enter fullscreen mode Exit fullscreen mode

4. Routes Configuration (app/routes.py)

from flask import render_template
from monitor import get_metrics
from redis_client import store_metrics, get_metrics_history

def init_routes(app):
    @app.route('/')
    def dashboard():
        metrics = get_metrics()
        store_metrics(metrics)
        history = get_metrics_history()
        return render_template('index.html', metrics=metrics, history=history)

    @app.route('/api/metrics')
    def api_metrics():
        return get_metrics()
Enter fullscreen mode Exit fullscreen mode

5. Frontend Template (app/templates/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laptop Monitor</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <link rel="stylesheet" type="text/css" href="/css/style.css">
</head>
<body>
    <div class="container">
        <h1>Laptop Performance Dashboard</h1>
        <div class="metrics-grid">
            <div class="metric-card">
                <h3>CPU Usage</h3>
                <p class="metric-value">{{ metrics.cpu_percent }}%</p>
            </div>
            <div class="metric-card">
                <h3>Memory Usage</h3>
                <p class="metric-value">{{ metrics.memory_percent }}%</p>
            </div>
            <div class="metric-card">
                <h3>Disk Usage</h3>
                <p class="metric-value">{{ metrics.disk_percent }}%</p>
            </div>
        </div>
        <div class="chart-container">
            <canvas id="performanceChart"></canvas>
        </div>
    </div>
    <script src="/js/script.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

6. Frontend Styling (static/css/style.css)

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f4f7fa;
    margin: 0;
    padding: 0;
    color: #333;
}

.container {
    max-width: 1200px;
    margin: 40px auto;
    padding: 20px;
}

h1 {
    text-align: center;
    color: #2c3e50;
    margin-bottom: 30px;
    font-size: 2.5em;
}

.metrics-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 20px;
    margin-bottom: 40px;
}

.metric-card {
    background: #ffffff;
    border-radius: 10px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    padding: 20px;
    text-align: center;
    transition: transform 0.2s;
}

.metric-card:hover {
    transform: translateY(-5px);
}

.metric-card h3 {
    margin: 0 0 10px;
    color: #34495e;
    font-size: 1.2em;
}

.metric-value {
    font-size: 2em;
    font-weight: bold;
    color: #2980b9;
    margin: 0;
}

.chart-container {
    background: #ffffff;
    border-radius: 10px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    padding: 20px;
    max-width: 100%;
}

canvas {
    width: 100% !important;
    height: auto !important;
}
Enter fullscreen mode Exit fullscreen mode

7. Frontend JavaScript (static/js/script.js)

let chart;
const maxDataPoints = 10;

function initChart() {
    const ctx = document.getElementById('performanceChart').getContext('2d');
    chart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: [],
            datasets: [
                {
                    label: 'CPU Usage',
                    borderColor: '#2980b9',
                    data: [],
                    fill: false
                },
                {
                    label: 'Memory Usage',
                    borderColor: '#27ae60',
                    data: [],
                    fill: false
                },
                {
                    label: 'Disk Usage',
                    borderColor: '#c0392b',
                    data: [],
                    fill: false
                }
            ]
        },
        options: {
            responsive: true,
            scales: {
                y: {
                    beginAtZero: true,
                    max: 100
                }
            },
            animation: {
                duration: 0
            }
        }
    });
}

function updateMetrics() {
    fetch('/api/metrics')
        .then(response => response.json())
        .then(data => {
            // Update metric values
            document.querySelector('.metric-card:nth-child(1) .metric-value').textContent = `${data.cpu_percent}%`;
            document.querySelector('.metric-card:nth-child(2) .metric-value').textContent = `${data.memory_percent}%`;
            document.querySelector('.metric-card:nth-child(3) .metric-value').textContent = `${data.disk_percent}%`;

            // Update chart
            const timestamp = new Date().toLocaleTimeString();
            chart.data.labels.push(timestamp);
            chart.data.datasets[0].data.push(data.cpu_percent);
            chart.data.datasets[1].data.push(data.memory_percent);
            chart.data.datasets[2].data.push(data.disk_percent);

            // Remove old data points if exceeding maxDataPoints
            if (chart.data.labels.length > maxDataPoints) {
                chart.data.labels.shift();
                chart.data.datasets.forEach(dataset => dataset.data.shift());
            }

            chart.update();
        })
        .catch(error => console.error('Error fetching metrics:', error));
}

// Initialize chart when page loads
document.addEventListener('DOMContentLoaded', () => {
    initChart();
    // Update metrics every 2 seconds
    setInterval(updateMetrics, 2000);
});
Enter fullscreen mode Exit fullscreen mode

8. Requirements (requirements.txt)

Flask==2.0.1
redis==4.0.2
psutil==5.8.0
Enter fullscreen mode Exit fullscreen mode

9. Docker Configuration

Dockerfile

FROM python:3.9-slim
WORKDIR /app

# Copy requirements first to leverage Docker cache
COPY requirements.txt /app/
RUN pip install -r requirements.txt

# Create static directory structure explicitly
RUN mkdir -p /app/static/css /app/static/js

# Copy all application files
COPY . /app/

# Ensure proper permissions for static files
RUN chmod -R 755 /app/static

CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

services:
  flask-app:
    build: .
    ports:
      - "5001:5000"
    depends_on:
      - redis
    environment:
      - REDIS_HOST=redis
    volumes:
      - ./static:/app/static
  redis:
    image: redis:latest
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

volumes:
  redis-data:
Enter fullscreen mode Exit fullscreen mode

Running the Application

To run the application, follow these steps:

  1. Clone the repository:
cd laptop-monitor #at the root of you project where we have the docker compose file
Enter fullscreen mode Exit fullscreen mode
  1. Build and start the containers:
docker-compose up --build
Enter fullscreen mode Exit fullscreen mode
  1. Access the dashboard: Open your web browser and navigate to http://localhost:5001

CPU Analysis

Conclusion

This laptop monitoring project serves as an excellent refresher for Python Flask development while incorporating modern development practices like containerization and real-time data handling. The combination of Flask's simplicity and Redis's powerful data handling capabilities creates a robust solution for system monitoring.

The project demonstrates several key concepts:

  • Building a Flask web application
  • Real-time data handling with Redis
  • Docker containerization
  • Frontend development with Chart.js
  • System metrics monitoring with psutil

If you encounter any challenges while setting up or running the project, feel free to reach out in the comments section below. Your feedback and questions are valuable for improving this tutorial and helping others in the community.
Link to the code on github

References

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (2)

Collapse
 
francis-pouatcha profile image
Francis Pouatcha

This is wonderful. Thank you very much for publishing.

Collapse
 
bansikah profile image
Tandap Noel Bansikah

Thank you sir really appreciate🙏😊

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

If this article connected with you, consider tapping ❤️ or leaving a brief comment to share your thoughts!

Okay