App-Yath-Plugin-Utilization

 view release on metacpan or  search on metacpan

lib/Test2/Harness/Resource/Utilization/CPU.pm  view on Meta::CPAN

use warnings;

our $VERSION = '0.000001';

use Carp qw/croak/;
use Time::HiRes qw/time/;

use Test2::Harness::Resource::Utilization::Util qw/read_file_lines/;

use parent 'Test2::Harness::Runner::Resource';
use Test2::Harness::Util::HashBase qw/<settings <utilize_percent <min_concurrent <ema_alpha <min_dt +prev_stat +last_busy_pct +have_sample +in_flight +assigned/;

sub new {
    my $class = shift;
    my $self = bless({@_}, $class);
    $self->init;
    return $self;
}

sub init {
    my $self = shift;

lib/Test2/Harness/Resource/Utilization/CPU.pm  view on Meta::CPAN


    $self->{+UTILIZE_PERCENT} = $u + 0;
    $self->{+MIN_CONCURRENT}  //= 1;
    $self->{+EMA_ALPHA}       //= 0.3;
    $self->{+MIN_DT}          //= 10;     # jiffies; ~100ms on HZ=100
    $self->{+LAST_BUSY_PCT}   //= 0;
    $self->{+HAVE_SAMPLE}     //= 0;
    $self->{+IN_FLIGHT}       //= 0;

    # Prime PREV_STAT now so the first runtime sample has something to
    # diff against rather than returning 0% busy.
    $self->_read_and_record;
}

sub _read_and_record {
    my $self = shift;

    my $line = $self->_read_stat_first_line;
    chomp $line;
    my @fields = split /\s+/, $line;
    shift @fields;

lib/Test2/Harness/Resource/Utilization/CPU.pm  view on Meta::CPAN

    my $total = 0;
    $total += $_ for @fields;

    my $prev = $self->{+PREV_STAT};
    return $self->{+LAST_BUSY_PCT} unless $prev;

    my $dt = $total - $prev->{total};

    # Require a minimum jiffy window before consuming PREV_STAT. Without
    # this, a 1-jiffy window where the kernel happened to record an idle
    # tick returns 0% busy on a fully loaded box -- pure noise. We let
    # PREV_STAT keep accumulating until we have a wide enough window for
    # a stable reading.
    return $self->{+LAST_BUSY_PCT} if $dt < $self->{+MIN_DT};

    my $di = $idle - $prev->{idle};
    my $sample = 100 * (1 - $di / $dt);
    $sample = 0   if $sample < 0;
    $sample = 100 if $sample > 100;

    # Exponential moving average across samples to dampen residual noise.

lib/Test2/Harness/Resource/Utilization/CPU.pm  view on Meta::CPAN

    $self->{+HAVE_SAMPLE}   = 1;
    return $smoothed;
}

sub available {
    my $self = shift;
    my ($task) = @_;

    return 1 if $self->{+IN_FLIGHT} < $self->{+MIN_CONCURRENT};

    my $busy = $self->_sample;
    return $busy >= $self->{+UTILIZE_PERCENT} ? 0 : 1;
}

sub assign {
    my $self = shift;
    my ($task, $state) = @_;
    $state->{record} = {cpu_assign => 1};
}

sub record {
    my $self = shift;

lib/Test2/Harness/Resource/Utilization/CPU.pm  view on Meta::CPAN

    $self->{+IN_FLIGHT}-- if $self->{+IN_FLIGHT} > 0;
}

sub status_data {
    my $self = shift;
    return [
        {
            title => 'CPU',
            tables => [
                {
                    header => [qw/utilize_percent busy_pct in_flight/],
                    rows => [[
                        $self->{+UTILIZE_PERCENT},
                        sprintf('%.1f', $self->{+LAST_BUSY_PCT} // 0),
                        $self->{+IN_FLIGHT} // 0,
                    ]],
                },
            ],
        },
    ];
}

t/unit/Test2/Harness/Resource/Utilization/CPU.t  view on Meta::CPAN

    $args{min_dt}    //= 1;
    no warnings 'redefine';
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { $init_line };
    return $CLASS->new(%args);
}

