Skip to content

Add support for Intel turbo frequency throttling through intel_pstate #227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mbpfan.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ low_temp = 63 # try ranges 55-63, default is 63
high_temp = 66 # try ranges 58-66, default is 66
max_temp = 86 # take highest number returned by "cat /sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_max", divide by 1000
polling_interval = 1 # default is 1 seconds
intel_pstate_control = 0 # whether mbpfan should throttle down max turbo speed when exceeding max temp
4 changes: 4 additions & 0 deletions src/daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "global.h"
#include "daemon.h"
#include "util.h"
#include "intel_pstate.h"

int daemonize = 1;
int verbose = 0;
Expand Down Expand Up @@ -108,6 +109,9 @@ static void cleanup_and_exit(int exit_code)
sensors = next_sensor;
}

intel_pstate_exit(intel_pstate);
free(intel_pstate);

exit(exit_code);
}

Expand Down
148 changes: 148 additions & 0 deletions src/intel_pstate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* intel_pstate.c - automatically control turbo frequency for MacBook Pro
* Copyright (C) 2021 Gokturk Yuksek <[email protected]>
*
* 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 3 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.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>

#include "intel_pstate.h"
#include "util.h"
#include <syslog.h>

static int read_int(FILE *fp, int *val)
{
char buf[4]; /* Maximum we can read in this module is '100\0' */
ssize_t ret;

ret = pread(fileno(fp), buf, sizeof(buf), 0);
if (ret >= 0) {
buf[ret] = '\0';
*val = atoi(buf);
return 0;
} else {
return (int)ret;
}
}

static inline int write_str(FILE *fp, char *str, int len)
{
ssize_t ret;
ret = (int)pwrite(fileno(fp), str, len, 0);
if (ret == len)
return 0;
else
return -1;
}

static int write_int(FILE *fp, int val)
{
char buf[4]; /* Maximum we can read in this module is '100\0' */
int ret;

ret = snprintf(buf, sizeof(buf), "%d", val);
if (ret < 0)
return ret;

return write_str(fp, buf, ret);
}

int intel_pstate_init(t_intel_pstate *intel_pstate)
{
const char *path_max_perf_pct = INTEL_PT_PATH "/max_perf_pct";
const char *path_min_perf_pct = INTEL_PT_PATH "/min_perf_pct";
FILE *fp;
int err;

fp = fopen(path_max_perf_pct, "w+");
if (!fp)
return -1;
/* Save the initial value of max_perf_pct, to restore it on exit */
err = read_int(fp, &intel_pstate->preserved_max_perf_pct);
if (err)
return err;
/* Start with the maximum performance percentage at 100% */
err = write_str(fp, "100", 4);
if (err)
return err;
intel_pstate->f_max_perf_pct = fp;

fp = fopen(path_min_perf_pct, "w+");
if (!fp)
return -1;
/* Save the initial value of min_perf_pct, to restore it on exit */
err = read_int(fp, &intel_pstate->preserved_min_perf_pct);
if (err)
return err;
/* Set the minimum performance percentage to 0 */
err = write_str(fp, "0", 2);
if (err)
return err;
intel_pstate->f_min_perf_pct = fp;

return 0;
}

int intel_pstate_is_available(void)
{
DIR* dir = opendir(INTEL_PT_PATH);

if ((!dir) && ENOENT == errno)
return 0;

closedir(dir);
return 1;
}

int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step)
{
int val;
int err;

if (!intel_pstate)
return 1;

err = read_int(intel_pstate->f_max_perf_pct, &val);
if (err)
return err;

mbp_log(LOG_INFO, "Adjusting intel_pstate: val: %d, step: %d", val, step);

val += step;
if (val < 0)
val = 0;
if (val > 100)
val = 100;

return write_int(intel_pstate->f_max_perf_pct, val);
}

