Introduction


πŸ’‘ Idea

Performance Bot was created when I needed a simple way to monitor my home server from outside my home, without using complicated commands.

At first, I considered building a web page, but I wanted to try something new. That’s when I came up with the idea of building a bot.


πŸ“‹ Summary

To start my project, I needed to gather all the essential information from my server:

  • /system – System info
  • /cpu – CPU info
  • /memory – Memory info
  • /disk – Disk info
  • /network – Network info
  • /docker – Docker info
  • /sensors – Sensors info

After some research, I found two very useful Python libraries:

  • psutil for gathering system info
  • docker, the Docker SDK for getting Docker info (images and containers)

βš™οΈ Implementation

With those libraries in mind, I started a WebSocket server using the FastAPI framework and began serving it on my localhost at port 8000.

Once I had the WebSocket server working, I started researching how to create a Telegram bot. I discovered a Telegram bot called TheBotFather, which lets you create and customize bot profiles and provides you with a token to work with.

After the bot was ready, I worked on the Dockerfile to make deployment easier, whether in the cloud or if the bot and backend are running on different servers. While working on the Dockerfile, I found that GitHub can help you create an image and push that image to Docker Hub automatically when you push to the GitHub repo.


πŸ“š Documentation

Finally, I found mdBook, which helps me deploy documentation for the repo using Markdown files, making everything much easier.

πŸ“š Documentation

Welcome to the documentation section. Here you'll find detailed guides and references for both the WebSocket server and the Telegram bot components of this project.

WebSocket

Welcome to the WebSocket server documentation. Here you'll find everything you need to understand, set up, and extend the WebSocket functionality in this project.

  • WebSocket Imports β€” Overview of required libraries and modules.
  • Create WebSocket β€” Step-by-step guide and example code for building the WebSocket server.

πŸ“¦WebSocket Imports

This section documents all the imports used in backend/main.py and their purposes.


Standard Library Imports

  • datetime
    For handling dates and times, such as timestamps and formatting.

  • platform
    To retrieve information about the underlying platform (OS, node, release, etc.).

  • time
    For time-related functions, such as calculating uptime.

  • typing (Dict, Set)
    For type hinting dictionaries and sets.

  • contextlib (asynccontextmanager)
    To manage the application lifespan with asynchronous context managers.

  • asyncio
    For asynchronous programming, including tasks, locks, and sleep.

  • json
    For encoding and decoding JSON data.


Third-Party Imports

  • fastapi

    • FastAPI: The main FastAPI application class.
    • WebSocket: For handling WebSocket connections.
  • fastapi.middleware.cors (CORSMiddleware)
    To enable Cross-Origin Resource Sharing (CORS) for the API.

  • psutil
    For retrieving system information such as CPU, memory, disk, sensors, and network stats.

  • docker
    For interacting with Docker, including listing images and containers.


Summary Table

ModulePurpose
datetimeDate and time handling
platformSystem/platform information
timeTime calculations
typingType hints for Dict and Set
contextlibAsync context management
fastapiAPI and WebSocket framework
fastapi.middleware.corsCORS middleware for FastAPI
psutilSystem resource monitoring
asyncioAsync programming primitives
jsonJSON serialization/deserialization
dockerDocker API interaction

Create WebSocket

This guide shows how to build a simple WebSocket server using FastAPI.
Clients can connect to the /ws endpoint and receive a JSON message with CPU, memory, and Docker container information.
You can use this as a starting point for more advanced real-time monitoring features.


Step 1: Basic FastAPI Setup

