Back to index

d-push  2.0
z-push-top.php
Go to the documentation of this file.
00001 #!/usr/bin/php
00002 <?php
00003 /***********************************************
00004 * File      :   z-push-top.php
00005 * Project   :   Z-Push
00006 * Descr     :   Shows realtime information about
00007 *               connected devices and active
00008 *               connections in a top-style format.
00009 *
00010 * Created   :   07.09.2011
00011 *
00012 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00013 *
00014 * This program is free software: you can redistribute it and/or modify
00015 * it under the terms of the GNU Affero General Public License, version 3,
00016 * as published by the Free Software Foundation with the following additional
00017 * term according to sec. 7:
00018 *
00019 * According to sec. 7 of the GNU Affero General Public License, version 3,
00020 * the terms of the AGPL are supplemented with the following terms:
00021 *
00022 * "Zarafa" is a registered trademark of Zarafa B.V.
00023 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00024 * The licensing of the Program under the AGPL does not imply a trademark license.
00025 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00026 *
00027 * However, if you propagate an unmodified version of the Program you are
00028 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00029 * Furthermore you may use our trademarks where it is necessary to indicate
00030 * the intended purpose of a product or service provided you use it in accordance
00031 * with honest practices in industrial or commercial matters.
00032 * If you want to propagate modified versions of the Program under the name "Z-Push",
00033 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00034 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00035 *
00036 * This program is distributed in the hope that it will be useful,
00037 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00038 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00039 * GNU Affero General Public License for more details.
00040 *
00041 * You should have received a copy of the GNU Affero General Public License
00042 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00043 *
00044 * Consult LICENSE file for details
00045 ************************************************/
00046 
00047 include('lib/exceptions/exceptions.php');
00048 include('lib/core/zpushdefs.php');
00049 include('lib/core/zpush.php');
00050 include('lib/core/zlog.php');
00051 include('lib/core/interprocessdata.php');
00052 include('lib/core/topcollector.php');
00053 include('lib/utils/utils.php');
00054 include('lib/request/request.php');
00055 include('lib/request/requestprocessor.php');
00056 include('config.php');
00057 include('version.php');
00058 
00059 /************************************************
00060  * MAIN
00061  */
00062     declare(ticks = 1);
00063     define('BASE_PATH_CLI',  dirname(__FILE__) ."/");
00064 
00065     try {
00066         ZPush::CheckConfig();
00067         if (!function_exists("pcntl_signal"))
00068             throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system.");
00069 
00070         $zpt = new ZPushTop();
00071         if ($zpt->IsAvailable()) {
00072             pcntl_signal(SIGINT, array($zpt, "SignalHandler"));
00073             $zpt->run();
00074             $zpt->scrClear();
00075         }
00076         else
00077             echo "Z-Push shared memory interprocess communication is not available.\n";
00078     }
00079     catch (ZPushException $zpe) {
00080         die(get_class($zpe) . ": ". $zpe->getMessage() . "\n");
00081     }
00082 
00083     echo "terminated\n";
00084 
00085 
00086 /************************************************
00087  * Z-Push-Top
00088  */
00089 class ZPushTop {
00090     private $topCollector;
00091     private $starttime;
00092     private $action;
00093     private $filter;
00094     private $status;
00095     private $statusexpire;
00096     private $wide;
00097     private $wasEnabled;
00098     private $terminate;
00099     private $scrSize;
00100     private $pingInterval;
00101 
00102     private $linesUpdate = array();
00103     private $linesActive = array();
00104     private $linesUnknown = array();
00105     private $linesTerm = array();
00106     private $pushConn = 0;
00107     private $activeConn = array();
00108     private $activeHosts = array();
00109     private $activeUsers = array();
00110     private $activeDevices = array();
00111 
00117     public function ZPushTop() {
00118         $this->starttime = time();
00119         $this->currenttime = time();
00120         $this->action = "";
00121         $this->filter = false;
00122         $this->status = false;
00123         $this->statusexpire = 0;
00124         $this->helpexpire = 0;
00125         $this->doingTail = false;
00126         $this->wide = false;
00127         $this->terminate = false;
00128         $this->scrSize = array('width' => 80, 'height' => 24);
00129         $this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12;
00130 
00131         // get a TopCollector
00132         $this->topCollector = new TopCollector();
00133     }
00134 
00141     private function initialize() {
00142         // request feedback from active processes
00143         $this->wasEnabled = $this->topCollector->CollectData();
00144 
00145         // remove obsolete data
00146         $this->topCollector->ClearLatest(true);
00147 
00148         // start with default colours
00149         $this->scrDefaultColors();
00150     }
00151 
00159     public function run() {
00160         $this->initialize();
00161 
00162         do {
00163             $this->currenttime = time();
00164 
00165             // see if shared memory is active
00166             if (!$this->IsAvailable())
00167                 $this->terminate = true;
00168 
00169             // active processes should continue sending data
00170             $this->topCollector->CollectData();
00171 
00172             // get and process data from processes
00173             $this->topCollector->ClearLatest();
00174             $topdata = $this->topCollector->ReadLatest();
00175             $this->processData($topdata);
00176 
00177             // clear screen
00178             $this->scrClear();
00179 
00180             // check if screen size changed
00181             $s = $this->scrGetSize();
00182             if ($this->scrSize['width'] != $s['width']) {
00183                 if ($s['width'] > 180)
00184                     $this->wide = true;
00185                 else
00186                     $this->wide = false;
00187             }
00188             $this->scrSize = $s;
00189 
00190             // print overview
00191             $this->scrOverview();
00192 
00193             // wait for user input
00194             $this->readLineProcess();
00195         }
00196         while($this->terminate != true);
00197     }
00198 
00205     public function IsAvailable() {
00206         return $this->topCollector->IsActive();
00207     }
00208 
00217     private function processData($data) {
00218         $this->linesUpdate = array();
00219         $this->linesActive = array();
00220         $this->linesUnknown = array();
00221         $this->linesTerm = array();
00222         $this->pushConn = 0;
00223         $this->activeConn = array();
00224         $this->activeHosts = array();
00225         $this->activeUsers = array();
00226         $this->activeDevices = array();
00227 
00228         if (!is_array($data))
00229             return;
00230 
00231         foreach ($data as $devid=>$users) {
00232             foreach ($users as $user=>$pids) {
00233                 foreach ($pids as $pid=>$line) {
00234                     if (!is_array($line))
00235                         continue;
00236 
00237                     $line['command'] = Utils::GetCommandFromCode($line['command']);
00238 
00239                     if ($line["ended"] == 0) {
00240                         $this->activeDevices[$devid] = 1;
00241                         $this->activeUsers[$user] = 1;
00242                         $this->activeConn[$pid] = 1;
00243                         $this->activeHosts[$line['ip']] = 1;
00244 
00245                         $line["time"] = $this->currenttime - $line['start'];
00246                         if ($line['push'] === true) $this->pushConn += 1;
00247 
00248                         if ($this->filter !== false) {
00249                             $f = $this->filter;
00250                             if (!($line["pid"] == $f || $line["ip"] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?$f.*?/i", $line['user']) ||
00251                                 preg_match("/.*?$f.*?/i", $line['devagent']) || preg_match("/.*?$f.*?/i", $line['devid']) || preg_match("/.*?$f.*?/i", $line['addinfo']) ))
00252                                 continue;
00253                         }
00254 
00255                         $lastUpdate = $this->currenttime - $line["update"];
00256                         if ($this->currenttime - $line["update"] < 2)
00257                             $this->linesUpdate[$line["update"].$line["pid"]] = $line;
00258                         else if (($line['push'] === true  && $lastUpdate > ($this->pingInterval+2)) || ($line['push'] !== true  && $lastUpdate > 4))
00259                             $this->linesUnknown[$line["update"].$line["pid"]] = $line;
00260                         else
00261                             $this->linesActive[$line["update"].$line["pid"]] = $line;
00262                     }
00263                     else {
00264                         if ($this->filter !== false) {
00265                             $f = $this->filter;
00266                             if (!($line['pid'] == $f || $line['ip'] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?$f.*?/i", $line['user']) ||
00267                                 preg_match("/.*?$f.*?/i", $line['devagent']) || preg_match("/.*?$f.*?/i", $line['devid']) || preg_match("/.*?$f.*?/i", $line['addinfo']) ))
00268                                 continue;
00269                         }
00270 
00271                         $line['time'] = $line['ended'] - $line['start'];
00272                         $this->linesTerm[$line['update'].$line['pid']] = $line;
00273                     }
00274                 }
00275             }
00276         }
00277 
00278         // sort by execution time
00279         krsort($this->linesUpdate);
00280         krsort($this->linesActive);
00281         krsort($this->linesUnknown);
00282         krsort($this->linesTerm);
00283     }
00284 
00291     private function scrOverview() {
00292         $linesAvail = $this->scrSize['height'] - 8;
00293         $lc = 1;
00294         $this->scrPrintAt($lc,0, "\033[1mZ-Push top live statistics\033[0m\t\t\t\t\t". @strftime("%d/%m/%Y %T")."\n"); $lc++;
00295 
00296         $this->scrPrintAt($lc,0, sprintf("Open connections: %d\t\t\t\tUsers:\t %d\tZ-Push:   %s ",count($this->activeConn),count($this->activeUsers), $this->getVersion())); $lc++;
00297         $this->scrPrintAt($lc,0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices),phpversion("mapi"))); $lc++;
00298         $this->scrPrintAt($lc,0, sprintf("                                                Hosts:\t %d", $this->pushConn, count($this->activeHosts))); $lc++;
00299         $lc++;
00300 
00301         $this->scrPrintAt($lc,0, "\033[4m". $this->getLine(array('pid'=>'PID', 'ip'=>'IP', 'user'=>'USER', 'command'=>'COMMAND', 'time'=>'TIME', 'devagent'=>'AGENT', 'devid'=>'DEVID', 'addinfo'=>'Additional Information')). str_repeat(" ",20)."\033[0m"); $lc++;
00302 
00303         // print help text if requested
00304         if ($this->helpexpire > $this->currenttime) {
00305             $help = $this->scrHelp();
00306             $linesAvail -= count($help);
00307             $hl = $this->scrSize['height'] - count($help) -1;
00308             foreach ($help as $h) {
00309                 $this->scrPrintAt($hl,0, $h);
00310                 $hl++;
00311             }
00312         }
00313 
00314         $toPrintUpdate = $linesAvail;
00315         $toPrintActive = $linesAvail;
00316         $toPrintUnknown = $linesAvail;
00317 
00318         // TODO this could be optimized to use the max amount of lines available on the screen
00319         if (count($this->linesUpdate) + count($this->linesActive) + count($this->linesUnknown) > $linesAvail) {
00320             $toPrintUpdate = $linesAvail/3;
00321             $toPrintActive = $linesAvail/3;
00322             $toPrintUnknown = $linesAvail/3;
00323         }
00324 
00325         $linesprinted = 0;
00326         foreach ($this->linesUpdate as $time=>$l) {
00327             $this->scrPrintAt($lc,0, "\033[01m" . $this->getLine($l)  ."\033[0m");
00328             $lc++;
00329             $linesprinted++;
00330             if ($linesprinted >= $toPrintUpdate)
00331                 break;
00332         }
00333 
00334         $linesprinted = 0;
00335         foreach ($this->linesActive as $time=>$l) {
00336             $this->scrPrintAt($lc,0, $this->getLine($l));
00337             $lc++;
00338             $linesprinted++;
00339             if ($linesprinted >= $toPrintActive)
00340                 break;
00341         }
00342 
00343         $linesprinted = 0;
00344         foreach ($this->linesUnknown as $time=>$l) {
00345             $color = "0;31m";
00346             if ($l['push'] == false && $time - $l["start"] > 30)
00347                 $color = "1;31m";
00348             $this->scrPrintAt($lc,0, "\033[0". $color . $this->getLine($l)  ."\033[0m");
00349             $lc++;
00350             $linesprinted++;
00351             if ($linesprinted >= $toPrintUnknown)
00352                 break;
00353         }
00354 
00355         foreach ($this->linesTerm as $time=>$l){
00356             $this->scrPrintAt($lc,0, "\033[01;30m" . $this->getLine($l)  ."\033[0m");
00357             $lc++;
00358             if ($lc > $linesAvail+6)
00359                 break;
00360         }
00361         $this->scrPrintAt($lc,0, "\033[K"); $lc++;
00362         $this->scrPrintAt($lc,0, "Colorscheme: \033[01mActive  \033[0mOpen  \033[01;31mUnknown  \033[01;30mTerminated\033[0m");
00363 
00364         // remove old status
00365         if ($this->statusexpire < $this->currenttime)
00366             $this->status = false;
00367 
00368         // show request information and help command
00369         if ($this->starttime + 6 > $this->currenttime) {
00370             $this->status = sprintf("Requesting information (takes up to %dsecs)", $this->pingInterval). str_repeat(".", ($this->currenttime-$this->starttime)) . "  type \033[01;31mh\033[00;31m or \033[01;31mhelp\033[00;31m for usage instructions";
00371             $this->statusexpire = $this->currenttime+1;
00372         }
00373 
00374         if ($this->filter !== false || ($this->status !== false && $this->statusexpire > $this->currenttime)) {
00375             $str = "";
00376             // print filter in green
00377             if ($this->filter !== false)
00378                 $str = "\033[00;32mFilter: \033[01;32m$this->filter\033[0m   ";
00379             // print status in red
00380             if ($this->status !== false)
00381                 $str .= "\033[00;31m$this->status\033[0m";
00382             $this->scrPrintAt(5,0, $str);
00383         }
00384 
00385         $this->scrPrintAt(4,0,"Action: \033[01m".$this->action . "\033[0m");
00386     }
00387 
00394     private function readLineProcess() {
00395         $ans = explode("^^", `bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"`);
00396 
00397         if ($ans[0] < 128) {
00398             if (isset($ans[1]) && bin2hex(trim($ans[1])) == "7f") {
00399                 $this->action = substr($this->action,0,-1);
00400             }
00401 
00402             if (isset($ans[1]) && $ans[1] != "" ){
00403                 $this->action .= trim(preg_replace("/[^A-Za-z0-9:]/","",$ans[1]));
00404             }
00405 
00406             if (bin2hex($ans[0]) == "30" && bin2hex($ans[1]) == "0a")  {
00407                 $cmds = explode(':', $this->action);
00408                 if ($cmds[0] == "quit" || $cmds[0] == "q" || (isset($cmds[1]) && $cmds[0] == "" && $cmds[1] == "q")) {
00409                     $this->topCollector->CollectData(true);
00410                     $this->topCollector->ClearLatest(true);
00411 
00412                     $this->terminate = true;
00413                 }
00414                 else if ($cmds[0] == "clear" ) {
00415                     $this->topCollector->ClearLatest(true);
00416                     $this->topCollector->CollectData(true);
00417                     $this->topCollector->ReInitSharedMem();
00418                 }
00419                 else if ($cmds[0] == "filter" || $cmds[0] == "f") {
00420                     if (!isset($cmds[1]) || $cmds[1] == "") {
00421                         $this->filter = false;
00422                         $this->status = "No filter";
00423                         $this->statusexpire = $this->currenttime+5;
00424                     }
00425                     else {
00426                         $this->filter = $cmds[1];
00427                         $this->status = false;
00428                     }
00429                 }
00430                 else if ($cmds[0] == "reset" || $cmds[0] == "r") {
00431                     $this->filter = false;
00432                     $this->wide = false;
00433                     $this->helpexpire = 0;
00434                     $this->status = "resetted";
00435                     $this->statusexpire = $this->currenttime+2;
00436                 }
00437                 else if ($cmds[0] == "wide" || $cmds[0] == "w") {
00438                     $this->wide = true;
00439                     $this->status = "w i d e  view";
00440                     $this->statusexpire = $this->currenttime+2;
00441                 }
00442                 else if ($cmds[0] == "help" || $cmds[0] == "h") {
00443                     $this->helpexpire = $this->currenttime+20;
00444                 }
00445                 else if (($cmds[0] == "log" || $cmds[0] == "l") && isset($cmds[1]) ) {
00446                     if (!file_exists(LOGFILE)) {
00447                         $this->status = "Logfile can not be found: ". LOGFILE;
00448                     }
00449                     else {
00450                         system('bash -c "fgrep -a '.escapeshellarg($cmds[1]).' '. LOGFILE .' | less +G" > `tty`');
00451                         $this->status = "Returning from log, updating data";
00452                     }
00453                     $this->statusexpire = time()+5; // it might be much "later" now
00454                 }
00455                 else if (($cmds[0] == "tail" || $cmds[0] == "t")) {
00456                     if (!file_exists(LOGFILE)) {
00457                         $this->status = "Logfile can not be found: ". LOGFILE;
00458                     }
00459                     else {
00460                         $this->doingTail = true;
00461                         $this->scrClear();
00462                         $this->scrPrintAt(1,0,$this->scrAsBold("Press CTRL+C to return to Z-Push-Top\n\n"));
00463                         $secondary = "";
00464                         if (isset($cmds[1])) $secondary =  " -n 200 | grep ".escapeshellarg($cmds[1]);
00465                         system('bash -c "tail -f '. LOGFILE . $secondary . '" > `tty`');
00466                         $this->doingTail = false;
00467                         $this->status = "Returning from tail, updating data";
00468                     }
00469                     $this->statusexpire = time()+5; // it might be much "later" now
00470                 }
00471 
00472                 else if ($cmds[0] != "") {
00473                     $this->status = sprintf("Command '%s' unknown", $cmds[0]);
00474                     $this->statusexpire = $this->currenttime+8;
00475                 }
00476                 $this->action = "";
00477             }
00478         }
00479     }
00480 
00489     public function SignalHandler($signo) {
00490         // don't terminate if the signal was sent by terminating tail
00491         if (!$this->doingTail) {
00492             $this->topCollector->CollectData(true);
00493             $this->topCollector->ClearLatest(true);
00494             $this->terminate = true;
00495         }
00496     }
00497 
00504     private function scrHelp() {
00505         $h = array();
00506         $secs = $this->helpexpire - $this->currenttime;
00507         $h[] = "Actions supported by Z-Push-Top (help page still displayed for ".$secs."secs)";
00508         $h[] = "  ".$this->scrAsBold("Action")."\t\t".$this->scrAsBold("Comment");
00509         $h[] = "  ".$this->scrAsBold("h")." or ".$this->scrAsBold("help")."\t\tDisplays this information.";
00510         $h[] = "  ".$this->scrAsBold("q").", ".$this->scrAsBold("quit")." or ".$this->scrAsBold(":q")."\t\tExits Z-Push-Top.";
00511         $h[] = "  ".$this->scrAsBold("w")." or ".$this->scrAsBold("wide")."\t\tTries not to truncate data. Automatically done if more than 180 columns available.";
00512         $h[] = "  ".$this->scrAsBold("f:VAL")." or ".$this->scrAsBold("filter:VAL")."\tOnly display connections which contain VAL. This value is case-insensitive.";
00513         $h[] = "  ".$this->scrAsBold("f:")." or ".$this->scrAsBold("filter:")."\t\tWithout a search word: resets the filter.";
00514         $h[] = "  ".$this->scrAsBold("l:STR")." or ".$this->scrAsBold("log:STR")."\tIssues 'less +G' on the logfile, after grepping on the optional STR.";
00515         $h[] = "  ".$this->scrAsBold("t:STR")." or ".$this->scrAsBold("tail:STR")."\tIssues 'tail -f' on the logfile, grepping for optional STR.";
00516         $h[] = "  ".$this->scrAsBold("r")." or ".$this->scrAsBold("reset")."\t\tResets 'wide' or 'filter'.";
00517         return $h;
00518     }
00519 
00528     private function scrAsBold($text) {
00529         return "\033[01m" . $text  ."\033[0m";
00530     }
00531 
00540     private function getLine($l) {
00541         if ($this->wide === true)
00542             return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'],6), $this->ptStr($l['ip'],16), $this->ptStr($l['user'],16), $this->ptStr($l['command'],16), $this->ptStr($this->sec2min($l['time']),8), $this->ptStr($l['devagent'],28), $this->ptStr($l['devid'],40, true), $l['addinfo']);
00543         else
00544             return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'],6), $this->ptStr($l['ip'],10), $this->ptStr($l['user'],8), $this->ptStr($l['command'],11), $this->ptStr($this->sec2min($l['time']),6), $this->ptStr($l['devagent'],20), $this->ptStr($l['devid'],18, true), $l['addinfo']);
00545     }
00546 
00558     private function ptStr($str, $size, $cutmiddle = false) {
00559         if (strlen($str) < $size)
00560             return str_pad($str, $size);
00561         else if ($cutmiddle == true) {
00562             $cut = ($size-2)/2;
00563             return $this->ptStr(substr($str,0,$cut) ."..". substr($str,(-1)*($cut-1)), $size);
00564         }
00565         else {
00566             return substr($str,0,$size-3).".. ";
00567         }
00568     }
00569 
00576     private function scrGetSize() {
00577         preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", strtolower(exec('stty -a | fgrep columns')), $output);
00578         if(sizeof($output) == 3)
00579             return array('width' => $output[2][0], 'height' => $output[1][0]);
00580 
00581         return array('width' => 80, 'height' => 24);
00582     }
00583 
00590     private function getVersion() {
00591         if (ZPUSH_VERSION == "SVN checkout" && file_exists(REAL_BASE_PATH.".svn/entries")) {
00592             $svn = file(REAL_BASE_PATH.".svn/entries");
00593             return "SVN " . substr(trim($svn[4]),stripos($svn[4],"z-push")+7) ." r".trim($svn[3]);
00594         }
00595         return ZPUSH_VERSION;
00596     }
00597 
00606     private function sec2min($s) {
00607         if (!is_int($s))
00608             return $s;
00609         return sprintf("%02.2d:%02.2d", floor($s/60), $s%60);
00610     }
00611 
00618     private function scrDefaultColors() {
00619         echo "\033[0m";
00620     }
00621 
00630     public function scrClear() {
00631         echo "\033[2J";
00632     }
00633 
00644     private function scrPrintAt($row, $col, $text="") {
00645         echo "\033[".$row.";".$col."H".$text;
00646     }
00647 
00648 }
00649 
00650 ?>