Blame scripts/damemtop

Packit 4e8bc4
#!/usr/bin/perl
Packit 4e8bc4
#  dormando's awesome memcached top utility!
Packit 4e8bc4
#
Packit 4e8bc4
#  Copyright 2009 Dormando (dormando@rydia.net).  All rights reserved.
Packit 4e8bc4
#
Packit 4e8bc4
#  Use and distribution licensed under the BSD license.  See
Packit 4e8bc4
#  the COPYING file for full text.
Packit 4e8bc4
Packit 4e8bc4
use strict;
Packit 4e8bc4
use warnings FATAL => 'all';
Packit 4e8bc4
Packit 4e8bc4
use AnyEvent;
Packit 4e8bc4
use AnyEvent::Socket;
Packit 4e8bc4
use AnyEvent::Handle;
Packit 4e8bc4
use Getopt::Long;
Packit 4e8bc4
use YAML qw/Dump Load LoadFile/;
Packit 4e8bc4
use Term::ReadKey qw/ReadMode ReadKey GetTerminalSize/;
Packit 4e8bc4
Packit 4e8bc4
our $VERSION = '0.1';
Packit 4e8bc4
Packit 4e8bc4
my $CLEAR     = `clear`;
Packit 4e8bc4
my @TERM_SIZE = ();
Packit 4e8bc4
$|++;
Packit 4e8bc4
Packit 4e8bc4
my %opts = ();
Packit 4e8bc4
GetOptions(\%opts, 'help|h', 'config=s');
Packit 4e8bc4
Packit 4e8bc4
if ($opts{help}) {
Packit 4e8bc4
    show_help(); exit;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
$SIG{INT} = sub {
Packit 4e8bc4
    ReadMode('normal');
Packit 4e8bc4
    print "\n";
Packit 4e8bc4
    exit;
Packit 4e8bc4
};
Packit 4e8bc4
Packit 4e8bc4
# TODO: make this load from central location, and merge in homedir changes.
Packit 4e8bc4
# then merge Getopt::Long stuff on top of that
Packit 4e8bc4
# TODO: Set a bunch of defaults and merge in.
Packit 4e8bc4
my $CONF = load_config();
Packit 4e8bc4
my %CONS    = ();
Packit 4e8bc4
my $LAST_RUN = time; # time after the last loop cycle.
Packit 4e8bc4
my $TIME_SINCE_LAST_RUN = time; # time since last loop cycle.
Packit 4e8bc4
my $loop_timer;
Packit 4e8bc4
my $main_cond;
Packit 4e8bc4
my $prev_stats_results;
Packit 4e8bc4
Packit 4e8bc4
my %display_modes = (
Packit 4e8bc4
    't' => \&display_top_mode,
Packit 4e8bc4
    '?' => \&display_help_mode,
Packit 4e8bc4
    'h' => \&display_help_mode,
Packit 4e8bc4
);
Packit 4e8bc4
Packit 4e8bc4
my %column_compute = (
Packit 4e8bc4
    'hostname' => { stats => [], code => \&compute_hostname},
Packit 4e8bc4
    'hit_rate' => { stats => ['get_hits', 'get_misses'],
Packit 4e8bc4
                    code  => \&compute_hit_rate },
Packit 4e8bc4
    'fill_rate' => { stats => ['bytes', 'limit_maxbytes'],
Packit 4e8bc4
                    code => \&compute_fill_rate },
Packit 4e8bc4
);
Packit 4e8bc4
Packit 4e8bc4
my %column_format = (
Packit 4e8bc4
    'hit_rate' => \&format_percent,
Packit 4e8bc4
    'fill_rate' => \&format_percent,
Packit 4e8bc4
);
Packit 4e8bc4
Packit 4e8bc4
# This can collapse into %column_compute
Packit 4e8bc4
my %column_format_totals = (
Packit 4e8bc4
    'hit_rate' => 0,
Packit 4e8bc4
    'fill_rate' => 0,
Packit 4e8bc4
);
Packit 4e8bc4
Packit 4e8bc4
ReadMode('cbreak');
Packit 4e8bc4
my $LAST_KEY = '';
Packit 4e8bc4
my $read_keys = AnyEvent->io (
Packit 4e8bc4
    fh => \*STDIN, poll => 'r',
Packit 4e8bc4
    cb => sub {
Packit 4e8bc4
        $LAST_KEY = ReadKey(-1);
Packit 4e8bc4
        # If there is a running timer, cancel it.
Packit 4e8bc4
        # Don't want to interrupt a main loop run.
Packit 4e8bc4
        # fire_main_loop()'s iteration will pick up the keypress.
Packit 4e8bc4
        if ($loop_timer) {
Packit 4e8bc4
            $loop_timer = undef;
Packit 4e8bc4
            $main_cond->send;
Packit 4e8bc4
        }
Packit 4e8bc4
    }
Packit 4e8bc4
);
Packit 4e8bc4
Packit 4e8bc4
# start main loop
Packit 4e8bc4
fire_main_loop();
Packit 4e8bc4
Packit 4e8bc4
### AnyEvent related code.
Packit 4e8bc4
Packit 4e8bc4
sub fire_main_loop {
Packit 4e8bc4
    for (;;) {
Packit 4e8bc4
        $loop_timer = undef;
Packit 4e8bc4
        $main_cond = AnyEvent->condvar;
Packit 4e8bc4
        my $time_taken = main_loop();
Packit 4e8bc4
        my $delay = $CONF->{delay} - $time_taken;
Packit 4e8bc4
        $delay = 0 if $delay < 0;
Packit 4e8bc4
        $loop_timer = AnyEvent->timer(
Packit 4e8bc4
            after => $delay,
Packit 4e8bc4
            cb    => $main_cond,
Packit 4e8bc4
        );
Packit 4e8bc4
        $main_cond->recv;
Packit 4e8bc4
    }
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub main_loop {
Packit 4e8bc4
    my $start = AnyEvent->now; # use ->time to find the end.
Packit 4e8bc4
    maintain_connections();
Packit 4e8bc4
Packit 4e8bc4
    my $cv = AnyEvent->condvar;
Packit 4e8bc4
Packit 4e8bc4
    # FIXME: Need to dump early if there're no connected conns
Packit 4e8bc4
    # FIXME: Make this only fetch stats from cons we care to visualize?
Packit 4e8bc4
    # maybe keep everything anyway to maintain averages?
Packit 4e8bc4
    my %stats_results = ();
Packit 4e8bc4
    while (my ($hostname, $con) = each %CONS) {
Packit 4e8bc4
        $cv->begin;
Packit 4e8bc4
        call_stats($con, ['', 'items', 'slabs'], sub {
Packit 4e8bc4
            $stats_results{$hostname} = shift;
Packit 4e8bc4
            $cv->end;
Packit 4e8bc4
        });
Packit 4e8bc4
    }
Packit 4e8bc4
    $cv->recv;
Packit 4e8bc4
Packit 4e8bc4
    # Short circuit since we don't have anything to compare to.
Packit 4e8bc4
    unless ($prev_stats_results) {
Packit 4e8bc4
        $prev_stats_results = \%stats_results;
Packit 4e8bc4
        return $CONF->{delay};
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    # Semi-exact global time diff for stats that want to average
Packit 4e8bc4
    # themselves per-second.
Packit 4e8bc4
    my $this_run = AnyEvent->time;
Packit 4e8bc4
    $TIME_SINCE_LAST_RUN = $this_run - $LAST_RUN;
Packit 4e8bc4
    $LAST_RUN = $this_run;
Packit 4e8bc4
Packit 4e8bc4
    # Done all our fetches. Drive the display.
Packit 4e8bc4
    display_run($prev_stats_results, \%stats_results);
Packit 4e8bc4
    $prev_stats_results = \%stats_results;
Packit 4e8bc4
Packit 4e8bc4
    my $end  = AnyEvent->time;
Packit 4e8bc4
    my $diff = $LAST_RUN - $start;
Packit 4e8bc4
    print "loop took: $diff";
Packit 4e8bc4
    return $diff;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub maintain_connections {
Packit 4e8bc4
    my $cv    = AnyEvent->condvar;
Packit 4e8bc4
Packit 4e8bc4
    $cv->begin (sub { shift->send });
Packit 4e8bc4
    for my $host (@{$CONF->{servers}}) {
Packit 4e8bc4
        next if $CONS{$host};
Packit 4e8bc4
        $cv->begin;
Packit 4e8bc4
        $CONS{$host} = connect_memcached($host, sub {
Packit 4e8bc4
            if ($_[0] eq 'err') {
Packit 4e8bc4
                print "Failed connecting to $host: ", $_[1], "\n";
Packit 4e8bc4
                delete $CONS{$host};
Packit 4e8bc4
            }
Packit 4e8bc4
            $cv->end;
Packit 4e8bc4
        });
Packit 4e8bc4
    }
Packit 4e8bc4
    $cv->end;
Packit 4e8bc4
Packit 4e8bc4
    $cv->recv;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub connect_memcached {
Packit 4e8bc4
    my ($fullhost, $cb)   = @_;
Packit 4e8bc4
    my ($host, $port) = split /:/, $fullhost;
Packit 4e8bc4
Packit 4e8bc4
    my $con; $con = AnyEvent::Handle->new (
Packit 4e8bc4
        connect => [$host => $port],
Packit 4e8bc4
        on_connect => sub {
Packit 4e8bc4
            $cb->('con');
Packit 4e8bc4
        },
Packit 4e8bc4
        on_connect_error => sub {
Packit 4e8bc4
            $cb->('err', $!);
Packit 4e8bc4
            $con->destroy;
Packit 4e8bc4
        },
Packit 4e8bc4
        on_eof   => sub {
Packit 4e8bc4
            $cb->('err', $!);
Packit 4e8bc4
            $con->destroy;
Packit 4e8bc4
        },
Packit 4e8bc4
    );
Packit 4e8bc4
    return $con;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# Function's getting a little weird since I started optimizing it.
Packit 4e8bc4
# As of my first set of production tests, this routine is where we spend
Packit 4e8bc4
# almost all of our processing time.
Packit 4e8bc4
sub call_stats {
Packit 4e8bc4
    my ($con, $cmds, $cb) = @_;
Packit 4e8bc4
Packit 4e8bc4
    my $stats = {};
Packit 4e8bc4
    my $num_types = @$cmds;
Packit 4e8bc4
Packit 4e8bc4
    my $reader; $reader = sub {
Packit 4e8bc4
        my ($con, $results) = @_;
Packit 4e8bc4
        {
Packit 4e8bc4
            my %temp = ();
Packit 4e8bc4
            for my $line (split(/\n/, $results)) {
Packit 4e8bc4
                my ($k, $v) = (split(/\s+/, $line))[1,2];
Packit 4e8bc4
                $temp{$k} = $v;
Packit 4e8bc4
            }
Packit 4e8bc4
            $stats->{$cmds->[0]} = \%temp;
Packit 4e8bc4
        }
Packit 4e8bc4
        shift @$cmds;
Packit 4e8bc4
        unless (@$cmds) {
Packit 4e8bc4
            # Out of commands to process, return goodies.
Packit 4e8bc4
            $cb->($stats);
Packit 4e8bc4
            return;
Packit 4e8bc4
        }
Packit 4e8bc4
    };
Packit 4e8bc4
Packit 4e8bc4
    for my $cmd (@$cmds) {
Packit 4e8bc4
        $con->push_write('stats ' . $cmd . "\n");
Packit 4e8bc4
        $stats->{$cmd} = {};
Packit 4e8bc4
        $con->push_read(line => "END\r\n", $reader);
Packit 4e8bc4
    }
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
### Compute routines
Packit 4e8bc4
Packit 4e8bc4
sub compute_hostname {
Packit 4e8bc4
    return $_[0];
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub compute_hit_rate {
Packit 4e8bc4
    my $s = $_[1];
Packit 4e8bc4
    my $total = $s->{get_hits} + $s->{get_misses};
Packit 4e8bc4
    return 'NA' unless $total;
Packit 4e8bc4
    return $s->{get_hits} / $total;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub compute_fill_rate {
Packit 4e8bc4
    my $s = $_[1];
Packit 4e8bc4
    return $s->{bytes} / $s->{limit_maxbytes};
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub format_column {
Packit 4e8bc4
    my ($col, $val) = @_;
Packit 4e8bc4
    my $res;
Packit 4e8bc4
    $col =~ s/^all_//;
Packit 4e8bc4
    if ($column_format{$col}) {
Packit 4e8bc4
        if (ref($column_format{$col}) eq 'CODE') {
Packit 4e8bc4
            return $column_format{$col}->($val);
Packit 4e8bc4
        } else {
Packit 4e8bc4
            return $val .= $column_format{$col};
Packit 4e8bc4
        }
Packit 4e8bc4
    } else {
Packit 4e8bc4
        return format_commas($val);
Packit 4e8bc4
    }
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub column_can_total {
Packit 4e8bc4
    my $col = shift;
Packit 4e8bc4
    $col =~ s/^all_//;
Packit 4e8bc4
    return 1 unless exists $column_format_totals{$col};
Packit 4e8bc4
    return $column_format_totals{$col};
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
### Display routines
Packit 4e8bc4
Packit 4e8bc4
# If there isn't a specific column type computer, see if we just want to
Packit 4e8bc4
# look at the specific stat and return it.
Packit 4e8bc4
# If column is a generic type and of 'all_cmd_get' format, return the more
Packit 4e8bc4
# complete stat instead of the diffed stat.
Packit 4e8bc4
sub compute_column {
Packit 4e8bc4
    my ($col, $host, $prev_stats, $curr_stats) = @_;
Packit 4e8bc4
    my $diff_stats = 1;
Packit 4e8bc4
    $diff_stats    = 0 if ($col =~ s/^all_//);
Packit 4e8bc4
Packit 4e8bc4
    # Really should decide on whether or not to flatten the hash :/
Packit 4e8bc4
    my $find_stat = sub {
Packit 4e8bc4
        for my $type (keys %{$_[0]}) {
Packit 4e8bc4
            return $_[0]->{$type}->{$_[1]} if exists $_[0]->{$type}->{$_[1]};
Packit 4e8bc4
        }
Packit 4e8bc4
    };
Packit 4e8bc4
Packit 4e8bc4
    my $diff_stat = sub {
Packit 4e8bc4
        my $stat = shift;
Packit 4e8bc4
        return 'NA' unless defined $find_stat->($curr_stats, $stat);
Packit 4e8bc4
        if ($diff_stats) {
Packit 4e8bc4
            my $diff = eval {
Packit 4e8bc4
                return ($find_stat->($curr_stats, $stat)
Packit 4e8bc4
                       - $find_stat->($prev_stats, $stat))
Packit 4e8bc4
                       / $TIME_SINCE_LAST_RUN;
Packit 4e8bc4
            };
Packit 4e8bc4
            return 'NA' if ($@);
Packit 4e8bc4
            return $diff;
Packit 4e8bc4
        } else {
Packit 4e8bc4
            return $find_stat->($curr_stats, $stat);
Packit 4e8bc4
        }
Packit 4e8bc4
    };
Packit 4e8bc4
Packit 4e8bc4
    if (my $comp = $column_compute{$col}) {
Packit 4e8bc4
        my %s = ();
Packit 4e8bc4
        for my $stat (@{$comp->{stats}}) {
Packit 4e8bc4
            $s{$stat} = $diff_stat->($stat);
Packit 4e8bc4
        }
Packit 4e8bc4
        return $comp->{code}->($host, \%s);
Packit 4e8bc4
    } else {
Packit 4e8bc4
        return $diff_stat->($col);
Packit 4e8bc4
    }
Packit 4e8bc4
    return 'NA';
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# We have a bunch of stats from a bunch of connections.
Packit 4e8bc4
# At this point we run a particular display mode, capture the lines, then
Packit 4e8bc4
# truncate and display them.
Packit 4e8bc4
sub display_run {
Packit 4e8bc4
    my $prev_stats = shift;
Packit 4e8bc4
    my $curr_stats = shift;
Packit 4e8bc4
    @TERM_SIZE = GetTerminalSize;
Packit 4e8bc4
    die "cannot detect terminal size" unless $TERM_SIZE[0] && $TERM_SIZE[1];
Packit 4e8bc4
Packit 4e8bc4
    if ($LAST_KEY eq 'q') {
Packit 4e8bc4
        print "\n";
Packit 4e8bc4
        ReadMode('normal'); exit;
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    if ($LAST_KEY ne $CONF->{mode} && exists $display_modes{$LAST_KEY}) {
Packit 4e8bc4
        $CONF->{prev_mode} = $CONF->{mode};
Packit 4e8bc4
        $CONF->{mode} = $LAST_KEY;
Packit 4e8bc4
    } elsif ($CONF->{mode} eq 'h' || $CONF->{mode} eq '?') {
Packit 4e8bc4
        # Bust out of help mode on any key.
Packit 4e8bc4
        $CONF->{mode} = $CONF->{prev_mode};
Packit 4e8bc4
    }
Packit 4e8bc4
    my $lines = $display_modes{$CONF->{mode}}->($prev_stats, $curr_stats);
Packit 4e8bc4
    display_lines($lines) if $lines;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# Default "top" mode.
Packit 4e8bc4
# create a set of computed columns as requested by the config.
Packit 4e8bc4
# this has gotten a little out of hand... needs more cleanup/abstraction.
Packit 4e8bc4
sub display_top_mode {
Packit 4e8bc4
    my $prev_stats = shift;
Packit 4e8bc4
    my $curr_stats = shift;
Packit 4e8bc4
Packit 4e8bc4
    my @columns = @{$CONF->{top_mode}->{columns}};
Packit 4e8bc4
    my @rows    = ();
Packit 4e8bc4
    my @tot_row = ();
Packit 4e8bc4
Packit 4e8bc4
    # Round one.
Packit 4e8bc4
    for my $host (sort keys %{$curr_stats}) {
Packit 4e8bc4
        my @row = ();
Packit 4e8bc4
        for my $colnum (0 .. @columns-1) {
Packit 4e8bc4
            my $col = $columns[$colnum];
Packit 4e8bc4
            my $res = compute_column($col, $host, $prev_stats->{$host},
Packit 4e8bc4
                      $curr_stats->{$host});
Packit 4e8bc4
            $tot_row[$colnum] += $res if is_numeric($res);
Packit 4e8bc4
            push @row, $res;
Packit 4e8bc4
        }
Packit 4e8bc4
        push(@rows, \@row);
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    # Sort rows by sort column (ascending or descending)
Packit 4e8bc4
    if (my $sort = $CONF->{top_mode}->{sort_column}) {
Packit 4e8bc4
        my $order  = $CONF->{top_mode}->{sort_order} || 'asc';
Packit 4e8bc4
        my $colnum = 0;
Packit 4e8bc4
        for (0 .. @columns-1) { $colnum = $_ if $columns[$_] eq $sort; }
Packit 4e8bc4
        my @newrows;
Packit 4e8bc4
        if ($order eq 'asc') {
Packit 4e8bc4
            if (is_numeric($rows[0]->[$colnum])) {
Packit 4e8bc4
                @newrows = sort { $a->[$colnum] <=> $b->[$colnum] } @rows;
Packit 4e8bc4
            } else {
Packit 4e8bc4
                @newrows = sort { $a->[$colnum] cmp $b->[$colnum] } @rows;
Packit 4e8bc4
            }
Packit 4e8bc4
        } else {
Packit 4e8bc4
            if (is_numeric($rows[0]->[$colnum])) {
Packit 4e8bc4
                @newrows = sort { $b->[$colnum] <=> $a->[$colnum] } @rows;
Packit 4e8bc4
            } else {
Packit 4e8bc4
                @newrows = sort { $b->[$colnum] cmp $a->[$colnum] } @rows;
Packit 4e8bc4
            }
Packit 4e8bc4
        }
Packit 4e8bc4
        @rows = @newrows;
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    # Format each column after the sort...
Packit 4e8bc4
    {
Packit 4e8bc4
        my @newrows = ();
Packit 4e8bc4
        for my $row (@rows) {
Packit 4e8bc4
            my @newrow = ();
Packit 4e8bc4
            for my $colnum (0 .. @columns-1) {
Packit 4e8bc4
                push @newrow, is_numeric($row->[$colnum]) ?
Packit 4e8bc4
                            format_column($columns[$colnum], $row->[$colnum]) :
Packit 4e8bc4
                            $row->[$colnum];
Packit 4e8bc4
            }
Packit 4e8bc4
            push @newrows, \@newrow;
Packit 4e8bc4
        }
Packit 4e8bc4
        @rows = @newrows;
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    # Create average and total rows.
Packit 4e8bc4
    my @avg_row = ();
Packit 4e8bc4
    for my $col (0 .. @columns-1) {
Packit 4e8bc4
        if (is_numeric($tot_row[$col])) {
Packit 4e8bc4
            my $countable_rows = 0;
Packit 4e8bc4
            for my $row (@rows) {
Packit 4e8bc4
                next unless $row->[$col];
Packit 4e8bc4
                $countable_rows++ unless $row->[$col] eq 'NA';
Packit 4e8bc4
            }
Packit 4e8bc4
            $countable_rows = 1 unless $countable_rows;
Packit 4e8bc4
            push @avg_row, format_column($columns[$col],
Packit 4e8bc4
                 sprintf('%.2f', $tot_row[$col] / $countable_rows));
Packit 4e8bc4
        } else {
Packit 4e8bc4
            push @avg_row, 'NA';
Packit 4e8bc4
        }
Packit 4e8bc4
        $tot_row[$col] = 'NA' unless defined $tot_row[$col];
Packit 4e8bc4
        $tot_row[$col] = 'NA' unless (column_can_total($columns[$col]));
Packit 4e8bc4
        $tot_row[$col] = format_column($columns[$col], $tot_row[$col])
Packit 4e8bc4
                         unless $tot_row[$col] eq 'NA';
Packit 4e8bc4
    }
Packit 4e8bc4
    unshift @rows, \@avg_row;
Packit 4e8bc4
    unshift @rows, ['AVERAGE:'];
Packit 4e8bc4
    unshift @rows, \@tot_row;
Packit 4e8bc4
    unshift @rows, ['TOTAL:'];
Packit 4e8bc4
Packit 4e8bc4
    # Round two. Pass @rows into a function which returns an array with the
Packit 4e8bc4
    # desired format spacing for each column.
Packit 4e8bc4
    unshift @rows, \@columns;
Packit 4e8bc4
    my $spacing = find_optimal_spacing(\@rows);
Packit 4e8bc4
Packit 4e8bc4
    my @display_lines = ();
Packit 4e8bc4
    for my $row (@rows) {
Packit 4e8bc4
        my $line = '';
Packit 4e8bc4
        for my $col (0 .. @$row-1) {
Packit 4e8bc4
            my $space = $spacing->[$col];
Packit 4e8bc4
            $line .= sprintf("%-${space}s ", $row->[$col]);
Packit 4e8bc4
        }
Packit 4e8bc4
        push @display_lines, $line;
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    return \@display_lines;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub display_help_mode {
Packit 4e8bc4
    my $help = <<"ENDHELP";
Packit 4e8bc4
Packit 4e8bc4
dormando's awesome memcached top utility version v$VERSION
Packit 4e8bc4
Packit 4e8bc4
This early version requires you to edit the ~/.damemtop/damemtop.yaml
Packit 4e8bc4
(or /etc/damemtop.yaml) file in order to change options.
Packit 4e8bc4
See --help for more info.
Packit 4e8bc4
Packit 4e8bc4
Hit any key to exit help.
Packit 4e8bc4
ENDHELP
Packit 4e8bc4
    my @lines = split /\n/, $help;
Packit 4e8bc4
    display_lines(\@lines);
Packit 4e8bc4
    $LAST_KEY = ReadKey(0);
Packit 4e8bc4
    return;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# Takes a set of lines, clears screen, dumps header, trims lines, etc
Packit 4e8bc4
# MAYBE: mode to wrap lines instead of trim them?
Packit 4e8bc4
sub display_lines {
Packit 4e8bc4
    my $lines = shift;
Packit 4e8bc4
Packit 4e8bc4
    my $width         = $TERM_SIZE[0];
Packit 4e8bc4
    my $height_remain = $TERM_SIZE[1];
Packit 4e8bc4
Packit 4e8bc4
    unshift @$lines, display_header($width);
Packit 4e8bc4
    clear_screen() unless $CONF->{no_clear};
Packit 4e8bc4
Packit 4e8bc4
    while (--$height_remain && @$lines) {
Packit 4e8bc4
        # truncate too long lines.
Packit 4e8bc4
        my $line = shift @$lines;
Packit 4e8bc4
        $line = substr $line, 0, $width-1;
Packit 4e8bc4
        print $line, "\n";
Packit 4e8bc4
    }
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub display_header {
Packit 4e8bc4
    my $topbar = 'damemtop: ' . scalar localtime;
Packit 4e8bc4
    if ($CONF->{mode} eq 't' && $CONF->{top_mode}->{sort_column}) {
Packit 4e8bc4
        $topbar .= ' [sort: ' . $CONF->{top_mode}->{sort_column} . ']';
Packit 4e8bc4
    }
Packit 4e8bc4
    $topbar .= ' [delay: ' . $CONF->{delay} . 's]';
Packit 4e8bc4
    return $topbar;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
### Utilities
Packit 4e8bc4
Packit 4e8bc4
# find the optimal format spacing for each column, which is:
Packit 4e8bc4
# longest length of item in col + 2 (whitespace).
Packit 4e8bc4
sub find_optimal_spacing {
Packit 4e8bc4
    my $rows  = shift;
Packit 4e8bc4
    my @maxes = ();
Packit 4e8bc4
Packit 4e8bc4
    my $num_cols = @{$rows->[0]};
Packit 4e8bc4
    for my $row (@$rows) {
Packit 4e8bc4
        for my $col (0 .. $num_cols-1) {
Packit 4e8bc4
            $maxes[$col] = 0 unless $maxes[$col];
Packit 4e8bc4
            next unless $row->[$col];
Packit 4e8bc4
            $maxes[$col] = length($row->[$col])
Packit 4e8bc4
                if length($row->[$col]) > $maxes[$col];
Packit 4e8bc4
        }
Packit 4e8bc4
    }
Packit 4e8bc4
    for my $col (0 .. $num_cols) {
Packit 4e8bc4
        $maxes[$col] += 1;
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    return \@maxes;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# doesn't try too hard to identify numbers...
Packit 4e8bc4
sub is_numeric {
Packit 4e8bc4
    return 0 unless $_[0];
Packit 4e8bc4
    return 1 if $_[0] =~ m/^\d+(\.\d*)?(\w+)?$/;
Packit 4e8bc4
    return 0;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub format_percent {
Packit 4e8bc4
    return sprintf("%.2f%%", $_[0] * 100);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub format_commas {
Packit 4e8bc4
    my $num = shift;
Packit 4e8bc4
    $num = int($num);
Packit 4e8bc4
    $num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
Packit 4e8bc4
    return $num;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# Can tick counters/etc here as well.
Packit 4e8bc4
sub clear_screen {
Packit 4e8bc4
    print $CLEAR;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# tries minimally to find a localized config file.
Packit 4e8bc4
# TODO: Handle the YAML error and make it prettier.
Packit 4e8bc4
sub load_config {
Packit 4e8bc4
    my $config = $opts{config} if $opts{config};
Packit 4e8bc4
    my $homedir = "$ENV{HOME}/.damemtop/damemtop.yaml";
Packit 4e8bc4
    if (-e $homedir) {
Packit 4e8bc4
        $config = $homedir;
Packit 4e8bc4
    } else {
Packit 4e8bc4
        $config = '/etc/damemtop.yaml';
Packit 4e8bc4
    }
Packit 4e8bc4
    return LoadFile($config);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub show_help {
Packit 4e8bc4
    print <<"ENDHELP";
Packit 4e8bc4
dormando's awesome memcached top utility version v$VERSION
Packit 4e8bc4
Packit 4e8bc4
This program is copyright (c) 2009 Dormando.
Packit 4e8bc4
Use and distribution licensed under the BSD license.  See
Packit 4e8bc4
the COPYING file for full text.
Packit 4e8bc4
Packit 4e8bc4
contact: dormando\@rydia.net or memcached\@googlegroups.com.
Packit 4e8bc4
Packit 4e8bc4
This early version requires you to edit the ~/.damemtop/damemtop.yaml
Packit 4e8bc4
(or /etc/damemtop.yaml) file in order to change options.
Packit 4e8bc4
Packit 4e8bc4
You may display any column that is in the output of
Packit 4e8bc4
'stats', 'stats items', or 'stats slabs' from memcached's ASCII protocol.
Packit 4e8bc4
Start a column with 'all_' (ie; 'all_get_hits') to display the current stat,
Packit 4e8bc4
otherwise the stat is displayed as an average per second.
Packit 4e8bc4
Packit 4e8bc4
Specify a "sort_column" under "top_mode" to sort the output by any column.
Packit 4e8bc4
Packit 4e8bc4
Some special "computed" columns exist:
Packit 4e8bc4
hit_rate (get/miss hit ratio)
Packit 4e8bc4
fill_rate (% bytes used out of the maximum memory limit)
Packit 4e8bc4
ENDHELP
Packit 4e8bc4
    exit;
Packit 4e8bc4
}