|
1
|
1 // topfetch++ - live system monitor + fetch tool |
|
|
2 // ISC licensed, see LICENSE. |
|
|
3 // Logos adapted from pfetch (MIT), see THIRD-PARTY-NOTICES._ |
|
|
4 #include <algorithm> |
|
|
5 #include <chrono> |
|
0
|
6 #include <fstream> |
|
|
7 #include <iomanip> |
|
1
|
8 #include <iostream> |
|
|
9 #include <map> |
|
|
10 #include <sstream> |
|
|
11 #include <string> |
|
|
12 #include <thread> |
|
|
13 #include <vector> |
|
|
14 |
|
|
15 // ---------- simple readers ---------- |
|
|
16 |
|
|
17 std::string readHostname() { |
|
|
18 std::ifstream f("/proc/sys/kernel/hostname"); |
|
|
19 if (!f.is_open()) return "(none)"; |
|
|
20 std::string s; |
|
|
21 std::getline(f, s); |
|
|
22 return s.empty() ? "(none)" : s; |
|
|
23 } |
|
|
24 |
|
|
25 std::string readVersion() { |
|
|
26 std::ifstream f("/proc/version"); |
|
|
27 if (!f.is_open()) return "unknown"; |
|
|
28 std::string w1, w2, version; |
|
|
29 f >> w1 >> w2 >> version; // "Linux version 6.x.y ..." |
|
|
30 return version.empty() ? "unknown" : version; |
|
|
31 } |
|
|
32 |
|
|
33 long readUptime() { |
|
|
34 std::ifstream f("/proc/uptime"); |
|
|
35 if (!f.is_open()) return -1; |
|
|
36 double seconds = 0; |
|
|
37 f >> seconds; |
|
|
38 return static_cast<long>(seconds); |
|
|
39 } |
|
|
40 |
|
|
41 struct Mem { |
|
|
42 long total = 0; // kB |
|
|
43 long available = 0; // kB |
|
|
44 }; |
|
|
45 |
|
|
46 Mem readMeminfo() { |
|
|
47 Mem m; |
|
|
48 std::ifstream f("/proc/meminfo"); |
|
|
49 if (!f.is_open()) return m; |
|
|
50 std::string line; |
|
|
51 while (std::getline(f, line)) { |
|
|
52 std::istringstream ss(line); |
|
|
53 std::string name; |
|
|
54 long value = 0; |
|
|
55 ss >> name >> value; |
|
|
56 if (name == "MemTotal:") m.total = value; |
|
|
57 else if (name == "MemAvailable:") m.available = value; |
|
|
58 } |
|
|
59 return m; |
|
|
60 } |
|
|
61 |
|
|
62 struct CpuTimes { |
|
|
63 long busy = 0; |
|
|
64 long total = 0; |
|
|
65 }; |
|
|
66 |
|
|
67 CpuTimes readCpuTimes() { |
|
|
68 CpuTimes t; |
|
|
69 std::ifstream f("/proc/stat"); |
|
|
70 if (!f.is_open()) return t; |
|
|
71 |
|
|
72 std::string label; |
|
|
73 f >> label; // "cpu" |
|
|
74 |
|
|
75 long values[8] = {0}; // user nice system idle iowait irq softirq steal |
|
|
76 for (int i = 0; i < 8; i++) f >> values[i]; |
|
|
77 |
|
0
|
78 long total = 0; |
|
1
|
79 for (int i = 0; i < 8; i++) total += values[i]; |
|
|
80 |
|
|
81 long idle = values[3] + values[4]; |
|
|
82 t.total = total; |
|
|
83 t.busy = total - idle; |
|
|
84 return t; |
|
|
85 } |
|
|
86 |
|
|
87 // ---------- distro ---------- |
|
|
88 |
|
|
89 struct Distro { |
|
|
90 std::string pretty = "Linux"; |
|
|
91 std::string id; |
|
|
92 std::string idLike; |
|
|
93 }; |
|
|
94 |
|
|
95 std::string unquote(const std::string& v) { |
|
|
96 if (v.size() >= 2 && (v.front() == '"' || v.front() == '\'') && |
|
|
97 v.back() == v.front()) { |
|
|
98 return v.substr(1, v.size() - 2); |
|
|
99 } |
|
|
100 return v; |
|
|
101 } |
|
|
102 |
|
|
103 Distro readDistro() { |
|
|
104 Distro d; |
|
|
105 std::ifstream f("/etc/os-release"); |
|
|
106 if (!f.is_open()) return d; |
|
|
107 |
|
|
108 std::string line; |
|
|
109 while (std::getline(f, line)) { |
|
|
110 size_t pos = line.find('='); |
|
|
111 if (pos == std::string::npos) continue; |
|
|
112 std::string key = line.substr(0, pos); |
|
|
113 std::string val = unquote(line.substr(pos + 1)); |
|
|
114 if (key == "PRETTY_NAME" && !val.empty()) d.pretty = val; |
|
|
115 else if (key == "ID") d.id = val; |
|
|
116 else if (key == "ID_LIKE") d.idLike = val; |
|
|
117 } |
|
|
118 return d; |
|
|
119 } |
|
|
120 |
|
|
121 // ---------- logos ---------- |
|
|
122 |
|
|
123 struct Logo { |
|
|
124 std::vector<std::string> lines; |
|
|
125 std::string color; // ANSI escape |
|
|
126 }; |
|
|
127 |
|
|
128 // Logos adapted from pfetch (MIT) |
|
|
129 // Copyright (c) 2016-2021 Dylan Araps |
|
|
130 // Copyright (c) 2024-2025 Joey Reinhart |
|
|
131 Logo pickLogo(const Distro& d, const std::string& override = "") { |
|
|
132 static const std::map<std::string, Logo> logos = { |
|
|
133 {"nixos", {{ |
|
|
134 R"art( \\ \\ //)art", |
|
|
135 R"art( ==\\__\\/ //)art", |
|
|
136 R"art( // \\//)art", |
|
|
137 R"art(==// //==)art", |
|
|
138 R"art( //\\___//)art", |
|
|
139 R"art(// /\\ \\==)art", |
|
|
140 R"art( // \\ \\)art", |
|
|
141 }, "\033[34m"}}, |
|
|
142 {"arch", {{ |
|
|
143 R"art( /\)art", |
|
|
144 R"art( / \)art", |
|
|
145 R"art( /\ \)art", |
|
|
146 R"art( / \)art", |
|
|
147 R"art( / ,, \)art", |
|
|
148 R"art( / | | -\)art", |
|
|
149 R"art(/_-'' ''-_\)art", |
|
|
150 }, "\033[36m"}}, |
|
|
151 {"gentoo", {{ |
|
|
152 R"art( _-----_)art", |
|
|
153 R"art(( \)art", |
|
|
154 R"art(\ 0 \)art", |
|
|
155 R"art( \ ))art", |
|
|
156 R"art( / _/)art", |
|
|
157 R"art(( _-)art", |
|
|
158 R"art(\____-)art", |
|
|
159 }, "\033[35m"}}, |
|
|
160 }; |
|
|
161 |
|
|
162 static const Logo fallback = {{ |
|
|
163 R"art( .--.)art", |
|
|
164 R"art( |o_o |)art", |
|
|
165 R"art( |:_/ |)art", |
|
|
166 R"art( // \ \)art", |
|
|
167 R"art( (| | ))art", |
|
|
168 R"art(/'\_ _/`\)art", |
|
|
169 R"art(\___)=(___/)art", |
|
|
170 }, "\033[33m"}; |
|
|
171 |
|
|
172 std::string id = override.empty() ? d.id : override; |
|
|
173 auto it = logos.find(id); |
|
|
174 if (it != logos.end()) return it->second; |
|
|
175 |
|
|
176 // ID_LIKE fallback (Manjaro -> arch, etc.) |
|
|
177 std::istringstream ss(d.idLike); |
|
|
178 std::string word; |
|
|
179 while (ss >> word) { |
|
|
180 it = logos.find(word); |
|
|
181 if (it != logos.end()) return it->second; |
|
|
182 } |
|
|
183 return fallback; |
|
|
184 } |
|
|
185 |
|
|
186 // ---------- main ---------- |
|
|
187 |
|
|
188 int main(int argc, char** argv) { |
|
|
189 std::string override; |
|
|
190 if (argc >= 3 && std::string(argv[1]) == "--logo") override = argv[2]; |
|
|
191 |
|
|
192 const Distro distro = readDistro(); |
|
|
193 const Logo logo = pickLogo(distro, override); |
|
|
194 const std::string hostname = readHostname(); |
|
|
195 const std::string kernver = readVersion(); |
|
|
196 |
|
|
197 size_t logoWidth = 0; |
|
|
198 for (const auto& line : logo.lines) logoWidth = std::max(logoWidth, line.size()); |
|
|
199 |
|
|
200 CpuTimes prev = readCpuTimes(); |
|
|
201 const double kbToGb = 1024.0 * 1024.0; |
|
|
202 |
|
|
203 while (true) { |
|
|
204 std::this_thread::sleep_for(std::chrono::seconds(1)); |
|
|
205 |
|
|
206 Mem m = readMeminfo(); |
|
|
207 long up = readUptime(); |
|
|
208 CpuTimes now = readCpuTimes(); |
|
|
209 |
|
|
210 long totalChange = now.total - prev.total; |
|
|
211 double cpuPercent = 0.0; |
|
|
212 if (totalChange > 0) cpuPercent = 100.0 * (now.busy - prev.busy) / totalChange; |
|
|
213 prev = now; |
|
|
214 |
|
|
215 std::ostringstream uptimeLine, memLine, cpuLine; |
|
|
216 uptimeLine << "Uptime: "; |
|
|
217 if (up < 0) { |
|
|
218 uptimeLine << "Unknown"; |
|
|
219 } else { |
|
|
220 uptimeLine << up / 86400 << "d " << (up % 86400) / 3600 << "h " |
|
|
221 << (up % 3600) / 60 << "m"; |
|
0
|
222 } |
|
1
|
223 |
|
|
224 memLine << std::fixed << std::setprecision(2) << "Memory: "; |
|
|
225 if (m.total == 0) { |
|
|
226 memLine << "Unknown"; |
|
|
227 } else { |
|
|
228 memLine << (m.total - m.available) / kbToGb << " GB / " |
|
|
229 << m.total / kbToGb << " GB"; |
|
0
|
230 } |
|
1
|
231 |
|
|
232 cpuLine << std::fixed << std::setprecision(1) |
|
|
233 << "CPU: " << cpuPercent << " %"; |
|
|
234 |
|
|
235 std::vector<std::string> info = { |
|
|
236 "OS: " + distro.pretty, |
|
|
237 "Hostname: " + hostname, |
|
|
238 "Kernel: " + kernver, |
|
|
239 uptimeLine.str(), |
|
|
240 memLine.str(), |
|
|
241 cpuLine.str(), |
|
|
242 }; |
|
|
243 |
|
|
244 size_t rows = std::max(logo.lines.size(), info.size()); |
|
|
245 |
|
|
246 std::cout << "\033[H\033[2J"; |
|
|
247 for (size_t i = 0; i < rows; i++) { |
|
|
248 std::string art = i < logo.lines.size() ? logo.lines[i] : ""; |
|
|
249 std::string text = i < info.size() ? info[i] : ""; |
|
|
250 std::cout << logo.color << std::left << std::setw(logoWidth) << art |
|
|
251 << "\033[0m" << " " << text << '\n'; |
|
|
252 } |
|
|
253 std::cout << std::flush; |
|
0
|
254 } |
|
|
255 } |