X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ijackson/git?a=blobdiff_plain;f=cgi;h=e0e90b8d9de18ca00dab42af5e16aed0a769a9e2;hb=600d931f641ddff9cfdedcdb0a58ade7f31b6eae;hp=e2860697adfc4b2e65d8f14eee2a9a4d062df8e0;hpb=df9605e193d1e2d67a51a9d75b75476c83c738f9;p=rrd-graphs.git diff --git a/cgi b/cgi index e286069..e0e90b8 100755 --- a/cgi +++ b/cgi @@ -1,7 +1,9 @@ -#!/usr/bin/perl -w +#!/usr/bin/speedy -w -- -t100 -M1 use strict qw(vars); -use CGI qw/:standard/; +use CGI::SpeedyCGI qw/:standard -no_xhtml/; +use CGI qw/:standard -no_xhtml/; +use POSIX; sub fail ($) { print(header(-status=>500), @@ -12,42 +14,62 @@ sub fail ($) { exit 0; } -our $R= '/var/lib/collectd/rrd/chiark.greenend.org.uk'; +our (@sections, %section_groups, %group_elems, %graphs); + +#---------- initialisation code, run once - graphs setup ---------- -my $self= url(-relative=>1); +BEGIN { -our (@graphs, %graphs); +our $R= '/var/lib/collectd/rrd/chiark.greenend.org.uk'; +our $SELF= '/home/ijackson/things/rrd-graphs'; -sub graph ($$$) { - my ($gname, $basis, $args) = @_; +our @timeranges= (3600, map { $_*86400 } qw(1 7 28), 13*7+1, 366); + +sub graph_of_group ($$$$$) { + my ($section, $group, $elem, $basis, $args) = @_; $basis->{Args}= $args; - $graphs{$gname}= $basis; - push @graphs, $gname; + $basis->{Slower}= 0 unless exists $basis->{Slower}; + $basis->{TimeRanges} ||= \@timeranges; + $graphs{$section,$group,$elem}= $basis; + if (!exists $group_elems{$section,$group}) { + # new group then + if (!exists $section_groups{$section}) { + # new section even + push @sections, $section; + } + push @{ $section_groups{$section} }, $group; + } + push @{ $group_elems{$section,$group} }, $elem; } -graph('Load', { }, +sub graph ($$$$) { + my ($section, $gname, $basis, $args) = @_; + graph_of_group($section, $gname,'', $basis, $args); +} + +graph('General', 'Load and processes', { }, [ "DEF:load=$R/load/load.rrd:shortterm:AVERAGE", (map { "DEF:$_=$R/processes/ps_state-$_.rrd:value:AVERAGE" } qw(blocked running stopped paging sleeping zombies)), - "AREA:running#88f:running processes:STACK", - "AREA:blocked#8f8:blocked processes:STACK", - "AREA:paging#f88:paging processes:STACK", + "AREA:running#88f:running:STACK", + "AREA:blocked#8f8:disk wait:STACK", + "AREA:paging#f88:paging:STACK", "LINE:load#000:load", ]); -graph('Processes', { }, +graph('General', 'Processes', { }, [ (map { "DEF:$_=$R/processes/ps_state-$_.rrd:value:AVERAGE" } qw(blocked running stopped paging sleeping zombies)), "CDEF:busy=0".(join '', map { ",$_,+" } qw(running blocked paging)), "AREA:sleeping#ccc:sleeping:STACK", - "AREA:stopped#f00:stopped:STACK", - "AREA:zombies#0f0:zombie:STACK", - "AREA:busy#f00:busy:STACK", + "AREA:stopped#00f:stopped:STACK", + "AREA:zombies#ff0:zombie:STACK", + "AREA:busy#000:busy:STACK", ]); -graph('CPU', { Units => '[%]' }, +graph('General', 'CPU', { Units => '[%]' }, [ (map { my $thing= $_; @@ -55,67 +77,440 @@ graph('CPU', { Units => '[%]' }, (0..7)), "CDEF:$thing=0".join('', map { ",$thing$_,+" } (0..7)).",8.0,/"; } qw(idle interrupt nice softirq steal system user wait)), - "AREA:system#00f:system:STACK", - "AREA:wait#f88:wait:STACK", + "CDEF:allintr=softirq,steal,+,interrupt,+", + "AREA:allintr#ff0:interrupt:STACK", + "AREA:system#88f:system:STACK", + "AREA:user#00f:user:STACK", "AREA:nice#ccc:nice:STACK", - "AREA:user#080:user:STACK", - "AREA:softirq#f0f:softirq:STACK", - "AREA:interrupt#ff0:interrupt:STACK", - "AREA:steal#0ff:steal:STACK", + "AREA:wait#f00:wait:STACK", + ]); + +graph('General', 'Memory', { }, + [ '-b',1024, + (map { "DEF:swap_$_=$R/swap/swap-$_.rrd:value:AVERAGE" } + qw(free used cached)), + (map { "DEF:mem_$_=$R/memory/memory-$_.rrd:value:AVERAGE" } + qw(buffered free used cached)), + "CDEF:c_swap_used=0,swap_used,-", + "CDEF:c_swap_cached=0,swap_cached,-", + "CDEF:c_swap_free=0,swap_free,-", + "AREA:c_swap_used#000:used swap", + "AREA:c_swap_cached#888:\"cached\" swap:STACK", +# "AREA:c_swap_free#88f:free swap:STACK", + "AREA:mem_used#ff0:used memory", + "AREA:mem_buffered#00f:page cache:STACK", + "AREA:mem_cached#008:buffer cache:STACK", + "AREA:mem_free#ccc:unused memory:STACK", + ]); + +graph('General', 'Network', { Units => '[/sec; tx +ve; errs x1000]' }, + [ + (map { + ("DEF:tx_$_=$R/interface/if_$_-eth0.rrd:tx:AVERAGE", + "DEF:rx_$_=$R/interface/if_$_-eth0.rrd:rx:AVERAGE", + "CDEF:mrx_$_=0,rx_$_,-") + } qw(octets packets errors)), + (map { + ("CDEF:${_}_kb=${_}_octets,1024,/", + "CDEF:${_}_errsx=${_}_errors,1000,*") + } qw(mrx tx)), + "AREA:tx_kb#080:kby", + "LINE:tx_packets#0f0:pkts", + "LINE:tx_errsx#000:errs", + "AREA:mrx_kb#008:kby", + "LINE:mrx_packets#00f:pkts", + "LINE:mrx_errsx#444:errs", + ]); + +graph('General', 'Users', { }, + [ + "DEF:users=$R/users/users.rrd:users:AVERAGE", + "LINE:users#008:users" ]); +foreach my $src (<$R/df/df-*.rrd>) { + my $vol= $src; + $vol =~ s,\.rrd$,, or next; + $vol =~ s,.*/,,; + $vol =~ s,^df-,,; + graph('Disk space', $vol, { + Slower => 1, + }, + [ '-A','-l',0,'-r', + qw(-b 1024 -l 0), + (map { "DEF:$_=$src:$_:AVERAGE" } qw(free used)), + "AREA:used#000:used:STACK", + "AREA:free#88f:free:STACK", + ]); +} + +our %news_name_map; + +if (!open NM, '<', "$SELF/data/news/name-map") { + die unless $!==&ENOENT; +} else { + while () { + s/^\s*//; s/\s+$//; + next unless m/^[^\#]/; + m/^(\S+)\s+(in|out|\*)\s+(\S+)$/ or die; + if ($2 eq '*') { + $news_name_map{$1,$_}= $3 foreach qw(in out); + } else { + $news_name_map{$1,$2}= $3; + } + } +} + +our @news_graphs; + +foreach my $src (<$SELF/data/news/*.rrd>) { + my $site= $src; + $site =~ s,\.rrd$,, or next; + $site =~ s,.*/,,; + $site =~ s,_(in|out)$,,; + my $inout= $1; + $site =~ s/^([-.0-9a-z]+)_//; + my $us= $1; # all very well but we ignore it + my $newsite= $news_name_map{$site,$inout}; + $site= $newsite if defined $newsite; + next if $site eq '-'; + #my $sk= join '.', reverse split /\./, $site; + my $sk= $site; + $sk .= " $&" if $sk =~ s/^[^.]*(?:news|nntp|peer)[^.]*\.//; + $sk .= " $inout"; + push @news_graphs, [ $sk, $site, $inout, $src ]; +} + +foreach my $siteinfo (sort { $a->[0] cmp $b->[0] } @news_graphs) { + my ($sortkey, $site, $inout, $src)= @$siteinfo; + graph_of_group("News", $site, $inout, + { + Units => '[art/s]', + TimeRanges => [ map { $_*86400 } qw(1 7 31), 366, 366*3 ] + }, $inout eq 'out' ? + [ + (map { "DEF:$_=$src:$_:AVERAGE" } + qw(missing deferred unwanted accepted rejected body_missing)), + "AREA:accepted#00f:ok", + "AREA:body_missing#ff0:miss:STACK", + "AREA:rejected#f00:rej:STACK", + "AREA:unwanted#aaa:unw:STACK", + "AREA:deferred#ddd:defer:STACK", + ] : + [ + (map { "DEF:$_=$src:$_:AVERAGE" } + qw(accepted refused rejected duplicate)), + (map { ("DEF:bytes_$_=$src:${_}_size:AVERAGE", + "CDEF:kb_$_=bytes_$_,1024,/") + } qw(accepted duplicate)), + "AREA:accepted#00f:ok:STACK", + "AREA:rejected#f00:rej:STACK", + "AREA:duplicate#000:dupe:STACK", + "AREA:refused#aaa:unw:STACK", + "CDEF:kb_accepted_smooth=kb_accepted,,TREND", + "LINE:kb_duplicate#ff0:kb dupe", + "LINE:kb_accepted_smooth#008:~kb", + ]); +} + +our %disk_rdev2rrd; + +foreach my $physdiskrrd (<$R/disk-*/disk_octets.rrd>) { + $physdiskrrd =~ s,octets\.rrd$,, or die; + $physdiskrrd =~ m,-([^/]+)/disk_$, or die; + my $physdev= "/dev/$1"; + if (!stat $physdev) { + die "$physdev $!" unless $!==&ENOENT; + next; + } + die "$physdev ?" unless S_ISBLK((stat _)[2]); + $disk_rdev2rrd{(stat _)[6]}= $physdiskrrd; +} + +our @disk_vgs; + +sub lvgraphs { + my ($vg, $label, $factor, $rcolour, $wcolour) = @_; + my @lvs; + my $varname= $vg; + $varname =~ s/[^0-9a-zA-Y]/ sprintf "Z%02x", ord($&) /ge; + my $vginfo= { + Name => $label, + Varname => $varname, + Colour => { 'read' => $rcolour, 'write' => $wcolour }, + Lvs => [] + }; + foreach my $bo (qw(octets ops)) { + foreach my $rw (qw(read write)) { + $vginfo->{VarDefs}{$bo}{$rw}= []; + $vginfo->{Sumdef}{$bo}{$rw}= '0'; + } + } + my $ix=0; + foreach my $lvpath () { + my $lv= $lvpath; $lv =~ s,.*/,,; + if (!stat $lvpath) { + die "$lvpath $!" unless $!==&ENOENT; + next; + } + die "$lvpath ?" unless S_ISBLK((stat _)[2]); + my $rrd= $disk_rdev2rrd{(stat _)[6]}; + next unless defined $rrd; + + my $lvinfo= { Name => $lv }; + push @{ $vginfo->{Lvs} }, $lvinfo; + + foreach my $bo (qw(octets ops)) { + $lvinfo->{Defs}{$bo}= + [ + (map { ("DEF:$_=${rrd}${bo}.rrd:$_:AVERAGE") } qw(read write)), + "CDEF:mwrite=0,write,-", + "AREA:read#00f:read", + "AREA:mwrite#f00:write" + ]; + + foreach my $rw (qw(read write)) { + $ix++; + my $tvar= "lv_${rw}_${bo}_${varname}_${ix}"; + push @{ $vginfo->{VarDefs}{$bo}{$rw} }, + "DEF:$tvar=${rrd}${bo}.rrd:$rw:AVERAGE"; + $vginfo->{Sumdef}{$bo}{$rw} .= ",$tvar,+"; + } + } + } + foreach my $bo (qw(octets ops)) { + foreach my $rw (qw(read write)) { + my $defs= []; + push @$defs, @{ $vginfo->{VarDefs}{$bo}{$rw} }; + push @$defs, "CDEF:${rw}_vg_${varname}=". + $vginfo->{Sumdef}{$bo}{$rw}. + sprintf(",%f,*", $rw eq 'write' ? -$factor : $factor); + $vginfo->{Defs}{$bo}{$rw}= $defs; + } + } + push @disk_vgs, $vginfo; +} + +lvgraphs('vg-main', 'main', 1, qw(00f f00)); +lvgraphs('vg-chiark-stripe', 'stripe', 0.5, qw(008 800)); + +foreach my $bo (qw(octets ops)) { + my @a= (); + foreach my $rw (qw(read write)) { + my $stack= ''; + foreach my $vginfo (@disk_vgs) { + push @a, @{ $vginfo->{Defs}{$bo}{$rw} }; + push @a, "AREA:${rw}_vg_$vginfo->{Varname}#". + $vginfo->{Colour}{$rw}. + ":$vginfo->{Name} ".substr($rw,0,1). + $stack; + $stack= ':STACK'; + } + } + graph_of_group('IO', 'IO', $bo, { Units => '[/s]' }, \@a); +} + +foreach my $vginfo (@disk_vgs) { + foreach my $bo (qw(octets ops)) { + foreach my $lv (@{ $vginfo->{Lvs} }) { + graph_of_group('IO', "$vginfo->{Name} $lv->{Name}", + $bo, { Units => '[/s]' }, $lv->{Defs}{$bo}); + } + } +} + +push @{ $section_groups{General} }, { + Section => 'IO', + Group => 'IO', + UrlParams => "section=IO&sloth=SLOTH" +}; + +} +#---------- right, that was the initialisation ---------- + +our $self= url(-relative=>1); + if (param('debug')) { print "Content-Type: text/plain\n\n"; } -my $gname= param('graph'); +our @navsettings; + +@navsettings= (); + +sub navsetting ($) { + my ($nav) = @_; + my $var= $nav->{Variable}; + $$var= param($nav->{Param}); + $$var= $nav->{Default} if !defined $$var; + die $nav->{Param} unless grep { $_ eq $$var } @{ $nav->{Values} }; + push @navsettings, $nav; +} + +our $section; + +navsetting({ + Desc => 'Section', + Param => 'section', + Variable => \$section, + Default => $sections[0], + Values => [@sections], + Show => sub { return $_[0]; } +}); + + +sub num_param ($$$$) { + my ($param,$def,$min,$max) = @_; + my $v= param($param); + return $def if !defined $v; + $v =~ m/^([1-9]\d{0,8})$/ or die; + $v= $1; + die unless $v >= $min && $v <= $max; + return $v + 0; +} -if ($gname) { - my $g= $graphs{$gname}; +our $group= param('graph'); + +our $elem= param('elem'); +if (defined $elem) { + my $g= $graphs{$section,$group,$elem}; die unless $g; + my $width= num_param('w',370,100,1600); + my $height= num_param('h',200,100,1600); + + my $sloth= param('sloth'); + die unless defined $sloth; + $sloth =~ m/^(\d+)$/ or die; + $sloth= $1+0; + my $end= $g->{TimeRanges}[$sloth]; + die unless defined $end; + + my $cacheid= "$section!$group!$elem!$sloth!$width!$height"; + my $cachepath= "cache/$cacheid.png"; + my @args= @{ $g->{Args} }; + s,\, $end/$1 ,ge foreach @args; + unshift @args, qw(--end now --start), "end-${end}s"; + + my $title= $group; + if (length $elem) { $title.= " $elem"; } + + $title .= " $g->{Units}" if $g->{Units}; + unshift @args, '-t', $title, '-w',$width, '-h',$height; + unshift @args, qw(-a PNG --full-size-mode); - my $end= param('end'); - if (defined $end) { - $end =~ m/^(\d+)$/ or die; - unshift @args, qw(--end now --start), "end-${end}s"; - } if (param('debug')) { print((join "\n",@args),"\n"); exit 0; } - print "Content-Type: image/png\n\n"; - my $title= $gname; - $title .= " $g->{Units}" if $g->{Units}; - unshift @args, '-t', $title; - - exec qw(rrdtool graph - -a PNG --full-size-mode -w 380 -h 200), @args; +#print STDERR "||| ",(join ' ', map { "'$_'" } @args)." |||\n"; + exec(qw(sh -ec), <<'END', 'x', $cachepath, @args); + p="$1"; shift + rrdtool graph "$p" --lazy "$@" >/dev/null + printf "Content-Type: image/png\n\n" + exec cat "$p" +END die $!; } -my $detail= param('detail'); -if ($detail) { - die unless $graphs{$detail}; - print header(), start_html(), h1("$detail graphs"); - foreach my $end (qw(3600 86400 604800 2419200)) { - print "\n"; +sub start_page ($) { + my ($title) = @_; + print header(), start_html($title); + my $outerdelim= ''; + foreach my $nav (@navsettings) { + print $outerdelim; + print $nav->{Desc}, ": "; + my $delim= ''; + my $current= $nav->{Variable}; $current= $$current; + foreach my $couldbe (@{ $nav->{Values} }) { + print $delim; + my $show= $nav->{Show}($couldbe); + if ($couldbe eq $current) { + print "$show"; + } else { + my $u= $self; + my $delim2= '?'; + foreach my $nav2 (@navsettings) { + my $current2= $nav2->{Variable}; $current2= $$current2; + $current2= $couldbe if $nav2->{Param} eq $nav->{Param}; + next if $current2 eq $nav2->{Default}; + $u .= $delim2; $u .= "$nav2->{Param}=$current2"; + $delim2= '&'; + } + print a({href=>$u}, $show); + } + $delim= ' | '; + } + $outerdelim= "
\n"; + } + print "\n"; + + print h1("$title"); +} + +our $detail= param('detail'); +if (defined $detail) { + my $elems= $group_elems{$section,$detail}; + die unless $elems; + start_page("$detail - $section - graphs"); + foreach my $tsloth (0..5) { + foreach my $elem (@$elems) { + my $g= $graphs{$section,$detail,$elem}; + die unless $g; + next if $tsloth >= @{ $g->{TimeRanges} }; + my $imgurl= "$self?graph=$detail§ion=$section". + "&sloth=$tsloth&elem=$elem"; + print a({href=>"$imgurl&w=780&h=800"}, + img({src=>$imgurl, alt=>''})); + } } print end_html(); exit 0; } +our $sloth; + +navsetting({ + Desc => 'Time interval', + Param => 'sloth', + Variable => \$sloth, + Default => 1, + Values => [0..3], + Show => sub { + my ($sl) = @_; + return ('Narrower', 'Normal', 'Wider', 'Extra wide')[$sl]; + } +}); + if (param('debug')) { use Data::Dumper; print Dumper(\%graphs); exit 0; } -print header(), start_html(); -print h1('Graphs'); +start_page("$section - graphs"); -foreach my $gname (@graphs) { - print ""; #,h2($gname),""; - print "\n"; +foreach my $group (@{ $section_groups{$section} }) { + my $ref_group= $group; + my $ref_section= $section; + my $ref_urlparams= "detail=$group§ion=$section"; + if (ref $group) { + $ref_group= $group->{Group}; + $ref_section= $group->{Section}; + $ref_urlparams= $group->{UrlParams}; + $ref_urlparams =~ s/\bSLOTH\b/$sloth/; + } + print a({href=>"$self?$ref_urlparams"}); + my $imgurl= "$self?graph=$ref_group§ion=$ref_section"; + print ""; + my $elems= $group_elems{$ref_section,$ref_group}; + foreach my $elem (@$elems) { + my $g= $graphs{$ref_section,$ref_group,$elem}; + print img({src=>"$imgurl&elem=$elem&sloth=".($sloth + $g->{Slower}), + alt=>''}); + } + print ""; + print "\n"; }