# File: LagChecker.pm # Author: pragma_ # # Purpose: sends PING command to IRC server and times duration for PONG reply in # order to maintain lag history and average. # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. package PBot::LagChecker; use warnings; use strict; use feature 'unicode_strings'; use feature 'switch'; use Time::HiRes qw(gettimeofday tv_interval); use Time::Duration; use Carp (); sub new { Carp::croak("Options to LagChecker should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH'; my ($class, %conf) = @_; my $self = bless {}, $class; $self->initialize(%conf); return $self; } sub initialize { my ($self, %conf) = @_; $self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to LagChecker"); $self->{lag_average} = undef; # average of entries in lag history, in seconds $self->{lag_string} = undef; # string representation of lag history and lag average $self->{lag_history} = []; # history of previous PING/PONG timings $self->{pong_received} = undef; # tracks pong replies; undef if no ping sent; 0 if ping sent but no pong reply yet; 1 if ping/pong completed $self->{ping_send_time} = undef; # when last ping was sent # maximum number of lag history entries to retain $self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_history_max', $conf{lag_history_max} // 3); # lagging is true if lag_average reaches or exceeds this threshold, in milliseconds $self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_threshold', $conf{lag_threshhold} // 2000); # how often to send PING, in seconds $self->{pbot}->{registry}->add_default('text', 'lagchecker', 'lag_history_interval', $conf{lag_history_interval} // 10); $self->{pbot}->{registry}->add_trigger('lagchecker', 'lag_history_interval', sub { $self->lag_history_interval_trigger(@_) }); $self->{pbot}->{timer}->register( sub { $self->send_ping }, $self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_interval'), 'lag_history_interval' ); $self->{pbot}->{commands}->register(sub { return $self->lagcheck(@_) }, "lagcheck", 0); $self->{pbot}->{event_dispatcher}->register_handler('irc.pong', sub { $self->on_pong(@_) }); } sub lag_history_interval_trigger { my ($self, $section, $item, $newvalue) = @_; $self->{pbot}->{timer}->update_interval('lag_history_interval', $newvalue); } sub send_ping { my $self = shift; return unless defined $self->{pbot}->{conn}; $self->{ping_send_time} = [gettimeofday]; $self->{pong_received} = 0; $self->{pbot}->{conn}->sl("PING :lagcheck"); } sub on_pong { my $self = shift; $self->{pong_received} = 1; my $elapsed = tv_interval($self->{ping_send_time}); push @{$self->{lag_history}}, [ $self->{ping_send_time}[0], $elapsed * 1000]; my $len = @{$self->{lag_history}}; my $lag_history_max = $self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_max'); while ($len > $lag_history_max) { shift @{$self->{lag_history}}; $len--; } $self->{lag_string} = ""; my $comma = ""; my $lag_total = 0; foreach my $entry (@{$self->{lag_history}}) { my ($send_time, $lag_result) = @$entry; $lag_total += $lag_result; my $ago = concise ago(gettimeofday - $send_time); $self->{lag_string} .= $comma . "[$ago] " . sprintf "%.1f ms", $lag_result; $comma = "; "; } $self->{lag_average} = $lag_total / $len; $self->{lag_string} .= "; average: " . sprintf "%.1f ms", $self->{lag_average}; return 0; } sub lagging { my $self = shift; if (defined $self->{pong_received} and $self->{pong_received} == 0) { # a ping has been sent (pong_received is not undef) and no pong has been received yet my $elapsed = tv_interval($self->{ping_send_time}); return $elapsed >= $self->{pbot}->{registry}->get_value('lagchecker', 'lag_threshold'); } return 0 if not defined $self->{lag_average}; return $self->{lag_average} >= $self->{pbot}->{registry}->get_value('lagchecker', 'lag_threshold'); } sub lagstring { my $self = shift; my $lag = $self->{lag_string} || "initializing"; return $lag; } sub lagcheck { my ($self, $from, $nick, $user, $host, $arguments) = @_; if (defined $self->{pong_received} and $self->{pong_received} == 0) { # a ping has been sent (pong_received is not undef) and no pong has been received yet my $elapsed = tv_interval($self->{ping_send_time}); my $lag_total = $elapsed; my $len = @{$self->{lag_history}}; my $lagstring = ""; my $comma = ""; foreach my $entry (@{$self->{lag_history}}) { my ($send_time, $lag_result) = @$entry; $lag_total += $lag_result; my $ago = concise ago(gettimeofday - $send_time); $lagstring .= $comma . "[$ago] " . sprintf "%.1f ms", $lag_result; $comma = "; "; } $lagstring .= $comma . "[waiting for pong] $elapsed"; my $average = $lag_total / ($len + 1); $lagstring .= "; average: " . sprintf "%.1f ms", $average; return $lagstring; } return "My lag: " . $self->lagstring; } 1;