#!/usr/bin/perl
#
# timer_top.pl
#
# Timer Top: gets timers info exported by kernel in /proc/timer_info and
# organizes and shows useful info in the screen. Its main purpose is to
# test the dynamic tick patch by Tony Lindgren and Tuukka Tikkanen.
# Idea is to evolve this in order to get more useful info
# It needs the System.map file to be in the same directory
#
# Copyright (C) 2005 Insituto Nokia de Tecnologia - INdT - Manaus
# Written by Daniel Petrini <daniel.petrini@indt.org.br> and
#            Ilias Biris <ilias.biris@indt.org.br>
#
# With some code from pmstats-0.2 by Tony Lindgren
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
# 

open (INFILE, "System.map") || die ("cannot open file\n");

my ($interval) = $ARGV[0];
my ($refresh_rate) = $ARGV[1];

if ($refresh_rate eq "") {
    $refresh_rate = 0.5;
}

if ($interval eq "") {
    printf("Usage: %s refresh rate(in seconds) interval(in seconds) \n", $0);
    print "Refresh rate (default = 0.50) is period between /proc/timer_info reads\n";
    print "Interval is max period that a sampled timer will apear without being recalled\n";	
    exit(1); 
}

%sys_map_lines=();
%top_lines=();
%numb_func=();
%diff_time=();

# Get functions from System.map 
while(<INFILE>) {
    chomp $_;
    if( $_ ne "" ) {
	$sys_map_line = substr($_, 0, 8);	
	$sys_map_lines{$sys_map_line}=substr($_, 10, length($_));
    }
}
close(INFILE);

#
# Reads kernel timers info from /proc/timer_info
#
sub read_top_info {

    open (INFILE2, "/proc/timer_info") || die ("cannot open file\n");

    while(<INFILE2>){
	if( $_ !~ m/^Function\scounter/ ){
	    #$func_adr = substr( $_, 0, 8);
	    #$numb_top = substr( $_, 9, 8);
	    @words = split( / / , $_);
	    $func_adr = $words[0];  # Timer Function address
	    $numb_top = $words[1];  # Timer Function counter
	    $pid_top  = $words[2];  # Pid, if exists, which uses the timer function
	    $comm_top = $words[3];  # Command line for the pid
	    if( $sys_map_lines{$func_adr} ne "" ){
		# Check if there is variation for that timer function
		if ( ($numb_top) != ($numb_func{$func_adr})  ) {
		    $top_lines{$func_adr} = $sys_map_lines{$func_adr};
		    $diff_time{$func_adr} = $numb_top - $numb_func{$func_adr};	#Get the difference
		    $numb_func{$func_adr} = $numb_top;

		    # Store function address, cmdline and timestamp per pid
		    $pid_2{$pid_top} = $func_adr;
		    $comm_2{$pid_top} = $comm_top;
		    $time_st{$pid_top} = time();

		    #print "$numb_top \n"
	    	} else {
		    $top_lines{$func_adr} = "";
		    $pid_proc{$func_adr}  = 0;
		    $comm_proc{$func_adr} = "";
		} 
	    }
	}
    }
    close(INFILE2);
}

# 
# Based in code from pmstats-0.2 from Tony Lindgren
#
sub read_timer_ticks() {
    my $buf = "";
    open(INFILE3, "/proc/interrupts") or die "Could not open file\n";
    while (<INFILE3>) {
	# It looks for the string 'timer' ...
	if ($_ =~ m/timer/ ) { 
	    $_ =~ s/ +/ /gi;
	    my ($tag, $val) = split(": ", $_ );
	    $tag =~ s/ +//g;
	    ($val) = split(" ", $val);
	    close (INFILE3);
	    return($val);
	}
    }
    close (INFILE3);
    return 0;
}

my $flag = 0;

#
# Main routine. Refresh screen every $interval seconds
#
while (1) {

    read_top_info();

    system("clear");

    print "Timer Top v0.9.3 \n";

    my $ticks = read_timer_ticks();
    my $delta_ticks = ($ticks - $last_ticks) / $refresh_rate;
    if ($last_ticks == 0) {
	$delta_ticks = 0; 
    }
    $last_ticks = $ticks;

    printf "Ticks: %d HZ\n", $delta_ticks;
    print "Address   Count    Freq(Hz)    Function         PID    Command\n";

    my $time_stamp = time();

    # For each function address ...
    while(($item, $value) = each(%top_lines)) {
	if ( $top_lines{$item} ne "" ) {
	    chomp ($numb_func{$item});
	    $addres = $item;
	    $count  = $numb_func{$item};
	    $freq   = $diff_time{$item}/$refresh_rate;
	    $functn = $value;
	    
	    # Search for pids from function addresses
	    while ( ($proc, $data) = each(%pid_2) ) {
		#print "$proc $data\n";
		# If matches function address and they are below time threshould ...
		if ($data eq $item && (($time_st{$proc} + $interval) >= $time_stamp) ) {
		    # Get the pid and command line string and print whole line
		    if ($proc eq 0) {
			$pid_fn = "X";
		    } else { 
			$pid_fn = $proc;
		    }
		    $name_pid = $comm_2{$proc};
		    write();
		}
	    }
	    $flag = 1;
	}
    }
    
    if ($flag eq 0) {
        printf("Please, make sure the current System map file is in the current directory.\n");	
	exit(1);
}
    select(undef, undef, undef, $refresh_rate);
    #sleep $interval;
}

format STDOUT = 
@<<<<<<<<|@<<<<<<<|@####.##|@>>>>>>>>>>>>>>>>>>|@<<<<<|@<<<<<<<<<<<<<<<|
$addres, $count, $freq, $functn, $pid_fn, $name_pid
.
