#!/usr/bin/perl -W
#
# lscpumf - Display information about CPU-measurement facilities
#
# Copyright IBM Corp. 2014, 2017
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#
use strict;
use warnings;
use Data::Dumper;
use File::Basename qw/fileparse/;
use Getopt::Long qw/:config no_ignore_case/;

# Global constants
my $CPUMF_HELPER = '@lib_path@/cpumf_helper';

# Prototypes
sub main();
sub show_help();
sub show_version();
sub do_show_info($);
sub do_show_cf($);
sub do_show_sf($);
sub do_show_ctr($;$);
sub do_show_sf_events($);
sub invoke_cpumf_helper($);


sub main()
{
	my $config = {
		# Internal data
		cpumf	    => {},	# CPU-MF information hash
	};

	unless (GetOptions(
		# General options for help, version,...
		"h|help"	      => \&show_help,
		"v|version"	      => \&show_version,
		# Display options
		"i|info"	      => \$config->{opt_info},
		"c|list-counters"     => \$config->{opt_ctr},
		"C|list-all-counters" => \$config->{opt_ctr_all},
		"s|list-sampling-events" => \$config->{opt_ctr_sf},
	)) {
		print STDERR "One or more options are not valid\n";
		print STDERR "Try '" . fileparse($0) .
			      " --help' for more information\n";
		exit 1;
	}

	# Collect CPU-MF information
	$config->{cpumf} = invoke_cpumf_helper("-i");
	die "Failed to collect CPU-MF information: $!\n" unless $config->{cpumf};

	# Process parameters
	my $exitval = 5;
	if (defined($config->{opt_info})) {
		do_show_cf($config);
		do_show_sf($config);
		$exitval = 0;

	} elsif (defined($config->{opt_ctr})) {
		$exitval = do_show_ctr($config);
	} elsif (defined($config->{opt_ctr_all})) {
		$exitval = do_show_ctr($config, "all")
	} elsif (defined($config->{opt_ctr_sf})) {
		$exitval = do_show_sf_events($config);
	} else {
		$exitval = do_show_info($config);
	}

	exit($exitval);
}

sub show_help()
{
	my $prog = fileparse($0);

	print <<"EoHelp";
Usage:	lscpumf -h|-v
	lscpumf [-i]
	lscpumf -c|-C

Options:
	-i    Displays detailed information.
	-c    Lists counters for which the LPAR is authorized.
	-C    Lists counters regardless of LPAR authorization.
	-s    Lists perf raw events that activate the sampling facility.
	-h    Displays help information, then exits.
	-v    Displays version information, then exits.

For more help information, issue 'man $prog'.
EoHelp
	exit 0;
}

sub show_version()
{
	print <<'EoVersion';
CPU-measurement facility utilities, version @S390_TOOLS_RELEASE@
Copyright IBM Corp. 2014, 2017

EoVersion
	exit 0;
}

sub do_show_info($)
{
	my $c = shift();
	my $cpumf = $c->{cpumf};
	my @f = ();

	push @f, "CPU-measurement Counter Facility" if exists $cpumf->{cf};
	push @f, "CPU-measurement Sampling Facility" if exists $cpumf->{sf};

	if (@f) {
		print((join "\n", @f) . "\n");
	} else {
		print STDERR "No CPU-measurement facilities detected\n";
		return 2;
	}
	return 0;
}