subtest sample_logic => sub {
    my $r = make_cpu(utilize_percent => 50, init_line => "cpu 100 0 100 800 0 0 0 0 0 0\n");

    no warnings 'redefine';
    # Increment idle by 50, total by 100. busy = 1 - 50/100 = 50%
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 125 0 125 850 0 0 0 0 0 0\n" };
    is($r->_sample, 50, '50% busy computed against init-primed PREV_STAT');

    # Same /proc/stat (dt=0) -> returns LAST_BUSY_PCT and does NOT clobber PREV_STAT.
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 125 0 125 850 0 0 0 0 0 0\n" };
    is($r->_sample, 50, 'dt=0 returns cached');

    # Next reading: prev now (total=1100, idle=850). New: total=1300, idle=875.
    # dt=200, di=25 => busy = 100*(1 - 25/200) = 87.5%
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 200 0 225 875 0 0 0 0 0 0\n" };
    is($r->_sample, 87.5, 'next real reading computed against accumulated PREV_STAT');
};

subtest available => sub {
    my $r = make_cpu(utilize_percent => 50, min_concurrent => 0, init_line => "cpu 100 0 100 800 0 0 0 0 0 0\n");

    no warnings 'redefine';
    # 80% busy: di=20 dt=100
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 180 0 180 820 0 0 0 0 0 0\n" };
    is($r->available({}), 0, 'defer when above utilize');

    # PREV_STAT now at (200,820). 20% busy: di=80 dt=100 -> di/dt=0.8 -> busy=20%
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 210 0 210 900 0 0 0 0 0 0\n" };
    is($r->available({}), 1, 'allow when below utilize');
};

subtest min_concurrent_floor => sub {
    my $r = make_cpu(utilize_percent => 50, min_concurrent => 2, init_line => "cpu 0 0 0 0 0 0 0 0 0 0\n");
    no warnings 'redefine';
    # Each call: total +100, idle +0 -> 100% busy
    my $t = 0;
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub {
        $t += 100;
        return "cpu $t 0 0 0 0 0 0 0 0 0\n";
    };
    $r->_sample; # sets busy=100 against init-primed prev

    is($r->available({}), 1, 'always allow under floor (in_flight=0)');

    my $state = {};
    $r->assign({}, $state);
    $r->record('job1', $state->{record});
    is($r->available({}), 1, 'still allow at in_flight=1 < min=2');

    $state = {};
    $r->assign({}, $state);

t/unit/Test2/Harness/Resource/Utilization/CPU.t  view on Meta::CPAN

    is($r->available({}), 1, 'allow after release (back under floor)');
};

subtest ema_smoothing => sub {
    # alpha=0.5 means new sample contributes 50%, prev smoothed 50%.
    # min_dt=1 so each call consumes.
    my $r = make_cpu(utilize_percent => 50, ema_alpha => 0.5, min_dt => 1,
                     init_line => "cpu 0 0 0 0 0 0 0 0 0 0\n");
    no warnings 'redefine';

    # 100% busy reading: total +100, idle +0
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 100 0 0 0 0 0 0 0 0 0\n" };
    is($r->_sample, 100, 'first sample seeds EMA directly');

    # 0% busy reading: total +100, idle +100. EMA = 0.5*0 + 0.5*100 = 50
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 100 0 0 100 0 0 0 0 0 0\n" };
    is($r->_sample, 50, 'second sample blended');

    # 0% busy again. EMA = 0.5*0 + 0.5*50 = 25
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 100 0 0 200 0 0 0 0 0 0\n" };
    is($r->_sample, 25, 'third sample blended further');
};

subtest min_dt_threshold => sub {
    # min_dt=100 jiffies; small dt should not consume PREV_STAT
    my $r = make_cpu(utilize_percent => 50, min_dt => 100, ema_alpha => 1,
                     init_line => "cpu 0 0 0 0 0 0 0 0 0 0\n");
    no warnings 'redefine';

    # dt = 50 (under threshold). Should return LAST_BUSY_PCT (0) and NOT update PREV_STAT.
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 25 0 25 0 0 0 0 0 0 0\n" };
    is($r->_sample, 0, 'small dt below threshold: returns cached');

    # Now a much larger reading: dt from original prev (0,0) = 200, idle delta=0 -> 100% busy
    local *Test2::Harness::Resource::Utilization::CPU::_read_stat_first_line = sub { "cpu 100 0 100 0 0 0 0 0 0 0\n" };
    is($r->_sample, 100, 'large dt: real computation against accumulated PREV_STAT');
};

done_testing;



( run in 0.648 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )