Various Changes

- Code refactorisations
- Logic optimisations
- Update README.md
- etc

Signed-off-by: MOVZX <movzx@yahoo.com>
This commit is contained in:
2025-07-01 17:08:15 +07:00
parent 5e58a58374
commit aa3d0e5951
6 changed files with 1255 additions and 663 deletions

View File

@@ -1,5 +1,95 @@
# Ryzen-Power
A simple app to get the Ryzen CPU power (in Watt) using just the sysfs interface
# Ryzen Power
Kumpulan utilitas baris perintah sederhana untuk memantau berbagai sensor perangkat keras pada sistem Linux, dengan fokus pada CPU AMD Ryzen. Utilitas ini membaca data langsung dari antarmuka sysfs dan mengeksekusi beberapa perintah eksternal untuk mendapatkan informasi yang komprehensif.
![Screenshot](screenshot.png)
![Screenshot](screenshot-stats.png)
## Fitur
Proyek ini menyediakan empat utilitas terpisah:
1. `ryzen`: Menampilkan konsumsi daya CPU saat ini dalam Watt.
2. `cpuf`: Menampilkan frekuensi, suhu, dan daya CPU secara real-time.
3. `powerusage`: Menampilkan statistik penggunaan untuk CPU atau GPU.
4. `sens`: Utilitas pemantauan sensor lengkap untuk seluruh sistem.
## Prasyarat
- `gcc` (GNU Compiler Collection)
- `libpci-dev` (atau yang setara) untuk `powerusage`
- `lm-sensors` (opsional, untuk beberapa data sensor)
- `rocm-smi` (untuk GPU AMD)
- `nvidia-ml` (NVML) dan `CUDA SDK` (untuk GPU NVIDIA)
- `dmidecode`
## Kompilasi
Untuk mengkompilasi semua utilitas, jalankan skrip build:
```bash
./build.sh
```
Skrip akan secara otomatis mendeteksi keberadaan NVIDIA CUDA SDK dan mengkompilasi `powerusage` dengan dukungan untuk GPU NVIDIA jika ditemukan.
## Penggunaan
### 1. `ryzen`
Utilitas paling dasar. Cukup jalankan untuk mendapatkan konsumsi daya CPU saat ini.
```bash
./ryzen
```
Outputnya adalah nilai tunggal dalam Watt.
### 2. `cpuf`
Menampilkan informasi terperinci tentang CPU, termasuk nama model, suhu (Tctl/Tccd), konsumsi daya, dan frekuensi setiap inti.
```bash
./cpuf
```
### 3. `powerusage`
Menyediakan statistik untuk CPU atau GPU. Anda harus menentukan target (`cpu` atau `gpu`).
**Untuk CPU:**
```bash
./powerusage cpu
```
Menampilkan penggunaan CPU (%), penggunaan memori (GB), suhu CPU, suhu DRAM, dan konsumsi daya CPU.
**Untuk GPU:**
```bash
./powerusage gpu
```
Menampilkan penggunaan GPU (%), penggunaan VRAM (%), suhu (tepi, sambungan, memori), dan konsumsi daya GPU. Mendukung GPU AMD (melalui `rocm-smi`) dan NVIDIA (melalui `NVML`).
### 4. `sens`
Alat pemantauan sensor terlengkap. Memberikan gambaran umum tentang berbagai suhu dan kecepatan kipas di seluruh sistem.
```bash
./sens
```
Output mencakup:
- Informasi sistem (Motherboard)
- Suhu Motherboard (Mobo, VRM, Chipset)
- Kecepatan Kipas
- Informasi CPU (Nama, Suhu, Daya)
- Informasi DRAM (Model, Suhu)
- Informasi GPU (Suhu, Daya)
- Informasi SSD NVMe (Suhu)
## Lisensi
Proyek ini dilisensikan di bawah Lisensi Publik Umum GNU v2.0. Lihat file `LICENSE` untuk detailnya.

View File

@@ -2,5 +2,17 @@
gcc -O3 -o ryzen ryzen.c -lm
gcc -O3 -o cpuf cpuf.c -lm
gcc -O3 -o sens sens.c -lm
gcc -O3 -Wall -Werror -Wextra -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wold-style-definition -Wvla -I/opt/cuda/include -o powerusage powerusage.c -lpci -lnvidia-ml -lm
if [ -d /opt/cuda ]; then
# NVIDIA
echo -e "\e[32m✔ NVIDIA CUDA SDK found. Compiling with NVIDIA support. 🚀\e[0m"
gcc -O3 -Wall -Werror -Wextra -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wold-style-definition -Wvla -I/opt/cuda/include -o powerusage powerusage.c -lpci -lnvidia-ml -lm -DNVIDIA_GPU
gcc -O3 -o sens sens.c -lm
else
# AMD/Intel
echo -e "\e[33m NVIDIA CUDA SDK not found. Compiling without NVIDIA support. CPU only. 💻\e[0m"
gcc -O3 -Wall -Werror -Wextra -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wold-style-definition -Wvla -o powerusage powerusage.c -lpci -lm
gcc -O3 -o sens sens.c -lm
fi

297
cpuf.c
View File

