#!/usr/bin/perl

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 calc_efficiency_metrics
		convert_samples_hash_to_array create_graph_hash
		create_uid get_cpubusy_series get_label get_mean_hash);
use File::Basename;
use Data::Dumper;
use List::Util('sum');
use JSON;

sub mean {
	if ( scalar @_ > 0 ) {
		return sum(@_)/@_;
	} else {
		return 0;
	}
}

my $script = basename($0);
my $dir = $ARGV[0];
my $protocol = $ARGV[1];
my $message_size_bytes = int $ARGV[2];
my $instances = int $ARGV[3];
my $test_type = $ARGV[4];
my $clients = $ARGV[5];
my $servers = $ARGV[6];
my $tool_label_pattern = $ARGV[7];
my $tool_group = $ARGV[8];
my $benchmark_version = $ARGV[9];
my $benchmark_name = 'uperf';
my $series_trim = 3;
my $primary_metric;
if ($test_type =~ /rr/) {
	$primary_metric="trans_sec";
} else {
	$primary_metric="Gb_sec";
}
# %workload
#		   {Throughput|Latency|Resource|Efficiency}
#		   {Throughput}[Gb_sec, 

my %workload;  # root hash for all data, contains hash refs to
		# %throughput, %latency, %resource, %efficiency
my %parameters;	# a hash of parameter-type:array-of-parameter-values

my @benchmark;	# each array element contains a hash with paramters
		# for this benchmark:
		# message-size-bytes: 256
		# protocol: tcp
		# instances: 4
		# test-type: stream

my %resource;	# a hash of resource-type:array-of-resource-values,

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;
my %latency;    # a hash of throughput-type:array-of-throughput-values,
		# for example $throughput{Gb_sec[0..1]
my @usec;	# each array element contains a hash with:
	    	# hostname: <hostname or IP>
	    	# role: <client or server> <-uperf happens to always be client
	    	# timeseries: a hash of timstamp:value elements

my %throughput; # a hash of throughput-type:array-of-throughput-values,
		# for example $throughput{Gb_sec[0..1]
my @Gb_sec;	# each array element contains a hash with:
	    	# hostname: <hostname or IP>
	    	# role: <client or server> <-uperf happens to always be client
	    	# timeseries: a hash of timstamp:value key-pairs
my @trans_sec;	# each array element contains a hash with:
	    	# hostname: <hostname or IP>
	    	# role: <client or server> <-uperf happens to always be client
	    	# timeseries: a hash of timstamp:value key-pair

my %benchmark_parameters_dataset = ( 	get_label('benchmark_name_label') => $benchmark_name,
					get_label('benchmark_version_label') => $benchmark_version,
					get_label('test_type_label') => $test_type,
					get_label('protocol_label') => $protocol,
					get_label('message_size_bytes_label') => $message_size_bytes,
					get_label('instances_label') => $instances,
					get_label('clients_label') => $clients,
					get_label('servers_label') => $servers,
					get_label('primary_metric_label') => $primary_metric,
					get_label('uid_label') => create_uid('benchmark_name_label', 'controller_host_label')  );

push(@benchmark, \%benchmark_parameters_dataset);

# Get the tool data
# Define a set of tool directories which we want to use to report CPU and efficiency metrics
# Search for tool directories which match the $tool_label_pattern
my $this_tool_dir;
my %tool_ids;
my $tool_group_dir = "$dir/tools-$tool_group";
my $dh;
if (opendir($dh, $tool_group_dir)) {
	foreach $this_tool_dir (sort readdir($dh)) {
		if ($this_tool_dir =~ /^$tool_label_pattern/) {
			my $tool_dir_id = $this_tool_dir;
			$tool_dir_id =~ s/^$tool_label_pattern//;
			$this_tool_dir = $tool_group_dir . "/" . $this_tool_dir;
			$tool_ids{$this_tool_dir} = $tool_dir_id;
		}
	}
} else {
	print STDERR "$script: could not find any directories in $tool_group_dir which matched $tool_label_pattern\n";
}
closedir  $dh;
foreach $this_tool_dir (sort keys %tool_ids) {
	my $this_tool_id = $tool_ids{$this_tool_dir};
	my $role = "host";
	my $hostname;
	($role, $hostname) = split(/:/,$this_tool_id);
	my %cpubusy_samples;
	my $res = get_cpubusy_series($this_tool_dir, \%cpubusy_samples );
	if ( $res == 0 ) {
		my $description = "Total busy CPU usage, averaged over N seconds, where 1.0 = 1 logical CPU";
		push(@cpu_busy, {
			get_label('uid_label')         => create_uid('hostname_label'),
			get_label('description_label') => $description,
			get_label('role_label')        => $role,
			get_label('hostname_label')    => $hostname,
			get_label('value_label')       => get_mean_hash(\%cpubusy_samples),
			get_label('timeseries_label')  => \%cpubusy_samples
		});
	}
}
if ( @cpu_busy ) {
	$resource{'cpu_busy'} = \@cpu_busy;
}

