#!/usr/bin/perl

# Author: Andrew Theurer
#
# usage: openvswitch-postprocess <dir>  dir = directory where openvswitch.txt can be found
#
# The purpose of this script is to
# -output the chart data in JSON format (openvswitch.js)
# -output html file (openvswitch.html) with embedded javascript, using d3.js libraries
#
# The input file that this scripts processes must be in the format which is generated by openvswitch-datalog

use strict;
use warnings;

use lib $ENV{'pbench_lib_dir'};
no lib ".";
use GenData qw(gen_data);

my $dir=$ARGV[0];
my $stats_file_name = "ovs-stats.txt";
my $timestamp;
my $timestamp_ms;
my $line;
my $bridge;
my $stats_type;
my $stats_mode;
my $pmd_thread_id;
my $portqueue;
my $port;
my %pmd_data;
my %bridge_data;
my %flow_data;
my %rate;


my %pmd_mappings;
#Load the mappings from pmd thread to bridge ports
my $pmd_port_mapping_file = "pmd-rxq-show.txt";
open(TXT, "$dir/$pmd_port_mapping_file") || die "could not find $dir/$pmd_port_mapping_file\n";
# file looks like:
#
#pmd thread numa_id 1 core_id 1:
#	isolated : true
#	port: vhu63773cfe-11	queue-id: 0
#pmd thread numa_id 1 core_id 3:
#	isolated : true
#	port: dpdk2	queue-id: 0
#pmd thread numa_id 0 core_id 30:
#	isolated : false
#
#pmd thread numa_id 0 core_id 26:
#	isolated : true
#	port: dpdk0	queue-id: 0
#pmd thread numa_id 0 core_id 2:
#	isolated : true
#	port: vhuabac543b-5f	queue-id: 0
#pmd thread numa_id 0 core_id 28:
#	isolated : true
#	port: dpdk1	queue-id: 0
#pmd thread numa_id 0 core_id 4:
#	isolated : true
#	port: vhu9680c9c2-fe	queue-id: 0
#pmd thread numa_id 0 core_id 6:
#	isolated : false
$pmd_thread_id = "";
$portqueue = "";
while (my $line = <TXT>) {
	chomp $line;
	if ($line =~ /^pmd thread numa_id (\d+) core_id (\d+):/ ) {
		# we found a new PMD thread, log the previous one if there is one
		if ($pmd_thread_id ne "") {
			$pmd_mappings{$pmd_thread_id} = $portqueue;
			$pmd_thread_id = "";
			$portqueue = "";
		}
		my $node = $1;
		my $cpu = $2;
		$pmd_thread_id = "node" . $node . "cpu" . $cpu;
	} elsif ($line =~ /^\s*port:\s(\S+)\s+queue-id:\s+(\d+)/ ) {
		$portqueue = $1 . ":" . $2;
	}
}
# get the final PMD
if ($pmd_thread_id ne "") {
	$pmd_mappings{$pmd_thread_id} = $portqueue;
}
# create one more for "main" thread
$pmd_mappings{"main"} = '';
close TXT;

