#!/usr/bin/perl
# -*- mode: perl; indent-tabs-mode: t; perl-indent-level: 8 -*-

#
## Author: Andrew Theurer
# Post-process a result sample for pbench-trafficgen benchmark
# Currently this script will only find the total average rate of received packets per second
# Later we will add latency data as well as tool and efficiency metrics

use strict;
use warnings;

use lib $ENV{'pbench_lib_dir'};
use lib $ENV{'pbench_bspp_dir'};
no lib ".";
use GenData qw(gen_data);
use BenchPostprocess qw(
	calc_aggregate_metrics create_graph_hash create_uid
	convert_samples_hash_to_array get_label get_mean_hash);
use File::Basename;
use JSON;

my $script_name = basename($0);

# This script produces a JSON format of a benchmark iteration sample,
# and uses the standard metric types for pbench, which are:
# %workload{parameters,throughput|latency|resource|efficiency}
# The result is a "result.json" file with a JSON array, "sample = {...}"
#
# This is the data layout used in perl to construct the JSON array:
#
my %workload;	# root hash for all data, contains hash refs to
				# %paramters, %throughput, %latency, %resource, %efficiency
		
my %parameters;	# a hash of parameter-type:parameter-value that was
				# $parameters{benchmark[0].rate}
my @benchmark;	# each array element contains a hash with:
				# benchmark_name:
				# :
				# role:
				# description:

my %resource;	# a hash of resource-type:array-of-resource-values,
				# for example $resource{cpu_busy[0..1]}
my @cpu_busy;	# each array element contains a hash with
				# hostname: hostname or IP
				# role: client, server, host, kvm-host, container-host, etc.
				# timeseries: a hash of timestamp:value key-pairs

my %efficiency;	# a hash of throughput-per-resource-type:array-of-throughput-per-resource-values
				# for example $efficincy{Mframes_cpu[0..1]}
my @Mframes_cpu;# each array element contains a hash with:
				# hostname:
				# port_id:
				# role:
				# description:
				# timeseries: a hash of timestamp,value elements

my %latency;	# a hash of latency-type:array-of-latency-values,
				# for example $latency{usec[0..1]}
				#
				# each array element contains a hash with:
				# hostname:
				# port_id:
				# role:
				# description:
my @usec_avg;
my @usec_min;
my @usec_max;


my %throughput;	# a hash of throughput-type:array-of-throughput-values,
				# for example $throughput{Mpps[0..1]

my @rx_Mpps;	#
my @tx_Mpps;	# each array element contains a hash with:
				# hostname:
				# port_id:
				# role: packet-generator
				# value: the number value for millions of pckets (frames) per second

my @rx_L1_Gbps; #
my @tx_L1_Gbps; # each array element contains a hash with:
				# hostname:
				# port_id:
				# role: packet-generator
				# the number value for 10^9 bits per second for physical Ethernet (layer 1, including CRC and padding)

my @rx_L2_Gbps; #
my @tx_L2_Gbps; # each array element contains a hash with:
				# hostname:
				# port_id:
				# role: packet-generator
				# the number value for 10^9 bits per second for physical Ethernet (layer 1, including CRC and padding)

my $script = basename($0);
my $dir = $ARGV[0];
my $controller_host = $ARGV[1];
my $rate_arg = $ARGV[2];
my $frame_size_arg = $ARGV[3];
my $num_flows_arg = $ARGV[4];
my $traffic_profile_arg = $ARGV[5];
my $benchmark_version = $ARGV[6];

my $result_file = $dir . "/binary-search.json";
if (! -e $result_file) {
	opendir(my $clients_dh, $dir . '/clients') ||
		die "$script_name: could not open directory clients: $!\n";
	my @client_hostname_dirs = grep(!/^\./, (sort readdir($clients_dh)));
	closedir $clients_dh;
	for my $client_hostname (@client_hostname_dirs) {
		$result_file = "$dir/clients/$client_hostname/binary-search.json";
		if (-e $result_file) {
			last;
		} else {
			$result_file = '';
		}
	}
	if ($result_file eq '') {
		die "$script_name: could not find binary-search.json\n";
	}
}

