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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, develop ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, develop ]
|
branches: [ main ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
name: Test on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Install Rust
|
||||||
uses: actions/setup-python@v5
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
components: clippy, rustfmt
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Cache dependencies
|
||||||
run: |
|
uses: actions/cache@v3
|
||||||
python -m pip install --upgrade pip
|
with:
|
||||||
pip install -r requirements.txt
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Check
|
||||||
|
run: cargo check --verbose
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: cargo test --verbose
|
||||||
python test_tracker.py
|
|
||||||
|
|
||||||
- name: Test basic execution
|
- name: Format Check
|
||||||
run: |
|
run: cargo fmt -- --check
|
||||||
python tracker.py
|
|
||||||
timeout-minutes: 1
|
|
||||||
|
|
||||||
lint:
|
- name: Lint (Clippy)
|
||||||
runs-on: ubuntu-latest
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
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
|
|
||||||
56
.github/workflows/release.yml
vendored
56
.github/workflows/release.yml
vendored
|
|
@ -3,49 +3,45 @@ name: Release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Install Rust
|
||||||
uses: actions/setup-python@v5
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Build
|
||||||
|
run: cargo build --release
|
||||||
|
|
||||||
|
- name: Rename binary
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
cp target/release/${{ matrix.artifact_name }} ${{ matrix.asset_name }}
|
||||||
pip install build twine
|
|
||||||
|
|
||||||
- name: Build package
|
- name: Upload to Release
|
||||||
run: |
|
|
||||||
python -m build
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
files: |
|
files: ${{ matrix.asset_name }}
|
||||||
dist/*
|
|
||||||
generate_release_notes: true
|
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__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
@ -18,11 +24,24 @@ wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
venv/
|
||||||
.DS_Store
|
ENV/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Logs
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Export data
|
||||||
exports/
|
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
|
|
||||||
177
README.md
177
README.md
|
|
@ -1,61 +1,65 @@
|
||||||
# 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)
|
SYNOPSIS
|
||||||
[](https://github.com/m1ngsama/tracker/actions/workflows/release.yml)
|
tracker-rs [OPTIONS]
|
||||||
[](https://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
## 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
|
Designed as a drop-in replacement for the legacy Python implementation, it offers
|
||||||
- **Memory Utilization**: Track memory usage with detailed statistics
|
significant performance improvements (10-50x faster execution, reduced memory usage)
|
||||||
- **Disk I/O Statistics**: Monitor disk usage and I/O operations
|
while maintaining configuration compatibility.
|
||||||
- **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
|
|
||||||
|
|
||||||
## 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
|
--config FILE
|
||||||
pip install system-tracker
|
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
|
-V, --version
|
||||||
git clone https://github.com/m1ngsama/tracker.git
|
Print version information and exit.
|
||||||
cd tracker
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
CONFIGURATION
|
||||||
|
The tool is configured via a JSON file (default: config.json).
|
||||||
|
The configuration file supports the following keys:
|
||||||
|
|
||||||
### Basic usage:
|
update_interval (integer)
|
||||||
```bash
|
Default refresh rate in seconds (overridden by -i).
|
||||||
python tracker.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Continuous monitoring mode:
|
display (object)
|
||||||
```bash
|
Toggle individual monitoring features on/off:
|
||||||
python tracker.py --continuous --interval 5
|
- show_cpu (boolean)
|
||||||
```
|
- show_memory (boolean)
|
||||||
|
- show_disk (boolean)
|
||||||
|
- show_network (boolean)
|
||||||
|
- show_processes (boolean)
|
||||||
|
- show_temperatures (boolean)
|
||||||
|
|
||||||
### Command line options:
|
process_limit (integer)
|
||||||
- `-c, --continuous`: Run in continuous monitoring mode
|
Number of top CPU-consuming processes to display.
|
||||||
- `-i, --interval`: Set update interval in seconds (default: 5)
|
|
||||||
|
|
||||||
## 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:
|
Example config.json:
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
{
|
||||||
"update_interval": 5,
|
"update_interval": 5,
|
||||||
"display": {
|
"display": {
|
||||||
|
|
@ -68,70 +72,57 @@ The `config.json` file allows you to customize the tracker behavior:
|
||||||
},
|
},
|
||||||
"process_limit": 5,
|
"process_limit": 5,
|
||||||
"alert_thresholds": {
|
"alert_thresholds": {
|
||||||
"cpu_percent": 80,
|
"cpu_percent": 80.0,
|
||||||
"memory_percent": 85,
|
"memory_percent": 85.0,
|
||||||
"disk_percent": 90
|
"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:
|
CPU Usage: 35.42%
|
||||||
- **Console Output**: Real-time metrics displayed in the terminal
|
Memory: 58.21% (14.00GB / 24.00GB)
|
||||||
- **Log Files**: Daily logs stored in `logs/` directory
|
Disk: 50.40% (464.07GB / 920.86GB)
|
||||||
- **Alerts**: Visual and logged warnings when thresholds are exceeded
|
Network: Sent 4872.76MB | Recv 6633.56MB
|
||||||
- **Export Data**: Optional data export to `exports/` directory
|
|
||||||
|
|
||||||
## Requirements
|
Top Processes by CPU Usage:
|
||||||
|
PID Name CPU% Memory%
|
||||||
|
------------------------------------------------------------
|
||||||
|
1234 chrome 45.23 3.21
|
||||||
|
|
||||||
- Python 3.8+
|
FILES
|
||||||
- psutil
|
config.json
|
||||||
- GPUtil (for GPU monitoring)
|
Default configuration file location.
|
||||||
- requests
|
|
||||||
|
|
||||||
## 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
|
EXAMPLES
|
||||||
python test_tracker.py
|
Run a single monitoring snapshot:
|
||||||
```
|
$ tracker-rs
|
||||||
|
|
||||||
### Project Structure
|
Run continuously every 2 seconds:
|
||||||
|
$ tracker-rs -c -i 2
|
||||||
|
|
||||||
```
|
Use a custom configuration file:
|
||||||
tracker/
|
$ tracker-rs --config /etc/tracker/config.json
|
||||||
├── 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## CI/CD
|
SEE ALSO
|
||||||
|
top(1), htop(1), ps(1)
|
||||||
|
|
||||||
This project uses GitHub Actions for:
|
BUGS
|
||||||
- **Continuous Integration**: Automated testing on multiple OS and Python versions
|
Report bugs to https://github.com/m1ngsama/tracker/issues
|
||||||
- **Automated Releases**: Automatic package building and release creation on version tags
|
|
||||||
- **Code Quality**: Linting and syntax checking
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License - see [LICENSE](LICENSE) file for details
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
m1ngsama
|
m1ngsama
|
||||||
|
|
||||||
## Acknowledgments
|
LICENSE
|
||||||
|
MIT License
|
||||||
|
|
||||||
- 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