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 )