Skip to content

Commit

Permalink
Reporting / traffic: add "top talkers" tab which shows the current (a…
Browse files Browse the repository at this point in the history
…nd 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)
  • Loading branch information
AdSchellevis committed Dec 25, 2020
1 parent b4104cf commit 4186f2b
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 46 deletions.
Expand Up @@ -71,9 +71,11 @@ public function TopAction($interfaces)
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;
}
}
}
}
Expand Down
140 changes: 107 additions & 33 deletions src/opnsense/mvc/app/views/OPNsense/Diagnostics/traffic.volt
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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);
}
Expand All @@ -346,7 +400,6 @@ POSSIBILITY OF SUCH DAMAGE.
window.localStorage.setItem("api.diagnostics.traffic.interface", $(this).val());
}
});

});


Expand All @@ -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>
&nbsp;
</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>
&nbsp;
</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>
</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 class="col-xs-12 col-lg-6">
<div class="chart-container">
<canvas id="txTopChart" data-src_field="out"></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>
30 changes: 20 additions & 10 deletions src/opnsense/scripts/interfaces/traffic_top.py
Expand Up @@ -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()
Expand All @@ -86,16 +98,14 @@ def local_addresses():
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])
Expand Down

2 comments on commit 4186f2b

@fichtner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super, thanks! ❤️

@AdSchellevis
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it has some rough edges I'm trying to polish now (mainly javascript / sorting), but the idea is there. :)

Please sign in to comment.