mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 02:48:38 -04:00
CPU can be throttled in UI.
This commit is contained in:
parent
703f916c53
commit
1222a39867
8 changed files with 199 additions and 5 deletions
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue