mirror of
https://github.com/m1ngsama/tracker.git
synced 2025-12-26 12:04:09 +00:00
Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
25 changed files with 798 additions and 697 deletions
64
.github/workflows/ci.yml
vendored
64
.github/workflows/ci.yml
vendored
|
|
@ -2,46 +2,60 @@ name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main, develop ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main, develop ]
|
||||||
|
|
||||||
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: Install Rust
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
components: clippy, rustfmt
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Install dependencies
|
||||||
uses: actions/cache@v3
|
run: |
|
||||||
with:
|
python -m pip install --upgrade pip
|
||||||
path: |
|
pip install -r requirements.txt
|
||||||
~/.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: cargo test --verbose
|
run: |
|
||||||
|
python test_tracker.py
|
||||||
|
|
||||||
- name: Format Check
|
- name: Test basic execution
|
||||||
run: cargo fmt -- --check
|
run: |
|
||||||
|
python tracker.py
|
||||||
|
timeout-minutes: 1
|
||||||
|
|
||||||
- name: Lint (Clippy)
|
lint:
|
||||||
run: cargo clippy -- -D warnings
|
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
|
||||||
|
|
|
||||||
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
|
|
@ -3,45 +3,49 @@ name: Release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*.*.*'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Build and Release for ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
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: Install Rust
|
- name: Set up Python
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions/setup-python@v5
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --release
|
|
||||||
|
|
||||||
- name: Rename binary
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cp target/release/${{ matrix.artifact_name }} ${{ matrix.asset_name }}
|
|
||||||
|
|
||||||
- name: Upload to Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
with:
|
||||||
files: ${{ matrix.asset_name }}
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install build twine
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
python -m build
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
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,9 +1,3 @@
|
||||||
# Rust
|
|
||||||
/target
|
|
||||||
Cargo.lock
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
@ -24,24 +18,11 @@ wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
venv/
|
.env
|
||||||
ENV/
|
.venv
|
||||||
env/
|
env/
|
||||||
|
venv/
|
||||||
# Logs
|
.DS_Store
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Export data
|
|
||||||
exports/
|
exports/
|
||||||
|
|
||||||
# IDEs
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
|
||||||
17
Cargo.toml
17
Cargo.toml
|
|
@ -1,17 +0,0 @@
|
||||||
[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"
|
|
||||||
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
include README.md
|
||||||
|
include LICENSE
|
||||||
|
include CHANGELOG.md
|
||||||
|
include requirements.txt
|
||||||
|
include config.json
|
||||||
|
recursive-include tests *.py
|
||||||
207
README.md
207
README.md
|
|
@ -1,128 +1,137 @@
|
||||||
tracker-rs(1) System Tracker tracker-rs(1)
|
# Tracker
|
||||||
|
|
||||||
NAME
|
A comprehensive system monitoring tool for tracking various machine health metrics and performance indicators.
|
||||||
tracker-rs - high-performance system monitoring tool
|
|
||||||
|
|
||||||
SYNOPSIS
|
[](https://github.com/m1ngsama/tracker/actions/workflows/ci.yml)
|
||||||
tracker-rs [OPTIONS]
|
[](https://github.com/m1ngsama/tracker/actions/workflows/release.yml)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
DESCRIPTION
|
## Features
|
||||||
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.
|
|
||||||
|
|
||||||
Designed as a drop-in replacement for the legacy Python implementation, it offers
|
- **CPU Monitoring**: Real-time CPU usage percentage tracking
|
||||||
significant performance improvements (10-50x faster execution, reduced memory usage)
|
- **Memory Utilization**: Track memory usage with detailed statistics
|
||||||
while maintaining configuration compatibility.
|
- **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
|
||||||
|
|
||||||
OPTIONS
|
## Installation
|
||||||
-c, --continuous
|
|
||||||
Run in continuous monitoring mode. The tool will repeatedly display statistics
|
|
||||||
and log data based on the update interval.
|
|
||||||
|
|
||||||
-i, --interval SECONDS
|
### From PyPI (coming soon)
|
||||||
Set the update interval in seconds. Default is 5 seconds.
|
|
||||||
This option is primarily used with --continuous mode.
|
|
||||||
|
|
||||||
--config FILE
|
```bash
|
||||||
Path to the configuration file. Default is "config.json".
|
pip install system-tracker
|
||||||
If the file does not exist, internal defaults are used.
|
```
|
||||||
|
|
||||||
-h, --help
|
### From Source
|
||||||
Print help message and exit.
|
|
||||||
|
|
||||||
-V, --version
|
```bash
|
||||||
Print version information and exit.
|
git clone https://github.com/m1ngsama/tracker.git
|
||||||
|
cd tracker
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
CONFIGURATION
|
## Usage
|
||||||
The tool is configured via a JSON file (default: config.json).
|
|
||||||
The configuration file supports the following keys:
|
|
||||||
|
|
||||||
update_interval (integer)
|
### Basic usage:
|
||||||
Default refresh rate in seconds (overridden by -i).
|
```bash
|
||||||
|
python tracker.py
|
||||||
|
```
|
||||||
|
|
||||||
display (object)
|
### Continuous monitoring mode:
|
||||||
Toggle individual monitoring features on/off:
|
```bash
|
||||||
- show_cpu (boolean)
|
python tracker.py --continuous --interval 5
|
||||||
- show_memory (boolean)
|
```
|
||||||
- show_disk (boolean)
|
|
||||||
- show_network (boolean)
|
|
||||||
- show_processes (boolean)
|
|
||||||
- show_temperatures (boolean)
|
|
||||||
|
|
||||||
process_limit (integer)
|
### Command line options:
|
||||||
Number of top CPU-consuming processes to display.
|
- `-c, --continuous`: Run in continuous monitoring mode
|
||||||
|
- `-i, --interval`: Set update interval in seconds (default: 5)
|
||||||
|
|
||||||
alert_thresholds (object)
|
## Configuration
|
||||||
Percentage thresholds for triggering alerts:
|
|
||||||
- cpu_percent (float)
|
|
||||||
- memory_percent (float)
|
|
||||||
- disk_percent (float)
|
|
||||||
|
|
||||||
Example config.json:
|
The `config.json` file allows you to customize the tracker behavior:
|
||||||
{
|
|
||||||
"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.0,
|
|
||||||
"memory_percent": 85.0,
|
|
||||||
"disk_percent": 90.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OUTPUT
|
```json
|
||||||
tracker-rs outputs formatted system statistics to the console.
|
{
|
||||||
In continuous mode, it updates at the specified interval.
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
CPU Usage: 35.42%
|
## Output
|
||||||
Memory: 58.21% (14.00GB / 24.00GB)
|
|
||||||
Disk: 50.40% (464.07GB / 920.86GB)
|
|
||||||
Network: Sent 4872.76MB | Recv 6633.56MB
|
|
||||||
|
|
||||||
Top Processes by CPU Usage:
|
The tracker provides:
|
||||||
PID Name CPU% Memory%
|
- **Console Output**: Real-time metrics displayed in the terminal
|
||||||
------------------------------------------------------------
|
- **Log Files**: Daily logs stored in `logs/` directory
|
||||||
1234 chrome 45.23 3.21
|
- **Alerts**: Visual and logged warnings when thresholds are exceeded
|
||||||
|
- **Export Data**: Optional data export to `exports/` directory
|
||||||
|
|
||||||
FILES
|
## Requirements
|
||||||
config.json
|
|
||||||
Default configuration file location.
|
|
||||||
|
|
||||||
logs/tracker_YYYYMMDD.log
|
- Python 3.8+
|
||||||
Daily rotating log files containing system stats and alerts.
|
- psutil
|
||||||
|
- GPUtil (for GPU monitoring)
|
||||||
|
- requests
|
||||||
|
|
||||||
EXIT STATUS
|
## Development
|
||||||
0 Success.
|
|
||||||
1 Failure (e.g., invalid configuration).
|
|
||||||
|
|
||||||
EXAMPLES
|
### Running Tests
|
||||||
Run a single monitoring snapshot:
|
|
||||||
$ tracker-rs
|
|
||||||
|
|
||||||
Run continuously every 2 seconds:
|
```bash
|
||||||
$ tracker-rs -c -i 2
|
python test_tracker.py
|
||||||
|
```
|
||||||
|
|
||||||
Use a custom configuration file:
|
### Project Structure
|
||||||
$ tracker-rs --config /etc/tracker/config.json
|
|
||||||
|
|
||||||
SEE ALSO
|
```
|
||||||
top(1), htop(1), ps(1)
|
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
|
||||||
|
```
|
||||||
|
|
||||||
BUGS
|
## CI/CD
|
||||||
Report bugs to https://github.com/m1ngsama/tracker/issues
|
|
||||||
|
|
||||||
AUTHOR
|
This project uses GitHub Actions for:
|
||||||
m1ngsama
|
- **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
|
||||||
|
|
||||||
LICENSE
|
## Contributing
|
||||||
MIT License
|
|
||||||
|
|
||||||
v1.0.1 2025-12-18 tracker-rs(1)
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
|
||||||
53
alert_system.py
Normal file
53
alert_system.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
50
config_manager.py
Normal file
50
config_manager.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
51
data_exporter.py
Normal file
51
data_exporter.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
"""
|
||||||
|
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
Normal file
55
logger.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""
|
||||||
|
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}")
|
||||||
51
process_monitor.py
Normal file
51
process_monitor.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
"""
|
||||||
|
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}")
|
||||||
44
pyproject.toml
Normal file
44
pyproject.toml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[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"]
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
psutil>=5.9.0
|
||||||
|
GPUtil>=1.4.0
|
||||||
|
requests>=2.28.0
|
||||||
55
setup.py
Normal file
55
setup.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
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
61
src/alert.rs
|
|
@ -1,61 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
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
55
src/main.rs
|
|
@ -1,55 +0,0 @@
|
||||||
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
183
src/monitor.rs
|
|
@ -1,183 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
temperature_monitor.py
Normal file
35
temperature_monitor.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
"""
|
||||||
|
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}")
|
||||||
26
test_export.py
Normal file
26
test_export.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/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}")
|
||||||
45
test_tracker.py
Normal file
45
test_tracker.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""
|
||||||
|
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
Normal file
139
tracker.py
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
#!/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