addFaH($cpu, $log) where $cpu and $log are the path * * to unitinfo.txt and FAHlog.txt respectively * * * * Then, you need to parse everything, to do so, use $obj->parse($bool) * * where bool is an optional boolean parameter. If it's set to TRUE, parse* * will then ouput the result of the parsing so that you won't need to use* * $obj->output(). Default is TRUE. * * * * Then, to get an ouput, you need to call $obj->ouput($mode) where $mode * * is (an optional param) one of the following string: * * ¤ txt: simple output, 1 F@H / Line (usefull for IRC clients) * * ¤ mirc: same as the output produced by txt but with mIRC color tags. * * I use that mode because I open a socket in mirc and say the result * * of the script. This will probably be useless to you... * * ¤ html: HTML output. See hereafter to set the HTML options * * ¤ img: outputs an iage. See hereafter to set the image options * * * * When you output HTML, you may use $obj->setHTMLstyle($style1, $style2) * * where $style1 and $style2 are CSS styles. $style1 is for the sub-header* * of each line and $style2 for the content. Try it to see how it works. * * * * When you output an image, you can set 2 different things : whether you * * want the image to be saved in a file or streamed to the client and what* * type of image you want to get. * * To do so, use: $obj->setImgOptions($out, $type, $file) where $out is a * * string: 'stream' or 'file', $type is a string: 'png' or 'jpeg'. $file * * is optional and relevant only if $out == 'file'. The default filename * * is fah.EXT where EXT is the relevant extension depending on $type. Note* * that $file must be the filename WITHOUT the extension in it. If you set* * $type = 'png' and you want to get mypic.png you need $file = 'mypic' * * There are different colors in the image which you can customize. To do * * so, you may call $obj->setImgColors($bg, $title, $color1, $color2). All* * the params must be strings of this format: 'R,G,B' where RGB are digits* * between 0 and 255 included (it's the RGB code of the color you want) * * * * You might want to set a label for each F@H (generally 1 F@H = 1 CPU) * * to do so, use $obj->setCPUlabel($cpuID, $label) where $cpuID = n - 1, * * given that this label is for the n-th CPU you added (in the order you * * added them) and $label is any string you want for example 'P4C 3Ghz HT'* * * * Copyright (C) 2004 TsunaQuake * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 1, or (at your option) * * any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * You can also provide yourself with an online copy of the text: * * * * * * * *************************************************************************/ class FaHnatX { var $mode; // string. current output mode var $cpu; // array. each entry is an array with 1 line of unitinfo.txt / entry var $log; // array. each entry is the path to a FAHlog.txt var $out; // array. each entry is a line of the output var $WUinf; // array. each entry is an information on the current WU what has been parsed var $parsed; // boolean. TRUE if the log(s) have been parsed var $options; // array. options for the output. default values are given below function FaHnatX($cpu=NULL, $log=NULL, $mode='txt') { $this->out = $this->cpu = $this->WUinf = array(); $this->parsed = FALSE; if(!@is_null($cpu)) { if(is_array($cpu)) { // we have been given an array (multiple CPUs) if(count($cpu) == count($log)) { // make sure we have the same number of entries for($i = 0; $i < count($cpu); $i++) $this->addFaH($cpu[$i], $log[$i]); } } else { // we have been given a string (single CPU) if(!@is_null($log) && !empty($log)) $this->addFaH($cpu, $log); } } if($this->_isValidMode($mode)) $this->mode = $mode; else $this->mode = 'txt'; // default mode // default options $this->options = array('html' => array( 'title' => 'font-size: 24px; color: black; font-weight:bold;', 'style1' => 'color:#456789; font-weight:bold; font-family: Helvetica', 'style2' => 'color:black; font-family:Fixedsys, Courier;', 'label' => 'color:blue; font-family: Helvetica' ), 'img' => array( 'out' => 'stream', 'type' => 'png', 'file' => 'fah', 'bg' => '255,255,255', 'title' => '0,0,255', 'color1' => '46,69,91', 'color2' => '0,0,0' ), 'mirc' => array( 'color1' => '12', 'color2' => '14' ), 'labels' => array() // labels for each CPU ); } function parse($output=TRUE) { if($this->parsed && (boolean) $output) { // the logs have been parsed already $this->output(); // so we don't to it all over again } // working CPU per CPU... for($j = 0; $j < count($this->cpu); $j++) { $this->out[$j] = array(); // the name of the WU is read from the line 3 of unitinfo.txt $this->out[$j][0] = trim($this->cpu[$j][2]); $this->WUinf[$j]['name'] = substr($this->out[$j][0], 6); // the partial time of download of the WU is read from the line 4 - this is to split the log correctly $this->WUinf[$j]['dltime'] = substr($b = substr($this->cpu[$j][3], 15), 0, strpos($b, ':')); $WUlog = ''; // find the path of the log so that ... $WUlogPath = substr($this->log[$j], 0, strrpos($this->log[$j], '/') + 1); // ... we can check whether FAHlog-prev.txt exists at the same place if(file_exists($WUlogPath.'FAHlog-prev.txt')) { $WUlog = file_get_contents($WUlogPath.'FAHlog-prev.txt'); } // simply puts the log in a string variable $WUlog .= file_get_contents($this->log[$j]); // we split the log to keep only the part of it that corresponds to the WU we're working on $WUlog = substr($WUlog, strpos($WUlog, '['.$this->WUinf[$j]['dltime'])); // finds what kind of core we're using $this->WUinf[$j]['core'] = (strpos($WUlog, 'TINKER') !== FALSE ? 'TINKER' : 'Gromacs'); // this finds the line where the version of the core must be, that is, on the next line where the name of the core was found $b = strpos($WUlog, "\n", strpos($WUlog, $this->WUinf[$j]['core'])); $e = strpos($WUlog, "\n", ($b + 1)) - $b; $version = substr($WUlog, $b, $e); // find the version number if(preg_match('`[[0-9]{2}:[0-9]{2}:[0-9]{2}\] Version ([0-9.]+).*`', $version, $res)) $this->WUinf[$j]['version'] = $res[1]; else $this->WUinf[$j]['version'] = '?'; // in case we don't find it (should never happen unless the log format changed or the log file is corrupted) $WUlog = explode("\n", $WUlog); // we now make an array with our log // with TINKER cores we can simply work with frames so we don't need to bother with steps. This stepsPerFrame is 1 if($this->WUinf[$j]['core'] == 'TINKER') $this->WUinf[$j]['stepsPerFrame'] = 1; // these are patterns we'll need to parse the log with preg_match hereafter $framesPattern['completed']['TINKER'] = '`[[0-9]{2}:[0-9]{2}:[0-9]{2}\] - Frames Completed: ([0-9]+), Remaining: ([0-9]+)`'; $framesPattern['completed']['Gromacs'] = '`[[0-9]{2}:[0-9]{2}:[0-9]{2}\] Completed [0-9]+ out of ([0-9]+) steps \([0-9]+[%]?\)`'; $framesPattern['framelag']['TINKER'] = 1; $framesPattern['framelag']['Gromacs'] = 2; $framesPattern['framefinish']['TINKER'] = '`\[[0-9]{2}:[0-9]{2}:[0-9]{2}\] Finished a frame \(([0-9]+)\)`'; $framesPattern['framefinish']['Gromacs'] = '`\[[0-9]{2}:[0-9]{2}:[0-9]{2}\] Completed ([0-9]+) out of [0-9]+ steps \([0-9]+[%]?\)`'; $this->WUinf[$j]['firstFrame'] = $this->WUinf[$j]['lastFrame'] = NULL; $firstFrameFound = 0; // ok now we parse the log line per line for($i = 0; $i < count($WUlog); $i++) { // if we don't know how many frames we'll have AND we are on the line with the name of protein we're working on if(!isset($this->WUinf[$j]['frames']) && preg_match('`\[[0-9]{2}:[0-9]{2}:[0-9]{2}\] Protein: '.$this->WUinf[$j]['name'].'`', $WUlog[$i])) { // this while is a workaround to find the total number of frames with Gromacs. With TINKER cores it should stop after $k = 1 // for Gromacs cores it's slightly more complicated since Gromacs adds more lines according to the available optimizations $k = 0; while(!isset($this->WUinf[$j]['frames']) && $k < 50) { if(preg_match($framesPattern['completed'][$this->WUinf[$j]['core']], $WUlog[($i + $framesPattern['framelag'][$this->WUinf[$j]['core']] + $k + 1)], $res)) { if($this->WUinf[$j]['core'] == 'TINKER') $this->WUinf[$j]['frames'] = $res[1] + $res[2]; // at any time the total number of frames is frames done + frames left (with TINKER cores) else $this->WUinf[$j]['frames'] = $res[1]; // Gromacs display the total number of frames on each line when they've completed several steps } ++$k; } } // this is when we match that a frame (TINKER) or some steps (Gromacs) have been finished if(preg_match($framesPattern['framefinish'][$this->WUinf[$j]['core']], $WUlog[$i], $res)) { // if the frames done so far (not overall, but as we are parsing the log) doesn't exist or is less than what we've found if(!isset($this->WUinf[$j]['framesdone']) || $this->WUinf[$j]['framesdone'] < (int) $res[1]) { // this saves the first frame done or the first frame available in the log (so that we can calculate how many frames are done in this log and thus the average time / frame) if(@is_null($this->WUinf[$j]['firstFrame'])) { $this->WUinf[$j]['firstFrame'] = (int) $res[1]; $firstFrameFound = (int) $res[1]; } // if we have a lframedone (LastFrameDone) AND the lframedone is the right number of lines before the current line (depends on cores) // OR we are on a TINKER core at the 1st frame OR we are on a Gromacs core at 0 step if((isset($this->WUinf[$j]['lframedone']) && $this->WUinf[$j]['lframedone'] == ($i - $framesPattern['framelag'][$this->WUinf[$j]['core']])) || (($this->WUinf[$j]['core'] == 'TINKER' && (int) $res[1] === 1) || ($this->WUinf[$j]['core'] == 'Gromacs' && (int) $res[1] === 0))) { // this is a workaround to find out how much time has it taken to calculate the 1st frame if($this->WUinf[$j]['core'] == 'TINKER' && (int) $res[1] === 1) $this->WUinf[$j]['lframedone'] = $i - 1; // when firstFrame = lastFrame it means we've parsed 1 frame already, so we are on the seconde frame if($this->WUinf[$j]['core'] == 'Gromacs' && $firstFrameFound !== FALSE && (int) $res[1] > $firstFrameFound) { $this->WUinf[$j]['stepsPerFrame'] = (int) $res[1] - $this->WUinf[$j]['firstFrame']; // thus we can find the number of steps between 2 frames $firstFrameFound = FALSE; } // when Gromacs has done 0 step we don't need to go any further otherwise it'll mess up the average time / frame if($this->WUinf[$j]['core'] == 'Gromacs' && (int) $res[1] === 0) { $this->WUinf[$j]['lastFrame'] = 0; $this->WUinf[$j]['lframedone'] = $i; continue; } list($hh, $mm, $ss) = explode(':', substr($WUlog[$i], 1, 8)); $time = $hh * 3600 + $mm * 60 + $ss; // timestamp of this line converted in seconds list($hh, $mm, $ss) = explode(':', substr($WUlog[$this->WUinf[$j]['lframedone']], 1, 8)); $time -= $hh * 3600 + $mm * 60 + $ss; // timestamp of the line with last frame done converted in seconds and substracted from the 1st value of $time if($time < 0) // when we get a negative value, it means we've passed midnight. Thus we add 24h in seconds $time += 86400; if(@is_null($this->WUinf[$j]['totaltime'])) { $this->WUinf[$j]['totaltime'] = 0; } $this->WUinf[$j]['totaltime'] += $time; $this->WUinf[$j]['lastFrame'] = (int) $res[1]; } $this->WUinf[$j]['lframedone'] = $i; $this->WUinf[$j]['framesdone'] = (int) $res[1]; } } } // we have just started and no frame have been done yet if(!isset($this->WUinf[$j]['framesdone'])) $this->WUinf[$j]['framesdone'] = 0; // this is the total number of frames we have found in the log $this->WUinf[$j]['totalcnt'] = $this->WUinf[$j]['lastFrame'] - $this->WUinf[$j]['firstFrame'] + ($this->WUinf[$j]['core'] == 'TINKER' ? 1 : 0); $this->WUinf[$j]['framesleft'] = $this->WUinf[$j]['frames'] - $this->WUinf[$j]['framesdone']; // so if we have at least 1 frame done so far we can work out average time and estimated time left if($this->WUinf[$j]['totalcnt'] >= 1 && $this->WUinf[$j]['framesdone'] >= 1 && $this->WUinf[$j]['totaltime'] > 0) { $this->WUinf[$j]['avgtime'] = round($this->WUinf[$j]['totaltime'] / ($this->WUinf[$j]['totalcnt'] / $this->WUinf[$j]['stepsPerFrame']), 2); $this->WUinf[$j]['timeleft'] = floor(($this->WUinf[$j]['framesleft'] / $this->WUinf[$j]['stepsPerFrame']) * $this->WUinf[$j]['avgtime']); $this->WUinf[$j]['timeleft'] = (floor($this->WUinf[$j]['timeleft'] / 86400) > 0 ? floor($this->WUinf[$j]['timeleft'] / 86400).'day'.(floor($this->WUinf[$j]['timeleft'] / 86400) > 1 ? 's' : '') : '').' ' . (floor(($this->WUinf[$j]['timeleft'] % 86400) / 3600) > 0 ? floor(($this->WUinf[$j]['timeleft'] % 86400) / 3600).'hr ' : '') . (floor((($this->WUinf[$j]['timeleft'] % 86400) % 3600) / 60) > 0 ? floor((($this->WUinf[$j]['timeleft'] % 86400) % 3600) / 60).'min ' : '') . (floor((($this->WUinf[$j]['timeleft'] % 86400) % 3600) % 60) > 0 ? floor((($this->WUinf[$j]['timeleft'] % 86400) % 3600) % 60).'sec' : ''); $this->WUinf[$j]['avgtime'] = (floor($this->WUinf[$j]['avgtime'] / 3600) > 0 ? floor($this->WUinf[$j]['avgtime'] / 3600).'hr ' : '') . (floor(($this->WUinf[$j]['avgtime'] % 3600) / 60) > 0 ? floor(($this->WUinf[$j]['avgtime'] % 3600) / 60).'min ' : '') . (floor(($this->WUinf[$j]['avgtime'] % 3600) % 60) > 0 ? floor(($this->WUinf[$j]['avgtime'] % 3600) % 60).'sec' : ''); } else { $this->WUinf[$j]['avgtime'] = $this->WUinf[$j]['timeleft'] = 'unknown'; } $this->WUinf[$j]['donepercent'] = ((int) $this->WUinf[$j]['frames'] > 0 ? round($this->WUinf[$j]['framesdone'] * 100 / $this->WUinf[$j]['frames'], 2) : '?').'%'; $this->out[$j][0] .= ' (Core: '.$this->WUinf[$j]['core'].' v'.$this->WUinf[$j]['version'].')'; $progress = str_repeat('|', floor($this->WUinf[$j]['donepercent'] / 10)); if($this->WUinf[$j]['donepercent'] - floor($this->WUinf[$j]['donepercent'] / 10) * 10 >= 7.5) { $progress .= '>'; } elseif($this->WUinf[$j]['donepercent'] - floor($this->WUinf[$j]['donepercent'] / 10) * 10 >= 5) { $progress .= '-'; } elseif($this->WUinf[$j]['donepercent'] - floor($this->WUinf[$j]['donepercent'] / 10) * 10 >= 2.5) { $progress .= '<'; } $progress .= str_repeat('_', 10 - strlen($progress)); $this->out[$j][1] = 'Progress: '.$this->WUinf[$j]['donepercent'].' ['.$progress.'] '.$this->WUinf[$j]['framesdone'].'/'.$this->WUinf[$j]['frames'].' '.($this->WUinf[$j]['core'] == 'TINKER' ? 'frames' : 'steps'); $this->out[$j][2] = 'Average Time/'.($this->WUinf[$j]['core'] == 'TINKER' ? 'Frame' : $this->WUinf[$j]['stepsPerFrame'].' steps').': '.$this->WUinf[$j]['avgtime']; $this->out[$j][3] = 'Estimated Timeleft: '.trim($this->WUinf[$j]['timeleft']); $this->parsed = TRUE; } if((boolean) $output) $this->output(); } function output($mode=NULL) { if($this->_isValidMode($mode)) $this->mode = $mode; if($this->mode == 'img') return $this->_imgOut(); if($this->mode == 'html') return $this->_htmlOut(); $out = $this->out; for($i = 0; $i < count($this->out); $i++) { if($this->mode == 'mirc') { for($j = 0; $j < count($out[$i]); $j++) { $title = substr($out[$i][$j], 0, strpos($out[$i][$j], ':') + 1); $content = substr($out[$i][$j], strpos($out[$i][$j], ':') + 1); $out[$i][$j] = ''.$this->options['mirc']['color1'].$title.''.$this->options['mirc']['color2'].$content.''; } } if(isset($this->options['html']['label'])) { $out[$i][0] = ($this->mode == 'mirc' ? '' : '').'[CPU '.$i.': '.$this->options['labels'][$i].'] '.($this->mode == 'mirc' ? '' : '').$out[$i][0]; } $out[$i] = implode(' ¦ ', $out[$i]); echo $out[$i]."\n"; } } function setCPUlabel($cpuID, $label) { if(isset($this->cpu[$cpuID])) { $this->options['labels'][$cpuID] = $label; } } function setHTMLstyle($style1, $style2) { $this->options['html']['style1'] = $style1; $this->options['html']['style2'] = $style2; } function setmIRCoptions($color1, $color2) { if(($color1 = (int) $color1) < 16) $this->options['mirc']['color1'] = $color1; if(($color2 = (int) $color2) < 16) $this->options['mirc']['color2'] = $color2; } function setImgOptions($out, $type, $file=NULL) { switch($out) { case 'stream': $this->options['img']['out'] = $out; break; case 'file': $this->options['img']['out'] = $out; $this->options['img']['file'] = (!@is_null($file) ? $file : $this->options['img']['file']); } switch($type) { case 'png': case 'jpeg': $this->options['img']['type'] = $type; } } function setImgColors($bg, $title, $color1, $color2) { if($this->_isValidColor($bg)) $this->options['img']['bg'] = $bg; if($this->_isValidColor($title)) $this->options['img']['title'] = $title; if($this->_isValidColor($color1)) $this->options['img']['color1'] = $color1; if($this->_isValidColor($color2)) $this->options['img']['color2'] = $color2; } function _htmlOut() { $out = $this->out; for($i = 0; $i < count($out); $i++) { echo 'CPU '.$i.'
'."\n"; if(isset($this->options['labels'][$i])) echo ''.$this->options['labels'][$i].'
'."\n"; for($j = 0; $j < count($out[$i]); $j++) { $title = substr($out[$i][$j], 0, strpos($out[$i][$j], ':') + 1); $content = substr($out[$i][$j], strpos($out[$i][$j], ':') + 1); echo ''.$title.''.htmlentities($content).'
'."\n"; } } } function _imgOut() { if(!extension_loaded('gd')) { return FALSE; } $out = $this->out; $maxlength = $nblines = 0; for($i = 0; $i < count($out); $i++) { ++$nblines; for($j = 0; $j < count($out[$i]); $j++) { if(strlen($out[$i][$j]) > $maxlength) $maxlength = strlen($out[$i][$j]); ++$nblines; } } $width = $maxlength * imagefontwidth(2) + 5; $height = ($nblines + 1) * imagefontheight(2) + 5; $im = imagecreate($width, $height); list($red, $green, $blue) = explode(',', $this->options['img']['bg']); $bg = imagecolorallocate($im, $red, $green, $blue); $invbg = imagecolorallocate($im, 255 - $red, 255 - $green, 255 - $blue); list($red, $green, $blue) = explode(',', $this->options['img']['title']); $title = imagecolorallocate($im, $red, $green, $blue); list($red, $green, $blue) = explode(',', $this->options['img']['color1']); $textcolor1 = imagecolorallocate($im, $red, $green, $blue); list($red, $green, $blue) = explode(',', $this->options['img']['color2']); $textcolor2 = imagecolorallocate($im, $red, $green, $blue); $posy = 2; for($i = 0; $i < count($out); $i++) { $label = ''; if(isset($this->options['labels'][$i])) $label = ': '.$this->options['labels'][$i]; imagestring($im, 3, 2, $posy, 'CPU '.$i.$label, $title); $posy += imagefontheight(3); for($j = 0; $j < count($out[$i]); $j++) { $subtitle = substr($out[$i][$j], 0, strpos($out[$i][$j], ':') + 1); $content = substr($out[$i][$j], strpos($out[$i][$j], ':') + 1); imagestring($im, 2, 5, $posy, $subtitle, $textcolor1); imagestring($im, 2, imagefontwidth(2) * strlen($subtitle) + 5, $posy, $content, $textcolor2); $posy += imagefontheight(2); } } $posy += 5; $timelag = substr(date('r'), strrpos(date('r'), ' ') + 1); $date = 'Generated on '.date('d/m/y @ H:i:s').' '.$timelag.' [F@HnatX v'.FAHNATX_VERSION.']'; imagestring($im, 1, $width - strlen($date) * imagefontwidth(1), $posy, $date, $invbg); if($this->options['img']['out'] == 'stream') { header('Content-type: image/'.$this->options['img']['type']); imagepng($im); } else { $ext = 'png'; switch($this->options['img']['type']) { case 'png': $ext = 'png'; break; case 'jpeg': $ext = 'jpg'; } imagepng($im, $this->options['img']['file'].'.'.$ext); } } function addFaH($cpu, $log) { if((is_file($cpu) && is_file($log) && is_readable($cpu) && is_readable($log)) || (strpos($cpu, '://') !== FALSE && strpos($log, '://') !== FALSE)) { $cnt = count($this->cpu); $this->cpu[$cnt] = file($cpu); $this->log[$cnt] = $log; } } function _isValidMode(&$mode) { switch($mode) { case 'txt': case 'mirc': case 'html': case 'img': return TRUE; default: return FALSE; } } function _isValidColor($color) { $color = explode(',', $color); if(count($color) == 3) { for($i = 0; $i < 3; $i++) { if((int) $color[$i] < 0 || (int) $color[$i] > 255) return FALSE; } return TRUE; } return FALSE; } } // first off, we need the paths to your unitinfo.txt and FAHlog.txt $cpu[0] = 'fah-files/cpu1/unitinfo.txt'; $log[0] = 'fah-files/cpu1/FAHlog.txt'; $cpu[1] = 'fah-files/cpu2/unitinfo.txt'; $log[1] = 'fah-files/cpu2/FAHlog.txt'; // then you need to choose the output mode. // Choices are: txt, html, img and mirc // see the documentation within the script,Ê default is txt $mode = 'html'; // this requires the script to be by itself in the PHP page // then create an object. You need only ONE object for all your CPUs $fah = new FaHnatX($cpu, $log, $mode); // you might want to add some short descriptive labels for each CPU (optional) $fah->setCPUlabel(0, 'My Unit 1'); // first CPU $fah->setCPUlabel(1, 'My Unit 2'); // second CPU // parses and outputs the result immediatly $fah->parse(); ?>