my $json_file = $dir . "/result.json";
my $benchmark_name = 'trafficgen';
my $primary_metric="rx_mpps";
my $date_label = get_label('date_label');
my $value_label = get_label('value_label');
my %config;

my %benchmark_parameters_dataset = (
	get_label('controller_host_label')   => $controller_host,
	get_label('benchmark_name_label')    => $benchmark_name,
	get_label('benchmark_version_label') => $benchmark_version,
	get_label('primary_metric_label')    => $primary_metric,
	get_label('uid_label')               => create_uid(
		                                         'benchmark_name_label',
		                                         'controller_host_label'));

push(@benchmark, \%benchmark_parameters_dataset);

my @expected_metrics = (
	{ 'js_label'       => 'rx_latency_maximum',
		'metric_label' => 'latency_max',
		'tx_or_rx'     => 'rx',
		'metric_class' => 'latency',
		'desc'         => 'Time in microseconds between packet transmission and reception',
		'divisor'      => int 1
	},
	{ 'js_label'       => 'rx_latency_average',
		'metric_label' => 'latency_avg',
		'tx_or_rx'     => 'rx',
		'metric_class' => 'latency',
		'desc'         => 'Time in microseconds between packet transmission and reception',
		'divisor'      => int 1
	},
	{ 'js_label'       => 'tx_l2_bps',
		'metric_label' => 'tx_L2_Gbps',
		'tx_or_rx'     => 'tx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of layer-2 10^9 bits transmitted per second',
		'divisor'      => int 1000000000
	},
	{ 'js_label'       => 'rx_l1_bps',
		'metric_label' => 'tx_L1_Gbps',
		'tx_or_rx'     => 'tx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of layer-1 10^9 bits trasmitted per second',
		'divisor'      => int 1000000000
	},
	{ 'js_label'       => 'rx_l2_bps',
		'metric_label' => 'rx_L2_Gbps',
		'tx_or_rx'     => 'rx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of layer-2 10^9 bits received per second',
		'divisor'      => int 1000000000
	},
	{ 'js_label'       => 'rx_l1_bps',
		'metric_label' => 'rx_L1_Gbps',
		'tx_or_rx'     => 'rx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of layer-1 10^9 bits received per second',
		'divisor'      => int 1000000000
	},
	{ 'js_label'       => 'rx_pps',
		'metric_label' => 'rx_Mpps',
		'tx_or_rx'     => 'rx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of 10^6 packets (Ethernet frames) received per second',
		'divisor'      => int 1000000
	},
	{ 'js_label'       => 'tx_pps',
		'metric_label' => 'tx_Mpps',
		'tx_or_rx'     => 'tx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of 10^6 packets (Ethernet frames) transmitted per second',
		'divisor'      => int 1000000
	},
	{ 'js_label'       => 'rx_lost_pps',
		'metric_label' => 'rx_lost_pps',
		'tx_or_rx'     => 'rx',
		'metric_class' => 'throughput',
		'desc'         => 'The number of packets (Ethernet frames) lost (dropped) per second',
		'divisor'      => int 1
	}
);

# Load the data from result.txt and create throughput metrics
open( TXT, "<$result_file" ) or die "Can't open $result_file $!";
my $json_text = "";
while ( <TXT> ) {
	$json_text = $json_text . $_;
}
close TXT;
my $json_result = from_json($json_text);
$json_text = "";  # Release this huge string to relieve my IDE's debugger
my %last_trial = %{ $$json_result{'trials'}[-1] };

foreach my $trial_param ( keys %{ $last_trial{'trial_params'} } ) {
	if ( $trial_param eq 'output_dir' ) {
		# The output_dir parameter is different per sample; we explicitly omit it
		# so that when process-iteration-samples runs it will properly merge the
		# same sample data sets together.
		next;
	}
	if (defined($last_trial{'trial_params'}{$trial_param})) {
		$config{$trial_param} = $last_trial{'trial_params'}{$trial_param}
	}
}