@@ -1,32 +1,62 @@
/*
* cpuf - Utilitas untuk memantau frekuensi, suhu, dan daya CPU.
*
* Hak Cipta (C) 2024 MOVZX
*
* Program ini adalah perangkat lunak bebas; Anda dapat menyebarluaskannya kembali
* dan/atau memodifikasinya di bawah ketentuan Lisensi Publik Umum GNU
* sebagaimana dipublikasikan oleh Free Software Foundation; baik versi 2
* dari Lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
*
* Program ini didistribusikan dengan harapan akan bermanfaat,
* tetapi TANPA JAMINAN APAPUN; bahkan tanpa jaminan tersirat
* DAGANGAN atau KESESUAIAN UNTUK TUJUAN TERTENTU. Lihat
* Lisensi Publik Umum GNU untuk lebih jelasnya.
*
* Anda seharusnya telah menerima salinan Lisensi Publik Umum GNU
* bersama dengan program ini; jika tidak, tulislah ke Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdint.h>
#include <glob.h>
#include <limits.h>
#include <fcntl.h>
#include <libgen.h>
#define RAPL_FILE_PATH "/sys/class/powercap/intel-rapl:0/energy_uj"
#define NUM_CPUS 16
#define BUFFER_SIZE 256
#define USEC 1000000
#define BOLD "\033[1m"
#define RESET "\033[0m"
int64_t get_cpuConsumptionUJoules()
/**
* @brief Mendapatkan konsumsi energi CPU saat ini dalam mikrojoule.
*
* @return int64_t Konsumsi energi dalam mikrojoule, atau -1 jika gagal.
*/
int64_t get_cpu_consumption_ujoules()
{
int64_t consumption = -1;
FILE *file = fopen(RAPL_FILE_PATH, "r");
if (file == NULL)
{
perror("Error opening RAPL energy file!");
perror("Error opening RAPL energy file");
return -1;
}
if (fscanf(file, "%lld", &consumption) != 1)
if (fscanf(file, "%ld", &consumption) != 1)
{
perror("Error reading energy consumption!");
perror("Error reading energy consumption");
consumption = -1;
}
@@ -36,216 +66,231 @@ int64_t get_cpuConsumptionUJoules()
return consumption;
}
int64_t get_currentTimeUSec()
/**
* @brief Mengembalikan waktu saat ini dalam mikrodetik.
*
* @return int64_t Waktu saat ini dalam mikrodetik, atau -1 jika gagal.
*/
int64_t get_current_time_usec()
{
struct timeval tv;
if (gettimeofday(&tv, NULL) != 0)
{
perror("Error getting current time!");
perror("Error getting current time");
return -1;
}
return ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec;
return ((int64_t)tv.tv_sec * USEC) + tv.tv_usec;
}
/**
* @brief Menghitung daya CPU rata-rata dalam Watt selama interval 1 detik.
*
* @return float Daya CPU dalam Watt, atau -1.0f jika gagal.
*/
float calculate_cpu_power()
{
int64_t initial_usage = get_cpuConsumptionUJoules();
int64_t initial_time = get_currentTimeUSec();
int64_t initial_usage = get_cpu_consumption_ujoules();
int64_t initial_time = get_current_time_usec();
if (initial_usage == -1 || initial_time == -1)
{
fprintf(stderr, "Failed to read initial CPU consumption or time data!\n");
fprintf(stderr, "Failed to read initial CPU consumption or time data\n");
return -1.0f;
}
usleep(1000000);
sleep(1);
int64_t final_usage = get_cpuConsumptionUJoules();
int64_t final_time = get_currentTimeUSec();
int64_t final_usage = get_cpu_consumption_ujoules();
int64_t final_time = get_current_time_usec();
if (final_usage == -1 || final_time == -1)
{
fprintf(stderr, "Failed to read final CPU consumption or time data!\n");
fprintf(stderr, "Failed to read final CPU consumption or time data\n");
return -1.0f;
}
if (final_time < initial_time)
if (final_time <= initial_time)
{
fprintf(stderr, "Time went backwards!\n");
fprintf(stderr, "Time did not advance or went backwards!\n");
return -1.0f;
}
int64_t time_diff_usec = final_time - initial_time;
int64_t energy_diff_uj = final_usage - initial_usage;
if (time_diff_usec <= 0)
{
fprintf(stderr, "Invalid time difference!\n");
return -1.0f;
}
int64_t time_diff_usec = final_time - initial_time;
if (energy_diff_uj < 0)
{
fprintf(stderr, "Energy consumption decreased!\n");
fprintf(stderr, "Energy consumption decreased, which is not possible.\n");
return -1.0f;
}
double time_diff_sec = time_diff_usec / 1000000.0;
return (float)(energy_diff_uj / (time_diff_sec * 1e6));
return (float)energy_diff_uj / (float)time_diff_usec;
}
/**
* @brief Membaca nilai integer dari file yang ditentukan.
*
* @param path Path ke file.
* @return int Nilai integer yang dibaca dari file, atau -1 jika gagal.
*/
int read_int_from_file(const char *path)
{
int value = 0;
int value = -1;
FILE *file = fopen(path, "r");
if (file == NULL)
{
perror("Error opening file!");
return -1;
}
if (fscanf(file, "%d", &value) != 1)
{
perror("Error reading integer from file!");
fclose(file);
return -1;
}
value = -1;
fclose(file);
return value;
}
int read_int_from_command(const char *command)
/**
* @brief Mendapatkan suhu CPU (Tctl dan Tccd).
*
* @param cpu_tctl Pointer untuk menyimpan suhu Tctl.
* @param cpu_tccd Pointer untuk menyimpan suhu Tccd.
* @return int 0 jika berhasil, -1 jika gagal.
*/
int get_cpu_temperatures(int *cpu_tctl, int *cpu_tccd)
{
FILE *fp;
char output[BUFFER_SIZE];
int value = -1;
char hwmon_path[BUFFER_SIZE];
char temp_path[BUFFER_SIZE];
int found = 0;
fp = popen(command, "r");
glob_t glob_result;
if (fp == NULL)
if (glob("/sys/class/hwmon/hwmon*/name", 0, NULL, &glob_result) == 0)
{
perror("Error running command!");
for (size_t i = 0; i < glob_result.gl_pathc; i++)
{
FILE *name_file = fopen(glob_result.gl_pathv[i], "r");
if (name_file)
{
char name[32];
if (fgets(name, sizeof(name), name_file))
{
name[strcspn(name, "\n")] = 0;
if (strcmp(name, "k10temp") == 0)
{
char *dir = dirname(glob_result.gl_pathv[i]);
strncpy(hwmon_path, dir, sizeof(hwmon_path) - 1);
hwmon_path[sizeof(hwmon_path) - 1] = '\0';
found = 1;
}
}
fclose(name_file);
if (found)
break;
}
}
}
globfree(&glob_result);
if (!found)
{
fprintf(stderr, "k10temp sensor module not found!\n");
return -1;
}
if (fgets(output, sizeof(output), fp) != NULL)
{
char *endptr;
value = strtol(output, &endptr, 10);
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", hwmon_path);
if (*endptr != '\0')
{
fprintf(stderr, "Error converting command output to integer!\n");
*cpu_tctl = read_int_from_file(temp_path);
value = -1;
}
}
else
snprintf(temp_path, sizeof(temp_path), "%s/temp3_input", hwmon_path);
*cpu_tccd = read_int_from_file(temp_path);
if (*cpu_tctl == -1 || *cpu_tccd == -1)
{
perror("Error reading output from command!");
fprintf(stderr, "Failed to read Tctl/Tccd temperatures.\n");
return -1;
}
pclose(fp);
return value;
return 0;
}
int main()
/**
* @brief Mendapatkan frekuensi saat ini untuk setiap inti CPU.
*
* @param freqs Array untuk menyimpan frekuensi setiap inti dalam MHz.
*/
void get_cpu_frequencies(int *freqs)
{
char hwmon_path[BUFFER_SIZE];
FILE *fp;
int cpu_tctl = -1, cpu_tccd = -1;
float cpu_power = -1.0f;
int cpu_freq[NUM_CPUS];
fp = popen("grep -l 'k10temp' /sys/class/hwmon/hwmon*/name", "r");
if (fp == NULL || fgets(hwmon_path, sizeof(hwmon_path), fp) == NULL)
{
printf("k10temp sensor module not found!\n");
pclose(fp);
return 1;
}
pclose(fp);
hwmon_path[strcspn(hwmon_path, "\n")] = 0;
size_t len = strlen(hwmon_path);
if (len > 5)
{
hwmon_path[len - 5] = '\0';
}
else
{
fprintf(stderr, "Invalid hwmon_path format!\n");
return 1;
}
char temp1_path[BUFFER_SIZE];
char temp3_path[BUFFER_SIZE];
snprintf(temp1_path, sizeof(temp1_path), "%s/temp1_input", hwmon_path);
snprintf(temp3_path, sizeof(temp3_path), "%s/temp3_input", hwmon_path);
cpu_tctl = read_int_from_file(temp1_path) / 1000;
cpu_tccd = read_int_from_file(temp3_path) / 1000;
if (cpu_tctl == -1 || cpu_tccd == -1)
{
printf("Failed to read temperatures!\n");
return 1;
}
cpu_power = calculate_cpu_power();
if (cpu_power == -1.0f)
{
printf("Failed to calculate CPU power!\n");
return 1;
}
for (int i = 0; i < NUM_CPUS; i++)
{
char freq_path[BUFFER_SIZE];
snprintf(freq_path, sizeof(freq_path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i);
cpu_freq[i] = read_int_from_file(freq_path) / 1000;
int freq_khz = read_int_from_file(freq_path);
freqs[i] = (freq_khz != -1) ? freq_khz / 1000 : 0;
}
}
/**
* @brief Mencetak informasi CPU yang diformat.
*
* @param cpu_tctl Suhu CPU Tctl.
* @param cpu_tccd Suhu CPU Tccd.
* @param cpu_power Daya CPU dalam Watt.
* @param cpu_freqs Array frekuensi inti CPU.
*/
void print_cpu_info(int cpu_tctl, int cpu_tccd, float cpu_power, const int *cpu_freqs)
{
printf(BOLD "Ryzen 7 7800X3D" RESET "\n\n");
printf("Tctl : %8d°C\n", cpu_tctl);
printf("Tccd : %8d°C\n", cpu_tccd);
printf("Tctl : %8d°C\n", cpu_tctl / 1000);
printf("Tccd : %8d°C\n", cpu_tccd / 1000);
printf("Power : %8.2f W\n", cpu_power);
printf("\n");
for (int i = 0; i < NUM_CPUS; i++)
{
printf("CPU %2d : %6d MHz\n", i + 1, cpu_freq[i]);
printf("CPU %2d : %6d MHz\n", i, cpu_freqs[i]);
}
}
/**
* @brief Titik masuk utama untuk program.
*
* @return int 0 jika berhasil, 1 jika gagal.
*/
int main()
{
int cpu_tctl = -1, cpu_tccd = -1;
int cpu_freqs[NUM_CPUS] = {0};
if (get_cpu_temperatures(&cpu_tctl, &cpu_tccd) != 0)
return 1;
float cpu_power = calculate_cpu_power();
if (cpu_power < 0)
return 1;
get_cpu_frequencies(cpu_freqs);
print_cpu_info(cpu_tctl, cpu_tccd, cpu_power, cpu_freqs);
return 0;
}

View File

@@ -1,3 +1,23 @@
/*
* powerusage - Utilitas untuk memantau penggunaan daya CPU dan GPU.
*
* Hak Cipta (C) 2024 MOVZX
*
* Program ini adalah perangkat lunak bebas; Anda dapat menyebarluaskannya kembali
* dan/atau memodifikasinya di bawah ketentuan Lisensi Publik Umum GNU
* sebagaimana dipublikasikan oleh Free Software Foundation; baik versi 2
* dari Lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
*
* Program ini didistribusikan dengan harapan akan bermanfaat,
* tetapi TANPA JAMINAN APAPUN; bahkan tanpa jaminan tersirat
* DAGANGAN atau KESESUAIAN UNTUK TUJUAN TERTENTU. Lihat
* Lisensi Publik Umum GNU untuk lebih jelasnya.
*
* Anda seharusnya telah menerima salinan Lisensi Publik Umum GNU
* bersama dengan program ini; jika tidak, tulislah ke Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <stdio.h>
@@ -12,133 +32,105 @@
#include <sys/mman.h>
#include <pci/pci.h>
#include <signal.h>
#include <glob.h>
#include <limits.h>
#include <syslog.h>
#ifdef NVIDIA_GPU
#include <nvml.h>
#endif
#define RAPL_FILE_PATH "/sys/class/powercap/intel-rapl:0/energy_uj"
#define MAX_NAME_LENGTH 300
#define USEC 1000000
#define KILO 1000
#define TO_GB (1024.0 * 1024.0)
#define VRAM_REGISTER_OFFSET 0x0000E2A8
#define RAPL_FILE_PATH "/sys/class/powercap/intel-rapl:0/energy_uj"
#define MAX_NAME_LENGTH 300
#define USEC 1000000
#define KILO 1000
#define TO_GB (1024.0 * 1024.0)
#define VRAM_REGISTER_OFFSET 0x0000E2A8
#define HOTSPOT_REGISTER_OFFSET 0x0002046c
#define PG_SZ sysconf(_SC_PAGE_SIZE)
#define MEM_PATH "/dev/mem"
#define MAX_DEVICES 1 // Sesuaikan dengan jumlah GPU yang ada
#define NVIDIA_VRAM_TEMP_MASK 0x00000fff
#define NVIDIA_VRAM_TEMP_DIVISOR 32
#define NVIDIA_HOTSPOT_TEMP_SHIFT 8
#define NVIDIA_HOTSPOT_TEMP_MASK 0xff
#define NVIDIA_HOTSPOT_VALID_MAX 0x7f
#define PG_SZ sysconf(_SC_PAGE_SIZE)
#define MEM_PATH "/dev/mem"
#define MAX_DEVICES 1
#define DEBUG 0
#define BUFFER_SIZE 32
float get_cpu_usage(void);
int64_t get_memory_usage(void);
float get_memory_usage(void);
int64_t get_cpuConsumptionUJoules(void);
int64_t get_currentTimeUSec(void);
float calculate_cpu_power(void);
char *execute_command(const char *command);
void print_cpu_info(void);
void print_amd_gpu_info(void);
#ifdef NVIDIA_GPU
void print_nvidia_gpu_info(void);
#endif
int detect_gpu_type(void);
enum GpuType
{
GPU_TYPE_UNINITIALIZED = -1,
GPU_TYPE_NONE = 0,
GPU_TYPE_AMD,
GPU_TYPE_NVIDIA,
};
typedef struct
{
unsigned int vram_temp;
unsigned int hotspot_temp;
unsigned int gpu_temp;
unsigned int power_usage;
unsigned int gpu_util;
float fb_used;
} NvidiaDeviceData;
static NvidiaDeviceData nvidia_devices[MAX_DEVICES];
static int nvidia_fd = -1;
static void *nvidia_map_base = MAP_FAILED;
static int gpu_type = 0;
unsigned long long total;
unsigned long long idle;
} CpuTimes;
/**
* Mendapatkan penggunaan CPU dalam satuan persentase.
* @brief Menghitung penggunaan memori dalam GB.
*
* Nilai balik:
* -1.0f jika gagal membaca penggunaan CPU,
* float lainnya adalah persentase CPU yang digunakan.
* Fungsi ini membaca /proc/meminfo untuk mendapatkan total dan memori yang tersedia,
* lalu menghitung memori yang digunakan dan mengembalikannya dalam gigabyte.
*
* @return float Penggunaan memori dalam GB, atau -1.0f jika terjadi kesalahan.
*/
float get_cpu_usage(void)
{
char *cpu_usage = execute_command("top -bn1 | grep 'Cpu(s)' | awk '{print $2}'");
if (!cpu_usage)
return -1.0f;
float usage = atof(cpu_usage);
free(cpu_usage);
return usage;
}
/**
* Mendapatkan penggunaan memori dalam satuan gigabyte (GB).
*
* Parameter:
* Tidak ada parameter yang diperlukan.
*
* Nilai Kembali:
* - Jika berhasil, fungsi mengembalikan penggunaan memori dalam satuan gigabyte (GB).
* - Jika terjadi kegagalan (misalnya, tidak dapat membuka file "/proc/meminfo"), fungsi mengembalikan nilai -1.
*
* Catatan:
* - Fungsi ini bergantung pada file "/proc/meminfo". Pastikan file ini tersedia di sistem.
* - Fungsi ini menggunakan alokasi memori dinamis untuk menyimpan hasil membaca file "/proc/meminfo". Pastikan memori dibebaskan dengan benar untuk menghindari kebocoran memori.
*/
int64_t get_memory_usage(void)
float get_memory_usage(void)
{
FILE *file = fopen("/proc/meminfo", "r");
if (!file)
{
perror("fopen");
if (DEBUG)
perror("fopen");
return -1;
return -1.0f;
}
char buffer[MAX_NAME_LENGTH];
int64_t total_memory = 0, available_memory = 0;
size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, file);
buffer[bytes_read] = '\0';
long total_memory = 0, available_memory = 0;
while (fgets(buffer, sizeof(buffer), file))
{
if (sscanf(buffer, "MemTotal: %ld kB", &total_memory) == 1)
continue;
if (sscanf(buffer, "MemAvailable: %ld kB", &available_memory) == 1)
break;
}
fclose(file);
char *line = strtok(buffer, "\n");
while (line)
{
if (sscanf(line, "MemTotal: %ld kB", &total_memory) == 1)
{
// Read total memory
}
else if (sscanf(line, "MemAvailable: %ld kB", &available_memory) == 1)
{
break;
}
line = strtok(NULL, "\n");
}
if (total_memory == 0 || available_memory == 0)
return -1;
return -1.0f;
return (total_memory - available_memory) / TO_GB;
return (float)(total_memory - available_memory) / (1024.0f * 1024.0f);
}
/**
* Mendapatkan konsumsi energi/daya CPU saat ini dalam satuan mikrojoule.
* @brief Mengambil konsumsi energi CPU dalam microjoule.
*
* Parameter:
* Tidak ada parameter yang diperlukan.
* Fungsi ini membaca file RAPL untuk mendapatkan total energi yang dikonsumsi oleh CPU.
*
* Nilai Kembali:
* - Jika berhasil, fungsi mengembalikan konsumsi energi/daya CPU dalam satuan mikrojoule.
* - Jika terjadi kegagalan (misalnya, file RAPL_FILE_PATH tidak dapat dibuka), fungsi mengembalikan nilai -1.
*
* Catatan:
* - Fungsi ini bergantung pada file RAPL_FILE_PATH. Pastikan file ini tersedia di sistem (biasanya tersedia di distribusi Linux standar).
* - RAPL_FILE_PATH harus didefinisikan sebelumnya dalam kode untuk menentukan path file konsumsi energi/daya CPU.
* @return int64_t Konsumsi energi dalam microjoule, atau -1 jika terjadi kesalahan.
*/
int64_t get_cpuConsumptionUJoules(void)
{
@@ -147,7 +139,8 @@ int64_t get_cpuConsumptionUJoules(void)
if (!file || fscanf(file, "%ld", &consumption) != 1)
{
perror("Gagal membaca konsumsi energi/daya!");
if (DEBUG)
perror("Failed to read energy consumption!");
if (file)
fclose(file);
@@ -161,27 +154,21 @@ int64_t get_cpuConsumptionUJoules(void)
}
/**
* Mengembalikan waktu saat ini dalam satuan mikrodetik.
* @brief Mendapatkan waktu saat ini dalam mikrodetik.
*
* Parameter:
* Tidak ada parameter yang diperlukan.
* Fungsi ini menggunakan gettimeofday untuk mengambil waktu sistem saat ini dan
* mengubahnya menjadi mikrodetik.
*
* Nilai Kembali:
* - Jika berhasil, fungsi mengembalikan waktu saat ini dalam satuan mikrodetik.
* - Jika terjadi kegagalan (misalnya, fungsi gettimeofday gagal), fungsi mengembalikan nilai -1.
*
* Catatan:
* - Fungsi ini menggunakan gettimeofday untuk mendapatkan waktu saat ini, dan mengembalikan waktu
* dalam bentuk integer 64-bit yang mewakili jumlah mikrodetik sejak epoch.
* @return int64_t Waktu saat ini dalam mikrodetik, atau -1 jika terjadi kesalahan.
*/
int64_t get_currentTimeUSec(void)
{
struct timeval tv;
if (gettimeofday(&tv, NULL) != 0)
{
perror("Gagal mendapatkan waktu saat ini!");
if (DEBUG)
perror("Failed to get current time!");
return -1;
}
@@ -190,66 +177,37 @@ int64_t get_currentTimeUSec(void)
}
/**
* Menghitung daya listrik CPU dalam satuan Watt.
* @brief Menjalankan perintah shell dan mengembalikan outputnya.
*
* Fungsi ini menghitung daya listrik CPU dengan cara mengukur perubahan energi listrik
* yang dikonsumsi oleh CPU dalam satu detik. Jika terjadi kegagalan dalam pengukuran,
* fungsi ini mengembalikan nilai -1.0f.
* Fungsi ini membuka sebuah proses dengan perintah yang diberikan, membaca baris pertama
* dari output, dan mengembalikannya sebagai string yang dialokasikan secara dinamis.
* Pemanggil bertanggung jawab untuk membebaskan memori.
*
* Catatan:
* - Fungsi ini menggunakan fungsi gettimeofday dan get_cpuConsumptionUJoules untuk
* mengukur waktu dan energi listrik yang dikonsumsi oleh CPU.
* - Fungsi ini mengembalikan nilai daya listrik CPU dalam satuan Watt, dihitung
* berdasarkan perubahan energi listrik yang dikonsumsi oleh CPU dalam satu detik.
*/
float calculate_cpu_power(void)
{
int64_t initial_usage = get_cpuConsumptionUJoules();
int64_t initial_time = get_currentTimeUSec();
if (initial_usage == -1 || initial_time == -1)
return -1.0f;
sleep(1);
int64_t final_usage = get_cpuConsumptionUJoules();
int64_t final_time = get_currentTimeUSec();
if (final_usage == -1 || final_time == -1 || final_time <= initial_time || final_usage <= initial_usage)
return -1.0f;
return (float)((final_usage - initial_usage) / ((final_time - initial_time) / USEC * USEC));
}
/**
* Menjalankan perintah shell dan mengembalikan output perintah dalam bentuk string.
*
* Parameter:
* - command: perintah shell yang ingin dijalankan.
*
* Nilai Kembali:
* - Jika perintah berhasil dijalankan dan mengembalikan output, fungsi mengembalikan string yang berisi output perintah.
* - Jika perintah gagal dijalankan atau tidak mengembalikan output, fungsi mengembalikan nilai NULL.
*
* Catatan:
* - Fungsi ini menggunakan popen dan fgets untuk menjalankan perintah shell dan
* membaca outputnya.
* - Fungsi ini mengembalikan output perintah dalam bentuk string, yang diakhiri
* dengan karakter null (\0).
* @param command Perintah yang akan dijalankan.
* @return char* Output dari perintah, atau NULL jika terjadi kesalahan.
*/
char *execute_command(const char *command)
{
FILE *fp = popen(command, "r");
if (!fp)
return NULL;
{
if (DEBUG)
perror("popen");
char *output = malloc(KILO);
return NULL;
}
char *output = malloc(4096);
if (!output)
return NULL;
{
pclose(fp);
if (!fgets(output, KILO, fp))
return NULL;
}
if (fgets(output, 4096, fp) == NULL)
{
free(output);
@@ -257,7 +215,7 @@ char *execute_command(const char *command)
}
else
{
strtok(output, "\n");
output[strcspn(output, "\n")] = 0;
}
pclose(fp);
@@ -266,103 +224,295 @@ char *execute_command(const char *command)
}
/**
* Mencetak informasi CPU seperti penggunaan CPU, penggunaan memori, suhu CPU,
* dan konsumsi daya CPU.
* @brief Menemukan semua direktori hwmon dengan nama yang cocok.
*
* Informasi CPU dicetak dalam format:
* <cpu_usage>% | <memory_usage> GB | <cpu_temperature1> °C | <cpu_temperature2> °C | <cpu_power> W
* Fungsi ini memindai melalui /sys/class/hwmon untuk menemukan perangkat keras
* monitor yang cocok dengan nama yang diberikan dan menyimpan path mereka.
*
* Nilai balik:
* void
*
* Catatan:
* - Fungsi ini menggunakan perintah "sensors" dan "awk" untuk membaca suhu CPU.
* - Fungsi ini menggunakan fungsi "get_cpu_usage", "calculate_cpu_power", dan
* "get_memory_usage" untuk menghitung penggunaan CPU, konsumsi daya CPU, dan
* penggunaan memori.
* @param name Nama hwmon yang dicari (misalnya, "k10temp").
* @param out_paths Array untuk menyimpan path yang ditemukan.
* @param max_paths Jumlah maksimum path yang akan disimpan.
* @return int Jumlah path yang ditemukan.
*/
void print_cpu_info(void)
static int find_all_hwmon_by_name(const char *name, char (*out_paths)[PATH_MAX], int max_paths)
{
char *cpu_temperature1 = execute_command("sensors k10temp-pci-* | awk -F '[:°C]' '/Tctl:/ {print $2}'");
// char *cpu_temperature2 = execute_command("sensors k10temp-pci-* | awk -F '[:°C]' '/Tccd1:/ {print $2}'");
char *dram_temperature1 = execute_command("sensors spd5118-i2c-2-* | awk '/temp1:/ {print $2}' | tr -d '+°C' | awk 'NR==1'");
char *dram_temperature2 = execute_command("sensors spd5118-i2c-2-* | awk '/temp1:/ {print $2}' | tr -d '+°C' | awk 'NR==2'");
float cpu_usage = get_cpu_usage();
float cpu_power = calculate_cpu_power();
float used_memory_gb = get_memory_usage();
glob_t hwmon_paths;
int count = 0;
if (cpu_temperature1 && dram_temperature1 && dram_temperature2 && used_memory_gb >= 0 && cpu_usage >= 0)
printf("󰻠 %.0f %% |  %.1f GB |  %.0f °C |  %.0f °C |  %.0f °C | 󰚥 %.0f W\n",
cpu_usage, used_memory_gb, atof(cpu_temperature1), atof(dram_temperature1), atof(dram_temperature2), cpu_power);
if (glob("/sys/class/hwmon/hwmon*", 0, NULL, &hwmon_paths) != 0)
return 0;
free(cpu_temperature1);
free(dram_temperature1);
free(dram_temperature2);
for (size_t i = 0; i < hwmon_paths.gl_pathc && count < max_paths; i++)
{
char name_path[PATH_MAX];
snprintf(name_path, sizeof(name_path), "%s/name", hwmon_paths.gl_pathv[i]);
FILE *f = fopen(name_path, "r");
if (!f)
continue;
char buffer[BUFFER_SIZE];
if (fgets(buffer, sizeof(buffer), f))
{
buffer[strcspn(buffer, "\n")] = 0;
if (strcmp(buffer, name) == 0)
{
strncpy(out_paths[count], hwmon_paths.gl_pathv[i], PATH_MAX - 1);
out_paths[count][PATH_MAX - 1] = '\0';
count++;
}
}
fclose(f);
}
globfree(&hwmon_paths);
return count;
}
/**
* Mencetak informasi GPU seperti penggunaan GPU, suhu GPU, dan konsumsi daya GPU.
* @brief Membaca suhu dari file input suhu hwmon.
*
* Informasi GPU dicetak dalam format:
* <gpu_usage>% | <gpu_temperature1> °C | <gpu_temperature2> °C | <gpu_temperature3> °C | <gpu_power> W
* @param hwmon_path Path ke direktori hwmon.
* @param temp_file Nama file input suhu (misalnya, "temp1_input").
* @return int Suhu dalam mili-derajat Celsius, atau -1 jika terjadi kesalahan.
*/
static int read_hwmon_temp(const char *hwmon_path, const char *temp_file)
{
char full_path[PATH_MAX];
int ret = snprintf(full_path, sizeof(full_path), "%s/%s", hwmon_path, temp_file);
if (ret < 0 || (size_t)ret >= sizeof(full_path))
return -1;
FILE *f = fopen(full_path, "r");
if (!f)
return -1;
int temp;
if (fscanf(f, "%d", &temp) != 1)
temp = -1;
fclose(f);
return temp;
}
/**
* @brief Mengambil waktu CPU total dan idle dari /proc/stat.
*
* Nilai balik:
* void
* @param times Pointer ke struktur CpuTimes untuk menyimpan hasilnya.
* @return bool true jika berhasil, false jika gagal.
*/
static bool get_cpu_times(CpuTimes *times)
{
FILE *f = fopen("/proc/stat", "r");
if (!f)
{
if (DEBUG)
perror("fopen /proc/stat");
return false;
}
char buffer[256];
if (!fgets(buffer, sizeof(buffer), f))
{
fclose(f);
return false;
}
fclose(f);
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal = 0;
int ret = sscanf(buffer, "cpu %llu %llu %llu %llu %llu %llu %llu %llu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal);
if (ret < 4)
return false;
times->idle = idle + iowait;
times->total = user + nice + system + times->idle + irq + softirq + steal;
return true;
}
/**
* @brief Mencetak informasi penggunaan CPU, suhu, dan penggunaan memori.
*
* Catatan:
* - Fungsi ini menggunakan perintah "rocm-smi" untuk membaca informasi GPU.
* Fungsi ini mengukur penggunaan daya CPU, penggunaan CPU, dan suhu
* selama interval satu detik dan mencetaknya ke output standar.
*/
void print_cpu_info(void)
{
int64_t initial_energy_uj = get_cpuConsumptionUJoules();
int64_t initial_time_us = get_currentTimeUSec();
CpuTimes initial_cpu_times;
bool initial_times_ok = get_cpu_times(&initial_cpu_times);
if (initial_energy_uj == -1 || initial_time_us == -1 || !initial_times_ok)
{
if (DEBUG)
fprintf(stderr, "Failed to get initial CPU stats.\n");
return;
}
sleep(1);
int64_t final_energy_uj = get_cpuConsumptionUJoules();
int64_t final_time_us = get_currentTimeUSec();
CpuTimes final_cpu_times;
bool final_times_ok = get_cpu_times(&final_cpu_times);
if (final_energy_uj == -1 || final_time_us == -1 || !final_times_ok)
{
if (DEBUG)
fprintf(stderr, "Failed to get final CPU stats.\n");
return;
}
float cpu_power = 0.0f;
int64_t energy_delta_uj = final_energy_uj - initial_energy_uj;
int64_t time_delta_us = final_time_us - initial_time_us;
if (energy_delta_uj >= 0 && time_delta_us > 0)
cpu_power = (float)energy_delta_uj / (float)time_delta_us;
float cpu_usage = 0.0f;
unsigned long long total_diff = final_cpu_times.total - initial_cpu_times.total;
unsigned long long idle_diff = final_cpu_times.idle - initial_cpu_times.idle;
if (total_diff > 0)
cpu_usage = 100.0f * (float)(total_diff - idle_diff) / (float)total_diff;
float used_memory_gb = get_memory_usage();
int cpu_temperature1 = -1;
int dram_temperature1 = -1;
int dram_temperature2 = -1;
char cpu_hwmon_paths[1][PATH_MAX];
char dram_paths[2][PATH_MAX];
if (find_all_hwmon_by_name("k10temp", cpu_hwmon_paths, 1) > 0)
cpu_temperature1 = read_hwmon_temp(cpu_hwmon_paths[0], "temp1_input");
else if (find_all_hwmon_by_name("coretemp", cpu_hwmon_paths, 1) > 0)
cpu_temperature1 = read_hwmon_temp(cpu_hwmon_paths[0], "temp1_input");
int num_dram_sensors = find_all_hwmon_by_name("spd5118", dram_paths, 2);
if (num_dram_sensors > 0)
dram_temperature1 = read_hwmon_temp(dram_paths[0], "temp1_input");
if (num_dram_sensors > 1)
dram_temperature2 = read_hwmon_temp(dram_paths[1], "temp1_input");
if (used_memory_gb >= 0)
printf("󰻠 %.0f %% |  %.1f GB |  %d °C |  %d °C |  %d °C | 󰚥 %.0f W\n",
cpu_usage, used_memory_gb,
cpu_temperature1 != -1 ? cpu_temperature1 / 1000 : 0,
dram_temperature1 != -1 ? dram_temperature1 / 1000 : 0,
dram_temperature2 != -1 ? dram_temperature2 / 1000 : 0,
cpu_power);
}
/**
* @brief Mencetak informasi penggunaan GPU AMD, suhu, dan penggunaan daya.
*
* Fungsi ini menggunakan rocm-smi untuk mengambil dan mencetak berbagai metrik
* untuk GPU AMD.
*/
void print_amd_gpu_info(void)
{
char *gpu_usage = execute_command("rocm-smi -d 0 --showuse | awk '/GPU use \\(%\\)/ {print $NF}'");
char *gpu_vram_usage = execute_command("rocm-smi -d 0 --showmemuse | awk '/GPU Memory Allocated \\(VRAM%\\)/ {print $NF}'");
char *gpu_temperature1 = execute_command("rocm-smi -t | awk '/Temperature \\(Sensor edge\\) \\(C\\):/ {print $NF}'");
char *gpu_temperature2 = execute_command("rocm-smi -t | awk '/Temperature \\(Sensor junction\\) \\(C\\):/ {print $NF}'");
char *gpu_temperature3 = execute_command("rocm-smi -t | awk '/Temperature \\(Sensor memory\\) \\(C\\):/ {print $NF}'");
char *gpu_power = execute_command("rocm-smi -P | awk '/Average Graphics Package Power \\(W\\):/ {print $NF}'");
if (gpu_temperature1 && gpu_temperature2 && gpu_temperature3 && gpu_usage && gpu_power)
printf("󰻠 %.0f %% |  %.0f °C |  %.0f °C |  %.0f °C | 󰚥 %.0f W\n",
atof(gpu_usage), atof(gpu_temperature1), atof(gpu_temperature2), atof(gpu_temperature3), atof(gpu_power));
if (gpu_temperature1 && gpu_usage && gpu_vram_usage)
{
printf("󰻠 %.0f %% | 󰻠 %.0f %% |  %.0f °C |  %.0f °C |  %.0f °C | 󰚥 %.0f W\n",
atof(gpu_usage),
atof(gpu_vram_usage),
atof(gpu_temperature1),
gpu_temperature2 ? atof(gpu_temperature2) : 0.0f,
gpu_temperature3 ? atof(gpu_temperature3) : 0.0f,
gpu_power ? atof(gpu_power) : 0.0f);
}
free(gpu_usage);
free(gpu_temperature1);
free(gpu_temperature2);
free(gpu_temperature3);
free(gpu_power);
if (gpu_usage)
free(gpu_usage);
if (gpu_vram_usage)
free(gpu_vram_usage);
if (gpu_temperature1)
free(gpu_temperature1);
if (gpu_temperature2)
free(gpu_temperature2);
if (gpu_temperature3)
free(gpu_temperature3);
if (gpu_power)
free(gpu_power);
}
#ifdef NVIDIA_GPU
/**
* Mencetak informasi GPU seperti penggunaan GPU, suhu GPU, dan konsumsi daya GPU.
* @brief Mencetak informasi penggunaan GPU NVIDIA, suhu, dan penggunaan daya.
*
* Informasi GPU dicetak dalam format:
* <gpu_usage>% | <gpu_temperature1> °C | <gpu_temperature2> °C | <gpu_temperature3> °C | <gpu_power> W
*
* Nilai balik:
* void
*
* Catatan:
* - Fungsi ini menggunakan perintah "nvidia-smi" untuk membaca informasi GPU.
* - Fungsi ini hanya mendukung NVIDIA GPU.
* Fungsi ini menggunakan NVML dan akses PCI langsung untuk mengambil dan mencetak
* berbagai metrik untuk GPU NVIDIA, termasuk suhu VRAM dan hotspot.
*/
void print_nvidia_gpu_info(void)
{
if (nvmlInit() != NVML_SUCCESS)
nvmlReturn_t result;
struct pci_access *pacc = NULL;
unsigned int device_count = 0;
result = nvmlInit();
if (result != NVML_SUCCESS)
{
fprintf(stderr, "Failed to initialize NVML\n");
if (DEBUG)
fprintf(stderr, "Failed to initialize NVML: %s\n", nvmlErrorString(result));
return;
}
unsigned int device_count;
result = nvmlDeviceGetCount(&device_count);
if (nvmlDeviceGetCount(&device_count) != NVML_SUCCESS || device_count == 0)
if (result != NVML_SUCCESS || device_count == 0)
{
nvmlShutdown();
if (DEBUG && result != NVML_SUCCESS)
fprintf(stderr, "Failed to get device count: %s\n", nvmlErrorString(result));
return;
goto cleanup_nvml;
}
struct pci_access *pacc = pci_alloc();
pacc = pci_alloc();
if (!pacc)
{
if (DEBUG)
fprintf(stderr, "Failed to allocate pci_access\n");
goto cleanup_nvml;
}
pci_init(pacc);
pci_scan_bus(pacc);
@@ -379,30 +529,34 @@ void print_nvidia_gpu_info(void)
if (nvmlDeviceGetPciInfo(device, &pciInfo) != NVML_SUCCESS)
continue;
// NVIDIA: Penggunaan GPU
unsigned int gpu_util = 0, gpu_temp = 0, power_usage = 0, vram_temp = 0, hotspot_temp = 0;
float fb_used = 0.0f;
nvmlUtilization_t util;
nvidia_devices[i].gpu_util = (nvmlDeviceGetUtilizationRates(device, &util) == NVML_SUCCESS) ? util.gpu : 0;
// NVIDIA: Penggunaan VRAM
if (nvmlDeviceGetUtilizationRates(device, &util) == NVML_SUCCESS)
gpu_util = util.gpu;
nvmlMemory_t mem;
nvidia_devices[i].fb_used = (nvmlDeviceGetMemoryInfo(device, &mem) == NVML_SUCCESS) ? mem.used / (1024.0f * 1024.0f) : 0;
// NVIDIA: Suhu Core
if (nvmlDeviceGetMemoryInfo(device, &mem) == NVML_SUCCESS)
fb_used = (float)mem.used / (1024.0f * 1024.0f * 1024.0f);
unsigned int temp;
nvidia_devices[i].gpu_temp = (nvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &temp) == NVML_SUCCESS) ? temp : 0;
// NVIDIA: Power (Watt)
if (nvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &temp) == NVML_SUCCESS)
gpu_temp = temp;
unsigned int power;
nvidia_devices[i].power_usage = (nvmlDeviceGetPowerUsage(device, &power) == NVML_SUCCESS) ? power / 1000 : 0;
// NVML
if (nvmlDeviceGetPowerUsage(device, &power) == NVML_SUCCESS)
power_usage = power / 1000;
for (struct pci_dev *dev = pacc->devices; dev; dev = dev->next)
{
pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_BASES);
unsigned int dev_id = (unsigned int)((dev->device_id << 16) | dev->vendor_id);
if (dev_id != pciInfo.pciDeviceId ||
if ((unsigned int)((dev->device_id << 16) | dev->vendor_id) != pciInfo.pciDeviceId ||
(unsigned int)dev->domain != pciInfo.domain ||
dev->bus != pciInfo.bus ||
dev->dev != pciInfo.device)
@@ -410,116 +564,118 @@ void print_nvidia_gpu_info(void)
continue;
}
nvidia_fd = open(MEM_PATH, O_RDWR | O_SYNC);
int nvidia_fd = open(MEM_PATH, O_RDWR | O_SYNC);
if (nvidia_fd < 0)
break;
// NVIDIA: Suhu VRAM
uint32_t vram_addr = (dev->base_addr[0] & 0xFFFFFFFF) + VRAM_REGISTER_OFFSET;
nvidia_map_base = mmap(NULL, PG_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, nvidia_fd, vram_addr & ~(PG_SZ - 1));
void *nvidia_map_base = mmap(NULL, PG_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, nvidia_fd, vram_addr & ~(PG_SZ - 1));
if (nvidia_map_base != MAP_FAILED)
{
uint32_t *vram_reg = (uint32_t *)((char *)nvidia_map_base + (vram_addr & (PG_SZ - 1)));
nvidia_devices[i].vram_temp = (*vram_reg & 0x00000fff) / 0x20;
vram_temp = (*vram_reg & NVIDIA_VRAM_TEMP_MASK) / NVIDIA_VRAM_TEMP_DIVISOR;
munmap(nvidia_map_base, PG_SZ);
nvidia_map_base = MAP_FAILED;
}
// NVIDIA: Suhu Hotspot
uint32_t hotspot_addr = (dev->base_addr[0] & 0xFFFFFFFF) + HOTSPOT_REGISTER_OFFSET;
void *hotspot_base = mmap(NULL, PG_SZ, PROT_READ, MAP_SHARED, nvidia_fd, hotspot_addr & ~(PG_SZ - 1));
if (hotspot_base != MAP_FAILED)
{
uint32_t *hotspot_reg = (uint32_t *)((char *)hotspot_base + (hotspot_addr & (PG_SZ - 1)));
uint32_t hotspot_temp = (*hotspot_reg >> 8) & 0xff;
nvidia_devices[i].hotspot_temp = (hotspot_temp < 0x7f) ? hotspot_temp : 0;
uint32_t temp_hotspot = (*hotspot_reg >> NVIDIA_HOTSPOT_TEMP_SHIFT) & NVIDIA_HOTSPOT_TEMP_MASK;
hotspot_temp = (temp_hotspot < NVIDIA_HOTSPOT_VALID_MAX) ? temp_hotspot : 0;
munmap(hotspot_base, PG_SZ);
}
close(nvidia_fd);
nvidia_fd = -1;
break;
}
printf("󰻠 %u %% |  %.1f GB |  %u °C |  %u °C |  %u °C | 󰚥 %u W\n",
nvidia_devices[i].gpu_util,
nvidia_devices[i].fb_used / 1000,
nvidia_devices[i].gpu_temp,
nvidia_devices[i].hotspot_temp,
nvidia_devices[i].vram_temp,
nvidia_devices[i].power_usage);
gpu_util, fb_used, gpu_temp, hotspot_temp, vram_temp, power_usage);
break;
}
pci_cleanup(pacc);
cleanup_nvml:
nvmlShutdown();
}
#endif
/**
* Mendeteksi tipe GPU yang terpasang.
* @brief Mendeteksi jenis GPU yang ada di sistem (AMD, NVIDIA, atau tidak ada).
*
* Fungsi ini akan mencoba mendeteksi tipe GPU yang terpasang dengan
* menjalankan perintah "rocm-smi" untuk AMD dan "nvidia-smi" untuk NVIDIA.
* Fungsi ini mencoba mendeteksi GPU AMD menggunakan rocm-smi dan GPU NVIDIA
* menggunakan nvidia-smi. Hasilnya di-cache untuk panggilan berikutnya.
*
* Nilai balik:
* 0 jika tidak ada GPU yang kompatibel,
* 1 jika GPU AMD,
* 2 jika GPU NVIDIA.
* @return int Enum GpuType yang menunjukkan jenis GPU yang terdeteksi.
*/
int detect_gpu_type(void)
{
if (gpu_type)
return gpu_type;
static enum GpuType cached_gpu_type = GPU_TYPE_UNINITIALIZED;
if (cached_gpu_type != GPU_TYPE_UNINITIALIZED)
return cached_gpu_type;
char *amd_check = execute_command("rocm-smi --showid 2>/dev/null | grep -i 'GPU' >/dev/null && echo 'AMD'");
if (amd_check && strstr(amd_check, "AMD"))
if (amd_check)
{
free(amd_check);
if (strstr(amd_check, "AMD"))
{
free(amd_check);
cached_gpu_type = GPU_TYPE_AMD;
return 1; // AMD
return GPU_TYPE_AMD;
}
free(amd_check);
}
free(amd_check);
#ifdef NVIDIA_GPU
char *nvidia_check = execute_command("nvidia-smi >/dev/null 2>&1 && echo 'NVIDIA'");
if (nvidia_check && strstr(nvidia_check, "NVIDIA"))
if (nvidia_check)
{
if (strstr(nvidia_check, "NVIDIA"))
{
free(nvidia_check);
cached_gpu_type = GPU_TYPE_NVIDIA;
return GPU_TYPE_NVIDIA;
}
free(nvidia_check);
return 2; // NVIDIA
}
#endif
free(nvidia_check);
cached_gpu_type = GPU_TYPE_NONE;
return 0;
return GPU_TYPE_NONE;
}
/**
* @brief Program utama powerusage.
* @brief Titik masuk utama program.
*
* Program ini akan menghitung penggunaan daya CPU dan GPU.
* Menganalisis argumen baris perintah untuk menentukan apakah akan menampilkan
* informasi CPU atau GPU.
*
* @param argc Jumlah argument yang diberikan.
* @param argv Array argument yang diberikan.
*
* @return 0 jika berhasil, 1 jika tidak.
* @param argc Jumlah argumen baris perintah.
* @param argv Array argumen baris perintah.
* @return int 0 jika berhasil, 1 jika terjadi kesalahan.
*/
int main(int argc, char *argv[])
{
if (argc < 2)
if (argc < 2 || (strcmp(argv[1], "cpu") && strcmp(argv[1], "gpu")))
{
fprintf(stderr, "Sintaks: powerusage CONFIG CPU_GPU, Contoh: powerusage cpu\n");
fprintf(stderr, "Syntax: %s [cpu|gpu], Example: powerusage cpu\n", argv[0]);
return 1;
}
@@ -529,30 +685,25 @@ int main(int argc, char *argv[])
}
else if (!strcmp(argv[1], "gpu"))
{
gpu_type = detect_gpu_type();
int gpu_type = detect_gpu_type();
switch (gpu_type)
{
case 1: // AMD
case GPU_TYPE_AMD:
print_amd_gpu_info();
break;
case 2: // NVIDIA
#ifdef NVIDIA_GPU
case GPU_TYPE_NVIDIA:
print_nvidia_gpu_info();
break;
#endif
default:
fprintf(stderr, "Tidak ada GPU yang kompatibel!\n");
fprintf(stderr, "No compatible GPU found!\n");
return 1;
}
}
else
{
fprintf(stderr, "Salah mode: %s. Gunakan 'cpu' atau 'gpu'.\n", argv[2]);
return 1;
}
return 0;
}