# Load the data from uperf output and create throughput metrics
# There can be several result files, once for each server involved in this test
# These are organized by server IP:port.
opendir($dh, $dir) || die "$script: could not open directory $dir: $!\n";
my $nr_result_files=0;
my $result_file;
foreach $result_file (sort readdir($dh) ) {
	# for each result file, build this:
	# # {
	# # "hostname": "perf104", <-where this workload was running
	# # "role": "client", <-where this information came from: workload client or server
	# # "samples" : {1407890808: 7.75, 1407890809: 7.77}  <-time series data from workload output
	# # "average" : "7.76" <-the average of the time series data
	# # },
	# the server ip should be in the result file name, like "result-192.168.1.1.txt"
	if ( $result_file =~ /^client::(.+)-server::(.+):(\d+)--client_output\.txt$/) {
		my $client_hostname = $1;
		my $server_hostname = $2;
		my $server_port = "$3";
		my $role = "client";
		my %throughput_samples;
		my %latency_samples;
		open(IN_FILE, "$dir/$result_file") || die "$script: could not open file $dir/$result_file: $!\n";
		my %prev;
		my $timestamp_limit = 0;
		my $count = 0;
		while (<IN_FILE>) {
			my $line = "$_";
			chomp($line);

			# for each sample in the uperf result file, we get a number of bytes and a timestamp
			# example of a sample: "time:1395170796.1234 name:Txn2 nr_bytes:22037790720 nr_ops:43042560"
			if ( $line =~  /^timestamp_ms:(\d+)\.\d+\s+name:Txn2\s+nr_bytes:(\d+)\s+nr_ops:(\d+)/ ) {
				my $timestamp_ms = $1;
				my $bytes = $2;
				my $ops = $3;
				$count++;
				if ($count <= 2 and $series_trim > 0 or $count == 1) {
					# We don't record the first several data points:  we skip
					# the first because we need a reference against which to
					# calculate diffs; and, then we skip a configurable number
					# of seconds -- however, the time is relative to the
					# -second- data point, for historical reasons.  So, we set
					# the limit *twice*, once to skip the first data point,
					# and again once we have the second timestamp to calculate
					# from.  (However, if the configured limit is zero, then
					# we only skip the first data point.)
					$timestamp_limit = $timestamp_ms + $series_trim * 1000;
				}
				if ($timestamp_ms > $timestamp_limit) {
					my $timestamp_s_diff = ($timestamp_ms - $prev{'timestamp'})/1000;
					my $nr_bytes_diff = $bytes - $prev{'bytes'};
					my $nr_ops_diff = $ops - $prev{'ops'};
					# This is what we are really interested in: computing value/sec metrics like Gb/sec and ops/sec
					# We can do that once we have more than one sample
					if ($test_type =~ /rr/) {
						my $ops_sec = $nr_ops_diff /$timestamp_s_diff;
						my $trans_sec = $ops_sec /2;
						# for request-response, 1 transaction is two ops, a write and a read.
						# the latency is the full round trip for 1 transaction, which is also = ($instances / trans_sec)
						# if --log-response-times in pbench_uperf is used, the write and read latency can be computed (later).
						if ( $nr_ops_diff != 0) {
							$latency_samples{$timestamp_ms} = {
								get_label('date_label')  => int $timestamp_ms,
								get_label('value_label') =>
									$instances / $trans_sec * 1000000
							};
						}
						$throughput_samples{$timestamp_ms} = {
							get_label('date_label')  => int $timestamp_ms,
							get_label('value_label') => $trans_sec
						};
					} else {
						$throughput_samples{$timestamp_ms} = {
							get_label('date_label')  => int $timestamp_ms,
							get_label('value_label') =>
								($nr_bytes_diff * 8 / 1000000000 / $timestamp_s_diff)
						};
					}
				}
				$prev{'timestamp'} = $timestamp_ms;
				$prev{'ops'} = $ops;
				$prev{'bytes'} = $bytes;
			}
		}
		$nr_result_files++;
		close(IN_FILE);

		# Trim the last several seconds of data from the series.
		my $limit = $prev{'timestamp'} - $series_trim * 1000;
		foreach my $ts (sort {$b <=> $a} keys %latency_samples) {  # Reverse-order
			if ($ts <= $limit) {
				last;
			}
			delete $latency_samples{$ts};
		}

		if (%latency_samples) {
			my $description = "Average elapsed time spanning: client sending, server accepting/sending, and client receiving 1 message, over a 1 second window";
			my $mean = get_mean_hash(\%latency_samples);
			push(@usec, {
				get_label('description_label')     => $description,
				get_label('role_label')            => $role,
				get_label('client_hostname_label') => $client_hostname,
				get_label('server_hostname_label') => $server_hostname,
				get_label('server_port_label')     => "$server_port",
				get_label('value_label')           => $mean,
				get_label('timeseries_label')      => \%latency_samples,
				get_label('uid_label')             => create_uid(
					                                   'client_hostname_label',
					                                   'server_hostname_label',
					                                   'server_port_label')
			});
		}

		# Trim the last several seconds of data from the series.
		foreach my $ts (sort {$b <=> $a} keys %throughput_samples) {  # Reverse-order
			if ($ts < $limit) {
				last;
			}
			delete $throughput_samples{$ts};
		}

		if (%throughput_samples) {
			my %this_tput_dataset;
			my $mean = get_mean_hash(\%throughput_samples);
			%this_tput_dataset = (
				get_label('role_label')            => $role,
				get_label('client_hostname_label') => $client_hostname,
				get_label('server_hostname_label') => $server_hostname,
				get_label('server_port_label')     => "$server_port",
				get_label('value_label')           => $mean,
				get_label('timeseries_label')      => \%throughput_samples,
				get_label('uid_label')             => create_uid(
					                                   'client_hostname_label',
					                                   'server_hostname_label',
					                                   'server_port_label')
			);
			if ($test_type =~ /rr/) {
				$this_tput_dataset{get_label('description_label')} =
					"Number of transactions sent by client for a period of 1 second";
				push(@trans_sec, \%this_tput_dataset);
			} else {
				$this_tput_dataset{get_label('description_label')} =
					"Number of gigabits sent by client for a period of 1 second";
				push(@Gb_sec, \%this_tput_dataset);
			}
		}
	}
}
closedir $dh;
if ($nr_result_files == 0) {
	print STDERR "$script: could not find any result files to process, exiting\n";
	exit;
}
# construct what we have so far in a master workload hash
if ( @Gb_sec ) {
	$throughput{'Gb_sec'} = \@Gb_sec;
}
if ( @trans_sec ) {
	$throughput{'trans_sec'} = \@trans_sec;
}
if ( @usec ) {
	$latency{'usec'} = \@usec;
}
if ( @benchmark ) {
	$parameters{'benchmark'} = \@benchmark;
}
if ( %throughput ) {
	$workload{'throughput'} = \%throughput;
}
if ( %latency ) {
	$workload{'latency'} = \%latency;
}
if ( %resource ) {
	$workload{'resource'} = \%resource;
}
if ( %parameters ) {
	$workload{'parameters'} = \%parameters;
}