my $uid_label = sprintf("%s-%s", $benchmark_name, $benchmark_version);
if (exists($config{'traffic_generator'}) && defined($config{'traffic_generator'})) {
	my $traffic_generator = $config{'traffic_generator'};
	if ($traffic_generator eq "trex-txrx-profile" and $traffic_profile_arg ne "") {
		$config{'traffic_profile_arg'} = $traffic_profile_arg;
		$uid_label .= "-traffic_profile:%traffic_profile_arg%";
	} else {
		$config{'rate_arg'} = $rate_arg;
		$uid_label .= "-rate:%rate_arg%";
		$uid_label .= "-traffic_direction:";
		if (exists($config{'traffic_direction'}) && defined($config{'traffic_direction'})) {
			$uid_label .= "%traffic_direction%";
		} else {
			$uid_label .= "MISSING";
		}
		$config{'frame_size'} = int $frame_size_arg;
		$uid_label .= "-frame_size:%frame_size%";
		$config{'num_flows'} = int $num_flows_arg;
		$uid_label .= "-num_flows:%num_flows%";
	}
} else {
	$uid_label .= "-rate:MISSING";
	$uid_label .= "-traffic_direction:MISSING";
	$uid_label .= "-frame_size:MISSING";
	$uid_label .= "-num_flows:MISSING";
}
$uid_label .= "-pct_drop:";
if (exists($config{'max_loss_pct'}) && defined($config{'max_loss_pct'})) {
	$uid_label .= "%max_loss_pct%";
} else {
	$uid_label .= "MISSING";
}
$config{get_label('uid_label')} = $uid_label;

push(@benchmark, \%config);

foreach my $dev_pair ( @{ $last_trial{'trial_params'}{'test_dev_pairs'} } ) {
	#"dev_pair": "0:1",
	#"direction": "0->1",
	#"rx": 1,
	#"tx": 0

	# stats from binary-search:
	# rx_packets, rx_latency_pps, rx_latency_lost_packets_pct, rx_latency_maximum, rx_pps, rx_latency_average,
	# rx_latency_bandwidth, tx_pps_target, rx_lost_packets, tx_tolerance_min, rx_lost_pps, tx_tolerance_max,
	# tx_pps, tx_latency_pps, tx_l1_Gbps, tx_l2_Gbps, tx_packets, rx_latency_lost_pps, rx_latency_lost_packets,
	# rx_lost_packets_pct, tx_latency_bandwidth, tx_latency_packets, rx_latency_packets, rx_l1_Gbps, rx_l2_Gbps
	for my $expected_metric ( @expected_metrics ) {
		my %metric;
		$metric{'uid'} = "tx_port:%tx_port%-rx_port:%rx_port%";
		$metric{'tx_port'} = int $$dev_pair{'tx'};
		$metric{'rx_port'} = int $$dev_pair{'rx'};
		if (defined scalar $last_trial{'stats'}{$$dev_pair{$$expected_metric{'tx_or_rx'}}}{$$expected_metric{'js_label'}}) {
			$metric{'value'} = $last_trial{'stats'}{$$dev_pair{$$expected_metric{'tx_or_rx'}}}{$$expected_metric{'js_label'}} / $$expected_metric{'divisor'};
			$metric{'description'} = $$expected_metric{'desc'};
			if ($$expected_metric{'metric_class'} eq 'throughput') {
				push(@{ $throughput{$$expected_metric{'metric_label'}} }, \%metric);
			} elsif ($$expected_metric{'metric_class'} eq 'latency') {
				push(@{ $latency{$$expected_metric{'metric_label'}} }, \%metric);
			} else {
				print "Error: the metric class, $$expected_metric{'metric_class'}, is not valid\n";
			}
		}
	}
}