void intel_pstate_exit(t_intel_pstate *intel_pstate)
{
if (!intel_pstate)
return;

(void)write_int(intel_pstate->f_max_perf_pct, intel_pstate->preserved_max_perf_pct);
(void)write_int(intel_pstate->f_min_perf_pct, intel_pstate->preserved_min_perf_pct);

fclose(intel_pstate->f_max_perf_pct);
fclose(intel_pstate->f_min_perf_pct);

memset(intel_pstate, 0, sizeof(*intel_pstate));
}
38 changes: 38 additions & 0 deletions src/intel_pstate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* intel_pstate.c - automatically control turbo frequency for MacBook Pro
* Copyright (C) 2021 Gokturk Yuksek <[email protected]>
*
* 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 3 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.
*
*/

#ifndef _INTEL_PT_H_
#define _INTEL_PT_H_

#define INTEL_PT_PATH "/sys/devices/system/cpu/intel_pstate/"

struct s_intel_pstate {
FILE *f_max_perf_pct;
FILE *f_min_perf_pct;
int preserved_max_perf_pct;
int preserved_min_perf_pct;
};

typedef struct s_intel_pstate t_intel_pstate;

extern t_intel_pstate *intel_pstate;

extern int intel_pstate_init(t_intel_pstate *intel_pstate);
extern int intel_pstate_is_available(void);
extern int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step);
extern void intel_pstate_exit(t_intel_pstate *intel_pstate);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does extern have a meaning here? We don't do this for other function prototypes.


#endif /*_INTEL_PT_H_*/
37 changes: 37 additions & 0 deletions src/mbpfan.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "global.h"
#include "settings.h"
#include "util.h"
#include "intel_pstate.h"

/* lazy min/max... */
#define min(a,b) ((a) < (b) ? (a) : (b))
Expand All @@ -61,6 +62,9 @@ int low_temp = 63; // try ranges 55-63
int high_temp = 66; // try ranges 58-66
int max_temp = 86; // do not set it > 90

/* Whether mbpfan should control the turbo frequency or not */
int intel_pstate_control = 0; // disabled by default

// maximum number of processors etc supported
#define NUM_PROCESSORS 6
#define NUM_HWMONS 12
Expand All @@ -74,6 +78,7 @@ int polling_interval = 1;

t_sensors* sensors = NULL;
t_fans* fans = NULL;
t_intel_pstate* intel_pstate = NULL;

char *smprintf(const char *fmt, ...)
{
Expand Down Expand Up @@ -524,6 +529,12 @@ void retrieve_settings(const char* settings_path, t_fans* fans)
polling_interval = result;
}

result = settings_get_int(settings, "general", "intel_pstate_control");

if (result != 0) {
intel_pstate_control = (result == 0) ? 0 : 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this always 1? So you can just set intel_pstate_control = result.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be a case where it's not in the config file? Anyone upgrading from a previous release may not immediately update their config file.

Copy link
Member

@gaul gaul Mar 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we must handle the missing value case as well. But it appears that settings_get_int returns 0 when it is missing, the same as disabled?

}

