


Recall that all widget providers have to implement
Provider interface. They also have to sit inside folder named widget, and under the namespace AX\StatBoard\Widget. If we want to add a new kind of metric, just create a corresponding class, and create an object and add it to Widget class with the add_provider method.RAM Usage Widget
One of the first pieces of information we want to display is how much RAM is currently being used and how much is currently free.
In this case, free -m is our friend - it tells us RAM usage. the -m switch is to output the result in megabytes.
1 |
[vagrant@vagrant-centos64 ~]$ free -m |
2 |
total used free shared buffers cached |
3 |
Mem: 589 366 223 0 9 57 |
4 |
-/+ buffers/cache: 299 290 |
5 |
Swap: 0 0 0 |
We will name the class is Ram. The corresponding file will be widget/ram.php. We just compose the basic and implement get_title here.
1 |
<?php
|
2 |
namespace AX\StatBoard\Widget; |
3 |
|
4 |
class Ram implements Provider { |
5 |
function __construct() { |
6 |
}
|
7 |
|
8 |
public function get_title() { |
9 |
return "Ram Usage"; |
10 |
}
|
11 |
|
12 |
?>
|
get_metric to actually run the necessary shell command and pull out the information. I will explain more detail of get_metric later.1 |
<?php
|
2 |
function get_metric() { |
3 |
$df = `free -m | grep -E "(Mem|Swap)" | awk '{print $1, $2, $3, $4}'`; |
4 |
$df = explode("\n", $df); |
5 |
if ( is_array( $df ) && 2 <= count( $df ) ) { |
6 |
$df = array_map( function ( $line ) { |
7 |
if ( empty( $line ) ) { |
8 |
return; |
9 |
}
|
10 |
$segment = preg_split( '/\s+/', $line ); |
11 |
|
12 |
return array( |
13 |
'type' => trim( $segment[0]," :" ), |
14 |
'total' => (int)$segment[1], |
15 |
'used' => (int)$segment[2], |
16 |
'free' => (int)$segment[3], |
17 |
);
|
18 |
}, $df ); |
19 |
return $df; |
20 |
}
|
21 |
return false; |
22 |
}
|
23 |
?>
|
free -m | grep -E "Mem|Swap" | awk '{print $1, $2, $3, $4}'. Its output looks similar to this.1 |
[vagrant@vagrant-centos64 ~]$ free -m | grep -E "Mem|Swap" | awk '{print $1, $2, $3, $4}' |
2 |
Mem: 589 541 47 |
3 |
Swap: 255 0 255 |
4 |
[vagrant@vagrant-centos64 ~]$ |
array_map to loop over all element of array and for each of line, we split by spaces, then return an associative array with elements:-
type: first field
-
total: second field
-
used: third field -
free: forth field
get_content.1 |
public function get_content() { |
2 |
$metric = $this->get_metric(); |
3 |
$data = array( |
4 |
array('Type', 'Used(MB)', 'Free(MB)') |
5 |
);
|
6 |
|
7 |
foreach ($metric as $item) { |
8 |
if (empty($item)) { |
9 |
continue; |
10 |
}
|
11 |
if ($item['type'] !== 'Mem' && $item['type'] !== 'Swap') { |
12 |
continue; |
13 |
}
|
14 |
if ( 0 == ($item['free'] + $item['used'])) { |
15 |
continue; |
16 |
}
|
17 |
|
18 |
$data[] = array( |
19 |
$item['type'],$item['used'], $item['free'] |
20 |
);
|
21 |
}
|
22 |
$data = json_encode($data); |
23 |
echo <<<EOD |
24 |
<div id="widget_ram_usage"></div> |
25 |
<script type="text/javascript"> |
26 |
google.setOnLoadCallback(function () { |
27 |
var data = google.visualization.arrayToDataTable({$data}); |
28 |
var options = { |
29 |
isStacked: true |
30 |
};
|
31 |
var chart = new google.visualization.ColumnChart(document.getElementById('widget_ram_usage')); |
32 |
chart.draw(data, options); |
33 |
})
|
34 |
</script> |
35 |
EOD; |
36 |
}
|
get_metric() to get the necessary data. Then, we just loop over it and format it to match Google chart data requirement. Finally, we use json_encode to convert them into a JavaScript object notation (or JSON). Then, we output a HTML code that contains a div element to hold the chart object. div element.


Installed Software
The second widget we will cover is one that will display installed software. It's a widget that's intended to show what common packages we have on server and which version. widget/software.php with this initial content:1 |
<?php
|
2 |
namespace AX\StatBoard\Widget; |
3 |
|
4 |
class Software implements Provider { |
5 |
function __construct() { |
6 |
}
|
7 |
|
8 |
public function get_title() { |
9 |
return "Installed Software"; |
10 |
}
|
11 |
function get_metric() { |
12 |
$cmds = array(); |
13 |
|
14 |
$package = array( |
15 |
'php' => '-v', |
16 |
'node' => '-v', |
17 |
'mysql' => '-V', |
18 |
'vim' => '--version', |
19 |
'python' => '-V', |
20 |
'ruby' => '-v', |
21 |
'java' => '-version', |
22 |
'curl' => '-V'); |
23 |
|
24 |
foreach ( $package as $cmd=>$version_query ) { |
25 |
if ( NULL == $cmds[$cmd] = shell_exec( "which $cmd" ) ) { |
26 |
$cmds[ $cmd ] = 'Not installed'; |
27 |
continue; |
28 |
}
|
29 |
$version = shell_exec( "$cmd $version_query" ); |
30 |
$version = explode( "\n", $version ); |
31 |
if ( is_array( $version ) ) { |
32 |
$version = array_shift( $version ); |
33 |
}
|
34 |
$cmds[ $cmd ] .= '<br>' . $version; |
35 |
}
|
36 |
return $cmds; |
37 |
}
|
get_title and it just return a simple string. For the get_metric(), we want to know if particular piece of software is installed or not. If so, then get its version information. php -v shows version information, mysql --version shows MySQL information. shell_exec returns false if the command returns and error or the command is not found. In that case, we can determine that the software isn't installed; otherwise, we can parse the result to show the version information. Then, we split the result line-by-line, and retrieve the first line as version information. That's because the information we need is found only on the first line. get_content method.1 |
public function get_content() { |
2 |
|
3 |
$cmds = $this->get_metric(); |
4 |
$content = ''; |
5 |
|
6 |
foreach ( $cmds as $cmd => $info ) { |
7 |
$content .= "<p><strong>$cmd</strong> $info</p>"; |
8 |
}
|
9 |
echo $content; |
10 |
|
11 |
}
|



Disk Usage
Now we'll tackle disk usage. We name the class that handles this task Disk. Let's craft basic skeleton first.
1 |
<?php
|
2 |
namespace AX\StatBoard\Widget; |
3 |
|
4 |
class Disk implements Provider { |
5 |
function __construct() { |
6 |
}
|
7 |
public function get_title() { |
8 |
return "Disk Usage"; |
9 |
}
|
10 |
}
|
Provider interface. We set a title for our widget here. Next is the heart of our class: The method for getting disk usage.1 |
<?php
|
2 |
function get_metric() { |
3 |
$df = `df -h`; |
4 |
$df = explode("\n", $df); |
5 |
if (is_array($df) && count($df)>=2) { |
6 |
array_shift($df); //Get rid the first line |
7 |
$df = array_map(function ($line) { |
8 |
if (empty($line)) { |
9 |
return NULL; |
10 |
}
|
11 |
$segment=preg_split('/\s+/', $line); |
12 |
|
13 |
return array( |
14 |
'filesystem' => $segment[0], |
15 |
'size' => $segment[1], |
16 |
'used' => $segment[2], |
17 |
'available' => $segment[3], |
18 |
'use_percent' => $segment[4], |
19 |
);
|
20 |
}, $df); |
21 |
return $df; |
22 |
}
|
23 |
return false; |
24 |
}
|
df command so understanding the following command should be easy: df output:1 |
[vagrant@vagrant-centos64 ~]$ df -h |
2 |
Filesystem Size Used Avail Use% Mounted on |
3 |
/dev/sda1 7.3G 1.4G 5.6G 20% / |
4 |
tmpfs 295M 0 295M 0% /dev/shm |
5 |
/vagrant 60G 55G 4.9G 92% /vagrant |
6 |
/data/GeoIP 60G 55G 4.9G 92% /data/GeoIP |
7 |
/var/webapps 60G 55G 4.9G 92% /var/webapps |
8 |
/var/www/html 60G 55G 4.9G 92% /var/www/html |
get_content.1 |
public function get_content() { |
2 |
$metric = $this->get_metric(); |
3 |
$data = array( |
4 |
array( 'Disk', 'Space' ) |
5 |
);
|
6 |
|
7 |
$disk_container = array(); |
8 |
$data_partition = array( |
9 |
array('Filesystem', 'Free(GB)', 'Used(GB)') |
10 |
);
|
11 |
foreach ( $metric as $disk ) { |
12 |
$size = intval( $disk['size'] ); |
13 |
if ( 'M' == substr( $disk['size'], -1 ) ) { |
14 |
$size = round( $size / 1024, 2 ); |
15 |
}
|
16 |
$used = intval( $disk['used'] ); |
17 |
if ('M' == substr( $disk['used'], -1 ) ) { |
18 |
$used = round( $used / 1024, 2 ); |
19 |
}
|
20 |
|
21 |
if ( empty( $size ) ) { |
22 |
continue; |
23 |
}
|
24 |
$data[] = array( $disk['filesystem'], $size ); |
25 |
$data_partition[] = array($disk['filesystem'], $size - $used, $used); |
26 |
}
|
27 |
}
|
28 |
1 |
[ ['File System', 'Free', 'Used', |
2 |
['/dev/sda1', 10, 24], |
3 |
['/dev/sda2', 28, 19]] |
- The first chart is for the space of each mounted file system on the total. For this data, we will be using a pie chart.
- The second chart is for displaying the disk usage of each individual mounted file system. For this, we'll be using a bar chart.
To that end, let's modify our method to the following:
1 |
public function get_content() { |
2 |
$metric = $this->get_metric(); |
3 |
$data = array( |
4 |
array('Disk', 'Space') |
5 |
);
|
6 |
|
7 |
$disk_container = array(); |
8 |
$data_partition = array( |
9 |
array('Filesystem', 'Free(GB)', 'Used(GB)') |
10 |
);
|
11 |
foreach ($metric as $disk) { |
12 |
$size = intval($disk['size']); |
13 |
if ('M' == substr($disk['size'], -1)) { |
14 |
$size = round($size / 1024, 2); |
15 |
}
|
16 |
$used = intval($disk['used']); |
17 |
if ('M' == substr($disk['used'], -1)) { |
18 |
$used = round($used / 1024, 2); |
19 |
}
|
20 |
|
21 |
if (empty($size)) { |
22 |
continue; |
23 |
}
|
24 |
$data[] = array($disk['filesystem'], $size); |
25 |
$data_partition[] = array($disk['filesystem'], $size - $used, $used); |
26 |
}
|
27 |
$data = json_encode($data); |
28 |
$data_partition = json_encode($data_partition); |
29 |
|
30 |
echo <<<EOD |
31 |
<div id="widget_disk_usage"></div> |
32 |
<div id="widget_disk_partion"></div> |
33 |
<script type="text/javascript"> |
34 |
google.load("visualization", "1", {packages:["corechart"]}); |
35 |
google.setOnLoadCallback(function () { |
36 |
var data = google.visualization.arrayToDataTable({$data}); |
37 |
var options = { |
38 |
is3D: true, |
39 |
};
|
40 |
var chart = new google.visualization.PieChart(document.getElementById('widget_disk_usage')); |
41 |
chart.draw(data, options); |
42 |
|
43 |
var data2 = google.visualization.arrayToDataTable({$data_partition}); |
44 |
var options2 = { |
45 |
isStacked: true |
46 |
};
|
47 |
var chart2 = new google.visualization.ColumnChart(document.getElementById('widget_disk_partion')); |
48 |
chart2.draw(data2, options2); |
49 |
|
50 |
})
|
51 |
</script> |
52 |
EOD; |
53 |
}
|
div elements to contain the information1 |
<div id="widget_disk_usage"></div> |
2 |
<div id="widget_disk_partion"></div> |



Server Information
This widget shows us the information: Linux kernel, CPU architecture, Up time, IP Address. We don't need a chart here, a simple data table do the job. Calling the class is Server. Here is out first content for widget/server.php
1 |
<?php
|
2 |
namespace AX\StatBoard\Widget; |
3 |
use DateTime; |
4 |
|
5 |
class Server implements Provider { |
6 |
function __construct() { |
7 |
}
|
8 |
|
9 |
public function get_title() { |
10 |
return "Server Info"; |
11 |
}
|
12 |
|
13 |
/**
|
14 |
* Return server info: OS, Kernel, Uptime, and hostname
|
15 |
* @return array with 3 metric:
|
16 |
* * hostname
|
17 |
* * os
|
18 |
* * uptime
|
19 |
*/
|
20 |
function get_metric() { |
21 |
$server = array(); |
22 |
$server['hostname'] = `hostname`; |
23 |
$server['os'] = `uname -sr`; |
24 |
$server['core'] = `grep -c ^processor /proc/cpuinfo`; |
25 |
$total_uptime_sec = time() - `cut -d. -f1 /proc/uptime`; |
26 |
|
27 |
$now = new DateTime("now"); |
28 |
$server['uptime'] = $now->diff(new DateTime("@$total_uptime_sec"))->format('%a days, %h hours, %i minutes and %s seconds'); |
29 |
|
30 |
// Get the external ip with ifconfig.me, a website that show you ip address in plaintext
|
31 |
// when sending request with curl header
|
32 |
$server['ip'] = `curl ifconfig.me`; |
33 |
$server['ram'] = `free -m | grep Mem | awk '{print $2}'`; |
34 |
$server['cpu'] =`cat /proc/cpuinfo | grep "model name" | awk '{print $4,$5,$6,$7}'`; |
35 |
|
36 |
return $server; |
37 |
}
|
38 |
|
39 |
}
|
get_title(). We just return the title of this widget.use DateTime because we are inside the namespace AX\StatBoard\Widget and the DateTime class is from the global namespace. Every time we want to use DateTime we have to type \DateTime. So we use namespace importing to make DateTime name available inside our current name space. get_metric we run shell command to get result and just assign it back.hostname
Show your server hostname.
uname -sr
Show Linux kernel information:
1 |
[vagrant@vagrant-centos64 ~]$ uname -sr |
2 |
Linux 2.6.32-358.23.2.el6.x86_64 |
grep -c ^processor /proc/cpuinfo
-c switch prints a count of matching line in the input string. /proc/cpuinfo contains processor information. We grep it and count the occurrence of the word processor. Here is my result with 32 cores.
1 |
$ grep -c ^processor /proc/cpuinfo |
2 |
32 |
cut -d. -f1 /proc/uptime
This command shows how many seconds the server has been up and running. We convert that numbers of second into a format of "x day y hour z minute" to make it more user-friendly.
Using DateTime::diff we can achieve this easily. We create a DateTime object with current timestamp and another one with the timestamp is the current timestamp minus the numbers of second of uptime. Then using format method to format it to a human friendly string.
Here is my result with the uptime of 26194091 seconds.
1 |
$ cut -d. -f1 /proc/uptime |
2 |
26194091 |
curl ifconfig.me
ifconfig.me is a service that shows your IP address when visiting directly inside a browser. If you send it a request with curl, it returns your IP address as a single string.
1 |
[vagrant@vagrant-centos64 ~]$ curl ifconfig.me |
2 |
76.102.253.237 |
CPU Model
As mentioning above,/proc/cpuinfo stores CPU information. We can extract the CPU model from it. For example:1 |
[vagrant@vagrant-centos64 ~]$ cat /proc/cpuinfo | grep "model name" | awk '{print $4,$5,$6,$7}' |
2 |
Intel(R) Core(TM) i5-4250U CPU |
get_content method these piece of data. Here is out get_content, just showing the data:1 |
public function get_content() { |
2 |
$server = $this->get_metric(); |
3 |
echo <<<EOD |
4 |
<strong>Ip Address</strong> {$server['ip']}<br> |
5 |
<strong>CPU</strong> {$server['cpu']}<br> |
6 |
<strong>Number of Core</strong> {$server['core']}<br> |
7 |
<strong>Ram</strong> {$server['ram']}<br> |
8 |
<strong>Hostname</strong> {$server['hostname']}<br> |
9 |
<strong>OS</strong> {$server['os']}<br> |
10 |
<strong>Uptime</strong> {$server['uptime']}<br> |
11 |
EOD; |
12 |
}
|



Processor
Monitoring our processor is one of the most important things we can display. We want to know how much of the CPU is being used and/or the amount of memory a particular process is consuming. We call our classProcess, starting with get_title and get_metric first. I will explain more detail of get_metric after the code:1 |
<?php |
2 |
namespace AX\StatBoard\Widget; |
3 |
|
4 |
class Process implements Provider {
|
5 |
|
6 |
|
7 |
public function get_title() {
|
8 |
return "Processes"; |
9 |
} |
10 |
|
11 |
/** |
12 |
* Return server info: OS, Kernel, Uptime, and hostname |
13 |
* @return array with 3 metric: |
14 |
* * hostname |
15 |
* * os |
16 |
* * uptime |
17 |
*/ |
18 |
function get_metric() {
|
19 |
$processes = array(); |
20 |
$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}'`;
|
21 |
$output = explode("\n", $output);
|
22 |
if (!is_array($output) || count($output)<2) {
|
23 |
return false; |
24 |
} |
25 |
array_shift($output); |
26 |
foreach ($output as $line) {
|
27 |
//$line = preg_split('/\s+/', $line);
|
28 |
$line = explode(' ', $line);
|
29 |
if (count($line)<6) {
|
30 |
continue; |
31 |
} |
32 |
//var_dump($line); |
33 |
//echo count($line); |
34 |
if (empty($processes[$line[6]])) {
|
35 |
$processes[$line[6]] = array_combine(array('user', 'pid', '%cpu', '%mem','start','time', 'command'), $line);
|
36 |
} else {
|
37 |
$processes[$line[6]]['%cpu'] += $line[2]; |
38 |
$processes[$line[6]]['%mem'] += $line[3]; |
39 |
} |
40 |
} |
41 |
|
42 |
return $processes; |
43 |
} |
44 |
|
45 |
} |
46 |
-e as it allows us to see every process. For our widget, we only need to pull our COU, memory, PID, users, args, time and start. -o mean user-defined format like: ps -eo pcpu,pmem,pid,user,args,time,start. If you try out that command, you will get some of weird process like:1 |
[vagrant@vagrant-centos64 ~]$ ps -eo pcpu,pmem,pid,user,args,time,start |
2 |
%CPU %MEM PID USER COMMAND TIME STARTED |
3 |
0.0 0.2 1 root /sbin/init 00:00:00 06:50:39 |
4 |
0.0 0.0 2 root [kthreadd] 00:00:00 06:50:39
|
5 |
0.0 0.0 3 root [migration/0] 00:00:00 06:50:39
|
grep. grep has -v switch enable us to to invert matching. It return the result that doesn't contain the string we pass to it.1 |
[vagrant@vagrant-centos64 ~]$ ps -eo pcpu,pmem,pid,user,args,time,start | grep -v '\[' |
2 |
%CPU %MEM PID USER COMMAND TIME STARTED |
3 |
0.0 0.2 1 root /sbin/init 00:00:00 06:50:39 |
4 |
0.0 0.1 292 root /sbin/udevd -d 00:00:00 06:50:41
|
5 |
0.0 0.1 811 root /sbin/dhclient -H vagrant-c 00:00:00 06:50:48
|
6 |
0.0 0.2 948 root /sbin/rsyslogd -i /var/run/ 00:00:00 06:50:50
|
7 |
0.0 0.1 966 rpc rpcbind 00:00:00 06:50:50 |
8 |
0.0 0.2 984 rpcuser rpc.statd 00:00:00 06:50:50 |
9 |
0.0 0.0 1011 root rpc.idmapd 00:00:00 06:50:51 |
10 |
0.0 0.2 1073 root /usr/sbin/VBoxService 00:00:00 06:50:51 |
%MEM. We can do that with sort command of Linux. And %MEM is the second column. sort -k 1. It sorts lowest to highest. We actually care about the process that's consuming lot memory first. To that end, we should reverse the order with sort -k 1 -r. Once we got the result, we may only need the first 30 processes. Of course, it depends on you as you can choose to include everything, but I want to keep it short. 30 sounds like a reasonable number. 1 |
[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}' |
2 |
root 1151 0.0 0.0 00:00:00 -d /sbin/udevd
|
3 |
root 1152 0.0 0.0 00:00:00 -d /sbin/udevd
|
4 |
root 292 0.0 0.0 00:00:00 -d /sbin/udevd
|
5 |
root 811 0.0 0.0 vagrant-c -H /sbin/dhclient
|
6 |
root 1 0.0 0.1 06:50:39 00:00:00 /sbin/init |
7 |
root 2153 0.0 0.1 -q -1 /sbin/dhclient |
8 |
root 3642 0.0 0.1 00:00:00 -s /usr/sbin/anacron
|
9 |
vagrant 3808 0.0 0.1 pcpu,pmem,pid,user,a -eo ps
|
10 |
vagrant 3810 0.0 0.1 1 -k sort |
11 |
vagrant 3811 0.0 0.1 00:00:00 -30 head |
12 |
vagrant 3812 0.0 0.1 $4,$3,$1,$2,$7,$ {print awk |
13 |
root 948 0.0 0.1 /var/run/ -i /sbin/rsyslogd
|
14 |
rpc 966 0.0 0.1 06:50:50 00:00:00 rpcbind |
15 |
root 1073 0.0 0.2 06:50:51 00:00:00 /usr/sbin/VBoxService |
16 |
root 1105 0.0 0.2 06:50:51 00:00:00 /usr/sbin/sshd |
17 |
root 1121 0.0 0.2 06:50:52 00:00:00 crond |
18 |
rpcuser 984 0.0 0.2 06:50:50 00:00:00 rpc.statd |
19 |
496 1088 0.0 0.3 -p -d memcached |
20 |
vagrant 3544 0.0 0.3 00:00:00 vagrant@pts/0 sshd: |
21 |
vagrant 3545 0.0 0.3 06:59:27 00:00:00 -bash
|
22 |
root 1113 0.0 1.7 06:50:52 00:00:00 /usr/sbin/httpd |
23 |
apache 1157 0.0 4.2 06:50:53 00:00:01 /usr/sbin/httpd |
24 |
apache 3438 0.0 4.2 06:55:39 00:00:01 /usr/sbin/httpd |
foreach. We group same name process into an element and add up the CPU percent and memory into it.1 |
<?php |
2 |
//... |
3 |
// inside get_content |
4 |
|
5 |
foreach ( $output as $line ) {
|
6 |
//$line = preg_split( '/\s+/', $line ); |
7 |
$line = explode( ' ', $line ); |
8 |
if ( 6 > count( $line ) ) {
|
9 |
continue; |
10 |
} |
11 |
if ( empty( $processes[ $line[6] ] ) ) {
|
12 |
$processes[ $line[6]] = array_combine( array( 'user', 'pid', '%cpu', '%mem','start','time', 'command' ), $line ); |
13 |
} else {
|
14 |
$processes[ $line[6] ]['%cpu'] += $line[2]; |
15 |
$processes[ $line[6] ]['%mem'] += $line[3]; |
16 |
} |
17 |
} |
18 |
//... |
array_combine to create an associative array from two arrays: one for keys and one for values. get_content method. Just a remind that we have to implement threes method: get_title, get_metric , and get_content. For the process, we only want to show a simple table. get_content method is straight forward. 1 |
public function get_content() { |
2 |
$processes = $this->get_metric(); |
3 |
$html = '<table class="wp-list-table widefat"><thead><tr> |
4 |
<th>User</th>
|
5 |
<th>Pid</th>
|
6 |
<th>%CPU</th>
|
7 |
<th>%Mem</th>
|
8 |
<th>Command</th>
|
9 |
</tr></thead><tbody>'; |
10 |
foreach ($processes as $process) { |
11 |
$html .= "<tr> |
12 |
<td>{$process['user']}</td> |
13 |
<td>{$process['pid']}</td> |
14 |
<td>{$process['%cpu']}</td> |
15 |
<td>{$process['%mem']}</td> |
16 |
<td>{$process['command']}</td> |
17 |
</tr>"; |
18 |
}
|
19 |
$html .= '</tbody></table>'; |
20 |
echo $html; |
21 |
}
|



Average Load
Linux has a command that shows us the average load of CPU and IO in over the last minute, five minutes, and 15 minutes. Let crunch it into a widget. Call it Cpuload, and create our widget/cpuload.php1 |
<?php
|
2 |
namespace AX\StatBoard\Widget; |
3 |
|
4 |
class Cpuload implements Provider { |
5 |
function __construct() { |
6 |
}
|
7 |
|
8 |
public function get_title() { |
9 |
return "CPU Load"; |
10 |
}
|
11 |
function get_metric() { $number_of_core = intval(`/bin/grep -c processor /proc/cpuinfo`); $loadAvg = `cat /proc/loadavg | /usr/bin/awk '{print $1,$2,$3}'`; $loadAvg = explode(' ', $loadAvg); if ($loadAvg <3) { return false; } $loadTimes = array('1 min', '5 mins', '15 mins'); return array_map( function ($loadtime, $value, $number_of_core) { return array($loadtime, round($value * 100 / $number_of_core, 2), $value); }, $loadTimes, $loadAvg, array_fill(0, 3, $number_of_core) ); } |
12 |
|
13 |
}
|
/proc/cpuinfo and count the number of line contains the word "processor". We covered this in the section entitled Server Information./proc/loadavg hold average load information. The first three columns are the load of one minute, five minutes and 15 minutes, respectively. awk is used here to filter out only fields that we need.1 |
➜ ~ cat /proc/loadavg
|
2 |
0.01 0.04 0.05 1/217 16089 |
3 |
➜ ~ cat /proc/loadavg | awk '{print $1, $2, $3}' |
4 |
0.01 0.04 0.05 |
$loadAvg array with array_map and divide with the number of cores we have. Note that we create 2 extra arrays with same length as the $loadAvg, one is for the key, another are to hold the number of core to pass all of the one by one to the callback of array_map. get_content:1 |
public function get_content() { |
2 |
$metrics = $this->get_metric(); |
3 |
if ( ! $metrics ) { |
4 |
return false; |
5 |
}
|
6 |
// see https://google-developers.appspot.com/chart/interactive/docs/gallery/barchart#Data_Format for more detai of format
|
7 |
$data = array( array( 'Duration', '% Load' ) ); |
8 |
foreach ( $metrics as $key=>$metric ) { |
9 |
array_push( $data, array( $metric[0], $metric[1] ) ); |
10 |
}
|
11 |
$data = json_encode( $data ); |
12 |
echo <<<EOD |
13 |
<div id="avg_load"></div> |
14 |
<script type="text/javascript"> |
15 |
google.load("visualization", "1", {packages:["corechart"]}); |
16 |
google.setOnLoadCallback(drawChart); |
17 |
function drawChart() { |
18 |
var data = google.visualization.arrayToDataTable($data); |
19 |
|
20 |
var options = { |
21 |
hAxis: { |
22 |
titleTextStyle: {color: 'red'}, |
23 |
minValue:0, |
24 |
maxValue:100 |
25 |
}
|
26 |
};
|
27 |
|
28 |
var chart = new google.visualization.BarChart(document.getElementById('avg_load')); |
29 |
chart.draw(data, options); |
30 |
}
|
31 |
</script> |
32 |
EOD; |
33 |
}
|
json_encode to turn it into a JavaScript notation array that match the data format of bar chart. 1 |
[ ["Duration","% Load"], ["1 min",20], ["5 mins",11], ["15 mins",3]] |



Ethernet Interface
The next widget we will tackle is for the Ethernet interface. Some server can have multiple ethernet interfaces with different IP Addresses are assigned to them.
Seeing this information is very useful. Let's call this class Ethernet, start with this basic thing for widget/ethernet.php.
1 |
<?php
|
2 |
/**
|
3 |
* Adopt from https://github.com/afaqurk/linux-dash/blob/master/sh/ip.php
|
4 |
*
|
5 |
*/
|
6 |
namespace AX\StatBoard\Widget; |
7 |
|
8 |
class Ethernet implements Provider { |
9 |
function __construct() { |
10 |
}
|
11 |
|
12 |
public function get_title() { |
13 |
return "Ethernet"; |
14 |
}
|
15 |
|
16 |
function get_metric() { |
17 |
$ethernet = array(); |
18 |
$output = shell_exec("ip -oneline link show | awk '{print $2}' | sed 's/://'"); |
19 |
if (!$output) { // It didn't work with "ip" , so we do it with ifconfig |
20 |
$output = shell_exec( |
21 |
'ifconfig | /bin/grep -B1 "inet addr" | /usr/bin/awk \'' . |
22 |
'{ if ( $1 == "inet" ) { print $2 }' . |
23 |
'else if ( $2 == "Link" ) { printf "%s:",$1 } }\' | /usr/bin/awk' . |
24 |
' -F: \'{ print $1","$3 }\''
|
25 |
);
|
26 |
$output = trim($output, " \n"); |
27 |
$output = `ifconfig | grep "Link encap" | awk '{ print $1 }'`; |
28 |
$interfaces = explode("\n", $output); |
29 |
$output = `ifconfiga | grep "inet addr" | awk '{ print $2 }' | sed 's/addr://'`; |
30 |
$addreses = explode("\n", $output); |
31 |
$output = trim($output, " \n"); |
32 |
return array_combine($interfaces, $addreses); |
33 |
}
|
34 |
|
35 |
$output = trim($output, " \n"); |
36 |
$interfaces = explode("\n", $output); |
37 |
$addreses = array(); |
38 |
foreach ($interfaces as $interface) { |
39 |
$output = shell_exec("ip -oneline -family inet addr show $interface | awk '{print $4}' | cut -d'/' -f1"); |
40 |
$addreses[] = $output; |
41 |
}
|
42 |
return array_combine($interfaces, $addreses); |
43 |
}
|
44 |
|
45 |
}
|
46 |
get_metric, we will try to get all ethernet interface names, then we get the IP address of each one, and combine the device name and IP address to return. ip instead of ifconfig; therefore, we should run ip first to get ethernet devices.1 |
$output = shell_exec("ip -oneline link show | awk '{print $2}' | sed 's/://'"); |
Using IP Utility
The ip command with -oneline will show output in only one line, where as link and show will list all devices. We use awk to get the second column, with the device names; however it contains the : char. We used sed to replace : with an empty string. 1 |
[vagrant@vagrant-centos64 sbin]$ ip -oneline link show |
2 |
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 |
3 |
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 |
4 |
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 |
5 |
[vagrant@vagrant-centos64 sbin]$ ip -oneline link show | awk '{print $2}' |
6 |
lo: |
7 |
eth0: |
8 |
eth1: |
9 |
[vagrant@vagrant-centos64 sbin]$ ip -oneline link show | awk '{print $2}' | sed 's/://' |
10 |
lo |
11 |
eth0 |
12 |
eth1 |
13 |
[vagrant@vagrant-centos64 sbin]$ |
shell_exec runs successfully, we proceed with the IP utility. The above output is split into an array line by line. 1 |
$output = trim($output, " \n"); |
2 |
$interfaces = explode("\n", $output); |
ip command ip -oneline -family inet addr show device_name to get the IP address that is assigned to the device.1 |
$addreses = array(); |
2 |
foreach ($interfaces as $interface) { |
3 |
$output = shell_exec("ip -oneline -family inet addr show $interface | awk '{print $4}' | cut -d'/' -f1"); |
4 |
$addreses[] = $output; |
5 |
}
|
1 |
[vagrant@vagrant-centos64 sbin]$ ip -oneline -family inet addr show eth1 |
2 |
3: eth1 inet 192.168.1.111/24 brd 192.168.1.255 scope global eth1 |
3 |
[vagrant@vagrant-centos64 sbin]$ ip -oneline -family inet addr show eth1 | awk '{print $4}' |
4 |
192.168.1.111/24 |
5 |
[vagrant@vagrant-centos64 sbin]$ ip -oneline -family inet addr show eth1 | awk '{print $4}' | cut -d'/' -f1 |
6 |
192.168.1.111 |
7 |
[vagrant@vagrant-centos64 sbin]$ |
1 |
return array_combine($interfaces, $addreses); |
Servers Using ifconfig
In the case of ifconfig, theshell_exec of ip will return false. In that case, we run ifconfig instead. 1 |
if (!$output) { // It didn't work with "ip" , so we do it with ifconfig |
2 |
$output = `ifconfig | grep "Link encap" | awk '{ print $1 }'`; |
3 |
$interfaces = explode("\n", $output); |
4 |
$output = `ifconfig | grep "inet addr" | awk '{ print $2 }' | sed 's/addr://'`; |
5 |
$addreses = explode("\n", $output); |
6 |
$output = trim($output, " \n"); |
7 |
return array_combine($interfaces, $addreses); |
8 |
}
|
ifconfig shows the IP address directly in the output, therefore we can just get them instead of running a loop over devices array and get IP address of each one.1 |
[vagrant@vagrant-centos64 sbin]$ ifconfig |
2 |
eth0 Link encap:Ethernet HWaddr 08:00:27:08:C2:E4 |
3 |
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 |
4 |
inet6 addr: fe80::a00:27ff:fe08:c2e4/64 Scope:Link |
5 |
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 |
6 |
RX packets:4230 errors:0 dropped:0 overruns:0 frame:0 |
7 |
TX packets:2575 errors:0 dropped:0 overruns:0 carrier:0 |
8 |
collisions:0 txqueuelen:1000 |
9 |
RX bytes:444488 (434.0 KiB) TX bytes:2288676 (2.1 MiB) |
10 |
|
11 |
eth1 Link encap:Ethernet HWaddr 08:00:27:EB:11:E4 |
12 |
inet addr:192.168.1.111 Bcast:192.168.1.255 Mask:255.255.255.0 |
13 |
inet6 addr: fe80::a00:27ff:feeb:11e4/64 Scope:Link |
14 |
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 |
15 |
RX packets:4470 errors:0 dropped:0 overruns:0 frame:0 |
16 |
TX packets:2449 errors:0 dropped:0 overruns:0 carrier:0 |
17 |
collisions:0 txqueuelen:1000 |
18 |
RX bytes:1689803 (1.6 MiB) TX bytes:271675 (265.3 KiB) |
19 |
|
20 |
lo Link encap:Local Loopback |
21 |
inet addr:127.0.0.1 Mask:255.0.0.0 |
22 |
inet6 addr: ::1/128 Scope:Host |
23 |
UP LOOPBACK RUNNING MTU:16436 Metric:1 |
24 |
RX packets:264 errors:0 dropped:0 overruns:0 frame:0 |
25 |
TX packets:264 errors:0 dropped:0 overruns:0 carrier:0 |
26 |
collisions:0 txqueuelen:0 |
27 |
RX bytes:15840 (15.4 KiB) TX bytes:15840 (15.4 KiB) |
28 |
|
29 |
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "Link encap" |
30 |
eth0 Link encap:Ethernet HWaddr 08:00:27:08:C2:E4 |
31 |
eth1 Link encap:Ethernet HWaddr 08:00:27:EB:11:E4 |
32 |
lo Link encap:Local Loopback |
33 |
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "Link encap" | awk '{ print $1 }' |
34 |
eth0 |
35 |
eth1 |
36 |
lo |
37 |
|
38 |
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "inet addr" |
39 |
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 |
40 |
inet addr:192.168.1.111 Bcast:192.168.1.255 Mask:255.255.255.0 |
41 |
inet addr:127.0.0.1 Mask:255.0.0.0 |
42 |
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "inet addr" | awk '{ print $2 }' |
43 |
addr:10.0.2.15 |
44 |
addr:192.168.1.111 |
45 |
addr:127.0.0.1 |
46 |
[vagrant@vagrant-centos64 sbin]$ ifconfig | grep "inet addr" | awk '{ print $2 }' | sed 's/addr://' |
47 |
10.0.2.15 |
48 |
192.168.1.111 |
49 |
127.0.0.1 |
50 |
[vagrant@vagrant-centos64 sbin]$ |
get_content is easy because we just show a simple table here.1 |
public function get_content() { |
2 |
$interfaces = $this->get_metric(); |
3 |
$html = '<table class="wp-list-table widefat"><thead><tr> |
4 |
<th>Interface</th>
|
5 |
<th>IP</th>
|
6 |
</tr></thead><tbody>'; |
7 |
foreach ( $interfaces as $interface => $ip ) { |
8 |
$html .= "<tr> |
9 |
<td>{$interface}</td> |
10 |
<td>{$ip}</td> |
11 |
</tr>"; |
12 |
}
|
13 |
$html .= '</tbody></table>'; |
14 |
echo $html; |
15 |
}
|



Network Traffic
Network traffic, or Network IO, shows the status of transferring packages over the network of computers. The information is pulled off from netstat.
1 |
[vagrant@vagrant-centos64 sbin]$ netstat -i |
2 |
Kernel Interface table |
3 |
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg |
4 |
eth0 1500 0 4828 0 0 0 2933 0 0 0 BMRU |
5 |
eth1 1500 0 4806 0 0 0 2679 0 0 0 BMRU |
6 |
lo 16436 0 276 0 0 0 276 0 0 0 LRU |
Networkio in file widget/networkio.php1 |
<?php
|
2 |
/**
|
3 |
* Adopt from https://github.com/afaqurk/linux-dash/blob/master/sh/ip.php
|
4 |
*
|
5 |
*/
|
6 |
namespace AX\StatBoard\Widget; |
7 |
|
8 |
class Networkio implements Provider { |
9 |
function __construct() { |
10 |
}
|
11 |
|
12 |
public function get_title() { |
13 |
return "Network IO"; |
14 |
}
|
15 |
function get_metric() { $ethernet = array(); $output = `netstat -i | grep -v -E '(Iface|Interface)' | awk '{print $1","$4","$8}'`; $lines = explode("\n", $output); foreach ($lines as $line) { $line = explode(',', $line); if (count($line)<3) { continue; } $ethernet[] = array($line[0], intval($line[1]), intval($line[2])); } return $ethernet; }} |
16 |
1 |
[vagrant@vagrant-centos64 sbin]$ netstat -i |
2 |
Kernel Interface table |
3 |
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg |
4 |
eth0 1500 0 5727 0 0 0 3400 0 0 0 BMRU |
5 |
eth1 1500 0 5004 0 0 0 2797 0 0 0 BMRU |
6 |
lo 16436 0 292 0 0 0 292 0 0 0 LRU |
7 |
|
8 |
[vagrant@vagrant-centos64 sbin]$ netstat -i | grep -v -E '(Iface|Interface)' |
9 |
eth0 1500 0 5736 0 0 0 3405 0 0 0 BMRU |
10 |
eth1 1500 0 5004 0 0 0 2797 0 0 0 BMRU |
11 |
lo 16436 0 292 0 0 0 292 0 0 0 LRU |
12 |
|
13 |
[vagrant@vagrant-centos64 sbin]$ netstat -i | grep -v -E '(Iface|Interface)' | awk '{print $1","$4","$8}' |
14 |
eth0,5760,3420 |
15 |
eth1,5004,2797 |
16 |
lo,292,292 |
17 |
[vagrant@vagrant-centos64 sbin]$ |
netstat return many things, we used grep to eliminate the line contains words Iface or Kernel (the first two lines), we only care about the last three lines with our ethernet devices and their package transferring. awk is used to print out the data of first, forth and eighth column meaning the name of interface, RX-OK and TX-OK. In our
get_metric, we split the result line-by-line into an array. Because each of line contains data separate by the comma, so they are split again into an array. get_content.1 |
public function get_content() { |
2 |
$interfaces = $this->get_metric(); |
3 |
$data = array_merge(array(array('Interface', 'Receive(package)', 'Transfer(package)')), $interfaces); |
4 |
$data = json_encode($data); |
5 |
echo <<<EOD |
6 |
<div id="nio_chart"></div> |
7 |
<script type="text/javascript"> |
8 |
google.setOnLoadCallback(function () { |
9 |
var data = google.visualization.arrayToDataTable({$data}); |
10 |
|
11 |
var options = { |
12 |
};
|
13 |
|
14 |
var chart = new google.visualization.ColumnChart(document.getElementById('nio_chart')); |
15 |
chart.draw(data, options); |
16 |
})
|
17 |
</script> |
18 |
EOD; |
19 |
|
20 |
}
|



Input/Output Statistics
Now, we tackle io stat. IO means input/output. We will find out how many read/write operations are performed per second. We also tackle io_wait. IO Wait is the time that CPU is idle waiting the result that it read from hard drive.
For example you're reading the MySQL data, and CPU will be idle to wait for the result. The io wait is calculated on 1 second or 1000 milliseconds. If your code takes 100 milliseconds to read data from hard drive, then the io_wait is 100/1000 = 10%. The less IO wait, the better performance for system.
In order to proceed with this, please make sure you have sysstat package on system.
- For Arch Linux, install
with pacman -S sysstat
- For Debian/Ubuntu, you can get them with
apt-get install sysstat
- For Fedora/Centos, you can use
yum install sysstat - For other distributions,:please use your distribution package manager to install it
Once you installed, let's evaluate some commands we will use. First thing's first:
1 |
[vagrant@vagrant-centos64 sbin]$ iostat |
2 |
Linux 2.6.32-358.23.2.el6.x86_64 (vagrant-centos64.vagrantup.com) 04/27/2014 _x86_64_ (1 CPU) |
3 |
|
4 |
avg-cpu: %user %nice %system %iowait %steal %idle |
5 |
0.05 0.00 0.25 0.04 0.00 99.66 |
6 |
|
7 |
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn |
8 |
sda 0.18 7.62 1.04 157826 21584 |
iowait value in fourth column. The data from seventh line going forward contains read/write block per second of the hard drive. 1 |
[vagrant@vagrant-centos64 ~]$ cat /sys/block/sda/queue/physical_block_size |
2 |
512 |
3 |
[vagrant@vagrant-centos64 ~]$ |
With the basic above knowledge, let's create our class
Iostat in widget/iostat.php. 1 |
<?php |
2 |
|
3 |
namespace AX\StatBoard\Widget; |
4 |
|
5 |
class Iostat implements Provider {
|
6 |
function __construct() {
|
7 |
} |
8 |
|
9 |
public function get_title() {
|
10 |
return "Disk IO"; |
11 |
} |
12 |
|
13 |
|
14 |
/** |
15 |
* Make sure we install package sysstat |
16 |
* yum install sysstat |
17 |
* or apt-get install sysstat |
18 |
* |
19 |
* Return IO Stat information. CPU waiting time, disk read/write |
20 |
* |
21 |
*/ |
22 |
function get_metric() {
|
23 |
$metric = array(); |
24 |
|
25 |
$output = `iostat`; |
26 |
$number_of_core = intval(`/bin/grep -c processor /proc/cpuinfo`); |
27 |
|
28 |
$lines = explode("\n", $output);
|
29 |
//We should have more than 4 lines |
30 |
if (!is_array($lines) || sizeof($lines)<4) {
|
31 |
return false; |
32 |
} |
33 |
$avg_cpu = preg_split("/\s+/", $lines[3]);
|
34 |
$metric['cpu'] = array( |
35 |
'user' => floatval($avg_cpu[0]) * $number_of_core, |
36 |
'system' => floatval($avg_cpu[2]) * $number_of_core, |
37 |
'io_wait' => floatval($avg_cpu[3]) * $number_of_core, |
38 |
'other' => 100 - ($avg_cpu[0] + $avg_cpu[2] + $avg_cpu[3]) |
39 |
); |
40 |
|
41 |
if (sizeof($lines) >=7) {
|
42 |
for ($i=6,$l = sizeof($lines);$i<$l; $i++) {
|
43 |
$line = preg_split("/\s+/", $lines[$i]);
|
44 |
if (!is_array($line) || sizeof($line)<5) {
|
45 |
continue; |
46 |
} |
47 |
// Calculate block size |
48 |
$block_size = shell_exec("cat /sys/block/{$lines[1]}/queue/physical_block_size");
|
49 |
|
50 |
$metric['disk'][$line[0]] = array( |
51 |
'read' => floatval($line[2]) * $block_size / 1024, |
52 |
'write' => floatval($line[3]) * $block_size / 1024, |
53 |
); |
54 |
|
55 |
} |
56 |
} |
57 |
|
58 |
return $metric; |
59 |
} |
60 |
|
61 |
} |
62 |
iostat, turn it into an array with every line is an element. get_content and render our chart.1 |
public function get_content() { |
2 |
$metric = $this->get_metric(); |
3 |
$disk_io = array( |
4 |
array('Disk', 'Read(MB)', 'Write(MB)'), |
5 |
);
|
6 |
foreach ($metric['disk'] as $disk=>$stat) { |
7 |
$disk_io[] = array($disk, $stat['read'], $stat['write']); |
8 |
}
|
9 |
$disk_io = json_encode($disk_io); |
10 |
|
11 |
$cpu_io = json_encode(array( |
12 |
array('CPU Time', 'Percent'), |
13 |
array('IO Wait', $metric['cpu']['io_wait']), |
14 |
));
|
15 |
|
16 |
echo <<<EOD |
17 |
<div id="widget_disk_io"></div> |
18 |
<div id="widget_cpu_io_wait"></div> |
19 |
<script type="text/javascript"> |
20 |
google.load('visualization', '1', {packages:['gauge']}); |
21 |
google.setOnLoadCallback(function () { |
22 |
var data = google.visualization.arrayToDataTable({$cpu_io}); |
23 |
var goptions = { |
24 |
redFrom: 80, redTo: 100, |
25 |
yellowFrom:50, yellowTo: 80, |
26 |
minorTicks: 5 |
27 |
};
|
28 |
var chart = new google.visualization.Gauge(document.getElementById('widget_cpu_io_wait')); |
29 |
chart.draw(data, goptions); |
30 |
|
31 |
var data2 = google.visualization.arrayToDataTable({$disk_io}); |
32 |
var chart2 = new google.visualization.ColumnChart(document.getElementById('widget_disk_io')); |
33 |
chart2.draw(data2, {}); |
34 |
})
|
35 |
</script> |
36 |
EOD; |
37 |
|
38 |
}
|
1 |
var goptions = { |
2 |
redFrom: 80, redTo: 100, |
3 |
yellowFrom:50, yellowTo: 80, |
4 |
minorTicks: 5 |
5 |
};
|



What's Next?
At this point, we've finished all of the widgets for our plugin. You can download the plugin in this tutorial to avoid typing all of the tutorial code. Installing plugin, then refreshing your dashboard and use the Screen Option to decide which widget you want to show.



Our plugin works great at this point; however, we can go further to implement Users and Roles check because the data can be sensitive.
We also should use cronjob to pull these metric. Stay tuned for the next part.
As always, be sure to leave a comment to let us know what you think about the plugin, and if you customize it (as well as how!).