122
ryzen.c
View File

@@ -1,4 +1,26 @@
/*
* ryzen - Utilitas untuk memantau konsumsi daya CPU.
*
* Hak Cipta (C) 2024 MOVZX
*
* Program ini adalah perangkat lunak bebas; Anda dapat menyebarluaskannya kembali
* dan/atau memodifikasinya di bawah ketentuan Lisensi Publik Umum GNU
* sebagaimana dipublikasikan oleh Free Software Foundation; baik versi 2
* dari Lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
*
* Program ini didistribusikan dengan harapan akan bermanfaat,
* tetapi TANPA JAMINAN APAPUN; bahkan tanpa jaminan tersirat
* DAGANGAN atau KESESUAIAN UNTUK TUJUAN TERTENTU. Lihat
* Lisensi Publik Umum GNU untuk lebih jelasnya.
*
* Anda seharusnya telah menerima salinan Lisensi Publik Umum GNU
* bersama dengan program ini; jika tidak, tulislah ke Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <stdint.h>
@@ -6,10 +28,12 @@
#define RAPL_PATH "/sys/class/powercap/intel-rapl:0/energy_uj"
#define USEC 1000000
static int64_t last_read_time = 0;
static int64_t cached_consumption = -1;
int64_t get_monotonicTimeUSec()
/**
* @brief Mendapatkan waktu monotonik dalam mikrodetik.
*
* @return int64_t Waktu saat ini dalam mikrodetik.
*/
int64_t get_monotonic_time_usec()
{
struct timespec time;
@@ -18,62 +42,102 @@ int64_t get_monotonicTimeUSec()
return time.tv_sec * USEC + time.tv_nsec / 1000;
}
int64_t get_cpuConsumptionUJoules()
/**
* @brief Mengambil konsumsi energi CPU dalam microjoule dengan caching.
*
* Fungsi ini membaca file RAPL untuk mendapatkan total energi yang dikonsumsi oleh CPU.
* Hasilnya di-cache selama satu detik untuk mengurangi pembacaan file.
*
* @return int64_t Konsumsi energi dalam microjoule, atau -1 jika terjadi kesalahan.
*/
int64_t get_cpu_consumption_ujoules()
{
int64_t current_time = get_monotonicTimeUSec();
static int64_t last_read_time = 0;
static int64_t cached_consumption = -1;
int64_t current_time = get_monotonic_time_usec();
if (current_time - last_read_time >= USEC)
if (current_time - last_read_time >= USEC || cached_consumption == -1)
{
FILE *file = fopen(RAPL_PATH, "r");
if (file != NULL)
{
if (fscanf(file, "%lld", &cached_consumption) != 1)
if (fscanf(file, "%ld", &cached_consumption) != 1)
{
perror("Failed to read CPU consumption");
cached_consumption = -1;
}
fclose(file);
}
else
{
perror("Failed to open RAPL path");
cached_consumption = -1;
}
last_read_time = current_time;
}
return cached_consumption;
}
float get_cpuConsumptionWatts()
/**
* @brief Menghitung konsumsi daya CPU saat ini dalam Watt.
*
* Fungsi ini mengukur perubahan konsumsi energi selama interval satu detik
* untuk menghitung penggunaan daya rata-rata.
*
* @return float Penggunaan daya dalam Watt, atau -1.0f jika terjadi kesalahan.
*/
float get_cpu_consumption_watts()
{
static int64_t previous_usage = -1;
static int64_t previous_timestamp = 0;
int64_t initial_usage = get_cpu_consumption_ujoules();
int64_t initial_timestamp = get_monotonic_time_usec();
int64_t current_timestamp = get_monotonicTimeUSec();
int64_t current_usage = get_cpuConsumptionUJoules();
if (initial_usage < 0)
return -1.0f;
if (current_usage < 0 || previous_timestamp == current_timestamp)
sleep(1);
int64_t final_usage = get_cpu_consumption_ujoules();
int64_t final_timestamp = get_monotonic_time_usec();
if (final_usage < 0)
return -1.0f;
int64_t energy_diff_uj = final_usage - initial_usage;
int64_t time_diff_us = final_timestamp - initial_timestamp;
if (time_diff_us <= 0)
return 0;
if (previous_usage < 0)
{
previous_usage = current_usage;
previous_timestamp = current_timestamp;
usleep(USEC);
current_timestamp = get_monotonicTimeUSec();
current_usage = get_cpuConsumptionUJoules();
}
float watts = (float)(current_usage - previous_usage) / (float)(current_timestamp - previous_timestamp);
previous_timestamp = current_timestamp;
previous_usage = current_usage;
float watts = (float)energy_diff_uj / (float)time_diff_us;
return watts;
}
/**
* @brief Titik masuk utama program.
*
* Mengambil dan mencetak konsumsi daya CPU saat ini dalam Watt.
*
* @return int 0 jika berhasil, 1 jika terjadi kesalahan.
*/
int main()
{
printf("%.2f\n", get_cpuConsumptionWatts());
float power = get_cpu_consumption_watts();
if (power < 0)
{
fprintf(stderr, "Failed to get CPU power consumption.\n");
return 1;
}
printf("%.2f\n", power);
return 0;
}