if (exists($last_trial{'profiler-data'}) && defined($last_trial{'profiler-data'})) {
	my @profiler_metrics = (
		{ 'category'       => 'global',
			'js_label'     => 'Global PPS',
			'field'        => 'pps',
			'metric_label' => 'rx',
			'group'        => 'rx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of packets (Ethernet frames) received per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global PPS',
			'field'        => 'pps',
			'metric_label' => 'tx',
			'group'        => 'tx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of packets (Ethernet frames) transmitted per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global BPS',
			'field'        => 'bps',
			'metric_label' => 'rx',
			'group'        => 'rx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of bits received per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global BPS',
			'field'        => 'bps',
			'metric_label' => 'tx',
			'group'        => 'tx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of bits transmitted per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global BPS',
			'field'        => 'drop_bps',
			'metric_label' => 'drop',
			'group'        => 'rx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of bits dropped per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global CPU Utilization',
			'field'        => 'cpu_util',
			'metric_label' => 'TX CPU',
			'group'        => 'misc',
			'metric_class' => 'resource',
			'desc'         => 'The CPU utilization % for the TX cores',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global CPU Utilization',
			'field'        => 'cpu_util',
			'metric_label' => 'RX CPU',
			'group'        => 'rx',
			'metric_class' => 'resource',
			'desc'         => 'The CPU utilization % for the RX core',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global Per Core Bandwidth',
			'field'        => 'bw_per_core',
			'metric_label' => 'Per Core Bandwidth',
			'group'        => 'misc',
			'metric_class' => 'throughput',
			'desc'         => 'The bandwidth per core',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'global',
			'js_label'     => 'Global Queue Full',
			'field'        => 'queue_full',
			'metric_label' => 'Queue Full Count',
			'group'        => 'misc',
			'metric_class' => 'throughput',
			'desc'         => 'The number of times a queue was full when a packet enqueue was attempted',
			'divisor'      => int 1, 'cumulative' => 1, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface PPS - Port ',
			'field'        => 'pps',
			'metric_label' => 'rx',
			'group'        => 'rx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of packets (Ethernet frames) received per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface PPS - Port ',
			'field'        => 'pps',
			'metric_label' => 'tx',
			'group'        => 'tx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of packets (Ethernet frames) transmitted per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface BPS - Port ',
			'field'        => 'bps_l1',
			'metric_label' => 'L1 rx',
			'group'        => 'rx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of L1 bits received per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface BPS - Port ',
			'field'        => 'bps_l1',
			'metric_label' => 'L1 tx',
			'group'        => 'tx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of L1 bits transmitted per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface BPS - Port ',
			'field'        => 'bps',
			'metric_label' => 'L2 rx',
			'group'        => 'rx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of L2 bits received per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface BPS - Port ',
			'field'        => 'bps',
			'metric_label' => 'L2 tx',
			'group'        => 'tx',
			'metric_class' => 'throughput',
			'desc'         => 'The number of L2 bits transmitted per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface Utilization - Port ',
			'field'        => 'util',
			'metric_label' => 'tx',
			'group'        => 'tx',
			'metric_class' => 'resource',
			'desc'         => 'The TX port utilization',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'ports',
			'js_label'     => 'Interface Utilization - Port ',
			'field'        => 'util',
			'metric_label' => 'rx',
			'group'        => 'rx',
			'metric_class' => 'resource',
			'desc'         => 'The RX port utilization',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream PPS - ',
			'field'        => '',
			'metric_label' => 'rx',
			'group'        => 'rx_pps',
			'metric_class' => 'throughput',
			'desc'         => 'The number of bits received per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream PPS - ',
			'field'        => '',
			'metric_label' => 'tx',
			'group'        => 'tx_pps',
			'metric_class' => 'throughput',
			'desc'         => 'The number of bits transmitted per second',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency - PGID ',
			'field'        => 'average',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Average time in microseconds between packet transmission and reception',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency - PGID ',
			'field'        => 'total_max',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Maximum time in microseconds between packet transmission and reception',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency - PGID ',
			'field'        => 'total_min',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Minimum time in microseconds between packet transmission and reception',
			'divisor'      => int 1, 'cumulative' => 0, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency Errors - PGID ',
			'field'        => 'duplicate',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Number of duplicate packets detected',
			'divisor'      => int 1, 'cumulative' => 1, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency Errors - PGID ',
			'field'        => 'dropped',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Number of dropped packets detected',
			'divisor'      => int 1, 'cumulative' => 1, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency Errors - PGID ',
			'field'        => 'out_of_order',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Number of packets received in the wrong order (by sequence number)',
			'divisor'      => int 1, 'cumulative' => 1, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency Errors - PGID ',
			'field'        => 'seq_too_high',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Number of packets received before they were expected (by sequence number)',
			'divisor'      => int 1, 'cumulative' => 1, 'last' => 0
		},
		{ 'category'       => 'pgids',
			'js_label'     => 'Stream Latency Errors - PGID ',
			'field'        => 'seq_too_low',
			'metric_label' => '',
			'group'        => 'latency',
			'metric_class' => 'latency',
			'desc'         => 'Number of packets received after they were expected (by sequence number)',
			'divisor'      => int 1, 'cumulative' => 1, 'last' => 0
		},
	);

	my %trex_ports;
	my %trex_pgids;
	my $tsdelta_present = 0;

	foreach my $timestamp_ms (sort (keys %{ $last_trial{'profiler-data'} } ) ) {
		foreach my $port (keys %{ $last_trial{'profiler-data'}{$timestamp_ms}{'ports'} }) {
			$trex_ports{$port} = 1;
		}

		foreach my $pgid (keys %{ $last_trial{'profiler-data'}{$timestamp_ms}{'pgids'} }) {
			$trex_pgids{$pgid} = 1;
		}

		if (exists($last_trial{'profiler-data'}{$timestamp_ms}{'tsdelta'})) {
			$tsdelta_present = 1;
		}
	}

	if ($tsdelta_present) {
		my %samples;
		foreach my $timestamp_ms (keys %{ $last_trial{'profiler-data'} } ) {
			printf STDERR ("[WARNING] %s - %s:  timestamp %s found multiple times, previous samples overwritten\n",
				$controller_host, $json_file, $timestamp_ms) if exists($samples{$timestamp_ms});

			my $value = $last_trial{'profiler-data'}{$timestamp_ms}{'tsdelta'};
			$samples{$timestamp_ms} = {
				$date_label  => int $timestamp_ms,
				$value_label => $value
			};
		}

		if (%samples) {
			my $mean = get_mean_hash(\%samples);
			push(@{ $latency{"Profiler data sample collection latency"}}, {
				get_label('skip_aggregate_label') => 1,
				$value_label                      => $mean,
				get_label('timeseries_label')     => \%samples,
				get_label('description_label')    => "Time (in milliseconds) to collect a profiler data sample",
				get_label('uid_label')            => 'tsdelta'
			});
		}
	}

	for my $profiler_metric ( @profiler_metrics ) {
		if ($$profiler_metric{'category'} eq 'global') {
			my %samples;

			$$profiler_metric{'last'} = 0;
			foreach my $timestamp_ms (sort (keys %{ $last_trial{'profiler-data'} } ) ) {
				my $value = $last_trial{'profiler-data'}{$timestamp_ms}{'global'}{$$profiler_metric{'group'}}{$$profiler_metric{'field'}};

				if ($$profiler_metric{'cumulative'}) {
					my $tmp = $value;
					$value = $value - $$profiler_metric{'last'};
					$$profiler_metric{'last'} = $tmp;
				}

				$samples{$timestamp_ms} = {
					$date_label  => int $timestamp_ms,
					$value_label => $value
				};
			}

			if (%samples) {
				my %this_dataset;
				my $mean = get_mean_hash(\%samples);
				%this_dataset = (
					get_label('skip_aggregate_label') => 1,
					$value_label                      => $mean,
					get_label('timeseries_label')     => \%samples,
					get_label('description_label')    => $$profiler_metric{'desc'},
					'category'                        => $$profiler_metric{'category'},
					'group'                           => $$profiler_metric{'group'},
					'field'                           => $$profiler_metric{'field'},
					get_label('uid_label')            =>
						'category:%category%-group:%group%-field:%field%'
				);

				my $dataset_label = $$profiler_metric{'js_label'};
				if ($$profiler_metric{'metric_class'} eq 'throughput') {
					push(@{ $throughput{$dataset_label}}, \%this_dataset);
				} elsif ($$profiler_metric{'metric_class'} eq 'resource') {
					push(@{ $resource{$dataset_label}}, \%this_dataset);
				} elsif ($$profiler_metric{'metric_class'} eq 'latency') {
					push(@{ $latency{$dataset_label}}, \%this_dataset);
				}
			}
		} elsif ($$profiler_metric{'category'} eq 'ports') {
			foreach my $port (sort (keys %trex_ports)) {
				my %samples;

				$$profiler_metric{'last'} = 0;
				foreach my $timestamp_ms (sort (keys %{ $last_trial{'profiler-data'} } ) ) {
					my $value = $last_trial{'profiler-data'}{$timestamp_ms}{'ports'}{$port}{$$profiler_metric{'group'}}{$$profiler_metric{'field'}};

					if ($$profiler_metric{'cumulative'}) {
						my $tmp = $value;
						$value = $value - $$profiler_metric{'last'};
						$$profiler_metric{'last'} = $tmp;
					}

					$samples{$timestamp_ms} = {
						$date_label  => int $timestamp_ms,
						$value_label => $value
					};
				}

				if (%samples) {
					my %this_dataset;
					my $mean = get_mean_hash(\%samples);
					%this_dataset = (
						get_label('skip_aggregate_label') => 1,
						$value_label                      => $mean,
						get_label('timeseries_label')     => \%samples,
						get_label('description_label')    => $$profiler_metric{'desc'},
						'category'                        => $$profiler_metric{'category'},
						'group'                           => $$profiler_metric{'group'},
						'field'                           => $$profiler_metric{'field'},
						'port'                            => $port,
						get_label('uid_label')            =>
							"category:%category%-port:%port%-group:%group%-field:%field%"
					);

					my $dataset_label = $$profiler_metric{'js_label'} . $port;
					if ($$profiler_metric{'metric_class'} eq 'throughput') {
						push(@{ $throughput{$dataset_label}}, \%this_dataset);
					} elsif ($$profiler_metric{'metric_class'} eq 'resource') {
						push(@{ $resource{$dataset_label}}, \%this_dataset);
					} elsif ($$profiler_metric{'metric_class'} eq 'latency') {
						push(@{ $latency{$dataset_label}}, \%this_dataset);
					}
				}
			}
		} elsif ($$profiler_metric{'category'} eq 'pgids') {
			foreach my $pgid (sort (keys %trex_pgids)) {
				if ($$profiler_metric{'group'} eq 'latency') {
					my %samples;
					my $valid = 0;

					$$profiler_metric{'last'} = 0;
					foreach my $timestamp_ms (sort (keys %{ $last_trial{'profiler-data'} } ) ) {
						my $value;
						if (! exists($last_trial{'profiler-data'}{$timestamp_ms}{'pgids'}{$pgid}{$$profiler_metric{'group'}}{$$profiler_metric{'field'}})) {
							$value = 0;
						} else {
							$value = $last_trial{'profiler-data'}{$timestamp_ms}{'pgids'}{$pgid}{$$profiler_metric{'group'}}{$$profiler_metric{'field'}};
							$valid = 1;
						}

						if ($$profiler_metric{'cumulative'}) {
							my $tmp = $value;
							$value = $value - $$profiler_metric{'last'};
							$$profiler_metric{'last'} = $tmp;
						}

						$samples{$timestamp_ms} = {
							$date_label  => int $timestamp_ms,
							$value_label => $value
						};
					}

					if ($valid && %samples) {
						my %this_dataset;
						my $mean = get_mean_hash(\%samples);
						%this_dataset = (
							get_label('skip_aggregate_label') => 1,
							$value_label                      => $mean,
							get_label('timeseries_label')     => \%samples,
							get_label('description_label')    => $$profiler_metric{'desc'},
							'category'                        => $$profiler_metric{'category'},
							'group'                           => $$profiler_metric{'group'},
							'pgid'                            => $pgid,
							'field'                           => $$profiler_metric{'field'},
							get_label('uid_label')            =>
								"category:%category%-pgid:%pgid%-group:%group%-field:%field%"
						);

						my $dataset_label = $$profiler_metric{'js_label'} . $pgid;
						if ($$profiler_metric{'metric_class'} eq 'throughput') {
							push(@{ $throughput{$dataset_label}}, \%this_dataset);
						} elsif ($$profiler_metric{'metric_class'} eq 'resource') {
							push(@{ $resource{$dataset_label}}, \%this_dataset);
						} elsif ($$profiler_metric{'metric_class'} eq 'latency') {
							push(@{ $latency{$dataset_label}}, \%this_dataset);
						}
					}
				} else {
					foreach my $port (sort (keys %trex_ports)) {
						my %samples;

						$$profiler_metric{'last'} = 0;
						foreach my $timestamp_ms (sort (keys %{ $last_trial{'profiler-data'} } ) ) {
							my $value;
							if (! exists($last_trial{'profiler-data'}{$timestamp_ms}{'pgids'}{$pgid}{$$profiler_metric{'group'}}{$port})) {
								$value = 0;
							} else {
								$value = $last_trial{'profiler-data'}{$timestamp_ms}{'pgids'}{$pgid}{$$profiler_metric{'group'}}{$port};
							}

							if ($$profiler_metric{'cumulative'}) {
								my $tmp = $value;
								$value = $value - $$profiler_metric{'last'};
								$$profiler_metric{'last'} = $tmp;
							}

							$samples{$timestamp_ms} = {
								$date_label  => int $timestamp_ms,
								$value_label => $value
							};
						}

						if (%samples) {
							my %this_dataset;
							my $mean = get_mean_hash(\%samples);
							%this_dataset = (
								get_label('skip_aggregate_label') => 1,
								$value_label                      => $mean,
								get_label('timeseries_label')     => \%samples,
								get_label('description_label')    => $$profiler_metric{'desc'},
								'category'                        => $$profiler_metric{'category'},
								'group'                           => $$profiler_metric{'group'},
								'port'                            => $port,
								'pgid'                            => $pgid,
								get_label('uid_label')            =>
									"category:%category%-pgid:%pgid%-group:%group%-port:%port%"
							);
							my $pgid_label = $$profiler_metric{'js_label'} .
								'PGID ' . $pgid;
							my $port_label = $$profiler_metric{'js_label'} .
								'Port ' . $port . ' ' . uc($$profiler_metric{'metric_label'});
							if ($$profiler_metric{'metric_class'} eq 'throughput') {
								push(@{ $throughput{$pgid_label}}, \%this_dataset);
								push(@{ $throughput{$port_label}}, \%this_dataset);
							} elsif ($$profiler_metric{'metric_class'} eq 'resource') {
								push(@{ $resource{$pgid_label}}, \%this_dataset);
								push(@{ $resource{$port_label}}, \%this_dataset);
							} elsif ($$profiler_metric{'metric_class'} eq 'latency') {
								push(@{ $latency{$pgid_label}}, \%this_dataset);
								push(@{ $latency{$port_label}}, \%this_dataset);
							}
						}
					}
				}
			}
		}
	}
}

