152 lines
6.2 KiB
JavaScript
152 lines
6.2 KiB
JavaScript
import * as preact from 'preact';
|
||
/** @jsx preact.h */
|
||
import { cluster_afr } from './afr.js';
|
||
|
||
class Calc extends preact.Component
|
||
{
|
||
state = {
|
||
hosts: 10,
|
||
drives: 10,
|
||
afr_drive: 3,
|
||
afr_host: 5,
|
||
capacity: 8,
|
||
speed: 20,
|
||
ec: false,
|
||
replicas: 2,
|
||
ec_data: 2,
|
||
ec_parity: 1,
|
||
eager: false,
|
||
result: 0,
|
||
}
|
||
|
||
calc(st)
|
||
{
|
||
st = { ...this.state, ...st };
|
||
st.result = 100*cluster_afr({
|
||
n_hosts: st.hosts,
|
||
n_drives: st.drives,
|
||
afr_drive: st.afr_drive/100,
|
||
afr_host: st.afr_host/100,
|
||
capacity: st.capacity*1000,
|
||
speed: st.speed/1000,
|
||
ec: st.ec ? [ st.ec_data, st.ec_parity ] : null,
|
||
replicas: st.replicas,
|
||
pgs: 100,
|
||
degraded_replacement: st.eager,
|
||
});
|
||
this.setState(st);
|
||
}
|
||
|
||
setter(field)
|
||
{
|
||
if (!this.setter[field])
|
||
{
|
||
this.setter[field] = (event) =>
|
||
{
|
||
this.calc({ [field]: event.target.value });
|
||
};
|
||
}
|
||
return this.setter[field];
|
||
}
|
||
|
||
setRepl = () =>
|
||
{
|
||
this.calc({ ec: false });
|
||
}
|
||
|
||
setEC = () =>
|
||
{
|
||
this.calc({ ec: true });
|
||
}
|
||
|
||
setEager = (event) =>
|
||
{
|
||
this.calc({ eager: event.target.checked });
|
||
}
|
||
|
||
componentDidMount()
|
||
{
|
||
this.calc({});
|
||
}
|
||
|
||
render(props, state)
|
||
{
|
||
return (<div style="width: 750px; margin: 20px; padding: 20px; box-shadow: 0 19px 60px rgba(0, 0, 0, 0.3), 0 15px 20px rgba(0, 0, 0, 0.22);">
|
||
<h2 style="text-align: center; font-size: 150%; margin: 10px 0 20px 0; font-weight: bold">
|
||
Калькулятор вероятности отказа кластера Ceph/Vitastor
|
||
</h2>
|
||
<p>
|
||
Вероятность полного отказа кластера зависит от числа серверов и дисков
|
||
(чем их больше, тем вероятность больше), от схемы избыточности, скорости ребаланса (восстановления),
|
||
и, конечно, непосредственно вероятности выхода из строя самих дисков и серверов.
|
||
</p>
|
||
<p>
|
||
Расчёт ведётся в простом предположении, что отказы распределены равномерно во времени.
|
||
</p>
|
||
<table>
|
||
<tr>
|
||
<th>Число серверов</th>
|
||
<td><input type="text" value={state.hosts} onchange={this.setter('hosts')} /></td>
|
||
</tr>
|
||
<tr>
|
||
<th>Число дисков в сервере</th>
|
||
<td><input type="text" value={state.drives} onchange={this.setter('drives')} /></td>
|
||
</tr>
|
||
<tr>
|
||
<th>Ёмкость дисков</th>
|
||
<td><input type="text" value={state.capacity} onchange={this.setter('capacity')} /> ТБ</td>
|
||
</tr>
|
||
<tr>
|
||
<th>Схема избыточности</th>
|
||
<td>
|
||
<label class={"switch l"+(state.ec ? "" : " sel")}>
|
||
<input type="radio" name="scheme" checked={!state.ec} onclick={this.setRepl} /> Репликация
|
||
</label>
|
||
<label class={"switch r"+(state.ec ? " sel" : "")}>
|
||
<input type="radio" name="scheme" checked={state.ec} onclick={this.setEC} /> EC (коды коррекции ошибок)
|
||
</label>
|
||
</td>
|
||
</tr>
|
||
{state.ec ? null : <tr>
|
||
<th>Число реплик</th>
|
||
<td><input type="text" value={state.replicas} onchange={this.setter('replicas')} /></td>
|
||
</tr>}
|
||
{state.ec ? <tr>
|
||
<th>Число дисков данных</th>
|
||
<td><input type="text" value={state.ec_data} onchange={this.setter('ec_data')} /></td>
|
||
</tr> : null}
|
||
{state.ec ? <tr>
|
||
<th>Число дисков чётности</th>
|
||
<td><input type="text" value={state.ec_parity} onchange={this.setter('ec_parity')} /></td>
|
||
</tr> : null}
|
||
<tr>
|
||
<th>Оценочная скорость<br />восстановления на 1 OSD</th>
|
||
<td><input type="text" value={state.speed} onchange={this.setter('speed')} /> МБ/с</td>
|
||
</tr>
|
||
<tr>
|
||
<th><abbr title="Annualized Failure Rate, вероятность отказа в течение года в %">AFR</abbr> диска</th>
|
||
<td><input type="text" value={state.afr_drive} onchange={this.setter('afr_drive')} /> %</td>
|
||
</tr>
|
||
<tr>
|
||
<th>AFR сервера</th>
|
||
<td><input type="text" value={state.afr_host} onchange={this.setter('afr_host')} /> %</td>
|
||
</tr>
|
||
</table>
|
||
<p>
|
||
<label><input type="checkbox" checked={state.eager} onchange={this.setEager} />
|
||
Я нетерпеливый и заменяю отказавший диск сразу, не давая данным уехать на остальные диски
|
||
(либо данным уезжать некуда, например, сервера всего 3 при 3 репликах)
|
||
</label>
|
||
</p>
|
||
<div style="text-align: center; font-size: 150%; margin: 20px 0; font-weight: bold">
|
||
Вероятность потери данных в течение года:
|
||
</div>
|
||
<div style="text-align: center; font-size: 200%; margin: 10px 0; font-weight: bold">
|
||
{Math.round(state.result*10000)/10000} %
|
||
</div>
|
||
</div>);
|
||
}
|
||
}
|
||
|
||
preact.render(<Calc />, document.body);
|