shield-stats 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: shield-stats
3
+ Version: 0.2.3
4
+ Summary: Lightweight cross-distro Linux telemetry collector
5
+ Author: Reyansh Raj Mishra
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shield-stats"
7
+ version = "0.2.3"
8
+ description = "Lightweight cross-distro Linux telemetry collector"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Reyansh Raj Mishra"}
14
+ ]
15
+
16
+ [project.scripts]
17
+ shield-stats = "shield_stats.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """shield_stats package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,152 @@
1
+ import argparse
2
+ import sys
3
+ from . import collector, storage
4
+ from .storage import load_snapshot_by_date
5
+ from datetime import date, timedelta
6
+ import subprocess
7
+
8
+
9
+ def _cmd_record() -> int:
10
+ metrics = collector.collect_metrics()
11
+ try:
12
+ storage.save_snapshot(metrics)
13
+ except Exception as e:
14
+ print(f"failed to save snapshot: {e}", file=sys.stderr)
15
+ return 1
16
+ return 0
17
+
18
+
19
+ def _format_bytes(n, signed=False):
20
+ value = float(n)
21
+ for unit in ["B", "KB", "MB", "GB", "TB"]:
22
+ if abs(value) < 1024:
23
+ if signed:
24
+ return f"{value:+.2f} {unit}"
25
+ return f"{value:.2f} {unit}"
26
+ value /= 1024
27
+ if signed:
28
+ return f"{value:+.2f} PB"
29
+ return f"{value:.2f} PB"
30
+
31
+ def _cmd_summary() -> int:
32
+ today = date.today()
33
+ data = load_snapshot_by_date(today)
34
+
35
+ if not data:
36
+ print("No snapshot found for today.")
37
+ return 1
38
+
39
+ print("System Snapshot (Today)\n")
40
+
41
+ # Disk (using df command for accurate calculation)
42
+ try:
43
+ df_output = subprocess.check_output(['df', '-h', '/'], text=True).splitlines()
44
+ disk_usage = df_output[1].split()
45
+ used = disk_usage[2] # Used space
46
+ total = disk_usage[1] # Total space
47
+ percent_disk = disk_usage[4] # Percentage used
48
+ print(f"Disk Usage: {used} of {total} ({percent_disk})")
49
+ except Exception as e:
50
+ print(f"Disk Usage: Error calculating disk usage: {e}")
51
+
52
+ # Memory
53
+ mem_total = data["memory_total_bytes"]
54
+ mem_used = data["memory_used_bytes"]
55
+
56
+ if mem_total and mem_used:
57
+ percent_mem = (mem_used / mem_total) * 100
58
+ print(f"Memory Usage: {_format_bytes(mem_used)} ({percent_mem:.1f}%)")
59
+ else:
60
+ print("Memory Usage: unavailable")
61
+
62
+ # Packages
63
+ print(f"Packages: {data['package_count']}")
64
+
65
+ # Load
66
+ print(f"Load (1m): {data['load_avg_1']:.2f}")
67
+
68
+ # Uptime
69
+ uptime = data["uptime_seconds"]
70
+ if uptime:
71
+ hours = int(uptime // 3600)
72
+ minutes = int((uptime % 3600) // 60)
73
+ print(f"Uptime: {hours}h {minutes}m")
74
+
75
+ # Kernel
76
+ print(f"Kernel: {data['kernel_version']}")
77
+
78
+ return 0
79
+
80
+ def _cmd_compare() -> int:
81
+ today = date.today()
82
+ yesterday = today - timedelta(days=1)
83
+
84
+ today_data = load_snapshot_by_date(today)
85
+ yesterday_data = load_snapshot_by_date(yesterday)
86
+
87
+ if not today_data:
88
+ print("No snapshot found for today.")
89
+ return 1
90
+
91
+ if not yesterday_data:
92
+ print("No snapshot found for yesterday.")
93
+ return 1
94
+
95
+ print("Changes Since Yesterday:\n")
96
+
97
+ # Disk
98
+ disk_delta = today_data["disk_used_bytes"] - yesterday_data["disk_used_bytes"]
99
+ print(f"Disk Usage: {_format_bytes(disk_delta, signed=True)}")
100
+
101
+ # Memory
102
+ mem_delta = (
103
+ (today_data["memory_used_bytes"] or 0)
104
+ - (yesterday_data["memory_used_bytes"] or 0)
105
+ )
106
+ print(f"Memory Usage: {_format_bytes(mem_delta, signed=True)}")
107
+
108
+ # Packages
109
+ pkg_delta = today_data["package_count"] - yesterday_data["package_count"]
110
+ print(f"Packages: {pkg_delta:+}")
111
+
112
+ # Load
113
+ load_delta = (
114
+ (today_data["load_avg_1"] or 0)
115
+ - (yesterday_data["load_avg_1"] or 0)
116
+ )
117
+ print(f"Load (1m): {load_delta:+.2f}")
118
+
119
+ # Kernel change
120
+ if today_data["kernel_version"] != yesterday_data["kernel_version"]:
121
+ print(
122
+ f"Kernel: {yesterday_data['kernel_version']} → {today_data['kernel_version']}"
123
+ )
124
+ else:
125
+ print("Kernel: unchanged")
126
+
127
+ return 0
128
+
129
+ def main(argv=None) -> int:
130
+ parser = argparse.ArgumentParser(prog="shield-stats")
131
+ sub = parser.add_subparsers(dest="command")
132
+ sub.required = True
133
+
134
+ sub.add_parser("record", help="collect and store today's metrics")
135
+ sub.add_parser("summary", help="show today's system summary")
136
+ sub.add_parser("compare", help="compare today with yesterday")
137
+
138
+ args = parser.parse_args(argv)
139
+
140
+ if args.command == "record":
141
+ return _cmd_record()
142
+
143
+ if args.command == "summary":
144
+ return _cmd_summary()
145
+
146
+ if args.command == "compare":
147
+ return _cmd_compare()
148
+
149
+ return 1
150
+
151
+ if __name__ == "__main__":
152
+ raise SystemExit(main())
@@ -0,0 +1,99 @@
1
+ """Gather system metrics for shield-stats."""
2
+
3
+ import platform
4
+ import shutil
5
+ import subprocess
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Dict, Optional
9
+
10
+
11
+ def _read_proc_file(path: Path) -> str:
12
+ try:
13
+ with path.open("r") as f:
14
+ return f.read()
15
+ except Exception:
16
+ return ""
17
+
18
+
19
+ def _parse_meminfo() -> Dict[str, int]:
20
+ data = _read_proc_file(Path("/proc/meminfo"))
21
+ result: Dict[str, int] = {}
22
+ for line in data.splitlines():
23
+ parts = line.split()
24
+ if len(parts) >= 2 and parts[0].endswith(":"):
25
+ key = parts[0].rstrip(":")
26
+ try:
27
+ value = int(parts[1]) * 1024
28
+ except ValueError:
29
+ continue
30
+ result[key] = value
31
+ return result
32
+
33
+
34
+ def _get_uptime() -> Optional[float]:
35
+ data = _read_proc_file(Path("/proc/uptime")).split()
36
+ try:
37
+ return float(data[0])
38
+ except (IndexError, ValueError):
39
+ return None
40
+
41
+
42
+ def _get_load_avg() -> Optional[float]:
43
+ data = _read_proc_file(Path("/proc/loadavg")).split()
44
+ try:
45
+ return float(data[0])
46
+ except (IndexError, ValueError):
47
+ return None
48
+
49
+
50
+ def _count_packages() -> int:
51
+ if shutil.which("pacman"):
52
+ cmd = ["pacman", "-Qq"]
53
+
54
+ elif shutil.which("apt"):
55
+ cmd = ["dpkg-query", "-f", "${binary:Package}\n", "-W"]
56
+
57
+ elif shutil.which("dnf"):
58
+ cmd = ["rpm", "-qa"]
59
+
60
+ else:
61
+ return 0
62
+
63
+ try:
64
+ proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
65
+ except Exception:
66
+ return 0
67
+
68
+ if proc.returncode != 0:
69
+ return 0
70
+
71
+ return len(proc.stdout.strip().splitlines())
72
+
73
+
74
+ def collect_metrics() -> Dict[str, Optional[object]]:
75
+ now = int(time.time())
76
+
77
+ total, used, _free = shutil.disk_usage("/")
78
+
79
+ meminfo = _parse_meminfo()
80
+ mem_total = meminfo.get("MemTotal")
81
+ mem_available = meminfo.get("MemAvailable")
82
+ mem_used = (
83
+ mem_total - mem_available
84
+ if mem_total is not None and mem_available is not None
85
+ else None
86
+ )
87
+
88
+ return {
89
+ "timestamp": now,
90
+ "hostname": platform.node(),
91
+ "disk_total_bytes": total,
92
+ "disk_used_bytes": used,
93
+ "memory_total_bytes": mem_total,
94
+ "memory_used_bytes": mem_used,
95
+ "uptime_seconds": _get_uptime(),
96
+ "load_avg_1": _get_load_avg(),
97
+ "package_count": _count_packages(),
98
+ "kernel_version": platform.release(),
99
+ }
@@ -0,0 +1,41 @@
1
+ """Handle storage of collected metrics."""
2
+
3
+ import json
4
+ from datetime import date
5
+ from pathlib import Path
6
+ from typing import Any, Dict
7
+ from datetime import date
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ def load_snapshot_by_date(d: date):
13
+ base = Path.home() / ".local" / "share" / "shield-stats"
14
+ path = base / f"{d.isoformat()}.json"
15
+ if not path.exists():
16
+ return None
17
+ try:
18
+ with path.open("r", encoding="utf-8") as f:
19
+ return json.load(f)
20
+ except Exception:
21
+ return None
22
+
23
+ def _get_storage_path() -> Path:
24
+ base = Path.home() / ".local" / "share" / "shield-stats"
25
+ base.mkdir(parents=True, exist_ok=True)
26
+ today = date.today().isoformat()
27
+ return base / f"{today}.json"
28
+
29
+
30
+ def save_snapshot(data: Dict[str, Any]) -> None:
31
+ path = _get_storage_path()
32
+ temp = path.with_suffix(".tmp")
33
+
34
+ try:
35
+ with temp.open("w", encoding="utf-8") as f:
36
+ json.dump(data, f, indent=2, ensure_ascii=False)
37
+ f.flush()
38
+ temp.replace(path)
39
+ except Exception:
40
+ with path.open("w", encoding="utf-8") as f:
41
+ json.dump(data, f, indent=2, ensure_ascii=False)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: shield-stats
3
+ Version: 0.2.3
4
+ Summary: Lightweight cross-distro Linux telemetry collector
5
+ Author: Reyansh Raj Mishra
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
@@ -0,0 +1,10 @@
1
+ pyproject.toml
2
+ shield_stats/__init__.py
3
+ shield_stats/cli.py
4
+ shield_stats/collector.py
5
+ shield_stats/storage.py
6
+ shield_stats.egg-info/PKG-INFO
7
+ shield_stats.egg-info/SOURCES.txt
8
+ shield_stats.egg-info/dependency_links.txt
9
+ shield_stats.egg-info/entry_points.txt
10
+ shield_stats.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ shield-stats = shield_stats.cli:main
@@ -0,0 +1 @@
1
+ shield_stats