mirror of
https://github.com/tcsenpai/sigmon.git
synced 2025-06-06 03:05:31 +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