mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-16 01:24:38 +00:00
Reporting / traffic: add "top talkers" tab which shows the current (and max) bandwith usage per ip address sorted by total (in+out).
some people seem to miss the numbers, since the top host graph uses the same information over time, it wouldn't hurt to add a tab containting the raw numbers for the selected interfaces. The current version doesn't limit the number of results, we might need to add a limit at some point in time, depending on how many results iftop samples. general remarks: o total amount of traffic is not being displayed, although we could count the "cumulative" from iftop, the numbers would always be flawed (since sampled with an interval) o no reverse lookups, maybe for a future version, let's first see if this helps. o hosts not seen for 120 seconds will automatically be removed from the list (fixed ttl)
This commit is contained in:
parent
b4104cf0a3
commit
4186f2bbe5
@ -71,9 +71,11 @@ class TrafficController extends ApiControllerBase
|
||||
if (count($iflist) > 0) {
|
||||
$data = (new Backend())->configdpRun('interface show top', [implode(",", $iflist)]);
|
||||
$data = json_decode($data, true);
|
||||
foreach ($data as $if => $content) {
|
||||
if (isset($ifmap[$if])) {
|
||||
$response[$ifmap[$if]] = $content;
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $if => $content) {
|
||||
if (isset($ifmap[$if])) {
|
||||
$response[$ifmap[$if]] = $content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +227,59 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
return new Chart(ctx, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* iftop (top talkers) update
|
||||
*/
|
||||
function updateTopTable(data) {
|
||||
let target = $("#rxTopTable > tbody");
|
||||
let update_stamp = Math.trunc(Date.now() / 1000.0);
|
||||
Object.keys(data).forEach(function(intf) {
|
||||
let intf_label = $("#interfaces > option[value="+intf+"]").data('content');
|
||||
['in', 'out'].forEach(function(dir) {
|
||||
for (var i=0; i < data[intf][dir].length ; i++) {
|
||||
let item = data[intf][dir][i];
|
||||
let tr = target.find("tr[data-address='"+item.address+"']");
|
||||
if (tr.length === 0) {
|
||||
tr = $("<tr data-bps_in='0' data-bps_out='0' data-bps_max_in='0' data-bps_max_out='0'/>");
|
||||
tr.attr("data-address", item.address);
|
||||
tr.append($("<td/>").html(intf_label));
|
||||
tr.append($("<td/>").text(item.address));
|
||||
tr.append($("<td class='bps_in'/>").text("0b"));
|
||||
tr.append($("<td class='bps_out'/>").text("0b"));
|
||||
tr.append($("<td class='bps_max_in'/>").text("0b"));
|
||||
tr.append($("<td class='bps_max_out'/>").text("0b"));
|
||||
target.append(tr);
|
||||
}
|
||||
tr.attr('data-bps_'+dir, item.rate_bits);
|
||||
tr.attr('data-last_seen', update_stamp);
|
||||
if (parseInt(tr.data('bps_max_'+dir)) < item.rate_bits) {
|
||||
tr.attr('data-bps_max_'+dir, item.rate_bits);
|
||||
tr.find('td.bps_max_'+dir).text(item.rate);
|
||||
}
|
||||
tr.find('td.bps_'+dir).text(item.rate);
|
||||
}
|
||||
});
|
||||
});
|
||||
let ttl = 120; // keep visible for ttl seconds
|
||||
target.find('tr').each(function(){
|
||||
if (parseInt($(this).data('last_seen')) < (update_stamp - ttl)) {
|
||||
$(this).remove();
|
||||
} else if (parseInt($(this).data('last_seen')) != update_stamp) {
|
||||
// reset measurements not in this set
|
||||
$(this).attr('data-bps_in', 0);
|
||||
$(this).attr('data-bps_out', 0);
|
||||
$(this).find('td.bps_in').text("0b");
|
||||
$(this).find('td.bps_out').text("0b");
|
||||
}
|
||||
});
|
||||
// sort by current top consumer
|
||||
target.find('tr').sort(function(a, b) {
|
||||
let a_total = +$(a).data('bps_in') + $(a).data('bps_out');
|
||||
let b_total = +$(b).data('bps_in') + $(b).data('bps_out');
|
||||
return b_total - a_total;
|
||||
}).appendTo(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* startup, fetch initial interface stats and create graphs
|
||||
*/
|
||||
@ -334,6 +387,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
if (status == 'success') {
|
||||
$( document ).trigger( "updateTrafficTopCharts", [ data ] );
|
||||
top_traffic_poller();
|
||||
updateTopTable(data);
|
||||
} else {
|
||||
setTimeout(top_traffic_poller, 2000);
|
||||
}
|
||||
@ -346,7 +400,6 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
window.localStorage.setItem("api.diagnostics.traffic.interface", $(this).val());
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -357,46 +410,67 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content-box">
|
||||
<div class="content-box-main">
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" id="graph_tab" href="#graph">{{ lang._('Graph') }}</a></li>
|
||||
<li><a data-toggle="tab" id="gtid_tab" href="#toptalkers">{{ lang._('Top talkers') }}</a></li>
|
||||
<div class="pull-right">
|
||||
<select class="selectpicker" id="interfaces" multiple=multiple>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</ul>
|
||||
<div class="tab-content content-box">
|
||||
<div id="graph" class="tab-pane fade in active">
|
||||
<div class="table-responsive">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="pull-right">
|
||||
<select class="selectpicker" id="interfaces" multiple=multiple>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="rxChart" data-src_field="bytes received"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="txChart" data-src_field="bytes transmitted"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<hr/>
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="rxChart" data-src_field="bytes received"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="rxTopChart" data-src_field="in"></canvas>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="txChart" data-src_field="bytes transmitted"></canvas>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="txTopChart" data-src_field="out"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="rxTopChart" data-src_field="in"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<div class="chart-container">
|
||||
<canvas id="txTopChart" data-src_field="out"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="toptalkers" class="tab-pane fade in">
|
||||
<div class="col-xs-12 col-lg-6">
|
||||
<table class="table table-condensed" id="rxTopTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ lang._('Address') }}</th>
|
||||
<th>{{ lang._('In (bps)') }}</th>
|
||||
<th>{{ lang._('Out (bps)') }}</th>
|
||||
<th>{{ lang._('In max(bps)') }}</th>
|
||||
<th>{{ lang._('Out max(bps)') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -61,6 +61,18 @@ def local_addresses():
|
||||
result.append(ip)
|
||||
return result
|
||||
|
||||
def convert_bformat(value):
|
||||
value = value.lower()
|
||||
if value.endswith('kb'):
|
||||
return decimal.Decimal(value[:-2]) * 1000
|
||||
elif value.endswith('mb'):
|
||||
return decimal.Decimal(value[:-2]) * 1000000
|
||||
elif value.endswith('gb'):
|
||||
return decimal.Decimal(value[:-2]) * 1000000000
|
||||
elif value.endswith('b') and value[:-1].isdigit():
|
||||
return decimal.Decimal(value[:-1])
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
result = dict()
|
||||
parser = argparse.ArgumentParser()
|
||||
@ -86,16 +98,14 @@ if __name__ == '__main__':
|
||||
parts = line.split()
|
||||
if parts[0].find('.') == -1 and parts[0].find(':') == -1:
|
||||
parts.pop(0)
|
||||
rate_bits = 0
|
||||
if parts[2].endswith('Kb'):
|
||||
rate_bits = decimal.Decimal(parts[2][:-2]) * 1000
|
||||
elif parts[2].endswith('Mb'):
|
||||
rate_bits = decimal.Decimal(parts[2][:-2]) * 1000000
|
||||
elif parts[2].endswith('Gb'):
|
||||
rate_bits = decimal.Decimal(parts[2][:-2]) * 1000000000
|
||||
elif parts[2].endswith('b') and parts[2][:-1].isdigit():
|
||||
rate_bits = decimal.Decimal(parts[2][:-1])
|
||||
item = {'address': parts[0], 'rate': parts[2], 'rate_bits': rate_bits, 'tags': []}
|
||||
item = {
|
||||
'address': parts[0],
|
||||
'rate': parts[2],
|
||||
'rate_bits': int(convert_bformat(parts[2])),
|
||||
'cumulative': parts[5],
|
||||
'cumulative_bytes': int(convert_bformat(parts[5])),
|
||||
'tags': []
|
||||
}
|
||||
# attach tags (type of address)
|
||||
try:
|
||||
ip = IPAddress(parts[0])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user