diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5104ea2..4f1e05b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,20 +2,24 @@ name: CI on: push: - branches: [ main ] + branches: [ main, linux_dev ] pull_request: - branches: [ main ] + branches: [ main, linux_dev ] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest] + os: [windows-latest, ubuntu-latest] steps: - uses: actions/checkout@v4 + - name: Install dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update && sudo apt-get install -y build-essential cmake + - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release diff --git a/CMakeLists.txt b/CMakeLists.txt index 001af5b..578f976 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,10 @@ target_include_directories(SysMood PRIVATE ${CMAKE_SOURCE_DIR}/include ) +if(WIN32) + target_link_libraries(SysMood PRIVATE Iphlpapi) +endif() + # Enable testing (used later) include(CTest) enable_testing() diff --git a/README.md b/README.md index efd2090..eded8bf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # SysMood +[![CI](https://github.com/RayBreeze/SysMood/actions/workflows/ci.yml/badge.svg)](https://github.com/RayBreeze/SysMood/actions/workflows/ci.yml) Hey there, the lone wonderer here! **SysMood** is a fun little console program for Windows that checks your CPU and memory usage, then tells you how your system is "feeling"—with some quirky moods and a splash of ASCII art. @@ -19,6 +20,8 @@ Hey there, the lone wonderer here! - **ASCII Art Banner:** Because every program deserves a cool entrance. + and many more.... + --- ## How to Use @@ -53,7 +56,7 @@ g++ -Iinclude src/main.cpp -o SysMood.exe winget install SysMood ``` -## What You’ll See +## What You’ll See [This is Demo of Release 1.0.0, future versions might differ...] ```sh ________ ___ ___ ________ _____ ______ ________ ________ ________ @@ -93,7 +96,7 @@ So much free memory! I could host a party in here! ## What You Need -- Windows (Linux folks—coming soon don't worry!) +- OS [ Windows or Linux ] - C++17 or newer (tested with g++ and MSVC) --- @@ -106,7 +109,17 @@ MIT License. See [LICENSE](LICENSE) for the legal stuff. ## Who Made This? -Made with ☕ and curiosity by [Samman Das (RayBreeze)](https://github.com/RayBreeze) +Made with ☕ and curiosity by [Samman Das (RayBreeze)](https://github.com/RayBreeze) and with the help of fellow Contributors across the Globe + +--- + +## Contributors + + + + + +Made with [contrib.rocks](https://contrib.rocks). --- diff --git a/SysMood.exe b/SysMood.exe new file mode 100644 index 0000000..1465fc8 Binary files /dev/null and b/SysMood.exe differ diff --git a/bin/sysMood b/bin/sysMood new file mode 100755 index 0000000..2b93353 Binary files /dev/null and b/bin/sysMood differ diff --git a/include/cpu_monitor.h b/include/cpu_monitor.h index 4247a3e..a67375c 100644 --- a/include/cpu_monitor.h +++ b/include/cpu_monitor.h @@ -21,48 +21,176 @@ // - this file is mostly chill, unless your system's on fire 🔥 // - not responsible if your computer gets feelings 🥲 // - if you find a bug, please report it to me on github -// +// #define _WIN32_WINNT 0x0602 #include #include -#include #include -uint64_t FromFileTime( const FILETIME& ft ) { - ULARGE_INTEGER uli = { 0 }; +#ifdef _WIN32 +#include +#elif __linux__ +#include +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +uint64_t FromFileTime(const FILETIME &ft) +{ + ULARGE_INTEGER uli = {0}; uli.LowPart = ft.dwLowDateTime; uli.HighPart = ft.dwHighDateTime; return uli.QuadPart; } +#elif __linux__ +struct CpuTimes +{ + uint64_t user, nice, system, idle, iowait, irq, softirq, steal; + uint64_t getTotalTime() const + { + return user + nice + system + idle + iowait + irq + softirq + steal; + } + uint64_t getIdleTime() const + { + return idle + iowait; + } +}; + +struct CPUStats +{ + uint64_t ctxt; + uint64_t btime; + uint64_t processes; + uint64_t procs_running; + uint64_t procs_blocked; +}; + +CpuTimes getCpuTimes() +{ + std::ifstream file("/proc/stat"); + if (!file.is_open()) + { + std::cerr << "Could not open /proc/stat" << std::endl; + return CpuTimes{0}; + } + std::string line; + std::getline(file, line); + + std::istringstream ss(line); + std::string cpu_label; + CpuTimes times = {0}; + + ss >> cpu_label >> times.user >> times.nice >> times.system >> times.idle >> times.iowait >> times.irq >> times.softirq >> times.steal; + + return times; +} + +CPUStats getCPUstats() +{ + std::ifstream file("/proc/stat"); + CPUStats stats = {0}; + if (!file.is_open()) + { + std::cerr << "Could not open /proc/stat" << std::endl; + return stats; + } + std::string line; + while (std::getline(file, line)) + { + std::istringstream ss(line); + std::string key; + ss >> key; + if (key == "ctxt") + { + ss >> stats.ctxt; + } + else if (key == "btime") + { + ss >> stats.btime; + } + else if (key == "processes") + { + ss >> stats.processes; + } + else if (key == "procs_running") + { + ss >> stats.procs_running; + } + else if (key == "procs_blocked") + { + ss >> stats.procs_blocked; + } + } + return stats; +} +#endif - class Processor{}; +class Processor +{ +}; - class Usage: public Processor +class Usage : public Processor +{ +public: +#ifdef _WIN32 + uint64_t FromFileTime(const FILETIME &ft) { - public: + ULARGE_INTEGER uli = {0}; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + return uli.QuadPart; + } - int now(){ - FILETIME i0, i1, k0, k1, u0, u1; - GetSystemTimes(&i0, &k0, &u0); - SleepEx(1000, false); - GetSystemTimes(&i1, &k1, &u1); + int now() + { + FILETIME i0, i1, k0, k1, u0, u1; + GetSystemTimes(&i0, &k0, &u0); + SleepEx(1000, false); + GetSystemTimes(&i1, &k1, &u1); + uint64_t idle0 = FromFileTime(i0); + uint64_t idle1 = FromFileTime(i1); + uint64_t kernel0 = FromFileTime(k0); + uint64_t kernel1 = FromFileTime(k1); + uint64_t user0 = FromFileTime(u0); + uint64_t user1 = FromFileTime(u1); - uint64_t idle0 = FromFileTime(i0); - uint64_t idle1 = FromFileTime(i1); - uint64_t kernel0 = FromFileTime(k0); - uint64_t kernel1 = FromFileTime(k1); - uint64_t user0 = FromFileTime(u0); - uint64_t user1 = FromFileTime(u1); + uint64_t idle = idle1 - idle0; + uint64_t kernel = kernel1 - kernel0; + uint64_t user = user1 - user0; - uint64_t idle = idle1 - idle0; - uint64_t kernel = kernel1 - kernel0; - uint64_t user = user1 - user0; - uint64_t total = (kernel + user) - idle; + double cpu = (1.0 - (double)idle / (kernel + user)) * 100.0; + return static_cast(cpu); + } - double cpu = (1.0 - (double)idle / (kernel + user)) * 100.0; +#elif __linux__ + int now() + { + CpuTimes times1 = getCpuTimes(); + sleep(1); + CpuTimes times2 = getCpuTimes(); - return static_cast(cpu); + uint64_t idle1 = times1.getIdleTime(); + uint64_t idle2 = times2.getIdleTime(); + uint64_t total1 = times1.getTotalTime(); + uint64_t total2 = times2.getTotalTime(); - } + uint64_t idle_diff = idle2 - idle1; + uint64_t total_diff = total2 - total1; + + if (total_diff == 0) + return 0; // Avoid division by zero + + double cpu = (double)(total_diff - idle_diff) / total_diff * 100.0; + return static_cast(cpu); + } + CPUStats getCPUstats() + { + return ::getCPUstats(); + } - }; +#endif +}; diff --git a/include/memory_monitor.h b/include/memory_monitor.h index 28ef439..8c895cc 100644 --- a/include/memory_monitor.h +++ b/include/memory_monitor.h @@ -24,36 +24,43 @@ // #include #include -#include #include + +#ifdef _WIN32 +#include #define DIV 1024 +#elif __linux__ +#include +#include +#include +#include +#define DIV 1024 +#endif + +class Memory { +public: +#ifdef _WIN32 int memory_percent(){ MEMORYSTATUSEX statusex; - statusex.dwLength = sizeof(statusex); - GlobalMemoryStatusEx(&statusex); - long long percent = statusex.dwMemoryLoad; - - return percent; + return statusex.dwMemoryLoad; } + int memory_available(){ MEMORYSTATUSEX statusex; - statusex.dwLength = sizeof(statusex); - GlobalMemoryStatusEx(&statusex); - long long available = statusex.ullAvailPhys / DIV / DIV; - - return available; + return statusex.ullAvailPhys / DIV / DIV; } + int memory_total(){ MEMORYSTATUSEX statusex; statusex.dwLength = sizeof(statusex); GlobalMemoryStatusEx(&statusex); - long long total = statusex.ullTotalPhys / DIV / DIV; - return total; + return statusex.ullTotalPhys / DIV / DIV; } + int memory_used(){ MEMORYSTATUSEX statusex; statusex.dwLength = sizeof(statusex); @@ -61,3 +68,60 @@ long long used = statusex.ullTotalPhys - statusex.ullAvailPhys; return used / DIV / DIV; } + +#elif __linux__ +private: + struct MemInfo { + long long total; + long long free; + long long available; + long long buffers; + long long cached; + }; + + MemInfo getMemInfo() { + MemInfo info = {0}; + std::ifstream file("/proc/meminfo"); + std::string line; + + while (std::getline(file, line)) { + std::istringstream iss(line); + std::string key; + long long value; + std::string unit; + + if (iss >> key >> value >> unit) { + if (key == "MemTotal:") info.total = value; + else if (key == "MemFree:") info.free = value; + else if (key == "MemAvailable:") info.available = value; + else if (key == "Buffers:") info.buffers = value; + else if (key == "Cached:") info.cached = value; + } + } + return info; + } + +public: + int memory_percent(){ + MemInfo info = getMemInfo(); + if (info.total == 0) return 0; + long long used = info.total - info.available; + return (used * 100) / info.total; + } + + int memory_available(){ + MemInfo info = getMemInfo(); + return info.available / DIV; + } + + int memory_total(){ + MemInfo info = getMemInfo(); + return info.total / DIV; + } + + int memory_used(){ + MemInfo info = getMemInfo(); + return (info.total - info.available) / DIV; + } +#endif +}; diff --git a/include/network_monitor.h b/include/network_monitor.h new file mode 100644 index 0000000..19d5349 --- /dev/null +++ b/include/network_monitor.h @@ -0,0 +1,109 @@ +#ifndef NETWORK_MONITOR_H +#define NETWORK_MONITOR_H + +// Written by: Anderson Yu-Hong Cai (https://github.com/AndersonTsaiTW) +// Written on: 2025-10-11 +// License: MIT +// Version: 2.0 +// +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0602 +#endif + +#include +#include +#include +#include +#include + +// Note: linking still required for iphlpapi when using g++: -liphlpapi + +static bool collect_if_counters(uint64_t &out_sent, uint64_t &out_recv) { + out_sent = 0; + out_recv = 0; + + // Fallback to GetIfTable which is widely available (MinGW). We'll + // allocate the table buffer dynamically as required. + MIB_IFTABLE *pIfTable = NULL; + DWORD dwSize = 0; + DWORD dwRetVal = GetIfTable(NULL, &dwSize, FALSE); + if (dwRetVal != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + + pIfTable = (MIB_IFTABLE*)malloc(dwSize); + if (pIfTable == NULL) return false; + + dwRetVal = GetIfTable(pIfTable, &dwSize, FALSE); + if (dwRetVal != NO_ERROR) { + free(pIfTable); + return false; + } + + std::set seenIndexes; + for (DWORD i = 0; i < pIfTable->dwNumEntries; ++i) { + MIB_IFROW &row = pIfTable->table[i]; + + // De-duplicate by dwIndex (if available). Some stacks list + // virtual/alias rows that may duplicate counters. + if (seenIndexes.find(row.dwIndex) != seenIndexes.end()) continue; + seenIndexes.insert(row.dwIndex); + + // Heuristic: include an interface if it shows any octets OR has a + // non-zero oper status. This avoids summing unused loopback/host + // entries while still counting active interfaces. + uint64_t outOct = (uint64_t)row.dwOutOctets; + uint64_t inOct = (uint64_t)row.dwInOctets; + if (outOct == 0 && inOct == 0 && row.dwOperStatus == 0) { + continue; + } + + out_sent += outOct; + out_recv += inOct; + } + + free(pIfTable); + return true; +} + +// Helper: compute difference with 64-bit wrap-around handling +static inline uint64_t counter_diff(uint64_t end, uint64_t start) { + if (end >= start) return end - start; + // wrapped + return (UINT64_MAX - start) + end + 1ULL; +} + +// Public API: samples counters for ~1s and returns KB/s +static void get_network_stats(double &sent_rate_kbps, double &received_rate_kbps) { + uint64_t sent0 = 0, recv0 = 0; + uint64_t sent1 = 0, recv1 = 0; + + if (!collect_if_counters(sent0, recv0)) { + sent_rate_kbps = 0.0; + received_rate_kbps = 0.0; + return; + } + + ULONGLONG t0 = GetTickCount64(); + Sleep(3000); // 3-second interval + + if (!collect_if_counters(sent1, recv1)) { + sent_rate_kbps = 0.0; + received_rate_kbps = 0.0; + return; + } + + ULONGLONG t1 = GetTickCount64(); + + double elapsed_s = (double)(t1 - t0) / 1000.0; + if (elapsed_s <= 0.0) elapsed_s = 1.0; // safety + + uint64_t ds = counter_diff(sent1, sent0); + uint64_t dr = counter_diff(recv1, recv0); + + // convert to KB/s + sent_rate_kbps = ((double)ds / 1024.0) / elapsed_s; + received_rate_kbps = ((double)dr / 1024.0) / elapsed_s; +} + +#endif // NETWORK_MONITOR_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a528335..5fc0474 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,19 +24,28 @@ // - if you find a bug, please report it to me on github // #include +#include #include "cpu_monitor.h" #include "memory_monitor.h" +#include "network_monitor.h" -int main() { +int main() +{ Usage cpu; - + Memory memory; int cpu_percent = cpu.now(); - int mem_percent = memory_percent(); - int mem_available = memory_available(); - int mem_total = memory_total(); - int mem_used = memory_used(); - std::cout << -R"(________ ___ ___ ________ _____ ______ ________ ________ ________ +#ifdef __linux__ + CPUStats stats = cpu.getCPUstats(); +#endif + int mem_percent = memory.memory_percent(); + int mem_available = memory.memory_available(); + int mem_total = memory.memory_total(); + int mem_used = memory.memory_used(); + double sent_rate_kbps, received_rate_kbps; + get_network_stats(sent_rate_kbps, received_rate_kbps); + double sum_rate_kbs = sent_rate_kbps + received_rate_kbps; + std::cout << + R"(________ ___ ___ ________ _____ ______ ________ ________ ________ |\ ____\ |\ \ / /|\ ____\|\ _ \ _ \|\ __ \|\ __ \|\ ___ \ \ \ \___|_ \ \ \/ / | \ \___|\ \ \\\__\ \ \ \ \|\ \ \ \|\ \ \ \_|\ \ \ \_____ \ \ \ / / \ \_____ \ \ \\|__| \ \ \ \\\ \ \ \\\ \ \ \ \\ \ @@ -46,33 +55,73 @@ R"(________ ___ ___ ________ _____ ______ ________ ________ ______ \|_________\|___|/ \|_________| )" << std::endl; - - std::cout << "Sysmood: Your system is feeling. It also has moods." << std::endl; - std::cout << "========================System Stats======================= " << std::endl; - std::cout << "CPU Usage: " << cpu_percent << "%" << std::endl; - std::cout << "Memory Usage: " << mem_percent << "%" << std::endl; - std::cout << "Memory Available: " << mem_available << " MB" << std::endl; - std::cout << "Memory Total: " << mem_total << " MB" << std::endl; - std::cout << "Memory Used: " << mem_used << " MB" << std::endl; - std::cout << "=========================================================== " << std::endl; - - std::cout << "========================System Mood======================== " << std::endl; - if (cpu_percent > 80) { - std::cout << "I am getting fried here helpppppp :o" << std::endl; - } else if (cpu_percent > 50) { - std::cout << "Well I am working here, cool :)" << std::endl; - } else { - std::cout << "Yeah I am chilling here, toally chillll ;)" << std::endl; + + std::cout << "Sysmood: Your system is feeling. It also has moods." << '\n'; + std::cout << "========================System Stats======================= " << '\n'; + std::cout << "CPU Usage: " << cpu_percent << "%" << '\n'; +#ifdef __linux__ + std::cout << "Context Switches: " << stats.ctxt << '\n'; + std::cout << "Boot Time: " << std::ctime(reinterpret_cast(&stats.btime)) << '\n'; + std::cout << "Processes Created: " << stats.processes << '\n'; + std::cout << "Processes Running: " << stats.procs_running << '\n'; + std::cout << "Processes Blocked: " << stats.procs_blocked << '\n'; +#endif + std::cout << "Memory Usage: " << mem_percent << "%" << '\n'; + std::cout << "Memory Available: " << mem_available << " MB" << '\n'; + std::cout << "Memory Total: " << mem_total << " MB" << '\n'; + std::cout << "Memory Used: " << mem_used << " MB" << '\n'; + std::cout << "Network Sent: " << sent_rate_kbps << " KB/s" << std::endl; + std::cout << "Network Received: " << received_rate_kbps << " KB/s" << std::endl; + std::cout << "=========================================================== " << '\n'; + + std::cout << "========================System Mood======================== " << '\n'; + if (cpu_percent > 80) + { + std::cout << "I am getting fried here helpppppp :o" << '\n'; + } + else if (cpu_percent > 50) + { + std::cout << "Well I am working here, cool :)" << '\n'; } - if (mem_percent > 80) { - std::cout << "Whoa! My brain is almost full... I'm about to forget something! " << std::endl; - } else if (mem_percent > 50) { - std::cout << "Memory's getting a little crowded, but I can still juggle things! " << std::endl; - } else { - std::cout << "So much free memory! I could host a party in here! " << std::endl; + else + { + std::cout << "Yeah I am chilling here, toally chillll ;)" << '\n'; } - std::cout << "=========================================================== " << std::endl; - - + if (mem_percent > 80) + { + std::cout << "Whoa! My brain is almost full... I'm about to forget something! " << '\n'; + } + else if (mem_percent > 50) + { + std::cout << "Memory's getting a little crowded, but I can still juggle things! " << '\n'; + } + else + { + std::cout << "So much free memory! I could host a party in here! " << '\n'; + } + + if (sum_rate_kbs <= 0.0) + { + std::cout << "Hello...? Anyone out there? I think I'm alone in the digital void..." << std::endl; + } + else if (sum_rate_kbs <= 10.0) // 1–10 KB/s:low + { + std::cout << "I can barely feel the connection... maybe send a ping to keep me alive?" << std::endl; + } + else if (sum_rate_kbs <= 100.0) // 11–100 KB/s:moderate + { // 11–100 KB/s:moderate + std::cout << "Hmm... data's trickling in. Not bad, but I'm starting to warm up!" << std::endl; + } + else if (sum_rate_kbs <= 1000.0) // 101–1000 KB/s:good + { + std::cout << "Nice flow! I can stream tunes, fetch memes, and still feel chill~" << std::endl; + } + else // > 1000 KB/s:fast + { + std::cout << "Wooosh! Data's flying-pages load before you blink. I'm unstoppable!!! " << std::endl; + } + + std::cout << "=========================================================== " << '\n'; + return 0; } \ No newline at end of file