root / plugins / other / proc_ @ e5ce7492
Historique | Voir | Annoter | Télécharger (12,4 ko)
| 1 | 94d9f9e0 | Trygve Vea | #!/usr/bin/perl |
|---|---|---|---|
| 2 | # -*- perl -*- |
||
| 3 | # |
||
| 4 | # proc_ - Munin plugin to for Process information |
||
| 5 | # Copyright (C) 2009 Redpill Linpro AS |
||
| 6 | # Copyright (C) 2010 Trygve Vea |
||
| 7 | # |
||
| 8 | # Author: Kristian Lyngstøl <kristian@redpill-linpro.com> |
||
| 9 | # Author: Trygve Vea <tv@redpill-linpro.com> |
||
| 10 | # |
||
| 11 | # This program is free software; you can redistribute it and/or modify |
||
| 12 | # it under the terms of the GNU General Public License as published by |
||
| 13 | # the Free Software Foundation; either version 2 of the License, or |
||
| 14 | # (at your option) any later version. |
||
| 15 | # |
||
| 16 | # This program is distributed in the hope that it will be useful, |
||
| 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 19 | # GNU General Public License for more details. |
||
| 20 | # |
||
| 21 | # You should have received a copy of the GNU General Public License along |
||
| 22 | # with this program; if not, write to the Free Software Foundation, Inc., |
||
| 23 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||
| 24 | |||
| 25 | =head1 NAME |
||
| 26 | |||
| 27 | proc_ - Munin plugin to monitor various aspects of named processes |
||
| 28 | |||
| 29 | =head1 APPLICABLE SYSTEMS |
||
| 30 | |||
| 31 | Processes running under Linux |
||
| 32 | |||
| 33 | =head1 CONFIGURATION |
||
| 34 | |||
| 35 | The plugin needs to be able to parse the /proc-filesystem. |
||
| 36 | |||
| 37 | The configuration section shows the defaults |
||
| 38 | [proc_*] |
||
| 39 | env.procname init |
||
| 40 | env.category Process Info |
||
| 41 | |||
| 42 | env.procname defines the processname as seen inside the paranthesis of the |
||
| 43 | second column in /proc/pid/stat. If you don't get the data you expect, you |
||
| 44 | can check if the value is what you expect here. |
||
| 45 | |||
| 46 | env.category is used to override the default category the plugin will show |
||
| 47 | up in. |
||
| 48 | |||
| 49 | =head1 INTERPRETATION |
||
| 50 | |||
| 51 | Each graph uses data from the proc filesystem. |
||
| 52 | |||
| 53 | =head1 MAGIC MARKERS |
||
| 54 | |||
| 55 | #%# family=auto |
||
| 56 | #%# capabilities=autoconf suggest |
||
| 57 | |||
| 58 | =head1 VERSION |
||
| 59 | |||
| 60 | $Id$ |
||
| 61 | |||
| 62 | =head1 BUGS |
||
| 63 | |||
| 64 | The CPU usage graph will be misleading in an event where you have multiple |
||
| 65 | processes monitored, but less then all of them is restarted (or exits). This |
||
| 66 | is due to the nature of counters, and I need to track state of individual |
||
| 67 | processes to do this in a reliable way. It's on my TODO. |
||
| 68 | |||
| 69 | =head1 PATCHES-TO |
||
| 70 | |||
| 71 | Dunno. |
||
| 72 | |||
| 73 | =head1 AUTHOR |
||
| 74 | |||
| 75 | Kristian Lyngstol <kristian@redpill-linpro.com> |
||
| 76 | Trygve Vea <tv@redpill-linpro.com> |
||
| 77 | |||
| 78 | =head1 THANKS |
||
| 79 | |||
| 80 | Thanks to Kristian Lyngstol, I stole most of the code in this plugin from his |
||
| 81 | varnish_-plugin, which is a really nice outline of how a wildcardplugin should |
||
| 82 | look like. |
||
| 83 | |||
| 84 | =head1 LICENSE |
||
| 85 | |||
| 86 | GPLv2 |
||
| 87 | |||
| 88 | =cut |
||
| 89 | |||
| 90 | use strict; |
||
| 91 | |||
| 92 | # Set to 1 to enable output when a variable is defined in a graph but |
||
| 93 | # omitted because it doesn't exist in varnishstat. |
||
| 94 | my $DEBUG = 0; |
||
| 95 | |||
| 96 | # Set to 1 to ignore 'DEBUG' and suggest all available aspects. |
||
| 97 | my $FULL_SUGGEST = 0; |
||
| 98 | |||
| 99 | # You should set the env-var "procname" to filter processes of their name. |
||
| 100 | # This will default to "init" unless you specify anything else. |
||
| 101 | my $procname = exists $ENV{'procname'} ? $ENV{'procname'} : "init";
|
||
| 102 | |||
| 103 | # You can set the env-var "procargs" to filter processes of their running |
||
| 104 | # arguments. |
||
| 105 | my $args = exists $ENV{'procargs'} ? $ENV{'procargs'} : undef;
|
||
| 106 | |||
| 107 | # You can set the env-var "category" to override the default category. |
||
| 108 | my $category = exists $ENV{'category'} ? $ENV{'category'} : "Process info";
|
||
| 109 | |||
| 110 | my %procstats = (); |
||
| 111 | my $self; |
||
| 112 | |||
| 113 | |||
| 114 | # Parameters that can be defined on top level of a graph. Config will print |
||
| 115 | # them as "graph_$foo $value\n" |
||
| 116 | my @graph_parameters = ('title','total','order','scale','vlabel','args');
|
||
| 117 | |||
| 118 | # Parameters that can be defined on a value-to-value basis and will be |
||
| 119 | # blindly passed to config. Printed as "$fieldname.$param $value\n". |
||
| 120 | my @field_parameters = ('graph', 'min', 'max', 'draw', 'cdef', 'warning',
|
||
| 121 | 'colour', 'info', 'type'); |
||
| 122 | # Data structure that defines all possible graphs (aspects) and how they |
||
| 123 | # are to be plotted. Every top-level entry is a graph/aspect. Each top-level graph |
||
| 124 | # MUST have title set and 'values'. |
||
| 125 | # |
||
| 126 | # Graphs with 'DEBUG' set to anything is omitted from 'suggest'. |
||
| 127 | # |
||
| 128 | # 'rpn' on values allows easy access to graphs consisting of multiple |
||
| 129 | # values from procstats. (Reverse polish notation). The RPN |
||
| 130 | # implementation only accepts +-*/ and procstats-values. |
||
| 131 | # |
||
| 132 | # Any value left undefined will be left up to Munin to define/ignore/yell |
||
| 133 | # about. |
||
| 134 | # |
||
| 135 | # See munin documentation or rrdgraph/rrdtool for more information. |
||
| 136 | my %ASPECTS = ( |
||
| 137 | 'cpu' => {
|
||
| 138 | 'title' => "CPU Usage: $procname", |
||
| 139 | 'order' => 'stime utime', |
||
| 140 | 'args' => '-l 0', |
||
| 141 | 'values' => {
|
||
| 142 | 'utime' => {
|
||
| 143 | 'type' => 'COUNTER', |
||
| 144 | 'label' => 'User time', |
||
| 145 | 'draw' => 'STACK' |
||
| 146 | }, |
||
| 147 | 'stime' => {
|
||
| 148 | 'type' => 'COUNTER', |
||
| 149 | 'label' => 'System time', |
||
| 150 | 'draw' => 'AREA' |
||
| 151 | } |
||
| 152 | } |
||
| 153 | }, |
||
| 154 | 'ctxt_switches' => {
|
||
| 155 | 'title' => "Context switches: $procname", |
||
| 156 | 'values' => {
|
||
| 157 | 'voluntary_ctxt_switches' => {
|
||
| 158 | 'type' => 'COUNTER', |
||
| 159 | 'label' => 'Voluntary Context Switches' |
||
| 160 | }, |
||
| 161 | 'nonvoluntary_ctxt_switches' => {
|
||
| 162 | 'type' => 'COUNTER', |
||
| 163 | 'label' => 'Nonvoluntary Context Switches' |
||
| 164 | } |
||
| 165 | } |
||
| 166 | }, |
||
| 167 | 'threads' => {
|
||
| 168 | 'title' => "Thread count: $procname", |
||
| 169 | 'values' => {
|
||
| 170 | 'threads' => {
|
||
| 171 | 'type' => 'GAUGE', |
||
| 172 | 'label' => 'Number of threads' |
||
| 173 | } |
||
| 174 | } |
||
| 175 | }, |
||
| 176 | 'processes' => {
|
||
| 177 | 'title' => "Process count: $procname", |
||
| 178 | 'values' => {
|
||
| 179 | 'processes' => {
|
||
| 180 | 'type' => 'GAUGE', |
||
| 181 | 'label' => 'Number of processes' |
||
| 182 | } |
||
| 183 | } |
||
| 184 | }, |
||
| 185 | 'memory' => {
|
||
| 186 | 'title' => "Memory usage: $procname", |
||
| 187 | 'vlabel' => 'bytes', |
||
| 188 | 'order' => 'VmStk VmExe VmLib VmData VmRSS VmSize', |
||
| 189 | 'args' => '-l 0', |
||
| 190 | 'values' => {
|
||
| 191 | 'VmSize' => {
|
||
| 192 | 'type' => 'GAUGE', |
||
| 193 | 'label' => 'Virtual Memory Size' |
||
| 194 | }, |
||
| 195 | 'VmRSS' => {
|
||
| 196 | 'type' => 'GAUGE', |
||
| 197 | 'label' => 'Resident set size', |
||
| 198 | }, |
||
| 199 | 'VmData' => {
|
||
| 200 | 'type' => 'GAUGE', |
||
| 201 | 'label' => 'Data size', |
||
| 202 | }, |
||
| 203 | 'VmStk' => {
|
||
| 204 | 'type' => 'GAUGE', |
||
| 205 | 'label' => 'Stack size', |
||
| 206 | }, |
||
| 207 | 'VmExe' => {
|
||
| 208 | 'type' => 'GAUGE', |
||
| 209 | 'label' => 'Segments size', |
||
| 210 | }, |
||
| 211 | 'VmLib' => {
|
||
| 212 | 'type' => 'GAUGE', |
||
| 213 | 'label' => 'Shared library size', |
||
| 214 | } |
||
| 215 | } |
||
| 216 | } |
||
| 217 | ); |
||
| 218 | |||
| 219 | # Populate %procstats with values. |
||
| 220 | sub populate_stats |
||
| 221 | {
|
||
| 222 | foreach my $line(`grep -h \\\($procname\\\) /proc/*/stat`) {
|
||
| 223 | if ($line =~ /^(\d+) \((.*)\) (.) \-?\d+ \-?\d+ \-?\d+ \-?\d+ \-?\d+ \d+ \d+ \d+ \d+ \d+ (\d+) (\d+) \d+ \d+ \d+ \d+ (\d+) \-?\d+ \d+ (\d+) (\d+)/) {
|
||
| 224 | $procstats{"utime"} += $4;
|
||
| 225 | $procstats{"stime"} += $5;
|
||
| 226 | $procstats{"threads"} += $6;
|
||
| 227 | $procstats{"vsize"} += $7;
|
||
| 228 | $procstats{"rss"} += $8;
|
||
| 229 | $procstats{"processes"} += 1;
|
||
| 230 | foreach my $line(`cat /proc/$1/status`){
|
||
| 231 | if ($line =~ /^Vm(.*):\s+(\d+) kB$/){
|
||
| 232 | $procstats{"Vm$1"} += ($2*1024);
|
||
| 233 | } |
||
| 234 | if ($line =~ /^(.*)_ctxt_switches:\s+(\d+)$/){
|
||
| 235 | $procstats{"$1_ctxt_switches"} += $2;
|
||
| 236 | } |
||
| 237 | } |
||
| 238 | } |
||
| 239 | } |
||
| 240 | } |
||
| 241 | |||
| 242 | # Bail-function. |
||
| 243 | sub usage |
||
| 244 | {
|
||
| 245 | if (defined(@_) && "@_" ne "") {
|
||
| 246 | print STDERR "@_" . "\n\n"; |
||
| 247 | } |
||
| 248 | print STDERR "Known arguments: suggest, config, autoconf.\n"; |
||
| 249 | print STDERR "Run with suggest to get a list of known aspects.\n"; |
||
| 250 | exit 1; |
||
| 251 | } |
||
| 252 | |||
| 253 | # Print 'yes' and exit true if it's reasonable to use this plugin. |
||
| 254 | # Otherwise exit with false and a human-readable reason. |
||
| 255 | sub autoconf |
||
| 256 | {
|
||
| 257 | print "no (Probably a yes, read about the plugin!)\n"; |
||
| 258 | exit 0; |
||
| 259 | } |
||
| 260 | |||
| 261 | # Suggest relevant aspects/values of $self. |
||
| 262 | # 'DEBUG'-graphs are excluded. |
||
| 263 | sub suggest |
||
| 264 | {
|
||
| 265 | foreach my $key (keys %ASPECTS) {
|
||
| 266 | if (defined($ASPECTS{$key}{'DEBUG'}) && $FULL_SUGGEST != 1) {
|
||
| 267 | next; |
||
| 268 | } |
||
| 269 | print "$key\n"; |
||
| 270 | } |
||
| 271 | } |
||
| 272 | |||
| 273 | # Print the value of a two-dimensional hash if it exist. |
||
| 274 | # Returns false if non-existant. |
||
| 275 | # |
||
| 276 | # Output is formatted for plugins if arg4 is blank, otherwise arg4 is used |
||
| 277 | # as the title/name of the field (ie: arg4=graph_title). |
||
| 278 | sub print_if_exist |
||
| 279 | {
|
||
| 280 | my %values = %{$_[0]};
|
||
| 281 | my $value = $_[1]; |
||
| 282 | my $field = $_[2]; |
||
| 283 | my $title = "$value.$field"; |
||
| 284 | if (defined($_[3])) {
|
||
| 285 | $title = $_[3]; |
||
| 286 | } |
||
| 287 | if (defined($values{$value}{$field})) {
|
||
| 288 | print "$title $values{$value}{$field}\n";
|
||
| 289 | } else {
|
||
| 290 | return 0; |
||
| 291 | } |
||
| 292 | } |
||
| 293 | |||
| 294 | # Walk through the relevant aspect and print all top-level configuration |
||
| 295 | # values and value-definitions. |
||
| 296 | sub get_config |
||
| 297 | {
|
||
| 298 | my $graph = $_[0]; |
||
| 299 | |||
| 300 | # Need to double-check since set_aspect only checks this if there |
||
| 301 | # is no argument (suggest/autoconf doesn't require a valid aspect) |
||
| 302 | if (!defined($ASPECTS{$graph})) {
|
||
| 303 | usage "No such aspect"; |
||
| 304 | } |
||
| 305 | my %values = %{$ASPECTS{$graph}{'values'}};
|
||
| 306 | |||
| 307 | print "graph_category $category\n"; |
||
| 308 | foreach my $field (@graph_parameters) {
|
||
| 309 | print_if_exist(\%ASPECTS,$graph,$field,"graph_$field"); |
||
| 310 | } |
||
| 311 | |||
| 312 | foreach my $value (keys %values) {
|
||
| 313 | # Need either RPN definition or a procstats value. |
||
| 314 | if (!defined($procstats{$value}) &&
|
||
| 315 | !defined($values{$value}{'rpn'})) {
|
||
| 316 | if ($DEBUG) {
|
||
| 317 | print "ERROR: $value not part of procstats.\n" |
||
| 318 | } |
||
| 319 | next; |
||
| 320 | } |
||
| 321 | |||
| 322 | if (!print_if_exist(\%values,$value,'label')) {
|
||
| 323 | print "$value.label ".$ASPECTS{$self}{'values'}{$value}{'label'}."\n";
|
||
| 324 | } |
||
| 325 | foreach my $field (@field_parameters) {
|
||
| 326 | print_if_exist(\%values,$value,$field); |
||
| 327 | } |
||
| 328 | } |
||
| 329 | } |
||
| 330 | |||
| 331 | # Read and verify the aspect ($self). |
||
| 332 | sub set_aspect |
||
| 333 | {
|
||
| 334 | $self = $0; |
||
| 335 | $self =~ s/^.*proc_//; |
||
| 336 | if (!defined($ASPECTS{$self}) && @ARGV == 0) {
|
||
| 337 | usage "No such aspect"; |
||
| 338 | } |
||
| 339 | } |
||
| 340 | |||
| 341 | # Handle arguments (config, autoconf, suggest) |
||
| 342 | # Populate stats for config is necessary, but we want to avoid it for |
||
| 343 | # autoconf as it would generate a nasty error. |
||
| 344 | sub check_args |
||
| 345 | {
|
||
| 346 | if (@ARGV && $ARGV[0] eq '') {
|
||
| 347 | shift @ARGV; |
||
| 348 | } |
||
| 349 | if (@ARGV == 1) {
|
||
| 350 | if ($ARGV[0] eq "config") {
|
||
| 351 | populate_stats; |
||
| 352 | get_config($self); |
||
| 353 | exit 0; |
||
| 354 | } elsif ($ARGV[0] eq "autoconf") {
|
||
| 355 | autoconf($self); |
||
| 356 | exit 0; |
||
| 357 | } elsif ($ARGV[0] eq "suggest") {
|
||
| 358 | # suggest; |
||
| 359 | exit 0; |
||
| 360 | } |
||
| 361 | usage "Unknown argument"; |
||
| 362 | } |
||
| 363 | } |
||
| 364 | |||
| 365 | # Braindead RPN: +,-,/,* will pop two items from @stack, and perform |
||
| 366 | # the relevant operation on the items. If the item in the array isn't one |
||
| 367 | # of the 4 basic math operations, a value from procstats is pushed on to |
||
| 368 | # the stack. IE: 'client_req','client_conn','/' will leave the value of |
||
| 369 | # "client_req/client_conn" on the stack. |
||
| 370 | # |
||
| 371 | # If only one item is left on the stack, it is printed. Otherwise, an error |
||
| 372 | # message is printed. |
||
| 373 | sub rpn |
||
| 374 | {
|
||
| 375 | my @stack; |
||
| 376 | my $left; |
||
| 377 | my $right; |
||
| 378 | foreach my $item (@{$_[0]}) {
|
||
| 379 | if ($item eq "+") {
|
||
| 380 | $right = pop(@stack); |
||
| 381 | $left = pop(@stack); |
||
| 382 | push(@stack,$left+$right); |
||
| 383 | } elsif ($item eq "-") {
|
||
| 384 | $right = pop(@stack); |
||
| 385 | $left = pop(@stack); |
||
| 386 | push(@stack,$left-$right); |
||
| 387 | } elsif ($item eq "/") {
|
||
| 388 | $right = pop(@stack); |
||
| 389 | $left = pop(@stack); |
||
| 390 | push(@stack,$left/$right); |
||
| 391 | } elsif ($item eq "*") {
|
||
| 392 | $right = pop(@stack); |
||
| 393 | $left = pop(@stack); |
||
| 394 | push(@stack,$left*$right); |
||
| 395 | } else {
|
||
| 396 | push(@stack,int($procstats{$item}));
|
||
| 397 | } |
||
| 398 | } |
||
| 399 | if (@stack > 1) |
||
| 400 | {
|
||
| 401 | print STDERR "RPN error: Stack has more than one item left.\n"; |
||
| 402 | print STDERR "@stack\n"; |
||
| 403 | exit 255; |
||
| 404 | } |
||
| 405 | print "@stack"; |
||
| 406 | print "\n"; |
||
| 407 | } |
||
| 408 | |||
| 409 | ################################ |
||
| 410 | # Execution starts here # |
||
| 411 | ################################ |
||
| 412 | |||
| 413 | set_aspect; |
||
| 414 | check_args; |
||
| 415 | populate_stats; |
||
| 416 | |||
| 417 | # We only get here if we're supposed to. |
||
| 418 | |||
| 419 | # Walks through the relevant values and either prints the procstat, or |
||
| 420 | # if the 'rpn' variable is set, calls rpn() to execute ... the rpn. |
||
| 421 | foreach my $value (keys %{$ASPECTS{$self}{'values'}}) {
|
||
| 422 | if (defined($ASPECTS{$self}{'values'}{$value}{'rpn'})) {
|
||
| 423 | print "$value.value "; |
||
| 424 | rpn($ASPECTS{$self}{'values'}{$value}{'rpn'});
|
||
| 425 | } else {
|
||
| 426 | print "$value.value "; |
||
| 427 | if (!defined($procstats{$value})) {
|
||
| 428 | print "0\n"; |
||
| 429 | next; |
||
| 430 | } |
||
| 431 | print "$procstats{$value}\n";
|
||
| 432 | } |
||
| 433 | } |
