Attachment 'jmetergraph.pl'

Download

   1 #!/usr/bin/perl
   2 #---
   3 # Original script by Christoph Meissner (email unknown)
   4 # Taken from http://wiki.apache.org/jakarta-jmeter-data/attachments/LogAnalysis/attachments/jmetergraph.pl
   5 # Fixes by George Barnett (george@alink.co.za)
   6 #
   7 # Fixes:
   8 # - Modified to 'use strict'
   9 # - Fixed scoping of various variables
  10 # - Added DEBUG print option
  11 #
  12 # Script Options:
  13 # perl jmetergraph.pl  [-alllb] [-stddev] [-range] <jtl file 1> [ ... <jtl file n>]
  14 # -alllb [Script does not draw stacked chart for requests that are < 1% of total.  This disables this behaviour]
  15 # -stddev [Will draw the std dev]
  16 # -range [Wil draw request range]
  17 #
  18 #---
  19 #
  20 # This script parses xml jtl files created by jmeter.
  21 # It extracts timestamps, active threads and labels from it.
  22 #
  23 # Further, based on this data
  24 # it builds below chart files (see sample files):
  25 #
  26 # 1. one chart containing the overall response times related to active threads.
  27 #    The resulting png file is named 'entire_user.png'.
  28 #
  29 # 2. one chart containing the overall response times related to throughput
  30 #    The resulting png file is named 'entire_throughput.png'.
  31 #
  32 # 3. one chart that will contain bars for each label
  33 #    which were stacked over response intervals (expressed in msec)
  34 #    The resulting png file is named 'ChartStacked.png'
  35 #    
  36 # 4. one chart that will contain stacked bars for each label
  37 #    which were stacked over response intervals (expressed in msec)
  38 #    The resulting png file is named 'ChartStackedPct.png'
  39 #
  40 # 5. one chart per label containing response times related to active threads and throughput
  41 #    This chart png is named like the label itself with the non-word characters substituted by '_'
  42 #    If it is related to active threads then '_user' is appended to the graph's file name -
  43 #    otherwise a '_throughput'.
  44 #
  45 # IMPORTANT:
  46 # ==========
  47 # The script works best with jmeter log format V2.1.
  48 # I never tested it on log's of either older or newer versions.
  49 # Also, I was too sluggish to include an XML parser.
  50 # Thus the script parses the jtl files by using regex.
  51 #
  52 # The graphs are built depending on the names of the labels of your requests.
  53 # Thus group your label names with this in mind
  54 # when you are about to create your jmeter testplan,
  55 #
  56 # Also, only the labels on the 1st level are considered.
  57 # They accumulate the response time, active users and throughput 
  58 # for all sub levels.
  59 #
  60 #
  61 # FAQ:
  62 # ====
  63 # How to make this script to create proper charts?
  64 #
  65 # Open your testplan with jmeter and
  66 # 1. insert listener 'Aggregate Report' (unless not present already).
  67 # 2. invoke 'Configure' there
  68 # 3. make sure that below checks are activated at least:
  69 #    'Save as XML'
  70 #    'Save Label'
  71 #    'Save Time Stamp'
  72 #    'Save Thread Name'
  73 #    'Save Active Thread Counts'
  74 #    'Save Elapsed Time'
  75 # 4. tell 'Aggregate Report' to write all data into a file
  76 # 5. when building your testplan
  77 #    you should name your critical request samplers in a way that data can be grouped into it
  78 #    (eg. 'Login', 'Host request', 'continue page', 'Req Database xyz', ...)
  79 #
  80 # Perl requisites:
  81 # 6. install the perl 'GD' package
  82 # 7. install the perl 'Chart' package
  83 # 8. test perl.
  84 #    This means that
  85 #     perl -MGD -MChart::Lines -MChart::Composite -MChart::StackedBars
  86 #    shouldn't fail
  87 #
  88 # To run the script:
  89 # perl jmetergraph.pl <jtl file1> <jtl file2> ... <jtl file_n>
  90 # 
  91 # The png graphs will be created in current working directory
  92 # 
  93 # I created this checklist to my best knowledge.
  94 # However, if it fails in your computer environment, ...
  95 # 
  96 #---
  97 
  98 use strict;
  99 use Chart::StackedBars;
 100 use Chart::Composite;
 101 use Chart::Lines;
 102 
 103 #---
 104 # arguments passed?
 105 #---
 106 my @files = grep {!/^-/ && -s "$_"} @ARGV;
 107 our @args = grep {/^-/ && !-f "$_"} @ARGV;
 108 
 109 my $DEBUG = 1;
 110 
 111 my %entire = ();
 112 my %glabels = ();
 113 my %gthreads = ();
 114 my $atflag = 0; # if active threads found then according graphs will be created
 115 #---
 116 # data received within this intervall will be averaged into one result
 117 #---
 118 
 119 my $collectinterval = 180;    # 60 seconds
 120 #---
 121 # cusps aggregate response times
 122 #---
 123 our @cusps = (200, 500, 1000, 2000, 5000, 10000, 60000);
 124 
 125 #---
 126 # labels determine the name of output charts
 127 #---
 128 our @labels = ();
 129 our @threads = ();
 130 
 131 #---
 132 # intermediate values
 133 #---
 134 our %timestamps = ();
 135 our $respcount = 0;
 136 our $measures = 0;
 137 
 138 my ($entireta,$entirecnt,$entireby);
 139 my ($respcount,$sumresptimes,$sumSQresptimes); 
 140 
 141 #---
 142 # define colors for stacked bar charts
 143 #---
 144 our %colors = (
 145   dataset0 => "green",
 146   dataset1 => [0, 139, 139], # dark cyan
 147   dataset2 => [255, 215,0],  # gold
 148   dataset3 => "DarkOrange",
 149   dataset4 => "red",
 150   dataset5 => [255, 0, 0],   # red
 151   dataset6 => [139, 0, 139], # dark magenta
 152   dataset7 => [0, 0, 0],     # black
 153 );
 154 
 155 #---
 156 # here we go thru all files and collect data
 157 #---
 158 
 159 while(my $file = shift(@files)) {
 160   print "Opening file $file\n" if $DEBUG;
 161   open(IN, "<$file") || do  {
 162     print $file, " ", $!, "\n";
 163     next;
 164   };
 165 
 166   print "Parsing data from $file\n" if $DEBUG;
 167   while(<IN>) {
 168     my ($time,$timestamp,$success,$label,$thread,$latency,$bytes,$DataEncoding,$DataType,$ErrorCount,$Hostname,$NumberOfActiveThreadsAll,$NumberOfActiveThreadsGroup,$ResponseCode,$ResponseMessage,$SampleCount);
 169     if(/^<(sample|httpSample)\s/) {
 170 
 171       ($time) = (/\st="(\d+)"/o);
 172       ($timestamp) = (/\sts="(\d+)"/o);
 173       ($success) = (/\ss="(.+?)"/o);
 174       ($label) = (/\slb="(.+?)"/o);
 175       ($thread) = (/\stn="(.+?)"/o);
 176       ($latency) = (/\slt="(\d+)"/o);
 177       ($bytes) = (/\sby="(\d+)"/o);
 178       ($DataEncoding) = (/\sde="(\d+)"/o);
 179       ($DataType) = (/\sdt="(.+?)"/o);
 180       ($ErrorCount) = (/\sec="(\d+)"/o);
 181       ($Hostname) = (/\shn="(.+?)"/o);
 182       ($NumberOfActiveThreadsAll) = (/\sna="(\d+)"/o);
 183       ($NumberOfActiveThreadsGroup) = (/\sng="(\d+)"/o);
 184       ($ResponseCode) = (/\src="(.+?)"/o);
 185       ($ResponseMessage) = (/\srm="(.+?)"/o);
 186       ($SampleCount) = (/\ssc="(\d+)"/o);
 187 
 188     } elsif(/^<sampleResult/) {
 189       ($time) = (/\stime="(\d+)"/o);
 190       ($timestamp) = (/timeStamp="(\d+)"/o);
 191       ($success) = (/success="(.+?)"/o);
 192       ($label) = (/label="(.+?)"/o);
 193       ($thread) = (/threadName="(.+?)"/o);
 194     } else {
 195       next;
 196     }
 197 
 198     $label =~ s/\s+$//g;
 199     $label =~ s/^\s+//g;
 200     $label =~ s/[\W\s]+/_/g;
 201 
 202     next if($label =~ /^garbage/i); # don't count these labels into statistics
 203 
 204     #---
 205     # memorize labels
 206     #---
 207           if(!grep(/^$label$/, @labels)) {
 208       push(@labels, $label);
 209       print "Found new label: $label\n" if $DEBUG;
 210     }
 211     $glabels{$label}{'respcount'} += 1;
 212     $entire{'respcount'} += 1;
 213 
 214     #---
 215     # memorize timestamps
 216     #---
 217 
 218     my $tstmp = int($timestamp / (1000 * $collectinterval)) * $collectinterval;
 219     $timestamps{$tstmp} += 1;
 220 
 221     #---
 222     # cusps
 223     #---
 224     for(my $i = 0; $i <= $#cusps; $i++) {
 225       if(($time <= $cusps[$i]) || (($i == $#cusps) && ($time > $cusps[$i]))) {
 226         $glabels{$label}{$cusps[$i]} += 1;
 227         $entire{$cusps[$i]} += 1;
 228         last;
 229       }
 230     }
 231     #---
 232     # stddev
 233     #---
 234     $respcount += 1;
 235     $sumresptimes += $time;
 236     $sumSQresptimes += ($time ** 2);
 237     if($respcount > 1) {
 238       my $stddev = sqrt(($respcount * $sumSQresptimes - $sumresptimes ** 2) /
 239         ($respcount * ($respcount - 1)));
 240 
 241       $entire{$tstmp, 'stddev'} = $glabels{$label}{$tstmp, 'stddev'} = $stddev;
 242 
 243     }
 244 
 245     #---
 246     # avg
 247     #---
 248     $entire{$tstmp, 'avg'} = $sumresptimes / $respcount;
 249 
 250     $glabels{$label}{$tstmp, 'responsetime'} += $time;
 251     $glabels{$label}{$tstmp, 'respcount'} += 1;
 252     $glabels{$label}{$tstmp, 'avg'} = int($glabels{$label}{$tstmp, 'responsetime'} / $glabels{$label}{$tstmp, 'respcount'});
 253 
 254     #---
 255     # active threads
 256     #---
 257 
 258     if(!$entire{$tstmp, 'activethreads'}) {
 259       $entireta = 0;
 260       $entirecnt = 0;
 261       $entireby = 0;
 262     }
 263 
 264     if($NumberOfActiveThreadsAll > 0) {
 265       $atflag = 1;
 266     }
 267 
 268     $entirecnt += 1;
 269 
 270     if($atflag == 1) {
 271       $entireta += $NumberOfActiveThreadsAll;
 272       $entire{$tstmp, 'activethreads'} = int($entireta / $entirecnt);
 273   
 274       if(!$glabels{$label}{$tstmp, 'activethreads'}) {
 275         $glabels{$label}{$tstmp, 'lbta'} = 0;
 276         $glabels{$label}{$tstmp, 'lbby'} = 0;
 277       }
 278       $glabels{$label}{$tstmp, 'lbta'} += $NumberOfActiveThreadsAll;
 279       $glabels{$label}{$tstmp, 'activethreads'} = sprintf("%.0f", $glabels{$label}{$tstmp, 'lbta'} / $glabels{$label}{$tstmp, 'respcount'});
 280 
 281     } else {
 282       #---
 283       # if NumberOfActiveThreads is not available
 284       # use threadname to extrapolate active threads later
 285       #---
 286       if($NumberOfActiveThreadsAll eq '') {
 287               if(!$gthreads{$thread}{'first'}) {
 288           $gthreads{$thread}{'first'} = $tstmp;
 289           push(@threads, $thread);
 290         }
 291   
 292         $gthreads{$thread}{'last'} = $tstmp;
 293       }
 294     }
 295 
 296     #---
 297     # throughput
 298     #---
 299     if($bytes > 0) {
 300       $entireby += $bytes;
 301       $entire{$tstmp, 'throughput'} = int($entireby / $entirecnt);
 302   
 303       $glabels{$label}{$tstmp, 'lbby'} += $bytes;
 304       $glabels{$label}{$tstmp, 'throughput'} = $glabels{$label}{$tstmp, 'lbby'}; # counts per $collectinterval
 305     }
 306 
 307   }
 308   print "Closing $file\n" if $DEBUG;
 309   close(IN);
 310 }
 311 
 312 print "Found $#labels labels\n" if $DEBUG;
 313 
 314 # Sort the labels.
 315 print "Sorting labels\n" if $DEBUG;
 316 my @tmplabels = sort @labels;
 317 @labels = @tmplabels;
 318 
 319 #---
 320 # if required (no NumbersOfActiveThreads)
 321 # then extrapolate users
 322 #---
 323 if($atflag == 0) {
 324   print "using timestamps to calculate active threads\n";
 325   my @tstmps = sort { $a <=> $b } keys(%timestamps);
 326   foreach my $label ('entire', @labels) {
 327     print "tracking $label\n";
 328     foreach my $thread (@threads) {
 329       foreach my $tstmp (@tstmps) {
 330         if($gthreads{$thread}{'first'} <= $tstmp && $gthreads{$thread}{'last'} >= $tstmp) {
 331           $glabels{$label}{$tstmp, 'activethreads'} += 1;
 332         }
 333       }
 334     }
 335   }
 336 }
 337 
 338 #---
 339 # charts will be created
 340 # if something could be parsed
 341 #---
 342 if($respcount > 0) {
 343   #---
 344   # number of time stamps
 345   #---
 346   $measures = scalar(keys(%timestamps));
 347 
 348   print "Generating stacked bars absolute\n" if $DEBUG;
 349   &ChartStackedBars();
 350 
 351   print "Generating stacked bars relative\n" if $DEBUG;
 352   &ChartStackedPct();
 353   
 354   foreach my $label ('entire', @labels) {
 355     if($entireby > 0) {
 356       &ChartLines($label, 'throughput');
 357     }
 358     &ChartLines($label, 'users');
 359   }
 360 }
 361 #-------------------------------------------------------------------------------
 362 sub ChartStackedPct {
 363 
 364   if(scalar(@labels) == 0) {
 365     return undef;
 366   }
 367 
 368   my $ChartStacked = Chart::StackedBars->new(1024, 768);
 369 
 370   #---
 371   # cusps
 372   #---
 373   my @xaxis = ();
 374   my @xlabels = ();
 375   foreach my $label (@labels) {
 376     print "Attempting to add $label to StackedPCT graph\n" if $DEBUG;
 377     if(($glabels{$label}{'respcount'} > ($respcount / 100)) || grep(/-alllb/i, @args)) {
 378       push(@xaxis, $label);
 379       push(@xlabels, $label);
 380       print " Added $label\n" if $DEBUG;
 381     }
 382   }
 383   $ChartStacked->add_dataset(@xlabels);
 384 
 385   my ($value,$i,$label);
 386   my @data = ();
 387   my @legend_labels = ();
 388 
 389   for($i = 0; $i <= $#cusps; $i++) {
 390     @data = ();
 391     foreach my $label (@xaxis) {
 392       $value = $glabels{$label}{$cusps[$i]};
 393       if(!defined $value) {
 394         $value = 0;
 395       }
 396       $value = (100 * $value) / $glabels{$label}{'respcount'};
 397       push(@data, $value);
 398     }
 399     $ChartStacked->add_dataset(@data);
 400 
 401     push(@legend_labels, "< " . $cusps[$i] . " msec");
 402   }
 403 
 404   my %settings = (
 405     transparent => 'true',
 406     title => 'Response Time %',
 407     y_grid_lines => 'true',
 408     legend => 'right',
 409     legend_labels => \@legend_labels,
 410     precision => 0,
 411     y_label => 'Requests %',
 412     max_val => 100,
 413     include_zero => 'true',
 414     point => 0,
 415     colors => \%colors,
 416     x_ticks => 'vertical',
 417     precision => 0,
 418   );
 419 
 420   $ChartStacked->set(%settings);
 421 
 422   print "Generated ChartStackedPct.png\n" if $DEBUG;
 423   $ChartStacked->png("ChartStackedPct.png");
 424 }
 425 #-------------------------------------------------------------------------------
 426 sub ChartStackedBars {
 427 
 428   if(scalar(@labels) == 0) {
 429     return undef;
 430   }
 431 
 432   my $ChartStacked = Chart::StackedBars->new(1024, 768);
 433 
 434   #---
 435   # cusps
 436   #---
 437   my @xaxis = ();
 438   my @xlabels = ();
 439   foreach my $label (@labels) {
 440     print "Added $label to StackedPCT graph\n" if $DEBUG;
 441     if(($glabels{$label}{'respcount'} > ($respcount / 100)) || grep(/-alllb/i, @args)) {
 442       push(@xaxis, $label);
 443       if(length($label) > 30) {
 444         push(@xlabels, substr($label, -30));
 445       } else {
 446         push(@xlabels, $label);
 447       }
 448     }
 449   }
 450   $ChartStacked->add_dataset(@xlabels);
 451 
 452   my ($value,$i,$label);
 453   my @data = ();
 454   my @legend_labels = ();
 455   for($i = 0; $i <= $#cusps; $i++) {
 456     @data = ();
 457     foreach my $label (@xaxis) {
 458       $value = $glabels{$label}{$cusps[$i]};
 459       if($value == undef) {
 460         $value = 0;
 461       }
 462       push(@data, $value);
 463     }
 464     $ChartStacked->add_dataset(@data);
 465 
 466     push(@legend_labels, "< " . $cusps[$i] . " msec");
 467   }
 468 
 469   my %settings = (
 470     transparent => 'true',
 471     title => 'Response Time',
 472     y_grid_lines => 'true',
 473     legend => 'right',
 474     legend_labels => \@legend_labels,
 475     precision => 0,
 476     y_label => 'Requests',
 477     include_zero => 'true',
 478     point => 0,
 479     colors => \%colors,
 480     x_ticks => 'vertical',
 481     precision => 0,
 482   );
 483 
 484   $ChartStacked->set(%settings);
 485 
 486   print "Generating ChartStacked.png\n" if $DEBUG;
 487   $ChartStacked->png("ChartStacked.png");
 488 }
 489 #-------------------------------------------------------------------------------
 490 sub ChartLines {
 491   my ($label, $mode) = @_;
 492 
 493   my %labelmap = (
 494     'entire' => 'total',
 495   );
 496 
 497   my $title = $label;
 498         $title = $labelmap{$label} if($labelmap{$label});
 499 
 500   my $ChartComposite = Chart::Composite->new(1024, 768);
 501 
 502   my @tstmps = sort { $a <=> $b } keys(%timestamps);
 503   my @responsetimes = ();
 504   my @plusstddev = ();
 505   my @minusstddev = ();
 506   my @users = ();
 507   my @throughput = ();
 508   my @xaxis = ();
 509   my $y2label;
 510 
 511 
 512   #---
 513   # response times
 514   #---
 515   my $tstmp;
 516   my ($pstd, $mstd) = (0, 0);
 517   foreach my $tstmp (@tstmps) {
 518     if($glabels{$label}{$tstmp, 'avg'}) {
 519       push(@xaxis, $tstmp);
 520       push(@responsetimes, $glabels{$label}{$tstmp, 'avg'});
 521 
 522       $mstd = $glabels{$label}{$tstmp, 'avg'} - $glabels{$label}{$tstmp, 'stddev'};
 523       $pstd = $glabels{$label}{$tstmp, 'avg'} + $glabels{$label}{$tstmp, 'stddev'};
 524       $mstd = 1 if($mstd < 0);  # supress lines below 0
 525       push(@plusstddev, $pstd);
 526       push(@minusstddev, $mstd);
 527     }
 528   }
 529   $ChartComposite->add_dataset(@xaxis);
 530   $ChartComposite->add_dataset(@responsetimes);
 531 
 532   my %colors = (
 533     dataset0 => "green",
 534     dataset1 => "red",
 535   );
 536   my @ds1 = (1);
 537   my @ds2 = (2);
 538   if(grep(/-stddev/ || /-range/, @args)) {
 539     $ChartComposite->add_dataset(@plusstddev);
 540     $ChartComposite->add_dataset(@minusstddev);
 541     @ds1 = (1, 2, 3);
 542     @ds2 = (4);
 543 
 544     %colors = (
 545       dataset0 => "green",
 546             dataset1 => [189, 183, 107],  # dark khaki
 547             dataset2 => [189, 183, 107],  # dark khaki
 548       dataset3 => "red",
 549     );
 550   }
 551 
 552   if($mode eq 'users') {
 553     #---
 554     # users
 555     #---
 556     foreach my $tstmp (@xaxis) {
 557       push(@users, $glabels{$label}{$tstmp, 'activethreads'});
 558     }
 559   
 560     $ChartComposite->add_dataset(@users);
 561     $y2label = "active threads";
 562   } else {
 563     #---
 564     # throughput
 565     #---
 566     foreach my $tstmp (@xaxis) {
 567       push(@throughput, $glabels{$label}{$tstmp, 'throughput'});
 568     }
 569     $ChartComposite->add_dataset(@throughput);
 570     $y2label = "throughput bytes/min";
 571   }
 572 
 573   my $skip = 0;
 574   if(scalar(@xaxis) > 40) {
 575     $skip = int(scalar(@xaxis) / 40) + 1;
 576   }
 577 
 578   my @labels = ($label, $mode);
 579   if(grep(/-stddev/, @args)) {
 580     @labels = ($label, "+stddev", "-stddev", $mode);
 581   }
 582 
 583   my $type = 'Lines';
 584   if(grep(/-range/i, @args)) {
 585     @labels = ($label, "n.a", "n.a", $mode);
 586     $type = 'ErrorBars';
 587   }
 588 
 589   my %settings = (
 590     composite_info => [ [$type, \@ds1], ['Lines', \@ds2 ]],
 591     transparent => 'true',
 592     title => 'Response Time ' . $title,
 593     y_grid_lines => 'true',
 594     legend => 'bottom',
 595     y_label => 'Response Time msec',
 596     y_label2 => $y2label,
 597     legend_labels => \@labels,
 598     legend_example_height => 1,
 599     legend_example_height0 => 10,
 600     legend_example_height1 => 2,
 601     legend_example_height2 => 2,
 602     legend_example_height3 => 10,
 603     legend_example_height4 => 10,
 604     include_zero => 'true',
 605     x_ticks => 'vertical',
 606     skip_x_ticks => $skip,
 607     brush_size1 => 3,
 608     brush_size2 => 3,
 609     pt_size => 6,
 610     point => 0,
 611     line => 1,
 612     f_x_tick => \&formatTime,
 613     colors => \%colors,
 614     precision => 0,
 615   );
 616 
 617   $ChartComposite->set(%settings);
 618 
 619   my $filename = $label;
 620   $filename=~ s/\W/_/g;
 621   $filename .= '_' . $mode . '.png';
 622   print $filename, "\n";
 623 
 624   $ChartComposite->png($filename);
 625 }
 626 #-------------------------------------------------------------------------------
 627 sub formatTime {
 628   my ($tstmp) = @_;
 629 
 630   my $string = scalar(localtime($tstmp));
 631 
 632   my ($rc) = ($string =~ /\s(\d\d:\d\d:\d\d)\s/);
 633 
 634   return $rc;
 635 }
 636 #-------------------------------------------------------------------------------
 637 #-------------------------------------------------------------------------------

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.