CPU can be throttled in UI.

This commit is contained in:
Roland 2022-02-07 13:37:58 -07:00
parent 703f916c53
commit 1222a39867
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
8 changed files with 199 additions and 5 deletions

View file

@ -487,3 +487,54 @@ export async function setHotspot(on) {
}
}
}
export async function getThrottleStatus() {
let response = await apiGet(`${API_URI}/throttle`);
if (response.status === 200) {
let {status} = await response.json();
return status;
} else {
let code = (await response.json())['code'];
if (response.status === 400 && code === 34) {
// Must not be running on a RPi. Don't warn the user.
return null;
} else {
toast({
type: 'error',
title: "Can't get throttle status",
description: 'Failed to get CPU Throttle status. Check client and server logs.',
time: 5000,
});
}
}
return null;
}
export async function setThrottle(on) {
let response;
if (on) {
response = await apiPost(`${API_URI}/throttle/on`);
} else {
response = await apiPost(`${API_URI}/throttle/off`);
}
if (response.status === 204) {
return null;
} else {
let code = (await response.json())['code'];
if (code === 34) {
toast({
type: 'error',
title: 'Unsupported!',
description: 'Throttle is only supported on a Raspberry Pi!',
time: 5000,
});
} else {
toast({
type: 'error',
title: 'Error!',
description: 'Could not modify throttle. See server logs.',
time: 5000,
});
}
}
}

View file

