1package Smokeping::matchers::Median;
2
3=head1 NAME
4
5Smokeping::matchers::Median - Find persistent changes in latency
6
7=head1 OVERVIEW
8
9The idea behind this matcher is to find sustained changes in latency.
10
11The median matcher takes a number of past median latencies. It splits the latencies into
12two groups (old and new) and again finds the median for each groups. If the
13difference between the two medians is bigger than a certain value, it will
14give a match.
15
16=head1 DESCRIPTION
17
18Call the matcher with the following sequence:
19
20 type = matcher
21 pattern =  Median(old=>x,new=>y,diff=>z)
22
23This will create a matcher which consumes x+y latency-datapoints, builds the
24two medians and the matches if the difference between the median latency is
25larger than z seconds.
26
27=head1 COPYRIGHT
28
29Copyright (c) 2004 by OETIKER+PARTNER AG. All rights reserved.
30
31=head1 LICENSE
32
33This program is free software; you can redistribute it and/or modify
34it under the terms of the GNU General Public License as published by
35the Free Software Foundation; either version 2 of the License, or
36(at your option) any later version.
37
38This program is distributed in the hope that it will be useful,
39but WITHOUT ANY WARRANTY; without even the implied warranty of
40MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41GNU General Public License for more details.
42
43You should have received a copy of the GNU General Public License
44along with this program; if not, write to the Free Software
45Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
46
47=head1 AUTHOR
48
49Tobias Oetiker <tobi@oetiker.ch>
50
51=cut
52
53use strict;
54use base qw(Smokeping::matchers::base);
55use vars qw($VERSION);
56$VERSION = 1.0;
57use Carp;
58
59sub new(@)
60{
61    my $class = shift;
62    my $rules = {
63                old=>'\d+',
64                new=>'\d+',
65                diff=>'\d+(\.\d+)?' };
66
67    my $self  = $class->SUPER::new($rules,@_);
68    return $self;
69}
70
71# how many values does the matcher need to do it's magic
72sub Length($)
73{
74    my $self = shift;
75    return $self->{param}{old} + $self->{param}{new};
76}
77
78sub Desc ($) {
79    croak "Find changes in median latency";
80}
81
82sub Test($$)
83{   my $self = shift;
84    my $data = shift; # @{$data->{rtt}} and @{$data->{loss}}
85    my $ac = $self->{param}{old};
86    my $bc = $self->{param}{new};
87    my $cc = $ac +$bc;
88    my $count = scalar @{$data->{rtt}};
89    $cc = $count if $count < $cc;
90    $bc = $count if $count < $bc;
91    my $oldm = robust_median(@{$data->{rtt}}[-$cc..-$bc-1]);
92    my $newm = robust_median(@{$data->{rtt}}[-$bc..-1]);
93    return abs($oldm-$newm) > $self->{param}{diff};
94}
95
96sub robust_median(@){
97    my @numbers = sort {$a <=> $b} grep { defined $_ and $_ =~ /\d/ } @_;
98    my $count = $#numbers;
99    return 0 if $count < 0;
100    return ($count / 2 == int($count/2)) ? $numbers[$count/2] : ($numbers[$count/2+0.5] + $numbers[$count/2-0.5])/2;
101}
102