From b415296c6272ab0388a85d74c45060039d32abdd Mon Sep 17 00:00:00 2001 From: Deon George Date: Tue, 11 Dec 2012 22:56:33 +1100 Subject: [PATCH] Added google chart --- application/bootstrap.php | 2 +- application/classes/Controller/Node.php | 38 +++ application/classes/Model/NODE.php | 33 ++- application/classes/lnApp/Script.php | 3 + application/views/node/detail.php | 3 + application/views/node/graph_backup.php | 18 ++ modules/gchart/classes/GoogleChart.php | 214 +++++++++++++++++ .../gchart/classes/GoogleChart/ComboChart.php | 155 ++++++++++++ modules/gchart/classes/GoogleChart/Legacy.php | 221 ++++++++++++++++++ 9 files changed, 685 insertions(+), 2 deletions(-) create mode 100644 application/views/node/graph_backup.php create mode 100644 modules/gchart/classes/GoogleChart.php create mode 100644 modules/gchart/classes/GoogleChart/ComboChart.php create mode 100644 modules/gchart/classes/GoogleChart/Legacy.php diff --git a/application/bootstrap.php b/application/bootstrap.php index 7bd97b8..73ea5f6 100644 --- a/application/bootstrap.php +++ b/application/bootstrap.php @@ -114,7 +114,7 @@ Kohana::modules(array( 'cron' => SMDPATH.'cron', // Kohana Cron Module // 'codebench' => SMDPATH.'codebench', // Benchmarking tool 'database' => SMDPATH.'database', // Database access - // 'gchart' => MODPATH.'gchart', // Google Chart Module + 'gchart' => MODPATH.'gchart', // Google Chart Module // 'image' => SMDPATH.'image', // Image manipulation 'khemail' => SMDPATH.'khemail', // Email module for Kohana 3 PHP Framework 'minion' => SMDPATH.'minion', // CLI Tasks diff --git a/application/classes/Controller/Node.php b/application/classes/Controller/Node.php index 389ac1c..5586208 100644 --- a/application/classes/Controller/Node.php +++ b/application/classes/Controller/Node.php @@ -14,5 +14,43 @@ class Controller_Node extends Controller_TemplateDefault_View { protected $index_title = 'TSM Nodes'; protected $detail_title = 'Information on Node'; protected $orm = 'NODE'; + + public function action_ajax_jsonbackup() { + $id = $this->request->param('id'); + $c = (isset($_REQUEST['c'])) ? $_REQUEST['c'] : null; + $s = (isset($_REQUEST['s'])) ? $_REQUEST['s'] : null; + $t = (isset($_REQUEST['t'])) ? $_REQUEST['t'] : null; + + $no = ORM::factory('NODE',$id); + + if (! $id OR ! $no->loaded() OR ! $t OR ! $c) + return ''; + + $result = array(); + + $google = GoogleChart::factory($c) + ->logy(TRUE) + ->title(sprintf('%s Activity',$t).($s ? sprintf(' for Schedule %s',$s) : ' outside of TSM schedules')); + + foreach ($no->act_bybtype($t) as $aso) { + if ($aso->SCHEDULE_NAME != $s) + continue; + + $google->pdata($aso->display('START_TIME'),array( + 'yr'=>array( + 'BYTES'=>$aso->BYTES, + ), + 'yl'=>array( + 'AFFECTED'=>$aso->AFFECTED, + 'EXAMINED'=>$aso->EXAMINED, + 'FAILED'=>$aso->FAILED, + ), + )); + } + + $this->auto_render = FALSE; + $this->response->headers('Content-Type','application/json'); + $this->response->body($google->json()); + } } ?> diff --git a/application/classes/Model/NODE.php b/application/classes/Model/NODE.php index 2532ff1..b8c3803 100644 --- a/application/classes/Model/NODE.php +++ b/application/classes/Model/NODE.php @@ -129,7 +129,7 @@ class Model_NODE extends ORM_TSM { // In the interest of performance, we load all the records and get PHP to process it. // Our ORM caching we reduce the hit on TSM. - foreach (ORM::factory('ACTSUM')->order_by('SCHEDULE_NAME')->find_all() as $o) + foreach (ORM::factory('ACTSUM')->find_all() as $o) if ($o->ENTITY == $this->NODE_NAME) array_push($result,$o); @@ -330,6 +330,7 @@ class Model_NODE extends ORM_TSM { if ($aso->ACTIVITY == $this->datatypemap($type)) array_push($result,$aso); + Sort::MASort($result,'SCHEDULE_NAME,START_TIME'); // @todo Cache time should be configurble Cache::instance($c)->set($k,$result,300); } @@ -338,6 +339,20 @@ class Model_NODE extends ORM_TSM { return $result; } + /** + * Return the Schedules for all activites of type + * @param $type is Bkup/Arch/SpMg + */ + public function act_schedules($type) { + $result = array(); + + foreach ($this->act_bybtype($type) as $ao) + if (! in_array($ao->SCHEDULE_NAME,$result)) + array_push($result,$ao->SCHEDULE_NAME); + + return $result; + } + /** * Return the BACKUP TYPES used by this NODE * ie: Bkup/Arch/SpMg @@ -460,6 +475,22 @@ class Model_NODE extends ORM_TSM { return $this->_filespaces(); } + /** + * Return a Graph of the Schedule Backup Activity + * @param $type is Bkup/Arch/SpMg + * @see [node/json_backup] + */ + public function graph_backup($type='Bkup',$schedule='') { + $chart = 'ComboChart'; + + $google = GoogleChart::factory($chart) + ->title(sprintf('%s Activity',$type)) + ->div(sprintf('%s_%s',$type,$schedule)) + ->dataurl(URL::site(sprintf('node/ajax_jsonbackup/%s?c=%s&s=%s&t=%s',$this->NODE_NAME,$chart,$schedule,$type))); + + return (string)$google; + } + /** * Return the LOGICAL_MB that this NODE has by backup TYPE * @param $type is Bkup/Arch/SpMg diff --git a/application/classes/lnApp/Script.php b/application/classes/lnApp/Script.php index 237acd5..696774b 100644 --- a/application/classes/lnApp/Script.php +++ b/application/classes/lnApp/Script.php @@ -40,6 +40,9 @@ abstract class lnApp_Script extends HTMLRender { case 'file': $foutput .= HTML::script($mediapath->uri(array('file'=>$value['data']))); break; + case 'src': + $foutput .= HTML::script($value['data']); + break; case 'stdin': $soutput .= sprintf("",$value['data']); break; diff --git a/application/views/node/detail.php b/application/views/node/detail.php index 2ad0823..60788f3 100644 --- a/application/views/node/detail.php +++ b/application/views/node/detail.php @@ -3,6 +3,9 @@ set('o',$o); ?> set('o',$o); ?> + + set('o',$o); ?> + set('o',$o); ?>   diff --git a/application/views/node/graph_backup.php b/application/views/node/graph_backup.php new file mode 100644 index 0000000..366f1d5 --- /dev/null +++ b/application/views/node/graph_backup.php @@ -0,0 +1,18 @@ + + + + + + + + + btypes() as $btype) { ?> + act_bybtype($btype)) { ?> + act_schedules($btype) as $schedule) { ?> + + + + + + +
Schedule Results
 
graph_backup($btype,$schedule); ?>
diff --git a/modules/gchart/classes/GoogleChart.php b/modules/gchart/classes/GoogleChart.php new file mode 100644 index 0000000..16808c5 --- /dev/null +++ b/modules/gchart/classes/GoogleChart.php @@ -0,0 +1,214 @@ +_plotdata); + } + public function current() { + return current($this->_plotdata); + } + public function key() { + return key($this->_plotdata); + } + public function next() { + return next($this->_plotdata); + } + public function rewind() { + reset($this->_plotdata); + } + public function valid() { + return key($this->_plotdata) ? TRUE : FALSE; + } + + public function __call($name,$args) { + switch ($name) { + case 'dataurl': $this->_dataurl = array_shift($args); + break; + case 'div': $this->_divname = array_shift($args); + break; + case 'title': $this->_title = array_shift($args); + break; + default: + throw new Kohana_Exception('Unknown method :name',array(':name'=>$name)); + } + + return $this; + } + + /** + * Render the URL that generates the graph + */ + final public function __toString() { + try { + return (string)$this->render(); + } catch (Exception $e) { + echo Debug::vars($e); + } + } + + /** + * Pick the driver that will render the graph + * @param $class The child class to invoke + */ + final public static function factory($class) { + $c = sprintf('%s_%s',get_called_class(),$class); + + if (! class_exists($c)) + throw new Kohana_Exception('Unknown Google Chart Type :class',array(':class'=>$class)); + else + return new $c(); + } + + // Render the chart data in a json format + abstract public function json(); + + // Our child class should define how to render as a string + abstract public function render(); + + /** + * Record one series of data + * @param $axis Axis used and legend title, eg: (yr->"Right Y",yl->"Left Y") + * @param $data Data for Axis in the format ("x label"=>value); + * + * Example: + * $this->data('yl'=>'Base Down Peak',array('11-12'=>1,'11-11'=>2)); + */ + public function sdata(array $axis,array $data) { + // Some sanity checking + if (count($axis) != 1) + throw new Kohana_Exception('We can only take 1 series at time.'); + + // This should only iterate once + foreach ($axis as $key => $l) { + if (! in_array($key,array('yr','yl'))) + throw new Kohaan_Exception('Unknown AXIS :axis',array(':axis'=>$key)); + + $this->_axis[$l] = $key; + $this->_data[$l] = $data[$l]; + + // Upate our plot data + foreach ($data[$l] as $k=>$v) + $this->_plotdata[$k][$l] = $v; + + ksort($this->_plotdata); + + $this->_max[$l] = max($data[$l]); + } + + return $this; + } + + /** + * Record on plot event + * @param $data Should contain an "X" with a "YL" and/or "YR" + */ + public function pdata($x,array $data) { + if (! is_string($x)) + throw new Kohana_Exception('X should be a string'); + + foreach ($data as $key => $values) { + switch ($key) { + case 'yr': + case 'yl': + foreach ($values as $k=>$v) { + if (! in_array($k,$this->_axis)) + $this->_axis[$k] = $key; + + $this->_data[$k][$x] = $v; + $this->_plotdata[$x][$k] = $v; + } + + break; + + default: + throw new Kohana_Exception('Unknown key :key',array(':key'=>$key)); + } + } + } + + /** + * Return the colors that will be used for this series + */ + protected function seriescolors() { + return array_slice($this->series_colors,0,count($this->_axis)); + } + + /** + * Render the chart data in a table format + */ + public function table($vertical=TRUE,$class=array()) { + if (! $this->_data) + return sprintf('
%s
',_('No Data')); + + $output = sprintf('', + isset($class['table']) ? $class['table'] : 'class="google-data-table"'); + + if ($vertical) { + $output .= ''; + $output .= ''; + foreach ($this->_axis as $l => $axis) + $output .= sprintf('',$l); + + $output .= ''; + + foreach ($this as $k => $details) { + $output .= ''; + $output .= sprintf('',$k); + foreach ($this->_axis as $l => $axis) + $output .= sprintf('',$details[$l]); + $output .= ''; + } + + // Horizontal table + } else { + $output .= ''; + $output .= ''; + foreach ($this as $k => $details) + $output .= sprintf('',$k); + $output .= ''; + + foreach ($this->_axis as $l => $axis) { + $output .= ''; + + $output .= sprintf('',$l); + + foreach ($this as $k => $v) + $output .= sprintf('',$v[$l]); + + $output .= ''; + } + } + + $output .= '
 %s
