#!/usr/bin/env perl
# coding: utf-8
#
# Syd: rock-solid application kernel
# dev/sstat.pl: Assign system call priorities based on strace -c output.
# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
#
# SPDX-License-Identifier: GPL-3.0

use strict;
use warnings;
use File::Temp qw/ tempfile /;

# Get the command-line arguments.
my @args = @ARGV;

# Create a temporary file to capture strace's output.
my ($fh, $filename) = tempfile();

# Insert -cfqo/tmp/out into the strace command.
unshift @args, 'strace', "-cfqo${filename}", '--';

# Run the strace command.
system(@args);

# Parse the strace output to extract syscall names and call counts.
my %syscall_counts;
my $parsing = 0;

unlink $filename or die "Cannot unlink $filename: $!";
seek $fh, 0, 0 or die "Cannot rewind $filename: $!";

while (my $line = <$fh>) {
	chomp $line;
	next if $line =~ /^\s*$/;

	if ($line =~ /^------/) {
		if ($parsing) {
			last;
		} else {
			$parsing = 1;
			next;
		}
	}
	next unless $parsing;

	# Split the line into fields.
	my @fields = split(/\s+/, $line);

	# Extract the number of calls and syscall name.
	my $syscall_id = $fields[-1];
	my $call_count = $fields[4];
	die "Invalid strace line: ${line}" unless $call_count =~ /^[1-9][0-9]*$/;

	# Store the syscall and its call count.
	$syscall_counts{$syscall_id} = $call_count;
}

# Apply the 8-level logic to assign priorities.
my @counts       = sort { $b <=> $a } values %syscall_counts;
my $num_syscalls = scalar @counts;

# Define priority levels.
my @levels = (
	255,    # Level 16
	238,    # Level 15
	221,    # Level 14
	204,    # Level 13
	187,    # Level 12
	170,    # Level 11
	153,    # Level 10
	136,    # Level 9
	119,    # Level 8
	102,    # Level 7
	85,     # Level 6
	68,     # Level 5
	51,     # Level 4
	34,     # Level 3
	17,     # Level 2
	0,      # Level 1
);

# Calculate thresholds for each level.
my @thresholds;
my $num_levels = scalar @levels - 1;    # Exclude Level 1
for (my $i = 0; $i < $num_levels; $i++) {
	my $percentile = ($i + 1) * 100 / $num_levels;
	my $index      = int($percentile * $num_syscalls / 100) - 1;
	$index = 0        if $index < 0;
	$index = $#counts if $index > $#counts;
	my $threshold = $counts[$index];
	push @thresholds, $threshold;
}

# Assign priorities based on call counts.
my %syscall_priorities;

foreach my $syscall (keys %syscall_counts) {
	my $count = $syscall_counts{$syscall};
	my $level;
	for ($level = 0; $level < @thresholds; $level++) {
		if ($count >= $thresholds[$level]) {
			last;
		}
	}
	my $priority = $levels[$level];
	$syscall_priorities{$syscall} = $priority;
}

# Output the syscalls with their assigned priorities, sorted by priority and name.
foreach my $syscall (
	sort { $syscall_priorities{$b} <=> $syscall_priorities{$a} || $a cmp $b }
	keys %syscall_priorities
) {
	my $priority = $syscall_priorities{$syscall};
	print "$syscall $priority\n";
}

1;
