#!/usr/bin/php -q 'green', 'operational' => 'orange']); return " / ".number_format($power,$power<10?2:1,$number[0]).' '._('W').""; } function my_clock($time) { if (!$time) return _('less than a minute'); $days = floor($time/1440); $hour = floor($time/60)%24; $mins = $time%60; return plus($days,'day',($hour|$mins)==0).plus($hour,'hour',$mins==0).plus($mins,'minute',true); } function find_day($D) { global $days; if ($days[0] == '*') return $D; foreach ($days as $d) if ($d >= $D) return $d; return $days[0]; } function find_month($M) { global $months, $Y; if ($M > 12) {$M = 1; $Y++;} if ($months[0] == '*') return $M; foreach ($months as $m) if ($m >= $M) return $m; return $months[0]; } function today($D) { global $days, $M, $Y; if ($days[0]=='*') return date('w',mktime(0,0,0,$M,$D,$Y)); for ($d = $D; $d < $D+7; $d++) { $day = date('w',mktime(0,0,0,$M,$d,$Y)); if (in_array($day,$days)) return $day; } } function next_day($D) { return find_day(($D+1)%7); } function last_day() { global $M, $Y; return date('t',mktime(0,0,0,$M,1,$Y)); } function mkdate($D, $s) { global $M, $Y; if ($s > last_day()) {$s = 1; $M = find_month($M+1);} for ($d = $s; $d < $s+7; $d++) if ($D == date('w',mktime(0,0,0,$M,$d,$Y))) return $d; } function stage($i) { global $h, $m, $D, $M, $Y, $time, $now; if ($i < 0) { $d = $now ? $D : today(1); $s = $now ? date('j',$time) : 1; $D = mkdate($d, $s); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // first day if ($t < 0) { $D = mkdate(next_day($d), $s+1); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next day } if ($t < 0) { $s += 7; if ($s > last_day()) { $s -= last_day(); $M = find_month($M+1); } $D = mkdate(today($d), $s); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next week } } else { $d = $i ? ($now ? $D : today($i)) : today(last_day()-6); $s = $i ?: last_day()-6; $D = mkdate($d, $s); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // first day if ($t < 0) { $D = mkdate(next_day($d), $s); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next day } if ($t < 0) { $M = find_month($M+1); $s = $i ?: last_day()-6; $D = mkdate(today($s), $s); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next month } if ($t < 0) { $Y++; $M = find_month(1); $s = $i ?: last_day()-6; $D = mkdate(today($s), $s); $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next year } } return $t; } function normalize($type,$count) { $words = explode('_',$type); foreach ($words as &$word) $word = $word==strtoupper($word) ? $word : preg_replace(['/^(ct|cnt)$/','/^blk$/'],['count','block'],strtolower($word)); return ucfirst(implode(' ',$words)).": ".str_replace('_',' ',strtolower($count))."\n"; } function active_disks($disk) { return substr(_var($disk,'status'),0,7)!='DISK_NP' && in_array(_var($disk,'type'),['Parity','Data']); } function device_name(&$disk) { switch (_var($disk,'type')) { case 'Extra' : case 'Parity': $type = _var($disk,'rotational') ? 'disk' : 'nvme'; break; case 'Data' : case 'Cache' : $type = _var($disk,'rotational') ? (_var($disk,'luksState') ? 'disk-encrypted' : 'disk') : 'nvme'; break; } $name = _var($disk,'name'); $fancy = _(my_disk(native($name,1)),3); return " $fancy"; } function device_status(&$disk, &$error, &$warning) { global $var; if (_var($disk,'type')!='Extra' && _var($var,'fsState')=='Stopped') { $color = 'green'; $text = 'off-line'; } else switch (_var($disk,'color')) { case 'green-on' : $color = 'green'; $text = 'active'; break; case 'green-blink' : $color = 'grey'; $text = 'standby'; break; case 'blue-on' : $color = 'blue'; $text = 'unassigned'; break; case 'blue-blink' : $color = 'grey'; $text = 'unassigned'; break; case 'yellow-on' : $color = 'yellow'; $text = _var($disk,'type')=='Parity' ? 'invalid' : 'emulated'; $warning++; break; case 'yellow-blink': $color = 'grey'; $text = _var($disk,'type')=='Parity' ? 'invalid' : 'emulated'; $warning++; break; case 'red-on' : $color = 'red'; $text = 'disabled'; $error++; break; case 'red-blink' : $color = 'grey'; $text = 'disabled'; $error++; break; case 'red-off' : $color = 'red'; $text = 'faulty'; $error++; break; case 'grey-off' : $color = 'grey'; $text = 'no device'; break; } return ""._($text); } function device_temp(&$disk, &$red, &$orange) { global $display; $spin = strpos(_var($disk,'color'),'blink')===false; $temp = _var($disk,'temp'); $dev = _var($disk,'device'); $nvme = _var($disk,'transport')=='nvme'; [$hotNVME,$maxNVME] = $nvme ? get_nvme_info($dev,'temp') : [-1,-1]; $hot = _var($disk,'hotTemp',-1)>=0 ? $disk['hotTemp'] : ($hotNVME>=0 ? $hotNVME : (_var($disk,'rotational',1)==0 && $display['hotssd']>=0 ? $display['hotssd'] : $display['hot'])); $max = _var($disk,'maxTemp',-1)>=0 ? $disk['maxTemp'] : ($maxNVME>=0 ? $maxNVME : (_var($disk,'rotational',1)==0 && $display['maxssd']>=0 ? $display['maxssd'] : $display['max'])); $top = $display['top'] ?? 120; $color= 'green'; if (exceed($temp,$max,$top) || (is_numeric($temp) && $temp<0)) { $color = 'red'; $red++; } elseif (exceed($temp,$hot,$top)) { $color = 'orange'; $orange++; } return ($spin ? "".my_temp($temp)."" : "*").($nvme ? my_power($dev) : ""); } function device_smart(&$disk, &$fail, &$smart) { global $numbers,$saved; if (!_var($disk,'device') || strpos(_var($disk,'color'),'blink')!==false) return "-"; $failed = ['FAILED','NOK']; $name = _var($disk,'name'); $named = no_tilde($name); $select = get_value($name,'smSelect',0); $level = get_value($name,'smLevel',1); $events = explode('|',get_value($disk,'smEvents',$numbers)); $title = ''; $thumb = 'thumbs-o-up'; $text = _('healthy'); $color = 'green'; $file = "state/smart/$name"; $ssa = file_exists($file) ? exec("grep -Pom1 '^SMART.+: \K[A-Z]+' ".escapeshellarg($file)) : ""; if (in_array($ssa,$failed)) { $title = _('SMART health-check failed')."\n"; $thumb = 'thumbs-o-down'; $color = 'red'; $text = 'fail'; $fail++; } else { if (empty($saved["smart"]["$named.ack"])) { exec("awk 'NR>7{print $1,$2,$4,$6,$9,$10}' ".escapeshellarg($file)." 2>/dev/null", $codes); foreach ($codes as $code) { if (!$code || !is_numeric($code[0])) continue; [$id,$class,$value,$thres,$when,$raw] = my_explode(' ',$code,7); $failing = strpos($when,'FAILING_NOW')!==false; if (!$failing && !in_array($id,$events)) continue; if ($failing || ($select ? $thres>0 && $value<=$thres*$level : $raw>0)) $title .= normalize($class,$failing?$when:$raw); } if ($title) {$thumb = 'thumbs-o-down'; $color = 'orange'; $text = _('error'); $smart++;} else $title = _('No errors reported')."\n"; } } $title .= _('Click for context menu'); return "$text"; } function device_usage(&$disk, &$full, &$high) { global $display; $text = $display['text']; $used = (_var($disk,'type')!='Parity' && _var($disk,'type')!='Extra' && _var($disk,'fsStatus')=='Mounted') ? (_var($disk,'fsSize',0)>0 ? round((1-_var($disk,'fsFree',0)/$disk['fsSize'])*100) : 0).'%' : false; if ($used) { if ($text==2 || $text==21) { $load = substr($used,0,-1); $critical = _var($disk,'critical')>=0 ? $disk['critical'] : (_var($display,'critical')>=0 ? $display['critical'] : 0); $warning = _var($disk,'warning')>=0 ? $disk['warning'] : (_var($display,'warning')>=0 ? $display['warning'] : 0); if ($critical>0 && $load>=$critical) {$class = 'redbar'; $full++;} elseif ($warning>0 && $load>=$warning) {$class = 'orangebar'; $high++;} else $class = 'greenbar'; } else $class = false; return $text%10==0 ? $used : "$used
"; } else return $text%10==0 ? "-" : "-
"; } function array_group($type,$pool=false) { global $disks,$error,$warning,$red,$orange,$fail,$smart,$full,$high; $echo = []; foreach ($disks as $disk) if (_var($disk,'type')==$type && strpos(_var($disk,'status'),'DISK_NP')===false && (!$pool||$pool==prefix(_var($disk,'name')))) { $echo[] = ""; $echo[] = "".device_name($disk).""; $echo[] = "".device_status($disk,$error,$warning).""; $echo[] = "".device_temp($disk,$red,$orange).""; $echo[] = "".device_smart($disk,$fail,$smart).""; $echo[] = "".device_usage($disk,$full,$high).""; $echo[] = ""; } return implode('',$echo); } function extra_group() { global $devs,$error,$warning,$red,$orange,$fail,$smart,$full,$high; $echo = []; foreach ($devs as $disk) { $name = _var($disk,'name'); $disk['type'] = "Extra"; $disk['color'] = _var($disk,'spundown')==0 ? 'blue-on' : 'blue-blink'; $echo[] = ""; $echo[] = "".device_name($disk).""; $echo[] = "".device_status($disk,$error,$warning).""; $echo[] = "".device_temp($disk,$red,$orange).""; $echo[] = "".device_smart($disk,$fail,$smart).""; $echo[] = "".device_usage($disk,$full,$high).""; $echo[] = ""; } return implode('',$echo); } function update_translation($locale) { global $docroot,$language; $language = []; if ($locale) { $text = "$docroot/languages/$locale/translations.txt"; if (file_exists($text)) { $store = "$docroot/languages/$locale/translations.dot"; if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text))); $language = unserialize(file_get_contents($store)); } $text = "$docroot/languages/$locale/dashboard.txt"; if (file_exists($text)) { $store = "$docroot/languages/$locale/dashboard.dot"; if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text))); $language = array_merge($language,unserialize(file_get_contents($store))); } } } function create_sync($file) { return file_exists($file) ? explode(',',file_get_contents($file)) : []; } function print_error($error) { return sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0'); } while (true) { $var = (array)@parse_ini_file("$varroot/var.ini"); $devs = (array)@parse_ini_file("$varroot/devs.ini",true); $disks = (array)@parse_ini_file("$varroot/disks.ini",true); $saved = (array)@parse_ini_file("$varroot/monitor.ini",true); $echo = []; $size = _var($var,'mdResyncSize'); $spot = _var($var,'mdResyncPos'); require "$docroot/webGui/include/CustomMerge.php"; require "$docroot/webGui/include/Preselect.php"; // check for language changes extract(parse_plugin_cfg('dynamix',true)); if (_var($display,'locale') != $locale_init) { $locale_init = _var($display,'locale'); update_translation($locale_init); } //array devices $error = $warning = $red = $orange = $fail = $smart = $full = $high = 0; $echo[0] = array_group('Parity'); $echo[0] .= array_group('Data'); $echo[0] .= "\0".($error+$warning)."\0".($red+$orange)."\0".($fail+$smart)."\0".($full+$high); //pool devices $echo[1] = ''; foreach (pools_filter($disks) as $pool) { if (empty($disks[$pool]['devices'])) continue; $error = $warning = $red = $orange = $fail = $smart = $full = $high = 0; $echo[1] .= array_group('Cache',$pool); $echo[1] .= "\0".($error+$warning)."\0".($red+$orange)."\0".($fail+$smart)."\0".($full+$high)."\r"; } //unassigned devices $error = $warning = $red = $orange = $fail = $smart = $full = $high = 0; $echo[2] = extra_group(); $echo[2] .= "\0".($error+$warning)."\0".($red+$orange)."\0".($fail+$smart)."\0".($full+$high); // parity status $echo[3] = ''; $disks = parity_filter($disks); $parity_slots = count($disks); $parity_disabled = $parity_invalid = 0; foreach ($disks as $disk) { if (strpos(_var($disk,'status'),"DISK_NP")===0) $parity_disabled++; elseif (strpos(_var($disk,'status'),"DISK_INVALID")===0) $parity_invalid++; } if ($spot) { $number = _var($display,'number','.,'); $action = preg_split('/\s+/',_var($var,'mdResyncAction')); switch ($action[0]) { case "recon": $mode = $action[1]=='P' ? 'Parity-Sync' : 'Data-Rebuild'; break; case "check": $mode = count($action)>1 ? 'Parity-Check' : 'Read-Check'; break; case "clear": $mode = 'Disk-Clear'; break; default : $mode = ''; break; } $echo[3] .= ""._($mode).' '._('in progress').'... '._('Completed').': '.number_format($spot/($size/100+1),1,$number[0],$number[1])." %."; } else { if ($parity_slots==$parity_disabled) { $echo[3] .= ""._('Parity disk'.($parity_slots==1?'':'s')." not present").""; } elseif ($parity_slots > $parity_invalid) { if ($parity_invalid==0) { $echo[3] .= ""._('Parity is valid').""; } else { $echo[3] .= ""._('Parity is degraded').": $parity_invalid "._('invalid device'.($parity_invalid==1?'':'s')).""; } } else { if (empty($var['mdInvalidDisk'])) { $echo[3] .= ""._('Parity is invalid').""; } else { $echo[3] .= ""._('Data is invalid').""; } } } // parity schedule [$delta,$bytes] = [_var($var,'mdResyncDt',0),_var($var,'mdResyncDb',0)]; $synced = create_sync($stamps); $sbSynced = array_shift($synced) ?: _var($var,'sbSynced'); $sbUpdate = $delta ? $sbSynced : _var($var,'sbUpdated'); if ($spot) { $echo[4] = sprintf(_('Current operation %s on **%s**'),($delta?_('started'):_('paused')),_(my_time($sbUpdate).day_count($sbUpdate),0)); $echo[4].= "
"._('Elapsed time').": "._(my_clock(floor((time()-$sbSynced)/60)),2); $echo[4].= "
"._('Estimated finish').': '.($bytes ? _(my_clock(round(((($delta*(($size-$spot)/($bytes/100+1)))/100)/60),0)),2) : _('Unknown')); $echo[4].= "
".print_error(_var($var,'sbSyncErrs',0)); $echo[4] .= "\0"; } else { [$date,$duration,$speed,$status,$error,$action,$size] = last_parity_log(); if (_var($var,'sbSyncExit',0)!=0) { $echo[4] = sprintf(_('Last check incomplete on **%s**'),_(my_time(_var($var,'sbSynced2',0)).day_count(_var($var,'sbSynced2',0)),0)); $echo[4].= "
"._('Error code').": ".my_error(_var($var,'sbSyncExit')); $echo[4].= "
".print_error(_var($var,'sbSyncErrs',0)); } elseif (_var($var,'sbSynced',0)==0) { if (!$date) { $echo[4] = _('Parity has not been checked yet'); } elseif ($status==0) { $echo[4] = sprintf(_('Last checked on **%s**'),_(my_time($date).day_count($date),0)); $echo[4].= "
"._('Duration').": ".my_check($duration,$speed); $echo[4].= "
".print_error($error); } else { $echo[4] = sprintf(_('Last check incomplete on **%s**'),_(my_time($date).day_count($date),0)); $echo[4].= "
"._('Error code').": ".my_error($status); $echo[4].= "
".print_error($error); } } elseif (_var($var,'sbSynced2',0)==0) { if ($status==0) { $echo[4] = sprintf(_('Last checked on **%s**'),_(my_time(_var($var,'sbSynced',0)).day_count(_var($var,'sbSynced',0)),0)); $echo[4].= "
"._('Duration').": ".my_check($duration,$speed); $echo[4].= "
".print_error($error); } else { $echo[4] = sprintf(_('Last check incomplete on **%s**'),_(my_time(_var($var,'sbSynced',0)).day_count(_var($var,'sbSynced',0)),0)); $echo[4].= "
"._('Error code').": ".my_error($status); $echo[4].= "
".print_error($error); } } else { $echo[4] = sprintf(_('Last check completed on **%s**'),_(my_time(_var($var,'sbSynced2',0)).day_count(_var($var,'sbSynced2',0)),0)); $echo[4].= "
"._('Duration').': '.my_check($duration,$speed); $echo[4].= "
".print_error(_var($var,'sbSyncErrs',0)); } [$m,$h] = explode(' ', $parity['hour']); $time = time(); $check = true; switch ($parity['mode']) { case 0: // check disabled $check = false; break; case 1: // daily check $t = mktime($h,$m,0)-$time; if ($t < 0) $t += 86400; break; case 2: // weekly check $t = $parity['day']-date('w',$time); if ($t < 0) $t += 7; $t = mktime($h,$m,0)+$t*86400-$time; if ($t < 0) $t += 86400*7; break; case 3: // monthly check $D = $parity['dotm']; $M = date('n',$time); $Y = date('Y',$time); $last = ($D == '28-31'); if ($last) $D = last_day(); $t = mktime($h,$m,0,$M,$D,$Y)-$time; if ($t < 0) { if ($M < 12) $M++; else {$M = 1; $Y++;} if ($last) $D = last_day(); $t = mktime($h,$m,0,$M,$D,$Y)-$time; } break; case 4: // yearly check $D = $parity['dotm']; $M = $parity['month']; $Y = date('Y',$time); $last = ($D == '28-31'); if ($last) $D = last_day(); $t = mktime($h,$m,0,$M,$D,$Y)-$time; if ($t < 0) { $Y++; if ($last) $D = last_day(); $t = mktime($h,$m,0,$M,$D,$Y)-$time; } break; case 5: // custom check $days = explode(',',$parity['day']); $months = explode(',',$parity['month']); $today = date('w',$time); $date = date('n',$time); $D = find_day($today); $M = find_month($date); $Y = date('Y',$time); $now = $M==$date; if ($M < $date) $Y++; switch ($parity['dotm']) { case '*' : $t = stage(-1); break; case 'W1': $t = stage(1); break; case 'W2': $t = stage(8); break; case 'W3': $t = stage(15); break; case 'W4': $t = stage(22); break; case 'WL': $t = stage(0); break;} break; } $echo[4] .= "\0"; if ($check) { $frmt = _var($display,'date').(_var($display,'date')!='%c' ? ", "._var($display,'time') : ""); $echo[4] .= sprintf(_('Next check scheduled on **%s**'),_(my_date($frmt,$time+$t),0)); $echo[4] .= "
"._('Due in').": "._(my_clock(floor($t/60)),2); } else { $echo[4] .= _('Scheduled parity check is disabled'); } } $echo = implode("\1",$echo); $md5_new = md5($echo,true); if ($md5_new !== $md5_old) { publish('update2', $echo); $md5_old = $md5_new; } sleep(2); } ?>