%s%s
 %s
%s%s
'; + + return $output; + } +} +?> diff --git a/modules/gchart/classes/GoogleChart/ComboChart.php b/modules/gchart/classes/GoogleChart/ComboChart.php new file mode 100644 index 0000000..fdef02f --- /dev/null +++ b/modules/gchart/classes/GoogleChart/ComboChart.php @@ -0,0 +1,155 @@ +_logy = $value; + + return $this; + } + + public function stacked($value) { + $this->_stacked = $value; + + return $this; + } + + /** + * Set the type of the chart + * @param $type Chart type as per $this->cht + */ + public function ltitle($side,$title) { + if (! in_array($side,array('yl','yr','x'))) + throw new Kohana_Exception('Unknown side :side',array(':side'=>$side)); + + $this->_ltitle[$side] = $title; + + return $this; + } + + public function json() { + $return = array(); + + $return['cols'][] = array( + 'id'=>'date', + 'label'=>'date', + 'type'=>'string', + ); + + // Columns + foreach (array_keys($this->_axis) as $l) { + $return['cols'][] = array( + 'id'=>$l, + 'label'=>$l, + 'type'=>'number', + ); + } + + // Values + foreach ($this as $k => $v) { + $data = array(); + + array_push($data,array('v'=>$k)); + + foreach ($this->_axis as $l => $axis) + array_push($data,array('v'=>$v[$l])); + + $return['rows'][] = array('c'=>$data); + } + + $options = array( + 'bar' => array('groupWidth'=>'75%'), + 'vAxis' => array('logScale'=>$this->_logy ? 1:0), + 'title' => $this->_title, + 'isStacked' => $this->_stacked ? 1:0, + 'seriesType' => $this->_type, + 'series' => $this->series(), + ); + + return json_encode(array('data'=>$return,'options'=>$options)); + } + + public function render() { + Script::add(array( + 'type'=>'src', + 'data'=>'https://www.google.com/jsapi', + )); + + Script::add(array( + 'type'=>'stdin', + 'data'=>'google.load("visualization", "1", {packages: ["corechart"]});', + )); + + Script::add(array( + 'type'=>'stdin', + 'data'=>" +function drawChart".$this->_divname."() { + var jsonData = $.ajax({ + url: '".$this->_dataurl."', + dataType:'json', + async: false, + }).responseText; + + var x = JSON.parse(jsonData); + for(var key in x) { + if (key == 'data') + data = x[key]; + else if (key == 'options') + options = x[key]; + else + alert('UNKNOWN Key: '+key); + } + + // Create our data table out of JSON data loaded from server. + var data = new google.visualization.DataTable(data); + + // Instantiate and draw our chart, passing in some options. + var chart = new google.visualization.ComboChart(document.getElementById('".$this->_divname."')); + chart.draw(data, options); +} +", + )); + + Script::add(array( + 'type'=>'stdin', + 'data'=>'google.setOnLoadCallback(drawChart'.$this->_divname.');', + )); + + return sprintf('
',$this->_divname,$this->_width,$this->_height); + } + + private function series() { + $return = array(); + $c = $this->seriescolors(); + + $i = 0; + foreach ($this->_axis as $l => $axis) { + // @todo This shouldnt be hard coded + if ($axis == 'yl') + array_push($return,array('type'=>'bar','color'=>$c[$i],'targetAxisIndex'=>0)); + else + array_push($return,array('type'=>'line','color'=>$c[$i],'targetAxisIndex'=>1)); + + $i++; + } + + return $return; + } +} +?> diff --git a/modules/gchart/classes/GoogleChart/Legacy.php b/modules/gchart/classes/GoogleChart/Legacy.php new file mode 100644 index 0000000..0ad104d --- /dev/null +++ b/modules/gchart/classes/GoogleChart/Legacy.php @@ -0,0 +1,221 @@ + 'lc', +// 'sparkline' => 'ls', +// 'line_xy' => 'lxy', + // Bar +// 'horizontal_bar' => 'bhs', + 'vertical_bar' => 'bvs', +// 'horizontal_bar_grp' => 'bhg', +// 'vertical_bar_grp' => 'bvg', + // Pie +// 'pie' => 'p', +// 'pie_3d' => 'p3', +// 'pie_concentric' => 'pc', + // Venn +// 'venn' => 'v', + // Scatter +// 'scatter' => 's', + // Radar +// 'radar' => 'r', +// 'radar_fill' => 'rs', + // Maps +// 'map' => 't', + // Google-o-meter +// 'google_o_meter' => 'gom', + // QR +// 'qr' => 'qr', + ); + + /** + * Set the type of the chart + * @param $type Chart type as per $this->cht + */ + public function type($type) { + if (empty($this->cht[$type])) + throw new Kohana_Exception('Unknown chart type :type for :class',array(':type'=>$type,':class'=>get_class($this))); + + $this->_type = $this->cht[$type]; + + return $this; + } + + /** + * Count how many metrics are being graphed per side + * @param $side Side YL (left) OR YR (right) + */ + private function axiscount($side) { + $i = 0; + + foreach ($this->_axis as $l => $axis) + if ($axis == $side) + $i++; + + return $i; + } + + /** + * Calculate our maximum for each side of the chart + */ + private function maxes() { + $return = array(); + + foreach ($this->_axis as $l => $axis) { + if (! isset($return[$axis])) + $return[$axis] = 0; + + $return[$axis] += $this->_max[$l]*1.1; // @todo This scaleup should be configurable + } + + return $return; + } + + /** CHART FIELDS **/ + + private function chd() { + $return = array(); + $maxes = $this->maxes(); + + // Perform our encoding + foreach ($this->_axis as $l => $axis) + array_push($return,$this->encode($this->_data[$l],$maxes[$axis])); + + $prefix = (count($maxes) > 1) ? sprintf('%s:',$this->axiscount('yl')) : ':'; + + // If encoding is text, we need to separate the series with a | + return ($this->_encodetype == 't') ? $prefix.implode('|',$return) : $prefix.implode(',',$return); + } + + private function chm() { + $return = array(); + $sc = $this->seriescolors(); + $i = 0; + + foreach ($this->_axis as $l => $axis) { + if ($axis == 'yr') + array_push($return,sprintf('%s,%s,%s,%s,%s,%s','D',$sc[$i],$i,0,2,2));// @todo 'D,0,2,2' May need to be configurable + + $i++; + } + + return count($return) ? implode('|',$return) : ''; + } + + private function chxl() { + $return = array(); + + // @todo This should be performed better - it may be a wrong assumption that all keys in the series have data. + foreach ($this->_data as $series => $data) + // @todo Why the 0:? + return '0:|'.implode('|',array_keys($data)); + } + + private function chxr() { + $return = array(); + $i = 1; + + foreach ($this->maxes() as $key => $value) + array_push($return,sprintf('%s,0,%s,0',$i++,$value)); + + return implode('|',$return); + } + + public function json() {} + + /** + * Return URL that renders the chart + */ + public function render() { + return sprintf('%s',$this->_url,http_build_query($this->build()),_('Google Chart')); + } + + /** + * Build the chart + */ + private function build() { + if ($this->_data ) { + return array( + 'chf'=>'bg,s,FFFFFF00', + 'cht'=>$this->_type, + 'chs'=>sprintf('%sx%s',$this->_width,$this->_height), + 'chtt'=>$this->_title, + 'chbh'=>'a', // @todo This might need to be calculated, valid options (a,r); + 'chg'=>'7.7,12.5,1,5', // @todo This should be calculated + 'chco'=>implode(',',$this->seriescolors()), + 'chdl'=>implode('|',array_keys($this->_axis)), + 'chd'=>$this->_encodetype.$this->chd(), + 'chm'=>$this->chm(), + 'chxt'=>'x,y,r', // @todo configurable? + 'chxl'=>$this->chxl(), + 'chxr'=>$this->chxr(), + ); + + } else + return array(); + } + + /** + * Encode the series data + * @param $data String of data to encode + * @param $max The maximum to scale to + */ + private function encode($data,$max=NULL) { + $table = array(); + $table['simple'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $table['extend'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'; + + $size = array(); + $size['simple'] = strlen($table['simple']); + $size['extend'] = strlen($table['extend']); + + if (is_null($max) OR $max == 0) + $max = max($data) > 0 ? max($data) : 1; + + $encode = ''; + + switch ($this->_encodetype) { + case 't' : + return join(',',$data); + + case 's' : + foreach ($data as $v) + $encode .= ($v > -1) ? substr($table['simple'],($size['simple']-1)*($v/$max),1) : '_'; + + break; + + case 'e' : + foreach ($data as $v) { + # Convert to a 0-4095 data range + $y = 4095*$v/$max; + $first = substr($table['extend'],floor($y/$size['extend']),1); + $second = substr($table['extend'],$y%$size['extend'],1); + $encode .= "$first$second"; + } + + break; + } + + return $encode; + } +} +?>