Arnaud Fenioux 's Personal Home Page

pmacct / sfacct + influxdb + grafana

January 11, 2018 - Arnaud
Categorie: Technical

I spent a lot of time messing with all of this, that's why I deceided to share my notes.
My aim was to collect sFlow samples and to agregate them (sfacct), store them in a TSDB (influxDB) and graph network traffic (grafana).



sFlow is used to make packet sampling (whereas netflow is flow -sampled or not- oriented). sFlow packets contains several Flow samples, you can easily read sFlow packets with wireshark or sflowtool.
(s)Flow samples can have several informations :

  • standard information : input interface, output interface, sampling_rate...
  • Raw packet header : from layer2 (mac...) to layer 4 (proto, and port)
  • Extended switch data : incoming/outgoing VLAN tag
  • Extended router data : direct router next-hop
  • Extended gateway data : BGP next-hop, AS Peer, AS source, AS sequence, localpref, communities...



PMACCT is a project with several binaries :

  • pmacct : the cli, used to query data
  • pmacctd : libpcap collector
  • nfacctd : netflow/IP-FIX collector
  • sfacctd : sFlow collector

Please note that the only up-to-date documentation can be fond on, documentation on is (at the time of writing) outdated.


You can use your packet manager to install it, or compile it (example for debian) :

git clone
apt-get install pkg-config libtool autoconf automake
apt-get install gcc make libstdc++-6-dev libssl-dev libpcap-dev libmariadbclient18 libmariadbd-dev librabbitmq-dev libjansson-dev librdkafka-dev libtool

cd pmacct
# make sure to enable all the plugins you need
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-rabbitmq --enable-mysql --enable-ipv6 --enable-l2 --enable-debug --enable-plabel --enable-64bit --enable-threads --enable-jansson

mkdir /etc/pmacct
make install

I will talk about sfacct, but it's almost the same for nfacct. sfacct will collect sFlow samples and agregarte them by primitives during a time interval. Primitives are the keys you are interrested for (can be IP_SRC, IP_DST, AS_SRC, AS_DST...) the whole list of primitives can be found in CONFIG-KEYS (look at the "KEY: aggregate" section). sfacct will agregate packets with common primitives and gives PACKETS count and BYTES.
You should also know that sfacct has many plugins (the way to export data), it can be in_memory, print in a file, sql, rabbitmq...

There are plenty of information and examples in QUICKSTART (good luck)

Here is a basic configuration example (/etc/pmacct/sfacctd.conf) :

debug: false
! for this example, I wan to run sfacctd by hand and look at the output
daemonize: false
pidfile: /var/run/
! remember to configure logrotate if you use logfile
!logfile : /var/log/sfacct.log

! returns warning messages in case of data loss
! look at CONFIG-KEYS for details
! bufferization of data transfers between core process and active plugins (default 4MB)
plugin_pipe_size: 10240000

! The value has to be <= the size defined by 'plugin_pipe_size' and keeping a ratio < 1:1000 between the two
! Once a buffer is filled, it is delivered to the plugin
plugin_buffer_size: 10240

! automatically renormalizes byte/packet counters value basing on information acquired
! and take count of the sampling_rate in the (s)Flow sample
sfacctd_renormalize: true

! I dont use it, but you can use several plugins if you like : "plugins: amqp[foo], memory[bar]"
! and set options to plugins with aggregate[foo]: 
plugins: memory
! check primitives list in CONFIG-KEYS
aggregate: peer_src_as, peer_dst_as, src_as, dst_as, etype


We can now run sfacct with sfacctd -f /etc/pmacct/sfacctd.conf command, if you enable debug it should print lot of things.
As we are using the memory plugin, we can easily check what sfacct is collecting :

  • pmacct -s ::  Show full statistics
  • pmacct -s -T bytes :: Output statistics ordered by decreasing value of bytes
  • pmacct -e :: Clear all statistics
  • pmacct -e -s -T bytes -O json :: combo! Show full statistics ordered by bytes in json format and clear statistics
