#!/usr/bin/perl

# Copyright (C) 2012 Christian Babeux <christian.babeux@efficios.com>
#
# SPDX-License-Identifier: GPL-2.0-only
#

use strict;
use warnings;

use Getopt::Long;

my $opt_tracepoint;

GetOptions('tracepoint=s' => \$opt_tracepoint)
	or die("Invalid command-line option\n");

defined($opt_tracepoint)
	or die("Missing tracepoint, use --tracepoint <name>");

# Parse an array string.
# The format is as follow: [ [index] = value, ... ]
sub parse_array
{
	my ($arr_str) = @_;
	my @array = ();

	# Strip leading and ending brackets, remove whitespace
	$arr_str =~ s/^\[//;
	$arr_str =~ s/\]$//;
	$arr_str =~ s/\s//g;

	my @entries = split(',', $arr_str);

	foreach my $entry (@entries) {
		if ($entry =~ /^\[(\d+)\]=(\d+)$/) {
			my $index = $1;
			my $value = $2;
			splice @array, $index, 0, $value;
		}
	}

	return \@array;
}

# Parse fields values.
# Format can either be a name = array or a name = value pair.
sub parse_fields
{
	my ($fields_str) = @_;
	my %fields_hash;

	my $field_name = '[\w\d_]+';
	my $field_value = '[\w\d_\\\*"]+';
	my $array = '\[(?:\s\[\d+\]\s=\s\d+,)*\s\[\d+\]\s=\s\d+\s\]';

	# Split the various fields
	my @fields = ($fields_str =~ /$field_name\s=\s(?:$array|$field_value)/g);

	foreach my $field (@fields) {
		if ($field =~ /($field_name)\s=\s($array)/) {
			my $name  = $1;
			my $value = parse_array($2);
			$fields_hash{$name} = $value;
		}

		if ($field =~ /($field_name)\s=\s($field_value)/) {
			my $name  = $1;
			my $value = $2;
			$fields_hash{$name} = $value;
		}
	}

	return \%fields_hash;
}

# Using an event array, merge all the fields
# of a particular tracepoint.
sub merge_fields
{
	my ($events_ref) = @_;
	my %merged;

	foreach my $event (@{$events_ref}) {
		my $tp_event     = $event->{'tp_event'};
		my $tracepoint  = "${tp_event}";

		foreach my $key (keys %{$event->{'fields'}}) {
			my $val = $event->{'fields'}->{$key};

			# TODO: Merge of array is not implemented.
			next if (ref($val) eq 'ARRAY');
			$merged{$tracepoint}{$key}{$val} = undef;
		}
	}

	return \%merged;
}

# Print the minimum and maximum of each fields
# for a particular tracepoint.
sub print_fields_stats
{
	my ($merged_ref, $tracepoint) = @_;

	return unless ($tracepoint && exists $merged_ref->{$tracepoint});

	foreach my $field (keys %{$merged_ref->{$tracepoint}}) {
		my @sorted;
		my @val = keys %{$merged_ref->{$tracepoint}->{$field}};

		if ($val[0] =~ /^\d+$/) {
			# Sort numerically
			@sorted = sort { $a <=> $b } @val;
		} elsif ($val[0] =~ /^0x[\da-f]+$/i) {
			# Convert the hex values and sort numerically
			@sorted = sort { hex($a) <=> hex($b) } @val;
		} else {
			# Fallback, alphabetical sort
			@sorted = sort { lc($a) cmp lc($b) } @val;
		}

		my $min = $sorted[0];
		my $max = $sorted[-1];

		print "$field $min $max\n";
	}
}

my @events;

while (<>)
{
	my $timestamp   = '\[(?:.*)\]';
	my $elapsed     = '\((?:.*)\)';
	my $hostname    = '(?:.*)';
	my $tp_event    = '(.*)';
	my $pkt_context = '(?:\{[^}]*\},\s)*';
	my $fields      = '\{(.*)\}$';

	# Parse babeltrace text output format
	if (/$timestamp\s$elapsed\s$hostname\s$tp_event:\s$pkt_context$fields/) {
		my %event_hash;
		$event_hash{'tp_event'}    = $1;
		$event_hash{'fields'}      = parse_fields($2);

		push @events, \%event_hash;
	}
}

my %merged_fields = %{merge_fields(\@{events})};
print_fields_stats(\%merged_fields, $opt_tracepoint);
