mirror of
https://github.com/tcsenpai/sigmon.git
synced 2025-06-03 01:40:14 +00:00
first version
This commit is contained in:
commit
6ee97f8760
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# uv files
|
||||
pyproject.toml
|
||||
.python-version
|
||||
uv.lock
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
# PyInstaller files
|
||||
*.spec
|
||||
pyinstall.sh
|
||||
|
||||
# Plots
|
||||
plots/
|
47
README.md
Normal file
47
README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Sigmon
|
||||
|
||||
A tool to monitor WiFi signal strength and metrics continuously in the terminal.
|
||||
It uses `iwconfig` to get the metrics and `plotext` to plot them.
|
||||
|
||||

|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Fast, lightweight and simple
|
||||
- Continuous monitoring of WiFi signal strength and metrics
|
||||
- Average signal strength is displayed too
|
||||
- Plots are saved as PNG files with averages
|
||||
|
||||
## Requirements
|
||||
|
||||
- `iw`
|
||||
- Python 3.10+ (may work on older versions, but not tested)
|
||||
|
||||
### Install `iw` on Ubuntu (and probably other Debian-based systems)
|
||||
|
||||
```bash
|
||||
sudo apt-get install iw
|
||||
```
|
||||
|
||||
## Usage from binary
|
||||
|
||||
Download the binary from the [releases](https://github.com/tcsenpai/sigmon/releases) page.
|
||||
|
||||
```bash
|
||||
./sigmon
|
||||
```
|
||||
|
||||
## Usage from source
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
python src/main.py
|
||||
```
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
plotext
|
BIN
sigmon.png
Normal file
BIN
sigmon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 696 KiB |
11
src/libs/metrics.py
Normal file
11
src/libs/metrics.py
Normal file
@ -0,0 +1,11 @@
|
||||
class Metrics:
|
||||
"""
|
||||
Class to store the metrics of the network adapter
|
||||
"""
|
||||
def __init__(self, signal_strength: str, bitrate: str, is_power_save_enabled: bool):
|
||||
self.signal_strength = signal_strength
|
||||
self.bitrate = bitrate
|
||||
self.is_power_save_enabled = is_power_save_enabled
|
||||
|
||||
def __str__(self):
|
||||
return f"Signal strength: {self.signal_strength} dBm, Bitrate: {self.bitrate} Mb/s, Power save enabled: {self.is_power_save_enabled}"
|
159
src/libs/plotter.py
Normal file
159
src/libs/plotter.py
Normal file
@ -0,0 +1,159 @@
|
||||
"""
|
||||
This module is used to plot the metrics of the network adapter on the cli
|
||||
"""
|
||||
|
||||
import plotext as plt
|
||||
import time
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
from libs.metrics import Metrics
|
||||
from libs.session import SessionMetrics
|
||||
import os
|
||||
|
||||
|
||||
class MetricsPlotter:
|
||||
def __init__(self, max_points: int = 50):
|
||||
self.signal_strengths: List[float] = []
|
||||
self.timestamps: List[str] = []
|
||||
self.max_points = max_points
|
||||
self.min_seen = 0
|
||||
self.max_seen = -100
|
||||
self.session = SessionMetrics()
|
||||
|
||||
# Create plots directory if it doesn't exist
|
||||
os.makedirs("plots", exist_ok=True)
|
||||
|
||||
def save_plot(self):
|
||||
"""Save the current plot with averages"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"plots/sigmon_{timestamp}.png"
|
||||
|
||||
# Calculate average
|
||||
avg_signal = sum(self.signal_strengths) / len(self.signal_strengths)
|
||||
|
||||
# Create the final plot
|
||||
plt.clf()
|
||||
plt.plotsize(150, 50) # Larger size for better image quality
|
||||
|
||||
# Calculate dynamic y-axis limits
|
||||
y_min = max(-100, self.min_seen - 10)
|
||||
y_max = min(0, self.max_seen + 10)
|
||||
plt.ylim(y_min, y_max)
|
||||
|
||||
# Set x-axis limits
|
||||
x_max = len(self.signal_strengths)
|
||||
plt.xlim(0, x_max)
|
||||
|
||||
# Plot signal strength
|
||||
plt.plot(self.signal_strengths, marker="dot", color="green", label="Signal")
|
||||
|
||||
# Plot average line
|
||||
avg_line = [avg_signal] * len(self.signal_strengths)
|
||||
plt.plot(avg_line, color="red", label=f"Avg: {avg_signal:.1f} dBm")
|
||||
|
||||
plt.theme("matrix")
|
||||
plt.title("WiFi Signal Strength Over Time")
|
||||
plt.xlabel("Time (seconds)")
|
||||
plt.ylabel("Signal Strength (dBm)")
|
||||
|
||||
# Build and save the plot
|
||||
plt.build()
|
||||
plt.save_fig(filename)
|
||||
|
||||
print(f"\nPlot saved as: {filename}")
|
||||
|
||||
def update_plot(self, metrics):
|
||||
# Convert signal strength to float and ensure it's negative
|
||||
try:
|
||||
signal = float(metrics.signal_strength)
|
||||
# Update min/max seen values
|
||||
self.min_seen = min(self.min_seen, signal)
|
||||
self.max_seen = max(self.max_seen, signal)
|
||||
except ValueError:
|
||||
print(f"Warning: Invalid signal strength value: {metrics.signal_strength}")
|
||||
signal = 0
|
||||
|
||||
current_time = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
# Add new data points
|
||||
self.signal_strengths.append(signal)
|
||||
self.timestamps.append(current_time)
|
||||
|
||||
# Keep only last max_points
|
||||
if len(self.signal_strengths) > self.max_points:
|
||||
self.signal_strengths.pop(0)
|
||||
self.timestamps.pop(0)
|
||||
|
||||
# Clear the terminal
|
||||
plt.clear_terminal()
|
||||
|
||||
# Create the plot
|
||||
plt.clf()
|
||||
plt.plotsize(100, 30)
|
||||
|
||||
# Calculate dynamic y-axis limits
|
||||
y_min = max(-100, self.min_seen - 10) # Don't go below -100
|
||||
y_max = min(0, self.max_seen + 10) # Don't go above 0
|
||||
plt.ylim(y_min, y_max)
|
||||
|
||||
# Set x-axis limits
|
||||
x_max = len(self.signal_strengths)
|
||||
plt.xlim(0, x_max)
|
||||
|
||||
# Plot with a thicker line
|
||||
# Color depends on the signal strength:
|
||||
# - Red if signal strength is less than -60
|
||||
# - Orange if signal strength is less than -50
|
||||
# - Green if signal strength is greater than -50
|
||||
signal_color = "red" if signal < -60 else "orange" if signal < -50 else "green"
|
||||
plt.plot(self.signal_strengths, color="green")
|
||||
plt.theme("matrix")
|
||||
plt.title("WiFi Signal Strength Over Time")
|
||||
plt.xlabel("Time (seconds)")
|
||||
plt.ylabel("Signal Strength (dBm)")
|
||||
|
||||
# Show the plot
|
||||
plt.show()
|
||||
|
||||
# Print current metrics below the plot
|
||||
print(
|
||||
f"Signal: {signal:.1f} dBm | Bitrate: {metrics.bitrate} Mb/s | Power Save: {'On' if metrics.is_power_save_enabled else 'Off'}"
|
||||
)
|
||||
status = (
|
||||
"Strong signal"
|
||||
if signal > -50
|
||||
else (
|
||||
"Good signal"
|
||||
if signal > -60
|
||||
else "Weak signal" if signal > -70 else "Very bad signal"
|
||||
)
|
||||
)
|
||||
print(f"Status: {status}")
|
||||
summary = self.session.get_session_summary()
|
||||
print(summary)
|
||||
|
||||
self.session.add_metrics(metrics)
|
||||
|
||||
|
||||
def plot_metrics_live(
|
||||
get_metrics_func, adapter_name: str = "wlan0", interval: float = 1.0
|
||||
):
|
||||
"""
|
||||
Continuously plot metrics in real-time
|
||||
|
||||
Args:
|
||||
get_metrics_func: Function to get metrics
|
||||
adapter_name: Name of the network adapter
|
||||
interval: Update interval in seconds
|
||||
"""
|
||||
plotter = MetricsPlotter()
|
||||
|
||||
try:
|
||||
while True:
|
||||
metrics = get_metrics_func(adapter_name)
|
||||
plotter.update_plot(metrics)
|
||||
time.sleep(interval)
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopping metrics plotting...")
|
||||
print(plotter.session.get_session_summary())
|
||||
plotter.save_plot()
|
35
src/libs/session.py
Normal file
35
src/libs/session.py
Normal file
@ -0,0 +1,35 @@
|
||||
from typing import List
|
||||
from .metrics import Metrics
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class SessionMetrics:
|
||||
def __init__(self):
|
||||
self.signal_readings: List[float] = []
|
||||
self.bitrate_readings: List[float] = []
|
||||
self.start_time = datetime.now()
|
||||
|
||||
def add_metrics(self, metrics: Metrics):
|
||||
try:
|
||||
self.signal_readings.append(float(metrics.signal_strength))
|
||||
self.bitrate_readings.append(float(metrics.bitrate))
|
||||
except ValueError:
|
||||
print("Warning: Invalid metrics value encountered")
|
||||
|
||||
def get_session_summary(self) -> str:
|
||||
if not self.signal_readings or not self.bitrate_readings:
|
||||
return "No data collected in this session"
|
||||
|
||||
avg_signal = sum(self.signal_readings) / len(self.signal_readings)
|
||||
avg_bitrate = sum(self.bitrate_readings) / len(self.bitrate_readings)
|
||||
|
||||
duration = datetime.now() - self.start_time
|
||||
minutes = duration.total_seconds() / 60
|
||||
|
||||
return (
|
||||
f"Session Summary:\n"
|
||||
f"Duration: {minutes:.1f} minutes\n"
|
||||
f"Average Signal: {avg_signal:.1f} dBm\n"
|
||||
f"Average Bitrate: {avg_bitrate:.1f} Mb/s\n"
|
||||
f"Samples collected: {len(self.signal_readings)}"
|
||||
)
|
92
src/main.py
Normal file
92
src/main.py
Normal file
@ -0,0 +1,92 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import argparse
|
||||
from libs.metrics import Metrics
|
||||
from libs.plotter import plot_metrics_live
|
||||
|
||||
|
||||
def get_metrics(adapter_name: str = "wlan0") -> Metrics:
|
||||
"""
|
||||
Get the metrics of the network adapter
|
||||
"""
|
||||
# Execute iwconfig command and return the output
|
||||
iwconfig_output = subprocess.run(
|
||||
["iwconfig", adapter_name], capture_output=True, text=True
|
||||
)
|
||||
|
||||
output = iwconfig_output.stdout
|
||||
|
||||
# Initialize default values
|
||||
signal_strength = "0"
|
||||
bitrate = "0"
|
||||
is_power_save_enabled = False
|
||||
|
||||
# Try different possible formats for signal level
|
||||
try:
|
||||
if "Signal level=" in output:
|
||||
signal_strength = output.split("Signal level=")[1].split(" dBm")[0].strip()
|
||||
elif "Signal level" in output:
|
||||
signal_strength = output.split("Signal level")[1].split("dBm")[0].strip()
|
||||
elif "signal:" in output.lower():
|
||||
signal_strength = output.lower().split("signal:")[1].split("dBm")[0].strip()
|
||||
|
||||
# Try different possible formats for bit rate
|
||||
if "Bit Rate=" in output:
|
||||
bitrate = output.split("Bit Rate=")[1].split(" Mb/s")[0].strip()
|
||||
elif "Bit Rate:" in output:
|
||||
bitrate = output.split("Bit Rate:")[1].split(" Mb/s")[0].strip()
|
||||
elif "tx bitrate:" in output.lower():
|
||||
bitrate = output.lower().split("tx bitrate:")[1].split("mbit/s")[0].strip()
|
||||
|
||||
# Try different possible formats for power management
|
||||
if "Power Management:" in output:
|
||||
is_power_save_enabled = (
|
||||
"on" in output.split("Power Management:")[1].lower().strip()
|
||||
)
|
||||
elif "power management" in output.lower():
|
||||
is_power_save_enabled = (
|
||||
"on" in output.lower().split("power management")[1].strip()
|
||||
)
|
||||
|
||||
except (IndexError, ValueError) as e:
|
||||
print(f"Warning: Error parsing iwconfig output: {e}")
|
||||
print(f"Raw output: {output}")
|
||||
|
||||
return Metrics(signal_strength, bitrate, is_power_save_enabled)
|
||||
|
||||
|
||||
def get_active_interface():
|
||||
"""
|
||||
Get the active interface name
|
||||
"""
|
||||
all_interfaces = subprocess.run(["iwconfig"], capture_output=True, text=True)
|
||||
for interface in all_interfaces.stdout.split("\n"):
|
||||
if "ESSID" in interface:
|
||||
return interface.split(" ")[0].strip()
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--interface", type=str, help="The interface to use")
|
||||
args = parser.parse_args() # Check if we got an interface name
|
||||
if not args.interface:
|
||||
print("No interface name provided: we will try to find the active interface")
|
||||
try:
|
||||
active_interface = get_active_interface()
|
||||
print(f"The active interface is {active_interface}")
|
||||
except Exception as e:
|
||||
print(f"Error getting active interface: {e}")
|
||||
print("Using wlan0 as default interface")
|
||||
active_interface = "wlan0"
|
||||
else:
|
||||
print(f"Using interface {args.interface}")
|
||||
active_interface = args.interface
|
||||
# Execute iwconfig command
|
||||
metrics = get_metrics(active_interface)
|
||||
print(metrics)
|
||||
try:
|
||||
plot_metrics_live(get_metrics, active_interface)
|
||||
except KeyboardInterrupt:
|
||||
print("[+] Done")
|
Loading…
x
Reference in New Issue
Block a user