Files
Ryzen-Power/sens.c
MOVZX aa3d0e5951 Various Changes
- Code refactorisations
- Logic optimisations
- Update README.md
- etc

Signed-off-by: MOVZX <movzx@yahoo.com>
2025-07-01 17:14:05 +07:00

626 lines
16 KiB
C

/*
* 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 BUFFER_SIZE 32
#define USEC 1000000
#define KILO 1000
#define BOLD "\033[1m"
#define RESET "\033[0m"
enum GpuType
{
GPU_TYPE_UNINITIALIZED = -1,
GPU_TYPE_NONE = 0,
GPU_TYPE_AMD,
GPU_TYPE_NVIDIA,
};
/**
* @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
{
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)
{
perror("Failed to get current time");
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_energy_uj = get_cpuConsumptionUJoules();
int64_t initial_time_us = get_currentTimeUSec();
if (initial_energy_uj == -1 || initial_time_us == -1)
{
fprintf(stderr, "Failed to read initial CPU consumption or time data\n");
return 0.0f;
}
usleep(USEC);
int64_t final_energy_uj = get_cpuConsumptionUJoules();
int64_t final_time_us = get_currentTimeUSec();
if (final_energy_uj == -1 || final_time_us == -1)
{
fprintf(stderr, "Failed to read final CPU consumption or time data\n");
return 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 (time_delta_us <= 0 || energy_delta_uj < 0)
{
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_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)
{
int value = -1;
FILE *file = fopen(path, "r");
if (!file)
return -1;
if (fscanf(file, "%d", &value) != 1)
value = -1;
fclose(file);
return value;
}
/**
* @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)
{
glob_t hwmon_paths;
int found = -1;
if (glob("/sys/class/hwmon/hwmon*", 0, NULL, &hwmon_paths) != 0)
return -1;
for (size_t i = 0; i < hwmon_paths.gl_pathc; 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_path, hwmon_paths.gl_pathv[i], out_path_size - 1);
out_path[out_path_size - 1] = '\0';
found = 0;
fclose(f);
break;
}
}
fclose(f);
}
globfree(&hwmon_paths);
return found;
}
/**
* @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)
{
static enum GpuType cached_gpu_type = GPU_TYPE_UNINITIALIZED;
if (cached_gpu_type != GPU_TYPE_UNINITIALIZED)
return cached_gpu_type;
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);
if (cached_gpu_type == GPU_TYPE_NVIDIA)
return cached_gpu_type;
}
#endif
cached_gpu_type = GPU_TYPE_NONE;
return cached_gpu_type;
}
#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] = "nvidia-smi --query-gpu=temperature.gpu,power.draw --format=csv,noheader";
FILE *fp = popen(command, "r");
if (fp)
{
char line[BUFFER_SIZE];
if (fgets(line, sizeof(line), fp))
sscanf(line, "%f, %f", temperature, power);
pclose(fp);
}
else
{
*temperature = -1;
*power = -1;
}
}
#endif
/**
* @brief Mencetak informasi sistem (produsen dan produk).
*/
static void print_system_info(void)
{
char *board_manufacturer = execute_command("dmidecode -s system-manufacturer");
char *board_product = execute_command("dmidecode -s system-product-name");
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, "NCT668x sensor module not found!\n");
}
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_power = calculate_cpu_power();
}
else
{
fprintf(stderr, "k10temp sensor module not found!\n");
}
char *processor_name = execute_command("dmidecode -s processor-version");
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");
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");
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)
{
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 (find_hwmon_path_by_name("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);
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");
}
}
#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);
}
}
}
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;
}