@ -21,6 +21,7 @@ import {
secondsToFrequency,
TabLinks,
textEllipsis,
ThrottleToggle,
WROLModeMessage
} from "./Common";
import {Route} from "react-router-dom";
@ -80,6 +81,8 @@ class Settings extends React.Component {
<WROLModeMessage content='Settings are disabled because WROL Mode is enabled.'/>
<HotspotToggle/>
<br/>
<ThrottleToggle style={{marginTop: '0.5em'}}/>
<Divider/>
<p>

View file

@ -5,7 +5,7 @@ import Button from "semantic-ui-react/dist/commonjs/elements/Button";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
import {getConfig} from "../api";
import {useDownloaders, useHotspot} from "../hooks/customHooks";
import {useDownloaders, useHotspot, useThrottle} from "../hooks/customHooks";
export const API_URI = `http://${window.location.host}/api`;
export const VIDEOS_API = `${API_URI}/videos`;
@ -645,9 +645,9 @@ export function CardGroupCentered(props) {
)
}
export function HotspotToggle() {
export function HotspotToggle(props) {
let {on, setHotspot} = useHotspot();
return <Checkbox toggle
return <Checkbox toggle {...props}
label='WiFi Hotspot'
disabled={on === null}
checked={on === true}
@ -655,6 +655,16 @@ export function HotspotToggle() {
/>;
}
export function ThrottleToggle(props) {
let {on, setThrottle} = useThrottle();
return <Checkbox toggle {...props}
label='CPU Power-save'
disabled={on === null}
checked={on === true}
onChange={(e, data) => setThrottle(data.checked)}
/>;
}
export function DisableDownloadsToggle() {
let {on, setDownloads} = useDownloaders();
return <Checkbox toggle

View file

@ -5,11 +5,13 @@ import {
getDownloaders,
getFiles,
getHotspotStatus,
getThrottleStatus,
getVersion,
killDownloads,
searchArchives,
searchVideos,
setHotspot,
setThrottle,
startDownloads
} from "../api";
import {useHistory} from "react-router-dom";
@ -247,3 +249,30 @@ export const useDownloaders = () => {
return {on, setOn, downloaders, setDownloads: localSetDownloads};
}
export const useThrottle = () => {
const [on, setOn] = useState(null);
const fetchThrottleStatus = async () => {
const status = await getThrottleStatus();
if (status === 'powersave') {
setOn(true);
} else if (status === 'ondemand') {
setOn(false);
} else {
setOn(null);
}
}
useEffect(() => {
fetchThrottleStatus();
}, []);
const localSetThrottle = async (on) => {
setOn(null);
await setThrottle(on);
await fetchThrottleStatus();
}
return {on, setOn, setThrottle: localSetThrottle};
}

View file

@ -3,7 +3,7 @@ import pathlib
import subprocess
from wrolpi.common import logger
from wrolpi.vars import PYTEST
from wrolpi.vars import PYTEST, DEFAULT_CPU_FREQUENCY
logger = logger.getChild(__name__)
@ -11,10 +11,21 @@ logger = logger.getChild(__name__)
SUDO_BIN = pathlib.Path('/usr/bin/sudo')
if not SUDO_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND sudo!!!')
NMCLI_BIN = pathlib.Path('/usr/bin/nmcli')
if not NMCLI_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND nmcli!!!')
CPUFREQ_INFO_BIN = pathlib.Path('/usr/bin/cpufreq-info')
if not CPUFREQ_INFO_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND cpufreq-info!!!')
CPUFREQ_SET_BIN = pathlib.Path('/usr/bin/cpufreq-set')
if not CPUFREQ_SET_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND cpufreq-set!!!')
POWER_SAVE_FREQ = 'powersave' # noqa
class HotspotStatus(enum.Enum):
disconnected = enum.auto() # Radio is on, but Hotspot is not connected.
@ -65,3 +76,45 @@ def disable_hotspot():
cmd = (SUDO_BIN, NMCLI_BIN, 'radio', 'wifi', 'off')
subprocess.check_call(cmd)
return True
class GovernorStatus(enum.Enum):
ondemand = enum.auto()
powersave = enum.auto()
unknown = enum.auto()
# These are unused by WROLPi.
# performance = enum.auto()
# schedutil = enum.auto()
# userspace = enum.auto()
# conservative = enum.auto()
GOVERNOR_MAP = {
'The governor "ondemand" may decide ': GovernorStatus.ondemand,
'The governor "powersave" may decide ': GovernorStatus.powersave,
}
def throttle_status() -> GovernorStatus:
cmd = (CPUFREQ_INFO_BIN,)
output = subprocess.check_output(cmd)
output = output.decode().strip()
for line in output.splitlines():
for governor, status in GOVERNOR_MAP.items():
if governor in line:
return status
return GovernorStatus.unknown
def throttle_cpu_on() -> bool:
cmd = (SUDO_BIN, CPUFREQ_SET_BIN, '-g', POWER_SAVE_FREQ)
subprocess.check_call(cmd)
return True
def throttle_cpu_off() -> bool:
cmd = (SUDO_BIN, CPUFREQ_SET_BIN, '-g', DEFAULT_CPU_FREQUENCY)
subprocess.check_call(cmd)
return True

View file

@ -15,7 +15,6 @@ from sanic_ext import validate
from sanic_ext.extensions.openapi import openapi
from wrolpi import admin
from wrolpi.admin import HotspotStatus
from wrolpi.common import set_sanic_url_parts, logger, get_config, wrol_mode_enabled, save_settings_config, \
Base, get_media_directory, wrol_mode_check, native_only
from wrolpi.dates import set_timezone
@ -244,6 +243,38 @@ async def hotspot_off(_: Request):
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
@root_api.get('/throttle')
@openapi.description('Get status of CPU throttling')
@native_only
async def throttle_status(_: Request):
status = admin.throttle_status()
# Get the name of the enum.
status = status.name
ret = dict(status=status)
return json_response(ret)
@root_api.post('/throttle/on')
@openapi.description('Turn on CPU throttling')
@native_only
async def throttle_on(_: Request):
result = admin.throttle_cpu_on()
if result:
return response.empty()
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
@root_api.post('/throttle/off')
@openapi.description('Turn off CPU throttling')
@native_only
async def throttle_off(_: Request):
result = admin.throttle_cpu_off()
if result:
return response.empty()
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):

View file

@ -61,3 +61,19 @@ def test_enable_hotspot_connected():
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'device', 'wifi', 'hotspot', 'ifname',
'wlan0', 'ssid', 'WROLPi', 'password', 'wrolpi hotspot'))]
)
def test_throttle_on():
with mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
assert admin.throttle_cpu_on()
mock_subprocess.check_call.assert_called_once_with(
(PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/cpufreq-set'), '-g', 'powersave')
)
def test_throttle_off():
with mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
assert admin.throttle_cpu_off()
mock_subprocess.check_call.assert_called_once_with(
(PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/cpufreq-set'), '-g', 'ondemand')
)

View file

@ -19,6 +19,7 @@ LAST_MODIFIED_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
DATE_FORMAT = '%Y-%M-%d'
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
DATETIME_FORMAT_MS = '%Y-%m-%d %H:%M:%S.%f'
DEFAULT_CPU_FREQUENCY = 'ondemand'
DEFAULT_FILE_PERMISSIONS = 0o644