calc_aggregate_metrics(\%workload);
calc_efficiency_metrics(\%workload);
		
# pbnch graphing uses a specific hash heirarchy, where the dimentions of the hash are:
# {html-file}{html-graph}{graph-series}{timeseries-data}
# for example, an html page which has all of the Gb_sec metrics graphed may look like:
# %throuhgput{'uperf-throughput'}{'Gb-per-second'}{client:host1-to-server-host2}{$timestamp_ms}
#                                                 {client:host1-to-server-host3}{$timestamp_ms}
# if we wanted another graph for transactions per second graped in a different graph, but on the same html page, we would add:
# %throuhgput{'uperf-throughput'}{'transper-second'}{client:host1-to-server-host2}{$timestamp_ms}
#                                                   {client:host1-to-server-host3}{$timestamp_ms}
#
# So, for uperf, we need to take parts of the data in %workload and transform it in to a new hash
# The %workload hash has much more information about the workload than needed for graphing, 
# and the timestamp data is stored in a different way.  So, we just need to do a minor 
# conversion, provided by BenchPostprocess.pm

my %uperf_graph;
create_graph_hash(\%uperf_graph, \%workload);
my %graph_threshold;
my %graph_type;
gen_data(\%uperf_graph, \%graph_type, \%graph_threshold, $dir, 1);

