PageRenderTime 371ms CodeModel.GetById 3ms app.highlight 350ms RepoModel.GetById 1ms app.codeStats 1ms

/postproc-lib/lib/SP/Endurance/GraphGenerators.pm

https://github.com/mkosola/sp-endurance
Perl | 2782 lines | 2281 code | 474 blank | 27 comment | 95 complexity | f9cb0ab544d78530ee0e7a560f41532b MD5 | raw file
   1# This file is part of sp-endurance.
   2#
   3# vim: ts=4:sw=4:et
   4#
   5# Copyright (C) 2010-2012 by Nokia Corporation
   6#
   7# Contact: Eero Tamminen <eero.tamminen@nokia.com>
   8#
   9# This program is free software; you can redistribute it and/or
  10# modify it under the terms of the GNU General Public License
  11# version 2 as published by the Free Software Foundation.
  12#
  13# This program is distributed in the hope that it will be useful, but
  14# WITHOUT ANY WARRANTY; without even the implied warranty of
  15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16# General Public License for more details.
  17#
  18# You should have received a copy of the GNU General Public License
  19# along with this program; if not, write to the Free Software
  20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  21# 02110-1301 USA
  22
  23package SP::Endurance::GraphGenerators;
  24
  25require Exporter;
  26@ISA = qw/Exporter/;
  27@EXPORT_OK = qw/graph_generators get_plots/;
  28
  29use SP::Endurance::Parser;
  30use SP::Endurance::Util qw/b2mb kb2mb nonzero has_changes max_change
  31    cumulative_to_changes change_per_second/;
  32
  33use List::Util qw/max sum/;
  34use List::MoreUtils qw/uniq zip any/;
  35use POSIX qw/ceil/;
  36use Data::Dumper;
  37
  38no warnings 'uninitialized';
  39eval 'use common::sense';
  40use strict;
  41
  42sub CGROUP_UNLIMITED()     { 9223372036854775807 }
  43sub CLK_TCK()              { 100 }
  44sub PAGE_SIZE()            { 4096 }
  45sub SECTOR_SIZE()          { 512 }
  46sub SHM_LOCKED()           { 02000 }
  47
  48my @plots;
  49my @generators;
  50
  51sub graph_generators { @generators }
  52sub get_plots        { sort { $a->{key} cmp $b->{key} } @plots }
  53
  54sub register_generator {
  55    my $g = shift;
  56    return unless ref $g eq 'CODE';
  57    push @generators, $g;
  58}
  59
  60our $done_plotting_cb;
  61
  62sub done_plotting {
  63    my $plot = shift;
  64    foreach ($plot->done_plotting) {
  65        push @plots, $_;
  66        $done_plotting_cb->($_) if ref $done_plotting_cb eq 'CODE';
  67    }
  68}
  69
  70my %pid_to_cmdline;
  71
  72sub pid_to_cmdline {
  73    my $masterdb = shift;
  74    my $pid = shift;
  75
  76    return unless $pid;
  77
  78    unless (defined $pid_to_cmdline{$pid}) {
  79        my @cmdlines = uniq grep { defined && length } map {
  80            if (exists $_->{'/proc/pid/cmdline'} and exists $_->{'/proc/pid/cmdline'}->{$pid}) {
  81                $_->{'/proc/pid/cmdline'}->{$pid}
  82            } elsif (exists $_->{'/proc/pid/smaps'}->{$pid} and exists $_->{'/proc/pid/smaps'}->{$pid}->{'#Name'}) {
  83                $_->{'/proc/pid/smaps'}->{$pid}->{'#Name'}
  84            } else {
  85                undef
  86            }
  87        } @$masterdb;
  88
  89        $pid_to_cmdline{$pid} = join(' / ', @cmdlines);
  90    }
  91
  92    join(': ', $pid, $pid_to_cmdline{$pid})
  93}
  94
  95sub sum_smaps {
  96    my $masterdb = shift;
  97    my $smaps_key = shift;
  98
  99    return map {
 100        my $entry = $_;
 101        if (exists $entry->{'/proc/pid/smaps'}) {
 102            sum map {
 103                my $pid = $_;
 104                exists $entry->{'/proc/pid/smaps'}->{$pid} &&
 105                exists $entry->{'/proc/pid/smaps'}->{$pid}->{"total_${smaps_key}"} ?
 106                       $entry->{'/proc/pid/smaps'}->{$pid}->{"total_${smaps_key}"} : undef
 107            } keys %{$entry->{'/proc/pid/smaps'}}
 108        } else { undef }
 109    } @$masterdb;
 110}
 111
 112sub generate_plot_system_memory_1 {
 113    my $plotter = shift;
 114    my $masterdb = shift;
 115
 116    my $plot = $plotter->new_linespoints(
 117        key => '2200_system_memory_1',
 118        label => 'System-level memory 1',
 119        legend => 'SYSTEM MEMORY 1',
 120        ylabel => 'MB',
 121    );
 122
 123    $plot->push(
 124        [kb2mb nonzero map { $_->{'/proc/meminfo'}->{SwapTotal} -
 125            $_->{'/proc/meminfo'}->{SwapFree} } @$masterdb],
 126        lw => 5, lc => 'FF0000', title => 'Swap used',
 127    );
 128    $plot->push(
 129        [kb2mb nonzero sum_smaps($masterdb, 'Swap')],
 130        lc => 'FF0000', title => 'Sum of swapped in applications',
 131    );
 132    foreach my $key (qw/SwapCached MemFree Cached Active(file)
 133                Inactive(file) Active(anon) Inactive(anon) Shmem/) {
 134        $plot->push(
 135            [kb2mb nonzero map { $_->{'/proc/meminfo'}->{$key} } @$masterdb],
 136            title => $key,
 137        );
 138    }
 139    $plot->push(
 140        [kb2mb nonzero sum_smaps($masterdb, 'Pss')],
 141        title => 'Sum of PSS',
 142    );
 143
 144    $plot->sort(sub { shift->[-1] });
 145
 146    done_plotting $plot;
 147}
 148BEGIN { register_generator \&generate_plot_system_memory_1; }
 149
 150sub generate_plot_system_memory_2 {
 151    my $plotter = shift;
 152    my $masterdb = shift;
 153
 154    my $plot = $plotter->new_linespoints(
 155        key => '2200_system_memory_2',
 156        label => 'System-level memory 2',
 157        legend => 'SYSTEM MEMORY 2',
 158        ylabel => 'MB',
 159    );
 160
 161    foreach my $key (qw/Dirty Buffers Mlocked PageTables KernelStack
 162                SReclaimable SUnreclaim/) {
 163        $plot->push(
 164            [kb2mb nonzero map { $_->{'/proc/meminfo'}->{$key} } @$masterdb],
 165            title => {
 166                SReclaimable => 'SlabReclaimable',
 167                SUnreclaim   => 'SlabUnreclaimable',
 168            }->{$key} // $key,
 169        );
 170    }
 171
 172    $plot->push(
 173        [b2mb nonzero map {
 174            my $sum;
 175            if (exists $_->{'/usr/bin/xmeminfo'}) {
 176                foreach my $xmem_entry (@{$_->{'/usr/bin/xmeminfo'}}) {
 177                    $sum += $xmem_entry->{Pixmap_mem}
 178                }
 179            }
 180            $sum;
 181        } @$masterdb],
 182        title => 'Pixmaps',
 183    );
 184
 185    $plot->sort(sub { shift->[-1] });
 186
 187    done_plotting $plot;
 188}
 189BEGIN { register_generator \&generate_plot_system_memory_2; }
 190
 191sub generate_plot_slab_sizes {
 192    my $plotter = shift;
 193    my $masterdb = shift;
 194
 195    my $plot = $plotter->new_histogram(
 196        key => '2255_slabs',
 197        label => 'Kernel slab memory',
 198        legend => 'KERNEL SLABS',
 199        ylabel => 'MB',
 200    );
 201
 202    my @slabs = uniq sort map { keys %{$_->{'/proc/slabinfo'}} } @$masterdb;
 203
 204    foreach my $slab (@slabs) {
 205        $plot->push(
 206            [nonzero kb2mb map { $_->{'/proc/slabinfo'}->{$slab} } @$masterdb],
 207            title => $slab,
 208        );
 209    }
 210
 211    $plot->sort(sub { shift->[-1] });
 212
 213    done_plotting $plot;
 214}
 215BEGIN { register_generator \&generate_plot_slab_sizes; }
 216
 217sub generate_plot_slab_changes {
 218    my $plotter = shift;
 219    my $masterdb = shift;
 220
 221    my $plot = $plotter->new_linespoints(
 222        key => '2256_slabs_changes',
 223        label => 'Kernel slab memory (changed only)',
 224        legend => 'KERNEL SLABS CHANGES',
 225        ylabel => 'MB',
 226    );
 227
 228    my @slabs = uniq sort map { keys %{$_->{'/proc/slabinfo'}} } @$masterdb;
 229
 230    foreach my $slab (@slabs) {
 231        $plot->push(
 232            [has_changes kb2mb nonzero map { $_->{'/proc/slabinfo'}->{$slab} } @$masterdb],
 233            title => $slab,
 234        );
 235    }
 236
 237    $plot->sort(sub { shift->[-1] });
 238
 239    done_plotting $plot;
 240}
 241BEGIN { register_generator \&generate_plot_slab_changes; }
 242
 243sub generate_plot_ctx_total {
 244    my $plotter = shift;
 245    my $masterdb = shift;
 246
 247    my $plot = $plotter->new_linespoints(
 248        key => '1165_ctx_total',
 249        label => 'Voluntary + non-voluntary context switches per second per process',
 250        legend => 'CTX TOTAL',
 251        ylabel => 'count per second',
 252    );
 253
 254    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
 255
 256    foreach my $pid (@pids) {
 257        my @total_ctx = map {
 258            if (exists $_->{'/proc/pid/status'}->{$pid}) {
 259                my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
 260                exists $entry{voluntary_ctxt_switches} &&
 261                exists $entry{nonvoluntary_ctxt_switches} ?
 262                       $entry{voluntary_ctxt_switches} + $entry{nonvoluntary_ctxt_switches} :
 263                       undef
 264            } else { undef }
 265        } @$masterdb;
 266        $plot->push(
 267            [nonzero change_per_second $masterdb, cumulative_to_changes @total_ctx],
 268            title => pid_to_cmdline($masterdb, $pid),
 269        );
 270    }
 271
 272    $plot->sort(sub { shift->[-1] });
 273
 274    done_plotting $plot;
 275}
 276BEGIN { register_generator \&generate_plot_ctx_total; }
 277
 278sub generate_plot_ctx_nonvol {
 279    my $plotter = shift;
 280    my $masterdb = shift;
 281
 282    my $plot = $plotter->new_linespoints(
 283        key => '1166_ctx_nonvolunt',
 284        label => 'Non-voluntary context switches per second per process',
 285        legend => 'CTX NON-VOLUNTARY',
 286        ylabel => 'count per second',
 287    );
 288
 289    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
 290
 291    foreach my $pid (@pids) {
 292        my @ctx = map {
 293            if (exists $_->{'/proc/pid/status'}->{$pid}) {
 294                my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
 295                exists $entry{nonvoluntary_ctxt_switches} ?
 296                       $entry{nonvoluntary_ctxt_switches} : undef
 297            } else { undef }
 298        } @$masterdb;
 299        $plot->push(
 300            [nonzero change_per_second $masterdb, cumulative_to_changes @ctx],
 301            title => pid_to_cmdline($masterdb, $pid),
 302        );
 303    }
 304
 305    done_plotting $plot->sort(sub { shift->[-1] });
 306}
 307BEGIN { register_generator \&generate_plot_ctx_nonvol; }
 308
 309sub generate_plot_ctx_vol {
 310    my $plotter = shift;
 311    my $masterdb = shift;
 312
 313    my $plot = $plotter->new_linespoints(
 314        key => '1166_ctx_volunt',
 315        label => 'Voluntary context switches per second per process',
 316        legend => 'CTX VOLUNTARY',
 317        ylabel => 'count per second',
 318    );
 319
 320    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
 321
 322    foreach my $pid (@pids) {
 323        my @ctx = map {
 324            if (exists $_->{'/proc/pid/status'}->{$pid}) {
 325                my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
 326                exists $entry{nonvoluntary_ctxt_switches} ?
 327                       $entry{nonvoluntary_ctxt_switches} : undef
 328            } else { undef }
 329        } @$masterdb;
 330        $plot->push(
 331            [nonzero change_per_second $masterdb, cumulative_to_changes @ctx],
 332            title => pid_to_cmdline($masterdb, $pid),
 333        );
 334    }
 335
 336    done_plotting $plot->sort(sub { shift->[-1] });
 337}
 338BEGIN { register_generator \&generate_plot_ctx_vol; }
 339
 340sub generate_plot_ctx_global {
 341    my $plotter = shift;
 342    my $masterdb = shift;
 343
 344    my $plot = $plotter->new_linespoints(
 345        key => '2060_ctx_global',
 346        label => 'left: total number of context switches\nright: number of processes in system',
 347        legend => 'CTX-SW,PROC-NUM',
 348        ylabel => 'context switches per second',
 349        y2label => 'number of processes',
 350    );
 351
 352    $plot->push(
 353        [nonzero change_per_second $masterdb, cumulative_to_changes map {
 354            exists $_->{'/proc/stat'} &&
 355            exists $_->{'/proc/stat'}->{ctxt} ?
 356                   $_->{'/proc/stat'}->{ctxt} : undef
 357        } @$masterdb],
 358        axes => 'x1y1', title => 'Context switches',
 359    );
 360
 361    $plot->push(
 362        [nonzero map {
 363            exists $_->{'/proc/loadavg'} &&
 364            exists $_->{'/proc/loadavg'}->{all} ?
 365                   $_->{'/proc/loadavg'}->{all} : undef
 366        } @$masterdb],
 367        axes => 'x2y2', title => 'Process and thread count',
 368    );
 369
 370    done_plotting $plot;
 371}
 372BEGIN { register_generator \&generate_plot_ctx_global; }
 373
 374sub generate_plot_loadavg {
 375    my $plotter = shift;
 376    my $masterdb = shift;
 377
 378    my $plot = $plotter->new_linespoints(
 379        key => '2005_loadavg',
 380        label => 'Load average',
 381        legend => 'LOAD AVERAGE',
 382        ylabel => 'load average',
 383    );
 384
 385    foreach my $avg (qw/min1 min5 min15/) {
 386        $plot->push(
 387            [map { $_->{'/proc/loadavg'}->{$avg} } @{$masterdb}],
 388            title => {
 389                min1 => '1 minute average',
 390                min5 => '5 minute average',
 391                min15 => '15 minute average',
 392            }->{$avg},
 393        );
 394    }
 395
 396    done_plotting $plot;
 397}
 398BEGIN { register_generator \&generate_plot_loadavg; }
 399
 400sub generate_plot_processes_global {
 401    my $plotter = shift;
 402    my $masterdb = shift;
 403
 404    done_plotting $plotter->new_linespoints(
 405        key => '2050_processes_created',
 406        label => 'Processes and threads created',
 407        legend => 'PROC/THREADS CREATED',
 408        ylabel => 'count',
 409    )->push(
 410        [cumulative_to_changes map { $_->{'/proc/stat'}->{processes} } @$masterdb],
 411        title => 'Processes and threads created');
 412}
 413BEGIN { register_generator \&generate_plot_processes_global; }
 414
 415sub generate_plot_major_pagefaults {
 416    my $plotter = shift;
 417    my $masterdb = shift;
 418
 419    my $plot = $plotter->new_linespoints(
 420        key => '1010_majorfault_%d',
 421        label => 'Major page faults per second',
 422        ylabel => 'count per second',
 423        multiple => {
 424            max_plots => 3,
 425            max_per_plot => 10,
 426            split_f => sub { max @{shift()} },
 427            split_factor => 5,
 428            legend_f => sub { 'MAJOR PAGE FAULTS &mdash; MAX ' . ceil(max @{shift()}) },
 429        },
 430    );
 431
 432    my @pids = uniq map { keys %{$_->{'/proc/pid/stat'}} } @$masterdb;
 433
 434    foreach my $pid (@pids) {
 435        $plot->push(
 436            [nonzero change_per_second $masterdb, cumulative_to_changes map {
 437                if (exists $_->{'/proc/pid/stat'}->{$pid}) {
 438                    my %entry = split ',', $_->{'/proc/pid/stat'}->{$pid};
 439                    exists $entry{majflt} ? $entry{majflt} : undef
 440                } else { undef }
 441            } @$masterdb],
 442            title => pid_to_cmdline($masterdb, $pid),
 443        );
 444    }
 445
 446    done_plotting $plot;
 447}
 448BEGIN { register_generator \&generate_plot_major_pagefaults; }
 449
 450sub generate_plot_minor_pagefaults {
 451    my $plotter = shift;
 452    my $masterdb = shift;
 453
 454    my $plot = $plotter->new_linespoints(
 455        key => '1011_minorfault_%d',
 456        label => 'Minor page faults per second',
 457        ylabel => 'count per second',
 458        multiple => {
 459            max_plots => 3,
 460            max_per_plot => 10,
 461            split_f => sub { max @{shift()} },
 462            split_factor => 5,
 463            legend_f => sub { 'MINOR PAGE FAULTS &mdash; MAX ' . ceil(max @{shift()}) },
 464        },
 465    );
 466
 467    my @pids = uniq map { keys %{$_->{'/proc/pid/stat'}} } @$masterdb;
 468
 469    foreach my $pid (@pids) {
 470        $plot->push(
 471            [nonzero change_per_second $masterdb, cumulative_to_changes map {
 472                if (exists $_->{'/proc/pid/stat'}->{$pid}) {
 473                    my %entry = split ',', $_->{'/proc/pid/stat'}->{$pid};
 474                    exists $entry{minflt} ? $entry{minflt} : undef
 475                } else { undef }
 476            } @$masterdb],
 477            title => pid_to_cmdline($masterdb, $pid),
 478        );
 479    }
 480
 481    done_plotting $plot;
 482}
 483BEGIN { register_generator \&generate_plot_minor_pagefaults; }
 484
 485sub generate_plot_cpu {
 486    my $plotter = shift;
 487    my $masterdb = shift;
 488
 489    my $plot = $plotter->new_histogram(
 490        key => '2015_cpu',
 491        label => 'CPU utilization',
 492        legend => 'CPU UTILIZATION',
 493        ylabel => 'percent',
 494    );
 495
 496    my @cpu_keys = qw/idle iowait nice user softirq irq sys/;
 497
 498    foreach my $key (@cpu_keys) {
 499        $plot->push(
 500            [nonzero cumulative_to_changes map {
 501                my @datakeys = qw/user nice sys idle iowait irq softirq/;
 502                my $h = { zip @datakeys, @{$_->{'/proc/stat'}->{cpu}} };
 503                $h->{$key}
 504            } @$masterdb],
 505            lc => {
 506                user    => "3149BD",
 507                nice    => "4265FF",
 508                sys     => "DE2821",
 509                idle    => "ADE739",
 510                iowait  => "EE00FF",
 511                irq     => "FF0000",
 512                softirq => "EF0000",
 513            }->{$key},
 514            title => $key,
 515        );
 516    }
 517
 518    $plot->scale(to => 100);
 519
 520    done_plotting $plot;
 521}
 522BEGIN { register_generator \&generate_plot_cpu; }
 523
 524sub generate_plot_cpu_freq {
 525    my $plotter = shift;
 526    my $masterdb = shift;
 527
 528    my @cpus = uniq map { keys %{$_->{'/sys/devices/system/cpu'} // {}} } @$masterdb;
 529
 530    foreach my $cpu_num (@cpus) {
 531        my $plot = $plotter->new_histogram(
 532            key => "2010_cpu${cpu_num}_time_in_state",
 533            label => "CPU${cpu_num} time in state",
 534            legend => "CPU${cpu_num} TIME IN STATE",
 535            ylabel => 'percent',
 536        );
 537
 538        my @freqs = uniq map { keys %{$_->{'/sys/devices/system/cpu'}->{$cpu_num}->{cpufreq}->{stats}->{time_in_state}} } @$masterdb;
 539
 540        foreach my $freq (sort { $b <=> $a } @freqs) {
 541            $plot->push(
 542            [cumulative_to_changes
 543                map { $_->{'/sys/devices/system/cpu'}->{$cpu_num}->{cpufreq}->{stats}->{time_in_state}->{$freq} } @$masterdb],
 544            title => int($freq/1000) . 'MHz',
 545            );
 546        }
 547
 548        $plot->scale(to => 100);
 549
 550        done_plotting $plot;
 551    }
 552}
 553BEGIN { register_generator \&generate_plot_cpu_freq; }
 554
 555sub fs_to_mountpoint {
 556    my $fs = shift;
 557    my $masterdb = shift;
 558
 559    my @mountpoints = uniq map { keys %{$_->{'/bin/df'} // {}} } @$masterdb;
 560
 561    foreach my $mountpoint (@mountpoints) {
 562        my ($filesystem) = uniq map { $_->{'/bin/df'}->{$mountpoint}->{filesystem} } @$masterdb;
 563        if ($filesystem =~ /\b\Q$fs\E\b/) {
 564            return $fs . ': ' . $mountpoint;
 565        }
 566    }
 567
 568    return $fs;
 569}
 570
 571sub generate_plot_fs_written {
 572    my $plotter = shift;
 573    my $masterdb = shift;
 574
 575    my $plot = $plotter->new_linespoints(
 576        key => '2102_ext4_written',
 577        label => 'Bytes written to ext4 partitions (excluding non-changed)',
 578        legend => 'EXT4 WRITES',
 579        ylabel => 'MB',
 580    );
 581
 582    my @filesystems = uniq map { keys %{$_->{'/sys/fs/ext4'}} } @$masterdb;
 583
 584    foreach my $fs (@filesystems) {
 585        $plot->push(
 586            [kb2mb nonzero has_changes cumulative_to_changes map {
 587                exists $_->{'/sys/fs/ext4'}->{$fs} ? $_->{'/sys/fs/ext4'}->{$fs}->{lifetime_write_kbytes} : undef
 588            } @$masterdb],
 589            title => fs_to_mountpoint($fs, $masterdb),
 590        );
 591    }
 592
 593    done_plotting $plot;
 594}
 595BEGIN { register_generator \&generate_plot_fs_written; }
 596
 597sub generate_plot_cputime {
 598    my $plotter = shift;
 599    my $masterdb = shift;
 600
 601    my $plot = $plotter->new_linespoints(
 602        key => '1150_cpu_user_sys_time_%d',
 603        label => 'CPU user+sys time',
 604        ylabel => 'percent',
 605        multiple => {
 606            max_plots => 2,
 607            max_per_plot => 20,
 608            split_f => sub { max @{shift()} },
 609            split_factor => 5,
 610            legend_f => sub { 'CPU TIME &mdash; USER+SYS &mdash; MAX ' . ceil(max @{shift()}) . '%' },
 611        },
 612    );
 613
 614    my @pids = uniq map { keys %{$_->{'/proc/pid/stat'}} } @$masterdb;
 615
 616    foreach my $pid (@pids) {
 617        my @entry = change_per_second $masterdb, cumulative_to_changes map {
 618                if (exists $_->{'/proc/pid/stat'}->{$pid}) {
 619                    my %entry = split ',', $_->{'/proc/pid/stat'}->{$pid};
 620                    exists $entry{utime} && exists $entry{stime} ?
 621                    $entry{utime} + $entry{stime} : undef
 622                } else { undef }
 623            } @$masterdb;
 624
 625        next unless any { defined && $_ > 0 } @entry;
 626
 627        if (CLK_TCK != 100) {
 628            foreach (0 .. @entry-1) {
 629                $entry[$_] /= CLK_TCK;
 630                $entry[$_] *= 100;
 631            }
 632        }
 633
 634        $plot->push([nonzero @entry], title => pid_to_cmdline($masterdb, $pid));
 635    }
 636
 637    done_plotting $plot;
 638}
 639BEGIN { register_generator \&generate_plot_cputime; }
 640
 641sub generate_plot_cputime_user {
 642    my $plotter = shift;
 643    my $masterdb = shift;
 644
 645    my $plot = $plotter->new_linespoints(
 646        key => '1160_cpu_usertime_%d',
 647        label => 'CPU user time',
 648        ylabel => 'percent',
 649        multiple => {
 650            max_plots => 2,
 651            max_per_plot => 20,
 652            split_f => sub { max @{shift()} },
 653            split_factor => 5,
 654            legend_f => sub { 'CPU TIME &mdash; USER &mdash; MAX ' . ceil(max @{shift()}) . '%' },
 655        },
 656    );
 657
 658    my @pids = uniq map { keys %{$_->{'/proc/pid/stat'}} } @$masterdb;
 659
 660    foreach my $pid (@pids) {
 661        my @entry = change_per_second $masterdb, cumulative_to_changes map {
 662                if (exists $_->{'/proc/pid/stat'}->{$pid}) {
 663                    my %entry = split ',', $_->{'/proc/pid/stat'}->{$pid};
 664                    exists $entry{utime} ? $entry{utime} : undef
 665                } else { undef }
 666            } @$masterdb;
 667
 668        next unless any { defined && $_ > 0 } @entry;
 669
 670        if (CLK_TCK != 100) {
 671            foreach (0 .. @entry-1) {
 672                $entry[$_] /= CLK_TCK;
 673                $entry[$_] *= 100;
 674            }
 675        }
 676
 677        $plot->push([nonzero @entry], title => pid_to_cmdline($masterdb, $pid));
 678    }
 679
 680    done_plotting $plot;
 681}
 682BEGIN { register_generator \&generate_plot_cputime_user; }
 683
 684sub generate_plot_cputime_sys {
 685    my $plotter = shift;
 686    my $masterdb = shift;
 687
 688    my $plot = $plotter->new_linespoints(
 689        key => '1162_cpu_systime_%d',
 690        label => 'CPU sys time',
 691        ylabel => 'percent',
 692        multiple => {
 693            max_plots => 2,
 694            max_per_plot => 20,
 695            split_f => sub { max @{shift()} },
 696            split_factor => 5,
 697            legend_f => sub { 'CPU TIME &mdash; SYS &mdash; MAX ' . ceil(max @{shift()}) . '%' },
 698        },
 699    );
 700
 701    my @pids = uniq map { keys %{$_->{'/proc/pid/stat'}} } @$masterdb;
 702
 703    foreach my $pid (@pids) {
 704        my @entry = change_per_second $masterdb, cumulative_to_changes map {
 705            if (exists $_->{'/proc/pid/stat'}->{$pid}) {
 706                my %entry = split ',', $_->{'/proc/pid/stat'}->{$pid};
 707                exists $entry{stime} ? $entry{stime} : undef
 708            } else { undef }
 709        } @$masterdb;
 710
 711        next unless any { defined && $_ > 0 } @entry;
 712
 713        if (CLK_TCK != 100) {
 714            foreach (0 .. @entry-1) {
 715                $entry[$_] /= CLK_TCK;
 716                $entry[$_] *= 100;
 717            }
 718        }
 719
 720        $plot->push([nonzero @entry], title => pid_to_cmdline($masterdb, $pid));
 721    }
 722
 723    done_plotting $plot;
 724}
 725BEGIN { register_generator \&generate_plot_cputime_sys; }
 726
 727sub sysvipc {
 728    my $masterdb = shift;
 729    my $type = shift;
 730    my $key = shift;
 731    map {
 732        exists $_->{"/proc/sysvipc/$type"} &&
 733        exists $_->{"/proc/sysvipc/$type"}->{$key} ?
 734               $_->{"/proc/sysvipc/$type"}->{$key} : undef
 735    } @$masterdb;
 736}
 737
 738sub generate_plot_sysvipc_count {
 739    my $plotter = shift;
 740    my $masterdb = shift;
 741
 742    my $plot = $plotter->new_linespoints(
 743        key => '2241_sysvipc_count',
 744        label => 'SysV IPC object counts:\n-Shared memory segments (SHM)\n-Message queues (MSG)\n-Semaphore sets (SEM)',
 745        legend => 'SYSV SHM+MSG+SEM COUNT',
 746        ylabel => 'count',
 747    );
 748
 749    my @nattch0 = sysvipc $masterdb, 'shm', 'nattch0';
 750    my @nattch1 = sysvipc $masterdb, 'shm', 'nattch1';
 751    my @nattch2 = sysvipc $masterdb, 'shm', 'nattch2';
 752    my @nattch3 = sysvipc $masterdb, 'shm', 'nattch3';
 753    my @msg     = sysvipc $masterdb, 'msg', 'count';
 754    my @sem     = sysvipc $masterdb, 'sem', 'count';
 755
 756    if (nonzero(@nattch0) > 0 or nonzero(@nattch1) > 0 or nonzero(@nattch2) > 0 or
 757            nonzero(@nattch3) > 0 or nonzero(@msg) > 0 or nonzero(@sem) > 0) {
 758        $plot->push([nonzero @nattch0], title => 'SHM - 0 processes attached');
 759        $plot->push([nonzero @nattch1], title => 'SHM - 1 process attached');
 760        $plot->push([nonzero @nattch2], title => 'SHM - 2 processes attached');
 761        $plot->push([nonzero @nattch3], title => 'SHM - 3+ processes attached');
 762        $plot->push([nonzero @msg],     title => 'MSG');
 763        $plot->push([nonzero @sem],     title => 'SEM');
 764    }
 765
 766    done_plotting $plot;
 767}
 768BEGIN { register_generator \&generate_plot_sysvipc_count; }
 769
 770sub generate_plot_sysvipc_locked_size {
 771    my $plotter = shift;
 772    my $masterdb = shift;
 773
 774    my $plot = $plotter->new_histogram(
 775        key => '2240_sysvipc_locked_unlocked_size',
 776        label => 'SysV shared memory locked+unlocked Size sum',
 777        legend => 'SYSV SHM LOCKED+UNLOCKED',
 778        ylabel => 'MB',
 779    );
 780
 781    my @locked   = sysvipc $masterdb, 'shm', 'size_locked';
 782    my @unlocked = sysvipc $masterdb, 'shm', 'size_unlocked';
 783
 784    if (nonzero(@locked) > 0 or nonzero(@unlocked) > 0) {
 785        $plot->push([b2mb @locked],   title => 'Locked to memory');
 786        $plot->push([b2mb @unlocked], title => 'Unlocked');
 787    }
 788
 789    done_plotting $plot;
 790}
 791BEGIN { register_generator \&generate_plot_sysvipc_locked_size; }
 792
 793sub generate_plot_sysvipc_shm_cpid {
 794    my $plotter = shift;
 795    my $masterdb = shift;
 796
 797    my $plot = $plotter->new_histogram(
 798        key => '1052_sysvipc_shm_cpid',
 799        label => 'SysV shared memory Size per Creator PID (CPID)',
 800        legend => 'SYSV SHM SIZE PER CPID',
 801        ylabel => 'MB',
 802    );
 803
 804    my @cpids = uniq grep { defined && length } map {
 805        exists $_->{'/proc/sysvipc/shm'} &&
 806        exists $_->{'/proc/sysvipc/shm'}->{cpid_to_size} ?
 807        keys %{$_->{'/proc/sysvipc/shm'}->{cpid_to_size}} : undef
 808    } @$masterdb;
 809
 810    foreach my $cpid (@cpids) {
 811        $plot->push(
 812            [nonzero b2mb map {
 813                exists $_->{'/proc/sysvipc/shm'} &&
 814                exists $_->{'/proc/sysvipc/shm'}->{cpid_to_size} &&
 815                exists $_->{'/proc/sysvipc/shm'}->{cpid_to_size}->{$cpid} ?
 816                       $_->{'/proc/sysvipc/shm'}->{cpid_to_size}->{$cpid} : undef
 817            } @$masterdb],
 818            title => pid_to_cmdline($masterdb, $cpid),
 819        );
 820    }
 821
 822    done_plotting $plot;
 823}
 824BEGIN { register_generator \&generate_plot_sysvipc_shm_cpid; }
 825
 826sub generate_plot_mlocked {
 827    my $plotter = shift;
 828    my $masterdb = shift;
 829
 830    my $plot = $plotter->new_histogram(
 831        key => '1020_locked',
 832        label => 'VmLck per process',
 833        legend => 'LOCKED',
 834        ylabel => 'MB',
 835    );
 836
 837    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
 838
 839    foreach my $pid (@pids) {
 840        $plot->push(
 841            [kb2mb nonzero map {
 842                if (exists $_->{'/proc/pid/status'}->{$pid}) {
 843                    my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
 844                    exists $entry{VmLck} ? $entry{VmLck} : undef
 845                } else { undef }
 846            } @$masterdb],
 847            title => pid_to_cmdline($masterdb, $pid),
 848        );
 849    }
 850
 851    done_plotting $plot->sort(sub { shift->[-1] });
 852}
 853BEGIN { register_generator \&generate_plot_mlocked; }
 854
 855sub generate_plot_vmsize {
 856    my $plotter = shift;
 857    my $masterdb = shift;
 858
 859    my $plot = $plotter->new_linespoints(
 860        key => '1040_vmsize_%d',
 861        label => 'Process virtual memory size (excluding non-changed)',
 862        ylabel => 'MB',
 863        multiple => {
 864            max_plots => 4,
 865            max_per_plot => 15,
 866            split_f => sub { max @{shift()} },
 867            split_factor => 5,
 868            legend_f => sub { 'VMSIZE &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
 869        },
 870    );
 871
 872    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
 873
 874    foreach my $pid (@pids) {
 875        $plot->push(
 876            [nonzero kb2mb has_changes map {
 877                if (exists $_->{'/proc/pid/status'}->{$pid}) {
 878                    my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
 879                    exists $entry{VmSize} ? $entry{VmSize} : undef
 880                } else { undef }
 881            } @$masterdb],
 882            title => pid_to_cmdline($masterdb, $pid),
 883        );
 884    }
 885
 886    done_plotting $plot;
 887}
 888BEGIN { register_generator \&generate_plot_vmsize; }
 889
 890sub generate_plot_memory_map_count {
 891    my $plotter = shift;
 892    my $masterdb = shift;
 893
 894    my $plot = $plotter->new_linespoints(
 895        key => '1045_num_mmaps_%d',
 896        label => 'Number of memory maps (virtual memory areas)',
 897        ylabel => 'count',
 898        multiple => {
 899            max_plots => 3,
 900            max_per_plot => 15,
 901            split_f => sub { max @{shift()} },
 902            split_factor => 5,
 903            legend_f => sub { '#MEMORY MAPS &mdash; MAX ' . max @{shift()} },
 904        },
 905    );
 906
 907    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
 908
 909    foreach my $pid (@pids) {
 910        $plot->push(
 911            [nonzero has_changes map {
 912                exists $_->{'/proc/pid/smaps'}->{$pid} &&
 913                exists $_->{'/proc/pid/smaps'}->{$pid}->{vmacount} ?
 914                       $_->{'/proc/pid/smaps'}->{$pid}->{vmacount} : undef
 915            } @$masterdb],
 916            title => pid_to_cmdline($masterdb, $pid),
 917        );
 918    }
 919
 920    done_plotting $plot;
 921}
 922BEGIN { register_generator \&generate_plot_memory_map_count; }
 923
 924sub private_dirty_collect_data {
 925    my $plot = shift;
 926    my $masterdb = shift;
 927
 928    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
 929    #print Dumper \@pids;
 930
 931    foreach my $pid (@pids) {
 932        $plot->push(
 933            [kb2mb nonzero map {
 934                if (exists $_->{'/proc/pid/smaps'}->{$pid} &&
 935                   (exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Private_Dirty} or
 936                    exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Swap})) {
 937
 938                    my $private_dirty = 0;
 939                    my $swap = 0;
 940
 941                    if (exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Private_Dirty}) {
 942                        $private_dirty = $_->{'/proc/pid/smaps'}->{$pid}->{total_Private_Dirty};
 943                    }
 944
 945                    if (exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Swap}) {
 946                        $swap = $_->{'/proc/pid/smaps'}->{$pid}->{total_Swap};
 947                    }
 948
 949                    $private_dirty + $swap
 950                } else { undef }
 951            } @$masterdb],
 952            title => pid_to_cmdline($masterdb, $pid),
 953        );
 954    }
 955
 956    return $plot;
 957}
 958
 959sub generate_plot_private_dirty {
 960    my $plotter = shift;
 961    my $masterdb = shift;
 962
 963    my $plot = $plotter->new_histogram(
 964        key => '1009_private_dirty_plus_swap',
 965        label => 'Private dirty + swap',
 966        legend => 'PRIVATE DIRTY+SWAP',
 967        ylabel => 'MB',
 968        column_limit => 1,
 969        reduce_f => sub {
 970            my @leftovers;
 971
 972            foreach my $idx (0 .. @$masterdb-1) {
 973                push @leftovers, sum map {
 974                    exists $_->{__data} &&
 975                    exists $_->{__data}->[$idx] ?
 976                           $_->{__data}->[$idx] : undef
 977                } @_;
 978            }
 979
 980            return [nonzero @leftovers],
 981                   title => 'Sum of ' . scalar(@_) . ' processes';
 982        },
 983    );
 984
 985    private_dirty_collect_data $plot, $masterdb;
 986
 987    $plot->sort(sub { max @{shift()} });
 988    $plot->reduce;
 989    $plot->sort(\&max_change, sub { max @{shift()} });
 990
 991    done_plotting $plot;
 992}
 993BEGIN { register_generator \&generate_plot_private_dirty; }
 994
 995sub generate_plot_private_dirty_changes {
 996    my $plotter = shift;
 997    my $masterdb = shift;
 998
 999    my $plot = $plotter->new_linespoints(
1000        key => '1009_private_dirty_plus_swap_%d',
1001        label => 'Private dirty + swap (excluding non-changed)',
1002        ylabel => 'MB',
1003        multiple => {
1004            max_plots => 4,
1005            max_per_plot => 15,
1006            split_f => sub { max @{shift()} },
1007            split_factor => 5,
1008            legend_f => sub { 'PRIVATE DIRTY+SWAP &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
1009        },
1010        exclude_nonchanged => 1,
1011    );
1012
1013    private_dirty_collect_data $plot, $masterdb;
1014
1015    done_plotting $plot;
1016}
1017BEGIN { register_generator \&generate_plot_private_dirty_changes; }
1018
1019sub generate_plot_heap_histogram {
1020    my $plotter = shift;
1021    my $masterdb = shift;
1022
1023    my $plot = $plotter->new_histogram(
1024        key => '1001_heap',
1025        label => 'Heap Size per process',
1026        legend => 'HEAP SIZE',
1027        ylabel => 'MB',
1028        column_limit => 1,
1029        reduce_f => sub {
1030            my @leftovers;
1031
1032            foreach my $idx (0 .. @$masterdb-1) {
1033                push @leftovers, sum map {
1034                    exists $_->{__data} &&
1035                    exists $_->{__data}->[$idx] ?
1036                           $_->{__data}->[$idx] : undef
1037                } @_;
1038            }
1039
1040            return [nonzero @leftovers],
1041                   title => 'Sum of ' . scalar(@_) . ' process heaps';
1042        },
1043    );
1044
1045    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1046
1047    foreach my $pid (@pids) {
1048        $plot->push([kb2mb nonzero map {
1049                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1050                exists $_->{'/proc/pid/smaps'}->{$pid}->{'[heap]'} &&
1051                exists $_->{'/proc/pid/smaps'}->{$pid}->{'[heap]'}->{total_Size} ?
1052                       $_->{'/proc/pid/smaps'}->{$pid}->{'[heap]'}->{total_Size} : undef
1053            } @$masterdb],
1054            title => pid_to_cmdline($masterdb, $pid),
1055        );
1056    }
1057
1058    $plot->sort(sub { max @{shift()} });
1059    $plot->reduce;
1060    $plot->sort(\&max_change, sub { max @{shift()} });
1061
1062    done_plotting $plot;
1063}
1064BEGIN { register_generator \&generate_plot_heap_histogram; }
1065
1066sub generate_plot_heap_changes {
1067    my $plotter = shift;
1068    my $masterdb = shift;
1069
1070    my $plot = $plotter->new_linespoints(
1071        key => '1001_heap_changes_%d',
1072        label => 'Heap Size per process (excluding non-changed)',
1073        ylabel => 'MB',
1074        multiple => {
1075            max_plots => 5,
1076            max_per_plot => 10,
1077            split_f => sub { max @{shift()} },
1078            split_factor => 5,
1079            legend_f => sub { 'HEAP SIZE &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
1080        },
1081    );
1082
1083    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1084
1085    foreach my $pid (@pids) {
1086        $plot->push([kb2mb has_changes nonzero map {
1087                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1088                exists $_->{'/proc/pid/smaps'}->{$pid}->{'[heap]'} &&
1089                exists $_->{'/proc/pid/smaps'}->{$pid}->{'[heap]'}->{total_Size} ?
1090                       $_->{'/proc/pid/smaps'}->{$pid}->{'[heap]'}->{total_Size} : undef
1091            } @$masterdb],
1092            title => pid_to_cmdline($masterdb, $pid),
1093        );
1094    }
1095
1096    done_plotting $plot;
1097}
1098BEGIN { register_generator \&generate_plot_heap_changes; }
1099
1100sub generate_plot_sysvipc_shm_size {
1101    my $plotter = shift;
1102    my $masterdb = shift;
1103
1104    my $plot = $plotter->new_histogram(
1105        key => '1050_sysvipc_shm_size',
1106        label => 'SysV shared memory segment total Size per process',
1107        legend => 'SYSV SHM SIZE',
1108        ylabel => 'MB',
1109    );
1110
1111    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'}} } @$masterdb;
1112
1113    foreach my $pid (@pids) {
1114        $plot->push(
1115            [kb2mb nonzero map {
1116                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1117                exists $_->{'/proc/pid/smaps'}->{$pid}->{'/SYSV'} &&
1118                exists $_->{'/proc/pid/smaps'}->{$pid}->{'/SYSV'}->{total_Size} ?
1119                       $_->{'/proc/pid/smaps'}->{$pid}->{'/SYSV'}->{total_Size} : undef
1120            } @$masterdb],
1121            title => pid_to_cmdline($masterdb, $pid),
1122        );
1123    }
1124
1125    done_plotting $plot->sort(sub { shift->[-1] });
1126}
1127BEGIN { register_generator \&generate_plot_sysvipc_shm_size; }
1128
1129sub generate_plot_posix_shm_size {
1130    my $plotter = shift;
1131    my $masterdb = shift;
1132
1133    my $plot = $plotter->new_histogram(
1134        key => '1051_posixipc_shm_size',
1135        label => 'POSIX shared memory segment total Size per process',
1136        legend => 'POSIX SHM SIZE',
1137        ylabel => 'MB',
1138    );
1139
1140    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'}} } @$masterdb;
1141
1142    foreach my $pid (@pids) {
1143        $plot->push(
1144            [kb2mb nonzero map {
1145                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1146                exists $_->{'/proc/pid/smaps'}->{$pid}->{'/dev/shm/'} &&
1147                exists $_->{'/proc/pid/smaps'}->{$pid}->{'/dev/shm/'}->{total_Size} ?
1148                       $_->{'/proc/pid/smaps'}->{$pid}->{'/dev/shm/'}->{total_Size} : undef
1149            } @$masterdb],
1150            title => pid_to_cmdline($masterdb, $pid),
1151        );
1152    }
1153
1154    done_plotting $plot->sort(sub { shift->[-1] });
1155}
1156BEGIN { register_generator \&generate_plot_posix_shm_size; }
1157
1158sub generate_plot_gfx_mmap_size {
1159    my $plotter = shift;
1160    my $masterdb = shift;
1161
1162    foreach my $gfx_mmap (@SP::Endurance::Parser::GFX_MMAPS) {
1163        my $plot = $plotter->new_histogram(
1164            key => '1060_gfx_mmap_size' . (($_ = $gfx_mmap) =~ s#/#_#g, $_),
1165            label => "Total Size of $gfx_mmap memory mappings per process",
1166            legend => "$gfx_mmap MMAP SIZE",
1167            ylabel => 'MB',
1168        );
1169
1170        my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1171
1172        foreach my $pid (@pids) {
1173            $plot->push([kb2mb nonzero map {
1174                    exists $_->{'/proc/pid/smaps'}->{$pid} &&
1175                    exists $_->{'/proc/pid/smaps'}->{$pid}->{$gfx_mmap} &&
1176                    exists $_->{'/proc/pid/smaps'}->{$pid}->{$gfx_mmap}->{total_Size} ?
1177                           $_->{'/proc/pid/smaps'}->{$pid}->{$gfx_mmap}->{total_Size} : undef
1178                } @$masterdb],
1179                title => pid_to_cmdline($masterdb, $pid),
1180            );
1181        }
1182
1183        $plot->sort(sub { shift->[-1] });
1184
1185        done_plotting $plot;
1186    }
1187}
1188BEGIN { register_generator \&generate_plot_gfx_mmap_size; }
1189
1190sub generate_plot_gfx_mmap_count {
1191    my $plotter = shift;
1192    my $masterdb = shift;
1193
1194    foreach my $gfx_mmap (@SP::Endurance::Parser::GFX_MMAPS) {
1195        my $plot = $plotter->new_linespoints(
1196            key => '1061_gfx_mmap_count' . (($_ = $gfx_mmap) =~ s#/#_#g, $_),
1197            label => "Count of $gfx_mmap memory mappings per process (excluding non-changed)",
1198            legend => "$gfx_mmap MMAP COUNT",
1199            ylabel => 'count',
1200        );
1201
1202        my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1203
1204        foreach my $pid (@pids) {
1205            $plot->push([has_changes nonzero map {
1206                    exists $_->{'/proc/pid/smaps'}->{$pid} &&
1207                    exists $_->{'/proc/pid/smaps'}->{$pid}->{$gfx_mmap} &&
1208                    exists $_->{'/proc/pid/smaps'}->{$pid}->{$gfx_mmap}->{vmacount} ?
1209                       $_->{'/proc/pid/smaps'}->{$pid}->{$gfx_mmap}->{vmacount} : undef
1210                } @$masterdb],
1211                title => pid_to_cmdline($masterdb, $pid),
1212            );
1213        }
1214
1215        $plot->sort(sub { shift->[-1] });
1216
1217        done_plotting $plot;
1218    }
1219}
1220BEGIN { register_generator \&generate_plot_gfx_mmap_count; }
1221
1222sub generate_plot_rwxp_mmap_size {
1223    my $plotter = shift;
1224    my $masterdb = shift;
1225
1226    my $plot = $plotter->new_histogram(
1227        key => '1030_rwxp_mmap_size',
1228        label => q/Total Size of memory mappings with 'rwxp' protection flags./,
1229        legend => 'WRITABLE-EXEC MMAP SIZE',
1230        ylabel => 'MB',
1231    );
1232
1233    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1234
1235    foreach my $pid (@pids) {
1236        $plot->push([kb2mb nonzero map { my $entry = $_;
1237                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1238                exists $_->{'/proc/pid/smaps'}->{$pid}->{rwxp} &&
1239                exists $_->{'/proc/pid/smaps'}->{$pid}->{rwxp}->{total_Size} ?
1240                       $_->{'/proc/pid/smaps'}->{$pid}->{rwxp}->{total_Size} : undef
1241            } @$masterdb],
1242            title => pid_to_cmdline($masterdb, $pid),
1243        );
1244    }
1245
1246    $plot->sort(\&max_change, sub { max @{shift()} });
1247
1248    done_plotting $plot;
1249}
1250BEGIN { register_generator \&generate_plot_rwxp_mmap_size; }
1251
1252sub generate_plot_pss {
1253    my $plotter = shift;
1254    my $masterdb = shift;
1255
1256    my $plot = $plotter->new_histogram(
1257        key => '1006_pss',
1258        label => 'Proportional Set Size (PSS) total per process',
1259        legend => 'PSS',
1260        ylabel => 'MB',
1261        column_limit => 1,
1262        reduce_f => sub {
1263            my @leftovers;
1264
1265            foreach my $idx (0 .. @$masterdb-1) {
1266                push @leftovers, sum map {
1267                    exists $_->{__data} &&
1268                    exists $_->{__data}->[$idx] ?
1269                           $_->{__data}->[$idx] : undef
1270                } @_;
1271            }
1272
1273            return [nonzero @leftovers],
1274                   title => 'Sum of ' . scalar(@_) . ' process PSS';
1275        },
1276    );
1277
1278    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1279
1280    foreach my $pid (@pids) {
1281        $plot->push(
1282            [kb2mb nonzero map {
1283                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1284                exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} ?
1285                       $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} : undef
1286            } @$masterdb],
1287            title => pid_to_cmdline($masterdb, $pid),
1288        );
1289    }
1290
1291    $plot->sort(sub { max @{shift()} });
1292    $plot->reduce;
1293    $plot->sort(\&max_change, sub { max @{shift()} });
1294
1295    done_plotting $plot;
1296}
1297BEGIN { register_generator \&generate_plot_pss; }
1298
1299sub generate_plot_pss_only_changes {
1300    my $plotter = shift;
1301    my $masterdb = shift;
1302
1303    my $plot = $plotter->new_linespoints(
1304        key => '1006_pss_changes_%d',
1305        label => 'Proportional Set Size (PSS) per process (excluding non-changed)',
1306        ylabel => 'MB',
1307        multiple => {
1308            max_plots => 4,
1309            max_per_plot => 10,
1310            split_f => sub { max @{shift()} },
1311            split_factor => 5,
1312            legend_f => sub { 'PSS &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
1313        },
1314    );
1315
1316    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1317
1318    foreach my $pid (@pids) {
1319        $plot->push(
1320            [kb2mb nonzero has_changes map {
1321                exists $_->{'/proc/pid/smaps'}->{$pid} &&
1322                exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} ?
1323                       $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} : undef
1324            } @$masterdb],
1325            title => pid_to_cmdline($masterdb, $pid),
1326        );
1327    }
1328
1329    done_plotting $plot;
1330}
1331
1332sub generate_plot_pss_swap_changes {
1333    my $plotter = shift;
1334    my $masterdb = shift;
1335
1336    my $plot = $plotter->new_linespoints(
1337        key => '1006_pss_swap_changes_%d',
1338        label => 'Proportional Set Size (PSS) + Swap per process (excluding non-changed)',
1339        ylabel => 'MB',
1340        multiple => {
1341            max_plots => 4,
1342            max_per_plot => 10,
1343            split_f => sub { max @{shift()} },
1344            split_factor => 5,
1345            legend_f => sub { 'PSS+SWAP &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
1346        },
1347    );
1348
1349    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1350
1351    foreach my $pid (@pids) {
1352        $plot->push(
1353            [kb2mb nonzero has_changes map {
1354                if (exists $_->{'/proc/pid/smaps'}->{$pid} and
1355                        (exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} or
1356                         exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Swap})) {
1357                    (exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} ?
1358                            $_->{'/proc/pid/smaps'}->{$pid}->{total_Pss} : 0) +
1359                    (exists $_->{'/proc/pid/smaps'}->{$pid}->{total_Swap} ?
1360                            $_->{'/proc/pid/smaps'}->{$pid}->{total_Swap} : 0)
1361                } else { undef }
1362            } @$masterdb],
1363            title => pid_to_cmdline($masterdb, $pid),
1364        );
1365    }
1366
1367    done_plotting $plot;
1368}
1369
1370sub generate_plot_pss_changes {
1371    my $plotter = shift;
1372    my $masterdb = shift;
1373
1374    my @pids = uniq map { keys %{$_->{'/proc/pid/smaps'} // {}} } @$masterdb;
1375
1376    foreach my $entry (@$masterdb) {
1377        foreach my $pid (@pids) {
1378            goto swap if exists $entry->{'/proc/pid/smaps'}->{$pid} and
1379                         exists $entry->{'/proc/pid/smaps'}->{$pid}->{total_Swap} and
1380                                $entry->{'/proc/pid/smaps'}->{$pid}->{total_Swap};
1381        }
1382    }
1383
1384    return generate_plot_pss_only_changes $plotter, $masterdb;
1385
1386swap:
1387    return generate_plot_pss_swap_changes $plotter, $masterdb;
1388}
1389BEGIN { register_generator \&generate_plot_pss_changes; }
1390
1391sub generate_plot_threads {
1392    my $plotter = shift;
1393    my $masterdb = shift;
1394
1395    my $plot = $plotter->new_histogram(
1396        key => '1200_threads_count',
1397        label => 'Number of threads per process (single threaded processes excluded)',
1398        legend => 'THREAD COUNT',
1399        ylabel => 'thread count',
1400        column_limit => 1,
1401        reduce_f => sub {
1402            my @leftovers;
1403
1404            foreach my $idx (0 .. @$masterdb-1) {
1405                push @leftovers, sum map {
1406                    exists $_->{__data} &&
1407                    exists $_->{__data}->[$idx] ?
1408                           $_->{__data}->[$idx] : undef
1409                } @_;
1410            }
1411
1412            return [nonzero @leftovers],
1413                   title => 'Sum of ' . scalar(@_) . ' process threads';
1414        },
1415    );
1416
1417    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
1418
1419    foreach my $pid (@pids) {
1420        my @threads = map {
1421                if (exists $_->{'/proc/pid/status'}->{$pid}) {
1422                    my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
1423                    exists $entry{Threads} ? $entry{Threads} : undef
1424                } else { undef }
1425            } @$masterdb;
1426
1427        next unless any { defined and $_ > 1 } @threads;
1428
1429        $plot->push(
1430            [nonzero @threads],
1431            title => pid_to_cmdline($masterdb, $pid),
1432        );
1433    }
1434
1435    $plot->sort(sub { max @{shift()} });
1436    $plot->reduce;
1437    $plot->sort(\&max_change, sub { max @{shift()} });
1438
1439    done_plotting $plot;
1440}
1441BEGIN { register_generator \&generate_plot_threads; }
1442
1443sub generate_plot_threads_changes {
1444    my $plotter = shift;
1445    my $masterdb = shift;
1446
1447    my $plot = $plotter->new_linespoints(
1448        key => '1201_threads_changes',
1449        label => 'Number of threads per process (non-changed and single threaded processes excluded)',
1450        legend => 'THREAD CHANGES',
1451        ylabel => 'thread count',
1452    );
1453
1454    my @pids = uniq map { keys %{$_->{'/proc/pid/status'}} } @$masterdb;
1455
1456    foreach my $pid (@pids) {
1457        my @threads = map {
1458                if (exists $_->{'/proc/pid/status'}->{$pid}) {
1459                    my %entry = split ',', $_->{'/proc/pid/status'}->{$pid};
1460                    exists $entry{Threads} ? $entry{Threads} : undef
1461                } else { undef }
1462            } @$masterdb;
1463
1464        next unless any { defined and $_ > 1 } @threads;
1465
1466        $plot->push(
1467            [has_changes nonzero @threads],
1468            title => pid_to_cmdline($masterdb, $pid),
1469        );
1470    }
1471
1472    done_plotting $plot->sort(sub { shift->[-1] });
1473}
1474BEGIN { register_generator \&generate_plot_threads_changes; }
1475
1476sub x11_pid_to_identifier {
1477    my $pid = shift;
1478    my $masterdb = shift;
1479
1480    uniq sort grep { defined && length } map { my $entry = $_;
1481        exists $entry->{'/usr/bin/xmeminfo'} ?
1482            (map { $_->{Identifier} } grep { $_->{PID} == $pid } @{$entry->{'/usr/bin/xmeminfo'}}) :
1483            undef
1484    } @$masterdb;
1485}
1486
1487sub generate_plot_x11_resource_count {
1488    my $plotter = shift;
1489    my $masterdb = shift;
1490
1491    my $plot = $plotter->new_linespoints(
1492        key => '1071_x11_resource_count_%d',
1493        label => 'X11 total resource count per process (excluding non-changed)',
1494        ylabel => 'count',
1495        multiple => {
1496            max_plots => 3,
1497            max_per_plot => 10,
1498            split_f => sub { max @{shift()} },
1499            split_factor => 5,
1500            legend_f => sub { 'X11 RESOURCE COUNT &mdash; MAX ' . max @{shift()} },
1501        },
1502    );
1503
1504    my @pids = uniq grep { defined && length } map { $_->{PID} } map {
1505        exists $_->{'/usr/bin/xmeminfo'} ? @{$_->{'/usr/bin/xmeminfo'} // []} : undef
1506    } @$masterdb;
1507
1508    foreach my $pid (@pids) {
1509        my @total_resource_count = map { my $entry = $_;
1510            exists $entry->{'/usr/bin/xmeminfo'} ?
1511            (sum map { $_->{total_resource_count} } grep { $_->{PID} == $pid } @{$entry->{'/usr/bin/xmeminfo'}}) :
1512            undef
1513        } @$masterdb;
1514
1515        my $identifier = join ' / ', x11_pid_to_identifier($pid, $masterdb);
1516
1517        $plot->push([nonzero has_changes @total_resource_count],
1518            title => pid_to_cmdline($masterdb, $pid) . ': ' . $identifier);
1519    }
1520
1521    done_plotting $plot;
1522}
1523BEGIN { register_generator \&generate_plot_x11_resource_count; }
1524
1525sub generate_plot_x11_pixmap_size {
1526    my $plotter = shift;
1527    my $masterdb = shift;
1528
1529    my $plot = $plotter->new_linespoints(
1530        key => '1071_x11_pixmap_size_%d',
1531        label => 'X11 pixmaps total size per process (excluding non-changed)',
1532        ylabel => 'MB',
1533        multiple => {
1534            max_plots => 2,
1535            max_per_plot => 10,
1536            split_f => sub { max @{shift()} },
1537            split_factor => 5,
1538            legend_f => sub { 'X11 PIXMAPS &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
1539        },
1540    );
1541
1542    my @pids = uniq grep { defined && length } map { $_->{PID} } map {
1543        exists $_->{'/usr/bin/xmeminfo'} ? @{$_->{'/usr/bin/xmeminfo'} // []} : undef
1544    } @$masterdb;
1545
1546    foreach my $pid (@pids) {
1547        my @pixmap_mem = map { my $entry = $_;
1548            exists $entry->{'/usr/bin/xmeminfo'} ?
1549            (sum map { $_->{Pixmap_mem} } grep { $_->{PID} == $pid } @{$entry->{'/usr/bin/xmeminfo'}}) :
1550            undef
1551        } @$masterdb;
1552
1553        my $identifier = join ' / ', x11_pid_to_identifier($pid, $masterdb);
1554
1555        $plot->push([nonzero has_changes b2mb @pixmap_mem],
1556            title => pid_to_cmdline($masterdb, $pid) . ': ' . $identifier);
1557    }
1558
1559    done_plotting $plot;
1560}
1561BEGIN { register_generator \&generate_plot_x11_pixmap_size; }
1562
1563sub generate_plot_df {
1564    my $plotter = shift;
1565    my $masterdb = shift;
1566
1567    my @mountpoints = uniq map { keys %{$_->{'/bin/df'}} } @$masterdb;
1568    #print Dumper(\@mountpoints);
1569
1570    my $plot = $plotter->new_linespoints(
1571        key => '2001_diskspace',
1572        label => '1. Disk space usage per mount point\n2. Global file descriptor usage %',
1573        legend => 'DISK USED, GLOBAL FD %',
1574        ylabel => 'percentage used',
1575    );
1576
1577    my $maxtitle = max map { length } @mountpoints;
1578
1579    foreach my $mountpoint (sort @mountpoints) {
1580        my ($filesystem) = uniq map { $_->{'/bin/df'}->{$mountpoint}->{filesystem} } @$masterdb;
1581        $plot->push(
1582            [nonzero map { $_->{'/bin/df'}->{$mountpoint}->{capacity} } @$masterdb],
1583            title => $filesystem ? sprintf("%-${maxtitle}s \t[$filesystem]", $mountpoint) : $mountpoint,
1584        );
1585    }
1586
1587    $plot->push(
1588        [nonzero map {
1589            $_->{'/proc/sys/fs/file-nr'}->{max_fds} > 0 ?
1590                ($_->{'/proc/sys/fs/file-nr'}->{allocated_fds} /
1591                 $_->{'/proc/sys/fs/file-nr'}->{max_fds}) * 100
1592            : undef
1593            } @$masterdb],
1594        lw => 5, title => 'Global FD usage %',
1595    );
1596
1597    done_plotting $plot;
1598}
1599BEGIN { register_generator \&generate_plot_df; }
1600
1601sub generate_plot_fd {
1602    my $plotter = shift;
1603    my $masterdb = shift;
1604
1605    my @pids = uniq map { keys %{$_->{'/proc/pid/fd_count'}} } @$masterdb;
1606
1607    my $plot = $plotter->new_histogram(
1608        key => '1080_fdcount',
1609        label => 'File descriptors per process',
1610        legend => 'FILE DESCRIPTORS',
1611        ylabel => 'count',
1612        column_limit => 1,
1613        reduce_f => sub {
1614            my @leftovers;
1615
1616            foreach my $idx (0 .. @$masterdb-1) {
1617                push @leftovers, sum map {
1618                    exists $_->{__data} &&
1619                    exists $_->{__data}->[$idx] ?
1620                           $_->{__data}->[$idx] : undef
1621                } @_;
1622            }
1623
1624            return [nonzero @leftovers],
1625                   title => 'Sum of ' . scalar(@_) . ' process FDs';
1626        },
1627    );
1628
1629    foreach my $pid (@pids) {
1630        $plot->push(
1631            [nonzero map { $_->{'/proc/pid/fd_count'}->{$pid} } @$masterdb],
1632            title => pid_to_cmdline($masterdb, $pid),
1633        );
1634    }
1635
1636    $plot->sort(sub { max @{shift()} });
1637    $plot->reduce;
1638    $plot->sort(\&max_change, sub { max @{shift()} });
1639
1640    done_plotting $plot;
1641}
1642BEGIN { register_generator \&generate_plot_fd; }
1643
1644sub generate_plot_fd_changes {
1645    my $plotter = shift;
1646    my $masterdb = shift;
1647
1648    my @pids = uniq map { keys %{$_->{'/proc/pid/fd_count'}} } @$masterdb;
1649
1650    my $plot = $plotter->new_linespoints(
1651        key => '1080_fdcount_changes',
1652        label => 'File descriptors per process (excluding non-changed)',
1653        legend => 'FILE DESCRIPTOR CHANGES',
1654        ylabel => 'count',
1655    );
1656
1657    foreach my $pid (@pids) {
1658        $plot->push(
1659            [nonzero has_changes map { $_->{'/proc/pid/fd_count'}->{$pid} } @$masterdb],
1660            title => pid_to_cmdline($masterdb, $pid),
1661        );
1662    }
1663
1664    $plot->sort(sub { shift->[-1] });
1665
1666    done_plotting $plot;
1667}
1668BEGIN { register_generator \&generate_plot_fd_changes; }
1669
1670sub generate_plot_pid_fd {
1671    my $plotter = shift;
1672    my $masterdb = shift;
1673
1674    my @pids = uniq map { keys %{$_->{'/proc/pid/fd'}} } @$masterdb;
1675
1676    foreach my $fdtype (keys %SP::Endurance::Parser::fdtypemap) {
1677        my $plot = $plotter->new_histogram(
1678            key => "1081_fdcount_$fdtype",
1679            label => ucfirst($fdtype) . ' file descriptor use per process',
1680            legend => 'FILE DESCRIPTORS &mdash; ' . uc $fdtype,
1681            ylabel => 'count',
1682            column_limit => 1,
1683            reduce_f => sub {
1684                my @leftovers;
1685
1686                foreach my $idx (0 .. @$masterdb-1) {
1687                    push @leftovers, sum map {
1688                        exists $_->{__data} &&
1689                        exists $_->{__data}->[$idx] ?
1690                               $_->{__data}->[$idx] : undef
1691                    } @_;
1692                }
1693
1694                return [nonzero @leftovers],
1695                       title => 'Sum of ' . scalar(@_) . ' process FDs';
1696            },
1697        );
1698
1699        foreach my $pid (@pids) {
1700            $plot->push(
1701                [nonzero map {
1702                    if (exists $_->{'/proc/pid/fd'}->{$pid}) {
1703                        my @entry = split ',', $_->{'/proc/pid/fd'}->{$pid};
1704                        exists $entry[$SP::Endurance::Parser::fdtypemap{$fdtype}] ?
1705                               $entry[$SP::Endurance::Parser::fdtypemap{$fdtype}] : undef
1706                    } else { undef }
1707                } @$masterdb],
1708                title => pid_to_cmdline($masterdb, $pid),
1709            );
1710        }
1711
1712        $plot->sort(sub { max @{shift()} });
1713        $plot->reduce;
1714        $plot->sort(\&max_change, sub { max @{shift()} });
1715
1716        done_plotting $plot;
1717    }
1718}
1719BEGIN { register_generator \&generate_plot_pid_fd; }
1720
1721sub generate_plot_pid_fd_changes {
1722    my $plotter = shift;
1723    my $masterdb = shift;
1724
1725    my @pids = uniq map { keys %{$_->{'/proc/pid/fd'}} } @$masterdb;
1726
1727    foreach my $fdtype (keys %SP::Endurance::Parser::fdtypemap) {
1728        my $plot = $plotter->new_linespoints(
1729            key => "1081_fdcount_${fdtype}_changes",
1730            label => ucfirst($fdtype) . ' file descriptor use per process (excluding non-changed)',
1731            legend => 'FILE DESCRIPTOR CHANGES &mdash; ' . uc $fdtype,
1732            ylabel => 'count',
1733        );
1734
1735        foreach my $pid (@pids) {
1736            $plot->push(
1737                [nonzero has_changes map {
1738                    if (exists $_->{'/proc/pid/fd'}->{$pid}) {
1739                    my @entry = map { int } split ',', $_->{'/proc/pid/fd'}->{$pid};
1740                    exists $entry[$SP::Endurance::Parser::fdtypemap{$fdtype}] ?
1741                           $entry[$SP::Endurance::Parser::fdtypemap{$fdtype}] : undef
1742                    } else { undef }
1743                } @$masterdb],
1744                title => pid_to_cmdline($masterdb, $pid),
1745            );
1746        }
1747
1748        $plot->sort(sub { shift->[-1] });
1749
1750        done_plotting $plot;
1751    }
1752}
1753BEGIN { register_generator \&generate_plot_pid_fd_changes; }
1754
1755sub generate_plot_interrupts {
1756    my $plotter = shift;
1757    my $masterdb = shift;
1758
1759    my $plot = $plotter->new_linespoints(
1760        key => '2070_interrupts',
1761        label => 'Interrupts.',
1762        legend => 'INTERRUPTS',
1763        ylabel => 'count per second',
1764        y2label => 'total count per second',
1765    );
1766
1767    my @interrupts = uniq grep { defined && length } map { keys %{$_->{'/proc/interrupts'} // {}} } @$masterdb;
1768    return unless @interrupts > 0;
1769
1770    foreach my $interrupt (@interrupts) {
1771        my ($desc) = uniq grep { defined && length } map {
1772            exists $_->{'/proc/interrupts'}->{$interrupt} &&
1773            exists $_->{'/proc/interrupts'}->{$interrupt}->{desc} ?
1774            $_->{'/proc/interrupts'}->{$interrupt}->{desc} : undef
1775        } @$masterdb;
1776
1777        my $idx = 0;
1778        $plot->push(
1779            [nonzero change_per_second $masterdb,
1780                cumulative_to_changes map {
1781                    exists $_->{'/proc/interrupts'}->{$interrupt} &&
1782                    exists $_->{'/proc/interrupts'}->{$interrupt}->{count} ?
1783                           $_->{'/proc/interrupts'}->{$interrupt}->{count} : undef
1784            } @$masterdb],
1785            axes => 'x1y1', title => sprintf("%-4s %s", $interrupt . ':', $desc),
1786        );
1787    }
1788
1789    my @total_interrupts = map {
1790        my $entry = $_;
1791        sum map { exists $_->{count} ? $_->{count} : undef } values %{$entry->{'/proc/interrupts'}}
1792    } @$masterdb;
1793
1794    $plot->push(
1795        [nonzero change_per_second $masterdb, cumulative_to_changes @total_interrupts],
1796        lw => 5, axes => 'x2y2', title => 'Total interrupts');
1797
1798    $plot->sort(sub { shift->[-1] });
1799
1800    done_plotting $plot;
1801}
1802BEGIN { register_generator \&generate_plot_interrupts; }
1803
1804sub generate_plot_diskstats_reads_mb {
1805    my $plotter = shift;
1806    my $masterdb = shift;
1807
1808    my @devices = uniq map { keys %{$_->{'/proc/diskstats'} // {}} } @$masterdb;
1809    return unless @devices > 0;
1810
1811    my $plot = $plotter->new_linespoints(
1812        key => '2100_diskstats_reads_mb',
1813        label => 'Bytes read from device',
1814        legend => 'DISK READS &mdash; MB',
1815        ylabel => 'MB',
1816    );
1817
1818    foreach my $device (@devices) {
1819        $plot->push(
1820            [b2mb nonzero cumulative_to_changes map {
1821                exists $_->{'/proc/diskstats'}->{$device} &&
1822                exists $_->{'/proc/diskstats'}->{$device}->{sectors_read} ?
1823                    $_->{'/proc/diskstats'}->{$device}->{sectors_read} * SECTOR_SIZE :
1824                    undef
1825                } @$masterdb],
1826            title => fs_to_mountpoint($device, $masterdb),
1827        );
1828    }
1829
1830    $plot->sort(sub { shift->[-1] });
1831
1832    done_plotting $plot;
1833}
1834BEGIN { register_generator \&generate_plot_diskstats_reads_mb; }
1835
1836sub generate_plot_diskstats_reads_mb_per_second {
1837    my $plotter = shift;
1838    my $masterdb = shift;
1839
1840    my @devices = uniq map { keys %{$_->{'/proc/diskstats'} // {}} } @$masterdb;
1841    return unless @devices > 0;
1842
1843    my $plot = $plotter->new_linespoints(
1844        key => '2100_diskstats_reads_mb_per_second',
1845        label => 'Bytes read from device per second',
1846        legend => 'DISK READS &mdash; MB/s',
1847        ylabel => 'MB per second',
1848    );
1849
1850    foreach my $device (@devices) {
1851        $plot->push(
1852            [b2mb nonzero change_per_second $masterdb, cumulative_to_changes map {
1853                exists $_->{'/proc/diskstats'}->{$device} &&
1854                exists $_->{'/proc/diskstats'}->{$device}->{sectors_read} ?
1855                       $_->{'/proc/diskstats'}->{$device}->{sectors_read} * SECTOR_SIZE :
1856                       undef
1857            } @$masterdb],
1858            title => fs_to_mountpoint($device, $masterdb),
1859        );
1860    }
1861
1862    $plot->sort(sub { shift->[-1] });
1863
1864    done_plotting $plot;
1865}
1866BEGIN { register_generator \&generate_plot_diskstats_reads_mb_per_second; }
1867
1868sub generate_plot_diskstats_written_mb {
1869    my $plotter = shift;
1870    my $masterdb = shift;
1871
1872    my @devices = uniq map { keys %{$_->{'/proc/diskstats'} // {}} } @$masterdb;
1873    return unless @devices > 0;
1874
1875    my $plot = $plotter->new_linespoints(
1876        key => '2100_diskstats_written_mb',
1877        label => 'Bytes written to device',
1878        legend => 'DISK WRITES &mdash; MB',
1879        ylabel => 'MB',
1880    );
1881
1882    foreach my $device (@devices) {
1883        $plot->push(
1884            [b2mb nonzero cumulative_to_changes map {
1885                exists $_->{'/proc/diskstats'}->{$device} &&
1886                exists $_->{'/proc/diskstats'}->{$device}->{sectors_written} ?
1887                    $_->{'/proc/diskstats'}->{$device}->{sectors_written} * SECTOR_SIZE :
1888                    undef
1889            } @$masterdb],
1890            title => fs_to_mountpoint($device, $masterdb),
1891        );
1892    }
1893
1894    $plot->sort(sub { shift->[-1] });
1895
1896    done_plotting $plot;
1897}
1898BEGIN { register_generator \&generate_plot_diskstats_written_mb; }
1899
1900sub generate_plot_diskstats_written_mb_per_second {
1901    my $plotter = shift;
1902    my $masterdb = shift;
1903
1904    my @devices = uniq map { keys %{$_->{'/proc/diskstats'} // {}} } @$masterdb;
1905    return unless @devices > 0;
1906
1907    my $plot = $plotter->new_linespoints(
1908        key => '2100_diskstats_written_mb_per_second',
1909        label => 'Bytes written to device per second',
1910        legend => 'DISK WRITES &mdash; MB/s',
1911        ylabel => 'MB per second',
1912    );
1913
1914    foreach my $device (@devices) {
1915        $plot->push(
1916            [b2mb nonzero change_per_second $masterdb, cumulative_to_changes map {
1917                exists $_->{'/proc/diskstats'}->{$device} &&
1918                exists $_->{'/proc/diskstats'}->{$device}->{sectors_written} ?
1919                       $_->{'/proc/diskstats'}->{$device}->{sectors_written} * SECTOR_SIZE :
1920                       undef
1921            } @$masterdb],
1922            title => fs_to_mountpoint($device, $masterdb),
1923        );
1924    }
1925
1926    $plot->sort(sub { shift->[-1] });
1927
1928    done_plotting $plot;
1929}
1930BEGIN { register_generator \&generate_plot_diskstats_written_mb_per_second; }
1931
1932sub generate_plot_bmestat {
1933    my $plotter = shift;
1934    my $masterdb = shift;
1935
1936    my ($type) = uniq map { $_->{'/usr/bin/bmestat'}->{battery_type} } @$masterdb;
1937    $type = " (type: $type)" if length $type;
1938
1939    my $plot = $plotter->new_linespoints(
1940        key => '2000_bmestat',
1941        label => "Battery$type\\n  Left: charge %, temperature\\n  Right: voltage",
1942        legend => 'BATTERY',
1943        ylabel => 'charge %, temperature in celsius',
1944        y2label => 'V',
1945    );
1946
1947    $plot->push(
1948        [nonzero map { $_->{'/usr/bin/bmestat'}->{battery_pct_level} } @$masterdb],
1949        axes => 'x1y1', lw => 5, title => 'Charge % left',
1950    );
1951
1952    $plot->push(
1953        [nonzero map { $_->{'/usr/bin/bmestat'}->{battery_temperature} } @$masterdb],
1954        axes => 'x1y1', title => 'Temperature',
1955    );
1956
1957    $plot->push(
1958        [nonzero map { $_->{'/usr/bin/bmestat'}->{battery_cur_voltage} / 1_000 } @$masterdb],
1959        axes => 'x2y2', title => 'Voltage',
1960    );
1961
1962    if ($plot->count) {
1963        my @backlights = uniq map { keys %{$_->{'/sys/class/backlight'}} } @$masterdb;
1964
1965        foreach my $bldev (@backlights) {
1966            $plot->push(
1967                [nonzero map {
1968                    exists $_->{'/sys/class/backlight'}->{$bldev} ?
1969                        ($_->{'/sys/class/backlight'}->{$bldev}->{actual_brightness} /
1970                         $_->{'/sys/class/backlight'}->{$bldev}->{max_brightness}) * 100
1971                             : undef
1972                } @$masterdb],
1973                axes => 'x1y1', title => "Backlight $bldev brightness %",
1974            );
1975        }
1976    }
1977
1978    done_plotting $plot;
1979}
1980BEGIN { register_generator \&generate_plot_bmestat; }
1981
1982sub generate_plot_ramzswap_1 {
1983    my $plotter = shift;
1984    my $masterdb = shift;
1985
1986    my $plot = $plotter->new_linespoints(
1987        key => '2111_ramzswap_1',
1988        label => 'ramzswap (compressed swap) reads and writes',
1989        legend => 'COMPR-SWAP READS/WRITES',
1990        ylabel => 'MB',
1991    );
1992
1993    foreach my $key (qw/NumReads NumWrites BDevNumReads BDevNumWrites/) {
1994        my @counts = map {
1995            exists $_->{ramzswap} &&
1996            exists $_->{ramzswap}->{'/dev/ramzswap0'} &&
1997            exists $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} ?
1998                   $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} : undef
1999        } @$masterdb;
2000        # Convert "reads" and "writes" to megabytes. I'm assuming that they are
2001        # always page sized operations...
2002        @counts = map { $_ * PAGE_SIZE } @counts;
2003        $plot->push([nonzero cumulative_to_changes b2mb @counts],
2004            title => $key);
2005    }
2006
2007    done_plotting $plot;
2008}
2009BEGIN { register_generator \&generate_plot_ramzswap_1; }
2010
2011sub generate_plot_ramzswap_2 {
2012    my $plotter = shift;
2013    my $masterdb = shift;
2014
2015    my $plot = $plotter->new_linespoints(
2016        key => '2111_ramzswap_2',
2017        label => 'ramzswap (compressed swap) memory usage',
2018        legend => 'COMPR-SWAP MEM USAGE',
2019        ylabel => 'GoodCompress and NoCompress %',
2020        y2label => 'MB',
2021    );
2022
2023    foreach my $key (qw/OrigDataSize ComprDataSize MemUsedTotal/) {
2024        $plot->push(
2025            [nonzero kb2mb map {
2026                exists $_->{ramzswap} &&
2027                exists $_->{ramzswap}->{'/dev/ramzswap0'} &&
2028                exists $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} ?
2029                       $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} : undef
2030            } @$masterdb],
2031            lw => 5, axes => 'x2y2', title => $key,
2032        );
2033    }
2034    $plot->push(
2035        [nonzero b2mb map { $_ * PAGE_SIZE } map {
2036            exists $_->{ramzswap} &&
2037            exists $_->{ramzswap}->{'/dev/ramzswap0'} &&
2038            exists $_->{ramzswap}->{'/dev/ramzswap0'}->{ZeroPages} ?
2039                   $_->{ramzswap}->{'/dev/ramzswap0'}->{ZeroPages} : undef
2040        } @$masterdb],
2041        lw => 5, axes => 'x2y2', title => 'ZeroPages',
2042    );
2043
2044    foreach my $key (qw/GoodCompress NoCompress/) {
2045        $plot->push(
2046            [nonzero map {
2047                exists $_->{ramzswap} &&
2048                exists $_->{ramzswap}->{'/dev/ramzswap0'} &&
2049                exists $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} ?
2050                       $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} : undef
2051            } @$masterdb],
2052            axes => 'x1y1', title => $key,
2053        );
2054    }
2055
2056    done_plotting $plot;
2057}
2058BEGIN { register_generator \&generate_plot_ramzswap_2; }
2059
2060sub generate_plot_ramzswap_3 {
2061    my $plotter = shift;
2062    my $masterdb = shift;
2063
2064    my $plot = $plotter->new_linespoints(
2065        key => '2111_ramzswap_3',
2066        label => 'ramzswap (compressed swap) errors',
2067        legend => 'COMPR-SWAP ERRORS',
2068        ylabel => 'count',
2069    );
2070
2071    foreach my $key (qw/FailedReads FailedWrites InvalidIO/) {
2072        $plot->push(
2073            [nonzero cumulative_to_changes map {
2074                exists $_->{ramzswap} &&
2075                exists $_->{ramzswap}->{'/dev/ramzswap0'} &&
2076                exists $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} ?
2077                       $_->{ramzswap}->{'/dev/ramzswap0'}->{$key} : undef
2078            } @$masterdb],
2079            title => $key,
2080        );
2081    }
2082
2083    done_plotting $plot;
2084}
2085BEGIN { register_generator \&generate_plot_ramzswap_3; }
2086
2087sub generate_plot_cgroups {
2088    my $plotter = shift;
2089    my $masterdb = shift;
2090
2091    my @cgroups = uniq grep { defined && length } map { keys %{$_->{cgroups} // {}} } @$masterdb;
2092    return unless @cgroups > 0;
2093
2094    my $tmp = 0;
2095    my %cgroup_colors = map { $_ => $SP::Endurance::Plot::line_colors[$tmp++ % scalar @SP::Endurance::Plot::line_colors] } @cgroups;
2096
2097    foreach my $key (qw/memory.usage_in_bytes memory.memsw.usage_in_bytes/) {
2098        my $plot;
2099
2100        $plot = $plotter->new_linespoints(
2101            key => '2300_cgroups-memory',
2102            label => 'Memory usage per cgroup.',
2103            legend => 'CGROUPS MEMORY',
2104            ylabel => 'MB',
2105        ) if $key eq 'memory.usage_in_bytes';
2106
2107        $plot = $plotter->new_linespoints(
2108            key => '2301_cgroups-memsw',
2109            label => 'Memory+Swap usage per cgroup.',
2110            legend => 'CGROUPS MEMORY+SWAP',
2111            ylabel => 'MB',
2112        ) if $key eq 'memory.memsw.usage_in_bytes';
2113
2114        foreach my $cgroup (sort @cgroups) {
2115            $plot->push([b2mb nonzero map {
2116                    exists $_->{cgroups} &&
2117                    exists $_->{cgroups}->{$cgroup} &&
2118                    exists $_->{cgroups}->{$cgroup}->{$key} ?
2119                        $_->{cgroups}->{$cgroup}->{$key} :
2120                        undef
2121                } @$masterdb],
2122                lc => $cgroup_colors{$cgroup},
2123                title => $cgroup,
2124            );
2125        }
2126
2127        my $limit_key = $key;
2128        $limit_key =~ s/usage/limit/;
2129
2130        foreach my $cgroup (sort @cgroups) {
2131            $plot->push([b2mb nonzero map {
2132                    exists $_->{cgroups} &&
2133                    exists $_->{cgroups}->{$cgroup} &&
2134                    exists $_->{cgroups}->{$cgroup}->{$limit_key} &&
2135                           $_->{cgroups}->{$cgroup}->{$limit_key} != CGROUP_UNLIMITED ?
2136                           $_->{cgroups}->{$cgroup}->{$limit_key} : undef
2137                } @$masterdb],
2138                lc => $cgroup_colors{$cgroup}, lw => 5,
2139                title => 'Limit for: ' . $cgroup,
2140            );
2141        }
2142
2143        done_plotting $plot;
2144    }
2145
2146    foreach my $memory_stat (qw/cache rss mapped_file swap inactive_anon
2147                active_anon inactive_file active_file unevictable
2148                pgpgin pgpgout/) {
2149
2150        my $plot = $plotter->new_linespoints(
2151            key => "2302_cgroups-$memory_stat",
2152            label => {
2153                cache         => 'Page cache per cgroup.',
2154                rss           => 'RSS (anonymous + swap cache) per cgroup.',
2155                mapped_file   => 'Mapped file per cgroup.',
2156                swap          => 'Swap usage per cgroup.',
2157                inactive_anon => 'Anon + swap cache on inactive LRU list per cgroup.',
2158                active_anon   => 'Anon + swap cache on active LRU list per cgroup.',
2159                inactive_file => 'File-backed memory on inactive LRU list per cgroup.',
2160                active_file   => 'File-backed memory on active LRU list per cgroup.',
2161                unevictable   => 'Unevictable memory per cgroup.',
2162                pgpgin        => 'Number of charging events to the memory cgroup.\n' .
2163                                 '(Charging event = page accounted as mapped anon page or cache page.)',
2164                pgpgout       => 'Number of uncharging events to the memory cgroup.\n' .
2165                                 '(Uncharging event = page unaccounted from the cgroup.)',
2166            }->{$memory_stat},
2167            legend => 'CGROUPS ' . uc $memory_stat,
2168            ylabel => ($memory_stat eq 'pgpgin' or $memory_stat eq 'pgpgout') ?
2169                'count' : 'MB',
2170        );
2171
2172        foreach my $cgroup (sort @cgroups) {
2173            my @dataset = map {
2174                    exists $_->{cgroups} &&
2175                    exists $_->{cgroups}->{$cgroup} &&
2176                    exists $_->{cgroups}->{$cgroup}->{'memory.stat'} &&
2177                    exists $_->{cgroups}->{$cgroup}->{'memory.stat'}->{$memory_stat} ?
2178                           $_->{cgroups}->{$cgroup}->{'memory.stat'}->{$memory_stat} :
2179                           undef
2180                } @$masterdb;
2181
2182            if ($memory_stat eq 'pgpgin' or $memory_stat eq 'pgpgout') {
2183                $plot->push([b2mb nonzero cumulative_to_changes @dataset], title => $cgroup);
2184            } else {
2185                $plot->push([b2mb nonzero @dataset], title => $cgroup);
2186            }
2187        }
2188
2189        done_plotting $plot;
2190    }
2191
2192    foreach my $key (qw/cgroup.procs tasks/) {
2193        my $plot = $plotter->new_linespoints(
2194            key => { 'cgroup.procs' => '2305_cgroups-procs',
2195                     tasks          => '2305_cgroups-tasks' }->{$key},
2196            label => { 'cgroup.procs' => 'Process count per cgroup.',
2197                       tasks          => 'Task count per cgroup.' }->{$key},
2198            legend => { 'cgroup.procs' => 'CGROUPS PROCESS COUNT',
2199                        tasks          => 'CGROUPS TASK COUNT' }->{$key},
2200            ylabel => 'count',
2201        );
2202
2203        foreach my $cgroup (sort @cgroups) {
2204            $plot->push([nonzero map {
2205                    exists $_->{cgroups} &&
2206                    exists $_->{cgroups}->{$cgroup} &&
2207                    exists $_->{cgroups}->{$cgroup}->{$key} ?
2208                           $_->{cgroups}->{$cgroup}->{$key} :
2209                           undef
2210                } @$masterdb],
2211                title => $cgroup,
2212            );
2213        }
2214
2215        done_plotting $plot;
2216    }
2217
2218    foreach my $key (qw/memory.failcnt memory.memsw.failcnt/) {
2219        my $plot = $plotter->new_linespoints(
2220            key => { 'memory.failcnt'       => '2306_cgroups-memory_failcnt',
2221                     'memory.memsw.failcnt' => '2306_cgroups-memsw_failcnt' }->{$key},
2222            label => { 'memory.failcnt'       => 'Memory fail count per cgroup.',
2223                       'memory.memsw.failcnt' => 'Memory+Swap fail count per cgroup.' }->{$key},
2224            legend => { 'memory.failcnt'       => 'CGROUPS MEMORY FAIL COUNT',
2225                        'memory.memsw.failcnt' => 'CGROUPS MEMORY+SWAP FAIL COUNT' }->{$key},
2226            ylabel => 'count',
2227        );
2228
2229        foreach my $cgroup (sort @cgroups) {
2230            $plot->push([nonzero cumulative_to_changes map {
2231                    exists $_->{cgroups} &&
2232                    exists $_->{cgroups}->{$cgroup} &&
2233                    exists $_->{cgroups}->{$cgroup}->{$key} ?
2234                        $_->{cgroups}->{$cgroup}->{$key} :
2235                        undef
2236                } @$masterdb],
2237                title => $cgroup,
2238            );
2239        }
2240
2241        done_plotting $plot;
2242    }
2243}
2244BEGIN { register_generator \&generate_plot_cgroups; }
2245
2246sub generate_plot_networking {
2247    my $plotter = shift;
2248    my $masterdb = shift;
2249
2250    my @interfaces = uniq map { keys %{$_->{'/sbin/ifconfig'} // {}} } @$masterdb;
2251
2252    foreach my $interface (sort @interfaces) {
2253        foreach my $key (qw/bytes packets/) {
2254            my $plot = $plotter->new_linespoints(
2255                key => "2090_networking-$key-$interface",
2256                label => "RX and TX $key for networking interface $interface",
2257                legend => 'NET RX/TX ' . uc($key) . ' &mdash; ' . $interface,
2258                ylabel => { bytes => 'kB', packets => 'packets' }->{$key},
2259            );
2260
2261            foreach my $rxtx (qw/RX TX/) {
2262                my @dataset = cumulative_to_changes map {
2263                            exists $_->{'/sbin/ifconfig'}->{$interface} &&
2264                            exists $_->{'/sbin/ifconfig'}->{$interface}->{$rxtx} &&
2265                            exists $_->{'/sbin/ifconfig'}->{$interface}->{$rxtx}->{$key} ?
2266                                   $_->{'/sbin/ifconfig'}->{$interface}->{$rxtx}->{$key} :
2267                                   undef
2268                    } @$masterdb;
2269
2270                if ($key eq 'bytes') {
2271                    foreach (@dataset) {
2272                        $_ /= 1_000 if defined;
2273                    }
2274                }
2275
2276                $plot->push([nonzero @dataset], title => $rxtx . ': ' . $interface);
2277            }
2278
2279            done_plotting $plot;
2280        }
2281    }
2282}
2283BEGIN { register_generator \&generate_plot_networking; }
2284
2285sub generate_plot_pagetypeinfo {
2286    my $plotter = shift;
2287    my $masterdb = shift;
2288
2289    my @pagetypes = uniq map { keys %{$_->{'/proc/pagetypeinfo'}->{0}->{Normal}} } @{$masterdb};
2290
2291    foreach my $pagetype (@pagetypes) {
2292        my $ok = 0;
2293        foreach my $ordernum (0 .. 10) {
2294            foreach my $entry (map { $_->{'/proc/pagetypeinfo'}->{0}->{Normal}->{$pagetype} } @{$masterdb}) {
2295                if (any { $_ } @$entry) {
2296                    $ok = 1;
2297                    last;
2298                }
2299            }
2300        }
2301        next unless $ok;
2302
2303        my $plot = $plotter->new_histogram(
2304            key => '2274_pagetypeinfo_' . lc $pagetype,
2305            label => "Free memory in $pagetype migrate type block pool (from /proc/pagetypeinfo)",
2306            legend => 'PAGETYPE &mdash; ' . uc $pagetype,
2307            ylabel => 'MB',
2308        );
2309
2310        foreach my $ordernum (reverse (0 .. 10)) {
2311            my @values;
2312            foreach my $entry (map { $_->{'/proc/pagetypeinfo'}->{0}->{Normal}->{$pagetype} } @{$masterdb}) {
2313                push @values, $entry->[$ordernum] * (1 << $ordernum) * PAGE_SIZE
2314            }
2315
2316            $plot->push([b2mb @values],
2317                title => ucfirst($pagetype) . " order 2^$ordernum",
2318            );
2319        }
2320
2321        done_plotting $plot;
2322    }
2323}
2324BEGIN { register_generator \&generate_plot_pagetypeinfo; }
2325
2326sub generate_plot_process_state_count {
2327    my $plotter = shift;
2328    my $masterdb = shift;
2329
2330    my $plot = $plotter->new_histogram(
2331        key => '2065_nonsleeping_process_count',
2332        label => 'Number of processes in non-sleep states.',
2333        legend => 'NON-SLEEP PROCESS COUNT',
2334        ylabel => 'count',
2335    );
2336
2337    my %states;
2338    foreach my $entry (@$masterdb) {
2339        next unless exists $entry->{'/proc/pid/stat'};
2340        foreach (values %{$entry->{'/proc/pid/stat'}}) {
2341            my %stat = split ',';
2342            next unless exists $stat{state};
2343            $states{$stat{state}} = 1;
2344        }
2345    }
2346
2347    foreach my $state (keys %states) {
2348        my @count;
2349
2350        foreach my $entry (@$masterdb) {
2351            my $cnt = grep { $_ eq $state } map {
2352                my %stat = split ',';
2353                exists $stat{state} ? $stat{state} : undef
2354            } values %{$entry->{'/proc/pid/stat'} // {}};
2355
2356            push @count, $cnt;
2357        }
2358
2359        $plot->push([nonzero @count],
2360            title => $state . {
2361                D => ' (Uninterruptible disk sleep)',
2362                R => ' (Running)',
2363                T => ' (Traced or stopped)',
2364                W => ' (Paging)',
2365                Z => ' (Zombie)',
2366            }->{$state},
2367        );
2368    }
2369
2370    $plot->sort(sub { shift->[-1] });
2371
2372    done_plotting $plot;
2373}
2374BEGIN { register_generator \&generate_plot_process_state_count; }
2375
2376my %wchan_suffix = (0 => ' (in user space)');
2377
2378sub generate_plot_wchan_count {
2379    my $plotter = shift;
2380    my $masterdb = shift;
2381
2382    my $plot = $plotter->new_histogram(
2383        key => '2350_wchan-count',
2384        label => 'Process count per wait channel',
2385        legend => 'WAIT CHANNEL COUNT',
2386        ylabel => 'process count',
2387    );
2388
2389    my @wchans = uniq map { keys %{$_->{'/proc/pid/wchan'}} } @$masterdb;
2390
2391    foreach my $wchan (@wchans) {
2392        $plot->push([nonzero map {
2393                exists $_->{'/proc/pid/wchan'} &&
2394                exists $_->{'/proc/pid/wchan'}->{$wchan} ?
2395                       $_->{'/proc/pid/wchan'}->{$wchan} : undef
2396            } @$masterdb],
2397            title => $wchan . $wchan_suffix{$wchan},
2398        );
2399    }
2400
2401    $plot->sort(sub { shift->[-1] });
2402
2403    done_plotting $plot;
2404}
2405BEGIN { register_generator \&generate_plot_wchan_count; }
2406
2407sub generate_plot_wchan_changes {
2408    my $plotter = shift;
2409    my $masterdb = shift;
2410
2411    my $plot = $plotter->new_linespoints(
2412        key => '2351_wchan-changes',
2413        label => 'Process count per wait channel (only changed shown)',
2414        legend => 'WAIT CHANNEL CHANGES',
2415        ylabel => 'process count',
2416    );
2417
2418    my @wchans = uniq map { keys %{$_->{'/proc/pid/wchan'}} } @$masterdb;
2419
2420    foreach my $wchan (@wchans) {
2421        $plot->push([has_changes nonzero map {
2422                exists $_->{'/proc/pid/wchan'} &&
2423                exists $_->{'/proc/pid/wchan'}->{$wchan} ?
2424                       $_->{'/proc/pid/wchan'}->{$wchan} : undef
2425            } @$masterdb],
2426            title => $wchan . $wchan_suffix{$wchan},
2427        );
2428    }
2429
2430    $plot->sort(sub { shift->[-1] });
2431
2432    done_plotting $plot;
2433}
2434BEGIN { register_generator \&generate_plot_wchan_changes; }
2435
2436sub generate_plot_power_supply {
2437    my $plotter = shift;
2438    my $masterdb = shift;
2439
2440    my @power_supplies = uniq map { keys %{$_->{'/sys/class/power_supply'}} } @$masterdb;
2441    my @backlights = uniq map { keys %{$_->{'/sys/class/backlight'}} } @$masterdb;
2442
2443    foreach my $dev (@power_supplies) {
2444        my ($type) = uniq map { $_->{'/sys/class/power_supply'}->{$dev}->{type} } @$masterdb;
2445        $type = '\nType: ' . $type if length $type;
2446
2447        my ($technology) = uniq map { $_->{'/sys/class/power_supply'}->{$dev}->{technology} } @$masterdb;
2448        $technology = '\nTechnology: ' . $technology if length $technology;
2449
2450        my ($model_name) = uniq map { $_->{'/sys/class/power_supply'}->{$dev}->{model_name} } @$masterdb;
2451        $model_name = '\nModel: ' . $model_name if length $model_name;
2452
2453        my ($manufacturer) = uniq map { $_->{'/sys/class/power_supply'}->{$dev}->{manufacturer} } @$masterdb;
2454        $manufacturer = '\nManufacturer: ' . $manufacturer if length $manufacturer;
2455
2456        my $plot = $plotter->new_linespoints(
2457            key => "2000_power_supply-$dev",
2458            label => "Power supply: ${dev}${type}${technology}${model_name}${manufacturer}",
2459            legend => "POWER SUPPLY &mdash; $dev",
2460            ylabel => 'charge-percent, temp-C',
2461            y2label => 'V',
2462        );
2463
2464        $plot->push(
2465            [nonzero map { $_->{'/sys/class/power_supply'}->{$dev}->{capacity} } @$masterdb],
2466            axes => 'x1y1', lw => 5, title => 'Charge % left',
2467        );
2468
2469        $plot->push(
2470            [nonzero map { $_->{'/sys/class/power_supply'}->{$dev}->{temp} } @$masterdb],
2471            axes => 'x1y1', title => 'Temperature',
2472        );
2473
2474        $plot->push(
2475            [nonzero map { $_->{'/sys/class/power_supply'}->{$dev}->{voltage_now} / 1e6 } @$masterdb],
2476            axes => 'x2y2', title => 'Voltage',
2477        );
2478
2479        if ($plot->count) {
2480            foreach my $bldev (@backlights) {
2481                $plot->push(
2482                    [nonzero map {
2483                        exists $_->{'/sys/class/backlight'}->{$bldev} ?
2484                            ($_->{'/sys/class/backlight'}->{$bldev}->{actual_brightness} /
2485                             $_->{'/sys/class/backlight'}->{$bldev}->{max_brightness}) * 100 :
2486                             undef
2487                    } @$masterdb],
2488                    axes => 'x1y1', title => "Backlight $bldev brightness %",
2489                );
2490            }
2491        }
2492
2493        done_plotting $plot;
2494    }
2495}
2496BEGIN { register_generator \&generate_plot_power_supply; }
2497
2498sub proc_pid_io_collect_data {
2499    my $plot = shift;
2500    my $masterdb = shift;
2501    my $pids = shift;
2502    my $key = shift;
2503
2504    my $idx = {
2505        read_bytes => 4,
2506        write_bytes => 5,
2507        cancelled_write_bytes => 6,
2508    }->{$key};
2509
2510    die "Invalid $key" unless defined $idx;
2511
2512    foreach my $pid (@$pids) {
2513        $plot->push(
2514            [nonzero cumulative_to_changes b2mb map {
2515                if (exists $_->{'/proc/pid/io'}->{$pid}) {
2516                    my @entry = unpack "d*", $_->{'/proc/pid/io'}->{$pid};
2517                    defined $entry[$idx] ? $entry[$idx] : undef
2518                } else { undef }
2519            } @$masterdb],
2520            title => pid_to_cmdline($masterdb, $pid),
2521        );
2522    }
2523
2524    return $plot;
2525}
2526
2527sub generate_plot_pid_io {
2528    my $plotter = shift;
2529    my $masterdb = shift;
2530
2531    my @pids = uniq map { keys %{$_->{'/proc/pid/io'}} } @$masterdb;
2532
2533    # Not generating the line graphs for 'cancelled_write_bytes' on purpose,
2534    # the histogram is enough for now.
2535    foreach my $key (qw/read_bytes write_bytes/) {
2536        my $plot = $plotter->new_linespoints(
2537            key => {
2538                    read_bytes => '1300_pid_io_read_bytes_%d',
2539                    write_bytes => '1301_pid_io_write_bytes_%d',
2540                    cancelled_write_bytes => '1302_pid_io_cancelled_write_bytes_%d',
2541                }->{$key},
2542            label => {
2543                    read_bytes => 'Per process disk reads.',
2544                    write_bytes => 'Per process disk writes.',
2545                    cancelled_write_bytes => 'Per process cancelled disk writes.',
2546                }->{$key},
2547            ylabel => 'MB',
2548            multiple => {
2549                max_plots => 3,
2550                max_per_plot => 10,
2551                split_f => sub { max @{shift()} },
2552                split_factor => 5,
2553                legend_f => sub {
2554                    { read_bytes => 'DISK READS',
2555                      write_bytes => 'DISK WRITES',
2556                      cancelled_write_bytes => 'CANCELLED DISK WRITES',
2557                      }->{$key} .
2558                    ' &mdash; MAX ' . ceil(max @{shift()}) . 'MB' },
2559            },
2560        );
2561
2562        proc_pid_io_collect_data $plot, $masterdb, \@pids, $key;
2563
2564        done_plotting $plot;
2565    }
2566}
2567BEGIN { register_generator \&generate_plot_pid_io; }
2568
2569sub generate_plot_pid_io_histogram {
2570    my $plotter = shift;
2571    my $masterdb = shift;
2572
2573    my @pids = uniq map { keys %{$_->{'/proc/pid/io'}} } @$masterdb;
2574
2575    foreach my $key (qw/read_bytes write_bytes cancelled_write_bytes/) {
2576        my $plot = $plotter->new_histogram(
2577            key => {
2578                    read_bytes => '1300_pid_io_read_bytes',
2579                    write_bytes => '1301_pid_io_write_bytes',
2580                    cancelled_write_bytes => '1302_pid_io_cancelled_write_bytes',
2581                }->{$key},
2582            label => {
2583                    read_bytes => 'Per process disk reads.',
2584                    write_bytes => 'Per process disk writes.',
2585                    cancelled_write_bytes => 'Per process cancelled disk writes.',
2586                }->{$key},
2587            legend => {
2588                    read_bytes => 'DISK READS',
2589                    write_bytes => 'DISK WRITES',
2590                    cancelled_write_bytes => 'CANCELLED DISK WRITES',
2591                }->{$key},
2592            ylabel => 'MB',
2593        );
2594
2595        proc_pid_io_collect_data $plot, $masterdb, \@pids, $key;
2596
2597        $plot->sort(\&max_change, sub { max @{shift()} });
2598
2599        done_plotting $plot;
2600    }
2601}
2602BEGIN { register_generator \&generate_plot_pid_io_histogram; }
2603
2604sub generate_plot_upstart_jobs_respawned {
2605    my $plotter = shift;
2606    my $masterdb = shift;
2607
2608    my @jobs = uniq grep { defined and length } map { keys %{$_->{upstart_jobs_respawned}} } @$masterdb;
2609
2610    my $plot = $plotter->new_histogram(
2611        key => '1400_upstart_jobs_respawned',
2612        label => 'Jobs respawned by Upstart',
2613        legend => 'UPSTART JOBS RESPAWNED',
2614        ylabel => 'count',
2615    );
2616
2617    foreach my $job (@jobs) {
2618        $plot->push(
2619            [nonzero cumulative_to_changes map {
2620                exists $_->{upstart_jobs_respawned} &&
2621                exists $_->{upstart_jobs_respawned}->{$job} ?
2622                       $_->{upstart_jobs_respawned}->{$job} : undef
2623            } @$masterdb],
2624            title => $job,
2625        );
2626    }
2627
2628    $plot->sort(\&max_change, sub { max @{shift()} });
2629
2630    done_plotting $plot;
2631}
2632BEGIN { register_generator \&generate_plot_upstart_jobs_respawned; }
2633
2634sub generate_plot_sched_wakeups {
2635    my $plotter = shift;
2636    my $masterdb = shift;
2637
2638    my $plot = $plotter->new_linespoints(
2639        key => '1500_sched_wakeups_%d',
2640        label => 'Number of times the process was woken up.',
2641        ylabel => 'count',
2642        multiple => {
2643            max_plots => 3,
2644            max_per_plot => 10,
2645            split_f => sub { max @{shift()} },
2646            split_factor => 5,
2647            legend_f => sub { 'SCHED &mdash; WAKEUPS &mdash; MAX ' . ceil(max @{shift()}) },
2648        },
2649    );
2650
2651    my @pids = uniq grep { defined and length } map { keys %{$_->{'/proc/pid/sched'}} } @$masterdb;
2652
2653    foreach my $pid (@pids) {
2654        $plot->push(
2655            [nonzero cumulative_to_changes map {
2656                if (exists $_->{'/proc/pid/sched'} && exists $_->{'/proc/pid/sched'}->{$pid}) {
2657                    my @entry = unpack('d*', $_->{'/proc/pid/sched'}->{$pid});
2658                    exists $entry[$SP::Endurance::Parser::schedmap{'se.statistics.nr_wakeups'}] ?
2659                           $entry[$SP::Endurance::Parser::schedmap{'se.statistics.nr_wakeups'}] :
2660                           undef
2661                } else { undef }
2662            } @$masterdb],
2663            title => pid_to_cmdline($masterdb, $pid),
2664        );
2665    }
2666
2667    done_plotting $plot;
2668}
2669BEGIN { register_generator \&generate_plot_sched_wakeups; }
2670
2671sub generate_plot_sched_iowait {
2672    my $plotter = shift;
2673    my $masterdb = shift;
2674
2675    my $plot = $plotter->new_linespoints(
2676        key => '1501_sched_iowait_%d',
2677        label => 'Time spent waiting for I/O.',
2678        ylabel => 'ms',
2679        multiple => {
2680            max_plots => 3,
2681            max_per_plot => 10,
2682            split_f => sub { max @{shift()} },
2683            split_factor => 5,
2684            legend_f => sub { 'SCHED &mdash; I/O WAIT &mdash; MAX ' . ceil(max @{shift()}) . 'MS'},
2685        },
2686    );
2687
2688    my @pids = uniq grep { defined and length } map { keys %{$_->{'/proc/pid/sched'}} } @$masterdb;
2689
2690    foreach my $pid (@pids) {
2691        $plot->push(
2692            [nonzero cumulative_to_changes map {
2693                if (exists $_->{'/proc/pid/sched'} && exists $_->{'/proc/pid/sched'}->{$pid}) {
2694                    my @entry = unpack('d*', $_->{'/proc/pid/sched'}->{$pid});
2695                    exists $entry[$SP::Endurance::Parser::schedmap{'se.statistics.iowait_sum'}] ?
2696                           $entry[$SP::Endurance::Parser::schedmap{'se.statistics.iowait_sum'}] :
2697                           undef
2698                } else { undef }
2699            } @$masterdb],
2700            title => pid_to_cmdline($masterdb, $pid),
2701        );
2702    }
2703
2704    done_plotting $plot;
2705}
2706BEGIN { register_generator \&generate_plot_sched_iowait; }
2707
2708sub generate_plot_sched_block_max {
2709    my $plotter = shift;
2710    my $masterdb = shift;
2711
2712    my $plot = $plotter->new_linespoints(
2713        key => '1502_sched_block_max_%d',
2714        label => 'Maximum time the process has been blocked in uninterruptible sleep.',
2715        ylabel => 'ms',
2716        multiple => {
2717            max_plots => 3,
2718            max_per_plot => 10,
2719            split_f => sub { max @{shift()} },
2720            split_factor => 5,
2721            legend_f => sub { 'SCHED &mdash; BLOCK_MAX &mdash; ' . ceil(max @{shift()}) . 'MS'},
2722        },
2723    );
2724
2725    my @pids = uniq grep { defined and length } map { keys %{$_->{'/proc/pid/sched'}} } @$masterdb;
2726
2727    foreach my $pid (@pids) {
2728        $plot->push(
2729            [nonzero map {
2730                if (exists $_->{'/proc/pid/sched'} && exists $_->{'/proc/pid/sched'}->{$pid}) {
2731                    my @entry = unpack('d*', $_->{'/proc/pid/sched'}->{$pid});
2732                    exists $entry[$SP::Endurance::Parser::schedmap{'se.statistics.block_max'}] ?
2733                           $entry[$SP::Endurance::Parser::schedmap{'se.statistics.block_max'}] :
2734                           undef
2735                } else { undef }
2736            } @$masterdb],
2737            title => pid_to_cmdline($masterdb, $pid),
2738        );
2739    }
2740
2741    done_plotting $plot;
2742}
2743BEGIN { register_generator \&generate_plot_sched_block_max; }
2744
2745sub generate_plot_sched_wait_max {
2746    my $plotter = shift;
2747    my $masterdb = shift;
2748
2749    my $plot = $plotter->new_linespoints(
2750        key => '1503_sched_wait_max_%d',
2751        label => 'Maximum time the process waited in kernel runqueue before entering CPU.',
2752        ylabel => 'ms',
2753        multiple => {
2754            max_plots => 3,
2755            max_per_plot => 10,
2756            split_f => sub { max @{shift()} },
2757            split_factor => 5,
2758            legend_f => sub { 'SCHED &mdash; WAIT_MAX &mdash; ' . ceil(max @{shift()}) . 'MS'},
2759        },
2760    );
2761
2762    my @pids = uniq grep { defined and length } map { keys %{$_->{'/proc/pid/sched'}} } @$masterdb;
2763
2764    foreach my $pid (@pids) {
2765        $plot->push(
2766            [nonzero map {
2767                if (exists $_->{'/proc/pid/sched'} && exists $_->{'/proc/pid/sched'}->{$pid}) {
2768                    my @entry = unpack('d*', $_->{'/proc/pid/sched'}->{$pid});
2769                    exists $entry[$SP::Endurance::Parser::schedmap{'se.statistics.wait_max'}] ?
2770                           $entry[$SP::Endurance::Parser::schedmap{'se.statistics.wait_max'}] :
2771                           undef
2772                } else { undef }
2773            } @$masterdb],
2774            title => pid_to_cmdline($masterdb, $pid),
2775        );
2776    }
2777
2778    done_plotting $plot;
2779}
2780BEGIN { register_generator \&generate_plot_sched_wait_max; }
2781
27821;