Release v1.0.0: Complete system monitoring solution

Major improvements and features:
- Integrate all monitoring modules (config, alert, logger, temperature)
- Add comprehensive error handling throughout codebase
- Fix data exporter directory creation issue
- Improve process monitor CPU accuracy with proper intervals
- Fix logger file handle management

New features:
- Alert system with configurable thresholds
- Automatic logging to daily log files
- Data export to JSON/CSV formats
- Configuration management via config.json
- Temperature monitoring support

CI/CD:
- Add GitHub Actions workflows for automated testing
- Add release workflow for automatic package building
- Multi-platform testing (Linux, macOS, Windows)
- Python 3.8-3.12 compatibility testing

Package distribution:
- Add setup.py and pyproject.toml for PyPI distribution
- Add MANIFEST.in for proper file inclusion
- Add comprehensive CHANGELOG.md
- Update README with full documentation

Bug fixes:
- Fix ResourceWarning in logger
- Add ZombieProcess exception handling
- Improve error handling in all metric collection methods
This commit is contained in:
m1ngsama 2025-11-25 16:12:46 +08:00
parent af5388cc49
commit 5529c0d573
12 changed files with 541 additions and 70 deletions

61
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,61 @@
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
python test_tracker.py
- name: Test basic execution
run: |
python tracker.py
timeout-minutes: 1
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install linting tools
run: |
python -m pip install --upgrade pip
pip install flake8 pylint
- name: Lint with flake8
run: |
# Stop build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# Exit-zero treats all errors as warnings
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
continue-on-error: true

47
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
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 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
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

62
CHANGELOG.md Normal file
View file

@ -0,0 +1,62 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0] - 2025-11-25
### Added
- Initial release of System Tracker
- Real-time CPU usage monitoring
- Memory utilization tracking with detailed statistics
- Disk I/O statistics and usage monitoring
- Network traffic analysis (bytes sent/received, packets)
- Process monitoring with top processes by CPU usage
- Temperature sensor monitoring (platform-dependent)
- Configurable alert system with thresholds for CPU, memory, and disk
- Comprehensive logging system with daily log files
- Data export functionality (JSON and CSV formats)
- Configuration management via `config.json`
- Continuous monitoring mode with customizable intervals
- CLI arguments support for flexible operation
- Cross-platform support (Linux, macOS, Windows)
### Features
- **Configuration System**: JSON-based configuration with sensible defaults
- **Alert System**: Real-time alerts when system metrics exceed configured thresholds
- **Logging**: Automatic daily log file creation in `logs/` directory
- **Data Export**: Export monitoring data to `exports/` directory
- **Process Monitor**: Enhanced CPU usage tracking with accurate process information
- **Temperature Monitoring**: System temperature sensors (when available)
- **Error Handling**: Comprehensive error handling throughout the codebase
- **Modular Architecture**: Clean separation of concerns with dedicated modules
### Technical Improvements
- Fixed data exporter directory creation issue
- Improved process monitor CPU data accuracy with proper interval handling
- Added error handling to all system metric collection methods
- Resolved logger file handle management issues
- Enhanced zombie process handling in process monitoring
### CI/CD
- GitHub Actions workflow for automated testing across multiple OS and Python versions
- Automated release workflow with package building
- Code quality checks with flake8 linting
- Multi-platform testing (Ubuntu, macOS, Windows)
- Python 3.8-3.12 compatibility testing
### Documentation
- Comprehensive README with installation and usage instructions
- Configuration file documentation
- MIT License
- Project structure documentation
- Contributing guidelines
### Dependencies
- psutil >= 5.9.0
- GPUtil >= 1.4.0
- requests >= 2.28.0
[1.0.0]: https://github.com/m1ngsama/tracker/releases/tag/v1.0.0

6
MANIFEST.in Normal file
View file

@ -0,0 +1,6 @@
include README.md
include LICENSE
include CHANGELOG.md
include requirements.txt
include config.json
recursive-include tests *.py

102
README.md
View file