#pmacct -e -s -T bytes | head
800    64496       64500       64496       64500        304324608             406280773632
800    64497       64500       64496       64500        175841280             241821032448
800    64500       64496       64500       64496        142401536             195753091072

Not bad ;) but the memory plugin is not "production grade"... even with plugin_pipe_size tuned, it still loss records :/
Instead we will use the print plugin (extract from sfacctd.conf):

plugins: print[print]
aggregate [print]: peer_src_as, peer_dst_as, src_as, dst_as, etype

! by default file is overwritten
print_output_file[print]: /tmp/5m_avg.json
print_output[print]: json
print_history[print]: 5m
print_history_roundoff[print]: m
print_refresh_time[print]: 300
! we want to run this script after purging cache (but that's another story) :
print_trigger_exec[print]: /opt/



InfluxDB has a really well done documentation you should follow to get stated :


#first steps

Here is how to create a new dayabase and check basics :

> use sflow

As you could see, the default retention policie is named autogen and duration is set to 0s (no record deleted after a duration time).
That's ok for me because I want to keep infinitelly all records that I will import every 5 minutes. But you can do really awesome things with RETENTION POLICY and CONTINUOUS QUERY... read the doc!

AS you could see with SHOW MEASUREMENTS, there is currently no measurement, but that's were comes in!

We declared in the sfacctd.conf to run a script via print_trigger_exec after writing the cache into /tmp/5m_avg.json but we can not pass arguments to our script.

As you guessed, this script will parse the JSON and import the result into InfluxDB. Here is the script I wrote (in bash!)
This was inspired from


#!/usr/bin/env bash


# Header for influx import
echo -e "# DML \n# CONTEXT-DATABASE: $DATABASE" > /tmp/pma2influx.txt

# We will import all the primitives of sfacctd as tags into influx (with the same name)
# only bytes are saved as field value
# these records are stored in a MEASUREMENT name "traffic"
# sfacctd BYTE size is w/o L2 informations, We need to add them to be more accurate with SNMP counters
# (26 bytes w/o VLAN tag, 30 bytes with) * PACKETS count

cat /tmp/5m_avg.json | sed 's/{//; s/}//; s/"//g; s/:/=/g; s/\ //g;' | sed 's/,packets=/\ /;s/,bytes=/ /'| awk '{print "traffic,"$1,"bytes="$2*26+$3}' >> /tmp/pma2influx.txt

# This is the JSON for one aggregation
# {"etype": "800", "as_src": 64496, "as_dst": 64500, "peer_as_src": 64496, "peer_as_dst": 64500, "packets": 112910336, "bytes": 150642491392}

# This is a record to add to influx, no timestamp specified, Influx will add it at this time of import
#traffic,etype=800,as_src=64496,as_dst=64500,peer_as_src=64496,peer_as_dst=64500 bytes=153578160128

# This is how we import data into influx
influx -import -path=/tmp/pma2influx.txt

After a few minutes we can check the result in influx :

influx -database 'sflow' -execute 'SELECT * FROM traffic' | wc -l




The installation is straight forward :

The configuration do not need much edits, by defaut grafana web-server is listening on port 3000.
Default user is admin/admin make sure to edit the config file or change the password on the http://host:3000/profile/password page
details are available at :

To configure Grafana with InfluxDB, I followed theses instructions :
adding a Data Source is really easy, set the URL to http://localhost:8086 and Access in proxy mode, make sure to set the Min time interval (5m for me).

Once the Data Source is configured, the last thing to do is to configure a dashboard, make sure to save it before closing the page, otherwise you will lost all your creation.

and tada!


Going further

I now want to get 95%tils trafic on the last month from AS64500 to AS64496 , which is quite easy to do with InfluxDB (remember we store data at 5min average, value in now in Mbps) :

SELECT PERCENTILE("bytes",95)*8/300/1024/1024,peer_as_src,as_dst,peer_as_dst FROM traffic WHERE peer_as_src = '64500' AND time > now() - 30d GROUP BY etype,as_dst'

name: traffic
tags: as_dst=64496, etype=800
time                percentile        peer_as_src as_dst peer_as_dst
----                ----------        ----------- ------ -----------
1516046701202122812 78744.20395833334 64500       64496   64496

name: traffic
tags: as_dst=64496, etype=86dd
time                percentile        peer_as_src as_dst peer_as_dst
----                ----------        ----------- ------ -----------
1516046701202122812 1484.853541666667 64500       64496   64496

As you could see, I had 2 series in result for traffic from 64500 to 64496. It is extremely important here to use "GROUP BY etype", otherwise you will mix values from both protocols and the result won't be what you expect it to be.

With the same idea, we can not get so easily the global 95%tils from AS64500 to any (I mean the 95%tils input on his port), trying to "GROUP BY peer_as_src" only, would result in a very low value.
To overcome this issue, we will agregate data per peer_as every 5 minutes in a CONTINUOUS QUERY :

name: sflow
name                      query
----                      -----
cq_sum_per_peer_as_dst_5m CREATE CONTINUOUS QUERY cq_sum_per_peer_as_dst_5m ON sflow BEGIN SELECT sum(bytes) AS bytes INTO sflow.autogen.sum_per_peer_as_dst FROM sflow.autogen.traffic GROUP BY peer_as_dst, time(5m) END
cq_sum_per_peer_as_src_5m CREATE CONTINUOUS QUERY cq_sum_per_peer_as_src_5m ON sflow BEGIN SELECT sum(bytes) AS bytes INTO sflow.autogen.sum_per_peer_as_src FROM sflow.autogen.traffic GROUP BY peer_as_src, time(5m) END

Let's wait a bit before seeing the result with a SHOW MEASUREMENTS and voila!

> SELECT * FROM sum_per_peer_as_src where peer_as_src = '64500';
name: sum_per_peer_as_src
time                bytes         peer_as_src
----                -----         -----------
1516228500000000000 7771814551552 64500
1516228800000000000 7627846352896 64500
1516229100000000000 7526702882816 64500

> SELECT PERCENTILE("bytes",95)*8/300/1024/1024,peer_as_src FROM sum_per_peer_as_src WHERE peer_as_src = '64500'  AND time > now() - 30d;
name: sum_per_peer_as_src
time                percentile         peer_as_src
----                ----------         -----------
1516228500000000000 197647.46479166666 64500

We wouldn't need all this if we were using SUM(), remember to use extra-care with PERCENTILE().



One point I should have mentionned before, is how is determined the PEER_AS_SRC, It's actually determined on the router that this sending the Flow via a FIB lookup.
In other words it's where we would have send the traffic for the source address we are looking in the packet, but if the traffic is asymetrical it is a wrong assumption.

Here is how to fix it (example of sfacctd.conf) :

! Even if you don't setup a BGP session, we MUST HAVE these lines to get bgp_peer_src_as_map working
bgp_daemon: true
bgp_daemon_max_peers: 1
bgp_peer_src_as_map: /etc/pmacct/
bgp_peer_src_as_type: map

And the example of :

! id is the ASN value we want to set PEER_AS_SRC well PEER_SRC_AS... it depends...
! I've choosed to use src_mac as a discreminent, please note that I use for a rather large prefix for ip (ip is a mandatory field...), it the source IP of the router sending the sFflow packets.
id=9036            src_mac=11:22:33:44:55:66    ip=
id=35280           src_mac=aa:bb:cc:dd:ee:11    ip=
! catch all
id=999999          ip=
! you could also do a BGP looking as last chance catch all with :
! but of cours you need to have setup an ibgp session from you router to pmacct listening IP.
id=bgp                      ip=

All fields usable are detailed in this, of cours you can write you own automation script or look at