Start by creating a FastAPI app and enabling CORS for local frontend development.

    from fastapi import FastAPI, WebSocket
    from fastapi.middleware.cors import CORSMiddleware

    app = FastAPI(title="Performance Dashboard API")

    app.add_middleware(
        CORSMiddleware,
        allow_origins=["http://localhost:3000"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        pass  # We'll implement this in the next steps

    @app.get("/")
    def read_root():
        return {"message": "Performance Dashboard API is running."}

    if __name__ == "__main__":
        import uvicorn
        uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)

Step 2: Accept WebSocket Connections

Now, accept WebSocket connections and send a simple message.

    # ... previous setup ...

    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        await websocket.accept()
        await websocket.send_text("WebSocket connection established!")
        await websocket.close()

    # ... previous setup ...

Step 3: Send System and Docker Stats

Finally, gather system and Docker stats and send them as JSON.

    import psutil
    import docker
    import json

    # ... previous setup ...
    docker_client = docker.from_env()

    # WebSocket endpoint
    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        await websocket.accept()
        docker_containers = []
        for idx, container in enumerate(docker_client.containers.list(all=True)):
            try:
                container_data = {
                    "id": container.id[:12] if container.id else "unknown",
                    "name": container.name if container.name else "unknown",
                    "status": container.status if container.status else "unknown",
                    "image": container.image.tags[0] if container.image.tags else (container.image.id[:12] if container.image.id else "unknown"),
                    "created": container.attrs.get('Created', '')[:19] if container.attrs.get('Created') else 'unknown'
                }
                docker_containers.append({f"container{idx}": container_data})
            except Exception as e:
                print(f"Error processing container: {e}")
                continue

        latest_data = {
            "cpu": {
                "cores_count": psutil.cpu_count(logical=False),
                "threads_count": psutil.cpu_count(),
                "cpu": round(psutil.cpu_percent(), 1),
                "frequency": round(psutil.cpu_freq().current, 1),
            },
            "memory": {
                "ram_total": round(psutil.virtual_memory().total / (1024**3), 2),
                "ram_available": round(psutil.virtual_memory().available / (1024**3), 2),
                "ram_percent": round(psutil.virtual_memory().percent, 1),
            },
            "docker": {
                "containers": docker_containers
            }
        }
        await websocket.send_text(json.dumps(latest_data))
        await websocket.close()
    
    # ... previous setup ...

How to Run the Backend

Follow these steps to start the FastAPI backend server:

    # Install dependencies
    pip install -r requirements.txt

    # Change directory to the backend
    cd backend
    
    # Run the backend 
    uvicorn main:app --reload

WebSocket Output

{
    "cpu": {
        "cores_count": 4,
        "threads_count": 8,
        "cpu": 4.5,
        "frequency": 2611.2
    },
    "memory": {
        "ram_total": 7.61,
        "ram_available": 4.94,
        "ram_percent": 35.1
    },
    "docker": {
        "containers": [
            {
                "container0": {
                "id": "970b24a9730e",
                "name": "bot2",
                "status": "exited",
                "image": "performance-bot:latest",
                "created": "2025-11-26T14:18:18"
                }
            },
            {
                "container1": {
                "id": "900d303cb952",
                "name": "bot",
                "status": "exited",
                "image": "snr1s3/performance-bot:latest",
                "created": "2025-11-26T10:53:37"
                }
            }
        ]
    }
}

Telegram Bot Documentation

Welcome to the Telegram bot documentation. Here you'll find everything you need to understand, set up, and extend the bot functionality in this project.

  • Bot Imports β€” Overview of required libraries and modules.
  • Create Bot β€” Step-by-step guide and example code for building and running the Telegram bot.

Telegram Bot Imports

Below is an overview of the main imports used in the Telegram bot component, along with their purposes:


Standard Library Imports

  • os
    Used for accessing environment variables, such as the Telegram bot token.

Third-Party Imports

  • telegram
    Provides classes and methods for interacting with the Telegram Bot API.

  • telegram.ext
    Supplies higher-level tools for building bots, such as Application, CommandHandler, and ContextTypes.


Summary Table

ModulePurpose
osAccess environment variables (e.g., bot token)

| telegram | Telegram Bot API classes and methods | | telegram.ext | Bot framework utilities and handlers |

Create Bot

This guide will help you set up and run the Telegram bot for the Performance Dashboard project.


Step 1: Create Your Bot with BotFather

  1. Open Telegram and search for @BotFather.
  2. Start a chat and send /newbot.
  3. Follow the instructions to set your bot’s name and username.
  4. Copy the token provided by BotFather β€” you’ll need it for the next step.

Step 2: Set Up the Project

Try to connect to the bot creating bot.py

    from telegram.ext import ApplicationBuilder
    from config import BOT_TOKEN
    from handlers import setup_handlers


    def main() -> None:
        app = ApplicationBuilder().token(BOT_TOKEN).build()
        setup_handlers(app)
        app.run_polling()

    if __name__ == "__main__":
        main()

Step 3: Add Command Handlers

Example for a /cpu command handler:

    from telegram.ext import CommandHandler, ContextTypes
    
    # ... previous setup ...
    def setup_handlers(app):
        app.add_handler(CommandHandler("cpu", cpu_Handler))

    async def cpu_Handler(update, context: ContextTypes.DEFAULT_TYPE):
        content = CpuInfo(SocketCon()).fetch()
        msg = "<b>CPU INFO:</b>\n" + "\n".join(content)
        await update.message.reply_text(msg, parse_mode="HTML")

Step 4: Create the CPU Handler

Create cpuInfo.py and infoBase.py:

    # cpuInfo.py
    from telegram import Update
    from telegram.ext import ContextTypes
    from .info_base import InfoBase 

    class CpuInfo(InfoBase):
        def fetch(self):
            def formatter(info):
                arr = []
                for key, value in info.items():
                    key_upper = key.upper()
                    if key_upper == "CPU":
                        value = str(value) + " %"
                    elif key_upper == "FREQUENCY":
                        value = str(value) + " GHz"
                    if key_upper not in ("CPU_CORE", "FREQUENCY_CORE"):
                        arr.append(f"{key_upper} : {value}")
                return arr
            return self.get_info("cpu", formatter)
    # infoBase.py
    import json

    class InfoBase:
        def __init__(self, socket_con):
            self.socket_con = socket_con

        def get_info(self, section: str, formatter: callable) -> list:
            content = self.socket_con.receive()
            arr = []
            try:
                data = json.loads(content)
                if section:
                    info = data.get(section, {})
                    arr = formatter(info)
                else:
                    return data
            except Exception as e:
                print("Error parsing JSON:", e)
                print("Raw content:", content)
            return arr

Step 5: Connect to the Backend via WebSocket Create socket_con.py:

import os
import websocket
import json

class SocketCon:
    _instance = None
    _ws = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(SocketCon, cls).__new__(cls)
        return cls._instance

    def __init__(self):
        self.ws = None
        self.connected = False

    def connect(self, url=os.getenv("WEB_SOCKET_URL", "")):
        if self.ws is None:
            self.ws = websocket.WebSocket()
            try:
                self.ws.connect(url)
                self.connected = True
            except Exception as e:
                print(f"Could not connect to {url}: {e}")
                self.ws = None
                self.connected = False

    def receive(self):
        if self.ws and self.connected:
            try:
                return self.ws.recv()
            except Exception as e:
                print(f"WebSocket receive error: {e}")
                self.ws = None
                self.connected = False
        print("Falling back to jsons/TEST.json")
        try:
            with open("jsons/TEST.json", "r") as f:
                return json.dumps(json.load(f))
        except Exception as e:
            print(f"Error reading jsons/TEST.json: {e}")
            return '{}'

How to Run the Bot

Follow these steps to start the Telegram bot:

    # Install dependencies
    pip install -r requirements.txt

    # Change directory to the backend
    cd bot
    
    # Run the backend 
    python bot.py

Bot Output

When you send /cpu to the bot, you might see:

User                    BOT
--------> /cpu              
                   <-------
        CPU INFO:
        CORES_COUNT: 4
        THREAD_COUNT: 8
        CPU: 4.5%
        FREQUENCY: 2611.2 MHz