'use strict'; 'require form'; 'require poll'; 'require rpc'; 'require uci'; 'require ui'; 'require view'; const btnStyleEnabled = 'btn cbi-button-save'; const btnStyleDisabled = 'btn cbi-button-reset'; return view.extend({ appName : 'cpu-perf', initStatus: null, callCpuPerf: rpc.declare({ object: 'luci.cpu-perf', method: 'getCpuPerf', expect: { '': {} } }), callInitStatus: rpc.declare({ object: 'luci', method: 'getInitList', params: [ 'name' ], expect: { '': {} } }), callInitAction: rpc.declare({ object: 'luci', method: 'setInitAction', params: [ 'name', 'action' ], expect: { result: false } }), getInitStatus() { return this.callInitStatus(this.appName).then(res => { if(res) { return res[this.appName].enabled; } else { throw _('Command failed'); } }).catch(e => { ui.addNotification(null, E('p', _('Failed to get %s init status: %s').format(this.appName, e))); }); }, handleServiceAction(action) { return this.callInitAction(this.appName, action).then(success => { if(!success) { throw _('Command failed'); }; return true; }).catch(e => { ui.addNotification(null, E('p', _('Service action failed "%s %s": %s').format(this.appName, action, e))); }); }, serviceRestart(ev) { poll.stop(); return this.handleServiceAction('restart').then(() => { poll.start(); }); }, freqFormat(freq) { if(!freq) { return '-'; }; return (freq >= 1000000) ? (freq / 1000000) + ' ' + _('GHz') : (freq / 1000) + ' ' + _('MHz'); }, updateCpuPerfData() { this.callCpuPerf().then((data) => { if(data.cpus) { for(let i of Object.values(data.cpus)) { let policy = data.freqPolicies[String(i.policy)]; if(policy) { document.getElementById('cpu' + i.number + 'number').textContent = _('CPU') + ' ' + i.number; document.getElementById('cpu' + i.number + 'curFreq').textContent = (policy.sCurFreq) ? this.freqFormat(policy.sCurFreq) : this.freqFormat(policy.curFreq); document.getElementById('cpu' + i.number + 'minFreq').textContent = (policy.sMinFreq) ? this.freqFormat(policy.sMinFreq) : this.freqFormat(policy.minFreq) document.getElementById('cpu' + i.number + 'maxFreq').textContent = (policy.sMaxFreq) ? this.freqFormat(policy.sMaxFreq) : this.freqFormat(policy.maxFreq); document.getElementById('cpu' + i.number + 'governor').textContent = policy.governor || '-'; }; }; }; if(data.ondemand) { document.getElementById('OdUpThreshold').textContent = data.ondemand.upThreshold || '-'; document.getElementById('OdIgnNiceLoad').textContent = (data.ondemand.ignNiceLoad !== undefined) ? data.ondemand.ignNiceLoad : '-'; document.getElementById('OdSmpDownFactor').textContent = data.ondemand.smpDownFactor || '-'; document.getElementById('OdPowersaveBias').textContent = (data.ondemand.powersaveBias !== undefined) ? data.ondemand.powersaveBias : '-'; }; if(data.conservative) { document.getElementById('CoFreqStep').textContent = (data.conservative.freqStep !== undefined) ? data.conservative.freqStep : '-'; document.getElementById('CoDownThreshold').textContent = data.conservative.downThreshold || '-'; document.getElementById('CoSmpDownFactor').textContent = data.conservative.smpDownFactor || '-'; }; if(data.eas) { document.getElementById('EasSchedEnergyAware').textContent = (data.eas.schedEnergyAware !== undefined) ? data.eas.schedEnergyAware : '-'; }; if(data.pcieAspm) { document.getElementById('PaCurrentPolicy').textContent = data.pcieAspm.currentPolicy || '-'; }; }).catch(e => {}); }, CBIBlockPerf: form.Value.extend({ __name__ : 'CBI.BlockPerf', __init__(map, section, ctx, perfData) { this.map = map; this.section = section; this.ctx = ctx; this.perfData = perfData; this.optional = true; this.rmempty = true; }, renderWidget(section_id, option_index, cfgvalue) { let cpuTableTitles = [ _('CPU'), _('Current frequency'), _('Minimum frequency'), _('Maximum frequency'), _('Scaling governor'), ]; let cpuTable = E('table', { 'class': 'table' }, E('tr', { 'class': 'tr table-titles' }, [ E('th', { 'class': 'th left' }, cpuTableTitles[0]), E('th', { 'class': 'th left' }, cpuTableTitles[1]), E('th', { 'class': 'th left' }, cpuTableTitles[2]), E('th', { 'class': 'th left' }, cpuTableTitles[3]), E('th', { 'class': 'th left' }, cpuTableTitles[4]), ]) ); let ondemandTable, conservativeTable, easTable, pcieAspmTable; if(this.perfData) { if(this.perfData.cpus) { for(let i of Object.values(this.perfData.cpus)) { let policy = this.perfData.freqPolicies[String(i.policy)]; if(policy) { cpuTable.append( E('tr', { 'class': 'tr' }, [ E('td', { 'id': 'cpu' + i.number + 'number', 'class': 'td left', 'data-title': cpuTableTitles[0], }, _('CPU') + ' ' + i.number), E('td', { 'id': 'cpu' + i.number + 'curFreq', 'class': 'td left', 'data-title': cpuTableTitles[1], }, (policy.sCurFreq) ? this.ctx.freqFormat(policy.sCurFreq) : this.ctx.freqFormat(policy.curFreq) ), E('td', { 'id': 'cpu' + i.number + 'minFreq', 'class': 'td left', 'data-title': cpuTableTitles[2], }, (policy.sMinFreq) ? this.ctx.freqFormat(policy.sMinFreq) : this.ctx.freqFormat(policy.minFreq) ), E('td', { 'id': 'cpu' + i.number + 'maxFreq', 'class': 'td left', 'data-title': cpuTableTitles[3], }, (policy.sMaxFreq) ? this.ctx.freqFormat(policy.sMaxFreq) : this.ctx.freqFormat(policy.maxFreq) ), E('td', { 'id': 'cpu' + i.number + 'governor', 'class': 'td left', 'data-title': cpuTableTitles[4], }, policy.governor || '-'), ]) ); }; }; }; if(this.perfData.ondemand) { ondemandTable = E('table', { 'class': 'table' }, [ E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left', 'style':'width:33%' }, "up_threshold"), E('td', { 'id': 'OdUpThreshold', 'class': 'td left' }, this.perfData.ondemand.upThreshold || '-'), ]), E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left' }, "ignore_nice_load"), E('td', { 'id': 'OdIgnNiceLoad', 'class': 'td left' }, (this.perfData.ondemand.ignNiceLoad !== undefined) ? this.perfData.ondemand.ignNiceLoad : '-'), ]), E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left' }, "sampling_down_factor"), E('td', { 'id': 'OdSmpDownFactor', 'class': 'td left' }, this.perfData.ondemand.smpDownFactor || '-'), ]), E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left' }, "powersave_bias"), E('td', { 'id': 'OdPowersaveBias', 'class': 'td left' }, (this.perfData.ondemand.powersaveBias !== undefined) ? this.perfData.ondemand.powersaveBias : '-'), ]), ]); }; if(this.perfData.conservative) { conservativeTable = E('table', { 'class': 'table' }, [ E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left', 'style':'width:33%' }, "freq_step"), E('td', { 'id': 'CoFreqStep', 'class': 'td left' }, (this.perfData.conservative.freqStep !== undefined) ? this.perfData.conservative.freqStep : '-'), ]), E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left' }, "down_threshold"), E('td', { 'id': 'CoDownThreshold', 'class': 'td left' }, this.perfData.conservative.downThreshold || '-'), ]), E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left' }, "sampling_down_factor"), E('td', { 'id': 'CoSmpDownFactor', 'class': 'td left' }, this.perfData.conservative.smpDownFactor || '-'), ]), ]); }; if(this.perfData.pcieAspm) { pcieAspmTable = E('table', { 'class': 'table' }, E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left', 'style':'width:33%' }, "policy"), E('td', { 'id': 'PaCurrentPolicy', 'class': 'td left' }, this.perfData.pcieAspm.currentPolicy || '-'), ]) ); }; if(this.perfData.eas) { easTable = E('table', { 'class': 'table' }, E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td left', 'style':'width:33%' }, "sched_energy_aware"), E('td', { 'id': 'EasSchedEnergyAware', 'class': 'td left' }, (this.perfData.eas.schedEnergyAware !== undefined) ? this.perfData.eas.schedEnergyAware : '-'), ]) ); }; }; if(cpuTable.childNodes.length === 1){ cpuTable.append( E('tr', { 'class': 'tr placeholder' }, E('td', { 'class': 'td' }, E('em', {}, _('No performance data...')) ) ) ); }; let tables = [ cpuTable ]; if(ondemandTable) { tables.push( E('h3', {}, _("Ondemand governor")), ondemandTable ); }; if(conservativeTable) { tables.push( E('h3', {}, _("Conservative governor")), conservativeTable, ); }; if(easTable) { tables.push( E('h3', {}, _("EAS")), easTable, ); }; if(pcieAspmTable) { tables.push( E('h3', {}, _("PCIe ASPM")), pcieAspmTable, ); }; return E(tables); }, }), CBIBlockInitButton: form.Value.extend({ __name__ : 'CBI.BlockInitButton', __init__(map, section, ctx) { this.map = map; this.section = section; this.ctx = ctx; this.optional = true; this.rmempty = true; }, renderWidget(section_id, option_index, cfgvalue) { this.ctx.initButton = E('button', { 'class': (!this.ctx.initStatus) ? btnStyleDisabled : btnStyleEnabled, 'click': ui.createHandlerFn(this, () => { return this.ctx.handleServiceAction( (!this.ctx.initStatus) ? 'enable' : 'disable' ).then(success => { if(!success) { return; }; if(!this.ctx.initStatus) { this.ctx.initButton.textContent = _('Enabled'); this.ctx.initButton.className = btnStyleEnabled; this.ctx.initStatus = true; } else { this.ctx.initButton.textContent = _('Disabled'); this.ctx.initButton.className = btnStyleDisabled; this.ctx.initStatus = false; }; }); }), }, (!this.ctx.initStatus) ? _('Disabled') : _('Enabled')); return E([ E('label', { 'class': 'cbi-value-title', 'for': 'initButton' }, _('Run at startup') ), E('div', { 'class': 'cbi-value-field' }, [ E('div', {}, this.ctx.initButton), E('input', { 'id' : 'initButton', 'type': 'hidden', }), ]), ]); }, }), freqValidate(section_id, value, slave_elem, max=false) { let slaveValue = slave_elem.formvalue(section_id); if(value === '' || slaveValue === '') { return true; }; value = Number(value); slaveValue = Number(slaveValue); if((max && value >= slaveValue) || (!max && value <= slaveValue)) { return true; }; return `${_('Frequency value must not be')} ${max ? _('lower') : _('higher')} ${_('than the')} "${slave_elem.title}"!`; }, load() { return Promise.all([ this.getInitStatus(), this.callCpuPerf(), uci.load(this.appName), ]).catch(e => { ui.addNotification(null, E('p', _('An error has occurred') + ': %s'.format(e.message))); }); }, render(data) { if(!data) { return; }; this.initStatus = data[0]; let cpuPerfDataArray = data[1]; let freqPolicyArray = cpuPerfDataArray.freqPolicies || {}; let pcieAspmPolicies; if(cpuPerfDataArray.pcieAspm) { pcieAspmPolicies = cpuPerfDataArray.pcieAspm.availPolicies; }; let easValue; if(cpuPerfDataArray.eas) { easValue = cpuPerfDataArray.eas.schedEnergyAware; }; let s, o, ss; let m = new form.Map(this.appName, _('CPU Performance'), _('CPU performance management.')); s = m.section(form.NamedSection, 'config', 'main'); /* Status */ s.tab('values', _('Current values')); o = s.taboption('values', this.CBIBlockPerf, this, cpuPerfDataArray); /* Performance managment */ s.tab('performance', _('Performance managment')); // enabled o = s.taboption('performance', form.Flag, 'enabled', _('Enable')); o.rmempty = false; // init button o = s.taboption('performance', this.CBIBlockInitButton, this); /* CPU frequency policies */ let sections = uci.sections(this.appName, 'cpu_freq_policy'); if(sections.length == 0) { o = s.taboption('performance', form.DummyValue, '_dummy'); o.rawhtml = true; o.default = '
' +
this.freqFormat(freqPolicyArray[policyNum].minFreq) + ').'
);
minFreq.rmempty = true;
minFreq.optional = true;
// scaling_max_freq
maxFreq = ss.option(form.ListValue,
'scaling_max_freq', _('Maximum scaling frequency'),
_('Maximum frequency the CPU is allowed to be running.') +
' (' + _('default value:') + ' ' +
this.freqFormat(freqPolicyArray[policyNum].maxFreq) + ').'
);
maxFreq.rmempty = true;
maxFreq.optional = true;
availFreqs.forEach(e => {
minFreq.value(e[0], e[1]);
maxFreq.value(e[0], e[1]);
});
} else {
// scaling_min_freq
minFreq = ss.option(form.Value,
'scaling_min_freq', `${_('Minimum scaling frequency')} (${_('KHz')})`,
_('Minimum frequency the CPU is allowed to be running.') +
' (' + _('default value:') + ' ' +
freqPolicyArray[policyNum].minFreq + ').'
);
minFreq.rmempty = true;
minFreq.optional = true;
minFreq.datatype = `and(integer,range(${freqPolicyArray[policyNum].minFreq},${freqPolicyArray[policyNum].maxFreq}))`;
minFreq.placeholder = `${freqPolicyArray[policyNum].minFreq}-${freqPolicyArray[policyNum].maxFreq} ${_('KHz')}`;
// scaling_max_freq
maxFreq = ss.option(form.Value,
'scaling_max_freq', `${_('Maximum scaling frequency')} (${_('KHz')})`,
_('Maximum frequency the CPU is allowed to be running.') +
' (' + _('default value:') + ' ' +
freqPolicyArray[policyNum].maxFreq + ').'
);
maxFreq.rmempty = true;
maxFreq.optional = true;
maxFreq.datatype = `and(integer,range(${freqPolicyArray[policyNum].minFreq},${freqPolicyArray[policyNum].maxFreq}))`;
maxFreq.placeholder = `${freqPolicyArray[policyNum].minFreq}-${freqPolicyArray[policyNum].maxFreq} ${_('KHz')}`;
};
minFreq.validate = L.bind(
function(section_id, value) {
return this.freqValidate(section_id, value, maxFreq, false);
},
this
);
maxFreq.validate = L.bind(
function(section_id, value) {
return this.freqValidate(section_id, value, minFreq, true);
},
this
);
};
};
if(!freqPolicyArray[policyNum] ||
!(freqPolicyArray[policyNum].sAvailGovernors &&
freqPolicyArray[policyNum].sAvailGovernors.length > 0) &&
!(freqPolicyArray[policyNum].sMinFreq && freqPolicyArray[policyNum].sMaxFreq &&
freqPolicyArray[policyNum].minFreq && freqPolicyArray[policyNum].maxFreq)) {
o = ss.option(form.DummyValue, '_dummy');
o.rawhtml = true;
o.default = '