240 lines
8.0 KiB
C++
240 lines
8.0 KiB
C++
#include <sys/statvfs.h>
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <csignal>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "include/LCDDisplay.hpp"
|
|
#include "include/gpu.hpp"
|
|
#include "include/utils.hpp"
|
|
|
|
#define LCD_PORT "/dev/serial/by-id/usb-Raspberry_Pi_Pico_E6616407E37AA12F-if00" // tis a symlink, can also be just /dev/ttyACM0
|
|
#define LCD_INTERVAL_MS 500
|
|
#define LCD_CONTRAST 215
|
|
#define FILL_CHAR ' '
|
|
#define N_CPU 16
|
|
|
|
struct mem_usage_t {
|
|
uint64_t free;
|
|
uint64_t used;
|
|
uint64_t total;
|
|
};
|
|
|
|
bool daemon_mode = false;
|
|
|
|
/* DISPLAY FUNCS */
|
|
// converts number to string with given decimal places
|
|
std::string pNum(auto num, uint8_t decs = 0) {
|
|
std::stringstream ret;
|
|
ret << std::fixed << std::setprecision(decs) << num;
|
|
return ret.str();
|
|
}
|
|
|
|
|
|
// converts large number in (k)bytes to human readable numer with suffix and sane decimal
|
|
std::string pBytes(double bytes, bool isKbytes = false) {
|
|
std::array suffix_list = {'K', 'M', 'G', 'T', 'P', 'E', 'Z'};
|
|
for (auto &suffix : suffix_list) {
|
|
if (suffix == 'K' && isKbytes) continue; // skip first loop if already in Kbytes
|
|
bytes /= 1024.0;
|
|
if (bytes < 1024) {
|
|
uint8_t decs = 2;
|
|
if (bytes > 10) decs = 1;
|
|
if (bytes > 100) decs = 0;
|
|
return pNum(bytes, decs) + suffix;
|
|
}
|
|
}
|
|
return "++++";
|
|
}
|
|
|
|
// pads given string to length
|
|
std::string pad(std::string s, uint8_t len, bool ljust = false) {
|
|
if (len > s.size())
|
|
s.insert(ljust ? s.end() : s.begin(), len - s.size(), FILL_CHAR);
|
|
return s;
|
|
}
|
|
|
|
// fills n spaces
|
|
std::string fill(uint8_t n) {
|
|
return std::string(n, FILL_CHAR);
|
|
}
|
|
|
|
/* GETTERS */
|
|
std::string getAvgs() {
|
|
std::string ret;
|
|
double avgs[3];
|
|
getloadavg(avgs, 3);
|
|
for (uint8_t i = 0; i < 3; i++)
|
|
ret += pNum(avgs[i], avgs[i] > 10 ? 1 : 2) + " ";
|
|
ret.pop_back(); // remove trailing space
|
|
return ret;
|
|
}
|
|
|
|
std::array<std::string, 2> getTimeDate() {
|
|
std::array months = {"sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paz", "lis", "gru"};
|
|
std::array wdays = {"Nie", "Pon", "Wto", "Sro", "Czw", "Pia", "Sob"};
|
|
// std::array wdays = {"Ni", "Pn", "Wt", "Sr", "Cz", "Pi", "So"};
|
|
auto now = std::time(nullptr);
|
|
auto tm = *std::localtime(&now);
|
|
std::stringstream time, date;
|
|
time << std::setfill('0');
|
|
time << std::setw(2) << tm.tm_hour << ":";
|
|
time << std::setw(2) << tm.tm_min << ":";
|
|
time << std::setw(2) << tm.tm_sec;
|
|
date << wdays[tm.tm_wday] << ", " << tm.tm_mday << " " << months[tm.tm_mon];
|
|
return {time.str(), date.str()};
|
|
}
|
|
|
|
mem_usage_t getRam() {
|
|
mem_usage_t mem;
|
|
std::ifstream proc_meminfo("/proc/meminfo");
|
|
|
|
if (proc_meminfo.good()) {
|
|
std::string content((std::istreambuf_iterator<char>(proc_meminfo)), std::istreambuf_iterator<char>());
|
|
mem.free = get_val("MemAvailable:", content);
|
|
mem.total = get_val("MemTotal:", content);
|
|
mem.used = mem.total - mem.free;
|
|
}
|
|
proc_meminfo.close();
|
|
return mem;
|
|
}
|
|
|
|
mem_usage_t getDisk(std::string mount) {
|
|
mem_usage_t disk;
|
|
struct statvfs diskData;
|
|
statvfs(mount.c_str(), &diskData);
|
|
disk.free = diskData.f_bfree * diskData.f_frsize;
|
|
disk.used = (diskData.f_blocks - diskData.f_bfree) * diskData.f_frsize;
|
|
disk.total = diskData.f_blocks * diskData.f_frsize;
|
|
return disk;
|
|
}
|
|
|
|
auto getCpuTemp(std::string hw) {
|
|
struct {
|
|
double tdie;
|
|
double tctl;
|
|
double tccd1;
|
|
double pcore = 0.0;
|
|
double psoc = 0.0;
|
|
double ptotal = 0.0;
|
|
double vcore = 0.0;
|
|
double vsoc = 0.0;
|
|
uint32_t avg_freq = 0;
|
|
} cpu;
|
|
|
|
// commented out unused values so its faster
|
|
if (readFile(hw + "/name") == "zenpower") {
|
|
// cpu.tdie = (double)(std::stoul(readFile(hw + "/temp1_input")) / 1000.0);
|
|
cpu.tctl = (double)(std::stoul(readFile(hw + "/temp2_input")) / 1000.0);
|
|
cpu.tccd1 = (double)(std::stoul(readFile(hw + "/temp3_input")) / 1000.0);
|
|
cpu.pcore = (double)(std::stoul(readFile(hw + "/power1_input")) / 1000000.0);
|
|
cpu.psoc = (double)(std::stoul(readFile(hw + "/power2_input")) / 1000000.0);
|
|
cpu.ptotal = cpu.pcore + cpu.psoc;
|
|
cpu.vcore = (double)(std::stoul(readFile(hw + "/in1_input")) / 1000.0);
|
|
// cpu.vsoc = (double)(std::stoul(readFile(hw + "/in2_input")) / 1000.0);
|
|
} else {
|
|
cpu.tctl = (double)(std::stoul(readFile(hw + "/temp1_input")) / 1000.0);
|
|
cpu.tccd1 = (double)(std::stoul(readFile(hw + "/temp3_input")) / 1000.0);
|
|
}
|
|
|
|
// lots of reads but still faster than reading /proc/cpuinfo
|
|
for(uint8_t i = 0; i < N_CPU; i++)
|
|
cpu.avg_freq += (long)(std::stol(readFile("/sys/devices/system/cpu/cpu" + std::to_string(i) + "/cpufreq/cpuinfo_avg_freq", 3)));
|
|
cpu.avg_freq /= N_CPU;
|
|
|
|
return cpu;
|
|
}
|
|
|
|
void sighandler(int s) {
|
|
daemon_mode = false;
|
|
std::cout << "[Signal] deamon_mode = false, exiting on next loop" << std::endl;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
signal(SIGINT, sighandler);
|
|
daemon_mode = (argc > 1 && std::string(argv[1]) == "-d");
|
|
|
|
/* init lcd and chars, set contrast and backlight */
|
|
LCDDisplay lcd(LCD_PORT);
|
|
std::array<std::string, 4> lines;
|
|
lcd.setCustomChar(0, {16, 6, 9, 8, 8, 9, 6, 0}); // celcius
|
|
|
|
/* find correct cpu module */
|
|
auto cpu_hwmon = findHwmonPath("k10temp");
|
|
if (cpu_hwmon.size() == 0) {
|
|
std::cout << "[Hwmon] k10temp not found, trying zenpower" << std::endl;
|
|
cpu_hwmon = findHwmonPath("zenpower");
|
|
}
|
|
|
|
/* figure out gpu path */
|
|
std::string gpu_hwmon;
|
|
GpuMetricsHeader_t gpu_mtr_rev;
|
|
for (uint8_t c = 0; c < 10; c++) {
|
|
gpu_hwmon = "/sys/class/drm/card" + std::to_string(c) + "/device";
|
|
gpu_mtr_rev = getGpuMetricsHeader(gpu_hwmon + "/gpu_metrics");
|
|
if (gpu_mtr_rev.structure_size > 0)
|
|
break;
|
|
}
|
|
|
|
/* loop setup */
|
|
std::chrono::milliseconds interval(LCD_INTERVAL_MS);
|
|
auto start = std::chrono::steady_clock::now();
|
|
if (daemon_mode) {
|
|
std::cout << "[Chrono] Running in daemon mode, " << LCD_INTERVAL_MS << "ms interval" << std::endl;
|
|
lcd.setContrast(LCD_CONTRAST);
|
|
lcd.setBacklight(true);
|
|
}
|
|
|
|
/* main loop */
|
|
while (true) {
|
|
auto td = getTimeDate();
|
|
auto ram = getRam();
|
|
// auto anime = getDisk("/mnt/anime");
|
|
auto home = getDisk("/home/drath");
|
|
auto gpu = getGpu(gpu_mtr_rev, gpu_hwmon);
|
|
auto cpu = getCpuTemp(cpu_hwmon);
|
|
|
|
lines[0] = pad(td[1], 11, true) + " " + td[0] + lcd.bar(1, gpu.core_usage / 100.0) + pad(getAvgs(), 17);
|
|
|
|
lines[1] = pad(pNum(cpu.tctl) + "/" + pNum(cpu.tccd1) + lcd.cc[0], 8, true);
|
|
lines[1] += pad(pBytes(ram.used, true) + "B", 7, true);
|
|
lines[1] += fill(7);
|
|
lines[1] += pad(pBytes(gpu.vram_used) + "B", 7);
|
|
lines[1] += pad(std::to_string(gpu.edge_temp) + "/" + std::to_string(gpu.hotspot_temp) + "/" + std::to_string(gpu.mem_temp) + lcd.cc[0], 11);
|
|
|
|
lines[2] = pad(pNum(cpu.ptotal, 1) + "W", 8, true);
|
|
lines[2] += pad("home: " + pBytes(home.free), 12);
|
|
lines[2] += fill(6);
|
|
lines[2] += pad(pNum(gpu.fan_speed) + "R", 5);
|
|
lines[2] += pad(std::to_string(gpu.power_avg) + "/" + std::to_string(gpu.power_cap) + "W", 9);
|
|
|
|
lines[3] = pad(pNum(cpu.vcore, 3) + "V", 7, true);
|
|
lines[3] += std::to_string(cpu.avg_freq) + "MHz";
|
|
lines[3] += fill(7);
|
|
lines[3] += pad(pNum(gpu.core_clock) + "/" + pNum(gpu.mem_clock) + "MHz ", 13);
|
|
lines[3] += pad(pNum(gpu.vcore, 3) + "V", 5);
|
|
|
|
lcd.writeLines(lines);
|
|
|
|
if (daemon_mode) {
|
|
std::this_thread::sleep_until(start + interval);
|
|
start += interval;
|
|
} else {
|
|
std::cout << "[Chrono] took " << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count() / 1000.0 << "ms" << std::endl;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* should exit in loop, if it doesnt something went wrong */
|
|
return 1;
|
|
}
|