682
sens.c
View File

@@ -1,53 +1,156 @@
/*
* sens - Utilitas untuk memantau sensor perangkat keras.
*
* Hak Cipta (C) 2024 MOVZX
*
* Program ini adalah perangkat lunak bebas; Anda dapat menyebarluaskannya kembali
* dan/atau memodifikasinya di bawah ketentuan Lisensi Publik Umum GNU
* sebagaimana dipublikasikan oleh Free Software Foundation; baik versi 2
* dari Lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.
*
* Program ini didistribusikan dengan harapan akan bermanfaat,
* tetapi TANPA JAMINAN APAPUN; bahkan tanpa jaminan tersirat
* DAGANGAN atau KESESUAIAN UNTUK TUJUAN TERTENTU. Lihat
* Lisensi Publik Umum GNU untuk lebih jelasnya.
*
* Anda seharusnya telah menerima salinan Lisensi Publik Umum GNU
* bersama dengan program ini; jika tidak, tulislah ke Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdint.h>
#include <fcntl.h>
#include <glob.h>
#include <limits.h>
#include <unistd.h>
#include <dirent.h>
#define RAPL_FILE_PATH "/sys/class/powercap/intel-rapl:0/energy_uj"
#define BOARD_NAME_PATH "/sys/devices/virtual/dmi/id/board_name"
#define BUFFER_SIZE 256
#define BUFFER_SIZE 32
#define USEC 1000000
#define KILO 1000
#define BOLD "\033[1m"
#define RESET "\033[0m"
int64_t get_cpuConsumptionUJoules()
enum GpuType
{
int64_t consumption = -1;
FILE *file = fopen(RAPL_FILE_PATH, "r");
GPU_TYPE_UNINITIALIZED = -1,
GPU_TYPE_NONE = 0,
GPU_TYPE_AMD,
GPU_TYPE_NVIDIA,
};
if (file && fscanf(file, "%lld", &consumption) == 1)
fclose(file);
/**
* @brief Menjalankan perintah shell dan mengembalikan outputnya.
*
* @param command Perintah shell yang akan dieksekusi.
* @return char* Baris pertama dari output perintah, atau NULL jika gagal.
*
* @note Memori yang dikembalikan harus dibebaskan oleh pemanggil.
* Fungsi ini merupakan bottleneck kinerja karena forking proses.
*/
char *execute_command(const char *command)
{
FILE *fp = popen(command, "r");
if (!fp)
{
perror("popen");
return NULL;
}
char *output = malloc(4096);
if (!output)
{
pclose(fp);
return NULL;
}
if (fgets(output, 4096, fp) == NULL)
{
free(output);
output = NULL;
}
else
{
perror("Error reading RAPL energy file");
if (file) fclose(file);
output[strcspn(output, "\n")] = 0;
}
pclose(fp);
return output;
}
/**
* @brief Mendapatkan konsumsi energi CPU saat ini dalam mikrojoule.
*
* @return int64_t Konsumsi energi dalam mikrojoule, atau -1 jika gagal.
*
* @note Membutuhkan antarmuka RAPL di RAPL_FILE_PATH.
*/
int64_t get_cpuConsumptionUJoules()
{
int64_t consumption;
FILE *file = fopen(RAPL_FILE_PATH, "r");
if (!file || fscanf(file, "%ld", &consumption) != 1)
{
perror("Failed to read CPU energy consumption");
if (file)
fclose(file);
return -1;
}
fclose(file);
return consumption;
}
/**
* @brief Mengembalikan waktu saat ini dalam mikrodetik.
*
* @return int64_t Waktu saat ini dalam mikrodetik, atau -1 jika gagal.
*/
int64_t get_currentTimeUSec()
{
struct timeval tv;
if (gettimeofday(&tv, NULL) == 0)
return ((int64_t)tv.tv_sec * USEC) + tv.tv_usec;
if (gettimeofday(&tv, NULL) != 0)
{
perror("Failed to get current time");
perror("Error getting current time");
return -1;
}
return -1;
return ((int64_t)tv.tv_sec * USEC) + tv.tv_usec;
}
/**
* @brief Menghitung daya CPU rata-rata dalam Watt selama interval 1 detik.
*
* @return float Daya CPU dalam Watt, atau 0.0f jika gagal.
*/
float calculate_cpu_power()
{
int64_t initial_usage = get_cpuConsumptionUJoules();
int64_t initial_time = get_currentTimeUSec();
int64_t initial_energy_uj = get_cpuConsumptionUJoules();
int64_t initial_time_us = get_currentTimeUSec();
if (initial_usage == -1 || initial_time == -1)
if (initial_energy_uj == -1 || initial_time_us == -1)
{
fprintf(stderr, "Failed to read initial CPU consumption or time data\n");
@@ -56,163 +159,160 @@ float calculate_cpu_power()
usleep(USEC);
int64_t final_usage = get_cpuConsumptionUJoules();
int64_t final_time = get_currentTimeUSec();
int64_t final_energy_uj = get_cpuConsumptionUJoules();
int64_t final_time_us = get_currentTimeUSec();
if (final_usage == -1 || final_time == -1 || final_time < initial_time)
if (final_energy_uj == -1 || final_time_us == -1)
{
fprintf(stderr, "Failed to read final CPU consumption or time data, or time went backwards\n");
fprintf(stderr, "Failed to read final CPU consumption or time data\n");
return 0.0f;
}
int64_t time_diff_usec = final_time - initial_time;
int64_t energy_diff_uj = final_usage - initial_usage;
int64_t energy_delta_uj = final_energy_uj - initial_energy_uj;
int64_t time_delta_us = final_time_us - initial_time_us;
if (time_diff_usec <= 0 || energy_diff_uj < 0)
if (time_delta_us <= 0 || energy_delta_uj < 0)
{
fprintf(stderr, "Invalid time or energy difference\n");
fprintf(stderr, "Invalid time or energy difference (time: %ld us, energy: %ld uJ)\n", time_delta_us, energy_delta_uj);
return 0.0f;
}
return (float)(energy_diff_uj) / (time_diff_usec / (float)USEC);
return (float)energy_delta_uj / (float)time_delta_us;
}
/**
* @brief Membaca nilai integer dari file yang ditentukan.
*
* @param path Path ke file.
* @return int Nilai integer yang dibaca dari file, atau -1 jika gagal.
*/
int read_int_from_file(const char *path)
{
FILE *file = fopen(path, "r");
int value = -1;
FILE *file = fopen(path, "r");
if (file && fscanf(file, "%d", &value) == 1)
fclose(file);
else
{
perror("Error reading file");
if (!file)
return -1;
if (file) fclose(file);
}
if (fscanf(file, "%d", &value) != 1)
value = -1;
fclose(file);
return value;
}
int get_nvme_device_model(const char *nvme_device, char *device_model, size_t size)
/**
* @brief Menemukan path hwmon berdasarkan nama.
*
* @param name Nama hwmon yang dicari.
* @param out_path Buffer untuk menyimpan path yang ditemukan.
* @param out_path_size Ukuran buffer out_path.
* @return int 0 jika berhasil, -1 jika gagal.
*/
static int find_hwmon_path_by_name(const char *name, char *out_path, size_t out_path_size)
{
char command[BUFFER_SIZE];
glob_t hwmon_paths;
int found = -1;
snprintf(command, sizeof(command), "udevadm info --query=property --name=%sn1 | grep 'ID_MODEL='", nvme_device);
FILE *fp = popen(command, "r");
if (glob("/sys/class/hwmon/hwmon*", 0, NULL, &hwmon_paths) != 0)
return -1;
if (fp)
for (size_t i = 0; i < hwmon_paths.gl_pathc; i++)
{
char line[BUFFER_SIZE];
char name_path[PATH_MAX];
while (fgets(line, sizeof(line), fp))
snprintf(name_path, sizeof(name_path), "%s/name", hwmon_paths.gl_pathv[i]);
FILE *f = fopen(name_path, "r");
if (!f)
continue;
char buffer[BUFFER_SIZE];
if (fgets(buffer, sizeof(buffer), f))
{
if (strstr(line, "ID_MODEL=") != NULL)
buffer[strcspn(buffer, "\n")] = 0;
if (strcmp(buffer, name) == 0)
{
char *start = strstr(line, "=");
strncpy(out_path, hwmon_paths.gl_pathv[i], out_path_size - 1);
if (start)
{
start++;
strncpy(device_model, start, size);
out_path[out_path_size - 1] = '\0';
found = 0;
device_model[strcspn(device_model, "\n")] = 0;
fclose(f);
pclose(fp);
return 0;
}
break;
}
}
pclose(fp);
fclose(f);
}
return -1;
globfree(&hwmon_paths);
return found;
}
int find_hwmon_path(const char *sensor_name, char *path, size_t size)
/**
* @brief Mendeteksi jenis GPU yang ada di sistem (AMD, NVIDIA, atau tidak ada).
*
* @return enum GpuType Jenis GPU yang terdeteksi.
*/
static enum GpuType detect_gpu_type(void)
{
char cmd[BUFFER_SIZE];
static enum GpuType cached_gpu_type = GPU_TYPE_UNINITIALIZED;
snprintf(cmd, sizeof(cmd), "grep -l '%s' /sys/class/hwmon/hwmon*/name", sensor_name);
FILE *fp = popen(cmd, "r");
if (cached_gpu_type != GPU_TYPE_UNINITIALIZED)
return cached_gpu_type;
if (fp && fgets(path, size, fp))
char hwmon_path[PATH_MAX];
if (find_hwmon_path_by_name("amdgpu", hwmon_path, sizeof(hwmon_path)) == 0)
{
cached_gpu_type = GPU_TYPE_AMD;
return cached_gpu_type;
}
#ifdef NVIDIA_GPU
FILE *fp = popen("nvidia-smi -L >/dev/null 2>&1 && echo 'NVIDIA'", "r");
if (fp)
{
char buffer[32];
if (fgets(buffer, sizeof(buffer), fp) != NULL && strstr(buffer, "NVIDIA"))
cached_gpu_type = GPU_TYPE_NVIDIA;
pclose(fp);
path[strcspn(path, "\n")] = 0;
size_t len = strlen(path);
if (len >= 5)
path[len - 5] = '\0';
else
path[0] = '\0';
return 0;
if (cached_gpu_type == GPU_TYPE_NVIDIA)
return cached_gpu_type;
}
if (fp) pclose(fp);
return -1;
#endif
cached_gpu_type = GPU_TYPE_NONE;
return cached_gpu_type;
}
int find_nvme_hwmon_path(const char *nvme_device, char *hwmon_path, size_t max_len)
#ifdef NVIDIA_GPU
/**
* @brief Membaca suhu dan daya GPU NVIDIA menggunakan nvidia-smi.
*
* @param temperature Pointer ke float untuk menyimpan suhu GPU.
* @param power Pointer ke float untuk menyimpan penarikan daya GPU.
*
* @note Fungsi ini tidak efisien karena memanggil `nvidia-smi` melalui `popen`.
* Untuk penggunaan produksi, pustaka NVML direkomendasikan.
*/
void read_nvidia_gpu_info(float *temperature, float *power)
{
char command[BUFFER_SIZE];
snprintf(command, sizeof(command), "udevadm info --query=path %s", nvme_device);
FILE *fp = popen(command, "r");
if (fp && fgets(hwmon_path, max_len, fp))
{
pclose(fp);
hwmon_path[strcspn(hwmon_path, "\n")] = 0;
snprintf(command, sizeof(command), "find /sys%s -type d -name 'hwmon*'", hwmon_path);
fp = popen(command, "r");
if (fp && fgets(hwmon_path, max_len, fp))
{
pclose(fp);
hwmon_path[strcspn(hwmon_path, "\n")] = 0;
return 0;
}
if (fp) pclose(fp);
}
return -1;
}
int read_board_name(char *board_name, size_t size)
{
FILE *file = fopen(BOARD_NAME_PATH, "r");
if (file)
{
if (fgets(board_name, size, file))
{
board_name[strcspn(board_name, "\n")] = 0;
fclose(file);
return 0;
}
fclose(file);
}
perror("Error reading board name file");
return -1;
}
void read_nvidia_gpu_info(float *temperature, float *power) {
char command[BUFFER_SIZE] = "nvidia-smi --query-gpu=temperature.gpu,power.draw --format=csv,noheader";
FILE *fp = popen(command, "r");
@@ -231,165 +331,295 @@ void read_nvidia_gpu_info(float *temperature, float *power) {
*power = -1;
}
}
#endif
int main()
/**
* @brief Mencetak informasi sistem (produsen dan produk).
*/
static void print_system_info(void)
{
char board_name[BUFFER_SIZE], hwmon_path[BUFFER_SIZE], nvme_device_model[BUFFER_SIZE], temp_path[BUFFER_SIZE];
int mobo_temp, vrm_temp, pch_temp;
int radiator_fan, top_fans, bottom1_fans, bottom2_fans;
int cpu_tctl, cpu_tccd;
float cpu_power = 0.0f, gpu_edge = 0.0f, gpu_junction = 0.0f, gpu_mem = 0.0f, gpu_power = 0.0f;
int nvme_temps[4] = {-1, -1, -1, -1};
int dram_temps[2] = {-1, -1};
int amdgpu_exist = 0;
float gpu_temp_nvidia = -1.0f, gpu_power_nvidia = -1.0f;
char *board_manufacturer = execute_command("dmidecode -s system-manufacturer");
char *board_product = execute_command("dmidecode -s system-product-name");
if (find_hwmon_path("nct668*", hwmon_path, sizeof(hwmon_path)) == 0)
printf(BOLD "%s %s" RESET "\n", board_manufacturer ? board_manufacturer : "System", board_product ? board_product : "");
free(board_manufacturer);
free(board_product);
}
/**
* @brief Mencetak suhu motherboard dan kecepatan kipas.
*/
static void print_motherboard_and_fan_info(void)
{
char hwmon_path[PATH_MAX], temp_path[PATH_MAX];
int mobo_temp = -1, vrm_temp = -1, pch_temp = -1;
int radiator_fan = -1, pump_fan = -1, top_fans = -1, bottom1_fans = -1, bottom2_fans = -1;
if (find_hwmon_path_by_name("nct668*", hwmon_path, sizeof(hwmon_path)) == 0)
{
snprintf(temp_path, sizeof(temp_path), "%s/temp2_input", hwmon_path);
mobo_temp = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/temp3_input", hwmon_path);
vrm_temp = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/temp4_input", hwmon_path);
pch_temp = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/fan1_input", hwmon_path);
radiator_fan = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/fan2_input", hwmon_path);
pump_fan = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/fan3_input", hwmon_path);
top_fans = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/fan4_input", hwmon_path);
bottom1_fans = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/fan5_input", hwmon_path);
bottom2_fans = read_int_from_file(temp_path);
}
else
{
fprintf(stderr, "nct668* sensor module not found!\n");
return 1;
fprintf(stderr, "NCT668x sensor module not found!\n");
}
if (find_hwmon_path("k10temp", hwmon_path, sizeof(hwmon_path)) == 0)
printf("Mobo : %.2f°C\n", mobo_temp != -1 ? mobo_temp / 1000.0 : 0.0);
printf("VRM : %.2f°C\n", vrm_temp != -1 ? vrm_temp / 1000.0 : 0.0);
printf("Chipset : %.2f°C\n", pch_temp != -1 ? pch_temp / 1000.0 : 0.0);
printf("\n");
printf(BOLD "Lian Li Lancool II" RESET "\n");
printf("Radiator : %d RPM\n", radiator_fan != -1 ? radiator_fan : 0);
printf("Pump : %d RPM\n", pump_fan != -1 ? pump_fan : 0);
printf("Top : %d RPM\n", top_fans != -1 ? top_fans : 0);
printf("Bottom 1 : %d RPM\n", bottom1_fans != -1 ? bottom1_fans : 0);
printf("Bottom 2 : %d RPM\n", bottom2_fans != -1 ? bottom2_fans : 0);
printf("\n");
}
/**
* @brief Mencetak suhu dan daya CPU.
*/
static void print_cpu_info(void)
{
char hwmon_path[PATH_MAX], temp_path[PATH_MAX];
int cpu_tctl = -1, cpu_tccd = -1;
float cpu_power = 0.0f;
if (find_hwmon_path_by_name("k10temp", hwmon_path, sizeof(hwmon_path)) == 0)
{
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", hwmon_path);
cpu_tctl = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/temp3_input", hwmon_path);
cpu_tccd = read_int_from_file(temp_path);
cpu_tccd = read_int_from_file(temp_path);
cpu_power = calculate_cpu_power();
}
else
{
fprintf(stderr, "k10temp sensor module not found!\n");
return 1;
}
if (find_hwmon_path("amdgpu", hwmon_path, sizeof(hwmon_path)) == 0)
{
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", hwmon_path);
gpu_edge = read_int_from_file(temp_path);
char *processor_name = execute_command("dmidecode -s processor-version");
snprintf(temp_path, sizeof(temp_path), "%s/temp2_input", hwmon_path);
gpu_junction = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/temp3_input", hwmon_path);
gpu_mem = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/power1_average", hwmon_path);
gpu_power = read_int_from_file(temp_path);
amdgpu_exist = 1;
}
read_nvidia_gpu_info(&gpu_temp_nvidia, &gpu_power_nvidia);
if (read_board_name(board_name, sizeof(board_name)) == 0)
{
printf(BOLD "%s" RESET "\n", board_name);
}
else
{
printf(BOLD "Unknown Motherboard" RESET "\n");
}
printf("Mobo : %.2f°C\n", mobo_temp >= 0 ? mobo_temp / 1000.0 : 0.0);
printf("VRM : %.2f°C\n", vrm_temp >= 0 ? vrm_temp / 1000.0 : 0.0);
printf("Chipset : %.2f°C\n", pch_temp >= 0 ? pch_temp / 1000.0 : 0.0);
printf(BOLD "%s" RESET "\n", processor_name ? processor_name : "CPU");
printf("Tctl : %.2f°C\n", cpu_tctl != -1 ? cpu_tctl / 1000.0 : 0.0);
printf("Tccd : %.2f°C\n", cpu_tccd != -1 ? cpu_tccd / 1000.0 : 0.0);
printf("Power : %.2f W\n", cpu_power);
printf("\n");
printf(BOLD "AMD Ryzen 7 7800X3D" RESET "\n");
printf("Tctl : %.2f°C\n", cpu_tctl >= 0 ? cpu_tctl / 1000.0 : 0.0);
printf("Tccd : %.2f°C\n", cpu_tccd >= 0 ? cpu_tccd / 1000.0 : 0.0);
printf("Power : %.2f W\n", cpu_power / USEC);
free(processor_name);
}
/**
* @brief Mencetak suhu DRAM.
*/
static void print_dram_info(void)
{
char temp_path[PATH_MAX];
char *dram_model = execute_command("dmidecode -t memory | grep \"Part Number:\" | awk '{$1=\"\"; print $0}' | sed 's/^ *//' | head -n 1");
printf(BOLD "%s" RESET "\n", dram_model ? dram_model : "DRAM");
int dram_temps[2] = {-1, -1};
glob_t spd_paths;
if (glob("/sys/class/hwmon/hwmon*/name", 0, NULL, &spd_paths) == 0)
{
int dram_idx = 0;
for (size_t i = 0; i < spd_paths.gl_pathc && dram_idx < 2; i++)
{
FILE *f = fopen(spd_paths.gl_pathv[i], "r");
if (!f)
continue;
char name_buf[32];
if (fgets(name_buf, sizeof(name_buf), f) && strncmp(name_buf, "spd5118", 7) == 0)
{
char *dir_path = dirname(spd_paths.gl_pathv[i]);
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", dir_path);
dram_temps[dram_idx++] = read_int_from_file(temp_path);
}
fclose(f);
}
}
globfree(&spd_paths);
printf("DRAM 1 : %.2f°C\n", dram_temps[0] != -1 ? dram_temps[0] / 1000.0 : 0.0);
printf("DRAM 2 : %.2f°C\n", dram_temps[1] != -1 ? dram_temps[1] / 1000.0 : 0.0);
printf("\n");
if (amdgpu_exist)
free(dram_model);
}
/**
* @brief Mendeteksi dan mencetak informasi suhu dan daya GPU.
*/
static void print_gpu_info(void)
{
enum GpuType gpu_type = detect_gpu_type();
if (gpu_type == GPU_TYPE_AMD)
{
printf(BOLD "AMD Radeon RX 6800 XT" RESET "\n");
printf("Edge : %.2f°C\n", gpu_edge >= 0 ? gpu_edge / 1000.0 : 0.0);
printf("Junction : %.2f°C\n", gpu_junction >= 0 ? gpu_junction / 1000.0 : 0.0);
printf("Mem : %.2f°C\n", gpu_mem >= 0 ? gpu_mem / 1000.0 : 0.0);
printf("Power : %.2f W\n", gpu_power / USEC);
printf("\n");
}
char hwmon_path[PATH_MAX], temp_path[PATH_MAX];
float gpu_edge = 0.0f, gpu_junction = 0.0f, gpu_mem = 0.0f, gpu_power = 0.0f;
if (gpu_temp_nvidia > 0 && gpu_power_nvidia > 0)
{
printf(BOLD "NVIDIA RTX 3090" RESET "\n");
printf("Temp : %.1f°C\n", gpu_temp_nvidia);
printf("Power : %.1f W\n", gpu_power_nvidia);
printf("\n");
}
printf(BOLD "G-SKILL Trident Z5 Neo" RESET "\n");
for (int i = 0; i < 2; i++)
{
snprintf(hwmon_path, sizeof(hwmon_path), "/sys/class/hwmon/hwmon%d", 5 + i);
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", hwmon_path);
dram_temps[i] = read_int_from_file(temp_path);
printf("DRAM %d : %.2f°C\n", i + 1, dram_temps[i] >= 0 ? dram_temps[i] / 1000.0 : 0.0);
}
printf("\n");
for (int i = 0; i < 4; i++)
{
char nvme_device[BUFFER_SIZE];
snprintf(nvme_device, sizeof(nvme_device), "/dev/nvme%d", i);
if (find_nvme_hwmon_path(nvme_device, hwmon_path, sizeof(hwmon_path)) == 0)
if (find_hwmon_path_by_name("amdgpu", hwmon_path, sizeof(hwmon_path)) == 0)
{
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", hwmon_path);
nvme_temps[i] = read_int_from_file(temp_path);
if (get_nvme_device_model(nvme_device, nvme_device_model, sizeof(nvme_device_model)) == 0)
printf(BOLD "%s" RESET "\n", nvme_device_model);
else
printf(BOLD "NVMe %d: Model name not found" RESET "\n", i + 1);
gpu_edge = read_int_from_file(temp_path);
printf("NAND : %.2f°C\n", nvme_temps[i] >= 0 ? nvme_temps[i] / 1000.0 : 0.0);
snprintf(temp_path, sizeof(temp_path), "%s/temp2_input", hwmon_path);
gpu_junction = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/temp3_input", hwmon_path);
gpu_mem = read_int_from_file(temp_path);
snprintf(temp_path, sizeof(temp_path), "%s/power1_average", hwmon_path);
gpu_power = read_int_from_file(temp_path);
printf(BOLD "AMD Radeon GPU" RESET "\n");
printf("Edge : %.2f°C\n", gpu_edge != -1 ? gpu_edge / 1000.0 : 0.0);
printf("Junction : %.2f°C\n", gpu_junction != -1 ? gpu_junction / 1000.0 : 0.0);
printf("Mem : %.2f°C\n", gpu_mem != -1 ? gpu_mem / 1000.0 : 0.0);
printf("Power : %.2f W\n", gpu_power != -1 ? gpu_power / 1000000.0 : 0.0);
printf("\n");
}
else
printf("Failed to find hwmon path for %s\n", nvme_device);
}
#ifdef NVIDIA_GPU
else if (gpu_type == GPU_TYPE_NVIDIA)
{
float gpu_temp_nvidia = -1.0f, gpu_power_nvidia = -1.0f;
read_nvidia_gpu_info(&gpu_temp_nvidia, &gpu_power_nvidia);
if (gpu_temp_nvidia > 0 && gpu_power_nvidia > 0)
{
char *nvidia_gpu_name = execute_command("nvidia-smi --query-gpu=gpu_name --format=csv,noheader");
printf(BOLD "%s" RESET "\n", nvidia_gpu_name ? nvidia_gpu_name : "NVIDIA GPU");
printf("Temp : %.1f°C\n", gpu_temp_nvidia);
printf("Power : %.1f W\n", gpu_power_nvidia);
printf("\n");
free(nvidia_gpu_name);
}
}
#endif
}
/**
* @brief Mencetak suhu SSD NVMe.
*/
static void print_nvme_info(void)
{
char temp_path[PATH_MAX];
glob_t nvme_paths;
if (glob("/sys/class/nvme/nvme*", 0, NULL, &nvme_paths) == 0)
{
for (size_t i = 0; i < nvme_paths.gl_pathc; i++)
{
char model[128] = "NVMe SSD";
char model_path[PATH_MAX];
snprintf(model_path, sizeof(model_path), "%s/model", nvme_paths.gl_pathv[i]);
FILE *f_model = fopen(model_path, "r");
if (f_model)
{
if (fgets(model, sizeof(model), f_model))
model[strcspn(model, "\n")] = 0;
fclose(f_model);
}
char nvme_hwmon_path[PATH_MAX];
snprintf(nvme_hwmon_path, sizeof(nvme_hwmon_path), "%s/hwmon*", nvme_paths.gl_pathv[i]);
glob_t nvme_hwmon_glob;
if (glob(nvme_hwmon_path, 0, NULL, &nvme_hwmon_glob) == 0)
{
if (nvme_hwmon_glob.gl_pathc > 0)
{
snprintf(temp_path, sizeof(temp_path), "%s/temp1_input", nvme_hwmon_glob.gl_pathv[0]);
int nvme_temp = read_int_from_file(temp_path);
printf(BOLD "%s" RESET "\n", model);
printf("NAND : %.2f°C\n", nvme_temp != -1 ? nvme_temp / 1000.0 : 0.0);
printf("\n");
}
globfree(&nvme_hwmon_glob);
}
}
}
printf(BOLD "Lian Li Lancool II" RESET "\n");
printf("Radiator : %d RPM\n", radiator_fan >= 0 ? radiator_fan : 0);
printf("Top : %d RPM\n", top_fans >= 0 ? top_fans : 0);
printf("Bottom 1 : %d RPM\n", bottom1_fans >= 0 ? bottom1_fans : 0);
printf("Bottom 2 : %d RPM\n", bottom2_fans >= 0 ? bottom2_fans : 0);
globfree(&nvme_paths);
}
/**
* @brief Titik masuk utama untuk program pembacaan sensor.
*
* @return int 0 jika berhasil.
*/
int main()
{
print_system_info();
print_motherboard_and_fan_info();
print_cpu_info();
print_dram_info();
print_gpu_info();
print_nvme_info();
return 0;
}