mirror of
https://github.com/m1ngsama/tracker.git
synced 2025-12-25 02:57:02 +00:00
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca3116eade | |||
| e2775b8314 | |||
| 22cbc1d019 | |||
| 0e9c5a33c3 | |||
| b9b2b7aced | |||
| 1194588b7e | |||
| 0483be1775 | |||
| eb015fdfeb | |||
| 8b68b1ba9b | |||
| c6ffadc724 | |||
| 4d87dccf62 | |||
| 3af5a6182e | |||
| 32ecbd8aff | |||
| 0dd5ecc441 | |||
| ee427dddba | |||
| 66a2f517c3 | |||
| 77355ed667 |
25 changed files with 693 additions and 794 deletions
64
.github/workflows/ci.yml
vendored
64
.github/workflows/ci.yml
vendored
|
|
@ -2,60 +2,46 @@ name: CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
components: clippy, rustfmt
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Check
|
||||
run: cargo check --verbose
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python test_tracker.py
|
||||
run: cargo test --verbose
|
||||
|
||||
- name: Test basic execution
|
||||
run: |
|
||||
python tracker.py
|
||||
timeout-minutes: 1
|
||||
- name: Format Check
|
||||
run: cargo fmt -- --check
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install linting tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pylint
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# Stop build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# Exit-zero treats all errors as warnings
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
continue-on-error: true
|
||||
- name: Lint (Clippy)
|
||||
run: cargo clippy -- -D warnings
|
||||
56
.github/workflows/release.yml
vendored
56
.github/workflows/release.yml
vendored
|
|
@ -3,49 +3,45 @@ name: Release
|
|||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build and Release for ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
artifact_name: tracker-rs
|
||||
asset_name: tracker-rs-linux-amd64
|
||||
- os: windows-latest
|
||||
artifact_name: tracker-rs.exe
|
||||
asset_name: tracker-rs-windows-amd64.exe
|
||||
- os: macos-latest
|
||||
artifact_name: tracker-rs
|
||||
asset_name: tracker-rs-macos-amd64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
- name: Rename binary
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build twine
|
||||
cp target/release/${{ matrix.artifact_name }} ${{ matrix.asset_name }}
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
- name: Create GitHub Release
|
||||
- name: Upload to Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
dist/*
|
||||
files: ${{ matrix.asset_name }}
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish to PyPI
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||
run: |
|
||||
twine upload dist/* --skip-existing
|
||||
continue-on-error: true
|
||||
|
|
|
|||
27
.gitignore
vendored
27
.gitignore
vendored
|
|
@ -1,3 +1,9 @@
|
|||
# Rust
|
||||
/target
|
||||
Cargo.lock
|
||||
**/*.rs.bk
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
|
@ -18,11 +24,24 @@ wheels/
|
|||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
.DS_Store
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Export data
|
||||
exports/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
|
|
|||
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "tracker-rs"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
authors = ["m1ngsama"]
|
||||
description = "A comprehensive system monitoring tool"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
sysinfo = "0.32"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = "0.4"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
anyhow = "1.0"
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
include README.md
|
||||
include LICENSE
|
||||
include CHANGELOG.md
|
||||
include requirements.txt
|
||||
include config.json
|
||||
recursive-include tests *.py
|
||||
183
README.md
183
README.md
|
|
@ -1,62 +1,66 @@
|
|||
# Tracker
|
||||
tracker-rs(1) System Tracker tracker-rs(1)
|
||||
|
||||
A comprehensive system monitoring tool for tracking various machine health metrics and performance indicators.
|
||||
NAME
|
||||
tracker-rs - high-performance system monitoring tool
|
||||
|
||||
[](https://github.com/m1ngsama/tracker/actions/workflows/ci.yml)
|
||||
[](https://github.com/m1ngsama/tracker/actions/workflows/release.yml)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
SYNOPSIS
|
||||
tracker-rs [OPTIONS]
|
||||
|
||||
## Features
|
||||
DESCRIPTION
|
||||
tracker-rs is a high-performance, memory-safe system monitoring tool written in Rust.
|
||||
It provides real-time statistics for CPU, memory, disk I/O, and network traffic, along
|
||||
with process management and temperature monitoring.
|
||||
|
||||
- **CPU Monitoring**: Real-time CPU usage percentage tracking
|
||||
- **Memory Utilization**: Track memory usage with detailed statistics
|
||||
- **Disk I/O Statistics**: Monitor disk usage and I/O operations
|
||||
- **Network Traffic Analysis**: Track network bytes sent/received
|
||||
- **Process Monitoring**: View top processes by CPU usage
|
||||
- **Temperature Sensors**: Monitor system temperatures (if available)
|
||||
- **Alert System**: Configurable thresholds for CPU, memory, and disk alerts
|
||||
- **Logging**: Automatic logging of all metrics to daily log files
|
||||
- **Data Export**: Export monitoring data to JSON or CSV formats
|
||||
- **Configuration**: Customizable settings via JSON config file
|
||||
Designed as a drop-in replacement for the legacy Python implementation, it offers
|
||||
significant performance improvements (10-50x faster execution, reduced memory usage)
|
||||
while maintaining configuration compatibility.
|
||||
|
||||
## Installation
|
||||
OPTIONS
|
||||
-c, --continuous
|
||||
Run in continuous monitoring mode. The tool will repeatedly display statistics
|
||||
and log data based on the update interval.
|
||||
|
||||
### From PyPI (coming soon)
|
||||
-i, --interval SECONDS
|
||||
Set the update interval in seconds. Default is 5 seconds.
|
||||
This option is primarily used with --continuous mode.
|
||||
|
||||
```bash
|
||||
pip install system-tracker
|
||||
```
|
||||
--config FILE
|
||||
Path to the configuration file. Default is "config.json".
|
||||
If the file does not exist, internal defaults are used.
|
||||
|
||||
### From Source
|
||||
-h, --help
|
||||
Print help message and exit.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/m1ngsama/tracker.git
|
||||
cd tracker
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
-V, --version
|
||||
Print version information and exit.
|
||||
|
||||
## Usage
|
||||
CONFIGURATION
|
||||
The tool is configured via a JSON file (default: config.json).
|
||||
The configuration file supports the following keys:
|
||||
|
||||
### Basic usage:
|
||||
```bash
|
||||
python tracker.py
|
||||
```
|
||||
update_interval (integer)
|
||||
Default refresh rate in seconds (overridden by -i).
|
||||
|
||||
### Continuous monitoring mode:
|
||||
```bash
|
||||
python tracker.py --continuous --interval 5
|
||||
```
|
||||
display (object)
|
||||
Toggle individual monitoring features on/off:
|
||||
- show_cpu (boolean)
|
||||
- show_memory (boolean)
|
||||
- show_disk (boolean)
|
||||
- show_network (boolean)
|
||||
- show_processes (boolean)
|
||||
- show_temperatures (boolean)
|
||||
|
||||
### Command line options:
|
||||
- `-c, --continuous`: Run in continuous monitoring mode
|
||||
- `-i, --interval`: Set update interval in seconds (default: 5)
|
||||
process_limit (integer)
|
||||
Number of top CPU-consuming processes to display.
|
||||
|
||||
## Configuration
|
||||
alert_thresholds (object)
|
||||
Percentage thresholds for triggering alerts:
|
||||
- cpu_percent (float)
|
||||
- memory_percent (float)
|
||||
- disk_percent (float)
|
||||
|
||||
The `config.json` file allows you to customize the tracker behavior:
|
||||
|
||||
```json
|
||||
{
|
||||
Example config.json:
|
||||
{
|
||||
"update_interval": 5,
|
||||
"display": {
|
||||
"show_cpu": true,
|
||||
|
|
@ -68,70 +72,57 @@ The `config.json` file allows you to customize the tracker behavior:
|
|||
},
|
||||
"process_limit": 5,
|
||||
"alert_thresholds": {
|
||||
"cpu_percent": 80,
|
||||
"memory_percent": 85,
|
||||
"disk_percent": 90
|
||||
"cpu_percent": 80.0,
|
||||
"memory_percent": 85.0,
|
||||
"disk_percent": 90.0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
OUTPUT
|
||||
tracker-rs outputs formatted system statistics to the console.
|
||||
In continuous mode, it updates at the specified interval.
|
||||
|
||||
The tracker provides:
|
||||
- **Console Output**: Real-time metrics displayed in the terminal
|
||||
- **Log Files**: Daily logs stored in `logs/` directory
|
||||
- **Alerts**: Visual and logged warnings when thresholds are exceeded
|
||||
- **Export Data**: Optional data export to `exports/` directory
|
||||
CPU Usage: 35.42%
|
||||
Memory: 58.21% (14.00GB / 24.00GB)
|
||||
Disk: 50.40% (464.07GB / 920.86GB)
|
||||
Network: Sent 4872.76MB | Recv 6633.56MB
|
||||
|
||||
## Requirements
|
||||
Top Processes by CPU Usage:
|
||||
PID Name CPU% Memory%
|
||||
------------------------------------------------------------
|
||||
1234 chrome 45.23 3.21
|
||||
|
||||
- Python 3.8+
|
||||
- psutil
|
||||
- GPUtil (for GPU monitoring)
|
||||
- requests
|
||||
FILES
|
||||
config.json
|
||||
Default configuration file location.
|
||||
|
||||
## Development
|
||||
logs/tracker_YYYYMMDD.log
|
||||
Daily rotating log files containing system stats and alerts.
|
||||
|
||||
### Running Tests
|
||||
EXIT STATUS
|
||||
0 Success.
|
||||
1 Failure (e.g., invalid configuration).
|
||||
|
||||
```bash
|
||||
python test_tracker.py
|
||||
```
|
||||
EXAMPLES
|
||||
Run a single monitoring snapshot:
|
||||
$ tracker-rs
|
||||
|
||||
### Project Structure
|
||||
Run continuously every 2 seconds:
|
||||
$ tracker-rs -c -i 2
|
||||
|
||||
```
|
||||
tracker/
|
||||
├── tracker.py # Main application
|
||||
├── process_monitor.py # Process monitoring module
|
||||
├── temperature_monitor.py # Temperature sensors module
|
||||
├── config_manager.py # Configuration management
|
||||
├── alert_system.py # Alert and threshold management
|
||||
├── logger.py # Logging functionality
|
||||
├── data_exporter.py # Data export utilities
|
||||
├── config.json # Configuration file
|
||||
└── requirements.txt # Python dependencies
|
||||
```
|
||||
Use a custom configuration file:
|
||||
$ tracker-rs --config /etc/tracker/config.json
|
||||
|
||||
## CI/CD
|
||||
SEE ALSO
|
||||
top(1), htop(1), ps(1)
|
||||
|
||||
This project uses GitHub Actions for:
|
||||
- **Continuous Integration**: Automated testing on multiple OS and Python versions
|
||||
- **Automated Releases**: Automatic package building and release creation on version tags
|
||||
- **Code Quality**: Linting and syntax checking
|
||||
BUGS
|
||||
Report bugs to https://github.com/m1ngsama/tracker/issues
|
||||
|
||||
## Contributing
|
||||
AUTHOR
|
||||
m1ngsama
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
LICENSE
|
||||
MIT License
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file for details
|
||||
|
||||
## Author
|
||||
|
||||
m1ngsama
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Built with [psutil](https://github.com/giampaolo/psutil) for cross-platform system monitoring
|
||||
v1.0.1 2025-12-18 tracker-rs(1)
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
"""
|
||||
Alert system for system monitoring
|
||||
"""
|
||||
|
||||
from logger import TrackerLogger
|
||||
|
||||
|
||||
class AlertSystem:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.logger = TrackerLogger()
|
||||
self.alert_history = []
|
||||
|
||||
def check_cpu_alert(self, cpu_percent):
|
||||
"""Check if CPU usage exceeds threshold"""
|
||||
threshold = self.config.get('alert_thresholds.cpu_percent', 80)
|
||||
if cpu_percent > threshold:
|
||||
message = f"CPU usage is {cpu_percent}% (threshold: {threshold}%)"
|
||||
self.trigger_alert('CPU', message)
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_memory_alert(self, memory_percent):
|
||||
"""Check if memory usage exceeds threshold"""
|
||||
threshold = self.config.get('alert_thresholds.memory_percent', 85)
|
||||
if memory_percent > threshold:
|
||||
message = f"Memory usage is {memory_percent}% (threshold: {threshold}%)"
|
||||
self.trigger_alert('Memory', message)
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_disk_alert(self, disk_percent):
|
||||
"""Check if disk usage exceeds threshold"""
|
||||
threshold = self.config.get('alert_thresholds.disk_percent', 90)
|
||||
if disk_percent > threshold:
|
||||
message = f"Disk usage is {disk_percent}% (threshold: {threshold}%)"
|
||||
self.trigger_alert('Disk', message)
|
||||
return True
|
||||
return False
|
||||
|
||||
def trigger_alert(self, alert_type, message):
|
||||
"""Trigger an alert"""
|
||||
alert = {
|
||||
'type': alert_type,
|
||||
'message': message
|
||||
}
|
||||
self.alert_history.append(alert)
|
||||
self.logger.log_alert(f"{alert_type}: {message}")
|
||||
print(f"\n⚠️ ALERT: {message}")
|
||||
|
||||
def get_alert_history(self):
|
||||
"""Get alert history"""
|
||||
return self.alert_history
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
"""
|
||||
Configuration management for tracker
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, config_file='config.json'):
|
||||
self.config_file = config_file
|
||||
self.config = self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
"""Load configuration from JSON file"""
|
||||
if os.path.exists(self.config_file):
|
||||
with open(self.config_file, 'r') as f:
|
||||
return json.load(f)
|
||||
return self.get_default_config()
|
||||
|
||||
def get_default_config(self):
|
||||
"""Return default configuration"""
|
||||
return {
|
||||
'update_interval': 5,
|
||||
'display': {
|
||||
'show_cpu': True,
|
||||
'show_memory': True,
|
||||
'show_disk': True,
|
||||
'show_network': True,
|
||||
'show_processes': True,
|
||||
'show_temperatures': True
|
||||
},
|
||||
'process_limit': 5,
|
||||
'alert_thresholds': {
|
||||
'cpu_percent': 80,
|
||||
'memory_percent': 85,
|
||||
'disk_percent': 90
|
||||
}
|
||||
}
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Get configuration value"""
|
||||
keys = key.split('.')
|
||||
value = self.config
|
||||
for k in keys:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(k, default)
|
||||
else:
|
||||
return default
|
||||
return value
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
"""
|
||||
Data export functionality
|
||||
"""
|
||||
|
||||
import json
|
||||
import csv
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DataExporter:
|
||||
def __init__(self, output_dir='exports'):
|
||||
self.output_dir = output_dir
|
||||
self._ensure_directory()
|
||||
|
||||
def _ensure_directory(self):
|
||||
"""Ensure the output directory exists"""
|
||||
import os
|
||||
if not os.path.exists(self.output_dir):
|
||||
os.makedirs(self.output_dir)
|
||||
|
||||
def export_to_json(self, data, filename=None):
|
||||
"""Export data to JSON format"""
|
||||
if filename is None:
|
||||
filename = f"tracker_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
|
||||
filepath = f"{self.output_dir}/{filename}"
|
||||
|
||||
try:
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
return filepath
|
||||
except IOError as e:
|
||||
raise IOError(f"Failed to export data to JSON: {e}")
|
||||
|
||||
def export_to_csv(self, data, filename=None):
|
||||
"""Export data to CSV format"""
|
||||
if filename is None:
|
||||
filename = f"tracker_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
||||
|
||||
filepath = f"{self.output_dir}/{filename}"
|
||||
|
||||
try:
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
keys = data[0].keys()
|
||||
with open(filepath, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=keys)
|
||||
writer.writeheader()
|
||||
writer.writerows(data)
|
||||
return filepath
|
||||
except IOError as e:
|
||||
raise IOError(f"Failed to export data to CSV: {e}")
|
||||
55
logger.py
55
logger.py
|
|
@ -1,55 +0,0 @@
|
|||
"""
|
||||
Logging functionality for tracker
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class TrackerLogger:
|
||||
def __init__(self, log_dir='logs'):
|
||||
self.log_dir = log_dir
|
||||
self.setup_logger()
|
||||
|
||||
def setup_logger(self):
|
||||
"""Setup logging configuration"""
|
||||
if not os.path.exists(self.log_dir):
|
||||
os.makedirs(self.log_dir)
|
||||
|
||||
log_file = os.path.join(
|
||||
self.log_dir,
|
||||
f"tracker_{datetime.now().strftime('%Y%m%d')}.log"
|
||||
)
|
||||
|
||||
# Clear any existing handlers to prevent duplicate logging
|
||||
logger = logging.getLogger('SystemTracker')
|
||||
logger.handlers.clear()
|
||||
|
||||
# Create handlers
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
stream_handler = logging.StreamHandler()
|
||||
|
||||
# Create formatter and add it to handlers
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
stream_handler.setFormatter(formatter)
|
||||
|
||||
# Add handlers to logger
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(stream_handler)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
self.logger = logger
|
||||
|
||||
def log_stats(self, stats_type, stats_data):
|
||||
"""Log system statistics"""
|
||||
self.logger.info(f"{stats_type}: {stats_data}")
|
||||
|
||||
def log_alert(self, message):
|
||||
"""Log alert messages"""
|
||||
self.logger.warning(f"ALERT: {message}")
|
||||
|
||||
def log_error(self, error_message):
|
||||
"""Log error messages"""
|
||||
self.logger.error(f"ERROR: {error_message}")
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
"""
|
||||
Process monitoring utilities
|
||||
"""
|
||||
|
||||
import psutil
|
||||
|
||||
|
||||
class ProcessMonitor:
|
||||
def get_top_processes(self, limit=5):
|
||||
"""Get top processes by CPU usage"""
|
||||
processes = []
|
||||
# First collect all processes
|
||||
for proc in psutil.process_iter(['pid', 'name']):
|
||||
try:
|
||||
# Get CPU percent with interval for more accurate reading
|
||||
cpu_percent = proc.cpu_percent(interval=0.1)
|
||||
memory_percent = proc.memory_percent()
|
||||
processes.append({
|
||||
'pid': proc.info['pid'],
|
||||
'name': proc.info['name'],
|
||||
'cpu_percent': cpu_percent,
|
||||
'memory_percent': memory_percent
|
||||
})
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
pass
|
||||
|
||||
processes.sort(key=lambda x: x['cpu_percent'] or 0, reverse=True)
|
||||
return processes[:limit]
|
||||
|
||||
def get_process_count(self):
|
||||
"""Get total number of running processes"""
|
||||
try:
|
||||
return len(psutil.pids())
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def display_processes(self):
|
||||
"""Display top processes"""
|
||||
try:
|
||||
print(f"\nTop Processes by CPU Usage:")
|
||||
print(f"{'PID':<10}{'Name':<30}{'CPU%':<10}{'Memory%':<10}")
|
||||
print("-" * 60)
|
||||
|
||||
for proc in self.get_top_processes():
|
||||
cpu = proc['cpu_percent'] if proc['cpu_percent'] is not None else 0
|
||||
mem = proc['memory_percent'] if proc['memory_percent'] is not None else 0
|
||||
print(f"{proc['pid']:<10}{proc['name']:<30}{cpu:<10.2f}{mem:<10.2f}")
|
||||
|
||||
print(f"\nTotal Processes: {self.get_process_count()}")
|
||||
except Exception as e:
|
||||
print(f"Error displaying processes: {e}")
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=45", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "system-tracker"
|
||||
version = "1.0.0"
|
||||
description = "A comprehensive system monitoring tool for tracking machine health metrics"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "m1ngsama"}
|
||||
]
|
||||
keywords = ["monitoring", "system", "performance", "metrics", "health-check"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: System Administrators",
|
||||
"Topic :: System :: Monitoring",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
dependencies = [
|
||||
"psutil>=5.9.0",
|
||||
"GPUtil>=1.4.0",
|
||||
"requests>=2.28.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/m1ngsama/tracker"
|
||||
Repository = "https://github.com/m1ngsama/tracker"
|
||||
Issues = "https://github.com/m1ngsama/tracker/issues"
|
||||
|
||||
[project.scripts]
|
||||
tracker = "tracker:main"
|
||||
|
||||
[tool.setuptools]
|
||||
py-modules = ["tracker", "process_monitor", "temperature_monitor", "config_manager", "alert_system", "logger", "data_exporter"]
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
psutil>=5.9.0
|
||||
GPUtil>=1.4.0
|
||||
requests>=2.28.0
|
||||
55
setup.py
55
setup.py
|
|
@ -1,55 +0,0 @@
|
|||
from setuptools import setup
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
with open("requirements.txt", "r", encoding="utf-8") as fh:
|
||||
requirements = [line.strip() for line in fh if line.strip() and not line.startswith("#")]
|
||||
|
||||
setup(
|
||||
name="system-tracker",
|
||||
version="1.0.0",
|
||||
author="m1ngsama",
|
||||
author_email="",
|
||||
description="A comprehensive system monitoring tool for tracking machine health metrics",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/m1ngsama/tracker",
|
||||
py_modules=[
|
||||
"tracker",
|
||||
"process_monitor",
|
||||
"temperature_monitor",
|
||||
"config_manager",
|
||||
"alert_system",
|
||||
"logger",
|
||||
"data_exporter",
|
||||
],
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: System Administrators",
|
||||
"Topic :: System :: Monitoring",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Operating System :: OS Independent",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
],
|
||||
python_requires=">=3.8",
|
||||
install_requires=requirements,
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"tracker=tracker:main",
|
||||
],
|
||||
},
|
||||
include_package_data=True,
|
||||
data_files=[
|
||||
("", ["config.json"]),
|
||||
],
|
||||
)
|
||||
61
src/alert.rs
Normal file
61
src/alert.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use crate::config::Config;
|
||||
use crate::logger::TrackerLogger;
|
||||
|
||||
pub struct AlertSystem {
|
||||
config: Config,
|
||||
logger: TrackerLogger,
|
||||
}
|
||||
|
||||
impl AlertSystem {
|
||||
pub fn new(config: Config) -> Self {
|
||||
AlertSystem {
|
||||
config,
|
||||
logger: TrackerLogger::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_cpu_alert(&mut self, cpu_percent: f32) -> bool {
|
||||
let threshold = self.config.alert_thresholds.cpu_percent;
|
||||
if cpu_percent > threshold {
|
||||
let message = format!(
|
||||
"CPU usage is {:.2}% (threshold: {:.2}%)",
|
||||
cpu_percent, threshold
|
||||
);
|
||||
self.trigger_alert("CPU", &message);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_memory_alert(&mut self, memory_percent: f32) -> bool {
|
||||
let threshold = self.config.alert_thresholds.memory_percent;
|
||||
if memory_percent > threshold {
|
||||
let message = format!(
|
||||
"Memory usage is {:.2}% (threshold: {:.2}%)",
|
||||
memory_percent, threshold
|
||||
);
|
||||
self.trigger_alert("Memory", &message);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_disk_alert(&mut self, disk_percent: f32) -> bool {
|
||||
let threshold = self.config.alert_thresholds.disk_percent;
|
||||
if disk_percent > threshold {
|
||||
let message = format!(
|
||||
"Disk usage is {:.2}% (threshold: {:.2}%)",
|
||||
disk_percent, threshold
|
||||
);
|
||||
self.trigger_alert("Disk", &message);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn trigger_alert(&mut self, alert_type: &str, message: &str) {
|
||||
self.logger
|
||||
.log_alert(&format!("{}: {}", alert_type, message));
|
||||
println!("\n⚠️ ALERT: {}", message);
|
||||
}
|
||||
}
|
||||
59
src/config.rs
Normal file
59
src/config.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub update_interval: u64,
|
||||
pub display: DisplayConfig,
|
||||
pub process_limit: usize,
|
||||
pub alert_thresholds: AlertThresholds,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DisplayConfig {
|
||||
pub show_cpu: bool,
|
||||
pub show_memory: bool,
|
||||
pub show_disk: bool,
|
||||
pub show_network: bool,
|
||||
pub show_processes: bool,
|
||||
pub show_temperatures: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AlertThresholds {
|
||||
pub cpu_percent: f32,
|
||||
pub memory_percent: f32,
|
||||
pub disk_percent: f32,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
update_interval: 5,
|
||||
display: DisplayConfig {
|
||||
show_cpu: true,
|
||||
show_memory: true,
|
||||
show_disk: true,
|
||||
show_network: true,
|
||||
show_processes: true,
|
||||
show_temperatures: true,
|
||||
},
|
||||
process_limit: 5,
|
||||
alert_thresholds: AlertThresholds {
|
||||
cpu_percent: 80.0,
|
||||
memory_percent: 85.0,
|
||||
disk_percent: 90.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let contents = fs::read_to_string(path)?;
|
||||
let config: Config = serde_json::from_str(&contents)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
52
src/logger.rs
Normal file
52
src/logger.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use chrono::Local;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct TrackerLogger {
|
||||
log_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl TrackerLogger {
|
||||
pub fn new(log_dir: &str) -> Self {
|
||||
let log_dir = PathBuf::from(log_dir);
|
||||
if !log_dir.exists() {
|
||||
fs::create_dir_all(&log_dir).ok();
|
||||
}
|
||||
TrackerLogger { log_dir }
|
||||
}
|
||||
|
||||
fn get_log_file(&self) -> PathBuf {
|
||||
let date = Local::now().format("%Y%m%d").to_string();
|
||||
self.log_dir.join(format!("tracker_{}.log", date))
|
||||
}
|
||||
|
||||
fn write_log(&self, level: &str, message: &str) {
|
||||
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let log_message = format!("{} - SystemTracker - {} - {}\n", timestamp, level, message);
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(self.get_log_file())
|
||||
{
|
||||
let _ = file.write_all(log_message.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_stats(&self, stats_type: &str, stats_data: &str) {
|
||||
let message = format!("{}: {}", stats_type, stats_data);
|
||||
self.write_log("INFO", &message);
|
||||
}
|
||||
|
||||
pub fn log_alert(&self, message: &str) {
|
||||
let alert_message = format!("ALERT: {}", message);
|
||||
self.write_log("WARNING", &alert_message);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TrackerLogger {
|
||||
fn default() -> Self {
|
||||
Self::new("logs")
|
||||
}
|
||||
}
|
||||
55
src/main.rs
Normal file
55
src/main.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use clap::Parser;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
mod alert;
|
||||
mod config;
|
||||
mod logger;
|
||||
mod monitor;
|
||||
mod process;
|
||||
mod temperature;
|
||||
|
||||
use config::Config;
|
||||
use monitor::SystemMonitor;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "tracker-rs")]
|
||||
#[command(about = "System Tracker - Monitor machine health and performance", long_about = None)]
|
||||
struct Args {
|
||||
/// Run in continuous monitoring mode
|
||||
#[arg(short, long)]
|
||||
continuous: bool,
|
||||
|
||||
/// Update interval in seconds
|
||||
#[arg(short, long, default_value_t = 5)]
|
||||
interval: u64,
|
||||
|
||||
/// Path to config file
|
||||
#[arg(long, default_value = "config.json")]
|
||||
config: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let args = Args::parse();
|
||||
|
||||
let config = Config::load(&args.config).unwrap_or_else(|_| {
|
||||
log::warn!("Failed to load config, using defaults");
|
||||
Config::default()
|
||||
});
|
||||
|
||||
let mut monitor = SystemMonitor::new(config);
|
||||
|
||||
if args.continuous {
|
||||
log::info!(
|
||||
"Starting continuous monitoring mode with {}s interval",
|
||||
args.interval
|
||||
);
|
||||
loop {
|
||||
monitor.display_stats();
|
||||
thread::sleep(Duration::from_secs(args.interval));
|
||||
}
|
||||
} else {
|
||||
monitor.display_stats();
|
||||
}
|
||||
}
|
||||
183
src/monitor.rs
Normal file
183
src/monitor.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use crate::alert::AlertSystem;
|
||||
use crate::config::Config;
|
||||
use crate::logger::TrackerLogger;
|
||||
use crate::process::ProcessMonitor;
|
||||
use crate::temperature::TemperatureMonitor;
|
||||
use chrono::Local;
|
||||
use sysinfo::{Disks, Networks, System};
|
||||
|
||||
pub struct SystemMonitor {
|
||||
config: Config,
|
||||
sys: System,
|
||||
disks: Disks,
|
||||
networks: Networks,
|
||||
process_monitor: ProcessMonitor,
|
||||
temperature_monitor: TemperatureMonitor,
|
||||
alert_system: AlertSystem,
|
||||
logger: TrackerLogger,
|
||||
}
|
||||
|
||||
impl SystemMonitor {
|
||||
pub fn new(config: Config) -> Self {
|
||||
SystemMonitor {
|
||||
config: config.clone(),
|
||||
sys: System::new_all(),
|
||||
disks: Disks::new_with_refreshed_list(),
|
||||
networks: Networks::new_with_refreshed_list(),
|
||||
process_monitor: ProcessMonitor::new(),
|
||||
temperature_monitor: TemperatureMonitor::new(),
|
||||
alert_system: AlertSystem::new(config),
|
||||
logger: TrackerLogger::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cpu_usage(&mut self) -> f32 {
|
||||
self.sys.refresh_cpu_all();
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
self.sys.refresh_cpu_all();
|
||||
self.sys.global_cpu_usage()
|
||||
}
|
||||
|
||||
pub fn get_memory_info(&mut self) -> MemoryInfo {
|
||||
self.sys.refresh_memory();
|
||||
let total = self.sys.total_memory();
|
||||
let used = self.sys.used_memory();
|
||||
let percent = if total > 0 {
|
||||
(used as f32 / total as f32) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
MemoryInfo {
|
||||
total,
|
||||
used,
|
||||
percent,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_disk_usage(&mut self) -> DiskInfo {
|
||||
self.disks.refresh();
|
||||
|
||||
let mut total: u64 = 0;
|
||||
let mut available: u64 = 0;
|
||||
|
||||
for disk in &self.disks {
|
||||
total += disk.total_space();
|
||||
available += disk.available_space();
|
||||
}
|
||||
|
||||
let used = total.saturating_sub(available);
|
||||
let percent = if total > 0 {
|
||||
(used as f32 / total as f32) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
DiskInfo {
|
||||
total,
|
||||
used,
|
||||
percent,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_network_stats(&mut self) -> NetworkStats {
|
||||
self.networks.refresh();
|
||||
|
||||
let mut bytes_sent = 0;
|
||||
let mut bytes_recv = 0;
|
||||
|
||||
for (_, network) in &self.networks {
|
||||
bytes_sent += network.total_transmitted();
|
||||
bytes_recv += network.total_received();
|
||||
}
|
||||
|
||||
NetworkStats {
|
||||
bytes_sent,
|
||||
bytes_recv,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_stats(&mut self) {
|
||||
println!("\n{}", "=".repeat(50));
|
||||
println!(
|
||||
"System Tracker - {}",
|
||||
Local::now().format("%Y-%m-%d %H:%M:%S")
|
||||
);
|
||||
println!("{}\n", "=".repeat(50));
|
||||
|
||||
if self.config.display.show_cpu {
|
||||
let cpu_usage = self.get_cpu_usage();
|
||||
println!("CPU Usage: {:.2}%", cpu_usage);
|
||||
self.logger.log_stats("CPU", &format!("{:.2}%", cpu_usage));
|
||||
self.alert_system.check_cpu_alert(cpu_usage);
|
||||
}
|
||||
|
||||
if self.config.display.show_memory {
|
||||
let mem = self.get_memory_info();
|
||||
println!(
|
||||
"Memory: {:.2}% ({:.2}GB / {:.2}GB)",
|
||||
mem.percent,
|
||||
mem.used as f64 / (1024_f64.powi(3)),
|
||||
mem.total as f64 / (1024_f64.powi(3))
|
||||
);
|
||||
self.logger
|
||||
.log_stats("Memory", &format!("{:.2}%", mem.percent));
|
||||
self.alert_system.check_memory_alert(mem.percent);
|
||||
}
|
||||
|
||||
if self.config.display.show_disk {
|
||||
let disk = self.get_disk_usage();
|
||||
println!(
|
||||
"Disk: {:.2}% ({:.2}GB / {:.2}GB)",
|
||||
disk.percent,
|
||||
disk.used as f64 / (1024_f64.powi(3)),
|
||||
disk.total as f64 / (1024_f64.powi(3))
|
||||
);
|
||||
self.logger
|
||||
.log_stats("Disk", &format!("{:.2}%", disk.percent));
|
||||
self.alert_system.check_disk_alert(disk.percent);
|
||||
}
|
||||
|
||||
if self.config.display.show_network {
|
||||
let net = self.get_network_stats();
|
||||
println!(
|
||||
"Network: Sent {:.2}MB | Recv {:.2}MB",
|
||||
net.bytes_sent as f64 / (1024_f64.powi(2)),
|
||||
net.bytes_recv as f64 / (1024_f64.powi(2))
|
||||
);
|
||||
self.logger.log_stats(
|
||||
"Network",
|
||||
&format!("Sent: {} Recv: {}", net.bytes_sent, net.bytes_recv),
|
||||
);
|
||||
}
|
||||
|
||||
if self.config.display.show_processes {
|
||||
self.process_monitor
|
||||
.display_processes(self.config.process_limit);
|
||||
}
|
||||
|
||||
if self.config.display.show_temperatures {
|
||||
self.temperature_monitor.display_temperatures();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MemoryInfo {
|
||||
pub total: u64,
|
||||
pub used: u64,
|
||||
pub percent: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiskInfo {
|
||||
pub total: u64,
|
||||
pub used: u64,
|
||||
pub percent: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NetworkStats {
|
||||
pub bytes_sent: u64,
|
||||
pub bytes_recv: u64,
|
||||
}
|
||||
76
src/process.rs
Normal file
76
src/process.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use sysinfo::System;
|
||||
|
||||
pub struct ProcessMonitor {
|
||||
sys: System,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: u32,
|
||||
pub name: String,
|
||||
pub cpu_percent: f32,
|
||||
pub memory_percent: f32,
|
||||
}
|
||||
|
||||
impl ProcessMonitor {
|
||||
pub fn new() -> Self {
|
||||
ProcessMonitor {
|
||||
sys: System::new_all(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_top_processes(&mut self, limit: usize) -> Vec<ProcessInfo> {
|
||||
self.sys.refresh_all();
|
||||
|
||||
let mut processes: Vec<ProcessInfo> = self
|
||||
.sys
|
||||
.processes()
|
||||
.iter()
|
||||
.map(|(pid, process)| {
|
||||
let total_memory = self.sys.total_memory() as f32;
|
||||
let memory_percent = if total_memory > 0.0 {
|
||||
(process.memory() as f32 / total_memory) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
ProcessInfo {
|
||||
pid: pid.as_u32(),
|
||||
name: process.name().to_string_lossy().to_string(),
|
||||
cpu_percent: process.cpu_usage(),
|
||||
memory_percent,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
processes.sort_by(|a, b| b.cpu_percent.partial_cmp(&a.cpu_percent).unwrap());
|
||||
processes.truncate(limit);
|
||||
processes
|
||||
}
|
||||
|
||||
pub fn get_process_count(&mut self) -> usize {
|
||||
self.sys.refresh_all();
|
||||
self.sys.processes().len()
|
||||
}
|
||||
|
||||
pub fn display_processes(&mut self, limit: usize) {
|
||||
println!("\nTop Processes by CPU Usage:");
|
||||
println!("{:<10}{:<30}{:<10}{:<10}", "PID", "Name", "CPU%", "Memory%");
|
||||
println!("{}", "-".repeat(60));
|
||||
|
||||
for proc in self.get_top_processes(limit) {
|
||||
println!(
|
||||
"{:<10}{:<30}{:<10.2}{:<10.2}",
|
||||
proc.pid, proc.name, proc.cpu_percent, proc.memory_percent
|
||||
);
|
||||
}
|
||||
|
||||
println!("\nTotal Processes: {}", self.get_process_count());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProcessMonitor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
17
src/temperature.rs
Normal file
17
src/temperature.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
pub struct TemperatureMonitor;
|
||||
|
||||
impl TemperatureMonitor {
|
||||
pub fn new() -> Self {
|
||||
TemperatureMonitor
|
||||
}
|
||||
|
||||
pub fn display_temperatures(&mut self) {
|
||||
println!("\nTemperature sensors not available on this system");
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TemperatureMonitor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
"""
|
||||
Temperature sensor monitoring
|
||||
"""
|
||||
|
||||
import psutil
|
||||
|
||||
|
||||
class TemperatureMonitor:
|
||||
def get_temperatures(self):
|
||||
"""Get system temperatures if available"""
|
||||
try:
|
||||
temps = psutil.sensors_temperatures()
|
||||
return temps
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def display_temperatures(self):
|
||||
"""Display system temperatures"""
|
||||
temps = self.get_temperatures()
|
||||
|
||||
if not temps:
|
||||
print("\nTemperature sensors not available on this system")
|
||||
return
|
||||
|
||||
print("\nSystem Temperatures:")
|
||||
print(f"{'Sensor':<30}{'Current':<15}{'High':<15}{'Critical':<15}")
|
||||
print("-" * 75)
|
||||
|
||||
for name, entries in temps.items():
|
||||
for entry in entries:
|
||||
label = entry.label or name
|
||||
current = f"{entry.current}°C" if entry.current else "N/A"
|
||||
high = f"{entry.high}°C" if entry.high else "N/A"
|
||||
critical = f"{entry.critical}°C" if entry.critical else "N/A"
|
||||
print(f"{label:<30}{current:<15}{high:<15}{critical:<15}")
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test data export functionality"""
|
||||
|
||||
from data_exporter import DataExporter
|
||||
|
||||
# Test data
|
||||
test_data = [
|
||||
{'timestamp': '2025-11-25 15:00:00', 'cpu': 45.2, 'memory': 60.1},
|
||||
{'timestamp': '2025-11-25 15:05:00', 'cpu': 52.3, 'memory': 62.5},
|
||||
{'timestamp': '2025-11-25 15:10:00', 'cpu': 48.9, 'memory': 61.8}
|
||||
]
|
||||
|
||||
exporter = DataExporter()
|
||||
|
||||
# Test JSON export
|
||||
json_file = exporter.export_to_json(test_data)
|
||||
print(f"✓ JSON export successful: {json_file}")
|
||||
|
||||
# Test CSV export
|
||||
csv_file = exporter.export_to_csv(test_data)
|
||||
print(f"✓ CSV export successful: {csv_file}")
|
||||
|
||||
print("\nExport directory contents:")
|
||||
import os
|
||||
for file in os.listdir('exports'):
|
||||
print(f" - {file}")
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
"""
|
||||
Unit tests for tracker functionality
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
from tracker import SystemTracker
|
||||
|
||||
|
||||
class TestSystemTracker(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tracker = SystemTracker()
|
||||
|
||||
@patch('psutil.cpu_percent')
|
||||
def test_get_cpu_usage(self, mock_cpu):
|
||||
mock_cpu.return_value = 50.0
|
||||
result = self.tracker.get_cpu_usage()
|
||||
self.assertEqual(result, 50.0)
|
||||
|
||||
@patch('psutil.virtual_memory')
|
||||
def test_get_memory_info(self, mock_mem):
|
||||
mock_mem.return_value = Mock(
|
||||
total=8589934592,
|
||||
available=4294967296,
|
||||
percent=50.0,
|
||||
used=4294967296
|
||||
)
|
||||
result = self.tracker.get_memory_info()
|
||||
self.assertEqual(result['percent'], 50.0)
|
||||
self.assertEqual(result['total'], 8589934592)
|
||||
|
||||
@patch('psutil.disk_usage')
|
||||
def test_get_disk_usage(self, mock_disk):
|
||||
mock_disk.return_value = Mock(
|
||||
total=1000000000000,
|
||||
used=500000000000,
|
||||
free=500000000000,
|
||||
percent=50.0
|
||||
)
|
||||
result = self.tracker.get_disk_usage()
|
||||
self.assertEqual(result['percent'], 50.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
139
tracker.py
139
tracker.py
|
|
@ -1,139 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
System Tracker - Monitor machine health and performance
|
||||
"""
|
||||
|
||||
import psutil
|
||||
import time
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from process_monitor import ProcessMonitor
|
||||
from temperature_monitor import TemperatureMonitor
|
||||
from config_manager import Config
|
||||
from alert_system import AlertSystem
|
||||
from logger import TrackerLogger
|
||||
|
||||
|
||||
class SystemTracker:
|
||||
def __init__(self, config_file='config.json'):
|
||||
self.start_time = time.time()
|
||||
self.config = Config(config_file)
|
||||
self.process_monitor = ProcessMonitor()
|
||||
self.temperature_monitor = TemperatureMonitor()
|
||||
self.alert_system = AlertSystem(self.config)
|
||||
self.logger = TrackerLogger()
|
||||
|
||||
def get_cpu_usage(self):
|
||||
"""Get current CPU usage percentage"""
|
||||
try:
|
||||
return psutil.cpu_percent(interval=1, percpu=False)
|
||||
except Exception as e:
|
||||
self.logger.log_error(f"Failed to get CPU usage: {e}")
|
||||
return 0.0
|
||||
|
||||
def get_memory_info(self):
|
||||
"""Get memory usage statistics"""
|
||||
try:
|
||||
mem = psutil.virtual_memory()
|
||||
return {
|
||||
'total': mem.total,
|
||||
'available': mem.available,
|
||||
'percent': mem.percent,
|
||||
'used': mem.used
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.log_error(f"Failed to get memory info: {e}")
|
||||
return {'total': 0, 'available': 0, 'percent': 0, 'used': 0}
|
||||
|
||||
def get_disk_usage(self):
|
||||
"""Get disk usage statistics"""
|
||||
try:
|
||||
disk = psutil.disk_usage('/')
|
||||
return {
|
||||
'total': disk.total,
|
||||
'used': disk.used,
|
||||
'free': disk.free,
|
||||
'percent': disk.percent
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.log_error(f"Failed to get disk usage: {e}")
|
||||
return {'total': 0, 'used': 0, 'free': 0, 'percent': 0}
|
||||
|
||||
def get_network_stats(self):
|
||||
"""Get network I/O statistics"""
|
||||
try:
|
||||
net = psutil.net_io_counters()
|
||||
return {
|
||||
'bytes_sent': net.bytes_sent,
|
||||
'bytes_recv': net.bytes_recv,
|
||||
'packets_sent': net.packets_sent,
|
||||
'packets_recv': net.packets_recv
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.log_error(f"Failed to get network stats: {e}")
|
||||
return {'bytes_sent': 0, 'bytes_recv': 0, 'packets_sent': 0, 'packets_recv': 0}
|
||||
|
||||
def display_stats(self):
|
||||
"""Display all system statistics"""
|
||||
print(f"\n{'='*50}")
|
||||
print(f"System Tracker - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"{'='*50}\n")
|
||||
|
||||
# CPU monitoring
|
||||
if self.config.get('display.show_cpu', True):
|
||||
cpu_usage = self.get_cpu_usage()
|
||||
print(f"CPU Usage: {cpu_usage}%")
|
||||
self.logger.log_stats('CPU', f"{cpu_usage}%")
|
||||
self.alert_system.check_cpu_alert(cpu_usage)
|
||||
|
||||
# Memory monitoring
|
||||
if self.config.get('display.show_memory', True):
|
||||
mem = self.get_memory_info()
|
||||
print(f"Memory: {mem['percent']}% ({mem['used'] / (1024**3):.2f}GB / {mem['total'] / (1024**3):.2f}GB)")
|
||||
self.logger.log_stats('Memory', f"{mem['percent']}%")
|
||||
self.alert_system.check_memory_alert(mem['percent'])
|
||||
|
||||
# Disk monitoring
|
||||
if self.config.get('display.show_disk', True):
|
||||
disk = self.get_disk_usage()
|
||||
print(f"Disk: {disk['percent']}% ({disk['used'] / (1024**3):.2f}GB / {disk['total'] / (1024**3):.2f}GB)")
|
||||
self.logger.log_stats('Disk', f"{disk['percent']}%")
|
||||
self.alert_system.check_disk_alert(disk['percent'])
|
||||
|
||||
# Network monitoring
|
||||
if self.config.get('display.show_network', True):
|
||||
net = self.get_network_stats()
|
||||
print(f"Network: Sent {net['bytes_sent'] / (1024**2):.2f}MB | Recv {net['bytes_recv'] / (1024**2):.2f}MB")
|
||||
self.logger.log_stats('Network', f"Sent: {net['bytes_sent']} Recv: {net['bytes_recv']}")
|
||||
|
||||
# Process monitoring
|
||||
if self.config.get('display.show_processes', True):
|
||||
self.process_monitor.display_processes()
|
||||
|
||||
# Temperature monitoring
|
||||
if self.config.get('display.show_temperatures', True):
|
||||
self.temperature_monitor.display_temperatures()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the tracker application"""
|
||||
parser = argparse.ArgumentParser(description='System Tracker - Monitor machine health')
|
||||
parser.add_argument('-c', '--continuous', action='store_true', help='Run continuously')
|
||||
parser.add_argument('-i', '--interval', type=int, default=5, help='Update interval in seconds')
|
||||
args = parser.parse_args()
|
||||
|
||||
tracker = SystemTracker()
|
||||
|
||||
if args.continuous:
|
||||
try:
|
||||
while True:
|
||||
tracker.display_stats()
|
||||
time.sleep(args.interval)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nTracker stopped by user")
|
||||
else:
|
||||
tracker.display_stats()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue