From b7c408678a35362e600070856264a4cc98cc0715 Mon Sep 17 00:00:00 2001 From: Nigel Kukard <nkukard@lbsd.net> Date: Sun, 1 Dec 2013 09:43:50 +0000 Subject: [PATCH] Wording fix and small code cleanup --- .../plugins/webserver/pages/statistics.pm | 493 +++++++++--------- 1 file changed, 241 insertions(+), 252 deletions(-) diff --git a/opentrafficshaper/plugins/webserver/pages/statistics.pm b/opentrafficshaper/plugins/webserver/pages/statistics.pm index b5018dc..7d519b6 100644 --- a/opentrafficshaper/plugins/webserver/pages/statistics.pm +++ b/opentrafficshaper/plugins/webserver/pages/statistics.pm @@ -1,4 +1,4 @@ -# OpenTrafficShaper webserver module: users page +# OpenTrafficShaper webserver module: statistics page # Copyright (C) 2007-2013, AllWorldIT # # This program is free software: you can redistribute it and/or modify @@ -37,9 +37,19 @@ use JSON; use opentrafficshaper::logger; use opentrafficshaper::plugins; -use opentrafficshaper::utils qw( parseURIQuery ); +use opentrafficshaper::utils qw( + parseURIQuery +); + +use opentrafficshaper::plugins::configmanager qw( + getLimit -use opentrafficshaper::plugins::configmanager qw( getLimit getInterface isTrafficClassValid getTrafficClassName ); + getInterfaceGroup + getInterface + + isTrafficClassValid + getTrafficClassName +); use opentrafficshaper::plugins::statistics::statistics; @@ -90,16 +100,9 @@ EOF # Build content $content = <<EOF; - <div id="content" style="float:left"> - - <div style="position: relative; top: 50px;"> - <h4 style="color:#8f8f8f;">Latest Data For: $usernameEncoded</h4> + <h4 style="color:#8f8f8f;">Latest Data For: $usernameEncoded</h4> <br/> - - <div id="ajaxData" class="ajaxData" style="float:left; width:1024px; height: 610px"></div> - </div> - - </div> + <div id="flotCanvas" class="flotCanvas" style="width: 1000px; height: 400px"></div> EOF #$content .= statistics::do_test(); @@ -107,20 +110,17 @@ EOF # Files loaded at end of HTML document my @javascripts = ( - '/static/awit-flot/jquery.flot.min.js', - '/static/awit-flot/jquery.flot.time.min.js', + '/static/flot/jquery.flot.min.js', + '/static/flot/jquery.flot.time.min.js', + '/static/awit-flot/functions.js', # '/static/awit-flot/jquery.flot.websockets.js' ); # Build our data path using the URI module to make sure its nice and clean - my $dataPath = URI->new('/statistics/data-by-limit'); - # Pass it the original query, just incase we had extra params we can use - $dataPath->query_form( $request->uri->query_form() ); - my $dataPathStr = $dataPath->as_string(); - + my $dataPath = sprintf('/statistics/jsondata?limit=%s',$limit->{'ID'}); # String put in <script> </script> tags after the above files are loaded - my $javascript = _getJavascript($dataPathStr); + my $javascript = _getJavascript($dataPath); END: @@ -129,56 +129,6 @@ END: -# Return data by limit -sub databylimit -{ - my ($kernel,$globals,$client_session_id,$request) = @_; - - - # Parse GET data - my $queryParams = parseURIQuery($request); - - # Check if the limit ID was passed to us - if (!defined($queryParams->{'lid'})) { - return (HTTP_OK,{ 'error' => 'Invalid lid' },{ 'type' => 'json' }); - } - - my $limit; - if (!defined($limit = getLimit($queryParams->{'lid'}->{'value'}))) { - return (HTTP_OK,{ 'error' => 'Invalid limit' },{ 'type' => 'json' }); - } - - # Pull in stats data - my $statsData = opentrafficshaper::plugins::statistics::getStatsByLID($queryParams->{'lid'}->{'value'}); - - # First stage refinement - my $rawData; - foreach my $timestamp (sort keys %{$statsData}) { - foreach my $direction (keys %{$statsData->{$timestamp}}) { - - foreach my $stat ('rate','pps','cir','limit') { - push( @{$rawData->{"$direction.$stat"}->{'data'}} , [ $timestamp , $statsData->{$timestamp}->{$direction}->{$stat} ] ); - } - - } - } - # Second stage - add labels - foreach my $direction ('tx','rx') { - foreach my $stat ('rate','pps','cir','limit') { - # Make it looks nice: Tx Rate - my $label = uc($direction) . " " . ucfirst($stat); - # And set it as the label - $rawData->{"$direction.$stat"}->{'label'} = $label; - } - } - - # Final stage, chop it out how we need it - my $jsonData = [ $rawData->{'tx.limit'}, $rawData->{'rx.limit'}, $rawData->{'tx.rate'} , $rawData->{'rx.rate'} ]; - - return (HTTP_OK,$jsonData,{ 'type' => 'json' }); -} - - # Graphs by class sub byclass { @@ -208,26 +158,26 @@ EOF EOF goto END; } - # Grab the class - if (!defined($queryParams->{'class'})) { + # Check if we get some data back when pulling the interface from the backend + if (!defined($interface = getInterface($queryParams->{'interface'}->{'value'}))) { $content .=<<EOF; <tr class="info"> - <td colspan="8"><p class="text-center">No class in Query String</p></td> + <td colspan="8"><p class="text-center">No Interface Results</p></td> </tr> EOF goto END; } - # Check if we get some data back when pulling the interface from the backend - if (!defined($interface = getInterface($queryParams->{'interface'}))) { + # Grab the class + if (!defined($queryParams->{'class'})) { $content .=<<EOF; <tr class="info"> - <td colspan="8"><p class="text-center">No Interface Results</p></td> + <td colspan="8"><p class="text-center">No class in Query String</p></td> </tr> EOF goto END; } # Check if our traffic class is valid - if (!defined($cid = isTrafficClassValid($queryParams->{'class'})) && $queryParams->{'class'} ne "0") { + if (!defined($cid = isTrafficClassValid($queryParams->{'class'}->{'value'})) && $queryParams->{'class'}->{'value'} ne "0") { $content .=<<EOF; <tr class="info"> <td colspan="8"><p class="text-center">No Class Results</p></td> @@ -249,33 +199,24 @@ EOF # Build content $content = <<EOF; - <div id="content" style="float:left"> - - <div style="position: relative; top: 50px;"> <h4 style="color:#8f8f8f;">Latest Data For: $classNameEncoded on $interfaceNameEncoded</h4> - <br/> - - <div id="ajaxData" class="ajaxData" style="float:left; width:1024px; height: 560px"></div> - </div> - - </div> + <br/> + <div id="flotCanvas" class="flotCanvas" style="width: 1000px; height: 400px"></div> EOF # Files loaded at end of HTML document my @javascripts = ( - '/static/awit-flot/jquery.flot.min.js', - '/static/awit-flot/jquery.flot.time.min.js', + '/static/flot/jquery.flot.min.js', + '/static/flot/jquery.flot.time.min.js', + '/static/awit-flot/functions.js', ); # Build our data path using the URI module to make sure its nice and clean - my $dataPath = URI->new('/statistics/data-by-class'); - # Pass it the original query, just incase we had extra params we can use - $dataPath->query_form( $request->uri->query_form() ); - my $dataPathStr = $dataPath->as_string(); + my $dataPath = '/statistics/jsondata?counter=ConfigManager:TotalLimits&interface-group=eth4,eth5'; # String put in <script> </script> tags after the above files are loaded - my $javascript = _getJavascript($dataPathStr); + my $javascript = _getJavascript($dataPath); END: @@ -284,8 +225,24 @@ END: -# Return data by class -sub databyclass +# Return data in json format +# +# Supported URLs: +# +# limit=<limit-id> +# +# class=<interface-group-id>:<class-id>,... +# - must return both tx and rx sides +# +# interface-group=<interface-group>,... +# - must retun both tx and rx sides +# +# counter=<counter>,... +# +# max=<max number of entries to return, default 100> +# +# key=<data key to return> +sub jsondata { my ($kernel,$globals,$client_session_id,$request) = @_; @@ -293,190 +250,222 @@ sub databyclass # Parse GET data my $queryParams = parseURIQuery($request); - # Check if the username was passed to us - if (!defined($queryParams->{'interface'})) { - return (HTTP_OK,{ 'error' => 'Missing interface' },{ 'type' => 'json' }); - } - if (!defined($queryParams->{'class'})) { - return (HTTP_OK,{ 'error' => 'Missing class' },{ 'type' => 'json' }); - } - if (!defined(my $iface = getInterface($queryParams->{'interface'}))) { - return (HTTP_OK,{ 'error' => 'Invalid interface' },{ 'type' => 'json' }); - } - if (!defined(my $cid = isTrafficClassValid($queryParams->{'class'})) && $queryParams->{'class'} ne "0") { - return (HTTP_OK,{ 'error' => 'Invalid class' },{ 'type' => 'json' }); - } + my $jsonData = [ ]; - # Pull in stats data - my $statsData = opentrafficshaper::plugins::statistics::getStatsByClass($queryParams->{'interface'},$queryParams->{'class'}); + # Process limits + if (defined($queryParams->{'limit'})) { + # Lets get unique limits as keys + my %limits; + foreach my $lid (@{$queryParams->{'limit'}->{'values'}}) { + $limits{$lid} = 1; + } - # First stage refinement - my $rawData; - foreach my $timestamp (sort keys %{$statsData}) { - foreach my $direction (keys %{$statsData->{$timestamp}}) { + # Then loop through the unique keys + foreach my $lid (keys %limits) { + # Grab limit + my $limit = getLimit($lid); + if (!defined($limit)) { + $globals->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Got request for non-existent limit ID '$lid'"); + next; + } - foreach my $stat ('rate','pps','cir','limit') { - push( @{$rawData->{"$direction.$stat"}->{'data'}} , [ $timestamp , $statsData->{$timestamp}->{$direction}->{$stat} ] ); + # Pull in stats data + my $sid = opentrafficshaper::plugins::statistics::getSIDFromLID($limit->{'ID'}); + my $statsData = opentrafficshaper::plugins::statistics::getStatsBySID($sid); + + # First stage, pull in the data items we want + my $rawData; + foreach my $timestamp (sort keys %{$statsData}) { + foreach my $direction (keys %{$statsData->{$timestamp}}) { + foreach my $stat ('cir','limit','pps','rate') { + # Flow of traffic is always in tx direction + push( @{$rawData->{"$direction.$stat"}->{'data'}} , [ $timestamp , $statsData->{$timestamp}->{$direction}->{$stat} ] ); + } + } } + # JSON stuff we looking for + foreach my $direction ('tx','rx') { + foreach my $stat ('cir','limit','rate') { + # Make it looks nice: Tx Rate + my $label = uc($direction) . " " . ucfirst($stat); + # And set it as the label + $rawData->{"$direction.$stat"}->{'label'} = $label; + # Push the data to return... + if (defined($rawData->{"$direction.$stat"}->{'data'})) { + push(@{$jsonData},$rawData->{"$direction.$stat"}); + } + } + } } } - my $jsonData = [ ]; - - # Second stage - add labels - foreach my $direction ('tx','rx') { - foreach my $stat ('rate','pps','cir','limit') { - # Make it looks nice: Tx Rate - my $label = uc($direction) . " " . ucfirst($stat); - # And set it as the label - $rawData->{"$direction.$stat"}->{'label'} = $label; + # Process classes + if (defined($queryParams->{'class'})) { + # Lets get unique counters as keys + my %classes; + foreach my $rawClass (@{$queryParams->{'class'}->{'values'}}) { + $classes{$rawClass} = 1; } + # Then loop through the unique keys + foreach my $rawClass (keys %classes) { + # Split off based on : + my ($rawInterfaceGroup,$rawClass) = split(/:/,$rawClass); + + # Get more sane values... + my $interfaceGroup = getInterfaceGroup($rawInterfaceGroup); + if (!defined($interfaceGroup)) { + $globals->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Got request for non-existent interface group '$rawInterfaceGroup'"); + next; + } + my $class = isTrafficClassValid($rawClass); + if (!defined($class)) { + $globals->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Got request for non-existent traffic class '$rawClass'"); + next; + } - # JSON stuff we looking for - foreach my $stat ('limit','rate') { - if (defined($rawData->{"$direction.$stat"}->{'data'})) { - push(@{$jsonData},$rawData->{"$direction.$stat"}); + # Second stage - add labels + foreach my $direction ('tx','rx') { + my $rawData; + + # Pull in stats data + my $sid = opentrafficshaper::plugins::statistics::getSIDFromCID($interfaceGroup->{"${direction}iface"},$class); + my $statsData = opentrafficshaper::plugins::statistics::getStatsBySID($sid); + + # First stage, pull in the data items we want + foreach my $timestamp (sort keys %{$statsData}) { + foreach my $stat ('cir','limit','pps','rate') { + # Flow of traffic is always in tx direction + push( @{$rawData->{"$direction.$stat"}->{'data'}} , [ $timestamp , $statsData->{$timestamp}->{"tx"}->{$stat} ] ); + } + } + # Second state, add labels + foreach my $stat ('cir','limit','pps','rate') { + # Make it looks nice: Tx Rate + my $label = uc($direction) . " " . ucfirst($stat); + # And set it as the label + $rawData->{"$direction.$stat"}->{'label'} = $label; + } + + # JSON stuff we looking for + foreach my $stat ('cir','limit','rate') { + if (defined($rawData->{"$direction.$stat"}->{'data'})) { + push(@{$jsonData},$rawData->{"$direction.$stat"}); + } + } } } } - - return (HTTP_OK,$jsonData,{ 'type' => 'json' }); -} - - -# Return javascript for the graph -sub _getJavascript -{ - my $dataPath = shift; - - - my $javascript =<<EOF; -// Tooltip - Displays detailed information regarding the data point -function showTooltip(x, y, contents) { - jQuery('<div id="tooltip">' + contents + '</div>').css({ - position: 'absolute', - display: 'none', - top: y - 30, - left: x - 50, - color: "#fff", - padding: '2px 5px', - 'border-radius': '6px', - 'background-color': '#000', - opacity: 0.80 - }).appendTo("body").fadeIn(200); -} - -var previousPoint = null; -jQuery("#ajaxData").bind("plothover", function (event, pos, item) { - if (item) { - if (previousPoint != item.dataIndex) { - previousPoint = item.dataIndex; - jQuery("#tooltip").remove(); - showTooltip(item.pageX, item.pageY, item.series.label); + # Process interface groups + if (defined($queryParams->{'interface-group'})) { + # Lets get unique counters as keys + my %interfaceGroups; + foreach my $group (@{$queryParams->{'interface-group'}->{'values'}}) { + $interfaceGroups{$group} = 1; } - } else { - jQuery("#tooltip").remove(); - previousPoint = null; - } -}); - - -// Setting up the graph here -options = { - - series: { - lines: { - show: true, - lineWidth: 1, - fill: true, - fillColor: { - colors: [ - { opacity: 0.1 }, - { opacity: 0.13 } - ] + # Then loop through the unique keys + foreach my $group (keys %interfaceGroups) { + my $interfaceGroup = getInterfaceGroup($group); + if (!defined($interfaceGroup)) { + next; } - }, - - points: { - show: false, - lineWidth: 2, - radius: 3 - }, - - shadowSize: 0, - stack: true - }, - - grid: { - hoverable: true, - clickable: false, - tickColor: "#f9f9f9", - borderWidth: 0 - }, - - legend: { - labelBoxBorderColor: "#fff" - }, - - xaxis: { - mode: "time", - tickSize: [60, "second"], - - tickFormatter: function (v, axis) { - var date = new Date(v); + # Second stage - add labels + foreach my $direction ('tx','rx') { + my $rawData; + + # Pull in stats data + my $sid = opentrafficshaper::plugins::statistics::getSIDFromCID($interfaceGroup->{"${direction}iface"},0); + my $statsData = opentrafficshaper::plugins::statistics::getStatsBySID($sid); + + # First stage, pull in the data items we want + foreach my $timestamp (sort keys %{$statsData}) { + foreach my $stat ('cir','limit','pps','rate') { + # Flow of traffic is always in tx direction + push( @{$rawData->{"$direction.$stat"}->{'data'}} , [ $timestamp , $statsData->{$timestamp}->{"tx"}->{$stat} ] ); + } + } + # Second state, add labels + foreach my $stat ('cir','limit','pps','rate') { + # Make it looks nice: Tx Rate + my $label = uc($direction) . " " . ucfirst($stat); + # And set it as the label + $rawData->{"$direction.$stat"}->{'label'} = $label; + } + + # JSON stuff we looking for + foreach my $stat ('cir','limit','rate') { + if (defined($rawData->{"$direction.$stat"}->{'data'})) { + push(@{$jsonData},$rawData->{"$direction.$stat"}); + } + } + } + } + } - if (date.getSeconds() % 5 == 0) { - var hours = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(); - var minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(); - var seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds(); + # If we need to return a counter, lets see what there is we can return... + if (defined($queryParams->{'counter'})) { + # Lets get unique counters as keys + my %counters; + foreach my $counter (@{$queryParams->{'counter'}->{'values'}}) { + $counters{$counter} = 1; + } + # Then loop through the unique keys + foreach my $counter (keys %counters) { + # Grab the SID + if (my $sid = opentrafficshaper::plugins::statistics::getSIDFromCounter($counter)) { + my $rawData; + + # Grab stats + my $statsData = opentrafficshaper::plugins::statistics::getStatsBasicBySID($sid); + + # First stage refinement + foreach my $timestamp (sort keys %{$statsData}) { + # Flow of traffic is always in tx direction + push( @{$rawData->{'data'}} , [ $timestamp , $statsData->{$timestamp}->{'counter'} ] ); + } + + # We need to give it a funky name ... + $rawData->{'label'} = "Unknown"; + if ($counter eq "ConfigManager:TotalLimits") { + $rawData->{'label'} = "Total Limits"; + } + + # Push it onto our data stack... + push(@{$jsonData},$rawData); - return hours + ":" + minutes + ":" + seconds; - } else { - return ""; - } - }, - }, - - yaxis: { - min: 0, -// max: 4000, - tickFormatter: function (v, axis) { - if (v % 10 == 0) { - res = v.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ","); - console.log(res); - return res + ' Kbps'; } else { - return ""; + return (HTTP_OK,{ 'error' => 'Invalid Counter' },{ 'type' => 'json' }); } - }, + } } + + return (HTTP_OK,$jsonData,{ 'type' => 'json' }); } -// Load data from ajax -jQuery.ajax({ - url: '$dataPath', - dataType: 'json', - success: function(statsData) { - plot = null; +# Return javascript for the graph +sub _getJavascript +{ + my $graphData = shift; - // formatting time to match javascript's epoch in milliseconds - for (i = 0; (i < statsData.length); i++) { - //console.log(statsData[i].data); - for (y = 0; (y < statsData[i].data.length); y++) { - d = new Date(statsData[i].data[y][0] * 1000); - statsData[i].data[y][0] = statsData[i].data[y][0] * 1000; - } - } + # Build our data path using the URI module to make sure its nice and clean + my $dataPath = URI->new($graphData); + my $dataPathStr = $dataPath->as_string(); - if (statsData.length > 0) { - plot = jQuery.plot(jQuery("#ajaxData"), statsData, options); - } - } -}); + my $javascript =<<EOF; + awit_flot_draw_graph({ + url: '$dataPathStr', + yaxes: [ + { + labels: ['Total Limits'], + position: 'right', + tickDecimals: 0, + min: 0 + } + ] + }); EOF return $javascript; -- GitLab