open(TXT, "$dir/$stats_file_name") || die "could not find $dir/$stats_file_name\n";
while (my $line = <TXT>) {
	chomp $line;
## sample file data:
##
#timestamp: 1490728615.182326715
#bridge: phy-br-0 stats: dump-ports
#OFPST_PORT reply (xid=0x2): 2 ports
#  port LOCAL: rx pkts=8, bytes=648, drop=0, errs=0, frame=0, over=0, crc=0
#           tx pkts=9, bytes=540, drop=0, errs=0, coll=0
#  port  1: rx pkts=1194615, bytes=207861984, drop=0, errs=0, frame=?, over=?, crc=?
#           tx pkts=967181, bytes=168284988, drop=0, errs=0, coll=?
#bridge: br-int stats: dump-flows
#NXST_FLOW reply (xid=0x4):
# cookie=0xb9a6deb9663c7428, duration=76802.555s, table=0, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=10,icmp6,in_port=12,icmp_type=136 actions=resubmit(,24)
# cookie=0xb9a6deb9663c7428, duration=76802.553s, table=0, n_packets=1901, n_bytes=79842, idle_age=75, hard_age=65534, priority=10,arp,in_port=12 actions=resubmit(,24)
# cookie=0xb9a6deb9663c7428, duration=426577.522s, table=0, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=2,in_port=1 actions=drop
# cookie=0xb9a6deb9663c7428, duration=426577.294s, table=0, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=2,in_port=2 actions=drop
# cookie=0xb9a6deb9663c7428, duration=426576.957s, table=0, n_packets=48369282, n_bytes=38576491640, idle_age=79, hard_age=65534, priority=2,in_port=3 actions=drop
# cookie=0xb9a6deb9663c7428, duration=426576.728s, table=0, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=2,in_port=4 actions=drop
# cookie=0xb9a6deb9663c7428, duration=76802.559s, table=0, n_packets=67873, n_bytes=21683412, idle_age=0, hard_age=65534, priority=9,in_port=12 actions=resubmit(,25)
# cookie=0xb9a6deb9663c7428, duration=372455.857s, table=0, n_packets=40755, n_bytes=11000926, idle_age=0, hard_age=65534, priority=3,in_port=1,vlan_tci=0x0000/0x1fff actions=mod_vlan_vid:5,NORMAL
# cookie=0xb9a6deb9663c7428, duration=426577.756s, table=0, n_packets=31062164161, n_bytes=23860319661928, idle_age=16, hard_age=65534, priority=0 actions=NORMAL
# cookie=0xb9a6deb9663c7428, duration=426577.757s, table=23, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=0 actions=drop
# cookie=0xb9a6deb9663c7428, duration=76802.557s, table=24, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=2,icmp6,in_port=12,icmp_type=136,nd_target=fe80::f816:3eff:fe71:1298 actions=NORMAL
# cookie=0xb9a6deb9663c7428, duration=76802.554s, table=24, n_packets=359, n_bytes=15078, idle_age=75, hard_age=65534, priority=2,arp,in_port=12,arp_spa=10.1.1.7 actions=resubmit(,25)
# cookie=0xb9a6deb9663c7428, duration=426577.755s, table=24, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=0 actions=drop
# cookie=0xb9a6deb9663c7428, duration=76802.561s, table=25, n_packets=69774, n_bytes=21763254, idle_age=0, hard_age=65534, priority=2,in_port=12,dl_src=fa:16:3e:71:12:98 actions=NORMAL
#bridge: phy-br-0 stats: fdb/show
# port=1 count=1000
#bridge: phy-br-1 stats: dump-ports
#OFPST_PORT reply (xid=0x2): 2 ports
#  port LOCAL: rx pkts=8, bytes=648, drop=0, errs=0, frame=0, over=0, crc=0
#           tx pkts=0, bytes=0, drop=0, errs=0, coll=0
#  port  1: rx pkts=0, bytes=0, drop=0, errs=0, frame=?, over=?, crc=?
#           tx pkts=49, bytes=3108, drop=0, errs=0, coll=?
#dpdk-stats: pmd-stats-show
#main thread:
#	emc hits:0
#	megaflow hits:0
#	avg. subtable lookups per hit:0.00
#	miss:0
#	lost:0
#	polling cycles:60721008 (100.00%)
#	processing cycles:0 (0.00%)
#pmd thread numa_id 0 core_id 2:
#	emc hits:0
#	megaflow hits:0
#	avg. subtable lookups per hit:0.00
#	miss:0
#	lost:0
#	polling cycles:1149996528518 (100.00%)
#	processing cycles:0 (0.00%)
#pmd thread numa_id 1 core_id 3:
#	emc hits:0
#	megaflow hits:0
#	avg. subtable lookups per hit:0.00
#	miss:0
#	lost:0
#	polling cycles:624184044056 (100.00%)
#	processing cycles:0 (0.00%)

	if ($line =~ /^timestamp: (\d+)\.\d+/ ) {
		$timestamp = $1;
		$timestamp_ms = $timestamp * 1000;
		$bridge = "";
		next;
	}

	if ($line =~ /^bridge: (\S+) stats: (\S+)/ ) {
		$bridge = $1;
		$stats_type = $2;
		$stats_mode = "bridge";
		$port = "";
		next;
	}

	#main thread:
	if ($line =~ /^main thread:/ ) {
		my $node = $1;
		my $cpu = $2;
		$stats_mode = "dpdk";
		$pmd_thread_id = "main";
		next;
	}

	#pmd thread numa_id 0 core_id 2:
	if ($line =~ /^pmd thread numa_id (\d+) core_id (\d+):/ ) {
		my $node = $1;
		my $cpu = $2;
		$stats_mode = "dpdk";
		$pmd_thread_id = "node" . $node . "cpu" . $cpu;
		next;
	}
	#  port  1: rx pkts=0, bytes=0, drop=0, errs=0, frame=?, over=?, crc=?
	if ($line =~ /^\s+port\s+(\S+):(.*)/ and $stats_mode eq "bridge" and $stats_type eq "dump-ports") {
		$port = $1;
		$line = $2;
	}

	#           tx pkts=49, bytes=3108, drop=0, errs=0, coll=?
	if ( $line =~ /\s*([r|t]x)(.*)/ and $stats_mode eq "bridge" and $stats_type eq "dump-ports") {
		my $direction = $1;
		$line = $2;
		my @stats = split(/,/, $line);
		my $stat;
		foreach $stat (@stats) {
			$stat =~ s/^\s+//;
			$stat =~ s/\s/_/g;
			if ($stat =~ /(\S+)=(\S+)/) {
				my $s = $1;
				my $v = $2;
				if ($v !~ /\?/) {
					$bridge_data{$bridge}{$port}{$direction . "-" . $s}{$timestamp_ms} = $v;
				}
			}
		}
		next;
	}

	#  port=N count=X
	if ($line =~ /^\s+port=(\S+)\s+count=(\S+)/ and $stats_mode eq "bridge" and $stats_type eq "fdb/show") {
		$port = $1;
		my $entry_count = $2;

		$bridge_data{$bridge}{$port}{'fdb-entries'}{$timestamp_ms} = $entry_count;

		next;
	}

	#       packet recirculations: 0
	if ( $line =~ /\s*packet\srecirculations:\s*(\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-packet-recirculations"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD packet recirculations stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       packets received: 0
	if ( $line =~ /\s*packets\sreceived:\s*(\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-packets-received"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD received packets stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       avg. packets per output batch: 14.67
	if ( $line =~ /\s*avg\.\spackets\sper\soutput\sbatch:\s*(\d+\.\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-packets-per-output-batch"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD packets per output batch stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       avg. datapath passes per packet: 0.00
	if ( $line =~ /\s*avg\.\sdatapath\spasses\sper\spacket:\s*(\d+\.\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-datapath-passes-per-packet"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD datapath passes per packet stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       avg. subtable lookups per megaflow hit: 1.62
	if ( $line =~ /\s*avg\.\ssubtable\slookups\sper\smegaflow\shit:\s*(\d+\.\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-subtable-lookups-per-megaflow-hit"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD subtable lookup per megaflow hit stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	if ( $line =~ /avg\.\ssubtable\slookups\sper\shit:\s*(\d+\.\d+)/ and $stats_mode eq "dpdk" ) {
	    if (exists $pmd_mappings{$pmd_thread_id}) {
		$rate{"openvswitch"}{"PMDthread-subtable-lookups-per-hit"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
	    } else {
		printf "warning: a PMD subtable lookup per hit stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
	    }
	    next;
        }

	#       miss with success upcall: 0
	if ( $line =~ /\s*miss\swith\ssuccess\supcall:\s*(\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-upcalls"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id} . "-success"}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD miss with success upcall stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       miss with failed upcall: 0
	if ( $line =~ /\s*miss\swith\sfailed\supcall:\s*(\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-upcalls"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id} . "-failed"}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD miss with failed upcall stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       polling cycles:0 (0.00%)
	if ( $line =~ /\s*polling\scycles:\s*\d+\s+\((\d+\.\d+)\%\)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-polling-utilization-percent"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD polling utilization percent stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       processing cycles:0 (0.00%)
	if ( $line =~ /\s*processing\scycles:\s*\d+\s+\((\d+\.\d+)\%\)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-processing-utilization-percent"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD processing utilization percent stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       idle cycles:0 (0.00%)
	if ( $line =~ /\s*idle\scycles:\s*\d+\s+\((\d+\.\d+)\%\)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-idle-utilization-percent"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a PMD idle utilization percent stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#	avg processing cycles per packet: 618.17 (2612413532/4226062)
	if ( $line =~ /\s*avg\sprocessing\scycles\sper\spacket:\s*(\d+\.\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-processing-cycles-per-packet"}{$pmd_thread_id. $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a processing cycles per packet stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#	avg cycles per packet: 1343.67 (6650670772/4949637)
	if ( $line =~ /\s*avg\scycles\sper\spacket:\s*(\d+\.\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-total-cycles-per-packet"}{$pmd_thread_id. $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a total cycles per packet stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}

	#       emc hits:0
	#       smc hits:0
	if ( $line =~ /\s*([es]mc)\shits:\s*(\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDthread-" . $1 . "-hits"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $2;
		} else {
			printf("warning: a %s hits stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n", $1);
		}
		next;
	}

	#       megaflow hits:0
	if ( $line =~ /\s*megaflow\shits:\s*(\d+)/ and $stats_mode eq "dpdk" ) {
		if (exists $pmd_mappings{$pmd_thread_id}) {
			$rate{"openvswitch"}{"PMDmegaflow-hits"}{$pmd_thread_id . $pmd_mappings{$pmd_thread_id}}{$timestamp_ms} = $1;
		} else {
			printf "warning: a megaflow hit stat for $pmd_thread_id was found, but there was no record of this PMD thread in pmd-rxq-mapping.txt\n";
		}
		next;
	}
	# cookie=0xb9a6deb9663c7428, duration=76802.555s, table=0, n_packets=0, n_bytes=0, idle_age=65534, hard_age=65534, priority=10,icmp6,in_port=12,icmp_type=136 actions=resubmit(,24)
	# cookie=0x0, duration=8309.936s, table=0, n_packets=1748622468, n_bytes=104917444080, idle_age=24, in_port=1 actions=output:2
	# cookie=0x0, duration=24917s, table=0, n_packets=2112658689, n_bytes=126759809020, idle_age=0, in_port=1 actions=output:2
	if ( $line =~ /\s*cookie=0x([0-9a-f]+),\sduration=([0-9.]+)s,\stable=(\d+),\sn_packets=(\d+),(\s\S+=\d+,)*\s(\S+)\s(\S+)/ and $stats_mode eq "bridge" and $stats_type eq "dump-flows") {
		my $cookie = $1;
		my $duration = $2;
		my $table = $3;
		my $n_packets = $4;
		my $extra = $5;
		my $match = $6;
		my $action = $7;
		$match =~ s/,/:/g;
		$action =~ s/,/_/g;
		my $flow_id = "t" . $table . ":" . $match . "::" . $action . "-packets";
		$flow_data{$bridge}{$flow_id}{$timestamp_ms} = $n_packets;
		next;
	}

	if ( $line =~ /OFPST_PORT/ or $line =~ /NXST_FLOW/ or $line =~ /OFPST_PORT/ or $line =~ /NXST_FLOW/ or $line =~ /dpdk-stats:\spmd-stats-show/ or
	     $line =~ /miss:/ or $line =~ /lost:/ or $line =~ /main\sthread:/ or $line =~ /OFPST_FLOW/ or $line =~ /^\s*duration=/ or $line eq "") {
		next;
	}
	print "found unrecognized line: $line\n";
}
close(TXT);


my %port_mappings;
#Load the mappings from port name to port number
for $bridge (sort keys %bridge_data) {
	my $bridge_port_mapping_file = $bridge . "-port-mappings.txt";
	open(TXT, "$dir/$bridge_port_mapping_file") || die "could not find $dir/$bridge_port_mapping_file\n";
	#file looks like:
	#
	#OFPT_FEATURES_REPLY (xid=0x2): dpid:00006805ca2ab7f3
	#n_tables:254, n_buffers:256
	#capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP
	#actions: output enqueue set_vlan_vid set_vlan_pcp strip_vlan mod_dl_src mod_dl_dst mod_nw_src mod_nw_dst mod_nw_tos mod_tp_src mod_tp_dst
	# 1(dpdk0): addr:68:05:ca:2a:b7:f3
	#     config:     0
	#     state:      0
	#     current:    10GB-FD
	#     speed: 10000 Mbps now, 0 Mbps max
	# LOCAL(phy-br-0): addr:68:05:ca:2a:b7:f3
	#     config:     0
	#     state:      0
	#     current:    10MB-FD COPPER
	#     speed: 10 Mbps now, 0 Mbps max
	#OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0
	while (my $line = <TXT>) {
		chomp $line;
		if ($line =~ /^\s(\S+)\((\S+)\):\saddr:/ ) {
			$port_mappings{$bridge}{$1} = $2;
			next;
		}
	}
	close TXT;
}

# generate the rates and an average for each stat
for $bridge (sort keys %bridge_data) {
	for $port (sort keys %{ $bridge_data{$bridge} }) {
		my $port_name;
		if ($port_mappings{$bridge}{$port}) {
			$port_name = $port_mappings{$bridge}{$port};
		} else {
			$port_name = $port;
		}
		my $stat;
		for $stat (sort keys %{ $bridge_data{$bridge}{$port} }) {
			my $last_timestamp_ms = 0;
			my $last_value;
			for $timestamp_ms (sort {$a<=>$b} keys %{ $bridge_data{$bridge}{$port}{$stat} } ) {
				my $value = $bridge_data{$bridge}{$port}{$stat}{$timestamp_ms};
				if ($last_timestamp_ms != 0) {
					my $duration = ($timestamp_ms - $last_timestamp_ms)/1000;
					my $value_diff = $value - $last_value;
					my $this_rate = $value_diff / $duration;
					if ( $stat =~ /fdb-entries/ ) {
						$rate{"openvswitch"}{$bridge . "-fdb-entries"}{$port_name . "-" . $stat}{$timestamp_ms} = $value;
					} elsif ( $stat =~ /bytes/ ) {
						$rate{"openvswitch"}{$bridge . "-bytes"}{$port_name . "-" . $stat}{$timestamp_ms} = $this_rate;
					} else {
						$rate{"openvswitch"}{$bridge . "-packets"}{$port_name . "-" . $stat}{$timestamp_ms} = $this_rate;
					}
				}
				$last_value = $value;
				$last_timestamp_ms = $timestamp_ms;
			}
		}
	}
}
# generate the rates and an average for each stat
for $bridge (sort keys %flow_data) {
	for my $flow (sort keys %{ $flow_data{$bridge} }) {
		my $last_timestamp_ms = 0;
		my $last_value;
		for $timestamp_ms (sort {$a<=>$b} keys %{ $flow_data{$bridge}{$flow} } ) {
			my $value = $flow_data{$bridge}{$flow}{$timestamp_ms};
			if ($last_timestamp_ms != 0) {
				my $duration = ($timestamp_ms - $last_timestamp_ms)/1000;
				my $value_diff = $value - $last_value;
				my $this_rate = $value_diff / $duration;
				$rate{"openvswitch"}{$bridge . "-flow-activity"}{$flow}{$timestamp_ms} = $this_rate;
			}
			$last_value = $value;
			$last_timestamp_ms = $timestamp_ms;
		}
	}
}
my %graph_type;
my %graph_threshold;
gen_data(\%rate, \%graph_type, \%graph_threshold, $dir);
