mirror of
https://github.com/pi-hole/FTL.git
synced 2025-12-20 04:18:25 +00:00
Merge pull request #2647 from pi-hole/tweak/improve_CPU_reporting
Improve CPU utilization reporting
This commit is contained in:
@@ -551,6 +551,8 @@ components:
|
||||
type: boolean
|
||||
readOnly:
|
||||
type: boolean
|
||||
normalizeCPU:
|
||||
type: boolean
|
||||
check:
|
||||
type: object
|
||||
properties:
|
||||
@@ -834,6 +836,7 @@ components:
|
||||
dnsmasq_lines: [ ]
|
||||
extraLogging: false
|
||||
readOnly: false
|
||||
normalizeCPU: false
|
||||
check:
|
||||
load: true
|
||||
shmem: 90
|
||||
|
||||
@@ -1416,6 +1416,12 @@ void initConfig(struct config *conf)
|
||||
conf->misc.readOnly.d.b = false;
|
||||
conf->misc.readOnly.c = validate_stub; // Only type-based checking
|
||||
|
||||
conf->misc.normalizeCPU.k = "misc.normalizeCPU";
|
||||
conf->misc.normalizeCPU.h = "Should FTL normalize reported CPU usage?\n\n On multi-core systems, a high workload can lead to CPU usage numbers exceeding 100% (e.g., 200% on a quad-core system if two out of four cores are fully utilized). This may look alarming at first glance even though the system is not actually overloaded. Enabling this setting will divide the CPU usage by the number of cores, leading to more intuitive numbers (e.g., 50% for the same load on a quad-core system).\n Note that this setting only affects how CPU usage is *reported*, it does not change the actual CPU usage.";
|
||||
conf->misc.normalizeCPU.t = CONF_BOOL;
|
||||
conf->misc.normalizeCPU.d.b = true;
|
||||
conf->misc.normalizeCPU.c = validate_stub; // Only type-based checking
|
||||
|
||||
// sub-struct misc.check
|
||||
conf->misc.check.load.k = "misc.check.load";
|
||||
conf->misc.check.load.h = "Pi-hole is very lightweight on resources. Nevertheless, this does not mean that you should run Pi-hole on a server that is otherwise extremely busy as queuing on the system can lead to unnecessary delays in DNS operation as the system becomes less and less usable as the system load increases because all resources are permanently in use. To account for this, FTL regularly checks the system load. To bring this to your attention, FTL warns about excessive load when the 15 minute system load average exceeds the number of cores.\n\n This check can be disabled with this setting.";
|
||||
|
||||
@@ -316,6 +316,7 @@ struct config {
|
||||
struct conf_item dnsmasq_lines;
|
||||
struct conf_item extraLogging;
|
||||
struct conf_item readOnly;
|
||||
struct conf_item normalizeCPU;
|
||||
struct {
|
||||
struct conf_item load;
|
||||
struct conf_item shmem;
|
||||
|
||||
52
src/daemon.c
52
src/daemon.c
@@ -472,53 +472,35 @@ static float ftl_cpu_usage = 0.0f;
|
||||
static float total_cpu_usage = 0.0f;
|
||||
void calc_cpu_usage(const unsigned int interval)
|
||||
{
|
||||
// Get the current resource usage
|
||||
// RUSAGE_SELF means here "the calling process" which is the sum of all
|
||||
// resources used by all threads in the process
|
||||
struct rusage usage = { 0 };
|
||||
if(getrusage(RUSAGE_SELF, &usage) != 0)
|
||||
{
|
||||
log_err("Unable to obtain CPU usage: %s (%i)", strerror(errno), errno);
|
||||
return;
|
||||
}
|
||||
// Get number of cores if we are normalizing CPU usage below
|
||||
const unsigned int norm_factor = config.misc.normalizeCPU.v.b ? get_nprocs_conf() : 1;
|
||||
|
||||
// Calculate the CPU usage: it is the total time spent in user mode and
|
||||
// kernel mode by this process since the total time since the last call
|
||||
// to this function. 100% means one core is fully used, 200% means two
|
||||
// cores are fully used, etc.
|
||||
const float ftl_cpu_time = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec + 1e-6 * (usage.ru_utime.tv_usec + usage.ru_stime.tv_usec);
|
||||
// Get the current FTL CPU time
|
||||
const double ftl_cpu_time = parse_proc_self_stat();
|
||||
|
||||
// Calculate the CPU usage in this interval
|
||||
static float last_ftl_cpu_time = 0.0f;
|
||||
ftl_cpu_usage = 100.0 * (ftl_cpu_time - last_ftl_cpu_time) / interval;
|
||||
|
||||
// Store the current time for the next call to this function
|
||||
static double last_ftl_cpu_time = 0.0f;
|
||||
ftl_cpu_usage = 100.0 * (ftl_cpu_time - last_ftl_cpu_time) / interval / norm_factor;
|
||||
last_ftl_cpu_time = ftl_cpu_time;
|
||||
|
||||
// Calculate the total CPU usage
|
||||
unsigned long total_total, total_idle;
|
||||
if(!parse_proc_stat(&total_total, &total_idle))
|
||||
{
|
||||
total_cpu_usage = -1.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
const double cpu_time = parse_proc_stat();
|
||||
|
||||
// Calculate the CPU usage since the last call to this function
|
||||
static unsigned long last_total_total = 0, last_total_idle = 0;
|
||||
if(total_total - last_total_total > 0)
|
||||
total_cpu_usage = 100.0 * (total_total - last_total_total - (total_idle - last_total_idle)) / (total_total - last_total_total);
|
||||
static double last_cpu_time = 0.0f;
|
||||
total_cpu_usage = 100.0 * (cpu_time - last_cpu_time) / interval / norm_factor;
|
||||
last_cpu_time = cpu_time;
|
||||
|
||||
// Store the current time for the next call to this function
|
||||
last_total_idle = total_idle;
|
||||
last_total_total = total_total;
|
||||
|
||||
log_debug(DEBUG_EXTRA, "CPU usage in the last %u seconds: FTL %.4f%%, total %.4f%%",
|
||||
interval, ftl_cpu_usage, total_cpu_usage);
|
||||
log_debug(DEBUG_EXTRA, "CPU usage in the last %u seconds: FTL %.4f%%, total %.4f%% (normalized by factor %ux)",
|
||||
interval, ftl_cpu_usage, total_cpu_usage, norm_factor);
|
||||
}
|
||||
|
||||
float __attribute__((pure)) get_ftl_cpu_percentage(void)
|
||||
{
|
||||
return ftl_cpu_usage;
|
||||
// Return the smaller of the two values to avoid showing a CPU usage
|
||||
// higher than the total CPU usage. This can happen due to rounding
|
||||
// errors and rare interval comparison differences.
|
||||
return ftl_cpu_usage < total_cpu_usage ? ftl_cpu_usage : total_cpu_usage;
|
||||
}
|
||||
|
||||
float __attribute__((pure)) get_total_cpu_percentage(void)
|
||||
|
||||
81
src/procps.c
81
src/procps.c
@@ -274,24 +274,18 @@ bool parse_proc_meminfo(struct proc_meminfo *mem)
|
||||
|
||||
|
||||
/**
|
||||
* @brief Parses the /proc/stat file to extract CPU statistics.
|
||||
* @brief Parses the /proc/stat file to extract total CPU usage statistics.
|
||||
*
|
||||
* This function reads the /proc/stat file to gather CPU usage statistics,
|
||||
* including user, system, idle, iowait, irq, softirq, steal, guest, and guest_nice times.
|
||||
* It calculates the total CPU time and idle time, and stores these values in the provided
|
||||
* pointers.
|
||||
*
|
||||
* @param total_sum Pointer to store the total CPU time.
|
||||
* @param idle_sum Pointer to store the idle CPU time.
|
||||
* @return true if the parsing is successful, false otherwise.
|
||||
* @return The total CPU time (in seconds) spent in user, nice, and system modes,
|
||||
* or -1.0 if an error occurs (e.g., file cannot be opened or parsed).
|
||||
*/
|
||||
bool parse_proc_stat(unsigned long *total_sum, unsigned long *idle_sum)
|
||||
double parse_proc_stat(void)
|
||||
{
|
||||
FILE *statfile = fopen("/proc/stat", "r");
|
||||
if(statfile == NULL)
|
||||
return false;
|
||||
return -1.0;
|
||||
|
||||
unsigned long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
|
||||
unsigned long user, nice, system;
|
||||
/*
|
||||
user (1) Time spent in user mode. (includes guest and guest_nice time)
|
||||
|
||||
@@ -326,45 +320,70 @@ bool parse_proc_stat(unsigned long *total_sum, unsigned long *idle_sum)
|
||||
|
||||
// Read the file until we find the first line starting with "cpu "
|
||||
char line[256];
|
||||
bool found = false;
|
||||
while(fgets(line, sizeof(line), statfile))
|
||||
{
|
||||
if(strncmp(line, "cpu ", 4) == 0)
|
||||
{
|
||||
if(sscanf(line, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
|
||||
&user, &nice, &system, &idle,
|
||||
&iowait, &irq, &softirq, &steal,
|
||||
&guest, &guest_nice) != 10)
|
||||
if(sscanf(line, "cpu %lu %lu %lu",
|
||||
&user, &nice, &system) != 3)
|
||||
{
|
||||
log_debug(DEBUG_ANY, "Failed to parse CPU line in /proc/stat");
|
||||
fclose(statfile);
|
||||
return false;
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (feof(statfile)) {
|
||||
if(!found) {
|
||||
log_warn("No CPU line found in /proc/stat");
|
||||
fclose(statfile);
|
||||
return false;
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
fclose(statfile);
|
||||
|
||||
// Guest time is already accounted in usertime
|
||||
user -= guest;
|
||||
nice -= guest_nice;
|
||||
const long ticks = sysconf(_SC_CLK_TCK);
|
||||
return (user + nice + system) / (double)ticks;
|
||||
}
|
||||
|
||||
// Fields existing on kernels >= 2.6
|
||||
// (and RHEL's patched kernel 2.4...)
|
||||
const unsigned long long int sys_all = system + irq + softirq;
|
||||
const unsigned long long int virtual = guest + guest_nice;
|
||||
const unsigned long long int busy_sum = user + nice + sys_all + steal + virtual;
|
||||
*idle_sum = idle + iowait;
|
||||
*total_sum = busy_sum + *idle_sum;
|
||||
/**
|
||||
* @brief Parses the /proc/self/stat file to retrieve the used CPU time for the
|
||||
* current process.
|
||||
*
|
||||
* This function opens the /proc/self/stat file, skips the first 13 fields, and
|
||||
* reads the user mode (utime) and kernel mode (stime) CPU times. It then
|
||||
* converts the sum of these times from clock ticks to seconds using the
|
||||
* system's clock tick rate.
|
||||
*
|
||||
* @return The total CPU time (user + system) in seconds for the current
|
||||
* process, or -1.0 if an error occurs (e.g., file cannot be opened,
|
||||
* parsing fails, or invalid clock tick rate).
|
||||
*/
|
||||
double parse_proc_self_stat(void)
|
||||
{
|
||||
// Open /proc/self/stat
|
||||
FILE *file = fopen("/proc/self/stat", "r");
|
||||
if(file == NULL)
|
||||
return -1.0;
|
||||
|
||||
return true;
|
||||
// Read utime and stime
|
||||
unsigned long utime = 0, stime = 0;
|
||||
const bool parsed = fscanf(file, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &utime, &stime) == 2;
|
||||
fclose(file);
|
||||
|
||||
// If we could not parse the file, return -1.0
|
||||
if(!parsed)
|
||||
return -1.0;
|
||||
|
||||
// Convert clock ticks to seconds
|
||||
const long ticks = sysconf(_SC_CLK_TCK);
|
||||
if(ticks <= 0)
|
||||
return -1.0;
|
||||
|
||||
return (utime + stime) / (double)ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,8 +47,9 @@ struct proc_meminfo {
|
||||
};
|
||||
|
||||
bool getProcessMemory(struct proc_mem *mem, const unsigned long total_memory);
|
||||
double parse_proc_self_stat(void);
|
||||
bool parse_proc_meminfo(struct proc_meminfo *mem);
|
||||
bool parse_proc_stat(unsigned long *total_sum, unsigned long *idle_sum);
|
||||
double parse_proc_stat(void);
|
||||
pid_t search_proc(const char *name);
|
||||
|
||||
#endif // PROCPS_H
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Pi-hole configuration file (v6.2.3-274-g68959ed0) on branch new/dns_domainLocal
|
||||
# Pi-hole configuration file (v6.2.3-301-gfcee3894-dirty) on branch tweak/api_info_system_ftl
|
||||
# Encoding: UTF-8
|
||||
# This file is managed by pihole-FTL
|
||||
# Last updated on 2025-09-28 20:09:54 UTC
|
||||
# Last updated on 2025-10-06 08:00:17 UTC
|
||||
|
||||
[dns]
|
||||
# Upstream DNS Servers to be used by Pi-hole. If this is not set, Pi-hole will not
|
||||
@@ -1399,6 +1399,20 @@
|
||||
# true or false
|
||||
readOnly = false
|
||||
|
||||
# Should FTL normalize reported CPU usage?
|
||||
#
|
||||
# On multi-core systems, a high workload can lead to CPU usage numbers exceeding 100%
|
||||
# (e.g., 200% on a quad-core system if two out of four cores are fully utilized). This
|
||||
# may look alarming at first glance even though the system is not actually overloaded.
|
||||
# Enabling this setting will divide the CPU usage by the number of cores, leading to
|
||||
# more intuitive numbers (e.g., 50% for the same load on a quad-core system).
|
||||
# Note that this setting only affects how CPU usage is *reported*, it does not change
|
||||
# the actual CPU usage.
|
||||
#
|
||||
# Allowed values are:
|
||||
# true or false
|
||||
normalizeCPU = true
|
||||
|
||||
[misc.check]
|
||||
# Pi-hole is very lightweight on resources. Nevertheless, this does not mean that you
|
||||
# should run Pi-hole on a server that is otherwise extremely busy as queuing on the
|
||||
@@ -1648,7 +1662,7 @@
|
||||
all = true ### CHANGED, default = false
|
||||
|
||||
# Configuration statistics:
|
||||
# 159 total entries out of which 106 entries are default
|
||||
# 160 total entries out of which 107 entries are default
|
||||
# --> 53 entries are modified
|
||||
# 3 entries are forced through environment:
|
||||
# - misc.nice
|
||||
|
||||
Reference in New Issue
Block a user