Вывод виджета информации о сервере в консоли WordPress
В предыдущей статье мы создали базовую структуру нашего плагина. Теперь пора реализовать класс вывода для каждого из наших виджетов:

Напомним, что все провайдеры виджетов должны реализовывать интерфейс Provider. Они также должны размещаться внутри папки widget и в пространстве имен AXStatBoardWidget.
Если мы хотим добавить новый вид метрики, то просто создаем соответствующий класс, а также объект и добавляем его в класс Widget с методом add_provider.
Виджет использования оперативной памяти
Одним из первых видов информации, которую мы хотим отобразить, является то, какая часть оперативной памяти в настоящее время используется и сколько памяти сейчас не задействовано.
В этом случае нам поможет free -m - таким образом, мы получим данные об использовании оперативной памяти. Переключатель -m задается для вывода результатов в мегабайтах:
[vagrant@vagrant-centos64 ~]$ free -m
total used free shared buffers cached
Mem: 589 366 223 0 9 57
-/+ buffers/cache: 299 290
Swap: 0 0 0
Мы назовем этот класс Ram. Соответствующий ему файл - widget/ram.php. Мы просто добавляем в него основу и реализуем get_title:
<?php
namespace AXStatBoardWidget;
class Ram implements Provider {
function __construct() {
}
public function get_title() {
return "Ram Usage";
}
?>
Далее, мы реализуем get_metric, чтобы собственно запустить необходимую команду shell и вытащить информацию. Более подробно о get_metric я расскажу чуть позже:
<?php
function get_metric() {
$df = `free -m | grep -E "(Mem|Swap)" | awk '{print $1, $2, $3, $4}'`;
$df = explode("n", $df);
if ( is_array( $df ) && 2 <= count( $df ) ) {
$df = array_map( function ( $line ) {
if ( empty( $line ) ) {
return;
}
$segment = preg_split( '/s+/', $line );
return array(
'type' => trim( $segment[0]," :" ),
'total' => (int)$segment[1],
'used' => (int)$segment[2],
'free' => (int)$segment[3],
);
}, $df );
return $df;
}
return false;
}
?>
Мы выполняем команду free -m | grep -E "Mem|Swap" | awk '{print $1, $2, $3, $4}'. Ее результат выглядит примерно так:
[vagrant@vagrant-centos64 ~]$ free -m | grep -E "Mem|Swap" | awk '{print $1, $2, $3, $4}'
Mem: 589 541 47
Swap: 255 0 255
[vagrant@vagrant-centos64 ~]$
Мы разбираем каждый бит информации с помощью PHP путем разделения строк в массив. Мы используем array_map, чтобы обработать через цикл все элементы массива и каждую строку мы разбиваем пробелами, затем возвращаем ассоциированный массив с элементами:
- type: первое поле;
- total: второе поле;
- used: третье поле;
- free: четвертое поле.
Теперь пришло время применить get_content:
public function get_content() {
$metric = $this->get_metric();
$data = array(
array('Type', 'Used(MB)', 'Free(MB)')
);
foreach ($metric as $item) {
if (empty($item)) {
continue;
}
if ($item['type'] !== 'Mem' && $item['type'] !== 'Swap') {
continue;
}
if ( 0 == ($item['free'] + $item['used'])) {
continue;
}
$data[] = array(
$item['type'],$item['used'], $item['free']
);
}
$data = json_encode($data);
echo <<<EOD
<div id="widget_ram_usage"></div>
<script>
google.setOnLoadCallback(function () {
var data = google.visualization.arrayToDataTable({$data});
var options = {
isStacked: true
};
var chart = new google.visualization.ColumnChart(document.getElementById('widget_ram_usage'));
chart.draw(data, options);
})
</script>
EOD;
}
Мы использовали столбчатую диаграмму для отображения данных по использованию оперативной памяти.
Сначала мы вызываем get_metric(), чтобы получить необходимые данные. После этого мы просто пропускаем их через цикл, чтобы отформатировать данные в соответствии с требованиями диаграмм Google.
Наконец, мы используем json_encode, чтобы преобразовать их в объект аннотации JavaScript (или JSON). После этого выводим HTML-код, содержащий элемент div для хранения объекта диаграммы.
В конце мы вызываем соответствующее Google Chart API для визуализации диаграммы из этого элемента div:

Установленное программное обеспечение
Второй виджет, который мы рассмотрим, будет отображать информацию по установленному программному обеспечению. Его задача - вывести данные о том, какой прикладной пакет установлен у нас на сервере и его версию.
Например, установлен ли у нас NodeJS, установлен ли Ruby? Какую версию PHP мы используем? И так далее.
Давайте создадим файл widget/software.php с таким исходным содержимым:
<?php
namespace AXStatBoardWidget;
class Software implements Provider {
function __construct() {
}
public function get_title() {
return "Installed Software";
}
function get_metric() {
$cmds = array();
$package = array(
'php' => '-v',
'node' => '-v',
'mysql' => '-V',
'vim' => '--version',
'python' => '-V',
'ruby' => '-v',
'java' => '-version',
'curl' => '-V');
foreach ( $package as $cmd=>$version_query ) {
if ( NULL == $cmds[$cmd] = shell_exec( "which $cmd" ) ) {
$cmds[ $cmd ] = 'Not installed';
continue;
}
$version = shell_exec( "$cmd $version_query" );
$version = explode( "n", $version );
if ( is_array( $version ) ) {
$version = array_shift( $version );
}
$cmds[ $cmd ] .= '<br>' . $version;
}
return $cmds;
}
Как и ранее, у нас есть метод get_title, и он всего лишь возвращает простую строку. Для get_metric(), нам необходимо знать, установлена ли на сервере конкретная программа или нет. Если это так, то получить информацию о ее версии.
Для этого мы создаем массив команд с помощью переключателя, который показывает версию программного обеспечения. Например, для PHP, php -v показывает информацию о версии пакета, mysql --version показывает информацию о версии MySQL.
shell_exec возвращает false, если команда возвращает ошибку или команда не найдена. В этом случае, мы знаем, что программное обеспечение не установлено; в противном случае, мы можем разобрать результат, чтобы вывести информацию о версии.
После этого мы разбиваем результат строка за строкой, и получаем первую строку в качестве информации о версии. Информация, которая нам нужна, должна содержаться только в первой строке.
Для других приложений, некоторые команды довольно сложны и содержат много информации. После того, как мы получим данные, мы создаем метод get_content:
public function get_content() {
$cmds = $this->get_metric();
$content = '';
foreach ( $cmds as $cmd => $info ) {
$content .= "<p><strong>$cmd</strong> $info</p>";
}
echo $content;
}
Мы просто выводим базовую таблицу для этого типа данных. Вот пример данных, выводимых в консоли:

Использование дискового пространства
Теперь мы займемся использованием дискового пространства. Мы называем класс, который обрабатывает эту задачу Disk. Давайте сперва разработаем базовую структуру:
<?php
namespace AXStatBoardWidget;
class Disk implements Provider {
function __construct() {
}
public function get_title() {
return "Disk Usage";
}
}
Как всегда, мы должны реализовать интерфейс Provider. Здесь мы устанавливаем заголовок нашего виджета. Далее следует сердце нашего класса: метод получения данных об использовании дискового пространства:
<?php
function get_metric() {
$df = `df -h`;
$df = explode("n", $df);
if (is_array($df) && count($df)>=2) {
array_shift($df); //Get rid the first line
$df = array_map(function ($line) {
if (empty($line)) {
return NULL;
}
$segment=preg_split('/s+/', $line);
return array(
'filesystem' => $segment[0],
'size' => $segment[1],
'used' => $segment[2],
'available' => $segment[3],
'use_percent' => $segment[4],
);
}, $df);
return $df;
}
return false;
}
В первой части этой серии, мы получили некоторое представление о df, поэтому вам должно быть не сложно разобраться с приведенной ниже командой.
Напомним, результат выполнения df выглядит следующим образом:
[vagrant@vagrant-centos64 ~]$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 7.3G 1.4G 5.6G 20% /
tmpfs 295M 0 295M 0% /dev/shm
/vagrant 60G 55G 4.9G 92% /vagrant
/data/GeoIP 60G 55G 4.9G 92% /data/GeoIP
/var/webapps 60G 55G 4.9G 92% /var/webapps
/var/www/html 60G 55G 4.9G 92% /var/www/html
Разбиваем его строка за строкой, чтобы преобразовать в массив. Мы пропускаем через цикл каждую строку, разделяя их пробелами, чтобы снова преобразовать в массив.
После этого мы просто переводим значения в ассоциированный массив с более понятным, удобочитаемым форматом. Когда мы получили эти данные, мы можем поместить их в get_content:
public function get_content() {
$metric = $this->get_metric();
$data = array(
array( 'Disk', 'Space' )
);
$disk_container = array();
$data_partition = array(
array('Filesystem', 'Free(GB)', 'Used(GB)')
);
foreach ( $metric as $disk ) {
$size = intval( $disk['size'] );
if ( 'M' == substr( $disk['size'], -1 ) ) {
$size = round( $size / 1024, 2 );
}
$used = intval( $disk['used'] );
if ('M' == substr( $disk['used'], -1 ) ) {
$used = round( $used / 1024, 2 );
}
if ( empty( $size ) ) {
continue;
}
$data[] = array( $disk['filesystem'], $size );
$data_partition[] = array($disk['filesystem'], $size - $used, $used);
}
}
Мы пропускаем через цикл массив метрики, чтобы перевести мегабайты в гигабайты. И создаем массив в соответствии с требованиями к формату данных диаграммы. Массив данных должен выглядеть следующим образом:
[
['File System', 'Free', 'Used',
['/dev/sda1', 10, 24],
['/dev/sda2', 28, 19]
]
После того, как мы получили данные, мы выводим диаграммы. Создаем две диаграммы:
- На первой диаграмме будет выводиться доля дискового пространства, занятого каждой смонтированной файловой системой. Для этих данных, мы будем использовать круговую диаграмму;
- Вторая диаграмма - для отображения объема используемого каждой смонтированной файловой системой дискового пространства. Для этого мы будем использовать гистограмму.
С этой целью, давайте изменим наш метод следующим образом:
public function get_content() {
$metric = $this->get_metric();
$data = array(
array('Disk', 'Space')
);
$disk_container = array();
$data_partition = array(
array('Filesystem', 'Free(GB)', 'Used(GB)')
);
foreach ($metric as $disk) {
$size = intval($disk['size']);
if ('M' == substr($disk['size'], -1)) {
$size = round($size / 1024, 2);
}
$used = intval($disk['used']);
if ('M' == substr($disk['used'], -1)) {
$used = round($used / 1024, 2);
}
if (empty($size)) {
continue;
}
$data[] = array($disk['filesystem'], $size);
$data_partition[] = array($disk['filesystem'], $size - $used, $used);
}
$data = json_encode($data);
$data_partition = json_encode($data_partition);
echo <<<EOD
<div id="widget_disk_usage"></div>
<div id="widget_disk_partion"></div>
<script>
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(function () {
var data = google.visualization.arrayToDataTable({$data});
var options = {
is3D: true,
};
var chart = new google.visualization.PieChart(document.getElementById('widget_disk_usage'));
chart.draw(data, options);
var data2 = google.visualization.arrayToDataTable({$data_partition});
var options2 = {
isStacked: true
};
var chart2 = new google.visualization.ColumnChart(document.getElementById('widget_disk_partion'));
chart2.draw(data2, options2);
})
</script>
EOD;
}
Мы создали два элемента div для хранения информации:
<br> <div id="widget_disk_usage"></div>
<div id="widget_disk_partion"></div><br><br>
После этого диаграмма будет отображаться внутри этих элементов с помощью метода извлечения Chart API. Наиболее сложным здесь является формат данных для нашей диаграммы.
Вот как будет выглядеть результат:

Информация о сервере
Этот виджет будет выводить следующую информацию: ядро Linux, архитектура процессора, время работы, IP-адрес. Для этого нам не нужна диаграмма, простой таблицы данных будет достаточно. Вызываемый класс будет называться Server. Для начала вставьте в файл widget/server.php следующий код:
<?php
namespace AXStatBoardWidget;
use DateTime;
class Server implements Provider {
function __construct() {
}
public function get_title() {
return "Server Info";
}
/**
* Возвращает информацию о сервере: ОС, ядро, время работы и имя хоста
* Массив @return с 3 метриками:
* * hostname
* * os
* * uptime
*/
function get_metric() {
$server = array();
$server['hostname'] = `hostname`;
$server['os'] = `uname -sr`;
$server['core'] = `grep -c ^processor /proc/cpuinfo`;
$total_uptime_sec = time() - `cut -d. -f1 /proc/uptime`;
$now = new DateTime("now");
$server['uptime'] = $now->diff(new DateTime("@$total_uptime_sec"))->format('%a days, %h hours, %i minutes and %s seconds');
// Получение расширенного ip с помощью ifconfig.me, сайт, который показывает ip-адрес
// открытым текстом при выполнении запроса с помощью заголовка curl
$server['ip'] = `curl ifconfig.me`;
$server['ram'] = `free -m | grep Mem | awk '{print $2}'`;
$server['cpu'] =`cat /proc/cpuinfo | grep "model name" | awk '{print $4,$5,$6,$7}'`;
return $server;
}
}
К этому моменту, вы уже должны хорошо представлять себе, что делает метод get_title(). Мы просто возвращаем заголовок виджета.
Во-первых, мы используем оператор use DateTime, потому что находимся внутри пространства имен AXStatBoardWidget, а класс DateTime принадлежит глобальному пространству имен.
Каждый раз, когда мы хотим использовать DateTime мы должны ввести DateTime. Поэтому мы используем импорт пространства имен, чтобы DateTime были доступны в нашем текущем пространстве имен.
Это своего рода ссылка. Внутри get_metric мы запускаем команду оболочки, чтобы получить результат и просто снова его присвоить.
hostname
Выводим имя прокси-сервера.
uname -sr
Выводим информацию о ядре Linux:
[vagrant@vagrant-centos64 ~]$ uname -sr
Linux 2.6.32-358.23.2.el6.x86_64
grep -c ^processor /proc/cpuinfo
Переключатель -c выводит количество совпадающих записей во входной строке. /proc/cpuinfo содержит информацию о процессоре. Мы вычленяем эту информацию и подсчитываем количество ядер. Вот данные моего сервера с 32 ядрами процессора:
$ grep -c ^processor /proc/cpuinfo
32
cut -d. -f1 /proc/uptime
Эта команда показывает, сколько секунд работает сервер. Чтобы сделать выводимые данные более удобными для пользователей, мы конвертируем это количество секунд в формат "x дней y часов z минут".
С помощью DateTime::diff мы можем легко этого достичь. Мы создаем объект DateTime с текущей временной меткой, а также еще один объект с отметкой времени равной текущему времени минус количество секунд беспрерывной работы. Затем, используем метод форматирования, чтобы перевести результат в более удобный для пользователя формат.
Вот данные моего сервера - 26194091 секунд беспрерывной работы:
$ cut -d. -f1 /proc/uptime
26194091
curl ifconfig.me
ifconfig.me - это сервис, который показывает ваш IP адрес. Если вы отправляете запрос с помощью curl, он возвращает IP-адрес в виде одной строки:
[vagrant@vagrant-centos64 ~]$ curl ifconfig.me
76.102.253.237
Модель процессора
Как отмечалось выше, в /proc/cpuinfo хранится информация о процессоре. Соответственно, мы можем извлечь модель процессора. Например:
[vagrant@vagrant-centos64 ~]$ cat /proc/cpuinfo | grep "model name" | awk '{print $4,$5,$6,$7}'
Intel(R) Core(TM) i5-4250U CPU
После того, как мы получили в свое распоряжение массив этих данных, мы возвращаем их и вставляем в метод get_content необходимые нам части. Вот пример вывода данных get_content:
public function get_content() {
$server = $this->get_metric();
echo <<<EOD
<strong>Ip Address</strong> {$server['ip']}<br>
<strong>CPU</strong> {$server['cpu']}<br>
<strong>Number of Core</strong> {$server['core']}<br>
<strong>Ram</strong> {$server['ram']}<br>
<strong>Hostname</strong> {$server['hostname']}<br>
<strong>OS</strong> {$server['os']}<br>
<strong>Uptime</strong> {$server['uptime']}<br>
EOD;
}
А вот скриншот соответствующего виджета с информацией:

Процессор
Параметры работы процессора - это одна из самых важных метрик, которые мы можем вывести. Мы хотим знать, какая часть ресурса процессора задействована и / или объем памяти, потребляемый конкретным процессом.
Мы называем наш класс Process и для начала размещаем в нем методы get_title и get_metric. Более подробно о методе get_metric я расскажу ниже:
<?php
namespace AXStatBoardWidget;
class Process implements Provider {
public function get_title() {
return "Processes";
}
/**
* Возвращаем информацию о сервере: ОС, ядро, время работы и имя хоста
* Массив @return с 3 метриками:
* * hostname
* * os
* * uptime
*/
function get_metric() {
$processes = array();
$output = `ps -eo pcpu,pmem,pid,user,args,time,start | grep -v '[' | sort -k 1 -r | head -30 | awk '{print $4,$3,$1,$2,$7,$6,$5}'`;
$output = explode("n", $output);
if (!is_array($output) || count($output)<2) {
return false;
}
array_shift($output);
foreach ($output as $line) {
//$line = preg_split('/s+/', $line);
$line = explode(' ', $line);
if (count($line)<6) {
continue;
}
//var_dump($line);
//echo count($line);
if (empty($processes[$line[6]])) {
$processes[$line[6]] = array_combine(array('user', 'pid', '%cpu', '%mem','start','time', 'command'), $line);
} else {
$processes[$line[6]]['%cpu'] += $line[2];
$processes[$line[6]]['%mem'] += $line[3];
}
}
return $processes;
}
}
ps - это команда, которая выводит нам информацию о запущенных процессах.
Она предоставляет подробную информацию о процессоре - для этого нужно установить переключатель -e, который позволяет видеть все процессы. Для нашего виджета, нам нужны параметры COU, memory, PID, users, args, time и start.
Для вывода данных в более понятном пользователю формате мы можем совместить команду с -o: ps -eo pcpu,pmem,pid,user,args,time,start. Если вы попробуете выполнить эту команду, то получите некоторый набор не слишком понятной информации о процессах:
[vagrant@vagrant-centos64 ~]$ ps -eo pcpu,pmem,pid,user,args,time,start
%CPU %MEM PID USER COMMAND TIME STARTED
0.0 0.2 1 root /sbin/init 00:00:00 06:50:39
0.0 0.0 2 root [kthreadd] 00:00:00 06:50:39
0.0 0.0 3 root [migration/0] 00:00:00 06:50:39
Обратите внимание на строки [kthread], [migration/0]. Как правило, это означает, что команда не привязана к файловой системе. Это может быть какой-либо внутренний системный процесс или поток ядра, и нам, возможно, информация о них не нужна.
Поэтому мы должны убрать эти процессы с помощью grep. grep позволит нам с переключателем -v убрать некоторые строки. В результате мы возвращаем данные, которые не содержат строк, переданных в grep:
[vagrant@vagrant-centos64 ~]$ ps -eo pcpu,pmem,pid,user,args,time,start | grep -v '['
%CPU %MEM PID USER COMMAND TIME STARTED
0.0 0.2 1 root /sbin/init 00:00:00 06:50:39
0.0 0.1 292 root /sbin/udevd -d 00:00:00 06:50:41
0.0 0.1 811 root /sbin/dhclient -H vagrant-c 00:00:00 06:50:48
0.0 0.2 948 root /sbin/rsyslogd -i /var/run/ 00:00:00 06:50:50
0.0 0.1 966 rpc rpcbind 00:00:00 06:50:50
0.0 0.2 984 rpcuser rpc.statd 00:00:00 06:50:50
0.0 0.0 1011 root rpc.idmapd 00:00:00 06:50:51
0.0 0.2 1073 root /usr/sbin/VBoxService 00:00:00 06:50:51
Чтобы данные выглядели более информативно, нам нужно отсортировать процессы по критерию использования памяти или ресурсов процессора. В данном случае, давайте отсортируем их по значению поля %MEM. Это можно сделать с помощью команды сортировки Linux. %MEM - это второй столбец, его мы используем в качестве базы для сортировки данных.
Так как массив имеет нулевой начальный индекс, то доступ ко второму элементу осуществляется по ключу индекса 1. Мы можем использовать команду sort -k 1. Сортировка будет производиться по возрастанию. Однако на самом деле более всего нас интересует процесс, который потребляет больше всего памяти. Поэтому мы должны изменить порядок сортировки sort -k 1 -r.
После того, как мы получили результат, скорее всего, нам будут интересны только первые 30 процессов. Конечно, это зависит от вас, вы можете включить их все, но я исхожу из соображений разумной достаточности. Первых 30 процессов, думаю, будет вполне достаточно.
Наконец, мы задействуем awk для формирования отчета. Ниже приведена команда и образец выводимых ею данных:
[vagrant@vagrant-centos64 ~]$ ps -eo pcpu,pmem,pid,user,args,time,start | grep -v '[' | sort -k 1 | head -30 | awk '{print $4,$3,$1,$2,$7,$6,$5}'
root 1151 0.0 0.0 00:00:00 -d /sbin/udevd
root 1152 0.0 0.0 00:00:00 -d /sbin/udevd
root 292 0.0 0.0 00:00:00 -d /sbin/udevd
root 811 0.0 0.0 vagrant-c -H /sbin/dhclient
root 1 0.0 0.1 06:50:39 00:00:00 /sbin/init
root 2153 0.0 0.1 -q -1 /sbin/dhclient
root 3642 0.0 0.1 00:00:00 -s /usr/sbin/anacron
vagrant 3808 0.0 0.1 pcpu,pmem,pid,user,a -eo ps
vagrant 3810 0.0 0.1 1 -k sort
vagrant 3811 0.0 0.1 00:00:00 -30 head
vagrant 3812 0.0 0.1 $4,$3,$1,$2,$7,$ {print awk
root 948 0.0 0.1 /var/run/ -i /sbin/rsyslogd
rpc 966 0.0 0.1 06:50:50 00:00:00 rpcbind
root 1073 0.0 0.2 06:50:51 00:00:00 /usr/sbin/VBoxService
root 1105 0.0 0.2 06:50:51 00:00:00 /usr/sbin/sshd
root 1121 0.0 0.2 06:50:52 00:00:00 crond
rpcuser 984 0.0 0.2 06:50:50 00:00:00 rpc.statd
496 1088 0.0 0.3 -p -d memcached
vagrant 3544 0.0 0.3 00:00:00 vagrant@pts/0 sshd:
vagrant 3545 0.0 0.3 06:59:27 00:00:00 -bash
root 1113 0.0 1.7 06:50:52 00:00:00 /usr/sbin/httpd
apache 1157 0.0 4.2 06:50:53 00:00:01 /usr/sbin/httpd
apache 3438 0.0 4.2 06:55:39 00:00:01 /usr/sbin/httpd
После того, как мы вернули результаты, мы разделяем их на массив и цикл с помощью foreach. Мы группируем процессы с одинаковыми именами в элементы и определяем их долю в загрузке процессора:
<?php
//...
// внутренний get_content
foreach ( $output as $line ) {
//$line = preg_split( '/s+/', $line );
$line = explode( ' ', $line );
if ( 6 > count( $line ) ) {
continue;
}
if ( empty( $processes[ $line[6] ] ) ) {
$processes[ $line[6]] = array_combine( array( 'user', 'pid', '%cpu', '%mem','start','time', 'command' ), $line );
} else {
$processes[ $line[6] ]['%cpu'] += $line[2];
$processes[ $line[6] ]['%mem'] += $line[3];
}
}
//...
Мы используем array_combine для создания объединенного массива из двух других массивов: ключей и значений.
Теперь нам осталось только реализовать метод get_content.
Просто напомню, что мы должны обязательно реализовать три метода: get_title, get_metric и get_content. Для данного параметра нам будет достаточно простой таблицы.
Вот как выглядит наш метод get_content:
public function get_content() {
$processes = $this->get_metric();
$html = '<table class="wp-list-table widefat"><thead><tr>
<th>User</th>
<th>Pid</th>
<th>%CPU</th>
<th>%Mem</th>
<th>Command</th>
</tr></thead><tbody>';
foreach ($processes as $process) {
$html .= "<tr>
<td>{$process['user']}</td>
<td>{$process['pid']}</td>
<td>{$process['%cpu']}</td>
<td>{$process['%mem']}</td>
<td>{$process['command']}</td>
</tr>";
}
$html .= '</tbody></table>';
echo $html;
}
А вот, что мы в результате получаем:

Средняя нагрузка
В Linux есть команда, которая показывает среднюю загрузку процессора и IO в течение последней минуты, 5 и 15 минут. Давайте загоним ее в виджет. Назовем этот виджет Cpuload и создадим файл widget/cpuload.php:
<?php
namespace AXStatBoardWidget;
class Cpuload implements Provider {
function __construct() {
}
public function get_title() {
return "CPU Load";
}
function get_metric() {<br> $number_of_core = intval(`/bin/grep -c processor /proc/cpuinfo`);<br> $loadAvg = `cat /proc/loadavg | /usr/bin/awk '{print $1,$2,$3}'`;<br> $loadAvg = explode(' ', $loadAvg);<br> if ($loadAvg <3) {<br> return false;<br> }<br> $loadTimes = array('1 min', '5 mins', '15 mins');<br> return array_map(<br> function ($loadtime, $value, $number_of_core) {<br> return array($loadtime, round($value * 100 / $number_of_core, 2), $value);<br> },<br> $loadTimes,<br> $loadAvg,<br> array_fill(0, 3, $number_of_core)<br> );<br><br> }
}
Первое, что мы определяем - это количество ядер процессора, для этого мы считываем /proc/cpuinfo и считаем количество строк, в которых содержится слово "processor". Мы рассматривали это тему в разделе "Информация о сервере".
В Linux информация о средней нагрузке содержится в /proc/loadavg. Первые три столбца - это нагрузка за одну минуту, пять минут и за пятнадцать минут соответственно. awk мы используем, чтобы отфильтровать только те поля, которые нам нужны:
➜ ~ cat /proc/loadavg
0.01 0.04 0.05 1/217 16089
➜ ~ cat /proc/loadavg | awk '{print $1, $2, $3}'
0.01 0.04 0.05
<br>
Полученный результат разделяется пробелами, в результате чего мы получаем массив с тремя элементами. Средняя нагрузка рассчитывается для всех ядер. Таким образом, чтобы получить результат, мы с помощью array_map обрабатываем через цикл массив $loadAvg и делим значение на количество ядер.
Обратите внимание, что мы создаем два дополнительных массива той же длины, что и $loadAvg: один для ключа, другой - для количества ядер, - они один за другим передаются в обратный вызов array_map.
Пора задействовать get_content:
public function get_content() {
$metrics = $this->get_metric();
if ( ! $metrics ) {
return false;
}
// ознакомьтесь с информацией по адресу https://google-developers.appspot.com/chart/interactive/docs/gallery/barchart#Data_Format for more detai of format
$data = array( array( 'Duration', '% Load' ) );
foreach ( $metrics as $key=>$metric ) {
array_push( $data, array( $metric[0], $metric[1] ) );
}
$data = json_encode( $data );
echo <<<EOD
<div id="avg_load"></div>
<script>
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable($data);
var options = {
hAxis: {
titleTextStyle: {color: 'red'},
minValue:0,
maxValue:100
}
};
var chart = new google.visualization.BarChart(document.getElementById('avg_load'));
chart.draw(data, options);
}
</script>
EOD;
}
Мы используем bar chart и создаем из нашего массива массив данных, используем json_encode, чтобы преобразовать его в массив аннотаций JavaScript, который соответствует формату данных гистограммы.
Пример:
[
["Duration","% Load"],
["1 min",20],
["5 mins",11],
["15 mins",3]
]
Вот как выглядит сама гистограмма:

Интерфейс Ethernet
Следующий виджет будет отображать данные по интерфейсу Ethernet. Некоторые сервера могут использовать несколько интерфейсов Ethernet с различными IP-адресами.
Эта информация может оказаться нам весьма полезной. Назовем этот класс Ethernet, и начнем с создания базового файла widget/ethernet.php:
<?php
/**
* Адаптировано с источника https://github.com/afaqurk/linux-dash/blob/master/sh/ip.php
*
*/
namespace AXStatBoardWidget;
class Ethernet implements Provider {
function __construct() {
}
public function get_title() {
return "Ethernet";
}
function get_metric() {
$ethernet = array();
$output = shell_exec("ip -oneline link show | awk '{print $2}' | sed 's/://'");
if (!$output) { // It didn't work with "ip" , so we do it with ifconfig
$output = shell_exec(
'ifconfig | /bin/grep -B1 "inet addr" | /usr/bin/awk '' .
'{ if ( $1 == "inet" ) { print $2 }' .
'else if ( $2 == "Link" ) { printf "%s:",$1 } }' | /usr/bin/awk' .
' -F: '{ print $1","$3 }''
);
$output = trim($output, " n");
$output = `ifconfig | grep "Link encap" | awk '{ print $1 }'`;
$interfaces = explode("n", $output);
$output = `ifconfiga | grep "inet addr" | awk '{ print $2 }' | sed 's/addr://'`;
$addreses = explode("n", $output);
$output = trim($output, " n");
return array_combine($interfaces, $addreses);
}
$output = trim($output, " n");
$interfaces = explode("n", $output);
$addreses = array();
foreach ($interfaces as $interface) {
$output = shell_exec("ip -oneline -family inet addr show $interface | awk '{print $4}' | cut -d'/' -f1");
$addreses[] = $output;
}
return array_combine($interfaces, $addreses);
}
}
Итак, наш виджет называется - Ethernet. Для get_metric нам нужно получить все имена интерфейсов Ethernet, после этого мы получим IP-адрес каждого из них и объединим имена устройств и IP-адреса, чтобы вернуть эту информацию.
Нам нужно учитывать два случая: когда сервер использует ifconfig и когда сервер использует ip-утилиты. Новые сервера, скорее всего, используют вместо ifconfig; ip; поэтому в первую очередь мы должны запускать ip, чтобы получить информацию об устройствах Ethernet:
$output = shell_exec("ip -oneline link show | awk '{print $2}' | sed 's/://'");
Использование IP-утилиты
Команда ip с переключателем -oneline выдает результат, состоящий всего из одной строки, в которой в качестве link и show представлен список всех устройств.
Мы используем awk, чтобы получить второй столбец с названиями устройств; однако он содержит символ :. Мы используем sed, чтобы заменить : пустой строкой.
Вот результат выполнения команды из командной строки:
[vagrant@vagrant-centos64 sbin]$ ip -oneline link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 08:00:27:08:c2:e4 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 08:00:27:eb:11:e4 brd ff:ff:ff:ff:ff:ff
[vagrant@vagrant-centos64 sbin]$ ip -oneline link show | awk '{print $2}'
lo:
eth0:
eth1:
[vagrant@vagrant-centos64 sbin]$ ip -oneline link show | awk '{print $2}' | sed 's/://'
lo
eth0
eth1
[vagrant@vagrant-centos64 sbin]$
Если проходит выполнение shell_exec, значит, мы имеем дело с IP-утилитой. Приведенные выше данные разбиваются в массив строка за строкой:
$output = trim($output, " n");
$interfaces = explode("n", $output);
После мы пропускаем этот массив через цикл, и снова выполняем ip-команду ip -oneline -family inet addr, чтобы получить IP-адрес, присвоенный устройству.
$addreses = array();
foreach ($interfaces as $interface) {
$output = shell_exec("ip -oneline -family inet addr show $interface | awk '{print $4}' | cut -d'/' -f1");
$addreses[] = $output;
}<br><br>
IP-адрес отображается в четвертом столбце, и мы используем awk, чтобы вывести это значение. Тогда мы разделяем эту команду и получаем первый элемент с помощью переключателя -f1.
Рассмотрите приведенный ниже результат, полученный после выполнения команды из командной строки, чтобы понять, как это все работает:
[vagrant@vagrant-centos64 sbin]$ ip -oneline -family inet addr show eth1
3: eth1 inet 192.168.1.111/24 brd 192.168.1.255 scope global eth1
[vagrant@vagrant-centos64 sbin]$ ip -oneline -family inet addr show eth1 | awk '{print $4}'
192.168.1.111/24
[vagrant@vagrant-centos64 sbin]$ ip -oneline -family inet addr show eth1 | awk '{print $4}' | cut -d'/' -f1
192.168.1.111
[vagrant@vagrant-centos64 sbin]$
После того, как мы получили названия устройств в массиве интерфейсов и IP-адреса в массиве адресов, создаем ассоциированный массив, в котором в качестве ключа используется название интерфейса, IP-адрес - в качестве значения:
return array_combine($interfaces, $addreses);
Серверы, использующие Ifconfig
Если сервер использует Ifconfig, команда shell_exec вернет значение false. В этом случае, мы обрабатываем сценарий, связанный с Ifconfig.
Логика, которую мы рассмотрели выше, остается той же - это всего лишь разные утилиты для захвата информации:
if (!$output) { // It didn't work with "ip" , so we do it with ifconfig
$output = `ifconfig | grep "Link encap" | awk '{ print $1 }'`;
$interfaces = explode("n", $output);
$output = `ifconfig | grep "inet addr" | awk '{ print $2 }' | sed 's/addr://'`;
$addreses = explode("n", $output);
$output = trim($output, " n");
return array_combine($interfaces, $addreses);
}
Давайте запустим приведенную выше команду в терминале.
Обратите внимание, что ifconfig показывает IP-адреса непосредственно в результате вывода, поэтому мы можем сразу же их получить, вместо того, чтобы пропускать через цикл массив устройств и получать IP-адрес каждого устройства:
[vagrant@vagrant-centos64 sbin]$ ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:08:C2:E4
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fe08:c2e4/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:4230 errors:0 dropped:0 overruns:0 frame:0
TX packets:2575 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:444488 (434.0 KiB) TX bytes:2288676 (2.1 MiB)
eth1 Link encap:Ethernet HWaddr 08:00:27:EB:11:E4
inet addr:192.168.1.111 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:feeb:11e4/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:4470 errors:0 dropped:0 overruns:0 frame:0
TX packets:2449 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1689803 (1.6 MiB) TX bytes:271675 (265.3 KiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:264 errors:0 dropped:0 overruns:0 frame:0
TX packets:264 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:15840 (15.4 KiB) TX bytes:15840 (15.4 KiB)
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "Link encap"
eth0 Link encap:Ethernet HWaddr 08:00:27:08:C2:E4
eth1 Link encap:Ethernet HWaddr 08:00:27:EB:11:E4
lo Link encap:Local Loopback
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "Link encap" | awk '{ print $1 }'
eth0
eth1
lo
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "inet addr"
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
inet addr:192.168.1.111 Bcast:192.168.1.255 Mask:255.255.255.0
inet addr:127.0.0.1 Mask:255.0.0.0
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "inet addr" | awk '{ print $2 }'
addr:10.0.2.15
addr:192.168.1.111
addr:127.0.0.1
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "inet addr" | awk '{ print $2 }' | sed 's/addr://'
10.0.2.15
192.168.1.111
127.0.0.1
[vagrant@vagrant-centos64 sbin]$
После того, как данные получены, мы можем легко поместить их в get_content; так как нам не нужна диаграмма, мы выводим простую таблицу:
public function get_content() {
$interfaces = $this->get_metric();
$html = '<table class="wp-list-table widefat"><thead><tr>
<th>Interface</th>
<th>IP</th>
</tr></thead><tbody>';
foreach ( $interfaces as $interface => $ip ) {
$html .= "<tr>
<td>{$interface}</td>
<td>{$ip}</td>
</tr>";
}
$html .= '</tbody></table>';
echo $html;
}
Вот как выглядят полученные данные в консоли администратора:

Сетевой трафик
Сетевой трафик или Network IO - это параметр, показывающий статус передачи пакетов в сети компьютеров. Информация снимается с netstat:
[vagrant@vagrant-centos64 sbin]$ netstat -i
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 0 4828 0 0 0 2933 0 0 0 BMRU
eth1 1500 0 4806 0 0 0 2679 0 0 0 BMRU
lo 16436 0 276 0 0 0 276 0 0 0 LRU
Давайте создадим базовый класс Networkio в файле widget/networkio.php:
<?php
/**
* Адаптировано из источника https://github.com/afaqurk/linux-dash/blob/master/sh/ip.php
*
*/
namespace AXStatBoardWidget;
class Networkio implements Provider {
function __construct() {
}
public function get_title() {
return "Network IO";
}
<br> function get_metric() {<br> $ethernet = array();<br> <br> $output = `netstat -i | grep -v -E '(Iface|Interface)' | awk '{print $1","$4","$8}'`;<br><br> $lines = explode("n", $output);<br> foreach ($lines as $line) {<br> $line = explode(',', $line);<br> if (count($line)<3) {<br> continue;<br> }<br> $ethernet[] = array($line[0], intval($line[1]), intval($line[2]));<br> }<br> return $ethernet;<br> }<br><br>}
Эти данные я поясню чуть позже. А сейчас давайте попробуем разобрать команду, которую мы использовали в приведенном выше коде.
Я хочу запустить сразу несколько команд, чтобы вы понимали, в чем заключается разница:
[vagrant@vagrant-centos64 sbin]$ netstat -i
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 0 5727 0 0 0 3400 0 0 0 BMRU
eth1 1500 0 5004 0 0 0 2797 0 0 0 BMRU
lo 16436 0 292 0 0 0 292 0 0 0 LRU
[vagrant@vagrant-centos64 sbin]$ netstat -i | grep -v -E '(Iface|Interface)'
eth0 1500 0 5736 0 0 0 3405 0 0 0 BMRU
eth1 1500 0 5004 0 0 0 2797 0 0 0 BMRU
lo 16436 0 292 0 0 0 292 0 0 0 LRU
[vagrant@vagrant-centos64 sbin]$ netstat -i | grep -v -E '(Iface|Interface)' | awk '{print $1","$4","$8}'
eth0,5760,3420
eth1,5004,2797
lo,292,292
[vagrant@vagrant-centos64 sbin]$<br><br>
netstat возвращает много показателей, мы использовали grep, чтобы удалить строки, содержащие слова Iface или Kernel (первые две строки).
Нам нужны только последние три строки с информацией об устройствах локальной сети и передаваемых ими пакетах. awk используется, чтобы вывести данные первого, четвертого и восьмого столбцов, в которых содержатся название интерфейса, RX-OK и TX-OK.
В нашем методе get_metric мы разбиваем строки результата в массив. Поскольку каждая из строк содержит данные, разделенные запятой, каждая из них соответственно тоже разбивается на массив.
Мы должны обеспечить, чтобы создавался массив только из трех элементов. Мы также хотим преобразовать цифровую строку в целое число. Данные, в свою очередь будут передаваться в get_content:
public function get_content() {
$interfaces = $this->get_metric();
$data = array_merge(array(array('Interface', 'Receive(package)', 'Transfer(package)')), $interfaces);
$data = json_encode($data);
echo <<<EOD
<div id="nio_chart"></div>
<script>
google.setOnLoadCallback(function () {
var data = google.visualization.arrayToDataTable({$data});
var options = {
};
var chart = new google.visualization.ColumnChart(document.getElementById('nio_chart'));
chart.draw(data, options);
})
</script>
EOD;
}<br><br>
Мы уже использовали гистограмму ранее, теперь мы снова формируем массив данных метрики в соответствии с форматом данных гистограммы, а затем выводим их.
Каждая строка массива данных представляет собой раздел группы с именем раздела и соответствующими ему значениями. В нашем случае, каждая строка содержит имя интерфейса и столбцы RX и TX.
Вот что мы получили:

Статистика ввода / вывода данных
Теперь, мы займемся виджетом io stat. IO - означает ввод / вывод (input/output). Мы узнаем, сколько операций чтения / записи выполняется в секунду. Мы также получим метрику io_wait. Это время, которое процессор простаивает в ожидании результата, который считывается с жесткого диска.
Например, вы считываете данные MySQL, и процессор простаивает в ожидании результата. io wait рассчитывается в 1 секунду или за 1000 миллисекунд. Если считывание кода с жесткого диска занимает 100 миллисекунд, то значение io_wait равно 100/1000 = 10%. Чем меньше io_wait, тем выше производительность системы.
Для того чтобы перейти к разработке следующего виджета, пожалуйста, убедитесь, что у вас установлен пакет Sysstat.
- Для Arch Linux он должен быть установлен с pacman -S sysstat;
- Для Debian / Ubuntu - с apt-get install sysstat;
- Для Fedora / Centos - с yum install sysstat;
- Для других дистрибутивов - пожалуйста, используйте менеджер пакетов дистрибутива.
Теперь давайте рассмотрим некоторые команды, которые мы будем использовать:
[vagrant@vagrant-centos64 sbin]$ iostat
Linux 2.6.32-358.23.2.el6.x86_64 (vagrant-centos64.vagrantup.com) 04/27/2014 _x86_64_ (1 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
0.05 0.00 0.25 0.04 0.00 99.66
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
sda 0.18 7.62 1.04 157826 21584
Четвертая строка содержит данные состояния IO. Нам нужно значение iowait в четвертом столбце. Данные седьмой строчки содержат блок количества считываний / записей на жесткий диск в секунду.
Если у вас есть много жестких дисков, подключенных к серверу, вы будете иметь более одного устройства: sdb, sdc и так далее.
Данные представляют количество блоков, а не объем в мегабайтах. Чтобы рассчитать общий объем, мы должны получить размер одного блока.
Размер блока хранится в /sys/block/sda/queue/physical_block_size:
[vagrant@vagrant-centos64 ~]$ cat /sys/block/sda/queue/physical_block_size
512
[vagrant@vagrant-centos64 ~]$
Таким образом, размер блока sda составляет 512. Умножаем количество блоков, считываемых в секунду на размер блока, чтобы получить реальный объем считывания / записи данных.
Основываясь на приведенной выше информации, давайте создадим класс Iostat в файле widget/iostat.php:
<?php
namespace AXStatBoardWidget;
class Iostat implements Provider {
function __construct() {
}
public function get_title() {
return "Disk IO";
}
/**
* Проверяем, установлен ли пакет sysstat
* yum устанавливает sysstat
* apt-get также устанавливает sysstat
*
* Выводим информацию о вводе/выводе данных: времени ожидания процессора, количеству обращений к жесткому диску
*
*/
function get_metric() {
$metric = array();
$output = `iostat`;
$number_of_core = intval(`/bin/grep -c processor /proc/cpuinfo`);
$lines = explode("n", $output);
//We should have more than 4 lines
if (!is_array($lines) || sizeof($lines)<4) {
return false;
}
$avg_cpu = preg_split("/s+/", $lines[3]);
$metric['cpu'] = array(
'user' => floatval($avg_cpu[0]) * $number_of_core,
'system' => floatval($avg_cpu[2]) * $number_of_core,
'io_wait' => floatval($avg_cpu[3]) * $number_of_core,
'other' => 100 - ($avg_cpu[0] + $avg_cpu[2] + $avg_cpu[3])
);
if (sizeof($lines) >=7) {
for ($i=6,$l = sizeof($lines);$i<$l; $i++) {
$line = preg_split("/s+/", $lines[$i]);
if (!is_array($line) || sizeof($line)<5) {
continue;
}
// Вычисляем размер блока
$block_size = shell_exec("cat /sys/block/{$lines[1]}/queue/physical_block_size");
$metric['disk'][$line[0]] = array(
'read' => floatval($line[2]) * $block_size / 1024,
'write' => floatval($line[3]) * $block_size / 1024,
);
}
}
return $metric;
}
}
Мы просто реализовали приведенную выше теорию через PHP-код. И получили выходные данные iostat. Преобразуем их в массив, где каждая строка представляет собой элемент массива.
Четвертая строка разделяется пробелами и преобразуется в ассоциированный массив. Все строки ниже седьмой помещены в другой ассоциированный массив, для которого ключом является имя устройства (sda, sdb, sdc и т.д.), а значением - массив объема считываний или записей данных на диск в мегабайтах.
После того, как мы получили метрику, отправляем ее в метод get_content и создаем диаграмму:
public function get_content() {
$metric = $this->get_metric();
$disk_io = array(
array('Disk', 'Read(MB)', 'Write(MB)'),
);
foreach ($metric['disk'] as $disk=>$stat) {
$disk_io[] = array($disk, $stat['read'], $stat['write']);
}
$disk_io = json_encode($disk_io);
$cpu_io = json_encode(array(
array('CPU Time', 'Percent'),
array('IO Wait', $metric['cpu']['io_wait']),
));
echo <<<EOD
<div id="widget_disk_io"></div>
<div id="widget_cpu_io_wait"></div>
<script>
google.load('visualization', '1', {packages:['gauge']});
google.setOnLoadCallback(function () {
var data = google.visualization.arrayToDataTable({$cpu_io});
var goptions = {
redFrom: 80, redTo: 100,
yellowFrom:50, yellowTo: 80,
minorTicks: 5
};
var chart = new google.visualization.Gauge(document.getElementById('widget_cpu_io_wait'));
chart.draw(data, goptions);
var data2 = google.visualization.arrayToDataTable({$disk_io});
var chart2 = new google.visualization.ColumnChart(document.getElementById('widget_disk_io'));
chart2.draw(data2, {});
})
</script>
EOD;
}
Для данных по объему считывания / записи на диск мы используем гистограмму. Для времени ожидания мы используем диаграмму - датчик, чтобы результат выглядел красиво.
Мы учитываем, что среднее значение времени ожидания в диапазоне 80-100 является тревожным сигналом, и выделяем этот сектор красным цветом.
Сектор 50-80 мы выделяем желтым цветом. Датчик позволяет нам сделать это с помощью опции:
var goptions = {
redFrom: 80, redTo: 100,
yellowFrom:50, yellowTo: 80,
minorTicks: 5
};
Вот как выглядит результат наших стараний:

Что дальше?
На данный момент мы создали все виджеты нашего плагина. Вы можете скачать весь код плагина этой статьи, чтобы не вводить его вручную. Установите плагин, а затем обновите страницу консоли и установите соответствующие опции экрана, чтобы задать, какой именно виджет вы хотите вывести:

Наш плагин уже сейчас прекрасно работает; однако, мы можем пойти еще дальше и реализовать проверку пользователя и его роли, чтобы отображать данные в зависимости от ее результатов.
Мы также должны использовать для получения этих метрик cronjob. Не пропустите следующую часть этой серии.
Как всегда, не забудьте оставить комментарий, дайте нам знать, что вы думаете о плагине, а также о том, как вам удалось его настроить.