sub do_show_cf($)
{
	my $c = shift();

	# Check if counter facility is available
	unless (exists $c->{cpumf}->{cf}) {
		print STDERR "No CPU-measurement counter facility detected\n";
		return 2;
	}

	# Retrieve counter facility information
	my $cf = $c->{cpumf}->{cf};

	# Create list of authorized counter sets
	my @sets = ();
	push @sets, "None" unless $cf->{auth};
	push @sets, "Crypto-Activity counter set" if $cf->{auth} & 0x8;
	push @sets, "Problem-State counter set" if $cf->{auth} & 0x4;
	push @sets, "Basic counter set" if $cf->{auth} & 0x2;
	push @sets, "Extented counter set" if $cf->{auth} & 0x1;
	push @sets, "MT-diagnostic counter set" if $cf->{auth} & 0x20;

	print "CPU-measurement counter facility\n";
	print "-" x 74 . "\n";
	# TODO Display additional information about available conters depending
	# on the version information
	print "Version: " . $cf->{version} . "\n";
	print "\n";
	print "Authorized counter sets:\n";
	print "    $_\n" foreach (sort @sets);
	printf "\nLinux perf event support: %s\n\n",
	       exists $cf->{perf} ? "Yes (PMU: $cf->{perf})" : "No";

	return 0;
}

sub div_ceil($$)
{
	my ($a, $b) = @_;
	return int(($a + $b - 1) / $b);
}

sub humanize_bytes($;$)
{
	my $bytes = shift();
	my @units = split //, " KMGTPEZY";

	my $u = @_ ? shift() : 0;
	while ($bytes >= 1024 && $u <= $#units) {
		$bytes /= 1024;
		$u++;
	}
	return sprintf "%.f%sB", $bytes, $units[$u];
}

sub get_sfb_details($)
{
	my $n_sdb = shift();

	# Calculate sampling buffer structure
	my $n_sdbt = div_ceil($n_sdb, 511);
	my $n_pages = $n_sdb + $n_sdbt;
	return [
		$n_sdb,		    # number of sample-data-blocks
		$n_sdbt,	    # number of sample-data-block-tables
		$n_pages,	    # number of 4K pages
		$n_pages * 4096,    # size in bytes
	];
}

sub do_show_sf($)
{
	my $c = shift();

	# Check if sampling facility is available
	unless (exists $c->{cpumf}->{sf}) {
		print STDERR "No CPU-measurement sampling facility detected\n";
		return 2;
	}
	my $sf = $c->{cpumf}->{sf};
	my $size = invoke_cpumf_helper("--sfb-size");

	# Sampling facility information
	print "CPU-measurement sampling facility\n";
	print "-" x 74 . "\n";
	print  "Sampling Interval:\n";
	printf "     Minimum: %10u cycles (approx. %8u Hz)\n",
		$sf->{min_sampl_interval},
		1000000 * $sf->{cpu_speed} / $sf->{min_sampl_interval};
	printf "     Maximum: %10u cycles (approx. %8u Hz)\n",
		$sf->{max_sampl_interval},
		1000000 * $sf->{cpu_speed} / $sf->{max_sampl_interval};
	print "\n";
	print "Authorized sampling modes:\n";
	foreach my $m (sort keys %{$sf->{modes}}) {
		printf "     %-10s (sample size: %3u bytes)\n", $m,
		       $sf->{modes}->{$m}->{sample_size};
	}
	print "\n";
	printf "\nLinux perf event support: %s\n\n",
	       exists $sf->{perf} ? "Yes (PMU: $sf->{perf})" : "No";

	# Sampling buffer settings for cpum_sf
	goto out unless exists $sf->{perf};

	print "Current sampling buffer settings for $sf->{perf}:\n";
	printf "    Basic-sampling mode\n";
	my $s = get_sfb_details($size->[0]);
	printf "	Minimum: %6u sample-data-blocks (%6s)\n",
			$s->[0], humanize_bytes($s->[3]);
	$s = get_sfb_details($size->[1]);
	printf "	Maximum: %6u sample-data-blocks (%6s)\n",
			$s->[0], humanize_bytes($s->[3]);
	unless (exists $sf->{modes}->{diagnostic}) {
		goto out;
	}

	# Sampling buffer setting specific to diagnostic-sampling mode
	my $f_diag = div_ceil($sf->{modes}->{diagnostic}->{sample_size},
			      $sf->{modes}->{basic}->{sample_size});
	print  "\n";
	printf "    Diagnostic-sampling mode (including basic-sampling)\n";
	$s = get_sfb_details($size->[0] * $f_diag);
	printf "	Minimum: %6u sample-data-blocks (%6s)\n",
			$s->[0], humanize_bytes($s->[3]);
	$s = get_sfb_details($size->[1] * $f_diag);
	printf "	Maximum: %6u sample-data-blocks (%6s)\n",
			$s->[0], humanize_bytes($s->[3]);
	printf "        Size factor: %2u\n", $f_diag;
out:
	return 0;
}