if ( %throughput ) {
	$workload{'throughput'} = \%throughput;
}
if ( %latency ) {
	$workload{'latency'} = \%latency;
}
calc_aggregate_metrics(\%workload);
# parameters
if ( @benchmark ) {
	$parameters{'benchmark'} = \@benchmark;
}
if ( %parameters ) {
	$workload{'parameters'} = \%parameters;
}

# efficiency
if ( @Mframes_cpu ) {
	$efficiency{'Mpps_cpu'} = \@Mframes_cpu;
}
if ( %efficiency ) {
	$workload{'efficiency'} = \%efficiency;
}

# latency
if ( @usec_avg ) {
	$latency{'usec_avg'} = \@usec_avg;
}
if ( @usec_min ) {
	$latency{'usec_min'} = \@usec_min;
}
if ( @usec_max ) {
	$latency{'usec_max'} = \@usec_max;
}

# resource
if ( @cpu_busy ) {
	$resource{'cpu_busy'} = \@cpu_busy;
}
if ( %resource ) {
	$workload{'resource'} = \%resource;
}

my %graph;
create_graph_hash(\%graph, \%workload);
my %graph_threshold;
my %graph_type;
foreach my $key (keys %{$graph{'trafficgen'}}) {
	if ($key =~ m/^Stream PPS - Port/) {
		$graph_type{'trafficgen'}{$key} = "stackedAreaChart";
		$graph_threshold{'trafficgen'}{$key} = 0.0001;
	} elsif ($key =~ m/^Stream PPS - PGID/) {
		$graph_threshold{'trafficgen'}{$key} = 0.0001;
	}
}
gen_data(\%graph, \%graph_type, \%graph_threshold, $dir, 1);

convert_samples_hash_to_array(\%workload);

$json_text = to_json( \%workload, { ascii => 1, pretty => 1 } );
open(JSON, ">$json_file") || die "$script: could not open file $json_file: $!\n";
print JSON $json_text;
close(JSON);