@ -2,6 +2,10 @@
A comprehensive system monitoring tool for tracking various machine health metrics and performance indicators.
[![CI](https://github.com/m1ngsama/tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/m1ngsama/tracker/actions/workflows/ci.yml)
[![Release](https://github.com/m1ngsama/tracker/actions/workflows/release.yml/badge.svg)](https://github.com/m1ngsama/tracker/actions/workflows/release.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## Features
- **CPU Monitoring**: Real-time CPU usage percentage tracking
@ -10,36 +14,124 @@ A comprehensive system monitoring tool for tracking various machine health metri
- **Network Traffic Analysis**: Track network bytes sent/received
- **Process Monitoring**: View top processes by CPU usage
- **Temperature Sensors**: Monitor system temperatures (if available)
- **System Uptime Tracking**: Track how long the system has been running
- **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
### From PyPI (coming soon)
```bash
pip install system-tracker
```
### From Source
```bash
git clone https://github.com/m1ngsama/tracker.git
cd tracker
pip install -r requirements.txt
```
## Usage
Basic usage:
### Basic usage:
```bash
python tracker.py
```
Continuous monitoring mode:
### Continuous monitoring mode:
```bash
python tracker.py --continuous --interval 5
```
Command line options:
### Command line options:
- `-c, --continuous`: Run in continuous monitoring mode
- `-i, --interval`: Set update interval in seconds (default: 5)
## Configuration
The `config.json` file allows you to customize the tracker behavior:
```json
{
"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
}
}
```
## Output
The tracker provides:
- **Console Output**: Real-time metrics displayed in the terminal
- **Log Files**: Daily logs stored in `logs/` directory
- **Alerts**: Visual and logged warnings when thresholds are exceeded
- **Export Data**: Optional data export to `exports/` directory
## Requirements
- Python 3.8+
- psutil
- GPUtil (for GPU monitoring)
- requests
## Development
### Running Tests
```bash
python test_tracker.py
```
### Project Structure
```
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
```
## CI/CD
This project uses GitHub Actions for:
- **Continuous Integration**: Automated testing on multiple OS and Python versions
- **Automated Releases**: Automatic package building and release creation on version tags
- **Code Quality**: Linting and syntax checking
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
MIT 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

View file

@ -10,6 +10,13 @@ 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"""
@ -18,10 +25,12 @@ class DataExporter:
filepath = f"{self.output_dir}/{filename}"
with open(filepath, 'w') as f:
json.dump(data, f, indent=2)
return filepath
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"""
@ -30,11 +39,13 @@ class DataExporter:
filepath = f"{self.output_dir}/{filename}"
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
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}")

View file

@ -22,16 +22,25 @@ class TrackerLogger:
f"tracker_{datetime.now().strftime('%Y%m%d')}.log"
)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
# Clear any existing handlers to prevent duplicate logging
logger = logging.getLogger('SystemTracker')
logger.handlers.clear()
self.logger = logging.getLogger('SystemTracker')
# 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"""

View file

@ -9,10 +9,19 @@ class ProcessMonitor:
def get_top_processes(self, limit=5):
"""Get top processes by CPU usage"""
processes = []
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
# First collect all processes
for proc in psutil.process_iter(['pid', 'name']):
try:
processes.append(proc.info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
# 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)
@ -20,17 +29,23 @@ class ProcessMonitor:
def get_process_count(self):
"""Get total number of running processes"""
return len(psutil.pids())
try:
return len(psutil.pids())
except Exception:
return 0
def display_processes(self):
"""Display top processes"""
print(f"\nTop Processes by CPU Usage:")
print(f"{'PID':<10}{'Name':<30}{'CPU%':<10}{'Memory%':<10}")
print("-" * 60)
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}")
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()}")
print(f"\nTotal Processes: {self.get_process_count()}")
except Exception as e:
print(f"Error displaying processes: {e}")

44
pyproject.toml Normal file
View file

@ -0,0 +1,44 @@
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
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]
packages = ["tracker"]

47
setup.py Normal file
View file

@ -0,0 +1,47 @@
from setuptools import setup, find_packages
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",
packages=find_packages(),
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,
package_data={
"": ["config.json"],
},
)

26
test_export.py Normal file
View 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}")

View file

@ -8,46 +8,70 @@ 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):
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"""
return psutil.cpu_percent(interval=1, percpu=False)
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"""
mem = psutil.virtual_memory()
return {
'total': mem.total,
'available': mem.available,
'percent': mem.percent,
'used': mem.used
}
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"""
disk = psutil.disk_usage('/')
return {
'total': disk.total,
'used': disk.used,
'free': disk.free,
'percent': disk.percent
}
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"""
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
}
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"""
@ -55,21 +79,44 @@ class SystemTracker:
print(f"System Tracker - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*50}\n")
print(f"CPU Usage: {self.get_cpu_usage()}%")
# 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)
mem = self.get_memory_info()
print(f"Memory: {mem['percent']}% ({mem['used'] / (1024**3):.2f}GB / {mem['total'] / (1024**3):.2f}GB)")
# 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 = self.get_disk_usage()
print(f"Disk: {disk['percent']}% ({disk['used'] / (1024**3):.2f}GB / {disk['total'] / (1024**3):.2f}GB)")
# 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'])
net = self.get_network_stats()
print(f"Network: Sent {net['bytes_sent'] / (1024**2):.2f}MB | Recv {net['bytes_recv'] / (1024**2):.2f}MB")
# 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']}")
self.process_monitor.display_processes()
# 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()
if __name__ == "__main__":
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')
@ -86,3 +133,7 @@ if __name__ == "__main__":
print("\n\nTracker stopped by user")
else:
tracker.display_stats()
if __name__ == "__main__":
main()