From fbee7205e54102cc3a52a0b9293979f5ea403932 Mon Sep 17 00:00:00 2001 From: fbsanchez Date: Mon, 18 Dec 2017 18:50:26 +0100 Subject: [PATCH] pandora_perf_report v1 --- pandora_server/util/pandora_perf_report.pl | 380 +++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 pandora_server/util/pandora_perf_report.pl diff --git a/pandora_server/util/pandora_perf_report.pl b/pandora_server/util/pandora_perf_report.pl new file mode 100644 index 0000000000..e740eec78b --- /dev/null +++ b/pandora_server/util/pandora_perf_report.pl @@ -0,0 +1,380 @@ +#!/usr/bin/perl +################################################################################ +# Pandora FMS Performance Report Tool +# Copyright (c) 2017 Artica Soluciones Tecnologicas S.L. +################################################################################ +use strict; +use warnings; + +use POSIX qw (strftime floor); +use Sys::Hostname; +use Time::HiRes qw(gettimeofday tv_interval); + +use lib '/usr/lib/perl5'; + +# Pandora Modules. +use PandoraFMS::Config; +use PandoraFMS::Tools; +use PandoraFMS::DataServer; +use PandoraFMS::DB; +use PandoraFMS::Core; +use Thread::Semaphore; + +our $VERSION = '1.0'; + +my $STRESS_AGENT_NAME = '__PANDORA_STRESS_AGENT__'; # Name of the agent used for XML stress tests. +my $STRESS_AGENT_MODULES = 50; # Number of modules per agent. +my $STRESS_AGENT_OPS = 50; # Number of XML iterations (one XML per iteration). +my $STRESS_AGENT_XML = { # Hash representing data coming from an XML data file. + 'os_name' => 'Other', + 'version' => '7.0NG', + 'interval' => '300', + 'description' => 'Pandora FMS XML stress agent', + 'os_version' => '', + 'agent_name' => $STRESS_AGENT_NAME, + 'timestamp' => 0, + 'agent_alias' => $STRESS_AGENT_NAME, + 'module' => [] +}; + +my $STRESS_CPU_OPS = 100000; # Number of CPU iterations. + +my $STRESS_DB_OPS = 5; # Number of DB iterations. +my $STRESS_DB_BLOCK = 1; # Number of rows read per SELECT statement. + +################################################################################ +# Print a generic ratio calculated as $num/$den. +################################################################################ +sub ratio { + my ($num, $den) = @_; + + return $den == 0 ? "Inf" : sprintf("%10.2f", $num/$den); +} + +################################################################################ +# Print a generic numeric metric. +################################################################################ +sub metric { + my ($metric) = @_; + + # Integer. + if ($metric =~ /^-?\d+\z/) { + return sprintf("%10d", $metric); + } + # Float. + else { + return sprintf("%10.2f", $metric); + } +} + +################################################################################ +# Print log messages. +################################################################################ +sub print_log { + my ($sec, $msg) = @_; + + print(strftime ("%Y-%m-%d %H:%M:%S", localtime()) . " [$sec] $msg\n"); +} + +################################################################################ +# Stress the CPU. +################################################################################ +sub stress_cpu { + my $t0 = [gettimeofday]; + + print_log("CPU", "start"); + for (my $i = 1; $i <= $STRESS_CPU_OPS; $i++) { + my $sqrt = sqrt($i); + my $exp = exp($i); + my $log = log($i); + my $sin = sin($i); + my $cos = cos($i); + } + my $elapsed = tv_interval($t0) * 1000; # Convert from s to ms. + + print_log("CPU", "stop"); + return ratio($STRESS_CPU_OPS, $elapsed); +} + +################################################################################ +# Stress the DB. +################################################################################ +sub stress_db { + my ($dbh) = @_; + + print_log("DB INSERT", "start"); + my $t0 = [gettimeofday]; + for (my $i = 1; $i <= $STRESS_DB_OPS; $i++) { + db_do($dbh, "INSERT INTO tagente_datos (`id_agente_modulo`, `datos`, `utimestamp`) VALUES (0, ?, ?)", $STRESS_DB_OPS - $i, $i); + } + my $insert_score = ratio($STRESS_DB_OPS, tv_interval($t0)); + print_log("DB INSERT", "stop"); + + print_log("DB UPDATE", "start"); + $t0 = [gettimeofday]; + for (my $i = 1; $i <= $STRESS_DB_OPS; $i++) { + db_do($dbh, "UPDATE tagente_datos SET datos=? WHERE id_agente_modulo = 0 AND utimestamp = ?", $i, $i); + } + my $update_score = ratio($STRESS_DB_OPS, tv_interval($t0)); + print_log("DB UPDATE", "stop"); + + print_log("DB SELECT", "start"); + $t0 = [gettimeofday]; + for (my $i = 1; $i <= $STRESS_DB_OPS; $i++) { + db_do($dbh, "SELECT SQL_NO_CACHE datos FROM tagente_datos WHERE id_agente_modulo = 0 AND utimestamp > ? AND utimestamp < ?", $i, $i + $STRESS_DB_BLOCK); + } + my $select_score = ratio($STRESS_DB_OPS, tv_interval($t0)); + print_log("DB SELECT", "stop"); + + print_log("DB DELETE", "start"); + $t0 = [gettimeofday]; + for (my $i = 1; $i <= $STRESS_DB_OPS; $i++) { + db_do($dbh, "DELETE FROM tagente_datos WHERE id_agente_modulo = 0 AND utimestamp = ?", $i); + } + my $delete_score = ratio($STRESS_DB_OPS, tv_interval($t0)); + print_log("DB DELETE", "stop"); + + return { 'insert_score' => $insert_score, 'update_score' => $update_score, 'select_score' => $select_score, 'delete_score' => $delete_score }; +} + +################################################################################ +# Stress a single agent. +################################################################################ +sub stress_agent { + my ($dbh, $conf) = @_; + + # Initialize some lexical variables needed by the Data Server. + PandoraFMS::DataServer->new($conf); + + # Create the stress agent. + my $agent_id = get_agent_id($dbh, $STRESS_AGENT_NAME); + if ($agent_id == -1) { + print_log("XML", "creating stress agent"); + $agent_id = pandora_create_agent ($conf, '', safe_input($STRESS_AGENT_NAME), '127.0.0.1', 10, 0, 10, 'Pandora FMS XML stress agent', 300, $dbh); + } else { + print_log("XML", "stress agent already exists"); + } + + if (!defined($agent_id) || $agent_id == -1) { + die("Error creating stress agent.\n\n"); + } + + # Make sure the timestamp from the XML file (as opposed to the timestamp in the XML data) is never used. + $conf->{'use_xml_timestamp'} = 0; + + # Pre-generate timestamps. + my @timestamps; + for (my $i = 0, my $offset = 0; $i < $STRESS_AGENT_OPS; $i++, $offset += 172800) { + $timestamps[$i] = strftime ("%Y-%m-%d %H:%M:%S", localtime($offset)); + } + + print_log("XML", "initializing $STRESS_AGENT_MODULES modules"); + for (my $i = 0; $i < $STRESS_AGENT_MODULES; $i++) { + push(@{$STRESS_AGENT_XML->{'module'}}, + { + 'name' => ["Module $i"], + 'data' => [$i], + 'type' => ['generic_data'] + }); + } + + print_log("XML", "start"); + my $t0 = [gettimeofday]; + for (my $i = 0; $i < $STRESS_AGENT_OPS; $i++) { + $STRESS_AGENT_XML->{'timestamp'} = $timestamps[$i]; + PandoraFMS::DataServer::process_xml_data ($conf, $0, $STRESS_AGENT_XML, 0, $dbh); + } + my $elapsed = tv_interval($t0); + my $agent_score = ratio($STRESS_AGENT_OPS, $elapsed); + my $module_score = ratio($STRESS_AGENT_OPS * $STRESS_AGENT_MODULES, $elapsed); + print_log("XML", "stop"); + + print_log("XML", "deleting stress agent"); + pandora_delete_agent($dbh, $agent_id); + + return { 'agent_score' => $agent_score, 'module_score' => $module_score }; +} + +################################################################################ +# Compute table stats. +################################################################################ +sub table_stats { + my ($dbh, $conf) = @_; + my $stats = { + tagent_access => 'N/A', + tagente => 'N/A', + tagente_datos => 'N/A', + tagente_datos_string => 'N/A', + tagente => 'N/A', + tevento => 'N/A', + tsesion => 'N/A', + }; + + my @rows = get_db_rows($dbh, "SELECT TABLE_NAME, TABLE_ROWS + FROM information_schema.TABLES + WHERE TABLE_SCHEMA=? + AND TABLE_NAME IN (?, ?, ?, ?, ?, ?, ?)", + $conf->{'dbname'}, + 'tagent_access', + 'tagente', + 'tagente_datos', + 'tagente_datos_string', + 'tagente_modulo', + 'tevento', + 'tsesion', + ); + + foreach my $row (@rows) { + $stats->{$row->{'TABLE_NAME'}} = metric($row->{'TABLE_ROWS'}); + } + + return $stats; +} + +################################################################################ +# Add recommendations based on the given table stats. +################################################################################ +sub table_comments { + my ($stats) = @_; + my $comments = { + tagent_access => 'OK', + tagente => 'OK', + tagente_datos => 'OK', + tagente_datos_string => 'OK', + tagente_modulo => 'OK', + tagente => 'OK', + tevento => 'OK', + tsesion => 'OK', + }; + + if ($stats->{'tagent_access'} > $stats->{'tagente'} * 24 * 250) { + $comments->{'tagent_access'} = 'CRITICAL: Table too big. Please contact our support team at: support@artica.es'; + } elsif ($stats->{'tagent_access'} > $stats->{'tagente'} * 24 * 100) { + $comments->{'tagent_access'} = 'WARNING: Table too big. Please contact our support team at: support@artica.es'; + } + + if ($stats->{'tagente_datos'} > 5000000) { + $comments->{'tagente_datos'} = 'CRITICAL: Table too big. Please use a history database or decrease the purge period.'; + } elsif ($stats->{'tagente_datos'} > 1000000) { + $comments->{'tagente_datos'} = 'WARNING: Table too big. Please use a history database or decrease the purge period.'; + } + + if ($stats->{'tagente_modulo'} > 500000) { + $comments->{'tsesion'} = 'CRITICAL: Table too big. Please contact our support team at: support@artica.es'; + } elsif ($stats->{'tagente_modulo'} > 350000) { + $comments->{'tsesion'} = 'WARNING Table too big. Please contact our support team at: support@artica.es'; + } + + if ($stats->{'tevento'} > 50000) { + $comments->{'tevento'} = 'CRITICAL: Table too big. Please use a history database or decrease the purge period.'; + } elsif ($stats->{'tevento'} > 25000) { + $comments->{'tevento'} = 'WARNING: Table too big. Please use a history database or decrease the purge period.'; + } + + if ($stats->{'tsesion'} > 50000) { + $comments->{'tsesion'} = 'CRITICAL: Table too big. Please contact our support team at: support@artica.es'; + } elsif ($stats->{'tsesion'} > 15000) { + $comments->{'tsesion'} = 'WARNING: Table too big. Please contact our support team at: support@artica.es'; + } + + return $comments; +} + +################################################################################ +# Main. +################################################################################ +my %conf; + +# Connect to the DB. +pandora_init(\%conf,"Pandora FMS Performance Report Tool"); +pandora_load_config(\%conf); +my $dbh = db_connect($conf{'dbengine'}, $conf{'dbname'}, $conf{'dbhost'}, $conf{'dbport'}, $conf{'dbuser'}, $conf{'dbpass'}); + +# Do not show server messages when running the stress tests. +$conf{'verbosity'} = 0; + +$conf{'daemon'} = 0; + +# CPU score. +my $cpu_score = stress_cpu(); + +# DB Scores. +my $db_scores = stress_db($dbh); + +# Table stats. +my $table_stats = table_stats($dbh, \%conf); + +# Add recommendations. +my $table_comments = table_comments($table_stats); + +# Agent and module stats. +my $agent_scores = stress_agent($dbh, \%conf); + +db_disconnect($dbh); + +# Print the report. +my $tstamp = strftime("%Y-%m-%d %H:%M:%S", localtime()); +my $host = hostname; +my $cpu = `cat /proc/cpuinfo | grep "model name" | head -1 | cut -d: -f2 | tr -d ' '`; +chomp($cpu); +my $cores = `cat /proc/cpuinfo | grep "model name" | wc -l`; +chomp($cores); +my $mem = `cat /proc/meminfo | grep "MemTotal" | cut -d: -f2 | tr -d ' '`; +chomp($mem); + +my $max_agents = floor ($agent_scores->{'agent_score'} * 300); +my $max_modules = floor ($agent_scores->{'module_score'} * 300); + +print <<__EOF +------------------------------------------------------------------------------- + +Pandora FMS Performance Report Tool v$VERSION +Report generated at: $tstamp + +Host: $host +CPU: $cpu +Cores: $cores +Memory: $mem + +Metric Value Reference value for small/medium/large systems (*) +------ ----- -------------------------------------------------- + +CPU ops/ms $cpu_score (NA/NA/NA) +DB INSERT/s $db_scores->{'insert_score'} (NA/NA/NA) +DB UPDATE/s $db_scores->{'update_score'} (NA/NA/NA) +DB SELECT/s $db_scores->{'select_score'} (NA/NA/NA) +DB DELETE/s $db_scores->{'delete_score'} (NA/NA/NA) + +(*) small ~ 250 agents | medium ~ 1000 agents | large > 5000 agents + + +Database Analisys Row count Comments & recomendations +----------------- --------- ------------------------- +Agent table $table_stats->{'tagente'} $table_comments->{'tagente'} +Module table $table_stats->{'tagente_modulo'} $table_comments->{'tagente_modulo'} +Data table $table_stats->{'tagente_datos'} $table_comments->{'tagente_datos'} +String data table $table_stats->{'tagente_datos_string'} $table_comments->{'tagente_datos_string'} +Event table $table_stats->{'tevento'} $table_comments->{'tevento'} +Access stats $table_stats->{'tagent_access'} $table_comments->{'tagent_access'} +Audit information $table_stats->{'tsesion'} $table_comments->{'tsesion'} + + +Agent Data Processing (**) +-------------------------- + +Agents per second $agent_scores->{'agent_score'} +Modules per second $agent_scores->{'module_score'} + + +Max usage recommended (**)(***) +------------------------------- + +Max agents $max_agents +Max modules $max_modules + +(**) Single thread worst case scenario. Not representative of the real + performance of a multi-threaded Pandora FMS Data Server. + +(***) Taking a base interval 300s (default). + +__EOF