# if there is a request-response test , and there is a log of the response times, process it.
my $all_server_write_avg_time;
my $all_server_read_avg_time;
my $all_server_nr_reads = 0;
my $all_server_nr_writes = 0;
my @all_server_transaction_times;
my $nr_resp_files = 0;
opendir($dh, $dir) || die "$script: can't open directory $dir: $!\n";
foreach my $resp_file (sort readdir($dh) ) {
	if ( $resp_file =~ /^client::(.+)-server::(.+):(\d+)--response-times\.txt$/) {
		open(RESP_FILE, "$dir/$resp_file") || die "$script: can't open file $dir/$resp_file: $!\n";
		#  This response file contains an elapsed time value for each "flow op" in the 5th column:
		#  35609 140172276365056  0 1408072771750861084           42765
		#  For request-response, 2 lines contain the two values: first the request (a write) and next a response (a read).
		#  The write is usually very fast, as it's just the write to the socket and should return almost immediately.
		#  The read is the time for the packet to be sent out, responded by the other system, sent back, and read by the
		#  uperf process.
		my $line = <RESP_FILE>; # this first value is the time it takes to open the stream
		# the following lines will have a pattern of write (send) then read (receive)
		my $time_us;
		my $time_ns;
		my $read_time;
		my $write_time;
		my @this_server_transaction_times;
		my $this_server_write_avg_time;
		my $this_server_read_avg_time;
		my $this_server_nr_reads;
		my $this_server_nr_writes;
		my $resp_type = "write"; # the remaining values are times alternating between two flow ops: a write (send a request) and a read (wait for response)
		while ($line = (<RESP_FILE>)) {
			#  35609 140172276365056  0 1408072771750861084           42765
			$line =~ /\s*\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/;
			$time_ns = $1;
			$time_us = $time_ns / 1000;
			if ($resp_type eq "write") {
				$write_time = $time_us;
				$all_server_write_avg_time += $write_time;
				$this_server_write_avg_time += $write_time;
				$all_server_nr_writes++;
				$this_server_nr_writes++;
				# the next value will be for a read
				$resp_type = "read";
				next;
			}
			if ($resp_type eq "read") {
				$read_time = $time_us;
				$all_server_read_avg_time += $read_time;
				$this_server_read_avg_time += $read_time;
				$all_server_nr_reads++;
				$this_server_nr_reads++;
				push(@this_server_transaction_times, ($write_time + $read_time));
				push(@all_server_transaction_times, ($write_time + $read_time));
				# the next value will be for a write
				$resp_type = "write";
				next;
			}
		}
		close RESP_FILE;
		$nr_resp_files++;
		@this_server_transaction_times = sort {$a <=> $b} @this_server_transaction_times;
		$this_server_read_avg_time /= $this_server_nr_reads;
		$this_server_write_avg_time /= $this_server_nr_writes;
		printf "resp-file: $resp_file\n";
		printf "average latency: %.4f\n", ($this_server_read_avg_time + $this_server_write_avg_time);
		printf "95th percentile latency: %.4f\n", $this_server_transaction_times[sprintf("%.0f",(0.95*($#this_server_transaction_times)))];
		printf "99.995th percentile latency: %.4f\n", $this_server_transaction_times[sprintf("%.0f",(0.99995*($#this_server_transaction_times)))];
		printf "maximum latency: %.4f\n", $this_server_transaction_times[$#this_server_transaction_times];
	}
}
closedir $dh;
if ($nr_resp_files > 0) {
	@all_server_transaction_times = sort {$a <=> $b} @all_server_transaction_times;
	$all_server_write_avg_time /= $all_server_nr_writes;
	$all_server_read_avg_time /= $all_server_nr_reads;
	my $filename = $dir . "/summary-response-times.txt";
	open(RESP_SUMMARY, ">$filename") || die "$script: could not open $filename\n";
	printf RESP_SUMMARY "Number of writes: %d  Average response time for writes is: %.4fus\n", $all_server_nr_writes, $all_server_write_avg_time;
	printf RESP_SUMMARY "Number of reads: %d  Average response time for reads is: %.4fus\n", $all_server_nr_reads, $all_server_read_avg_time;
	printf RESP_SUMMARY "Average transaction latency: %.4fus\n", ($all_server_read_avg_time + $all_server_write_avg_time);
	printf RESP_SUMMARY "95th percentile transaction latency: %.4fus\n", $all_server_transaction_times[sprintf("%.0f",(0.95*($#all_server_transaction_times)))];
	printf RESP_SUMMARY "99.995th percentile transaction latency: %.4fus\n", $all_server_transaction_times[sprintf("%.0f",(0.99995*($#all_server_transaction_times)))];
	printf RESP_SUMMARY "Maximum transaction latency: %.4fus\n", $all_server_transaction_times[$#all_server_transaction_times];
	close(RESP_SUMMARY);
}

convert_samples_hash_to_array(\%workload);

# write to JSON format file
my $json_file = $dir . "/result.json";
my $json_text   = to_json( \%workload, { ascii => 1, pretty => 1, canonical => 1 } );
open(JSON, ">$json_file") || die "$script: could not open file $json_file: $!\n";
print JSON $json_text;
close(JSON);
