* Copyright (C) 2023-2025 Colin Ian King.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "stress-ng.h"
#include "core-attribute.h"
#include "core-builtin.h"
#include "core-cpuidle.h"
#include "core-sort.h"
#include <ctype.h>
static cpu_cstate_t *cpu_cstate_list;
static size_t cpu_cstate_list_len;
#if defined(STRESS_ARCH_X86)
static const char * const busy_state = "C0";
#else
static const char * const busy_state = "BUSY";
#endif
* stress_cpuidle_cstate_list_head()
* return head of C-state list
*/
cpu_cstate_t *stress_cpuidle_cstate_list_head(void)
{
return cpu_cstate_list;
}
#if defined(__linux__)
static PURE int stress_cpuidle_value(const char *cstate)
{
while (isalpha((unsigned char)*cstate))
cstate++;
return atoi(cstate);
}
* stress_cpuidle_cstate_add_unique()
* add a new unique C-state to the C-state list
*/
static void stress_cpuidle_cstate_add_unique(
const char *cstate,
const uint32_t residency)
{
cpu_cstate_t **cc;
cpu_cstate_t *new_cc;
for (cc = &cpu_cstate_list; *cc; cc = &(*cc)->next) {
int cmp;
if (*cstate == 'C')
cmp = stress_cpuidle_value(cstate) - stress_cpuidle_value((*cc)->cstate);
else
cmp = 0;
if (cmp == 0)
cmp = strcmp(cstate, (*cc)->cstate);
if (cmp == 0)
return;
if (cmp < 0)
break;
}
new_cc = (cpu_cstate_t *)malloc(sizeof(*new_cc));
if (UNLIKELY(!new_cc))
return;
new_cc->cstate = shim_strdup(cstate);
if (UNLIKELY(!new_cc->cstate)) {
free(new_cc);
return;
}
new_cc->residency = residency;
new_cc->next = *cc;
*cc = new_cc;
cpu_cstate_list_len++;
}
#endif
* stress_cpuidle_init()
* initialize the C-state CPU idle list
*/
void stress_cpuidle_init(void)
{
#if defined(__linux__)
DIR *cpu_dir;
const struct dirent *cpu_d;
bool has_c0 = false;
cpu_cstate_list = NULL;
cpu_cstate_list_len = 0;
cpu_dir = opendir("/sys/devices/system/cpu");
if (UNLIKELY(!cpu_dir))
return;
* gather all known cpu states for all cpus
*/
while ((cpu_d = readdir(cpu_dir)) != NULL) {
char cpuidle_path[512];
DIR *cpuidle_dir;
const struct dirent *cpuidle_d;
if (strncmp(cpu_d->d_name, "cpu", 3))
continue;
(void)snprintf(cpuidle_path, sizeof(cpuidle_path),
"/sys/devices/system/cpu/%s/cpuidle", cpu_d->d_name);
cpuidle_dir = opendir(cpuidle_path);
if (!cpuidle_dir)
continue;
while ((cpuidle_d = readdir(cpuidle_dir)) != NULL) {
char path[PATH_MAX + 512], data[64], *ptr;
uint32_t residency = 0;
if (strncmp(cpuidle_d->d_name, "state", 5))
continue;
(void)snprintf(path, sizeof(path), "%s/%s/residency", cpuidle_path, cpuidle_d->d_name);
if (stress_system_read(path, data, sizeof(data)) > 0)
residency = (uint32_t)atoi(data);
(void)snprintf(path, sizeof(path), "%s/%s/name", cpuidle_path, cpuidle_d->d_name);
if (stress_system_read(path, data, sizeof(data)) < 1)
continue;
ptr = strchr(data, '\n');
if (ptr)
*ptr = '\0';
if (strcmp(data, "C0") == 0)
has_c0 = true;
stress_cpuidle_cstate_add_unique(data, residency);
}
(void)closedir(cpuidle_dir);
}
(void)closedir(cpu_dir);
if (cpu_cstate_list && !has_c0)
stress_cpuidle_cstate_add_unique(busy_state, 0);
#else
cpu_cstate_list = NULL;
cpu_cstate_list_len = 0;
#endif
}
* stress_cpuidle_free()
* free the C-state CPU idle list
*/
void stress_cpuidle_free(void)
{
cpu_cstate_t *cc = cpu_cstate_list;
while (cc) {
cpu_cstate_t *next = cc->next;
free(cc->cstate);
free(cc);
cc = next;
}
cpu_cstate_list = NULL;
cpu_cstate_list_len = 0;
}
static void stress_cpuidle_read_cstates(
const int which,
stress_cstate_stats_t *cstate_stats)
{
DIR *cpu_dir;
const struct dirent *cpu_d;
cpu_cstate_t *cc;
size_t i;
stress_cstate_stats_t stats;
if (UNLIKELY((which < 0) || (which > 1)))
return;
cpu_dir = opendir("/sys/devices/system/cpu");
if (UNLIKELY(!cpu_dir))
return;
for (i = 0; i < STRESS_CSTATES_MAX; i++) {
stats.valid = false;
stats.time[i] = 0.0;
stats.residency[i] = 0.0;
}
* total up cpu C state timings
*/
while ((cpu_d = readdir(cpu_dir)) != NULL) {
char cpuidle_path[768];
DIR *cpuidle_dir;
const struct dirent *cpuidle_d;
if (strncmp(cpu_d->d_name, "cpu", 3))
continue;
(void)snprintf(cpuidle_path, sizeof(cpuidle_path),
"/sys/devices/system/cpu/%s/cpuidle", cpu_d->d_name);
cpuidle_dir = opendir(cpuidle_path);
if (!cpuidle_dir)
continue;
while ((cpuidle_d = readdir(cpuidle_dir)) != NULL) {
char path[PATH_MAX + 768], cstate[64], data[64], *ptr;
uint64_t cstate_time;
double now;
if (strncmp(cpuidle_d->d_name, "state", 5))
continue;
(void)snprintf(path, sizeof(path), "%s/%s/name", cpuidle_path, cpuidle_d->d_name);
if (stress_system_read(path, cstate, sizeof(cstate)) < 1)
continue;
ptr = strchr(cstate, '\n');
if (ptr)
*ptr = '\0';
(void)snprintf(path, sizeof(path), "%s/%s/time", cpuidle_path, cpuidle_d->d_name);
if (stress_system_read(path, data, sizeof(data)) < 1)
continue;
now = stress_time_now();
cstate_time = 0;
if (sscanf(data, "%" SCNu64, &cstate_time) != 1)
continue;
for (i = 0, cc = cpu_cstate_list; (i < STRESS_CSTATES_MAX) && cc; i++, cc = cc->next) {
if (strcmp(cc->cstate, cstate) == 0) {
stats.time[i] += now;
stats.residency[i] += (double)cstate_time;
stats.valid = true;
break;
}
}
}
(void)closedir(cpuidle_dir);
}
(void)closedir(cpu_dir);
if (which == 0) {
shim_memcpy(cstate_stats, &stats, sizeof(*cstate_stats));
} else {
for (i = 0, cc = cpu_cstate_list; (i < STRESS_CSTATES_MAX) && cc; i++, cc = cc->next) {
cstate_stats->valid = stats.valid;
cstate_stats->time[i] = stats.time[i] - cstate_stats->time[i];
cstate_stats->residency[i] = stats.residency[i] - cstate_stats->residency[i];
}
}
}
void stress_cpuidle_read_cstates_begin(stress_cstate_stats_t *cstate_stats)
{
stress_cpuidle_read_cstates(0, cstate_stats);
}
void stress_cpuidle_read_cstates_end(stress_cstate_stats_t *cstate_stats)
{
stress_cpuidle_read_cstates(1, cstate_stats);
}
void stress_cpuidle_dump(FILE *yaml, stress_stressor_t *stressors_list)
{
stress_stressor_t *ss;
pr_yaml(yaml, "C-states:\n");
for (ss = stressors_list; ss; ss = ss->next) {
size_t i;
int32_t j;
double residencies[STRESS_CSTATES_MAX];
cpu_cstate_t *cc;
double c0_residency = 100.0;
bool valid = false;
if (ss->ignore.run)
continue;
for (i = 0, cc = cpu_cstate_list; (i < STRESS_CSTATES_MAX) && cc; i++, cc = cc->next) {
double duration_us = 0.0;
double residency_us = 0.0;
for (j = 0; j < ss->instances; j++) {
duration_us += ss->stats[j]->cstates.time[i];
residency_us += ss->stats[j]->cstates.residency[i];
valid |= ss->stats[j]->cstates.valid;
}
residencies[i] = (duration_us > 0) ?
100.0 * residency_us / (1000000.0 * duration_us) : 0.0;
c0_residency -= residencies[i];
}
for (; i < STRESS_CSTATES_MAX; i++)
residencies[i] = 0.0;
if (valid) {
pr_inf("%s:\n", ss->stressor->name);
pr_yaml(yaml, " - stressor: %s\n", ss->stressor->name);
for (i = 0, cc = cpu_cstate_list; (i < STRESS_CSTATES_MAX) && cc; i++, cc = cc->next) {
if (strcmp(cc->cstate, busy_state) == 0)
residencies[i] = c0_residency;
pr_inf(" %-5.5s %6.2f%%\n", cc->cstate, residencies[i]);
pr_yaml(yaml, " %s: %.2f\n", cc->cstate, residencies[i]);
}
pr_yaml(yaml, "\n");
}
}
}
* stress_cpuidle_log_info()
* log the C-states, only log them if list contains C-states
*/
void stress_cpuidle_log_info(void)
{
char *buf;
cpu_cstate_t *cc;
size_t len = 1;
if (cpu_cstate_list_len < 1)
return;
for (cc = cpu_cstate_list; cc; cc = cc->next) {
len += strlen(cc->cstate) + 2;
}
buf = (char *)calloc(len, sizeof(*buf));
if (UNLIKELY(!buf))
return;
for (cc = cpu_cstate_list; cc; cc = cc->next) {
(void)shim_strlcat(buf, cc->cstate, len);
if (cc->next)
(void)shim_strlcat(buf, ", ", len);
}
pr_dbg("CPU%s %zu idle state%s: %s\n",
(cpu_cstate_list_len == 1) ? " has" : "s have",
cpu_cstate_list_len, (cpu_cstate_list_len == 1) ? "" : "s", buf);
free(buf);
}