├── DrupalStress.jmx ├── README └── jmetergraph.pl /DrupalStress.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | outputDir 18 | /tmp 19 | = 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | urls.login 28 | /user/login 29 | = 30 | 31 | 32 | urls.logout 33 | /logout 34 | = 35 | 36 | 37 | host 38 | dev6.thinky 39 | = 40 | 41 | 42 | meta.name 43 | Drupal6 44 | = 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | urls.login 53 | /user/login 54 | = 55 | 56 | 57 | urls.logout 58 | /user/logout 59 | = 60 | 61 | 62 | host 63 | dev7.thinky 64 | = 65 | 66 | 67 | meta.name 68 | Drupal7 69 | = 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ${host} 79 | 80 80 | 81 | 82 | http 83 | 84 | 85 | 86 | 87 | 88 | 89 | MySQL server has gone away 90 | Unable to connect to database server 91 | 92 | false 93 | 6 94 | Assertion.response_data 95 | 96 | 97 | 98 | false 99 | 20 100 | 101 | 102 | 1184192411000 103 | stopthread 104 | 1 105 | 106 | false 107 | 10 108 | 109 | 1184192411000 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | /node 123 | GET 124 | true 125 | false 126 | true 127 | false 128 | 129 | 130 | 131 | false 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | /taxonomy/term/1 146 | GET 147 | true 148 | false 149 | true 150 | false 151 | 152 | 153 | 154 | false 155 | 156 | 157 | 158 | 159 | 160 | 161 | Accept-Language 162 | en-us,en;q=0.5 163 | 164 | 165 | Accept 166 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 167 | 168 | 169 | Keep-Alive 170 | 300 171 | 172 | 173 | User-Agent 174 | Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 175 | 176 | 177 | Accept-Encoding 178 | gzip,deflate 179 | 180 | 181 | Accept-Charset 182 | ISO-8859-1,utf-8;q=0.7,*;q=0.7 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | /node/1 199 | GET 200 | true 201 | false 202 | true 203 | false 204 | 205 | 206 | 207 | false 208 | 209 | 210 | 211 | 212 | 213 | 214 | Accept-Language 215 | en-us,en;q=0.5 216 | 217 | 218 | Accept 219 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 220 | 221 | 222 | Keep-Alive 223 | 300 224 | 225 | 226 | User-Agent 227 | Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 228 | 229 | 230 | Accept-Encoding 231 | gzip,deflate 232 | 233 | 234 | Accept-Charset 235 | ISO-8859-1,utf-8;q=0.7,*;q=0.7 236 | 237 | 238 | 239 | 240 | 241 | 242 | false 243 | 244 | saveConfig 245 | 246 | 247 | true 248 | true 249 | true 250 | 251 | true 252 | true 253 | true 254 | true 255 | false 256 | true 257 | true 258 | false 259 | false 260 | true 261 | false 262 | false 263 | false 264 | false 265 | false 266 | 0 267 | true 268 | 269 | 270 | 271 | 272 | 273 | 274 | false 275 | 276 | saveConfig 277 | 278 | 279 | true 280 | true 281 | true 282 | 283 | true 284 | true 285 | true 286 | true 287 | false 288 | true 289 | true 290 | false 291 | false 292 | false 293 | false 294 | false 295 | false 296 | false 297 | false 298 | 0 299 | 300 | 301 | ${outputDir}/${meta.name}/anon-summary.xml 302 | true 303 | 304 | 305 | 306 | 307 | false 308 | 10 309 | 310 | 311 | 1184192411000 312 | stopthread 313 | 1 314 | 315 | false 316 | 10 317 | 318 | 1184192411000 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | true 328 | false 329 | name 330 | editor 331 | = 332 | 333 | 334 | true 335 | false 336 | pass 337 | editor 338 | = 339 | 340 | 341 | true 342 | false 343 | form_id 344 | user_login 345 | = 346 | 347 | 348 | true 349 | false 350 | op 351 | Log+in 352 | = 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | ${urls.login} 363 | POST 364 | true 365 | false 366 | true 367 | false 368 | 369 | 370 | 371 | false 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | /node 387 | GET 388 | true 389 | false 390 | true 391 | false 392 | 393 | 394 | 395 | false 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | /taxonomy/term/1 410 | GET 411 | true 412 | false 413 | true 414 | false 415 | 416 | 417 | 418 | false 419 | 420 | 421 | 422 | 423 | 424 | 425 | Accept-Language 426 | en-us,en;q=0.5 427 | 428 | 429 | Accept 430 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 431 | 432 | 433 | Keep-Alive 434 | 300 435 | 436 | 437 | User-Agent 438 | Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 439 | 440 | 441 | Accept-Encoding 442 | gzip,deflate 443 | 444 | 445 | Accept-Charset 446 | ISO-8859-1,utf-8;q=0.7,*;q=0.7 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | /node/1 463 | GET 464 | true 465 | false 466 | true 467 | false 468 | 469 | 470 | 471 | false 472 | 473 | 474 | 475 | 476 | 477 | 478 | Accept-Language 479 | en-us,en;q=0.5 480 | 481 | 482 | Accept 483 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 484 | 485 | 486 | Keep-Alive 487 | 300 488 | 489 | 490 | User-Agent 491 | Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 492 | 493 | 494 | Accept-Encoding 495 | gzip,deflate 496 | 497 | 498 | Accept-Charset 499 | ISO-8859-1,utf-8;q=0.7,*;q=0.7 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | false 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | ${urls.logout} 523 | GET 524 | true 525 | false 526 | true 527 | false 528 | 529 | 530 | 531 | false 532 | 533 | 534 | 535 | 536 | 537 | false 538 | 539 | saveConfig 540 | 541 | 542 | true 543 | true 544 | true 545 | 546 | true 547 | true 548 | true 549 | true 550 | false 551 | true 552 | true 553 | false 554 | false 555 | true 556 | false 557 | false 558 | false 559 | false 560 | false 561 | 0 562 | true 563 | 564 | 565 | 566 | 567 | 568 | 569 | false 570 | 571 | saveConfig 572 | 573 | 574 | true 575 | true 576 | true 577 | 578 | true 579 | true 580 | true 581 | true 582 | false 583 | true 584 | true 585 | false 586 | false 587 | false 588 | false 589 | false 590 | false 591 | false 592 | false 593 | 0 594 | 595 | 596 | ${outputDir}/${meta.name}/auth-summary.xml 597 | true 598 | 599 | 600 | 601 | 602 | false 603 | 10 604 | 605 | 606 | 1181576981000 607 | stopthread 608 | 4 609 | 610 | false 611 | 10 612 | 613 | 1181576981000 614 | 615 | 616 | 617 | 618 | 619 | 620 | true 621 | false 622 | name 623 | editor 624 | = 625 | 626 | 627 | true 628 | false 629 | pass 630 | editor 631 | = 632 | 633 | 634 | true 635 | false 636 | form_id 637 | user_login 638 | = 639 | 640 | 641 | true 642 | false 643 | op 644 | Log+in 645 | = 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | ${urls.login} 656 | POST 657 | true 658 | false 659 | true 660 | false 661 | 662 | 663 | 664 | false 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | /user/ 679 | GET 680 | true 681 | false 682 | true 683 | false 684 | 685 | 686 | 687 | false 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | ${urls.logout} 702 | GET 703 | true 704 | false 705 | true 706 | false 707 | 708 | 709 | 710 | false 711 | 712 | 713 | 714 | 715 | 716 | false 717 | 718 | 719 | 720 | false 721 | 722 | saveConfig 723 | 724 | 725 | true 726 | true 727 | true 728 | 729 | true 730 | true 731 | true 732 | true 733 | false 734 | true 735 | true 736 | false 737 | false 738 | false 739 | false 740 | false 741 | false 742 | false 743 | false 744 | 0 745 | 746 | 747 | ${outputDir}/${meta.name}/user-summary.xml 748 | true 749 | 750 | 751 | 752 | 753 | false 754 | 755 | saveConfig 756 | 757 | 758 | true 759 | true 760 | true 761 | 762 | true 763 | true 764 | true 765 | true 766 | false 767 | true 768 | true 769 | false 770 | false 771 | false 772 | false 773 | false 774 | false 775 | false 776 | false 777 | 0 778 | 779 | 780 | 781 | 782 | 783 | 784 | false 785 | 786 | saveConfig 787 | 788 | 789 | true 790 | true 791 | true 792 | 793 | true 794 | true 795 | true 796 | true 797 | false 798 | true 799 | true 800 | false 801 | false 802 | false 803 | false 804 | false 805 | false 806 | false 807 | false 808 | 0 809 | 810 | 811 | ${outputDir}/${meta.name}/overall-summary.xml 812 | true 813 | 814 | 815 | 816 | false 817 | 818 | saveConfig 819 | 820 | 821 | true 822 | true 823 | true 824 | 825 | true 826 | true 827 | true 828 | true 829 | false 830 | true 831 | true 832 | false 833 | false 834 | false 835 | false 836 | false 837 | false 838 | false 839 | false 840 | 0 841 | 842 | 843 | ${outputDir}/${meta.name}/overall-graph.xml 844 | 845 | 846 | 847 | 848 | 849 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Drupal Performance Testing Suite. 2 | To run: 3 | 1. Open up the jmx file 4 | 2. Enable either Drupal-6 or Drupal-7 config sections (not both). 5 | 3. Edit the config section and change the host to reflect your target machine 6 | 4. (optional) change the output location in the general test configuration 7 | 5. (optional) run jmetergraph.pl on the *summary.xml files to generate charts. 8 | -------------------------------------------------------------------------------- /jmetergraph.pl: -------------------------------------------------------------------------------- 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] [ ... ] 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 ... 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 | # Some variables. Cunt who wrote this script didn't use strict so it's fucked ITO scoping 139 | my ($entireta,$entirecnt,$entireby); 140 | my ($respcount,$sumresptimes,$sumSQresptimes); 141 | 142 | #--- 143 | # define colors for stacked bar charts 144 | #--- 145 | our %colors = ( 146 | dataset0 => "green", 147 | dataset1 => [0, 139, 139], # dark cyan 148 | dataset2 => [255, 215,0], # gold 149 | dataset3 => "DarkOrange", 150 | dataset4 => "red", 151 | dataset5 => [255, 0, 0], # red 152 | dataset6 => [139, 0, 139], # dark magenta 153 | dataset7 => [0, 0, 0], # black 154 | ); 155 | 156 | #--- 157 | # here we go thru all files and collect data 158 | #--- 159 | 160 | while(my $file = shift(@files)) { 161 | print "Opening file $file\n" if $DEBUG; 162 | open(IN, "<$file") || do { 163 | print $file, " ", $!, "\n"; 164 | next; 165 | }; 166 | 167 | print "Parsing data from $file\n" if $DEBUG; 168 | while() { 169 | my ($time,$timestamp,$success,$label,$thread,$latency,$bytes,$DataEncoding,$DataType,$ErrorCount,$Hostname,$NumberOfActiveThreadsAll,$NumberOfActiveThreadsGroup,$ResponseCode,$ResponseMessage,$SampleCount); 170 | if(/^<(sample|httpSample)\s/) { 171 | 172 | ($time) = (/\st="(\d+)"/o); 173 | ($timestamp) = (/\sts="(\d+)"/o); 174 | ($success) = (/\ss="(.+?)"/o); 175 | ($label) = (/\slb="(.+?)"/o); 176 | ($thread) = (/\stn="(.+?)"/o); 177 | ($latency) = (/\slt="(\d+)"/o); 178 | ($bytes) = (/\sby="(\d+)"/o); 179 | ($DataEncoding) = (/\sde="(\d+)"/o); 180 | ($DataType) = (/\sdt="(.+?)"/o); 181 | ($ErrorCount) = (/\sec="(\d+)"/o); 182 | ($Hostname) = (/\shn="(.+?)"/o); 183 | ($NumberOfActiveThreadsAll) = (/\sna="(\d+)"/o); 184 | ($NumberOfActiveThreadsGroup) = (/\sng="(\d+)"/o); 185 | ($ResponseCode) = (/\src="(.+?)"/o); 186 | ($ResponseMessage) = (/\srm="(.+?)"/o); 187 | ($SampleCount) = (/\ssc="(\d+)"/o); 188 | 189 | } elsif(/^ $cusps[$i]))) { 227 | $glabels{$label}{$cusps[$i]} += 1; 228 | $entire{$cusps[$i]} += 1; 229 | last; 230 | } 231 | } 232 | #--- 233 | # stddev 234 | #--- 235 | $respcount += 1; 236 | $sumresptimes += $time; 237 | $sumSQresptimes += ($time ** 2); 238 | if($respcount > 1) { 239 | my $stddev = sqrt(($respcount * $sumSQresptimes - $sumresptimes ** 2) / 240 | ($respcount * ($respcount - 1))); 241 | 242 | $entire{$tstmp, 'stddev'} = $glabels{$label}{$tstmp, 'stddev'} = $stddev; 243 | 244 | } 245 | 246 | #--- 247 | # avg 248 | #--- 249 | $entire{$tstmp, 'avg'} = $sumresptimes / $respcount; 250 | 251 | $glabels{$label}{$tstmp, 'responsetime'} += $time; 252 | $glabels{$label}{$tstmp, 'respcount'} += 1; 253 | $glabels{$label}{$tstmp, 'avg'} = int($glabels{$label}{$tstmp, 'responsetime'} / $glabels{$label}{$tstmp, 'respcount'}); 254 | 255 | #--- 256 | # active threads 257 | #--- 258 | 259 | if(!$entire{$tstmp, 'activethreads'}) { 260 | $entireta = 0; 261 | $entirecnt = 0; 262 | $entireby = 0; 263 | } 264 | 265 | if($NumberOfActiveThreadsAll > 0) { 266 | $atflag = 1; 267 | } 268 | 269 | $entirecnt += 1; 270 | 271 | if($atflag == 1) { 272 | $entireta += $NumberOfActiveThreadsAll; 273 | $entire{$tstmp, 'activethreads'} = int($entireta / $entirecnt); 274 | 275 | if(!$glabels{$label}{$tstmp, 'activethreads'}) { 276 | $glabels{$label}{$tstmp, 'lbta'} = 0; 277 | $glabels{$label}{$tstmp, 'lbby'} = 0; 278 | } 279 | $glabels{$label}{$tstmp, 'lbta'} += $NumberOfActiveThreadsAll; 280 | $glabels{$label}{$tstmp, 'activethreads'} = sprintf("%.0f", $glabels{$label}{$tstmp, 'lbta'} / $glabels{$label}{$tstmp, 'respcount'}); 281 | 282 | } else { 283 | #--- 284 | # if NumberOfActiveThreads is not available 285 | # use threadname to extrapolate active threads later 286 | #--- 287 | if($NumberOfActiveThreadsAll eq '') { 288 | if(!$gthreads{$thread}{'first'}) { 289 | $gthreads{$thread}{'first'} = $tstmp; 290 | push(@threads, $thread); 291 | } 292 | 293 | $gthreads{$thread}{'last'} = $tstmp; 294 | } 295 | } 296 | 297 | #--- 298 | # throughput 299 | #--- 300 | if($bytes > 0) { 301 | $entireby += $bytes; 302 | $entire{$tstmp, 'throughput'} = int($entireby / $entirecnt); 303 | 304 | $glabels{$label}{$tstmp, 'lbby'} += $bytes; 305 | $glabels{$label}{$tstmp, 'throughput'} = $glabels{$label}{$tstmp, 'lbby'}; # counts per $collectinterval 306 | } 307 | 308 | } 309 | print "Closing $file\n" if $DEBUG; 310 | close(IN); 311 | } 312 | 313 | print "Found $#labels labels\n" if $DEBUG; 314 | 315 | # Sort the labels. 316 | print "Sorting labels\n" if $DEBUG; 317 | my @tmplabels = sort @labels; 318 | @labels = @tmplabels; 319 | 320 | #--- 321 | # if required (no NumbersOfActiveThreads) 322 | # then extrapolate users 323 | #--- 324 | if($atflag == 0) { 325 | print "using timestamps to calculate active threads\n"; 326 | my @tstmps = sort { $a <=> $b } keys(%timestamps); 327 | foreach my $label ('entire', @labels) { 328 | print "tracking $label\n"; 329 | foreach my $thread (@threads) { 330 | foreach my $tstmp (@tstmps) { 331 | if($gthreads{$thread}{'first'} <= $tstmp && $gthreads{$thread}{'last'} >= $tstmp) { 332 | $glabels{$label}{$tstmp, 'activethreads'} += 1; 333 | } 334 | } 335 | } 336 | } 337 | } 338 | 339 | #--- 340 | # charts will be created 341 | # if something could be parsed 342 | #--- 343 | if($respcount > 0) { 344 | #--- 345 | # number of time stamps 346 | #--- 347 | $measures = scalar(keys(%timestamps)); 348 | 349 | print "Generating stacked bars absolute\n" if $DEBUG; 350 | &ChartStackedBars(); 351 | 352 | print "Generating stacked bars relative\n" if $DEBUG; 353 | &ChartStackedPct(); 354 | 355 | foreach my $label ('entire', @labels) { 356 | if($entireby > 0) { 357 | &ChartLines($label, 'throughput'); 358 | } 359 | &ChartLines($label, 'users'); 360 | } 361 | } 362 | #------------------------------------------------------------------------------- 363 | sub ChartStackedPct { 364 | 365 | if(scalar(@labels) == 0) { 366 | return undef; 367 | } 368 | 369 | my $ChartStacked = Chart::StackedBars->new(1024, 768); 370 | 371 | #--- 372 | # cusps 373 | #--- 374 | my @xaxis = (); 375 | my @xlabels = (); 376 | foreach my $label (@labels) { 377 | print "Attempting to add $label to StackedPCT graph\n" if $DEBUG; 378 | if(($glabels{$label}{'respcount'} > ($respcount / 100)) || grep(/-alllb/i, @args)) { 379 | push(@xaxis, $label); 380 | if (length $label > 25) { 381 | $label = substr($label,0,25) . " " . substr($label,25); 382 | } 383 | push(@xlabels, $label); 384 | print " Added $label\n" if $DEBUG; 385 | } 386 | } 387 | $ChartStacked->add_dataset(@xlabels); 388 | 389 | my ($value,$i,$label); 390 | my @data = (); 391 | my @legend_labels = (); 392 | 393 | for($i = 0; $i <= $#cusps; $i++) { 394 | @data = (); 395 | foreach my $label (@xaxis) { 396 | $value = $glabels{$label}{$cusps[$i]}; 397 | if(!defined $value) { 398 | $value = 0; 399 | } 400 | $value = (100 * $value) / $glabels{$label}{'respcount'}; 401 | push(@data, $value); 402 | } 403 | $ChartStacked->add_dataset(@data); 404 | 405 | push(@legend_labels, "< " . $cusps[$i] . " msec"); 406 | } 407 | 408 | my %settings = ( 409 | transparent => 'true', 410 | title => 'Response Time %', 411 | y_grid_lines => 'true', 412 | legend => 'right', 413 | legend_labels => \@legend_labels, 414 | precision => 0, 415 | y_label => 'Requests %', 416 | max_val => 100, 417 | include_zero => 'true', 418 | point => 0, 419 | colors => \%colors, 420 | x_ticks => 'vertical', 421 | precision => 0, 422 | ); 423 | 424 | $ChartStacked->set(%settings); 425 | 426 | print "Generated ChartStackedPct.png\n" if $DEBUG; 427 | $ChartStacked->png("ChartStackedPct.png"); 428 | } 429 | #------------------------------------------------------------------------------- 430 | sub ChartStackedBars { 431 | 432 | if(scalar(@labels) == 0) { 433 | return undef; 434 | } 435 | 436 | my $ChartStacked = Chart::StackedBars->new(1024, 768); 437 | 438 | #--- 439 | # cusps 440 | #--- 441 | my @xaxis = (); 442 | my @xlabels = (); 443 | foreach my $label (@labels) { 444 | print "Added $label to StackedPCT graph\n" if $DEBUG; 445 | if(($glabels{$label}{'respcount'} > ($respcount / 100)) || grep(/-alllb/i, @args)) { 446 | push(@xaxis, $label); 447 | push(@xlabels, $label); 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 | #------------------------------------------------------------------------------- 638 | 639 | --------------------------------------------------------------------------------