2021-06-19 14:57:27 +02:00
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
Licensed under the Apache License , Version 2.0 ( the " License " ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an " AS IS " BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
indent = tab
tab - size = 4
*/
2023-07-05 13:23:11 +02:00
# include <cstdlib>
2022-05-24 14:42:59 +02:00
# include <robin_hood.h>
2021-06-19 14:57:27 +02:00
# include <fstream>
# include <ranges>
# include <cmath>
# include <unistd.h>
2021-07-29 23:40:56 +02:00
# include <numeric>
2021-08-10 20:20:33 +02:00
# include <sys/statvfs.h>
2021-08-15 23:20:55 +02:00
# include <netdb.h>
# include <ifaddrs.h>
# include <net/if.h>
2022-11-04 04:49:12 +01:00
# include <arpa/inet.h> // for inet_ntop()
2023-08-26 20:29:43 +02:00
# include <filesystem>
2023-11-25 21:01:11 +01:00
# include <future>
2023-06-01 03:41:56 +02:00
# include <dlfcn.h>
2022-11-04 04:49:12 +01:00
2023-07-14 02:39:44 +02:00
# if defined(RSMI_STATIC)
# include <rocm_smi/rocm_smi.h>
# endif
2021-06-19 14:57:27 +02:00
2021-10-17 01:45:26 +02:00
# if !(defined(STATIC_BUILD) && defined(__GLIBC__))
2021-10-06 10:47:24 +02:00
# include <pwd.h>
# endif
2023-08-26 20:29:43 +02:00
# include "../btop_shared.hpp"
# include "../btop_config.hpp"
# include "../btop_tools.hpp"
2021-06-19 14:57:27 +02:00
2022-10-02 23:29:05 +02:00
using std : : clamp ;
using std : : cmp_greater ;
using std : : cmp_less ;
using std : : ifstream ;
using std : : max ;
using std : : min ;
using std : : numeric_limits ;
using std : : round ;
using std : : streamsize ;
2023-08-26 20:29:43 +02:00
using std : : vector ;
2023-11-25 21:01:11 +01:00
using std : : future ;
using std : : async ;
using std : : pair ;
2022-10-02 23:29:05 +02:00
2021-06-19 14:57:27 +02:00
namespace fs = std : : filesystem ;
namespace rng = std : : ranges ;
2022-10-02 23:29:05 +02:00
using namespace Tools ;
using namespace std : : literals ; // for operator""s
2023-11-25 21:01:11 +01:00
using namespace std : : chrono_literals ;
2021-06-19 14:57:27 +02:00
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
2021-07-29 23:40:56 +02:00
namespace Cpu {
2021-08-03 23:47:46 +02:00
vector < long long > core_old_totals ;
vector < long long > core_old_idles ;
2023-08-27 00:31:07 +02:00
vector < string > available_fields = { " Auto " , " total " } ;
2021-09-12 15:58:23 +02:00
vector < string > available_sensors = { " Auto " } ;
2021-07-29 23:40:56 +02:00
cpu_info current_cpu ;
2021-08-03 23:47:46 +02:00
fs : : path freq_path = " /sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq " ;
2023-08-26 20:29:43 +02:00
bool got_sensors { } ; // defaults to false
bool cpu_temp_only { } ; // defaults to false
2021-08-03 23:47:46 +02:00
//* Populate found_sensors map
bool get_sensors ( ) ;
//* Get current cpu clock speed
string get_cpuHz ( ) ;
//* Search /proc/cpuinfo for a cpu name
string get_cpuName ( ) ;
struct Sensor {
fs : : path path ;
string label ;
2023-08-26 20:29:43 +02:00
int64_t temp { } ; // defaults to 0
int64_t high { } ; // defaults to 0
int64_t crit { } ; // defaults to 0
2021-08-03 23:47:46 +02:00
} ;
unordered_flat_map < string , Sensor > found_sensors ;
string cpu_sensor ;
vector < string > core_sensors ;
unordered_flat_map < int , int > core_mapping ;
2021-07-29 23:40:56 +02:00
}
2023-05-12 19:34:47 +02:00
namespace Gpu {
2023-05-15 13:58:54 +02:00
vector < gpu_info > gpus ;
2023-11-25 20:44:45 +01:00
# ifdef GPU_SUPPORT
2023-05-12 19:34:47 +02:00
//? NVIDIA data collection
namespace Nvml {
2023-06-02 15:34:12 +02:00
//? NVML defines, structs & typedefs
2023-06-01 03:41:56 +02:00
# define NVML_DEVICE_NAME_BUFFER_SIZE 64
# define NVML_SUCCESS 0
# define NVML_TEMPERATURE_THRESHOLD_SHUTDOWN 0
# define NVML_CLOCK_GRAPHICS 0
# define NVML_CLOCK_MEM 2
# define NVML_TEMPERATURE_GPU 0
# define NVML_PCIE_UTIL_TX_BYTES 0
# define NVML_PCIE_UTIL_RX_BYTES 1
typedef void * nvmlDevice_t ; // we won't be accessing any of the underlying struct's properties, so this is fine
typedef int nvmlReturn_t , // enums are basically ints
nvmlTemperatureThresholds_t ,
nvmlClockType_t ,
nvmlPstates_t ,
nvmlTemperatureSensors_t ,
nvmlPcieUtilCounter_t ;
struct nvmlUtilization_t { unsigned int gpu , memory ; } ;
struct nvmlMemory_t { unsigned long long total , free , used ; } ;
//? Function pointers
const char * ( * nvmlErrorString ) ( nvmlReturn_t ) ;
nvmlReturn_t ( * nvmlInit ) ( ) ;
nvmlReturn_t ( * nvmlShutdown ) ( ) ;
nvmlReturn_t ( * nvmlDeviceGetCount ) ( unsigned int * ) ;
nvmlReturn_t ( * nvmlDeviceGetHandleByIndex ) ( unsigned int , nvmlDevice_t * ) ;
nvmlReturn_t ( * nvmlDeviceGetName ) ( nvmlDevice_t , char * , unsigned int ) ;
nvmlReturn_t ( * nvmlDeviceGetPowerManagementLimit ) ( nvmlDevice_t , unsigned int * ) ;
nvmlReturn_t ( * nvmlDeviceGetTemperatureThreshold ) ( nvmlDevice_t , nvmlTemperatureThresholds_t , unsigned int * ) ;
nvmlReturn_t ( * nvmlDeviceGetUtilizationRates ) ( nvmlDevice_t , nvmlUtilization_t * ) ;
nvmlReturn_t ( * nvmlDeviceGetClockInfo ) ( nvmlDevice_t , nvmlClockType_t , unsigned int * ) ;
nvmlReturn_t ( * nvmlDeviceGetPowerUsage ) ( nvmlDevice_t , unsigned int * ) ;
nvmlReturn_t ( * nvmlDeviceGetPowerState ) ( nvmlDevice_t , nvmlPstates_t * ) ;
nvmlReturn_t ( * nvmlDeviceGetTemperature ) ( nvmlDevice_t , nvmlTemperatureSensors_t , unsigned int * ) ;
nvmlReturn_t ( * nvmlDeviceGetMemoryInfo ) ( nvmlDevice_t , nvmlMemory_t * ) ;
nvmlReturn_t ( * nvmlDeviceGetPcieThroughput ) ( nvmlDevice_t , nvmlPcieUtilCounter_t , unsigned int * ) ;
2023-06-02 15:34:12 +02:00
//? Data
2023-06-01 03:41:56 +02:00
void * nvml_dl_handle ;
2023-05-12 19:34:47 +02:00
bool initialized = false ;
bool init ( ) ;
bool shutdown ( ) ;
2023-05-21 18:02:50 +02:00
template < bool is_init > bool collect ( gpu_info * gpus_slice ) ;
2023-05-15 13:58:54 +02:00
vector < nvmlDevice_t > devices ;
unsigned int device_count = 0 ;
2023-05-12 19:34:47 +02:00
}
2023-05-15 19:42:55 +02:00
//? AMD data collection
namespace Rsmi {
2023-06-02 15:34:12 +02:00
# if !defined(RSMI_STATIC)
2023-06-16 11:11:57 +02:00
//? RSMI defines, structs & typedefs
# define RSMI_MAX_NUM_FREQUENCIES 32
# define RSMI_STATUS_SUCCESS 0
# define RSMI_MEM_TYPE_VRAM 0
# define RSMI_TEMP_CURRENT 0
# define RSMI_TEMP_TYPE_EDGE 0
# define RSMI_CLK_TYPE_MEM 4
# define RSMI_CLK_TYPE_SYS 0
# define RSMI_TEMP_MAX 1
typedef int rsmi_status_t ,
rsmi_temperature_metric_t ,
rsmi_clk_type_t ,
rsmi_memory_type_t ;
struct rsmi_frequencies_t { uint32_t num_supported , current , frequency [ RSMI_MAX_NUM_FREQUENCIES ] ; } ;
2023-06-02 15:34:12 +02:00
//? Function pointers
rsmi_status_t ( * rsmi_init ) ( uint64_t ) ;
rsmi_status_t ( * rsmi_shut_down ) ( ) ;
rsmi_status_t ( * rsmi_num_monitor_devices ) ( uint32_t * ) ;
rsmi_status_t ( * rsmi_dev_name_get ) ( uint32_t , char * , size_t ) ;
rsmi_status_t ( * rsmi_dev_power_cap_get ) ( uint32_t , uint32_t , uint64_t * ) ;
rsmi_status_t ( * rsmi_dev_temp_metric_get ) ( uint32_t , uint32_t , rsmi_temperature_metric_t , int64_t * ) ;
rsmi_status_t ( * rsmi_dev_busy_percent_get ) ( uint32_t , uint32_t * ) ;
rsmi_status_t ( * rsmi_dev_memory_busy_percent_get ) ( uint32_t , uint32_t * ) ;
rsmi_status_t ( * rsmi_dev_gpu_clk_freq_get ) ( uint32_t , rsmi_clk_type_t , rsmi_frequencies_t * ) ;
rsmi_status_t ( * rsmi_dev_power_ave_get ) ( uint32_t , uint32_t , uint64_t * ) ;
rsmi_status_t ( * rsmi_dev_memory_total_get ) ( uint32_t , rsmi_memory_type_t , uint64_t * ) ;
rsmi_status_t ( * rsmi_dev_memory_usage_get ) ( uint32_t , rsmi_memory_type_t , uint64_t * ) ;
rsmi_status_t ( * rsmi_dev_pci_throughput_get ) ( uint32_t , uint64_t * , uint64_t * , uint64_t * ) ;
//? Data
void * rsmi_dl_handle ;
# endif
2023-05-15 19:42:55 +02:00
bool initialized = false ;
bool init ( ) ;
bool shutdown ( ) ;
2023-05-21 18:02:50 +02:00
template < bool is_init > bool collect ( gpu_info * gpus_slice ) ;
2023-05-15 19:42:55 +02:00
uint32_t device_count = 0 ;
}
2023-11-25 20:44:45 +01:00
# endif
2023-05-12 19:34:47 +02:00
}
2023-05-15 13:58:54 +02:00
namespace Mem {
double old_uptime ;
}
2021-06-25 23:58:19 +02:00
namespace Shared {
2021-08-03 23:47:46 +02:00
fs : : path procPath , passwd_path ;
2021-07-26 01:06:34 +02:00
long pageSize , clkTck , coreCount ;
2021-06-25 23:58:19 +02:00
2021-07-21 03:17:34 +02:00
void init ( ) {
2021-07-26 01:06:34 +02:00
2021-07-29 23:40:56 +02:00
//? Shared global variables init
procPath = ( fs : : is_directory ( fs : : path ( " /proc " ) ) and access ( " /proc " , R_OK ) ! = - 1 ) ? " /proc " : " " ;
if ( procPath . empty ( ) )
2021-07-24 02:13:26 +02:00
throw std : : runtime_error ( " Proc filesystem not found or no permission to read from it! " ) ;
2021-06-25 23:58:19 +02:00
2021-07-24 02:13:26 +02:00
passwd_path = ( fs : : is_regular_file ( fs : : path ( " /etc/passwd " ) ) and access ( " /etc/passwd " , R_OK ) ! = - 1 ) ? " /etc/passwd " : " " ;
if ( passwd_path . empty ( ) )
Logger : : warning ( " Could not read /etc/passwd, will show UID instead of username. " ) ;
2021-06-25 23:58:19 +02:00
2022-10-13 09:26:06 +02:00
coreCount = sysconf ( _SC_NPROCESSORS_ONLN ) ;
2021-07-29 23:40:56 +02:00
if ( coreCount < 1 ) {
2022-10-07 13:41:04 +02:00
coreCount = sysconf ( _SC_NPROCESSORS_CONF ) ;
if ( coreCount < 1 ) {
coreCount = 1 ;
Logger : : warning ( " Could not determine number of cores, defaulting to 1. " ) ;
}
2021-07-29 23:40:56 +02:00
}
2021-07-26 01:06:34 +02:00
pageSize = sysconf ( _SC_PAGE_SIZE ) ;
if ( pageSize < = 0 ) {
pageSize = 4096 ;
2021-06-25 23:58:19 +02:00
Logger : : warning ( " Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect. " ) ;
}
2021-07-26 01:06:34 +02:00
clkTck = sysconf ( _SC_CLK_TCK ) ;
if ( clkTck < = 0 ) {
clkTck = 100 ;
2021-07-21 03:17:34 +02:00
Logger : : warning ( " Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect. " ) ;
2021-06-25 23:58:19 +02:00
}
2021-10-06 18:06:05 +02:00
2021-07-29 23:40:56 +02:00
//? Init for namespace Cpu
2021-08-03 23:47:46 +02:00
if ( not fs : : exists ( Cpu : : freq_path ) or access ( Cpu : : freq_path . c_str ( ) , R_OK ) = = - 1 ) Cpu : : freq_path . clear ( ) ;
2021-07-29 23:40:56 +02:00
Cpu : : current_cpu . core_percent . insert ( Cpu : : current_cpu . core_percent . begin ( ) , Shared : : coreCount , { } ) ;
Cpu : : current_cpu . temp . insert ( Cpu : : current_cpu . temp . begin ( ) , Shared : : coreCount + 1 , { } ) ;
Cpu : : core_old_totals . insert ( Cpu : : core_old_totals . begin ( ) , Shared : : coreCount , 0 ) ;
Cpu : : core_old_idles . insert ( Cpu : : core_old_idles . begin ( ) , Shared : : coreCount , 0 ) ;
Cpu : : collect ( ) ;
2022-10-07 20:45:12 +02:00
if ( Runner : : coreNum_reset ) Runner : : coreNum_reset = false ;
2021-07-29 23:40:56 +02:00
for ( auto & [ field , vec ] : Cpu : : current_cpu . cpu_percent ) {
2023-08-27 00:31:07 +02:00
if ( not vec . empty ( ) and not v_contains ( Cpu : : available_fields , field ) ) Cpu : : available_fields . push_back ( field ) ;
2021-07-29 23:40:56 +02:00
}
Cpu : : cpuName = Cpu : : get_cpuName ( ) ;
2021-08-03 23:47:46 +02:00
Cpu : : got_sensors = Cpu : : get_sensors ( ) ;
2021-09-12 15:58:23 +02:00
for ( const auto & [ sensor , ignored ] : Cpu : : found_sensors ) {
Cpu : : available_sensors . push_back ( sensor ) ;
}
2021-08-03 23:47:46 +02:00
Cpu : : core_mapping = Cpu : : get_core_mapping ( ) ;
2023-05-18 16:07:05 +02:00
//? Init for namespace Gpu
2023-11-25 20:44:45 +01:00
# ifdef GPU_SUPPORT
2023-05-18 16:07:05 +02:00
Gpu : : Nvml : : init ( ) ;
Gpu : : Rsmi : : init ( ) ;
if ( not Gpu : : gpu_names . empty ( ) ) {
2023-05-30 18:24:50 +02:00
for ( auto const & [ key , _ ] : Gpu : : gpus [ 0 ] . gpu_percent )
Cpu : : available_fields . push_back ( key ) ;
for ( auto const & [ key , _ ] : Gpu : : shared_gpu_percent )
Cpu : : available_fields . push_back ( key ) ;
2023-05-21 20:34:47 +02:00
using namespace Gpu ;
gpu_b_height_offsets . resize ( gpus . size ( ) ) ;
2023-06-05 21:59:26 +02:00
for ( size_t i = 0 ; i < gpu_b_height_offsets . size ( ) ; + + i )
2023-05-21 20:34:47 +02:00
gpu_b_height_offsets [ i ] = gpus [ i ] . supported_functions . gpu_utilization
+ gpus [ i ] . supported_functions . pwr_usage
+ ( gpus [ i ] . supported_functions . mem_total or gpus [ i ] . supported_functions . mem_used )
* ( 1 + 2 * ( gpus [ i ] . supported_functions . mem_total and gpus [ i ] . supported_functions . mem_used ) + 2 * gpus [ i ] . supported_functions . mem_utilization ) ;
2023-05-18 16:07:05 +02:00
}
2023-11-25 20:44:45 +01:00
# endif
2023-05-18 16:07:05 +02:00
2021-08-10 20:20:33 +02:00
//? Init for namespace Mem
2021-09-01 21:40:13 +02:00
Mem : : old_uptime = system_uptime ( ) ;
2021-08-10 20:20:33 +02:00
Mem : : collect ( ) ;
2021-08-03 23:47:46 +02:00
2023-05-20 01:41:04 +02:00
Logger : : debug ( " Shared::init() : Initialized. " ) ;
2021-06-25 23:58:19 +02:00
}
}
2021-07-04 22:02:31 +02:00
namespace Cpu {
2021-07-29 23:40:56 +02:00
string cpuName ;
string cpuHz ;
2021-09-17 14:25:54 +02:00
bool has_battery = true ;
tuple < int , long , string > current_bat ;
2021-07-29 23:40:56 +02:00
2023-08-26 20:29:43 +02:00
const array time_names {
" user " s , " nice " s , " system " s , " idle " s , " iowait " s ,
" irq " s , " softirq " s , " steal " s , " guest " s , " guest_nice " s
} ;
2021-07-29 23:40:56 +02:00
2021-08-03 23:47:46 +02:00
unordered_flat_map < string , long long > cpu_old = {
2021-07-29 23:40:56 +02:00
{ " totals " , 0 } ,
{ " idles " , 0 } ,
{ " user " , 0 } ,
{ " nice " , 0 } ,
{ " system " , 0 } ,
{ " idle " , 0 } ,
{ " iowait " , 0 } ,
{ " irq " , 0 } ,
{ " softirq " , 0 } ,
{ " steal " , 0 } ,
{ " guest " , 0 } ,
{ " guest_nice " , 0 }
} ;
2021-07-18 15:44:32 +02:00
2021-07-29 23:40:56 +02:00
string get_cpuName ( ) {
string name ;
ifstream cpuinfo ( Shared : : procPath / " cpuinfo " ) ;
if ( cpuinfo . good ( ) ) {
for ( string instr ; getline ( cpuinfo , instr , ' : ' ) and not instr . starts_with ( " model name " ) ; )
cpuinfo . ignore ( SSmax , ' \n ' ) ;
if ( cpuinfo . bad ( ) ) return name ;
2021-09-17 14:25:54 +02:00
else if ( not cpuinfo . eof ( ) ) {
cpuinfo . ignore ( 1 ) ;
getline ( cpuinfo , name ) ;
}
else if ( fs : : exists ( " /sys/devices " ) ) {
for ( const auto & d : fs : : directory_iterator ( " /sys/devices " ) ) {
if ( string ( d . path ( ) . filename ( ) ) . starts_with ( " arm " ) ) {
name = d . path ( ) . filename ( ) ;
break ;
}
}
if ( not name . empty ( ) ) {
auto name_vec = ssplit ( name , ' _ ' ) ;
if ( name_vec . size ( ) < 2 ) return capitalize ( name ) ;
else return capitalize ( name_vec . at ( 1 ) ) + ( name_vec . size ( ) > 2 ? ' ' + capitalize ( name_vec . at ( 2 ) ) : " " ) ;
}
}
2021-07-29 23:40:56 +02:00
auto name_vec = ssplit ( name ) ;
2021-08-03 23:47:46 +02:00
if ( ( s_contains ( name , " Xeon " s ) or v_contains ( name_vec , " Duo " s ) ) and v_contains ( name_vec , " CPU " s ) ) {
2021-07-29 23:40:56 +02:00
auto cpu_pos = v_index ( name_vec , " CPU " s ) ;
if ( cpu_pos < name_vec . size ( ) - 1 and not name_vec . at ( cpu_pos + 1 ) . ends_with ( ' ) ' ) )
name = name_vec . at ( cpu_pos + 1 ) ;
else
name . clear ( ) ;
}
2021-08-03 23:47:46 +02:00
else if ( v_contains ( name_vec , " Ryzen " s ) ) {
2021-07-29 23:40:56 +02:00
auto ryz_pos = v_index ( name_vec , " Ryzen " s ) ;
name = " Ryzen " + ( ryz_pos < name_vec . size ( ) - 1 ? ' ' + name_vec . at ( ryz_pos + 1 ) : " " )
+ ( ryz_pos < name_vec . size ( ) - 2 ? ' ' + name_vec . at ( ryz_pos + 2 ) : " " ) ;
}
2021-08-03 23:47:46 +02:00
else if ( s_contains ( name , " Intel " s ) and v_contains ( name_vec , " CPU " s ) ) {
2021-07-29 23:40:56 +02:00
auto cpu_pos = v_index ( name_vec , " CPU " s ) ;
if ( cpu_pos < name_vec . size ( ) - 1 and not name_vec . at ( cpu_pos + 1 ) . ends_with ( ' ) ' ) and name_vec . at ( cpu_pos + 1 ) ! = " @ " )
name = name_vec . at ( cpu_pos + 1 ) ;
else
name . clear ( ) ;
}
else
name . clear ( ) ;
if ( name . empty ( ) and not name_vec . empty ( ) ) {
for ( const auto & n : name_vec ) {
if ( n = = " @ " ) break ;
name + = n + ' ' ;
}
name . pop_back ( ) ;
2021-10-26 23:41:40 +02:00
for ( const auto & replace : { " Processor " , " CPU " , " (R) " , " (TM) " , " Intel " , " AMD " , " Core " } ) {
name = s_replace ( name , replace , " " ) ;
2021-10-26 23:50:28 +02:00
name = s_replace ( name , " " , " " ) ;
2021-07-29 23:40:56 +02:00
}
name = trim ( name ) ;
}
}
return name ;
}
2021-08-03 23:47:46 +02:00
bool get_sensors ( ) {
bool got_cpu = false , got_coretemp = false ;
vector < fs : : path > search_paths ;
try {
//? Setup up paths to search for sensors
if ( fs : : exists ( fs : : path ( " /sys/class/hwmon " ) ) and access ( " /sys/class/hwmon " , R_OK ) ! = - 1 ) {
for ( const auto & dir : fs : : directory_iterator ( fs : : path ( " /sys/class/hwmon " ) ) ) {
fs : : path add_path = fs : : canonical ( dir . path ( ) ) ;
if ( v_contains ( search_paths , add_path ) or v_contains ( search_paths , add_path / " device " ) ) continue ;
if ( s_contains ( add_path , " coretemp " ) )
got_coretemp = true ;
2021-11-17 00:08:05 +01:00
for ( const auto & file : fs : : directory_iterator ( add_path ) ) {
if ( string ( file . path ( ) . filename ( ) ) = = " device " ) {
for ( const auto & dev_file : fs : : directory_iterator ( file . path ( ) ) ) {
string dev_filename = dev_file . path ( ) . filename ( ) ;
if ( dev_filename . starts_with ( " temp " ) and dev_filename . ends_with ( " _input " ) ) {
search_paths . push_back ( file . path ( ) ) ;
break ;
}
}
}
string filename = file . path ( ) . filename ( ) ;
if ( filename . starts_with ( " temp " ) and filename . ends_with ( " _input " ) ) {
search_paths . push_back ( add_path ) ;
break ;
}
2021-08-03 23:47:46 +02:00
}
}
}
if ( not got_coretemp and fs : : exists ( fs : : path ( " /sys/devices/platform/coretemp.0/hwmon " ) ) ) {
for ( auto & d : fs : : directory_iterator ( fs : : path ( " /sys/devices/platform/coretemp.0/hwmon " ) ) ) {
fs : : path add_path = fs : : canonical ( d . path ( ) ) ;
2021-11-17 00:08:05 +01:00
for ( const auto & file : fs : : directory_iterator ( add_path ) ) {
string filename = file . path ( ) . filename ( ) ;
if ( filename . starts_with ( " temp " ) and filename . ends_with ( " _input " ) and not v_contains ( search_paths , add_path ) ) {
search_paths . push_back ( add_path ) ;
got_coretemp = true ;
break ;
}
2021-08-03 23:47:46 +02:00
}
}
}
//? Scan any found directories for temperature sensors
if ( not search_paths . empty ( ) ) {
for ( const auto & path : search_paths ) {
const string pname = readfile ( path / " name " , path . filename ( ) ) ;
2021-11-17 00:08:05 +01:00
for ( const auto & file : fs : : directory_iterator ( path ) ) {
const string file_suffix = " input " ;
const int file_id = atoi ( file . path ( ) . filename ( ) . c_str ( ) + 4 ) ; // skip "temp" prefix
string file_path = file . path ( ) ;
if ( ! s_contains ( file_path , file_suffix ) ) {
continue ;
}
const string basepath = file_path . erase ( file_path . find ( file_suffix ) , file_suffix . length ( ) ) ;
const string label = readfile ( fs : : path ( basepath + " label " ) , " temp " + to_string ( file_id ) ) ;
2021-08-03 23:47:46 +02:00
const string sensor_name = pname + " / " + label ;
const int64_t temp = stol ( readfile ( fs : : path ( basepath + " input " ) , " 0 " ) ) / 1000 ;
const int64_t high = stol ( readfile ( fs : : path ( basepath + " max " ) , " 80000 " ) ) / 1000 ;
const int64_t crit = stol ( readfile ( fs : : path ( basepath + " crit " ) , " 95000 " ) ) / 1000 ;
found_sensors [ sensor_name ] = { fs : : path ( basepath + " input " ) , label , temp , high , crit } ;
if ( not got_cpu and ( label . starts_with ( " Package id " ) or label . starts_with ( " Tdie " ) ) ) {
got_cpu = true ;
cpu_sensor = sensor_name ;
}
else if ( label . starts_with ( " Core " ) or label . starts_with ( " Tccd " ) ) {
got_coretemp = true ;
if ( not v_contains ( core_sensors , sensor_name ) ) core_sensors . push_back ( sensor_name ) ;
}
}
}
}
//? If no good candidate for cpu temp has been found scan /sys/class/thermal
if ( not got_cpu and fs : : exists ( fs : : path ( " /sys/class/thermal " ) ) ) {
const string rootpath = fs : : path ( " /sys/class/thermal/thermal_zone " ) ;
for ( int i = 0 ; fs : : exists ( fs : : path ( rootpath + to_string ( i ) ) ) ; i + + ) {
const fs : : path basepath = rootpath + to_string ( i ) ;
if ( not fs : : exists ( basepath / " temp " ) ) continue ;
const string label = readfile ( basepath / " type " , " temp " + to_string ( i ) ) ;
const string sensor_name = " thermal " + to_string ( i ) + " / " + label ;
const int64_t temp = stol ( readfile ( basepath / " temp " , " 0 " ) ) / 1000 ;
int64_t high , crit ;
for ( int ii = 0 ; fs : : exists ( basepath / string ( " trip_point_ " + to_string ( ii ) + " _temp " ) ) ; ii + + ) {
const string trip_type = readfile ( basepath / string ( " trip_point_ " + to_string ( ii ) + " _type " ) ) ;
if ( not is_in ( trip_type , " high " , " critical " ) ) continue ;
auto & val = ( trip_type = = " high " ? high : crit ) ;
val = stol ( readfile ( basepath / string ( " trip_point_ " + to_string ( ii ) + " _temp " ) , " 0 " ) ) / 1000 ;
}
if ( high < 1 ) high = 80 ;
if ( crit < 1 ) crit = 95 ;
found_sensors [ sensor_name ] = { basepath / " temp " , label , temp , high , crit } ;
}
}
}
catch ( . . . ) { }
2021-11-27 18:24:40 +01:00
if ( not got_coretemp or core_sensors . empty ( ) ) {
cpu_temp_only = true ;
}
else {
rng : : sort ( core_sensors , rng : : less { } ) ;
rng : : stable_sort ( core_sensors , [ ] ( const auto & a , const auto & b ) {
return a . size ( ) < b . size ( ) ;
} ) ;
}
2021-08-03 23:47:46 +02:00
if ( cpu_sensor . empty ( ) and not found_sensors . empty ( ) ) {
for ( const auto & [ name , sensor ] : found_sensors ) {
2021-10-26 07:26:52 +02:00
if ( s_contains ( str_to_lower ( name ) , " cpu " ) or s_contains ( str_to_lower ( name ) , " k10temp " ) ) {
2021-10-24 20:17:54 +02:00
cpu_sensor = name ;
break ;
}
2021-08-03 23:47:46 +02:00
}
if ( cpu_sensor . empty ( ) ) {
cpu_sensor = found_sensors . begin ( ) - > first ;
Logger : : warning ( " No good candidate for cpu sensor found, using random from all found sensors. " ) ;
}
}
return not found_sensors . empty ( ) ;
}
void update_sensors ( ) {
if ( cpu_sensor . empty ( ) ) return ;
const auto & cpu_sensor = ( not Config : : getS ( " cpu_sensor " ) . empty ( ) and found_sensors . contains ( Config : : getS ( " cpu_sensor " ) ) ? Config : : getS ( " cpu_sensor " ) : Cpu : : cpu_sensor ) ;
found_sensors . at ( cpu_sensor ) . temp = stol ( readfile ( found_sensors . at ( cpu_sensor ) . path , " 0 " ) ) / 1000 ;
current_cpu . temp . at ( 0 ) . push_back ( found_sensors . at ( cpu_sensor ) . temp ) ;
current_cpu . temp_max = found_sensors . at ( cpu_sensor ) . crit ;
if ( current_cpu . temp . at ( 0 ) . size ( ) > 20 ) current_cpu . temp . at ( 0 ) . pop_front ( ) ;
if ( Config : : getB ( " show_coretemp " ) and not cpu_temp_only ) {
vector < string > done ;
for ( const auto & sensor : core_sensors ) {
if ( v_contains ( done , sensor ) ) continue ;
found_sensors . at ( sensor ) . temp = stol ( readfile ( found_sensors . at ( sensor ) . path , " 0 " ) ) / 1000 ;
done . push_back ( sensor ) ;
}
for ( const auto & [ core , temp ] : core_mapping ) {
2021-08-11 20:25:11 +02:00
if ( cmp_less ( core + 1 , current_cpu . temp . size ( ) ) and cmp_less ( temp , core_sensors . size ( ) ) ) {
2021-08-03 23:47:46 +02:00
current_cpu . temp . at ( core + 1 ) . push_back ( found_sensors . at ( core_sensors . at ( temp ) ) . temp ) ;
if ( current_cpu . temp . at ( core + 1 ) . size ( ) > 20 ) current_cpu . temp . at ( core + 1 ) . pop_front ( ) ;
}
}
}
}
2021-07-29 23:40:56 +02:00
string get_cpuHz ( ) {
2023-08-26 20:29:43 +02:00
static int failed { } ; // defaults to 0
2022-10-02 23:29:05 +02:00
2023-08-26 20:29:43 +02:00
if ( failed > 4 )
return " " s ;
2022-10-02 23:29:05 +02:00
2021-07-29 23:40:56 +02:00
string cpuhz ;
try {
2023-08-26 20:29:43 +02:00
double hz { } ; // defaults to 0.0
2021-08-03 23:47:46 +02:00
//? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster)
if ( not freq_path . empty ( ) ) {
hz = stod ( readfile ( freq_path , " 0.0 " ) ) / 1000 ;
if ( hz < = 0.0 and + + failed > = 2 )
freq_path . clear ( ) ;
}
//? If freq from /sys failed or is missing try to use /proc/cpuinfo
if ( hz < = 0.0 ) {
ifstream cpufreq ( Shared : : procPath / " cpuinfo " ) ;
if ( cpufreq . good ( ) ) {
while ( cpufreq . ignore ( SSmax , ' \n ' ) ) {
if ( cpufreq . peek ( ) = = ' c ' ) {
cpufreq . ignore ( SSmax , ' ' ) ;
if ( cpufreq . peek ( ) = = ' M ' ) {
cpufreq . ignore ( SSmax , ' : ' ) ;
cpufreq . ignore ( 1 ) ;
cpufreq > > hz ;
break ;
}
}
}
2021-07-29 23:40:56 +02:00
}
}
2021-08-03 23:47:46 +02:00
2023-08-26 20:29:43 +02:00
if ( hz < = 1 or hz > = 1000000 )
throw std : : runtime_error ( " Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo. " ) ;
2021-08-03 23:47:46 +02:00
if ( hz > = 1000 ) {
if ( hz > = 10000 ) cpuhz = to_string ( ( int ) round ( hz / 1000 ) ) ; // Future proof until we reach THz speeds :)
else cpuhz = to_string ( round ( hz / 100 ) / 10.0 ) . substr ( 0 , 3 ) ;
cpuhz + = " GHz " ;
}
else if ( hz > 0 )
cpuhz = to_string ( ( int ) round ( hz ) ) + " MHz " ;
2021-07-29 23:40:56 +02:00
}
2021-08-03 23:47:46 +02:00
catch ( const std : : exception & e ) {
2023-08-26 20:29:43 +02:00
if ( + + failed < 5 )
return " " s ;
2021-08-03 23:47:46 +02:00
else {
2023-08-26 20:29:43 +02:00
Logger : : warning ( " get_cpuHZ() : " + string { e . what ( ) } ) ;
2021-08-03 23:47:46 +02:00
return " " s ;
}
2021-07-29 23:40:56 +02:00
}
return cpuhz ;
}
2021-07-18 15:44:32 +02:00
2021-08-04 00:11:50 +02:00
auto get_core_mapping ( ) - > unordered_flat_map < int , int > {
2021-08-03 23:47:46 +02:00
unordered_flat_map < int , int > core_map ;
2021-09-17 14:25:54 +02:00
if ( cpu_temp_only ) return core_map ;
2021-08-10 20:20:33 +02:00
//? Try to get core mapping from /proc/cpuinfo
ifstream cpuinfo ( Shared : : procPath / " cpuinfo " ) ;
2021-08-03 23:47:46 +02:00
if ( cpuinfo . good ( ) ) {
2023-08-26 20:29:43 +02:00
int cpu { } ; // defaults to 0
int core { } ; // defaults to 0
int n { } ; // defaults to 0
2021-08-03 23:47:46 +02:00
for ( string instr ; cpuinfo > > instr ; ) {
if ( instr = = " processor " ) {
cpuinfo . ignore ( SSmax , ' : ' ) ;
cpuinfo > > cpu ;
}
else if ( instr . starts_with ( " core " ) ) {
cpuinfo . ignore ( SSmax , ' : ' ) ;
cpuinfo > > core ;
2021-09-17 14:25:54 +02:00
if ( std : : cmp_greater_equal ( core , core_sensors . size ( ) ) ) {
if ( std : : cmp_greater_equal ( n , core_sensors . size ( ) ) ) n = 0 ;
core_map [ cpu ] = n + + ;
}
else
core_map [ cpu ] = core ;
2021-08-03 23:47:46 +02:00
}
cpuinfo . ignore ( SSmax , ' \n ' ) ;
}
}
2021-09-17 14:25:54 +02:00
//? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc.
2021-08-11 20:25:11 +02:00
if ( cmp_less ( core_map . size ( ) , Shared : : coreCount ) ) {
2021-08-10 20:20:33 +02:00
if ( Shared : : coreCount % 2 = = 0 and ( long ) core_map . size ( ) = = Shared : : coreCount / 2 ) {
2021-09-17 14:25:54 +02:00
for ( int i = 0 , n = 0 ; i < Shared : : coreCount / 2 ; i + + ) {
if ( std : : cmp_greater_equal ( n , core_sensors . size ( ) ) ) n = 0 ;
core_map [ Shared : : coreCount / 2 + i ] = n + + ;
}
2021-08-03 23:47:46 +02:00
}
else {
core_map . clear ( ) ;
2021-09-17 14:25:54 +02:00
for ( int i = 0 , n = 0 ; i < Shared : : coreCount ; i + + ) {
if ( std : : cmp_greater_equal ( n , core_sensors . size ( ) ) ) n = 0 ;
core_map [ i ] = n + + ;
}
2021-08-03 23:47:46 +02:00
}
}
2021-08-10 20:20:33 +02:00
//? Apply user set custom mapping if any
2021-08-03 23:47:46 +02:00
const auto & custom_map = Config : : getS ( " cpu_core_map " ) ;
if ( not custom_map . empty ( ) ) {
try {
for ( const auto & split : ssplit ( custom_map ) ) {
const auto vals = ssplit ( split , ' : ' ) ;
if ( vals . size ( ) ! = 2 ) continue ;
int change_id = std : : stoi ( vals . at ( 0 ) ) ;
int new_id = std : : stoi ( vals . at ( 1 ) ) ;
2021-09-17 14:25:54 +02:00
if ( not core_map . contains ( change_id ) or cmp_greater ( new_id , core_sensors . size ( ) ) ) continue ;
2021-08-10 20:20:33 +02:00
core_map . at ( change_id ) = new_id ;
2021-08-03 23:47:46 +02:00
}
}
catch ( . . . ) { }
}
return core_map ;
}
2021-10-17 22:26:43 +02:00
struct battery {
fs : : path base_dir , energy_now , energy_full , power_now , status , online ;
string device_type ;
bool use_energy = true ;
} ;
2021-09-17 14:25:54 +02:00
auto get_battery ( ) - > tuple < int , long , string > {
if ( not has_battery ) return { 0 , 0 , " " } ;
2021-10-17 22:26:43 +02:00
static string auto_sel ;
static unordered_flat_map < string , battery > batteries ;
2021-09-17 14:25:54 +02:00
//? Get paths to needed files and check for valid values on first run
2021-10-17 22:26:43 +02:00
if ( batteries . empty ( ) and has_battery ) {
2022-06-21 14:42:06 +02:00
try {
if ( fs : : exists ( " /sys/class/power_supply " ) ) {
for ( const auto & d : fs : : directory_iterator ( " /sys/class/power_supply " ) ) {
//? Only consider online power supplies of type Battery or UPS
//? see kernel docs for details on the file structure and contents
//? https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power
battery new_bat ;
fs : : path bat_dir ;
try {
if ( not d . is_directory ( )
or not fs : : exists ( d . path ( ) / " type " )
or not fs : : exists ( d . path ( ) / " present " )
or stoi ( readfile ( d . path ( ) / " present " ) ) ! = 1 )
continue ;
string dev_type = readfile ( d . path ( ) / " type " ) ;
if ( is_in ( dev_type , " Battery " , " UPS " ) ) {
bat_dir = d . path ( ) ;
new_bat . base_dir = d . path ( ) ;
new_bat . device_type = dev_type ;
}
} catch ( . . . ) {
//? skip power supplies not conforming to the kernel standard
2021-10-17 17:06:18 +02:00
continue ;
}
2021-10-17 22:26:43 +02:00
2022-06-21 14:42:06 +02:00
if ( fs : : exists ( bat_dir / " energy_now " ) ) new_bat . energy_now = bat_dir / " energy_now " ;
else if ( fs : : exists ( bat_dir / " charge_now " ) ) new_bat . energy_now = bat_dir / " charge_now " ;
else new_bat . use_energy = false ;
2021-10-17 22:26:43 +02:00
2022-06-21 14:42:06 +02:00
if ( fs : : exists ( bat_dir / " energy_full " ) ) new_bat . energy_full = bat_dir / " energy_full " ;
else if ( fs : : exists ( bat_dir / " charge_full " ) ) new_bat . energy_full = bat_dir / " charge_full " ;
else new_bat . use_energy = false ;
2021-10-17 22:26:43 +02:00
2022-06-21 14:42:06 +02:00
if ( not new_bat . use_energy and not fs : : exists ( bat_dir / " capacity " ) ) {
continue ;
}
2021-10-17 22:26:43 +02:00
2022-06-21 14:42:06 +02:00
if ( fs : : exists ( bat_dir / " power_now " ) ) new_bat . power_now = bat_dir / " power_now " ;
else if ( fs : : exists ( bat_dir / " current_now " ) ) new_bat . power_now = bat_dir / " current_now " ;
2021-10-17 22:26:43 +02:00
2022-06-21 14:42:06 +02:00
if ( fs : : exists ( bat_dir / " AC0/online " ) ) new_bat . online = bat_dir / " AC0/online " ;
else if ( fs : : exists ( bat_dir / " AC/online " ) ) new_bat . online = bat_dir / " AC/online " ;
2021-10-17 22:26:43 +02:00
2022-06-21 14:42:06 +02:00
batteries [ bat_dir . filename ( ) ] = new_bat ;
Config : : available_batteries . push_back ( bat_dir . filename ( ) ) ;
}
2021-09-17 14:25:54 +02:00
}
}
2022-06-21 14:42:06 +02:00
catch ( . . . ) {
batteries . clear ( ) ;
}
2021-10-17 22:26:43 +02:00
if ( batteries . empty ( ) ) {
2021-09-17 14:25:54 +02:00
has_battery = false ;
return { 0 , 0 , " " } ;
}
2021-10-17 22:26:43 +02:00
}
2021-09-17 14:25:54 +02:00
2021-10-17 22:26:43 +02:00
auto & battery_sel = Config : : getS ( " selected_battery " ) ;
2021-09-17 14:25:54 +02:00
2021-10-17 22:55:36 +02:00
if ( auto_sel . empty ( ) ) {
2021-10-17 22:26:43 +02:00
for ( auto & [ name , bat ] : batteries ) {
if ( bat . device_type = = " Battery " ) {
auto_sel = name ;
break ;
2021-09-17 14:25:54 +02:00
}
}
2021-10-17 22:26:43 +02:00
if ( auto_sel . empty ( ) ) auto_sel = batteries . begin ( ) - > first ;
2021-09-17 14:25:54 +02:00
}
2021-10-17 22:26:43 +02:00
auto & b = ( battery_sel ! = " Auto " and batteries . contains ( battery_sel ) ? batteries . at ( battery_sel ) : batteries . at ( auto_sel ) ) ;
2021-09-17 14:25:54 +02:00
int percent = - 1 ;
long seconds = - 1 ;
//? Try to get battery percentage
2021-10-17 22:26:43 +02:00
if ( b . use_energy ) {
2021-09-17 14:25:54 +02:00
try {
2021-10-17 22:26:43 +02:00
percent = round ( 100.0 * stoll ( readfile ( b . energy_now , " -1 " ) ) / stoll ( readfile ( b . energy_full , " 1 " ) ) ) ;
2021-09-17 14:25:54 +02:00
}
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
}
if ( percent < 0 ) {
try {
2021-10-17 22:26:43 +02:00
percent = stoll ( readfile ( b . base_dir / " capacity " , " -1 " ) ) ;
2021-09-17 14:25:54 +02:00
}
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
}
if ( percent < 0 ) {
has_battery = false ;
return { 0 , 0 , " " } ;
}
//? Get charging/discharging status
2021-10-17 22:26:43 +02:00
string status = str_to_lower ( readfile ( b . base_dir / " status " , " unknown " ) ) ;
if ( status = = " unknown " and not b . online . empty ( ) ) {
const auto online = readfile ( b . online , " 0 " ) ;
2021-09-17 14:25:54 +02:00
if ( online = = " 1 " and percent < 100 ) status = " charging " ;
else if ( online = = " 1 " ) status = " full " ;
else status = " discharging " ;
}
//? Get seconds to empty
if ( not is_in ( status , " charging " , " full " ) ) {
2021-10-17 22:26:43 +02:00
if ( b . use_energy and not b . power_now . empty ( ) ) {
2021-09-17 14:25:54 +02:00
try {
2021-10-17 22:26:43 +02:00
seconds = round ( ( double ) stoll ( readfile ( b . energy_now , " 0 " ) ) / stoll ( readfile ( b . power_now , " 1 " ) ) * 3600 ) ;
2021-09-17 14:25:54 +02:00
}
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
}
2021-10-17 22:26:43 +02:00
if ( seconds < 0 and fs : : exists ( b . base_dir / " time_to_empty " ) ) {
2021-09-17 14:25:54 +02:00
try {
2021-10-17 22:26:43 +02:00
seconds = stoll ( readfile ( b . base_dir / " time_to_empty " , " 0 " ) ) * 60 ;
2021-09-17 14:25:54 +02:00
}
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
}
}
return { percent , seconds , status } ;
}
2023-08-26 20:29:43 +02:00
auto collect ( bool no_update ) - > cpu_info & {
2021-08-03 23:47:46 +02:00
if ( Runner : : stopping or ( no_update and not current_cpu . cpu_percent . at ( " total " ) . empty ( ) ) ) return current_cpu ;
2021-07-29 23:40:56 +02:00
auto & cpu = current_cpu ;
2022-10-27 15:09:00 +02:00
if ( Config : : getB ( " show_cpu_freq " ) )
cpuHz = get_cpuHz ( ) ;
2023-07-05 13:23:11 +02:00
if ( getloadavg ( cpu . load_avg . data ( ) , cpu . load_avg . size ( ) ) < 0 ) {
Logger : : error ( " failed to get load averages " ) ;
}
2021-07-29 23:40:56 +02:00
ifstream cread ;
2021-08-03 23:47:46 +02:00
try {
//? Get cpu total times for all cores from /proc/stat
2022-10-02 15:25:10 +02:00
string cpu_name ;
2021-08-03 23:47:46 +02:00
cread . open ( Shared : : procPath / " stat " ) ;
2022-10-02 15:25:10 +02:00
int i = 0 ;
2022-10-07 20:45:12 +02:00
int target = Shared : : coreCount ;
for ( ; i < = target or ( cread . good ( ) and cread . peek ( ) = = ' c ' ) ; i + + ) {
2022-10-02 15:25:10 +02:00
//? Make sure to add zero value for missing core values if at end of file
2022-10-07 20:45:12 +02:00
if ( ( not cread . good ( ) or cread . peek ( ) ! = ' c ' ) and i < = target ) {
2022-10-02 15:25:10 +02:00
if ( i = = 0 ) throw std : : runtime_error ( " Failed to parse /proc/stat " ) ;
2022-10-07 20:45:12 +02:00
else {
//? Fix container sizes if new cores are detected
while ( cmp_less ( cpu . core_percent . size ( ) , i ) ) {
core_old_totals . push_back ( 0 ) ;
core_old_idles . push_back ( 0 ) ;
2023-07-05 13:23:11 +02:00
cpu . core_percent . emplace_back ( ) ;
2022-10-07 20:45:12 +02:00
}
cpu . core_percent . at ( i - 1 ) . push_back ( 0 ) ;
}
2021-07-29 23:40:56 +02:00
}
2022-10-02 15:25:10 +02:00
else {
if ( i = = 0 ) cread . ignore ( SSmax , ' ' ) ;
else {
cread > > cpu_name ;
int cpuNum = std : : stoi ( cpu_name . substr ( 3 ) ) ;
2022-10-07 20:45:12 +02:00
if ( cpuNum > = target - 1 ) target = cpuNum + ( cread . peek ( ) = = ' c ' ? 2 : 1 ) ;
2022-10-02 15:25:10 +02:00
//? Add zero value for core if core number is missing from /proc/stat
while ( i - 1 < cpuNum ) {
2022-10-07 20:45:12 +02:00
//? Fix container sizes if new cores are detected
while ( cmp_less ( cpu . core_percent . size ( ) , i ) ) {
core_old_totals . push_back ( 0 ) ;
core_old_idles . push_back ( 0 ) ;
2023-07-05 13:23:11 +02:00
cpu . core_percent . emplace_back ( ) ;
2022-10-07 20:45:12 +02:00
}
cpu . core_percent [ i - 1 ] . push_back ( 0 ) ;
2022-10-02 15:25:10 +02:00
if ( cpu . core_percent . at ( i - 1 ) . size ( ) > 40 ) cpu . core_percent . at ( i - 1 ) . pop_front ( ) ;
i + + ;
}
}
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Expected on kernel 2.6.3> : 0=user, 1=nice, 2=system, 3=idle, 4=iowait, 5=irq, 6=softirq, 7=steal, 8=guest, 9=guest_nice
vector < long long > times ;
long long total_sum = 0 ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
for ( uint64_t val ; cread > > val ; total_sum + = val ) {
times . push_back ( val ) ;
}
cread . clear ( ) ;
2023-07-05 13:23:11 +02:00
if ( times . size ( ) < 4 ) throw std : : runtime_error ( " Malformed /proc/stat " ) ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Subtract fields 8-9 and any future unknown fields
2023-05-26 07:26:21 +02:00
const long long totals = max ( 0ll , total_sum - ( times . size ( ) > 8 ? std : : accumulate ( times . begin ( ) + 8 , times . end ( ) , 0ll ) : 0 ) ) ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Add iowait field if present
const long long idles = max ( 0ll , times . at ( 3 ) + ( times . size ( ) > 4 ? times . at ( 4 ) : 0 ) ) ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Calculate values for totals from first line of stat
if ( i = = 0 ) {
const long long calc_totals = max ( 1ll , totals - cpu_old . at ( " totals " ) ) ;
const long long calc_idles = max ( 1ll , idles - cpu_old . at ( " idles " ) ) ;
cpu_old . at ( " totals " ) = totals ;
cpu_old . at ( " idles " ) = idles ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Total usage of cpu
cpu . cpu_percent . at ( " total " ) . push_back ( clamp ( ( long long ) round ( ( double ) ( calc_totals - calc_idles ) * 100 / calc_totals ) , 0ll , 100ll ) ) ;
2021-07-29 23:40:56 +02:00
//? Reduce size if there are more values than needed for graph
2022-10-02 15:25:10 +02:00
while ( cmp_greater ( cpu . cpu_percent . at ( " total " ) . size ( ) , width * 2 ) ) cpu . cpu_percent . at ( " total " ) . pop_front ( ) ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Populate cpu.cpu_percent with all fields from stat
for ( int ii = 0 ; const auto & val : times ) {
cpu . cpu_percent . at ( time_names . at ( ii ) ) . push_back ( clamp ( ( long long ) round ( ( double ) ( val - cpu_old . at ( time_names . at ( ii ) ) ) * 100 / calc_totals ) , 0ll , 100ll ) ) ;
cpu_old . at ( time_names . at ( ii ) ) = val ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
//? Reduce size if there are more values than needed for graph
while ( cmp_greater ( cpu . cpu_percent . at ( time_names . at ( ii ) ) . size ( ) , width * 2 ) ) cpu . cpu_percent . at ( time_names . at ( ii ) ) . pop_front ( ) ;
2021-07-29 23:40:56 +02:00
2022-10-02 15:25:10 +02:00
if ( + + ii = = 10 ) break ;
}
continue ;
}
//? Calculate cpu total for each core
else {
2022-10-07 20:45:12 +02:00
//? Fix container sizes if new cores are detected
while ( cmp_less ( cpu . core_percent . size ( ) , i ) ) {
core_old_totals . push_back ( 0 ) ;
core_old_idles . push_back ( 0 ) ;
2023-07-05 13:23:11 +02:00
cpu . core_percent . emplace_back ( ) ;
2022-10-07 20:45:12 +02:00
}
2022-10-02 15:25:10 +02:00
const long long calc_totals = max ( 0ll , totals - core_old_totals . at ( i - 1 ) ) ;
const long long calc_idles = max ( 0ll , idles - core_old_idles . at ( i - 1 ) ) ;
core_old_totals . at ( i - 1 ) = totals ;
core_old_idles . at ( i - 1 ) = idles ;
cpu . core_percent . at ( i - 1 ) . push_back ( clamp ( ( long long ) round ( ( double ) ( calc_totals - calc_idles ) * 100 / calc_totals ) , 0ll , 100ll ) ) ;
}
2021-07-29 23:40:56 +02:00
}
2022-10-02 15:25:10 +02:00
//? Reduce size if there are more values than needed for graph
if ( cpu . core_percent . at ( i - 1 ) . size ( ) > 40 ) cpu . core_percent . at ( i - 1 ) . pop_front ( ) ;
2021-07-29 23:40:56 +02:00
}
2022-10-02 15:25:10 +02:00
2022-10-07 20:45:12 +02:00
//? Notify main thread to redraw screen if we found more cores than previously detected
if ( cmp_greater ( cpu . core_percent . size ( ) , Shared : : coreCount ) ) {
Logger : : debug ( " Changing CPU max corecount from " + to_string ( Shared : : coreCount ) + " to " + to_string ( cpu . core_percent . size ( ) ) + " . " ) ;
Runner : : coreNum_reset = true ;
Shared : : coreCount = cpu . core_percent . size ( ) ;
while ( cmp_less ( current_cpu . temp . size ( ) , cpu . core_percent . size ( ) + 1 ) ) current_cpu . temp . push_back ( { 0 } ) ;
}
2021-07-29 23:40:56 +02:00
}
2021-08-03 23:47:46 +02:00
catch ( const std : : exception & e ) {
2023-08-26 20:29:43 +02:00
Logger : : debug ( " Cpu::collect() : " + string { e . what ( ) } ) ;
2021-08-03 23:47:46 +02:00
if ( cread . bad ( ) ) throw std : : runtime_error ( " Failed to read /proc/stat " ) ;
2023-08-26 20:29:43 +02:00
else throw std : : runtime_error ( " Cpu::collect() : " + string { e . what ( ) } ) ;
2021-07-29 23:40:56 +02:00
}
2021-08-03 23:47:46 +02:00
if ( Config : : getB ( " check_temp " ) and got_sensors )
update_sensors ( ) ;
2021-09-17 14:25:54 +02:00
if ( Config : : getB ( " show_battery " ) and has_battery )
current_bat = get_battery ( ) ;
2021-07-29 23:40:56 +02:00
return cpu ;
2021-07-18 15:44:32 +02:00
}
2021-07-04 22:02:31 +02:00
}
2023-11-25 20:44:45 +01:00
# ifdef GPU_SUPPORT
2023-05-15 13:58:54 +02:00
namespace Gpu {
//? NVIDIA
namespace Nvml {
bool init ( ) {
2023-05-21 18:02:50 +02:00
if ( initialized ) return false ;
2023-06-01 03:41:56 +02:00
//? Dynamic loading & linking
nvml_dl_handle = dlopen ( " libnvidia-ml.so " , RTLD_LAZY ) ;
if ( ! nvml_dl_handle ) {
Logger : : info ( std : : string ( " Failed to load libnvidia-ml.so, NVIDIA GPUs will not be detected: " ) + dlerror ( ) ) ;
return false ;
}
auto load_nvml_sym = [ & ] ( const char sym_name [ ] ) {
auto sym = dlsym ( nvml_dl_handle , sym_name ) ;
auto err = dlerror ( ) ;
if ( err ! = NULL ) {
Logger : : error ( string ( " NVML: Couldn't find function " ) + sym_name + " : " + err ) ;
return ( void * ) nullptr ;
} else return sym ;
} ;
# define LOAD_SYM(NAME) if ((NAME = (decltype(NAME))load_nvml_sym(#NAME)) == nullptr) return false
LOAD_SYM ( nvmlErrorString ) ;
LOAD_SYM ( nvmlInit ) ;
LOAD_SYM ( nvmlShutdown ) ;
LOAD_SYM ( nvmlDeviceGetCount ) ;
LOAD_SYM ( nvmlDeviceGetHandleByIndex ) ;
LOAD_SYM ( nvmlDeviceGetName ) ;
LOAD_SYM ( nvmlDeviceGetPowerManagementLimit ) ;
LOAD_SYM ( nvmlDeviceGetTemperatureThreshold ) ;
LOAD_SYM ( nvmlDeviceGetUtilizationRates ) ;
LOAD_SYM ( nvmlDeviceGetClockInfo ) ;
LOAD_SYM ( nvmlDeviceGetPowerUsage ) ;
LOAD_SYM ( nvmlDeviceGetPowerState ) ;
LOAD_SYM ( nvmlDeviceGetTemperature ) ;
LOAD_SYM ( nvmlDeviceGetMemoryInfo ) ;
LOAD_SYM ( nvmlDeviceGetPcieThroughput ) ;
# undef LOAD_SYM
//? Function calls
2023-05-15 13:58:54 +02:00
nvmlReturn_t result = nvmlInit ( ) ;
if ( result ! = NVML_SUCCESS ) {
2023-11-25 20:44:45 +01:00
Logger : : debug ( std : : string ( " Failed to initialize NVML, NVIDIA GPUs will not be detected: " ) + nvmlErrorString ( result ) ) ;
2023-05-15 13:58:54 +02:00
return false ;
}
//? Device count
result = nvmlDeviceGetCount ( & device_count ) ;
if ( result ! = NVML_SUCCESS ) {
2023-05-21 18:02:50 +02:00
Logger : : warning ( std : : string ( " NVML: Failed to get device count: " ) + nvmlErrorString ( result ) ) ;
2023-05-15 13:58:54 +02:00
return false ;
}
if ( device_count > 0 ) {
devices . resize ( device_count ) ;
gpus . resize ( device_count ) ;
gpu_names . resize ( device_count ) ;
2023-05-30 18:24:50 +02:00
initialized = true ;
//? Check supported functions & get maximums
Nvml : : collect < 1 > ( gpus . data ( ) ) ;
return true ;
} else { initialized = true ; shutdown ( ) ; return false ; }
}
bool shutdown ( ) {
if ( ! initialized ) return false ;
nvmlReturn_t result = nvmlShutdown ( ) ;
2023-06-01 03:41:56 +02:00
if ( NVML_SUCCESS = = result ) {
2023-05-30 18:24:50 +02:00
initialized = false ;
2023-06-01 03:41:56 +02:00
dlclose ( nvml_dl_handle ) ;
} else Logger : : warning ( std : : string ( " Failed to shutdown NVML: " ) + nvmlErrorString ( result ) ) ;
2023-05-30 18:24:50 +02:00
return ! initialized ;
}
template < bool is_init > // collect<1> is called in Nvml::init(), and populates gpus.supported_functions
bool collect ( gpu_info * gpus_slice ) { // raw pointer to vector data, size == device_count
if ( ! initialized ) return false ;
nvmlReturn_t result ;
2023-06-06 19:47:07 +02:00
std : : thread pcie_tx_thread , pcie_rx_thread ;
// DebugTimer nvTotalTimer("Nvidia Total");
2023-05-30 18:24:50 +02:00
for ( unsigned int i = 0 ; i < device_count ; + + i ) {
if constexpr ( is_init ) {
2023-05-15 13:58:54 +02:00
//? Device Handle
2023-05-30 18:24:50 +02:00
result = nvmlDeviceGetHandleByIndex ( i , devices . data ( ) + i ) ;
2023-05-15 13:58:54 +02:00
if ( result ! = NVML_SUCCESS ) {
2023-05-21 18:02:50 +02:00
Logger : : warning ( std : : string ( " NVML: Failed to get device handle: " ) + nvmlErrorString ( result ) ) ;
gpus [ i ] . supported_functions = { false , false , false , false , false , false , false , false } ;
2023-05-15 19:42:55 +02:00
continue ;
2023-05-15 13:58:54 +02:00
}
//? Device name
char name [ NVML_DEVICE_NAME_BUFFER_SIZE ] ;
result = nvmlDeviceGetName ( devices [ i ] , name , NVML_DEVICE_NAME_BUFFER_SIZE ) ;
2023-05-15 19:42:55 +02:00
if ( result ! = NVML_SUCCESS )
2023-05-21 18:02:50 +02:00
Logger : : warning ( std : : string ( " NVML: Failed to get device name: " ) + nvmlErrorString ( result ) ) ;
2023-05-18 16:07:05 +02:00
else {
gpu_names [ i ] = string ( name ) ;
2023-05-30 18:24:50 +02:00
for ( const auto & brand : { " NVIDIA " , " Nvidia " , " (R) " , " (TM) " } ) {
2023-05-18 16:07:05 +02:00
gpu_names [ i ] = s_replace ( gpu_names [ i ] , brand , " " ) ;
}
gpu_names [ i ] = trim ( gpu_names [ i ] ) ;
}
2023-05-15 13:58:54 +02:00
//? Power usage
2023-05-15 19:42:55 +02:00
unsigned int max_power ;
result = nvmlDeviceGetPowerManagementLimit ( devices [ i ] , & max_power ) ;
if ( result ! = NVML_SUCCESS )
2023-05-21 18:02:50 +02:00
Logger : : warning ( std : : string ( " NVML: Failed to get maximum GPU power draw, defaulting to 225W: " ) + nvmlErrorString ( result ) ) ;
2023-05-30 18:24:50 +02:00
else {
gpus [ i ] . pwr_max_usage = max_power ; // RSMI reports power in microWatts
gpu_pwr_total_max + = max_power ;
}
2023-05-15 13:58:54 +02:00
//? Get temp_max
2023-05-15 19:42:55 +02:00
unsigned int temp_max ;
2023-05-15 13:58:54 +02:00
result = nvmlDeviceGetTemperatureThreshold ( devices [ i ] , NVML_TEMPERATURE_THRESHOLD_SHUTDOWN , & temp_max ) ;
2023-05-15 19:42:55 +02:00
if ( result ! = NVML_SUCCESS )
2023-05-21 18:02:50 +02:00
Logger : : warning ( std : : string ( " NVML: Failed to get maximum GPU temperature, defaulting to 110°C: " ) + nvmlErrorString ( result ) ) ;
2023-05-15 19:42:55 +02:00
else gpus [ i ] . temp_max = ( long long ) temp_max ;
2023-05-15 13:58:54 +02:00
}
2023-05-21 18:02:50 +02:00
2023-06-06 19:47:07 +02:00
//? PCIe link speeds, the data collection takes >=20ms each call so they run on separate threads
2023-06-26 13:10:31 +02:00
if ( gpus_slice [ i ] . supported_functions . pcie_txrx and ( Config : : getB ( " nvml_measure_pcie_speeds " ) or is_init ) ) {
2023-06-06 19:47:07 +02:00
pcie_tx_thread = std : : thread ( [ gpus_slice , i ] ( ) {
unsigned int tx ;
nvmlReturn_t result = nvmlDeviceGetPcieThroughput ( devices [ i ] , NVML_PCIE_UTIL_TX_BYTES , & tx ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get PCIe TX throughput: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . pcie_txrx = false ;
} else gpus_slice [ i ] . pcie_tx = ( long long ) tx ;
} ) ;
pcie_rx_thread = std : : thread ( [ gpus_slice , i ] ( ) {
unsigned int rx ;
nvmlReturn_t result = nvmlDeviceGetPcieThroughput ( devices [ i ] , NVML_PCIE_UTIL_RX_BYTES , & rx ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get PCIe RX throughput: " ) + nvmlErrorString ( result ) ) ;
} else gpus_slice [ i ] . pcie_rx = ( long long ) rx ;
} ) ;
}
// DebugTimer nvTimer("Nv utilization");
2023-05-15 13:58:54 +02:00
//? GPU & memory utilization
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . gpu_utilization ) {
nvmlUtilization_t utilization ;
result = nvmlDeviceGetUtilizationRates ( devices [ i ] , & utilization ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get GPU utilization: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . gpu_utilization = false ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_utilization = false ;
} else {
2023-05-30 18:24:50 +02:00
gpus_slice [ i ] . gpu_percent . at ( " gpu-totals " ) . push_back ( ( long long ) utilization . gpu ) ;
2023-05-21 18:02:50 +02:00
gpus_slice [ i ] . mem_utilization_percent . push_back ( ( long long ) utilization . memory ) ;
}
}
2023-05-15 13:58:54 +02:00
2023-05-21 13:52:19 +02:00
// nvTimer.stop_rename_reset("Nv clock");
2023-05-15 13:58:54 +02:00
//? Clock speeds
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . gpu_clock ) {
unsigned int gpu_clock ;
result = nvmlDeviceGetClockInfo ( devices [ i ] , NVML_CLOCK_GRAPHICS , & gpu_clock ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get GPU clock speed: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . gpu_clock = false ;
} else gpus_slice [ i ] . gpu_clock_speed = ( long long ) gpu_clock ;
}
2023-05-15 19:42:55 +02:00
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_clock ) {
unsigned int mem_clock ;
result = nvmlDeviceGetClockInfo ( devices [ i ] , NVML_CLOCK_MEM , & mem_clock ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get VRAM clock speed: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_clock = false ;
} else gpus_slice [ i ] . mem_clock_speed = ( long long ) mem_clock ;
}
2023-05-15 13:58:54 +02:00
2023-05-21 13:52:19 +02:00
// nvTimer.stop_rename_reset("Nv power");
2023-05-15 13:58:54 +02:00
//? Power usage & state
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . pwr_usage ) {
unsigned int power ;
result = nvmlDeviceGetPowerUsage ( devices [ i ] , & power ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get GPU power usage: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . pwr_usage = false ;
} else {
gpus_slice [ i ] . pwr_usage = ( long long ) power ;
2023-05-30 18:24:50 +02:00
gpus_slice [ i ] . gpu_percent . at ( " gpu-pwr-totals " ) . push_back ( clamp ( ( long long ) round ( ( double ) gpus_slice [ i ] . pwr_usage * 100.0 / ( double ) gpus_slice [ i ] . pwr_max_usage ) , 0ll , 100ll ) ) ;
2023-05-21 18:02:50 +02:00
}
2023-05-15 13:58:54 +02:00
}
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . pwr_state ) {
nvmlPstates_t pState ;
result = nvmlDeviceGetPowerState ( devices [ i ] , & pState ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get GPU power state: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . pwr_state = false ;
} else gpus_slice [ i ] . pwr_state = static_cast < int > ( pState ) ;
}
2023-05-15 13:58:54 +02:00
2023-05-21 13:52:19 +02:00
// nvTimer.stop_rename_reset("Nv temp");
2023-05-15 13:58:54 +02:00
//? GPU temperature
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . temp_info ) {
if ( Config : : getB ( " check_temp " ) ) {
unsigned int temp ;
nvmlReturn_t result = nvmlDeviceGetTemperature ( devices [ i ] , NVML_TEMPERATURE_GPU , & temp ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get GPU temperature: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . temp_info = false ;
} else gpus_slice [ i ] . temp . push_back ( ( long long ) temp ) ;
}
2023-05-15 13:58:54 +02:00
}
2023-05-21 13:52:19 +02:00
// nvTimer.stop_rename_reset("Nv mem");
2023-05-15 13:58:54 +02:00
//? Memory info
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_total ) {
nvmlMemory_t memory ;
result = nvmlDeviceGetMemoryInfo ( devices [ i ] , & memory ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get VRAM info: " ) + nvmlErrorString ( result ) ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_total = false ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_used = false ;
} else {
gpus_slice [ i ] . mem_total = memory . total ;
gpus_slice [ i ] . mem_used = memory . used ;
//gpu.mem_free = memory.free;
auto used_percent = ( long long ) round ( ( double ) memory . used * 100.0 / ( double ) memory . total ) ;
2023-05-30 18:24:50 +02:00
gpus_slice [ i ] . gpu_percent . at ( " gpu-vram-totals " ) . push_back ( used_percent ) ;
2023-05-21 18:02:50 +02:00
}
2023-05-15 13:58:54 +02:00
}
2023-05-21 18:02:50 +02:00
//? TODO: Processes using GPU
/*unsigned int proc_info_len;
nvmlProcessInfo_t * proc_info = 0 ;
result = nvmlDeviceGetComputeRunningProcesses_v3 ( device , & proc_info_len , proc_info ) ;
if ( result ! = NVML_SUCCESS ) {
Logger : : warning ( std : : string ( " NVML: Failed to get compute processes: " ) + nvmlErrorString ( result ) ) ;
} else {
for ( unsigned int i = 0 ; i < proc_info_len ; + + i )
gpus_slice [ i ] . graphics_processes . push_back ( { proc_info [ i ] . pid , proc_info [ i ] . usedGpuMemory } ) ;
} */
2023-06-06 19:47:07 +02:00
// nvTimer.stop_rename_reset("Nv pcie thread join");
//? Join PCIE TX/RX threads
2023-06-08 20:24:01 +02:00
if constexpr ( is_init ) { // there doesn't seem to be a better way to do this, but this should be fine considering it's just 2 lines
pcie_tx_thread . join ( ) ;
pcie_rx_thread . join ( ) ;
2023-06-26 13:10:31 +02:00
} else if ( gpus_slice [ i ] . supported_functions . pcie_txrx and Config : : getB ( " nvml_measure_pcie_speeds " ) ) {
2023-06-08 20:24:01 +02:00
pcie_tx_thread . join ( ) ;
pcie_rx_thread . join ( ) ;
}
2023-05-15 13:58:54 +02:00
}
return true ;
}
}
2023-05-15 19:42:55 +02:00
//? AMD
namespace Rsmi {
bool init ( ) {
2023-05-21 18:02:50 +02:00
if ( initialized ) return false ;
2023-05-15 19:42:55 +02:00
2023-06-02 15:34:12 +02:00
//? Dynamic loading & linking
# if !defined(RSMI_STATIC)
rsmi_dl_handle = dlopen ( " /opt/rocm/lib/librocm_smi64.so " , RTLD_LAZY ) ; // first try /lib and /usr/lib, then /opt/rocm/lib if that fails
if ( dlerror ( ) ! = NULL ) {
rsmi_dl_handle = dlopen ( " librocm_smi64.so " , RTLD_LAZY ) ;
2023-11-25 20:44:45 +01:00
}
if ( ! rsmi_dl_handle ) {
Logger : : debug ( std : : string ( " Failed to load librocm_smi64.so, AMD GPUs will not be detected: " ) + dlerror ( ) ) ;
return false ;
2023-06-02 15:34:12 +02:00
}
auto load_rsmi_sym = [ & ] ( const char sym_name [ ] ) {
auto sym = dlsym ( rsmi_dl_handle , sym_name ) ;
auto err = dlerror ( ) ;
if ( err ! = NULL ) {
Logger : : error ( string ( " ROCm SMI: Couldn't find function " ) + sym_name + " : " + err ) ;
return ( void * ) nullptr ;
} else return sym ;
} ;
# define LOAD_SYM(NAME) if ((NAME = (decltype(NAME))load_rsmi_sym(#NAME)) == nullptr) return false
LOAD_SYM ( rsmi_init ) ;
LOAD_SYM ( rsmi_shut_down ) ;
LOAD_SYM ( rsmi_num_monitor_devices ) ;
LOAD_SYM ( rsmi_dev_name_get ) ;
LOAD_SYM ( rsmi_dev_power_cap_get ) ;
LOAD_SYM ( rsmi_dev_temp_metric_get ) ;
LOAD_SYM ( rsmi_dev_busy_percent_get ) ;
LOAD_SYM ( rsmi_dev_memory_busy_percent_get ) ;
LOAD_SYM ( rsmi_dev_gpu_clk_freq_get ) ;
LOAD_SYM ( rsmi_dev_power_ave_get ) ;
LOAD_SYM ( rsmi_dev_memory_total_get ) ;
LOAD_SYM ( rsmi_dev_memory_usage_get ) ;
LOAD_SYM ( rsmi_dev_pci_throughput_get ) ;
# undef LOAD_SYM
# endif
//? Function calls
rsmi_status_t result = rsmi_init ( 0 ) ;
2023-05-21 13:52:19 +02:00
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : debug ( " Failed to initialize ROCm SMI, AMD GPUs will not be detected " ) ;
return false ;
}
2023-05-15 19:42:55 +02:00
//? Device count
result = rsmi_num_monitor_devices ( & device_count ) ;
2023-05-21 13:52:19 +02:00
if ( result ! = RSMI_STATUS_SUCCESS ) {
2023-05-21 18:02:50 +02:00
Logger : : warning ( " ROCm SMI: Failed to fetch number of devices " ) ;
2023-05-21 13:52:19 +02:00
return false ;
}
2023-05-15 19:42:55 +02:00
if ( device_count > 0 ) {
gpus . resize ( gpus . size ( ) + device_count ) ;
gpu_names . resize ( gpus . size ( ) + device_count ) ;
initialized = true ;
2023-05-30 18:24:50 +02:00
//? Check supported functions & get maximums
2023-05-21 18:02:50 +02:00
Rsmi : : collect < 1 > ( gpus . data ( ) + Nvml : : device_count ) ;
2023-05-15 19:42:55 +02:00
return true ;
} else { initialized = true ; shutdown ( ) ; return false ; }
}
bool shutdown ( ) {
2023-05-21 18:02:50 +02:00
if ( ! initialized ) return false ;
2023-06-02 15:34:12 +02:00
if ( rsmi_shut_down ( ) = = RSMI_STATUS_SUCCESS ) {
2023-05-15 19:42:55 +02:00
initialized = false ;
2023-06-02 15:34:12 +02:00
# if !defined(RSMI_STATIC)
dlclose ( rsmi_dl_handle ) ;
# endif
} else Logger : : warning ( " Failed to shutdown ROCm SMI " ) ;
2023-05-15 19:42:55 +02:00
return true ;
}
2023-05-21 18:02:50 +02:00
template < bool is_init >
2023-05-15 19:42:55 +02:00
bool collect ( gpu_info * gpus_slice ) { // raw pointer to vector data, size == device_count, offset by Nvml::device_count elements
2023-05-21 18:02:50 +02:00
if ( ! initialized ) return false ;
2023-05-15 19:42:55 +02:00
rsmi_status_t result ;
for ( uint32_t i = 0 ; i < device_count ; + + i ) {
2023-05-30 18:24:50 +02:00
if constexpr ( is_init ) {
//? Device name
char name [ NVML_DEVICE_NAME_BUFFER_SIZE ] ; // ROCm SMI does not provide a constant for this as far as I can tell, this should be good enough
result = rsmi_dev_name_get ( i , name , NVML_DEVICE_NAME_BUFFER_SIZE ) ;
if ( result ! = RSMI_STATUS_SUCCESS )
Logger : : warning ( " ROCm SMI: Failed to get device name " ) ;
else gpu_names [ Nvml : : device_count + i ] = string ( name ) ;
//? Power usage
uint64_t max_power ;
result = rsmi_dev_power_cap_get ( i , 0 , & max_power ) ;
if ( result ! = RSMI_STATUS_SUCCESS )
Logger : : warning ( " ROCm SMI: Failed to get maximum GPU power draw, defaulting to 225W " ) ;
else {
gpus_slice [ i ] . pwr_max_usage = ( long long ) ( max_power / 1000 ) ; // RSMI reports power in microWatts
gpu_pwr_total_max + = gpus_slice [ i ] . pwr_max_usage ;
}
//? Get temp_max
int64_t temp_max ;
result = rsmi_dev_temp_metric_get ( i , RSMI_TEMP_TYPE_EDGE , RSMI_TEMP_MAX , & temp_max ) ;
if ( result ! = RSMI_STATUS_SUCCESS )
Logger : : warning ( " ROCm SMI: Failed to get maximum GPU temperature, defaulting to 110°C " ) ;
else gpus_slice [ i ] . temp_max = ( long long ) temp_max ;
}
2023-05-15 19:42:55 +02:00
//? GPU utilization
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . gpu_utilization ) {
uint32_t utilization ;
result = rsmi_dev_busy_percent_get ( i , & utilization ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get GPU utilization " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . gpu_utilization = false ;
2023-05-30 18:24:50 +02:00
} else gpus_slice [ i ] . gpu_percent . at ( " gpu-totals " ) . push_back ( ( long long ) utilization ) ;
2023-05-21 18:02:50 +02:00
}
2023-05-15 19:42:55 +02:00
//? Memory utilization
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_utilization ) {
uint32_t utilization ;
result = rsmi_dev_memory_busy_percent_get ( i , & utilization ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get VRAM utilization " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_utilization = false ;
} else gpus_slice [ i ] . mem_utilization_percent . push_back ( ( long long ) utilization ) ;
}
2023-05-15 19:42:55 +02:00
//? Clock speeds
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . gpu_clock ) {
rsmi_frequencies_t frequencies ;
result = rsmi_dev_gpu_clk_freq_get ( i , RSMI_CLK_TYPE_SYS , & frequencies ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get GPU clock speed: " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . gpu_clock = false ;
} else gpus_slice [ i ] . gpu_clock_speed = ( long long ) frequencies . frequency [ frequencies . current ] / 1000000 ; // Hz to MHz
}
2023-05-15 19:42:55 +02:00
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_clock ) {
rsmi_frequencies_t frequencies ;
result = rsmi_dev_gpu_clk_freq_get ( i , RSMI_CLK_TYPE_MEM , & frequencies ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get VRAM clock speed: " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_clock = false ;
} else gpus_slice [ i ] . mem_clock_speed = ( long long ) frequencies . frequency [ frequencies . current ] / 1000000 ; // Hz to MHz
}
2023-05-15 19:42:55 +02:00
//? Power usage & state
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . pwr_usage ) {
uint64_t power ;
result = rsmi_dev_power_ave_get ( i , 0 , & power ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get GPU power usage " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . pwr_usage = false ;
2023-05-30 18:24:50 +02:00
} else gpus_slice [ i ] . gpu_percent . at ( " gpu-pwr-totals " ) . push_back ( clamp ( ( long long ) round ( ( double ) gpus_slice [ i ] . pwr_usage * 100.0 / ( double ) gpus_slice [ i ] . pwr_max_usage ) , 0ll , 100ll ) ) ;
2023-05-21 18:02:50 +02:00
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . pwr_state = false ;
}
2023-05-15 19:42:55 +02:00
//? GPU temperature
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . temp_info ) {
if ( Config : : getB ( " check_temp " ) or is_init ) {
int64_t temp ;
result = rsmi_dev_temp_metric_get ( i , RSMI_TEMP_TYPE_EDGE , RSMI_TEMP_CURRENT , & temp ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get GPU temperature " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . temp_info = false ;
} else gpus_slice [ i ] . temp . push_back ( ( long long ) temp / 1000 ) ;
}
2023-05-15 19:42:55 +02:00
}
//? Memory info
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_total ) {
uint64_t total ;
result = rsmi_dev_memory_total_get ( i , RSMI_MEM_TYPE_VRAM , & total ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get total VRAM " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_total = false ;
} else gpus_slice [ i ] . mem_total = total ;
}
2023-05-15 19:42:55 +02:00
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_used ) {
uint64_t used ;
2023-05-15 19:42:55 +02:00
result = rsmi_dev_memory_usage_get ( i , RSMI_MEM_TYPE_VRAM , & used ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
2023-05-21 18:02:50 +02:00
Logger : : warning ( " ROCm SMI: Failed to get VRAM usage " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . mem_used = false ;
2023-05-15 19:42:55 +02:00
} else {
gpus_slice [ i ] . mem_used = used ;
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . mem_total )
2023-05-30 18:24:50 +02:00
gpus_slice [ i ] . gpu_percent . at ( " gpu-vram-totals " ) . push_back ( ( long long ) round ( ( double ) used * 100.0 / ( double ) gpus_slice [ i ] . mem_total ) ) ;
2023-05-15 19:42:55 +02:00
}
}
//? PCIe link speeds
2023-05-21 18:02:50 +02:00
if ( gpus_slice [ i ] . supported_functions . pcie_txrx ) {
uint64_t tx , rx ;
result = rsmi_dev_pci_throughput_get ( i , & tx , & rx , 0 ) ;
if ( result ! = RSMI_STATUS_SUCCESS ) {
Logger : : warning ( " ROCm SMI: Failed to get PCIe throughput " ) ;
if constexpr ( is_init ) gpus_slice [ i ] . supported_functions . pcie_txrx = false ;
} else {
gpus_slice [ i ] . pcie_tx = ( long long ) tx ;
gpus_slice [ i ] . pcie_rx = ( long long ) rx ;
}
2023-05-15 19:42:55 +02:00
}
}
return true ;
}
}
2023-05-15 13:58:54 +02:00
// TODO: Intel
//? Collect data from GPU-specific libraries
auto collect ( bool no_update ) - > vector < gpu_info > & {
if ( Runner : : stopping or ( no_update and not gpus . empty ( ) ) ) return gpus ;
2023-05-21 13:52:19 +02:00
// DebugTimer gpu_timer("GPU Total");
2023-05-15 19:42:55 +02:00
//* Collect data
2023-05-21 18:02:50 +02:00
Nvml : : collect < 0 > ( gpus . data ( ) ) ; // raw pointer to vector data, size == Nvml::device_count
Rsmi : : collect < 0 > ( gpus . data ( ) + Nvml : : device_count ) ; // size = Rsmi::device_count
2023-05-15 19:42:55 +02:00
2023-05-18 16:07:05 +02:00
//* Calculate average usage
long long avg = 0 ;
2023-05-30 18:24:50 +02:00
long long mem_usage_total = 0 ;
long long mem_total = 0 ;
long long pwr_total = 0 ;
2023-05-15 19:42:55 +02:00
for ( auto & gpu : gpus ) {
2023-05-21 18:02:50 +02:00
if ( gpu . supported_functions . gpu_utilization )
2023-05-30 18:24:50 +02:00
avg + = gpu . gpu_percent . at ( " gpu-totals " ) . back ( ) ;
if ( gpu . supported_functions . mem_used )
mem_usage_total + = gpu . mem_used ;
if ( gpu . supported_functions . mem_total )
mem_total + = gpu . mem_total ;
if ( gpu . supported_functions . pwr_usage )
mem_total + = gpu . pwr_usage ;
2023-05-18 16:07:05 +02:00
//* Trim vectors if there are more values than needed for graphs
if ( width ! = 0 ) {
//? GPU & memory utilization
2023-05-30 18:24:50 +02:00
while ( cmp_greater ( gpu . gpu_percent . at ( " gpu-totals " ) . size ( ) , width * 2 ) ) gpu . gpu_percent . at ( " gpu-totals " ) . pop_front ( ) ;
while ( cmp_greater ( gpu . mem_utilization_percent . size ( ) , width ) ) gpu . mem_utilization_percent . pop_front ( ) ;
2023-05-18 16:07:05 +02:00
//? Power usage
2023-05-30 18:24:50 +02:00
while ( cmp_greater ( gpu . gpu_percent . at ( " gpu-pwr-totals " ) . size ( ) , width ) ) gpu . gpu_percent . at ( " gpu-pwr-totals " ) . pop_front ( ) ;
2023-05-18 16:07:05 +02:00
//? Temperature
while ( cmp_greater ( gpu . temp . size ( ) , 18 ) ) gpu . temp . pop_front ( ) ;
//? Memory usage
2023-05-30 18:24:50 +02:00
while ( cmp_greater ( gpu . gpu_percent . at ( " gpu-vram-totals " ) . size ( ) , width / 2 ) ) gpu . gpu_percent . at ( " gpu-vram-totals " ) . pop_front ( ) ;
2023-05-18 16:07:05 +02:00
}
2023-05-15 19:42:55 +02:00
}
2023-05-18 16:07:05 +02:00
2023-05-30 18:24:50 +02:00
shared_gpu_percent . at ( " gpu-average " ) . push_back ( avg / gpus . size ( ) ) ;
if ( mem_total ! = 0 )
shared_gpu_percent . at ( " gpu-vram-total " ) . push_back ( mem_usage_total / mem_total ) ;
if ( gpu_pwr_total_max ! = 0 )
shared_gpu_percent . at ( " gpu-pwr-total " ) . push_back ( pwr_total / gpu_pwr_total_max ) ;
if ( width ! = 0 ) {
while ( cmp_greater ( shared_gpu_percent . at ( " gpu-average " ) . size ( ) , width * 2 ) ) shared_gpu_percent . at ( " gpu-average " ) . pop_front ( ) ;
while ( cmp_greater ( shared_gpu_percent . at ( " gpu-pwr-total " ) . size ( ) , width * 2 ) ) shared_gpu_percent . at ( " gpu-pwr-total " ) . pop_front ( ) ;
while ( cmp_greater ( shared_gpu_percent . at ( " gpu-vram-total " ) . size ( ) , width * 2 ) ) shared_gpu_percent . at ( " gpu-vram-total " ) . pop_front ( ) ;
}
2023-05-15 13:58:54 +02:00
return gpus ;
}
}
2023-11-25 20:44:45 +01:00
# endif
2023-05-15 13:58:54 +02:00
2021-07-04 22:02:31 +02:00
namespace Mem {
2023-08-26 20:29:43 +02:00
bool has_swap { } ; // defaults to false
2021-08-10 20:20:33 +02:00
vector < string > fstab ;
fs : : file_time_type fstab_time ;
2023-08-26 20:29:43 +02:00
int disk_ios { } ; // defaults to 0
2021-08-17 22:33:21 +02:00
vector < string > last_found ;
2021-08-10 20:20:33 +02:00
2022-07-04 23:20:33 +02:00
//?* Find the filepath to the specified ZFS object's stat file
2022-07-11 18:08:25 +02:00
fs : : path get_zfs_stat_file ( const string & device_name , size_t dataset_name_start , bool zfs_hide_datasets ) ;
2022-07-04 23:20:33 +02:00
//?* Collect total ZFS pool io stats
bool zfs_collect_pool_total_stats ( struct disk_info & disk ) ;
2021-08-10 20:20:33 +02:00
mem_info current_mem { } ;
2021-07-18 15:44:32 +02:00
2021-10-05 09:18:04 +02:00
uint64_t get_totalMem ( ) {
ifstream meminfo ( Shared : : procPath / " meminfo " ) ;
int64_t totalMem ;
if ( meminfo . good ( ) ) {
meminfo . ignore ( SSmax , ' : ' ) ;
meminfo > > totalMem ;
totalMem < < = 10 ;
}
if ( not meminfo . good ( ) or totalMem = = 0 )
throw std : : runtime_error ( " Could not get total memory size from /proc/meminfo " ) ;
2021-10-06 18:06:05 +02:00
2021-10-05 09:18:04 +02:00
return totalMem ;
}
2023-08-26 20:29:43 +02:00
auto collect ( bool no_update ) - > mem_info & {
2021-08-23 17:35:27 +02:00
if ( Runner : : stopping or ( no_update and not current_mem . percent . at ( " used " ) . empty ( ) ) ) return current_mem ;
2023-08-26 20:29:43 +02:00
auto show_swap = Config : : getB ( " show_swap " ) ;
auto swap_disk = Config : : getB ( " swap_disk " ) ;
auto show_disks = Config : : getB ( " show_disks " ) ;
auto zfs_arc_cached = Config : : getB ( " zfs_arc_cached " ) ;
2021-10-05 09:18:04 +02:00
auto totalMem = get_totalMem ( ) ;
2021-08-10 20:20:33 +02:00
auto & mem = current_mem ;
mem . stats . at ( " swap_total " ) = 0 ;
2022-04-30 05:08:00 +02:00
//? Read ZFS ARC info from /proc/spl/kstat/zfs/arcstats
2023-02-23 17:59:10 +01:00
uint64_t arc_size = 0 , arc_min_size = 0 ;
2022-04-30 05:08:00 +02:00
if ( zfs_arc_cached ) {
ifstream arcstats ( Shared : : procPath / " spl/kstat/zfs/arcstats " ) ;
if ( arcstats . good ( ) ) {
2023-02-23 17:59:10 +01:00
for ( string label ; arcstats > > label ; ) {
if ( label = = " c_min " ) {
arcstats > > arc_min_size > > arc_min_size ; // double read skips type column
}
else if ( label = = " size " ) {
arcstats > > arc_size > > arc_size ;
break ;
2022-04-30 05:08:00 +02:00
}
}
}
arcstats . close ( ) ;
}
2021-08-10 20:20:33 +02:00
//? Read memory info from /proc/meminfo
ifstream meminfo ( Shared : : procPath / " meminfo " ) ;
if ( meminfo . good ( ) ) {
bool got_avail = false ;
2021-11-22 21:43:40 +01:00
for ( string label ; meminfo . peek ( ) ! = ' D ' and meminfo > > label ; ) {
2021-08-10 20:20:33 +02:00
if ( label = = " MemFree: " ) {
meminfo > > mem . stats . at ( " free " ) ;
mem . stats . at ( " free " ) < < = 10 ;
}
else if ( label = = " MemAvailable: " ) {
meminfo > > mem . stats . at ( " available " ) ;
mem . stats . at ( " available " ) < < = 10 ;
got_avail = true ;
}
else if ( label = = " Cached: " ) {
meminfo > > mem . stats . at ( " cached " ) ;
mem . stats . at ( " cached " ) < < = 10 ;
if ( not show_swap and not swap_disk ) break ;
}
else if ( label = = " SwapTotal: " ) {
meminfo > > mem . stats . at ( " swap_total " ) ;
mem . stats . at ( " swap_total " ) < < = 10 ;
}
else if ( label = = " SwapFree: " ) {
meminfo > > mem . stats . at ( " swap_free " ) ;
mem . stats . at ( " swap_free " ) < < = 10 ;
break ;
}
meminfo . ignore ( SSmax , ' \n ' ) ;
}
if ( not got_avail ) mem . stats . at ( " available " ) = mem . stats . at ( " free " ) + mem . stats . at ( " cached " ) ;
2022-04-30 05:08:00 +02:00
if ( zfs_arc_cached ) {
mem . stats . at ( " cached " ) + = arc_size ;
2023-02-23 17:59:10 +01:00
// The ARC will not shrink below arc_min_size, so that memory is not available
if ( arc_size > arc_min_size )
mem . stats . at ( " available " ) + = arc_size - arc_min_size ;
2022-04-30 05:08:00 +02:00
}
2022-11-30 22:41:08 +01:00
mem . stats . at ( " used " ) = totalMem - ( mem . stats . at ( " available " ) < = totalMem ? mem . stats . at ( " available " ) : mem . stats . at ( " free " ) ) ;
2022-11-30 23:18:04 +01:00
2021-08-10 20:20:33 +02:00
if ( mem . stats . at ( " swap_total " ) > 0 ) mem . stats . at ( " swap_used " ) = mem . stats . at ( " swap_total " ) - mem . stats . at ( " swap_free " ) ;
}
else
throw std : : runtime_error ( " Failed to read /proc/meminfo " ) ;
meminfo . close ( ) ;
//? Calculate percentages
for ( const auto & name : mem_names ) {
2021-10-05 09:18:04 +02:00
mem . percent . at ( name ) . push_back ( round ( ( double ) mem . stats . at ( name ) * 100 / totalMem ) ) ;
2021-08-17 22:33:21 +02:00
while ( cmp_greater ( mem . percent . at ( name ) . size ( ) , width * 2 ) ) mem . percent . at ( name ) . pop_front ( ) ;
2021-08-10 20:20:33 +02:00
}
if ( show_swap and mem . stats . at ( " swap_total " ) > 0 ) {
for ( const auto & name : swap_names ) {
mem . percent . at ( name ) . push_back ( round ( ( double ) mem . stats . at ( name ) * 100 / mem . stats . at ( " swap_total " ) ) ) ;
2021-08-17 22:33:21 +02:00
while ( cmp_greater ( mem . percent . at ( name ) . size ( ) , width * 2 ) ) mem . percent . at ( name ) . pop_front ( ) ;
2021-08-10 20:20:33 +02:00
}
has_swap = true ;
}
else
has_swap = false ;
//? Get disks stats
2021-08-17 22:33:21 +02:00
if ( show_disks ) {
2022-10-23 19:33:04 +02:00
static vector < string > ignore_list ;
2021-09-01 21:40:13 +02:00
double uptime = system_uptime ( ) ;
2022-02-13 00:33:20 +01:00
auto free_priv = Config : : getB ( " disk_free_priv " ) ;
2021-08-10 20:20:33 +02:00
try {
auto & disks_filter = Config : : getS ( " disks_filter " ) ;
bool filter_exclude = false ;
2023-08-26 20:29:43 +02:00
auto use_fstab = Config : : getB ( " use_fstab " ) ;
auto only_physical = Config : : getB ( " only_physical " ) ;
auto zfs_hide_datasets = Config : : getB ( " zfs_hide_datasets " ) ;
2021-08-10 20:20:33 +02:00
auto & disks = mem . disks ;
2023-11-25 21:01:11 +01:00
static unordered_flat_map < string , future < pair < disk_info , int > > > disks_stats_promises ;
2021-08-10 20:20:33 +02:00
ifstream diskread ;
vector < string > filter ;
if ( not disks_filter . empty ( ) ) {
filter = ssplit ( disks_filter ) ;
if ( filter . at ( 0 ) . starts_with ( " exclude= " ) ) {
filter_exclude = true ;
filter . at ( 0 ) = filter . at ( 0 ) . substr ( 8 ) ;
}
}
//? Get list of "real" filesystems from /proc/filesystems
vector < string > fstypes ;
if ( only_physical and not use_fstab ) {
fstypes = { " zfs " , " wslfs " , " drvfs " } ;
diskread . open ( Shared : : procPath / " filesystems " ) ;
if ( diskread . good ( ) ) {
for ( string fstype ; diskread > > fstype ; ) {
if ( not is_in ( fstype , " nodev " , " squashfs " , " nullfs " ) )
fstypes . push_back ( fstype ) ;
diskread . ignore ( SSmax , ' \n ' ) ;
}
}
else
throw std : : runtime_error ( " Failed to read /proc/filesystems " ) ;
diskread . close ( ) ;
}
//? Get disk list to use from fstab if enabled
if ( use_fstab and fs : : last_write_time ( " /etc/fstab " ) ! = fstab_time ) {
fstab . clear ( ) ;
fstab_time = fs : : last_write_time ( " /etc/fstab " ) ;
diskread . open ( " /etc/fstab " ) ;
if ( diskread . good ( ) ) {
for ( string instr ; diskread > > instr ; ) {
if ( not instr . starts_with ( ' # ' ) ) {
diskread > > instr ;
2021-10-06 11:25:10 +02:00
# ifdef SNAPPED
if ( instr = = " / " ) fstab . push_back ( " /mnt " ) ;
else if ( not is_in ( instr , " none " , " swap " ) ) fstab . push_back ( instr ) ;
# else
if ( not is_in ( instr , " none " , " swap " ) ) fstab . push_back ( instr ) ;
# endif
2021-08-10 20:20:33 +02:00
}
diskread . ignore ( SSmax , ' \n ' ) ;
}
}
else
throw std : : runtime_error ( " Failed to read /etc/fstab " ) ;
diskread . close ( ) ;
}
//? Get mounts from /etc/mtab or /proc/self/mounts
diskread . open ( ( fs : : exists ( " /etc/mtab " ) ? fs : : path ( " /etc/mtab " ) : Shared : : procPath / " self/mounts " ) ) ;
if ( diskread . good ( ) ) {
2021-08-17 22:33:21 +02:00
vector < string > found ;
found . reserve ( last_found . size ( ) ) ;
2021-08-10 20:20:33 +02:00
string dev , mountpoint , fstype ;
while ( not diskread . eof ( ) ) {
std : : error_code ec ;
diskread > > dev > > mountpoint > > fstype ;
2022-02-17 22:28:10 +01:00
diskread . ignore ( SSmax , ' \n ' ) ;
2022-10-23 19:33:04 +02:00
if ( v_contains ( ignore_list , mountpoint ) or v_contains ( found , mountpoint ) ) continue ;
2021-08-10 20:20:33 +02:00
//? Match filter if not empty
if ( not filter . empty ( ) ) {
bool match = v_contains ( filter , mountpoint ) ;
if ( ( filter_exclude and match ) or ( not filter_exclude and not match ) )
continue ;
}
2022-07-11 18:08:25 +02:00
//? Skip ZFS datasets if zfs_hide_datasets option is enabled
2022-07-03 20:04:05 +02:00
size_t zfs_dataset_name_start = 0 ;
2022-07-11 18:08:25 +02:00
if ( fstype = = " zfs " & & ( zfs_dataset_name_start = dev . find ( ' / ' ) ) ! = std : : string : : npos & & zfs_hide_datasets ) continue ;
2022-07-03 20:04:05 +02:00
2021-08-10 20:20:33 +02:00
if ( ( not use_fstab and not only_physical )
or ( use_fstab and v_contains ( fstab , mountpoint ) )
or ( not use_fstab and only_physical and v_contains ( fstypes , fstype ) ) ) {
found . push_back ( mountpoint ) ;
2021-08-17 22:33:21 +02:00
if ( not v_contains ( last_found , mountpoint ) ) redraw = true ;
2021-08-10 20:20:33 +02:00
2022-06-25 17:12:35 +02:00
//? Save mountpoint, name, fstype, dev path and path to /sys/block stat file
2021-08-10 20:20:33 +02:00
if ( not disks . contains ( mountpoint ) ) {
2022-06-25 17:12:35 +02:00
disks [ mountpoint ] = disk_info { fs : : canonical ( dev , ec ) , fs : : path ( mountpoint ) . filename ( ) , fstype } ;
2021-08-10 20:20:33 +02:00
if ( disks . at ( mountpoint ) . dev . empty ( ) ) disks . at ( mountpoint ) . dev = dev ;
2021-10-06 11:25:10 +02:00
# ifdef SNAPPED
if ( mountpoint = = " /mnt " ) disks . at ( mountpoint ) . name = " root " ;
# endif
if ( disks . at ( mountpoint ) . name . empty ( ) ) disks . at ( mountpoint ) . name = ( mountpoint = = " / " ? " root " : mountpoint ) ;
2021-08-10 20:20:33 +02:00
string devname = disks . at ( mountpoint ) . dev . filename ( ) ;
2022-04-26 20:43:35 +02:00
int c = 0 ;
2021-08-10 20:20:33 +02:00
while ( devname . size ( ) > = 2 ) {
2021-08-17 22:33:21 +02:00
if ( fs : : exists ( " /sys/block/ " + devname + " /stat " , ec ) and access ( string ( " /sys/block/ " + devname + " /stat " ) . c_str ( ) , R_OK ) = = 0 ) {
2022-04-26 20:43:35 +02:00
if ( c > 0 and fs : : exists ( " /sys/block/ " + devname + ' / ' + disks . at ( mountpoint ) . dev . filename ( ) . string ( ) + " /stat " , ec ) )
disks . at ( mountpoint ) . stat = " /sys/block/ " + devname + ' / ' + disks . at ( mountpoint ) . dev . filename ( ) . string ( ) + " /stat " ;
else
disks . at ( mountpoint ) . stat = " /sys/block/ " + devname + " /stat " ;
2021-08-10 20:20:33 +02:00
break ;
2022-06-25 17:12:35 +02:00
//? Set ZFS stat filepath
2022-07-03 20:04:05 +02:00
} else if ( fstype = = " zfs " ) {
2022-07-11 18:08:25 +02:00
disks . at ( mountpoint ) . stat = get_zfs_stat_file ( dev , zfs_dataset_name_start , zfs_hide_datasets ) ;
2022-07-03 20:04:05 +02:00
if ( disks . at ( mountpoint ) . stat . empty ( ) ) {
2022-07-11 18:16:19 +02:00
Logger : : debug ( " Failed to get ZFS stat file for device " + dev ) ;
2022-07-03 20:04:05 +02:00
}
2022-06-25 17:12:35 +02:00
break ;
2021-08-10 20:20:33 +02:00
}
devname . resize ( devname . size ( ) - 1 ) ;
2022-04-26 20:43:35 +02:00
c + + ;
2021-08-10 20:20:33 +02:00
}
}
2022-07-11 18:08:25 +02:00
//? If zfs_hide_datasets option was switched, refresh stat filepath
if ( fstype = = " zfs " & & ( ( zfs_hide_datasets & & ! is_directory ( disks . at ( mountpoint ) . stat ) )
| | ( ! zfs_hide_datasets & & is_directory ( disks . at ( mountpoint ) . stat ) ) ) ) {
disks . at ( mountpoint ) . stat = get_zfs_stat_file ( dev , zfs_dataset_name_start , zfs_hide_datasets ) ;
2022-07-04 00:28:25 +02:00
if ( disks . at ( mountpoint ) . stat . empty ( ) ) {
2022-07-11 18:16:19 +02:00
Logger : : debug ( " Failed to get ZFS stat file for device " + dev ) ;
2022-07-04 00:28:25 +02:00
}
}
2021-08-10 20:20:33 +02:00
}
}
2022-10-23 19:33:04 +02:00
2021-08-10 20:20:33 +02:00
//? Remove disks no longer mounted or filtered out
if ( swap_disk and has_swap ) found . push_back ( " swap " ) ;
2021-08-15 23:20:55 +02:00
for ( auto it = disks . begin ( ) ; it ! = disks . end ( ) ; ) {
if ( not v_contains ( found , it - > first ) )
it = disks . erase ( it ) ;
2021-08-10 20:20:33 +02:00
else
2021-08-15 23:20:55 +02:00
it + + ;
2021-08-10 20:20:33 +02:00
}
2021-09-17 14:25:54 +02:00
if ( found . size ( ) ! = last_found . size ( ) ) redraw = true ;
2021-08-17 22:33:21 +02:00
last_found = std : : move ( found ) ;
2021-08-10 20:20:33 +02:00
}
else
throw std : : runtime_error ( " Failed to get mounts from /etc/mtab and /proc/self/mounts " ) ;
diskread . close ( ) ;
//? Get disk/partition stats
2023-11-25 21:01:11 +01:00
for ( auto it = disks . begin ( ) ; it ! = disks . end ( ) ; ) {
auto & [ mountpoint , disk ] = * it ;
if ( v_contains ( ignore_list , mountpoint ) ) {
it = disks . erase ( it ) ;
2021-08-10 20:20:33 +02:00
continue ;
}
2023-11-25 21:01:11 +01:00
if ( auto promises_it = disks_stats_promises . find ( mountpoint ) ; promises_it ! = disks_stats_promises . end ( ) ) {
auto & promise = promises_it - > second ;
if ( promise . valid ( ) & &
promise . wait_for ( 0 s ) = = std : : future_status : : timeout ) {
+ + it ;
continue ;
}
auto promise_res = promises_it - > second . get ( ) ;
if ( promise_res . second ! = - 1 ) {
ignore_list . push_back ( mountpoint ) ;
Logger : : warning ( " Failed to get disk/partition stats for mount \" " + mountpoint + " \" with statvfs error code: " + to_string ( promise_res . second ) + " . Ignoring... " ) ;
2022-10-23 19:33:04 +02:00
it = disks . erase ( it ) ;
2023-11-25 21:01:11 +01:00
continue ;
}
auto & updated_stats = promise_res . first ;
disk . total = updated_stats . total ;
disk . free = updated_stats . free ;
disk . used = updated_stats . used ;
disk . used_percent = updated_stats . used_percent ;
disk . free_percent = updated_stats . free_percent ;
2022-10-23 19:33:04 +02:00
}
2023-11-25 21:01:11 +01:00
disks_stats_promises [ mountpoint ] = async ( std : : launch : : async , [ mountpoint , & free_priv ] ( ) - > pair < disk_info , int > {
struct statvfs vfs ;
disk_info disk ;
if ( statvfs ( mountpoint . c_str ( ) , & vfs ) < 0 ) {
return pair { disk , errno } ;
}
disk . total = vfs . f_blocks * vfs . f_frsize ;
disk . free = ( free_priv ? vfs . f_bfree : vfs . f_bavail ) * vfs . f_frsize ;
disk . used = disk . total - disk . free ;
disk . used_percent = round ( ( double ) disk . used * 100 / disk . total ) ;
disk . free_percent = 100 - disk . used_percent ;
return pair { disk , - 1 } ;
} ) ;
+ + it ;
2022-10-23 19:33:04 +02:00
}
2021-08-10 20:20:33 +02:00
//? Setup disks order in UI and add swap if enabled
mem . disks_order . clear ( ) ;
2021-10-06 11:25:10 +02:00
# ifdef SNAPPED
if ( disks . contains ( " /mnt " ) ) mem . disks_order . push_back ( " /mnt " ) ;
# else
if ( disks . contains ( " / " ) ) mem . disks_order . push_back ( " / " ) ;
# endif
2021-08-10 20:20:33 +02:00
if ( swap_disk and has_swap ) {
mem . disks_order . push_back ( " swap " ) ;
2022-06-25 17:12:35 +02:00
if ( not disks . contains ( " swap " ) ) disks [ " swap " ] = { " " , " swap " , " swap " } ;
2021-08-10 20:20:33 +02:00
disks . at ( " swap " ) . total = mem . stats . at ( " swap_total " ) ;
disks . at ( " swap " ) . used = mem . stats . at ( " swap_used " ) ;
disks . at ( " swap " ) . free = mem . stats . at ( " swap_free " ) ;
disks . at ( " swap " ) . used_percent = mem . percent . at ( " swap_used " ) . back ( ) ;
disks . at ( " swap " ) . free_percent = mem . percent . at ( " swap_free " ) . back ( ) ;
}
2021-08-17 22:33:21 +02:00
for ( const auto & name : last_found )
2021-10-06 18:06:05 +02:00
# ifdef SNAPPED
if ( not is_in ( name , " /mnt " , " swap " ) ) mem . disks_order . push_back ( name ) ;
# else
if ( not is_in ( name , " / " , " swap " ) ) mem . disks_order . push_back ( name ) ;
# endif
2021-08-10 20:20:33 +02:00
//? Get disks IO
2022-07-04 00:28:25 +02:00
int64_t sectors_read , sectors_write , io_ticks , io_ticks_temp ;
2021-08-10 20:20:33 +02:00
disk_ios = 0 ;
for ( auto & [ ignored , disk ] : disks ) {
2021-08-17 22:33:21 +02:00
if ( disk . stat . empty ( ) or access ( disk . stat . c_str ( ) , R_OK ) ! = 0 ) continue ;
2022-07-11 18:08:25 +02:00
if ( disk . fstype = = " zfs " & & zfs_hide_datasets & & zfs_collect_pool_total_stats ( disk ) ) {
2022-07-04 00:28:25 +02:00
disk_ios + + ;
continue ;
}
2021-08-10 20:20:33 +02:00
diskread . open ( disk . stat ) ;
if ( diskread . good ( ) ) {
2022-07-03 20:04:05 +02:00
disk_ios + + ;
2022-06-25 17:12:35 +02:00
//? ZFS Pool Support
if ( disk . fstype = = " zfs " ) {
2022-07-03 20:04:05 +02:00
// skip first three lines
for ( int i = 0 ; i < 3 ; i + + ) diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' \n ' ) ;
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
2022-07-04 00:28:25 +02:00
diskread > > io_ticks ;
2022-06-25 17:12:35 +02:00
2022-07-03 20:04:05 +02:00
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
2022-06-25 17:12:35 +02:00
diskread > > sectors_write ; // nbytes written
if ( disk . io_write . empty ( ) )
disk . io_write . push_back ( 0 ) ;
else
disk . io_write . push_back ( max ( ( int64_t ) 0 , ( sectors_write - disk . old_io . at ( 1 ) ) ) ) ;
disk . old_io . at ( 1 ) = sectors_write ;
while ( cmp_greater ( disk . io_write . size ( ) , width * 2 ) ) disk . io_write . pop_front ( ) ;
2022-07-03 20:04:05 +02:00
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
2022-07-04 00:28:25 +02:00
diskread > > io_ticks_temp ;
io_ticks + = io_ticks_temp ;
2022-07-03 20:04:05 +02:00
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
diskread > > sectors_read ; // nbytes read
if ( disk . io_read . empty ( ) )
disk . io_read . push_back ( 0 ) ;
else
disk . io_read . push_back ( max ( ( int64_t ) 0 , ( sectors_read - disk . old_io . at ( 0 ) ) ) ) ;
disk . old_io . at ( 0 ) = sectors_read ;
while ( cmp_greater ( disk . io_read . size ( ) , width * 2 ) ) disk . io_read . pop_front ( ) ;
2022-06-25 17:12:35 +02:00
if ( disk . io_activity . empty ( ) )
disk . io_activity . push_back ( 0 ) ;
else
2022-07-04 00:28:25 +02:00
disk . io_activity . push_back ( max ( ( int64_t ) 0 , ( io_ticks - disk . old_io . at ( 2 ) ) ) ) ;
2022-06-25 17:12:35 +02:00
disk . old_io . at ( 2 ) = io_ticks ;
while ( cmp_greater ( disk . io_activity . size ( ) , width * 2 ) ) disk . io_activity . pop_front ( ) ;
} else {
for ( int i = 0 ; i < 2 ; i + + ) { diskread > > std : : ws ; diskread . ignore ( SSmax , ' ' ) ; }
diskread > > sectors_read ;
if ( disk . io_read . empty ( ) )
disk . io_read . push_back ( 0 ) ;
else
disk . io_read . push_back ( max ( ( int64_t ) 0 , ( sectors_read - disk . old_io . at ( 0 ) ) * 512 ) ) ;
disk . old_io . at ( 0 ) = sectors_read ;
while ( cmp_greater ( disk . io_read . size ( ) , width * 2 ) ) disk . io_read . pop_front ( ) ;
for ( int i = 0 ; i < 3 ; i + + ) { diskread > > std : : ws ; diskread . ignore ( SSmax , ' ' ) ; }
diskread > > sectors_write ;
if ( disk . io_write . empty ( ) )
disk . io_write . push_back ( 0 ) ;
else
disk . io_write . push_back ( max ( ( int64_t ) 0 , ( sectors_write - disk . old_io . at ( 1 ) ) * 512 ) ) ;
disk . old_io . at ( 1 ) = sectors_write ;
while ( cmp_greater ( disk . io_write . size ( ) , width * 2 ) ) disk . io_write . pop_front ( ) ;
for ( int i = 0 ; i < 2 ; i + + ) { diskread > > std : : ws ; diskread . ignore ( SSmax , ' ' ) ; }
diskread > > io_ticks ;
if ( disk . io_activity . empty ( ) )
disk . io_activity . push_back ( 0 ) ;
else
disk . io_activity . push_back ( clamp ( ( long ) round ( ( double ) ( io_ticks - disk . old_io . at ( 2 ) ) / ( uptime - old_uptime ) / 10 ) , 0l , 100l ) ) ;
disk . old_io . at ( 2 ) = io_ticks ;
while ( cmp_greater ( disk . io_activity . size ( ) , width * 2 ) ) disk . io_activity . pop_front ( ) ;
}
} else {
2023-08-26 20:29:43 +02:00
Logger : : debug ( " Error in Mem::collect() : when opening " + string { disk . stat } ) ;
2021-08-10 20:20:33 +02:00
}
diskread . close ( ) ;
}
2021-09-01 21:40:13 +02:00
old_uptime = uptime ;
2021-08-10 20:20:33 +02:00
}
catch ( const std : : exception & e ) {
2023-08-26 20:29:43 +02:00
Logger : : warning ( " Error in Mem::collect() : " + string { e . what ( ) } ) ;
2021-08-10 20:20:33 +02:00
}
}
return mem ;
2021-07-18 15:44:32 +02:00
}
2022-07-11 18:08:25 +02:00
fs : : path get_zfs_stat_file ( const string & device_name , size_t dataset_name_start , bool zfs_hide_datasets ) {
2022-07-05 00:11:17 +02:00
fs : : path zfs_pool_stat_path ;
2022-07-11 18:08:25 +02:00
if ( zfs_hide_datasets ) {
2022-07-05 00:11:17 +02:00
zfs_pool_stat_path = Shared : : procPath / " spl/kstat/zfs " / device_name ;
if ( access ( zfs_pool_stat_path . c_str ( ) , R_OK ) = = 0 ) {
2022-07-04 00:28:25 +02:00
return zfs_pool_stat_path ;
} else {
2022-07-11 18:16:19 +02:00
Logger : : debug ( " Cant access folder: " + zfs_pool_stat_path . string ( ) ) ;
2022-07-04 00:28:25 +02:00
return " " ;
}
}
2022-07-03 20:04:05 +02:00
ifstream filestream ;
string filename ;
2022-07-04 00:28:25 +02:00
string name_compare ;
2022-07-03 20:04:05 +02:00
if ( dataset_name_start ! = std : : string : : npos ) { // device is a dataset
2022-07-05 00:11:17 +02:00
zfs_pool_stat_path = Shared : : procPath / " spl/kstat/zfs " / device_name . substr ( 0 , dataset_name_start ) ;
2022-07-03 20:04:05 +02:00
} else { // device is a pool
2022-07-05 00:11:17 +02:00
zfs_pool_stat_path = Shared : : procPath / " spl/kstat/zfs " / device_name ;
2022-07-03 20:04:05 +02:00
}
// looking through all files that start with 'objset' to find the one containing `device_name` object stats
2022-07-05 00:11:17 +02:00
for ( const auto & file : fs : : directory_iterator ( zfs_pool_stat_path ) ) {
filename = file . path ( ) . filename ( ) ;
if ( filename . starts_with ( " objset " ) ) {
filestream . open ( file . path ( ) ) ;
2022-07-03 20:04:05 +02:00
if ( filestream . good ( ) ) {
// skip first two lines
for ( int i = 0 ; i < 2 ; i + + ) filestream . ignore ( numeric_limits < streamsize > : : max ( ) , ' \n ' ) ;
// skip characters until '7' is reached, indicating data type 7, next value will be object name
filestream . ignore ( numeric_limits < streamsize > : : max ( ) , ' 7 ' ) ;
filestream > > name_compare ;
if ( name_compare = = device_name ) {
filestream . close ( ) ;
2022-07-05 00:11:17 +02:00
if ( access ( file . path ( ) . c_str ( ) , R_OK ) = = 0 ) {
return file . path ( ) ;
2022-07-03 20:04:05 +02:00
} else {
2022-07-11 18:16:19 +02:00
Logger : : debug ( " Can't access file: " + file . path ( ) . string ( ) ) ;
2022-07-03 20:04:05 +02:00
return " " ;
}
}
}
filestream . close ( ) ;
}
}
2022-07-11 18:16:19 +02:00
Logger : : debug ( " Could not read directory: " + zfs_pool_stat_path . string ( ) ) ;
2022-07-03 20:04:05 +02:00
return " " ;
}
2022-07-04 00:28:25 +02:00
bool zfs_collect_pool_total_stats ( struct disk_info & disk ) {
ifstream diskread ;
2023-08-26 20:29:43 +02:00
int64_t bytes_read ;
int64_t bytes_write ;
int64_t io_ticks ;
int64_t bytes_read_total { } ; // defaults to 0
int64_t bytes_write_total { } ; // defaults to 0
int64_t io_ticks_total { } ; // defaults to 0
int64_t objects_read { } ; // defaults to 0
2022-07-04 00:28:25 +02:00
// looking through all files that start with 'objset'
2022-07-05 00:11:17 +02:00
for ( const auto & file : fs : : directory_iterator ( disk . stat ) ) {
if ( ( file . path ( ) . filename ( ) ) . string ( ) . starts_with ( " objset " ) ) {
diskread . open ( file . path ( ) ) ;
2022-07-04 00:28:25 +02:00
if ( diskread . good ( ) ) {
2022-07-09 02:46:15 +02:00
try {
// skip first three lines
for ( int i = 0 ; i < 3 ; i + + ) diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' \n ' ) ;
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
diskread > > io_ticks ;
io_ticks_total + = io_ticks ;
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
diskread > > bytes_write ;
bytes_write_total + = bytes_write ;
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
diskread > > io_ticks ;
io_ticks_total + = io_ticks ;
// skip characters until '4' is reached, indicating data type 4, next value will be out target
diskread . ignore ( numeric_limits < streamsize > : : max ( ) , ' 4 ' ) ;
diskread > > bytes_read ;
bytes_read_total + = bytes_read ;
} catch ( const std : : exception & e ) {
continue ;
}
// increment read objects counter if no errors were encountered
objects_read + + ;
2022-07-04 00:28:25 +02:00
} else {
2022-07-11 18:16:19 +02:00
Logger : : debug ( " Could not read file: " + file . path ( ) . string ( ) ) ;
2022-07-04 00:28:25 +02:00
}
diskread . close ( ) ;
}
}
2022-07-09 02:46:15 +02:00
// if for some reason no objects were read
if ( objects_read = = 0 ) return false ;
2022-07-04 00:28:25 +02:00
if ( disk . io_write . empty ( ) )
disk . io_write . push_back ( 0 ) ;
else
disk . io_write . push_back ( max ( ( int64_t ) 0 , ( bytes_write_total - disk . old_io . at ( 1 ) ) ) ) ;
disk . old_io . at ( 1 ) = bytes_write_total ;
while ( cmp_greater ( disk . io_write . size ( ) , width * 2 ) ) disk . io_write . pop_front ( ) ;
if ( disk . io_read . empty ( ) )
disk . io_read . push_back ( 0 ) ;
else
disk . io_read . push_back ( max ( ( int64_t ) 0 , ( bytes_read_total - disk . old_io . at ( 0 ) ) ) ) ;
disk . old_io . at ( 0 ) = bytes_read_total ;
while ( cmp_greater ( disk . io_read . size ( ) , width * 2 ) ) disk . io_read . pop_front ( ) ;
if ( disk . io_activity . empty ( ) )
disk . io_activity . push_back ( 0 ) ;
else
disk . io_activity . push_back ( max ( ( int64_t ) 0 , ( io_ticks_total - disk . old_io . at ( 2 ) ) ) ) ;
disk . old_io . at ( 2 ) = io_ticks_total ;
while ( cmp_greater ( disk . io_activity . size ( ) , width * 2 ) ) disk . io_activity . pop_front ( ) ;
return true ;
}
2021-07-18 15:44:32 +02:00
}
namespace Net {
2021-08-15 23:20:55 +02:00
unordered_flat_map < string , net_info > current_net ;
net_info empty_net = { } ;
vector < string > interfaces ;
string selected_iface ;
2023-08-26 20:29:43 +02:00
int errors { } ; // defaults to 0
2021-08-15 23:20:55 +02:00
unordered_flat_map < string , uint64_t > graph_max = { { " download " , { } } , { " upload " , { } } } ;
unordered_flat_map < string , array < int , 2 > > max_count = { { " download " , { } } , { " upload " , { } } } ;
2023-08-26 20:29:43 +02:00
bool rescale { true } ;
uint64_t timestamp { } ; // defaults to 0
2021-08-15 23:20:55 +02:00
//* RAII wrapper for getifaddrs
class getifaddr_wrapper {
struct ifaddrs * ifaddr ;
public :
int status ;
getifaddr_wrapper ( ) { status = getifaddrs ( & ifaddr ) ; }
~ getifaddr_wrapper ( ) { freeifaddrs ( ifaddr ) ; }
auto operator ( ) ( ) - > struct ifaddrs * { return ifaddr ; }
} ;
2023-08-26 20:29:43 +02:00
auto collect ( bool no_update ) - > net_info & {
2022-10-07 20:45:12 +02:00
if ( Runner : : stopping ) return empty_net ;
2021-08-15 23:20:55 +02:00
auto & net = current_net ;
auto & config_iface = Config : : getS ( " net_iface " ) ;
2023-08-26 20:29:43 +02:00
auto net_sync = Config : : getB ( " net_sync " ) ;
auto net_auto = Config : : getB ( " net_auto " ) ;
2021-08-22 16:04:01 +02:00
auto new_timestamp = time_ms ( ) ;
2021-08-15 23:20:55 +02:00
if ( not no_update and errors < 3 ) {
//? Get interface list using getifaddrs() wrapper
2021-08-17 22:33:21 +02:00
getifaddr_wrapper if_wrap { } ;
2021-08-15 23:20:55 +02:00
if ( if_wrap . status ! = 0 ) {
errors + + ;
Logger : : error ( " Net::collect() -> getifaddrs() failed with id " + to_string ( if_wrap . status ) ) ;
2021-09-01 21:40:13 +02:00
redraw = true ;
2021-08-15 23:20:55 +02:00
return empty_net ;
}
int family = 0 ;
2022-11-04 04:49:12 +01:00
static_assert ( INET6_ADDRSTRLEN > = INET_ADDRSTRLEN ) ; // 46 >= 16, compile-time assurance.
2022-11-04 10:44:22 +01:00
enum { IPBUFFER_MAXSIZE = INET6_ADDRSTRLEN } ; // manually using the known biggest value, guarded by the above static_assert
2022-11-04 04:49:12 +01:00
char ip [ IPBUFFER_MAXSIZE ] ;
2021-08-15 23:20:55 +02:00
interfaces . clear ( ) ;
string ipv4 , ipv6 ;
//? Iteration over all items in getifaddrs() list
2023-08-26 20:29:43 +02:00
for ( auto * ifa = if_wrap ( ) ; ifa ! = nullptr ; ifa = ifa - > ifa_next ) {
if ( ifa - > ifa_addr = = nullptr ) continue ;
2021-08-15 23:20:55 +02:00
family = ifa - > ifa_addr - > sa_family ;
const auto & iface = ifa - > ifa_name ;
2022-11-04 01:42:03 +01:00
//? Update available interfaces vector and get status of interface
if ( not v_contains ( interfaces , iface ) ) {
interfaces . push_back ( iface ) ;
net [ iface ] . connected = ( ifa - > ifa_flags & IFF_RUNNING ) ;
// An interface can have more than one IP of the same family associated with it,
// but we pick only the first one to show in the NET box.
// Note: Interfaces without any IPv4 and IPv6 set are still valid and monitorable!
net [ iface ] . ipv4 . clear ( ) ;
net [ iface ] . ipv6 . clear ( ) ;
}
2022-11-04 04:49:12 +01:00
2021-08-15 23:20:55 +02:00
//? Get IPv4 address
if ( family = = AF_INET ) {
2022-11-04 04:49:12 +01:00
if ( net [ iface ] . ipv4 . empty ( ) ) {
2023-08-26 20:29:43 +02:00
if ( nullptr ! = inet_ntop ( family , & ( reinterpret_cast < struct sockaddr_in * > ( ifa - > ifa_addr ) - > sin_addr ) , ip , IPBUFFER_MAXSIZE ) ) {
2022-11-04 04:49:12 +01:00
net [ iface ] . ipv4 = ip ;
} else {
int errsv = errno ;
Logger : : error ( " Net::collect() -> Failed to convert IPv4 to string for iface " + string ( iface ) + " , errno: " + strerror ( errsv ) ) ;
}
}
2021-08-15 23:20:55 +02:00
}
//? Get IPv6 address
else if ( family = = AF_INET6 ) {
2022-11-04 04:49:12 +01:00
if ( net [ iface ] . ipv6 . empty ( ) ) {
2023-08-26 20:29:43 +02:00
if ( nullptr ! = inet_ntop ( family , & ( reinterpret_cast < struct sockaddr_in6 * > ( ifa - > ifa_addr ) - > sin6_addr ) , ip , IPBUFFER_MAXSIZE ) ) {
2022-11-04 04:49:12 +01:00
net [ iface ] . ipv6 = ip ;
} else {
int errsv = errno ;
Logger : : error ( " Net::collect() -> Failed to convert IPv6 to string for iface " + string ( iface ) + " , errno: " + strerror ( errsv ) ) ;
}
}
2022-11-04 01:42:03 +01:00
} //else, ignoring family==AF_PACKET (see man 3 getifaddrs) which is the first one in the `for` loop.
2021-08-15 23:20:55 +02:00
}
//? Get total recieved and transmitted bytes + device address if no ip was found
for ( const auto & iface : interfaces ) {
if ( net . at ( iface ) . ipv4 . empty ( ) and net . at ( iface ) . ipv6 . empty ( ) )
net . at ( iface ) . ipv4 = readfile ( " /sys/class/net/ " + iface + " /address " ) ;
for ( const string dir : { " download " , " upload " } ) {
const fs : : path sys_file = " /sys/class/net/ " + iface + " /statistics/ " + ( dir = = " download " ? " rx_bytes " : " tx_bytes " ) ;
auto & saved_stat = net . at ( iface ) . stat . at ( dir ) ;
auto & bandwidth = net . at ( iface ) . bandwidth . at ( dir ) ;
2023-08-26 20:29:43 +02:00
uint64_t val { } ; // defaults to 0
2022-01-12 20:49:27 +01:00
try { val = ( uint64_t ) stoull ( readfile ( sys_file , " 0 " ) ) ; }
2021-09-30 22:49:14 +02:00
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
2021-08-15 23:20:55 +02:00
//? Update speed, total and top values
2021-12-30 11:26:23 +01:00
if ( val < saved_stat . last ) {
saved_stat . rollover + = saved_stat . last ;
saved_stat . last = 0 ;
}
if ( cmp_greater ( ( unsigned long long ) saved_stat . rollover + ( unsigned long long ) val , numeric_limits < uint64_t > : : max ( ) ) ) {
saved_stat . rollover = 0 ;
saved_stat . last = 0 ;
}
2021-08-22 16:04:01 +02:00
saved_stat . speed = round ( ( double ) ( val - saved_stat . last ) / ( ( double ) ( new_timestamp - timestamp ) / 1000 ) ) ;
2021-08-15 23:20:55 +02:00
if ( saved_stat . speed > saved_stat . top ) saved_stat . top = saved_stat . speed ;
2021-12-30 11:26:23 +01:00
if ( saved_stat . offset > val + saved_stat . rollover ) saved_stat . offset = 0 ;
saved_stat . total = ( val + saved_stat . rollover ) - saved_stat . offset ;
2021-08-15 23:20:55 +02:00
saved_stat . last = val ;
//? Add values to graph
bandwidth . push_back ( saved_stat . speed ) ;
2021-08-17 22:33:21 +02:00
while ( cmp_greater ( bandwidth . size ( ) , width * 2 ) ) bandwidth . pop_front ( ) ;
2021-08-15 23:20:55 +02:00
//? Set counters for auto scaling
if ( net_auto and selected_iface = = iface ) {
2022-05-28 20:48:02 +02:00
if ( net_sync and saved_stat . speed < net . at ( iface ) . stat . at ( dir = = " download " ? " upload " : " download " ) . speed ) continue ;
2021-08-15 23:20:55 +02:00
if ( saved_stat . speed > graph_max [ dir ] ) {
+ + max_count [ dir ] [ 0 ] ;
if ( max_count [ dir ] [ 1 ] > 0 ) - - max_count [ dir ] [ 1 ] ;
}
else if ( graph_max [ dir ] > 10 < < 10 and saved_stat . speed < graph_max [ dir ] / 10 ) {
+ + max_count [ dir ] [ 1 ] ;
if ( max_count [ dir ] [ 0 ] > 0 ) - - max_count [ dir ] [ 0 ] ;
}
}
}
}
//? Clean up net map if needed
if ( net . size ( ) > interfaces . size ( ) ) {
for ( auto it = net . begin ( ) ; it ! = net . end ( ) ; ) {
if ( not v_contains ( interfaces , it - > first ) )
it = net . erase ( it ) ;
else
it + + ;
}
2021-08-17 22:33:21 +02:00
net . compact ( ) ;
2021-08-15 23:20:55 +02:00
}
2021-08-22 16:04:01 +02:00
timestamp = new_timestamp ;
2021-08-15 23:20:55 +02:00
}
//? Return empty net_info struct if no interfaces was found
if ( net . empty ( ) )
return empty_net ;
//? Find an interface to display if selected isn't set or valid
if ( selected_iface . empty ( ) or not v_contains ( interfaces , selected_iface ) ) {
max_count [ " download " ] [ 0 ] = max_count [ " download " ] [ 1 ] = max_count [ " upload " ] [ 0 ] = max_count [ " upload " ] [ 1 ] = 0 ;
redraw = true ;
if ( net_auto ) rescale = true ;
if ( not config_iface . empty ( ) and v_contains ( interfaces , config_iface ) ) selected_iface = config_iface ;
else {
//? Sort interfaces by total upload + download bytes
auto sorted_interfaces = interfaces ;
rng : : sort ( sorted_interfaces , [ & ] ( const auto & a , const auto & b ) {
return cmp_greater ( net . at ( a ) . stat [ " download " ] . total + net . at ( a ) . stat [ " upload " ] . total ,
net . at ( b ) . stat [ " download " ] . total + net . at ( b ) . stat [ " upload " ] . total ) ;
} ) ;
selected_iface . clear ( ) ;
//? Try to set to a connected interface
for ( const auto & iface : sorted_interfaces ) {
if ( net . at ( iface ) . connected ) selected_iface = iface ;
break ;
}
//? If no interface is connected set to first available
2021-09-01 21:40:13 +02:00
if ( selected_iface . empty ( ) and not sorted_interfaces . empty ( ) ) selected_iface = sorted_interfaces . at ( 0 ) ;
else if ( sorted_interfaces . empty ( ) ) return empty_net ;
2021-08-15 23:20:55 +02:00
}
}
//? Calculate max scale for graphs if needed
if ( net_auto ) {
bool sync = false ;
for ( const auto & dir : { " download " , " upload " } ) {
for ( const auto & sel : { 0 , 1 } ) {
if ( rescale or max_count [ dir ] [ sel ] > = 5 ) {
2023-05-26 07:26:21 +02:00
const long long avg_speed = ( net [ selected_iface ] . bandwidth [ dir ] . size ( ) > 5
? std : : accumulate ( net . at ( selected_iface ) . bandwidth . at ( dir ) . rbegin ( ) , net . at ( selected_iface ) . bandwidth . at ( dir ) . rbegin ( ) + 5 , 0ll ) / 5
2021-08-15 23:20:55 +02:00
: net [ selected_iface ] . stat [ dir ] . speed ) ;
2021-09-18 14:42:53 +02:00
graph_max [ dir ] = max ( uint64_t ( avg_speed * ( sel = = 0 ? 1.3 : 3.0 ) ) , ( uint64_t ) 10 < < 10 ) ;
2021-08-15 23:20:55 +02:00
max_count [ dir ] [ 0 ] = max_count [ dir ] [ 1 ] = 0 ;
redraw = true ;
if ( net_sync ) sync = true ;
break ;
}
}
//? Sync download/upload graphs if enabled
if ( sync ) {
const auto other = ( string ( dir ) = = " upload " ? " download " : " upload " ) ;
graph_max [ other ] = graph_max [ dir ] ;
max_count [ other ] [ 0 ] = max_count [ other ] [ 1 ] = 0 ;
break ;
}
}
}
2021-07-18 15:44:32 +02:00
2021-08-15 23:20:55 +02:00
rescale = false ;
return net . at ( selected_iface ) ;
2021-07-18 15:44:32 +02:00
}
2021-07-04 22:02:31 +02:00
}
2021-06-19 14:57:27 +02:00
namespace Proc {
2021-08-17 22:33:21 +02:00
vector < proc_info > current_procs ;
unordered_flat_map < string , string > uid_user ;
2021-08-22 16:04:01 +02:00
string current_sort ;
string current_filter ;
2023-08-26 20:29:43 +02:00
bool current_rev { } ; // defaults to false
2021-08-17 22:33:21 +02:00
fs : : file_time_type passwd_time ;
uint64_t cputimes ;
2021-07-29 23:40:56 +02:00
int collapse = - 1 , expand = - 1 ;
2023-08-26 20:29:43 +02:00
uint64_t old_cputimes { } ; // defaults to 0
atomic < int > numpids { } ; // defaults to 0
int filter_found { } ; // defaults to 0
2021-06-19 14:57:27 +02:00
2021-06-30 22:28:12 +02:00
detail_container detailed ;
2022-05-21 14:14:16 +02:00
constexpr size_t KTHREADD = 2 ;
2022-06-29 09:54:28 +02:00
static robin_hood : : unordered_set < size_t > kernels_procs = { KTHREADD } ;
2021-06-30 22:28:12 +02:00
2021-07-04 01:18:48 +02:00
//* Get detailed info for selected process
2021-07-26 01:06:34 +02:00
void _collect_details ( const size_t pid , const uint64_t uptime , vector < proc_info > & procs ) {
2021-07-29 23:40:56 +02:00
fs : : path pid_path = Shared : : procPath / std : : to_string ( pid ) ;
2021-07-04 01:18:48 +02:00
if ( pid ! = detailed . last_pid ) {
2021-07-26 01:06:34 +02:00
detailed = { } ;
2021-07-04 01:18:48 +02:00
detailed . last_pid = pid ;
2021-08-10 20:20:33 +02:00
detailed . skip_smaps = not Config : : getB ( " proc_info_smaps " ) ;
2021-07-04 01:18:48 +02:00
}
//? Copy proc_info for process from proc vector
2021-08-10 20:20:33 +02:00
auto p_info = rng : : find ( procs , pid , & proc_info : : pid ) ;
detailed . entry = * p_info ;
2021-07-04 01:18:48 +02:00
//? Update cpu percent deque for process cpu graph
2021-07-29 23:40:56 +02:00
if ( not Config : : getB ( " proc_per_core " ) ) detailed . entry . cpu_p * = Shared : : coreCount ;
2021-09-20 17:23:34 +02:00
detailed . cpu_percent . push_back ( clamp ( ( long long ) round ( detailed . entry . cpu_p ) , 0ll , 100ll ) ) ;
2021-08-17 22:33:21 +02:00
while ( cmp_greater ( detailed . cpu_percent . size ( ) , width ) ) detailed . cpu_percent . pop_front ( ) ;
2021-07-04 01:18:48 +02:00
//? Process runtime
2021-08-17 22:33:21 +02:00
detailed . elapsed = sec_to_dhms ( uptime - ( detailed . entry . cpu_s / Shared : : clkTck ) ) ;
2021-07-26 01:06:34 +02:00
if ( detailed . elapsed . size ( ) > 8 ) detailed . elapsed . resize ( detailed . elapsed . size ( ) - 3 ) ;
2021-07-04 01:18:48 +02:00
//? Get parent process name
2021-08-17 22:33:21 +02:00
if ( detailed . parent . empty ( ) ) {
auto p_entry = rng : : find ( procs , detailed . entry . ppid , & proc_info : : pid ) ;
if ( p_entry ! = procs . end ( ) ) detailed . parent = p_entry - > name ;
}
2021-07-04 01:18:48 +02:00
//? Expand process status from single char to explanative string
detailed . status = ( proc_states . contains ( detailed . entry . state ) ) ? proc_states . at ( detailed . entry . state ) : " Unknown " ;
2021-06-30 22:28:12 +02:00
ifstream d_read ;
2021-07-04 01:18:48 +02:00
string short_str ;
2021-06-30 22:28:12 +02:00
2021-07-04 01:18:48 +02:00
//? Try to get RSS mem from proc/[pid]/smaps
detailed . memory . clear ( ) ;
if ( not detailed . skip_smaps and fs : : exists ( pid_path / " smaps " ) ) {
d_read . open ( pid_path / " smaps " ) ;
2021-08-03 23:47:46 +02:00
uint64_t rss = 0 ;
try {
while ( d_read . good ( ) ) {
d_read . ignore ( SSmax , ' R ' ) ;
if ( d_read . peek ( ) = = ' s ' ) {
d_read . ignore ( SSmax , ' : ' ) ;
getline ( d_read , short_str , ' k ' ) ;
rss + = stoull ( short_str ) ;
2021-07-26 01:06:34 +02:00
}
2021-06-30 22:28:12 +02:00
}
2021-08-03 23:47:46 +02:00
if ( rss = = detailed . entry . mem > > 10 )
detailed . skip_smaps = true ;
else {
detailed . mem_bytes . push_back ( rss < < 10 ) ;
detailed . memory = floating_humanizer ( rss , false , 1 ) ;
}
2021-06-30 22:28:12 +02:00
}
2021-08-03 23:47:46 +02:00
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
2021-06-30 22:28:12 +02:00
d_read . close ( ) ;
}
2021-07-26 01:06:34 +02:00
if ( detailed . memory . empty ( ) ) {
detailed . mem_bytes . push_back ( detailed . entry . mem ) ;
detailed . memory = floating_humanizer ( detailed . entry . mem ) ;
}
if ( detailed . first_mem = = - 1 or detailed . first_mem < detailed . mem_bytes . back ( ) / 2 or detailed . first_mem > detailed . mem_bytes . back ( ) * 4 ) {
2021-10-05 09:18:04 +02:00
detailed . first_mem = min ( ( uint64_t ) detailed . mem_bytes . back ( ) * 2 , Mem : : get_totalMem ( ) ) ;
2021-07-26 01:06:34 +02:00
redraw = true ;
}
2021-08-17 22:33:21 +02:00
while ( cmp_greater ( detailed . mem_bytes . size ( ) , width ) ) detailed . mem_bytes . pop_front ( ) ;
2021-06-19 14:57:27 +02:00
2021-07-04 01:18:48 +02:00
//? Get bytes read and written from proc/[pid]/io
if ( fs : : exists ( pid_path / " io " ) ) {
d_read . open ( pid_path / " io " ) ;
2021-08-03 23:47:46 +02:00
try {
string name ;
while ( d_read . good ( ) ) {
getline ( d_read , name , ' : ' ) ;
if ( name . ends_with ( " read_bytes " ) ) {
getline ( d_read , short_str ) ;
detailed . io_read = floating_humanizer ( stoull ( short_str ) ) ;
}
else if ( name . ends_with ( " write_bytes " ) ) {
getline ( d_read , short_str ) ;
detailed . io_write = floating_humanizer ( stoull ( short_str ) ) ;
break ;
2021-07-04 01:18:48 +02:00
}
2021-08-03 23:47:46 +02:00
else
d_read . ignore ( SSmax , ' \n ' ) ;
2021-07-04 01:18:48 +02:00
}
}
2021-08-03 23:47:46 +02:00
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
2021-07-04 01:18:48 +02:00
d_read . close ( ) ;
}
}
2021-06-19 14:57:27 +02:00
2021-07-04 01:18:48 +02:00
//* Collects and sorts process information from /proc
2023-08-26 20:29:43 +02:00
auto collect ( bool no_update ) - > vector < proc_info > & {
2022-10-07 20:45:12 +02:00
if ( Runner : : stopping ) return current_procs ;
2021-07-21 03:17:34 +02:00
const auto & sorting = Config : : getS ( " proc_sorting " ) ;
2023-08-26 20:29:43 +02:00
auto reverse = Config : : getB ( " proc_reversed " ) ;
2021-07-21 03:17:34 +02:00
const auto & filter = Config : : getS ( " proc_filter " ) ;
2023-08-26 20:29:43 +02:00
auto per_core = Config : : getB ( " proc_per_core " ) ;
auto should_filter_kernel = Config : : getB ( " proc_filter_kernel " ) ;
auto tree = Config : : getB ( " proc_tree " ) ;
auto show_detailed = Config : : getB ( " show_detailed " ) ;
2021-07-21 03:17:34 +02:00
const size_t detailed_pid = Config : : getI ( " detailed_pid " ) ;
2021-09-01 21:40:13 +02:00
bool should_filter = current_filter ! = filter ;
2021-08-22 16:04:01 +02:00
if ( should_filter ) current_filter = filter ;
2023-08-26 20:29:43 +02:00
bool sorted_change = ( sorting ! = current_sort or reverse ! = current_rev or should_filter ) ;
2021-08-22 16:04:01 +02:00
if ( sorted_change ) {
current_sort = sorting ;
current_rev = reverse ;
}
2021-06-19 14:57:27 +02:00
ifstream pread ;
2021-06-25 23:58:19 +02:00
string long_string ;
string short_str ;
2021-08-17 22:33:21 +02:00
2022-07-03 12:37:54 +02:00
static vector < size_t > found ;
2021-08-22 16:04:01 +02:00
const double uptime = system_uptime ( ) ;
2021-08-17 22:33:21 +02:00
2021-07-26 01:06:34 +02:00
const int cmult = ( per_core ) ? Shared : : coreCount : 1 ;
2021-06-30 22:28:12 +02:00
bool got_detailed = false ;
2021-08-11 23:21:33 +02:00
2023-08-26 20:29:43 +02:00
static size_t proc_clear_count { } ; // defaults to 0
2022-06-29 09:43:17 +02:00
2021-08-11 23:21:33 +02:00
//* Use pids from last update if only changing filter, sorting or tree options
2021-08-17 22:33:21 +02:00
if ( no_update and not current_procs . empty ( ) ) {
if ( show_detailed and detailed_pid ! = detailed . last_pid ) _collect_details ( detailed_pid , round ( uptime ) , current_procs ) ;
2021-06-19 14:57:27 +02:00
}
2021-08-11 23:21:33 +02:00
//* ---------------------------------------------Collection start----------------------------------------------
else {
2021-09-01 21:40:13 +02:00
should_filter = true ;
2022-07-03 12:37:54 +02:00
found . clear ( ) ;
2021-09-01 21:40:13 +02:00
2022-06-29 09:43:17 +02:00
//? First make sure kernel proc cache is cleared.
if ( should_filter_kernel and + + proc_clear_count > = 256 ) {
2022-07-03 12:37:54 +02:00
//? Clearing the cache is used in the event of a pid wrap around.
2022-06-29 09:43:17 +02:00
//? In that event processes that acquire old kernel pids would also be filtered out so we need to manually clean the cache every now and then.
2022-06-29 09:54:28 +02:00
kernels_procs . clear ( ) ;
kernels_procs . emplace ( KTHREADD ) ;
2022-06-29 09:43:17 +02:00
proc_clear_count = 0 ;
2022-05-29 15:11:27 +02:00
}
2021-09-01 21:40:13 +02:00
2021-10-05 09:18:04 +02:00
auto totalMem = Mem : : get_totalMem ( ) ;
int totalMem_len = to_string ( totalMem > > 10 ) . size ( ) ;
2021-08-11 23:21:33 +02:00
//? Update uid_user map if /etc/passwd changed since last run
if ( not Shared : : passwd_path . empty ( ) and fs : : last_write_time ( Shared : : passwd_path ) ! = passwd_time ) {
string r_uid , r_user ;
passwd_time = fs : : last_write_time ( Shared : : passwd_path ) ;
uid_user . clear ( ) ;
pread . open ( Shared : : passwd_path ) ;
if ( pread . good ( ) ) {
2021-10-15 08:32:37 +02:00
while ( pread . good ( ) ) {
2021-08-11 23:21:33 +02:00
getline ( pread , r_user , ' : ' ) ;
pread . ignore ( SSmax , ' : ' ) ;
getline ( pread , r_uid , ' : ' ) ;
2022-11-05 22:56:52 +01:00
if ( uid_user . contains ( r_uid ) ) break ;
2021-08-11 23:21:33 +02:00
uid_user [ r_uid ] = r_user ;
2021-06-30 22:28:12 +02:00
pread . ignore ( SSmax , ' \n ' ) ;
2021-06-19 14:57:27 +02:00
}
2021-06-30 22:28:12 +02:00
}
2021-08-11 23:21:33 +02:00
else {
Shared : : passwd_path . clear ( ) ;
}
2021-06-30 22:28:12 +02:00
pread . close ( ) ;
}
2021-08-11 23:21:33 +02:00
//? Get cpu total times from /proc/stat
cputimes = 0 ;
pread . open ( Shared : : procPath / " stat " ) ;
if ( pread . good ( ) ) {
pread . ignore ( SSmax , ' ' ) ;
for ( uint64_t times ; pread > > times ; cputimes + = times ) ;
}
else throw std : : runtime_error ( " Failure to read /proc/stat " ) ;
2021-08-12 22:25:18 +02:00
pread . close ( ) ;
2021-08-11 23:21:33 +02:00
//? Iterate over all pids in /proc
for ( const auto & d : fs : : directory_iterator ( Shared : : procPath ) ) {
if ( Runner : : stopping )
2021-08-17 22:33:21 +02:00
return current_procs ;
2022-10-02 23:29:05 +02:00
2021-08-11 23:21:33 +02:00
if ( pread . is_open ( ) ) pread . close ( ) ;
const string pid_str = d . path ( ) . filename ( ) ;
if ( not isdigit ( pid_str [ 0 ] ) ) continue ;
2021-08-17 22:33:21 +02:00
const size_t pid = stoul ( pid_str ) ;
2022-07-03 12:37:54 +02:00
2022-06-29 09:54:28 +02:00
if ( should_filter_kernel and kernels_procs . contains ( pid ) ) {
2022-05-21 14:14:16 +02:00
continue ;
}
2022-05-24 14:42:59 +02:00
2021-08-17 22:33:21 +02:00
found . push_back ( pid ) ;
2021-08-22 16:04:01 +02:00
//? Check if pid already exists in current_procs
2021-08-17 22:33:21 +02:00
auto find_old = rng : : find ( current_procs , pid , & proc_info : : pid ) ;
2023-08-26 20:29:43 +02:00
bool no_cache { } ; // defaults to false
2021-08-17 22:33:21 +02:00
if ( find_old = = current_procs . end ( ) ) {
current_procs . push_back ( { pid } ) ;
find_old = current_procs . end ( ) - 1 ;
no_cache = true ;
}
2021-08-11 23:21:33 +02:00
2021-08-17 22:33:21 +02:00
auto & new_proc = * find_old ;
//? Get program name, command and username
if ( no_cache ) {
2021-08-11 23:21:33 +02:00
pread . open ( d . path ( ) / " comm " ) ;
if ( not pread . good ( ) ) continue ;
2021-08-17 22:33:21 +02:00
getline ( pread , new_proc . name ) ;
2021-08-11 23:21:33 +02:00
pread . close ( ) ;
2021-08-22 16:04:01 +02:00
//? Check for whitespace characters in name and set offset to get correct fields from stat file
2021-08-17 22:33:21 +02:00
new_proc . name_offset = rng : : count ( new_proc . name , ' ' ) ;
2021-08-11 23:21:33 +02:00
pread . open ( d . path ( ) / " cmdline " ) ;
if ( not pread . good ( ) ) continue ;
long_string . clear ( ) ;
2022-02-15 07:11:22 +01:00
while ( getline ( pread , long_string , ' \0 ' ) ) {
2022-02-17 22:28:10 +01:00
new_proc . cmd + = long_string + ' ' ;
2022-02-15 07:11:22 +01:00
if ( new_proc . cmd . size ( ) > 1000 ) {
new_proc . cmd . resize ( 1000 ) ;
break ;
}
}
2021-08-11 23:21:33 +02:00
pread . close ( ) ;
2021-08-17 22:33:21 +02:00
if ( not new_proc . cmd . empty ( ) ) new_proc . cmd . pop_back ( ) ;
2021-08-11 23:21:33 +02:00
pread . open ( d . path ( ) / " status " ) ;
if ( not pread . good ( ) ) continue ;
string uid ;
string line ;
2021-10-15 08:32:37 +02:00
while ( pread . good ( ) ) {
2021-08-11 23:21:33 +02:00
getline ( pread , line , ' : ' ) ;
if ( line = = " Uid " ) {
pread . ignore ( ) ;
getline ( pread , uid , ' \t ' ) ;
break ;
} else {
pread . ignore ( SSmax , ' \n ' ) ;
}
}
pread . close ( ) ;
2021-10-06 10:47:24 +02:00
if ( uid_user . contains ( uid ) ) {
new_proc . user = uid_user . at ( uid ) ;
}
else {
2021-10-17 01:45:26 +02:00
# if !(defined(STATIC_BUILD) && defined(__GLIBC__))
2021-10-06 10:47:24 +02:00
try {
struct passwd * udet ;
udet = getpwuid ( stoi ( uid ) ) ;
2023-08-26 20:29:43 +02:00
if ( udet ! = nullptr and udet - > pw_name ! = nullptr ) {
2021-10-06 10:47:24 +02:00
new_proc . user = string ( udet - > pw_name ) ;
}
else {
new_proc . user = uid ;
}
}
catch ( . . . ) { new_proc . user = uid ; }
# else
new_proc . user = uid ;
# endif
}
2021-08-11 23:21:33 +02:00
}
2021-06-30 22:28:12 +02:00
2021-08-11 23:21:33 +02:00
//? Parse /proc/[pid]/stat
pread . open ( d . path ( ) / " stat " ) ;
if ( not pread . good ( ) ) continue ;
2021-06-30 22:28:12 +02:00
2021-08-17 22:33:21 +02:00
const auto & offset = new_proc . name_offset ;
2021-08-11 23:21:33 +02:00
short_str . clear ( ) ;
2021-09-01 21:40:13 +02:00
int x = 0 , next_x = 3 ;
2021-08-11 23:21:33 +02:00
uint64_t cpu_t = 0 ;
try {
2021-09-01 21:40:13 +02:00
for ( ; ; ) {
2021-10-15 08:32:37 +02:00
while ( pread . good ( ) and + + x < next_x + offset ) pread . ignore ( SSmax , ' ' ) ;
2021-09-01 21:40:13 +02:00
if ( not pread . good ( ) ) break ;
2021-10-15 08:32:37 +02:00
else getline ( pread , short_str , ' ' ) ;
2021-08-11 23:21:33 +02:00
switch ( x - offset ) {
2021-08-15 23:20:55 +02:00
case 3 : //? Process state
2021-08-11 23:21:33 +02:00
new_proc . state = short_str . at ( 0 ) ;
2021-09-01 21:40:13 +02:00
if ( new_proc . ppid ! = 0 ) next_x = 14 ;
2021-08-11 23:21:33 +02:00
continue ;
2021-08-15 23:20:55 +02:00
case 4 : //? Parent pid
2021-08-11 23:21:33 +02:00
new_proc . ppid = stoull ( short_str ) ;
next_x = 14 ;
continue ;
2021-08-15 23:20:55 +02:00
case 14 : //? Process utime
2021-08-11 23:21:33 +02:00
cpu_t = stoull ( short_str ) ;
continue ;
2021-08-15 23:20:55 +02:00
case 15 : //? Process stime
2021-08-11 23:21:33 +02:00
cpu_t + = stoull ( short_str ) ;
next_x = 19 ;
continue ;
2021-08-15 23:20:55 +02:00
case 19 : //? Nice value
2022-11-06 11:32:17 +01:00
new_proc . p_nice = stoll ( short_str ) ;
2021-08-11 23:21:33 +02:00
continue ;
2021-08-15 23:20:55 +02:00
case 20 : //? Number of threads
2021-08-11 23:21:33 +02:00
new_proc . threads = stoull ( short_str ) ;
2021-08-17 22:33:21 +02:00
if ( new_proc . cpu_s = = 0 ) {
2021-08-11 23:21:33 +02:00
next_x = 22 ;
2021-08-17 22:33:21 +02:00
new_proc . cpu_t = cpu_t ;
2021-08-11 23:21:33 +02:00
}
else
next_x = 24 ;
continue ;
2021-08-17 22:33:21 +02:00
case 22 : //? Get cpu seconds if missing
new_proc . cpu_s = stoull ( short_str ) ;
2021-07-15 23:49:16 +02:00
next_x = 24 ;
2021-08-11 23:21:33 +02:00
continue ;
2021-08-15 23:20:55 +02:00
case 24 : //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x)
2021-10-05 09:18:04 +02:00
if ( cmp_greater ( short_str . size ( ) , totalMem_len ) )
new_proc . mem = totalMem ;
2021-09-21 21:24:58 +02:00
else
new_proc . mem = stoull ( short_str ) * Shared : : pageSize ;
2021-06-19 14:57:27 +02:00
}
2021-09-01 21:40:13 +02:00
break ;
2021-06-19 14:57:27 +02:00
}
2021-08-11 23:21:33 +02:00
2021-06-19 14:57:27 +02:00
}
2021-08-11 23:21:33 +02:00
catch ( const std : : invalid_argument & ) { continue ; }
catch ( const std : : out_of_range & ) { continue ; }
2021-09-01 21:40:13 +02:00
2021-08-11 23:21:33 +02:00
pread . close ( ) ;
2022-07-03 12:37:54 +02:00
2022-06-29 09:54:28 +02:00
if ( should_filter_kernel and new_proc . ppid = = KTHREADD ) {
2022-05-29 15:11:27 +02:00
kernels_procs . emplace ( new_proc . pid ) ;
2022-06-29 09:54:28 +02:00
found . pop_back ( ) ;
2022-05-21 14:14:16 +02:00
}
2021-06-27 01:19:57 +02:00
2021-08-11 23:21:33 +02:00
if ( x - offset < 24 ) continue ;
2021-06-19 14:57:27 +02:00
2021-09-21 21:24:58 +02:00
//? Get RSS memory from /proc/[pid]/statm if value from /proc/[pid]/stat looks wrong
2021-10-05 09:18:04 +02:00
if ( new_proc . mem > = totalMem ) {
2021-09-21 21:24:58 +02:00
pread . open ( d . path ( ) / " statm " ) ;
if ( not pread . good ( ) ) continue ;
pread . ignore ( SSmax , ' ' ) ;
pread > > new_proc . mem ;
new_proc . mem * = Shared : : pageSize ;
pread . close ( ) ;
}
2021-08-11 23:21:33 +02:00
//? Process cpu usage since last update
2021-09-21 17:21:42 +02:00
new_proc . cpu_p = clamp ( round ( cmult * 1000 * ( cpu_t - new_proc . cpu_t ) / max ( ( uint64_t ) 1 , cputimes - old_cputimes ) ) / 10.0 , 0.0 , 100.0 * Shared : : coreCount ) ;
2021-06-19 14:57:27 +02:00
2021-08-11 23:21:33 +02:00
//? Process cumulative cpu usage since process start
2021-08-17 22:33:21 +02:00
new_proc . cpu_c = ( double ) cpu_t / max ( 1.0 , ( uptime * Shared : : clkTck ) - new_proc . cpu_s ) ;
2021-06-19 14:57:27 +02:00
2021-09-17 14:25:54 +02:00
//? Update cached value with latest cpu times
2021-08-17 22:33:21 +02:00
new_proc . cpu_t = cpu_t ;
2021-06-19 14:57:27 +02:00
2021-08-11 23:21:33 +02:00
if ( show_detailed and not got_detailed and new_proc . pid = = detailed_pid ) {
got_detailed = true ;
}
}
2021-06-30 22:28:12 +02:00
2022-06-29 09:54:28 +02:00
//? Clear dead processes from current_procs and remove kernel processes if enabled
2021-08-17 22:33:21 +02:00
auto eraser = rng : : remove_if ( current_procs , [ & ] ( const auto & element ) { return not v_contains ( found , element . pid ) ; } ) ;
current_procs . erase ( eraser . begin ( ) , eraser . end ( ) ) ;
2021-06-30 22:28:12 +02:00
2021-08-11 23:21:33 +02:00
//? Update the details info box for process if active
if ( show_detailed and got_detailed ) {
2021-08-17 22:33:21 +02:00
_collect_details ( detailed_pid , round ( uptime ) , current_procs ) ;
2021-08-11 23:21:33 +02:00
}
else if ( show_detailed and not got_detailed and detailed . status ! = " Dead " ) {
detailed . status = " Dead " ;
redraw = true ;
}
2021-07-26 01:06:34 +02:00
2021-08-11 23:21:33 +02:00
old_cputimes = cputimes ;
2021-07-04 01:18:48 +02:00
}
2021-08-11 23:21:33 +02:00
//* ---------------------------------------------Collection done-----------------------------------------------
2021-07-26 01:06:34 +02:00
2021-08-17 22:33:21 +02:00
//* Match filter if defined
2021-08-22 16:04:01 +02:00
if ( should_filter ) {
filter_found = 0 ;
for ( auto & p : current_procs ) {
if ( not tree and not filter . empty ( ) ) {
2022-04-30 19:08:27 +02:00
if ( not s_contains_ic ( to_string ( p . pid ) , filter )
and not s_contains_ic ( p . name , filter )
and not s_contains_ic ( p . cmd , filter )
and not s_contains_ic ( p . user , filter ) ) {
2021-08-17 22:33:21 +02:00
p . filtered = true ;
filter_found + + ;
2021-08-22 16:04:01 +02:00
}
else {
p . filtered = false ;
2021-08-17 22:33:21 +02:00
}
2021-08-22 16:04:01 +02:00
}
else {
p . filtered = false ;
2021-08-17 22:33:21 +02:00
}
}
}
2022-07-03 12:37:54 +02:00
//* Sort processes
if ( sorted_change or not no_update ) {
proc_sorter ( current_procs , sorting , reverse , tree ) ;
}
2021-06-19 14:57:27 +02:00
//* Generate tree view if enabled
2021-08-22 16:04:01 +02:00
if ( tree and ( not no_update or should_filter or sorted_change ) ) {
2022-07-03 12:37:54 +02:00
bool locate_selection = false ;
2021-08-17 22:33:21 +02:00
if ( auto find_pid = ( collapse ! = - 1 ? collapse : expand ) ; find_pid ! = - 1 ) {
auto collapser = rng : : find ( current_procs , find_pid , & proc_info : : pid ) ;
if ( collapser ! = current_procs . end ( ) ) {
if ( collapse = = expand ) {
collapser - > collapsed = not collapser - > collapsed ;
}
else if ( collapse > - 1 ) {
collapser - > collapsed = true ;
}
else if ( expand > - 1 ) {
collapser - > collapsed = false ;
}
2022-07-03 12:37:54 +02:00
if ( Config : : ints . at ( " proc_selected " ) > 0 ) locate_selection = true ;
2021-08-17 22:33:21 +02:00
}
2021-08-22 16:04:01 +02:00
collapse = expand = - 1 ;
2021-07-29 23:40:56 +02:00
}
2021-08-22 16:04:01 +02:00
if ( should_filter or not filter . empty ( ) ) filter_found = 0 ;
2021-07-29 23:40:56 +02:00
2022-07-03 12:37:54 +02:00
vector < tree_proc > tree_procs ;
2021-08-17 22:33:21 +02:00
tree_procs . reserve ( current_procs . size ( ) ) ;
2021-06-20 22:07:04 +02:00
2022-07-03 12:37:54 +02:00
for ( auto & p : current_procs ) {
if ( not v_contains ( found , p . ppid ) ) p . ppid = 0 ;
}
2021-06-20 22:07:04 +02:00
//? Stable sort to retain selected sorting among processes with the same parent
2022-07-03 12:37:54 +02:00
rng : : stable_sort ( current_procs , rng : : less { } , & proc_info : : ppid ) ;
2021-06-20 22:07:04 +02:00
2021-06-27 01:19:57 +02:00
//? Start recursive iteration over processes with the lowest shared parent pids
2021-08-22 16:04:01 +02:00
for ( auto & p : rng : : equal_range ( current_procs , current_procs . at ( 0 ) . ppid , rng : : less { } , & proc_info : : ppid ) ) {
_tree_gen ( p , current_procs , tree_procs , 0 , false , filter , false , no_update , should_filter ) ;
2021-06-19 14:57:27 +02:00
}
2021-07-18 15:44:32 +02:00
2022-07-03 12:37:54 +02:00
//? Recursive sort over tree structure to account for collapsed processes in the tree
int index = 0 ;
tree_sort ( tree_procs , sorting , reverse , index , current_procs . size ( ) ) ;
//? Add tree begin symbol to first item if childless
2022-10-02 13:20:15 +02:00
if ( tree_procs . size ( ) > 0 and tree_procs . front ( ) . children . empty ( ) and tree_procs . front ( ) . entry . get ( ) . prefix . size ( ) > = 8 )
2022-07-03 12:37:54 +02:00
tree_procs . front ( ) . entry . get ( ) . prefix . replace ( tree_procs . front ( ) . entry . get ( ) . prefix . size ( ) - 8 , 8 , " ┌─ " ) ;
//? Add tree terminator symbol to last item if childless
2022-10-02 13:20:15 +02:00
if ( tree_procs . size ( ) > 0 and tree_procs . back ( ) . children . empty ( ) and tree_procs . back ( ) . entry . get ( ) . prefix . size ( ) > = 8 )
2022-07-03 12:37:54 +02:00
tree_procs . back ( ) . entry . get ( ) . prefix . replace ( tree_procs . back ( ) . entry . get ( ) . prefix . size ( ) - 8 , 8 , " └─ " ) ;
2021-08-22 16:04:01 +02:00
//? Final sort based on tree index
2022-07-03 12:37:54 +02:00
rng : : sort ( current_procs , rng : : less { } , & proc_info : : tree_index ) ;
//? Move current selection/view to the selected process when collapsing/expanding in the tree
if ( locate_selection ) {
int loc = rng : : find ( current_procs , Proc : : selected_pid , & proc_info : : pid ) - > tree_index ;
if ( Config : : ints . at ( " proc_start " ) > = loc or Config : : ints . at ( " proc_start " ) < = loc - Proc : : select_max )
Config : : ints . at ( " proc_start " ) = max ( 0 , loc - 1 ) ;
Config : : ints . at ( " proc_selected " ) = loc - Config : : ints . at ( " proc_start " ) + 1 ;
}
2021-06-19 14:57:27 +02:00
}
2021-08-22 16:04:01 +02:00
numpids = ( int ) current_procs . size ( ) - filter_found ;
2021-07-26 01:06:34 +02:00
2021-08-17 22:33:21 +02:00
return current_procs ;
2021-06-19 14:57:27 +02:00
}
}
2021-08-10 20:20:33 +02:00
namespace Tools {
double system_uptime ( ) {
string upstr ;
ifstream pread ( Shared : : procPath / " uptime " ) ;
2021-09-26 01:03:57 +02:00
if ( pread . good ( ) ) {
try {
getline ( pread , upstr , ' ' ) ;
pread . close ( ) ;
return stod ( upstr ) ;
}
catch ( const std : : invalid_argument & ) { }
catch ( const std : : out_of_range & ) { }
}
2023-08-26 20:29:43 +02:00
throw std : : runtime_error ( " Failed to get uptime from " + string { Shared : : procPath } + " /uptime " ) ;
2021-08-10 20:20:33 +02:00
}
2021-11-17 00:08:05 +01:00
}