sub print_counters($$)
{
	my ($ctrdef, $header) = @_;
	my $set_name_map = invoke_cpumf_helper('--ctr-set-names');
	my $out = [];

	my ($ctr_perf, $ctr_num, $set, $name, $desc);
	format PERF_CTR_FORM =
r@<<<<	@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$ctr_perf,   $name

		^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
		$desc
		^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ~~
		$desc
		@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
		$set

.
	print $header;
	$~ = "PERF_CTR_FORM";
	foreach my $ctr (sort { $a <=> $b } keys %$ctrdef) {
		$ctr_perf = sprintf "%x", $ctr;
		$ctr_num = $ctr < (1 << 16) ? $ctr : "";
		$name = $ctrdef->{$ctr}->{name};
		$desc = $ctrdef->{$ctr}->{shortdesc} ?
				  $ctrdef->{$ctr}->{shortdesc}
				: $ctrdef->{$ctr}->{desc};
		$desc .= ".";
		$set = $set_name_map->{$ctrdef->{$ctr}->{set}};
		if ($set) {
			$set = "Counter $ctr_num / $set.";
		} else {
			$set = "This event is not associated with a counter set.";
		}
		write;
	}
}

sub do_show_ctr($;$)
{
	my $c = shift();

	# Check if counter facility is available
	unless (exists $c->{cpumf}->{cf}) {
		print STDERR "No CPU-measurement counter facility detected\n";
		return 2;
	}

	# Retrieve counter authorization ("all" or authorized counters only)
	my $auth = @_ ? hex("0xFFFF") : $c->{cpumf}->{cf}->{auth};

	# Retrieve counter information
	my $ctrs = invoke_cpumf_helper("-c $auth");
	unless ($ctrs) {
		print STDERR "No counters are available or authorized\n";
		return 3;
	}

	# Retrieve hardware type
	my $hwtype = invoke_cpumf_helper("--hardware-type");
	my $model = length $hwtype->[1] ? "for $hwtype->[1]" : "";
	my $header = <<"EoHeader";
Perf event counter list $model
==============================================================================

Raw
event	Name	Description
------------------------------------------------------------------------------
EoHeader
	print_counters($ctrs, $header);

	return 0;
}

sub do_show_sf_events($)
{
	my $c = shift();

	# Check if sampling facility is available
	unless (exists $c->{cpumf}->{sf}) {
		print STDERR "No CPU-measurement sampling facility detected\n";
		return 2;
	}
	my $sf = $c->{cpumf}->{sf};
	my $events = invoke_cpumf_helper("--ctr-sf");

	# Remove events with missing authorization
	delete $events->{0xB0000} unless exists $sf->{modes}->{basic};
	delete $events->{0xBD000} unless exists $sf->{modes}->{diagnostic};

	unless ($events) {
		print STDERR "Sampling facility is not authorized\n";
		return 3;
	}

	# Display sampling facility events (aka. counters)
	my $header = <<"EoHeader";
Perf events for activating the sampling facility
==============================================================================

Raw
event	Name	Description
------------------------------------------------------------------------------
EoHeader
	print_counters($events, $header);

	return 0;
}

sub invoke_cpumf_helper($)
{
	my $parms = shift();
	my $result;

	# Call helper module
	my $output = qx"$CPUMF_HELPER $parms";
	die "Failed to run helper module for '$parms'\n" if $? >> 8;
	$result = eval "$output";
	die "Failed to parse helper module data\n" if $@;

	return $result;
}

&main();
__DATA__
__END__