/* Destroy the settings object */
settings_delete(settings);
}
Expand Down Expand Up @@ -573,12 +584,31 @@ void mbpfan()
{
int old_temp, new_temp, fan_speed, steps;
int temp_change;
int err;

sensors = retrieve_sensors();
fans = retrieve_fans();

retrieve_settings(NULL, fans);

if (intel_pstate_control) {
if (!intel_pstate_is_available()) {
mbp_log(LOG_ERR, "Intel pstate control is requested but not available");
exit(EXIT_FAILURE);
} else {
intel_pstate = (t_intel_pstate*)malloc(sizeof(t_intel_pstate));
if (!intel_pstate) {
mbp_log(LOG_ERR, "Failed to allocate memory for intel_pstate control");
exit(EXIT_FAILURE);
}
err = intel_pstate_init(intel_pstate);
if (err) {
mbp_log(LOG_ERR, "Failed to initialize intel_pstate control");
exit(EXIT_FAILURE);
}
}
}

t_fans* fan = fans;
while(fan != NULL) {

Expand Down Expand Up @@ -620,6 +650,13 @@ void mbpfan()
old_temp = new_temp;
new_temp = get_temp(sensors);

if (new_temp <= high_temp) /* maximize turbo below high_temp */
intel_pstate_adjust(intel_pstate, +100);
else if (new_temp >= max_temp) /* throttle down to keep the temp in control */
intel_pstate_adjust(intel_pstate, -4);
else if ((new_temp - old_temp) <= -3) /* core is cooling down, increase turbo */
intel_pstate_adjust(intel_pstate, +1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with stress --cpu 4 on a MBA 2014 and found that the pstate gradually decreased under load but after removing the load it stayed in one state for a while then jumped by a lot:

Adjusting intel_pstate: val: 40, step: -4
Old Temp: 85 New Temp: 86 Fan: Exhaust Speed: 6500
Sleeping for 1 seconds
Old Temp: 86 New Temp: 84 Fan: Exhaust Speed: 6443
Sleeping for 1 seconds
Old Temp: 84 New Temp: 83 Fan: Exhaust Speed: 6386
Sleeping for 1 seconds
Old Temp: 83 New Temp: 81 Fan: Exhaust Speed: 6215
Sleeping for 1 seconds
Old Temp: 81 New Temp: 81 Fan: Exhaust Speed: 6215
Sleeping for 1 seconds
Old Temp: 81 New Temp: 80 Fan: Exhaust Speed: 6101
Sleeping for 1 seconds
Old Temp: 80 New Temp: 80 Fan: Exhaust Speed: 6101
Sleeping for 1 seconds
Old Temp: 80 New Temp: 79 Fan: Exhaust Speed: 5968
Sleeping for 1 seconds
Old Temp: 79 New Temp: 79 Fan: Exhaust Speed: 5968
Sleeping for 1 seconds
Old Temp: 79 New Temp: 78 Fan: Exhaust Speed: 5816
Sleeping for 1 seconds
Old Temp: 78 New Temp: 78 Fan: Exhaust Speed: 5816
Sleeping for 1 seconds
Old Temp: 78 New Temp: 78 Fan: Exhaust Speed: 5816
Sleeping for 1 seconds
Old Temp: 78 New Temp: 77 Fan: Exhaust Speed: 5645
Sleeping for 1 seconds
Old Temp: 77 New Temp: 77 Fan: Exhaust Speed: 5645
Sleeping for 1 seconds
Old Temp: 77 New Temp: 77 Fan: Exhaust Speed: 5645
Sleeping for 1 seconds
Old Temp: 77 New Temp: 76 Fan: Exhaust Speed: 5455
Sleeping for 1 seconds
Old Temp: 76 New Temp: 75 Fan: Exhaust Speed: 5246
Sleeping for 1 seconds
Old Temp: 75 New Temp: 77 Fan: Exhaust Speed: 5246
Sleeping for 1 seconds
Old Temp: 77 New Temp: 77 Fan: Exhaust Speed: 5246
Sleeping for 1 seconds
Old Temp: 77 New Temp: 76 Fan: Exhaust Speed: 5246
Sleeping for 1 seconds
Adjusting intel_pstate: val: 36, step: 1
Old Temp: 76 New Temp: 73 Fan: Exhaust Speed: 4771
Sleeping for 1 seconds
Old Temp: 73 New Temp: 73 Fan: Exhaust Speed: 4771
Sleeping for 1 seconds
Old Temp: 73 New Temp: 72 Fan: Exhaust Speed: 4505
Sleeping for 1 seconds
Old Temp: 72 New Temp: 72 Fan: Exhaust Speed: 4505
Sleeping for 1 seconds
Old Temp: 72 New Temp: 71 Fan: Exhaust Speed: 4220
Sleeping for 1 seconds
Old Temp: 71 New Temp: 70 Fan: Exhaust Speed: 3916
Sleeping for 1 seconds
Old Temp: 70 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 69 Fan: Exhaust Speed: 3593
Sleeping for 1 seconds
Old Temp: 69 New Temp: 68 Fan: Exhaust Speed: 3251
Sleeping for 1 seconds
Old Temp: 68 New Temp: 68 Fan: Exhaust Speed: 3251
Sleeping for 1 seconds
Old Temp: 68 New Temp: 67 Fan: Exhaust Speed: 2890
Sleeping for 1 seconds
Old Temp: 67 New Temp: 67 Fan: Exhaust Speed: 2890
Sleeping for 1 seconds
Old Temp: 67 New Temp: 68 Fan: Exhaust Speed: 2890
Sleeping for 1 seconds
Old Temp: 68 New Temp: 67 Fan: Exhaust Speed: 2890
Sleeping for 1 seconds
Old Temp: 67 New Temp: 67 Fan: Exhaust Speed: 2890
Sleeping for 1 seconds
Adjusting intel_pstate: val: 37, step: 100
Old Temp: 67 New Temp: 66 Fan: Exhaust Speed: 2510

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing you use the default value of high_temp in mbpfan.conf. This was a deliberate design decision by me to maximize the performance when the temperature is below high_temp. Note that setting the pstate doesn't directly change the CPU frequency. An SMT core would opportunistically engage turbo on its own under certain type of loads and then disengage. If a core is stalling on a cache miss for one of its threads, it wouldn't engage turbo for example. If you schedule two compute-bound jobs on the same physical SMT core and the IPC is high, then the core can bet that increasing the clock would provide even more IPC and engage turbo.

Let's take your log for example. The turbo % goes from 40 to 36 at the beginning and the temperature starts falling. At this point, it might be falling because we capped the maximum turbo frequency, or it could be because the load has decreased and turbo isn't kicking in like it used to. There is also the thermal lag, which is apparent in the graph I posted above. Notice how in the middle sections of the graph, every time turbo pct is reduced, it's reflected in the temperature only a few seconds after.

After seeing a temperature drop of 3C (from 76C to 73C), the pstate controller assumes that it's safe to increase the turbo pct by 1% up to 37%. Clearly, if I increase turbo for 1% for every 3C drop in the temperature, I'll never get back to 100%. At low temperatures. you do want the turbo to be at 100%. The fans will be enough to handle short, bursty turbo kick-ins that improve performance. So below high_temp, I restore it back to 100%. Note again that, restoring it to 100% doesn't affect the CPU frequency under no load, which you might have observed yourself too. Notice how in the graph I posted above, at the end the turbo pct is set to 100% yet the temperature keeps dropping.

Why decrease by 4% at a time? If you look at the beginning of the graph, you'll see that in a manner of seconds the temperature jumps from ~42C to ~77C. With a polling frequency of 1 second and a turbo reduction of 1%, we can never respond to that type of increase in a timely manner. So we decrease aggressively, increase opportunistically. Why increase by 1% only after a 3C drop in temperature? I originally coded with 1C and the change was very jittery. 3C allows for smoother transitions.

The relationship between the turbo and the temperature is unlike the fan speed and the temperature. With the fan, you want higher fan speed at high temperatures, and lower fan speed at lower temperatures. With the turbo, because it's an opportunistic performance mechanism, you want it at maximum at low temperatures until the fans are not enough to contain the increase temperature. My mbpro will settle at 79C with a turbo pct of 80% under hours-long compilation workloads. However, for web browsing and things like that, it will have no issues running at 100% turbo.

Sorry this has been a relatively long response to a comment. It's just more intricate than fan control. I'm more than open to suggestions of course.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with the latest PR doing a dnf update. I observed similar results and I still don't understand the intended behavior. In my mind, if the CPU is throttled by a lower pstate, the fan should run as fast as possible to allow the pstate to increase as soon as possible. I am testing on a MBA 2014 with default config -- maybe we observe different behavior based on hardware?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gktrk Do we have a path forward here? I still don't understand the intended behavior.


fan = fans;

while(fan != NULL) {
Expand Down