2009-06-05 17:38:19 +02:00
#!/usr/bin/perl
################################################################################
# Pandora XML Stress tool.
################################################################################
# Copyright (c) 2009 Ramon Novoa, rnovoa@artica.es
# Copyright (c) 2009 Artica Soluciones Tecnologicas S.L.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
################################################################################
use strict ;
use warnings ;
use threads ;
use threads::shared ;
use Time::Local ;
use Time::HiRes qw( gettimeofday ) ;
2009-06-08 20:36:12 +02:00
use POSIX qw (strftime ceil ) ;
2009-06-05 17:38:19 +02:00
# Global variables used for statistics
my $ Agents : shared = 0 ;
my $ Modules : shared = 0 ;
my $ XMLFiles : shared = 0 ;
my $ LogLock : shared ;
################################################################################
# Load the configuration file.
################################################################################
sub load_config ($\%\@) {
my ( $ conf_file , $ conf , $ modules ) = @ _ ;
open ( FILE , "<" , $ conf_file ) || die ( "[error] Could not open configuration file '$conf_file': $!.\n\n" ) ;
while ( my $ line = <FILE> ) {
# A module definition
if ( $ line =~ m/module_begin/ ) {
my % module ;
2009-07-08 13:01:47 +02:00
# A comment
next if ( $ line =~ m/^#/ ) ;
2009-06-05 17:38:19 +02:00
while ( my $ line = <FILE> ) {
# A comment
next if ( $ line =~ m/^#/ ) ;
2009-07-08 13:01:47 +02:00
last if ( $ line =~ m/module_end/ ) ;
2009-06-05 17:38:19 +02:00
# Unknown line
next if ( $ line !~ /^\s*(\w+)\s+(.+)$/ ) ;
$ module { $ 1 } = $ 2 ;
}
push ( @ { $ modules } , \ % module ) ;
$ Modules + + ;
next ;
}
# Unknown line
next if ( $ line !~ /^\s*(\w+)\s+(.+)$/ ) ;
$ conf - > { $ 1 } = $ 2 ;
}
close ( FILE ) ;
}
################################################################################
# Generate XML files.
################################################################################
2009-06-08 20:36:12 +02:00
sub generate_xml_files ($$$$$) {
my ( $ agents , $ start , $ step , $ conf , $ modules ) = @ _ ;
2009-06-05 17:38:19 +02:00
# Read agent configuration
2009-07-08 13:01:47 +02:00
my $ interval = get_conf_token ( $ conf , 'agent_interval' , '300' ) ;
2009-06-05 17:38:19 +02:00
my $ xml_version = get_conf_token ( $ conf , 'xml_version' , '1.0' ) ;
my $ encoding = get_conf_token ( $ conf , 'encoding' , 'ISO-8859-1' ) ;
my $ os_name = get_conf_token ( $ conf , 'os_name' , 'Linux' ) ;
my $ os_version = get_conf_token ( $ conf , 'os_version' , '2.6' ) ;
my $ temporal = get_conf_token ( $ conf , 'temporal' , '/tmp' ) ;
2010-01-20 14:34:13 +01:00
my $ startup_delay = get_conf_token ( $ conf , 'startup_delay' , '5' ) ;
my $ ag_timezone_offset = get_conf_token ( $ conf , 'timezone_offset' , '0' ) ;
2010-02-05 16:27:25 +01:00
my $ ag_timezone_offset_range = get_conf_token ( $ conf , 'timezone_offset_range' , '0' ) ;
2010-01-12 18:22:14 +01:00
my $ latitude_base = get_conf_token ( $ conf , 'latitude_base' , '40.42056' ) ;
my $ longitude_base = get_conf_token ( $ conf , 'longitude_base' , '-3.708187' ) ;
my $ altitude_base = get_conf_token ( $ conf , 'altitude_base' , '0' ) ;
my $ position_radius = get_conf_token ( $ conf , 'position_radius' , '10' ) ;
2009-06-05 17:38:19 +02:00
# Get time_from
my $ time_now = strftime ( "%Y-%m-%d %H:%M:%S" , localtime ( ) ) ;
my $ time_from = get_conf_token ( $ conf , 'time_from' , $ time_now ) ;
die ( "[error] Invalid time_from: $time_from\n\n" ) unless ( $ time_from =~ /(\d+)\-(\d+)\-(\d+) +(\d+):(\d+):(\d+)/ ) ;
my $ utimestamp_from = timelocal ( $ 6 , $ 5 , $ 4 , $ 3 , $ 2 - 1 , $ 1 - 1900 ) ;
# Get time_to
my $ time_to = get_conf_token ( $ conf , 'time_to' , $ time_now ) ;
die ( "[error] Invalid time_to: $time_to\n\n" ) unless ( $ time_to =~ /(\d+)\-(\d+)\-(\d+) +(\d+):(\d+):(\d+)/ ) ;
my $ utimestamp_to = timelocal ( $ 6 , $ 5 , $ 4 , $ 3 , $ 2 - 1 , $ 1 - 1900 ) ;
# Generate data files
my $ utimestamp = $ utimestamp_from ;
2009-06-08 20:36:12 +02:00
while ( $ utimestamp <= $ utimestamp_to ) {
for ( my $ i = $ start ; $ i < $ start + $ step ; $ i + + ) {
# Get the name of the agent
last unless defined ( $ agents - > [ $ i ] ) ;
my $ agent_name = $ agents - > [ $ i ] ;
2010-01-12 18:22:14 +01:00
# Agent random position
my $ ag_latitude = $ latitude_base + ( rand ( $ position_radius ) - $ position_radius /2)/ 100 ;
my $ ag_longitude = $ longitude_base + ( rand ( $ position_radius ) - $ position_radius /2)/ 100 ;
my $ ag_altitude = $ altitude_base + ( rand ( $ position_radius ) - $ position_radius /2)/ 100 ;
2009-06-08 20:36:12 +02:00
# XML header
my $ timestamp = strftime ( "%Y-%m-%d %H:%M:%S" , localtime ( $ utimestamp ) ) ;
my $ xml_data = "<?xml version='$xml_version' encoding='$encoding'?>\n" ;
2010-02-05 16:27:25 +01:00
my $ sign = int rand ( 2 ) ;
$ ag_timezone_offset += ( $ sign * ( - 1 ) + ( 1 - $ sign ) ) * int rand ( $ ag_timezone_offset_range ) ;
2010-01-20 14:34:13 +01:00
$ xml_data . = "<agent_data os_name='$os_name' os_version='$os_version' interval='$interval' version='$os_version' timestamp='$timestamp' agent_name='$agent_name' timezone_offset='$ag_timezone_offset' longitude='$ag_longitude' latitude='$ag_latitude' altitude='$ag_altitude'>\n" ;
2009-06-08 20:36:12 +02:00
foreach my $ module ( @ { $ modules } ) {
# Skip unnamed modules
my $ module_name = get_conf_token ( $ module , 'module_name' , '' ) ;
next if ( $ module_name eq '' ) ;
# Read module configuration
my $ module_type = get_conf_token ( $ module , 'module_type' , 'generic_data' ) ;
my $ module_description = get_conf_token ( $ module , 'module_description' , '' ) ;
my $ module_min = get_conf_token ( $ module , 'module_min' , '0' ) ;
my $ module_max = get_conf_token ( $ module , 'module_max' , '255' ) ;
my $ module_variation = get_conf_token ( $ module , 'module_variation' , '100' ) ;
my $ module_data = get_conf_token ( $ module , 'module_data' , '' ) ;
# Generate module data
$ xml_data . = "\t<module>\n" ;
$ xml_data . = "\t\t<name>$module_name</name>\n" ;
$ xml_data . = "\t\t<description>$module_description</description>\n" ;
$ xml_data . = "\t\t<type>$module_type</type>\n" ;
my $ rnd_data = generate_random_data ( $ module_type , $ module_data , $ module_min , $ module_max , $ module_variation ) ;
$ module - > { 'module_data' } = $ rnd_data ;
$ xml_data . = "\t\t<data>$rnd_data</data>\n" ;
$ xml_data . = "\t</module>\n" ;
}
$ xml_data . = "</agent_data>\n" ;
# Fix the temporal path
my $ last_char = chop ( $ temporal ) ;
$ temporal . = $ last_char if ( $ last_char ne '/' ) ;
# Save the XML data file
my $ xml_file = $ temporal . '/' . $ agent_name . '_' . $ utimestamp . '.data' ;
open ( FILE , ">" , $ xml_file ) || die ( "[error] Could not write to '$xml_file': $!.\n\n" ) ;
print FILE $ xml_data ;
close ( FILE ) ;
copy_xml_file ( $ conf , $ xml_file ) ;
$ XMLFiles + + ;
}
2009-06-05 17:38:19 +02:00
# First run, let the server create the new agent before sending more data
if ( $ utimestamp == $ utimestamp_from ) {
threads - > yield ( ) ;
sleep ( $ startup_delay ) ;
}
$ utimestamp += $ interval ;
}
}
################################################################################
# Generates random data according to the module type.
################################################################################
sub generate_random_data ($$$$$) {
my ( $ module_type , $ current_data , $ min , $ max , $ variation ) = @ _ ;
my $ change_rnd = int rand ( 100 ) ;
return $ current_data unless ( $ variation > $ change_rnd ) or $ current_data eq '' ;
# String
if ( $ module_type =~ m/string/ ) {
my @ chars = ( "A" .. "Z" , "a" .. "z" , 0 .. 9 ) ;
return join ( "" , @ chars [ map { rand @ chars } ( 1 .. ( rand ( $ max - $ min + 1 ) + $ min ) ) ] ) ;
}
# Proc
if ( $ module_type =~ m/proc/ ) {
2011-01-07 01:01:13 +01:00
return int ( rand ( $ max - $ min + 1 ) + $ min ) ;
2009-06-05 17:38:19 +02:00
}
# Generic data
return int ( rand ( $ max - $ min + 1 ) + $ min ) ;
}
################################################################################
# Returns the value of a configuration token.
################################################################################
sub copy_xml_file ($$) {
my ( $ conf , $ file ) = @ _ ;
my $ server_ip = get_conf_token ( $ conf , 'server_ip' , '' ) ;
return if ( $ server_ip eq '' ) ;
my $ server_port = get_conf_token ( $ conf , 'server_port' , '41121' ) ;
my $ tentacle_opts = get_conf_token ( $ conf , 'tentacle_opts' , '' ) ;
# Send the file and delete it
2011-02-14 09:42:10 +01:00
`tentacle_client -a $server_ip -p $server_port $tentacle_opts "$file" > /dev/null 2>&1` ;
2009-06-05 17:38:19 +02:00
unlink ( $ file ) ;
}
################################################################################
# Returns the value of a configuration token.
################################################################################
sub get_conf_token ($$$) {
my ( $ hash_ref , $ token , $ def_value ) = @ _ ;
return $ def_value unless ref ( $ hash_ref ) and defined ( $ hash_ref - > { $ token } ) ;
return $ hash_ref - > { $ token } ;
}
################################################################################
# Prints a message to the logfile.
################################################################################
sub log_message ($$) {
my ( $ conf , $ message ) = @ _ ;
my $ utimestamp = time ( ) ;
my $ log_file = get_conf_token ( $ conf , 'log_file' , '' ) ;
# Log to stdout
if ( $ log_file eq '' ) {
print "[$utimestamp] $message\n" ;
return ;
}
# Log to file
{
lock $ LogLock ;
open ( LOG_FILE , '>>' , $ log_file ) || die ( "[error] Could not open log file '$log_file': $!.\n\n" ) ;
print LOG_FILE "[$utimestamp] $message\n" ;
close ( LOG_FILE ) ;
}
}
################################################################################
# Main
################################################################################
my ( % conf , @ modules ) ;
# Check command line parameters
if ( $# ARGV != 0 ) {
print "Usage:\n\t$0 <configuration file>\n\n" ;
exit 1 ;
}
# Load configuration file
load_config ( $ ARGV [ 0 ] , % conf , @ modules ) ;
die ( "[error] No agent file specified in configuration file.\n\n" ) unless defined ( $ conf { 'agent_file' } ) ;
open ( FILE , "<" , $ conf { 'agent_file' } ) || die ( "[error] Could not open agent configuration file '" . $ conf { 'agent_file' } . "': $!.\n\n" ) ;
2009-06-08 20:36:12 +02:00
# Read agent names
my @ agents ;
2009-06-05 17:38:19 +02:00
while ( my $ agent_name = <FILE> ) {
chomp ( $ agent_name ) ;
2009-06-08 20:36:12 +02:00
push ( @ agents , $ agent_name ) ;
2009-06-05 17:38:19 +02:00
$ Agents + + ;
}
close ( FILE ) ;
2009-06-08 20:36:12 +02:00
# Get the maximum number of threads and the number of agents per thread
my $ max_threads = get_conf_token ( \ % conf , 'max_threads' , '10' ) ;
$ max_threads = 10 unless ( $ max_threads > 0 ) ;
my $ step = ceil ( $ Agents / $ max_threads ) ;
my $ t0 = gettimeofday ( ) ;
for ( my $ i = 0 ; $ i < $ Agents ; $ i += $ step ) {
threads - > create ( \ & generate_xml_files , \ @ agents , $ i , $ step , \ % conf , \ @ modules ) ;
}
2009-06-05 17:38:19 +02:00
# Log some information for the user
my $ time_now = strftime ( "%Y-%m-%d %H:%M:%S" , localtime ( ) ) ;
my $ time_from = get_conf_token ( \ % conf , 'time_from' , $ time_now ) ;
my $ time_to = get_conf_token ( \ % conf , 'time_to' , $ time_now ) ;
2009-12-21 11:16:01 +01:00
my $ interval = get_conf_token ( \ % conf , 'agent_interval' , '300' ) ;
2009-06-05 17:38:19 +02:00
log_message ( \ % conf , "Generating XML data files for $Agents agents from $time_from to $time_to interval $interval." ) ;
# Wait for all threads to finish
foreach my $ thr ( threads - > list ( threads:: all ) ) {
$ thr - > join ( ) ;
}
my $ t1 = gettimeofday ( ) ;
# Log statistics
log_message ( \ % conf , "\tTotal agents:\t$Agents\n\t\tTotal modules:\t" . ( $ Agents * $ Modules ) . "\t($Modules per agent)\n\t\tTotal XML:\t$XMLFiles\t(" . int ( $ XMLFiles / ( $ t1 - $ t0 ) ) . " per second)" ) ;