#!/usr/bin/perl use strict; #################### main pod documentation begin ################### =head1 NAME Zymonic - Test script to save (presave add then update) records to a table, for performance testing =head1 SYNOPSIS =head1 DESCRIPTION This script will take in a table name then create a record in that table with random data. =head1 SUPPORT As in the license, Zymonic is provided without warranty or support unless purchased separately, however... If you email zymonic-support@zednax.com your issue will be noted and may receive a response. For security issues, please contact zymonic-security@zednax.com and someone will respond within 8 working hours. =head1 AUTHOR Alex Masidlover et al. CPAN ID: MODAUTHOR Zednax Limited alex.masidlover@zednax.com http://www.zednax.com =head1 COPYRIGHT This program is free software licensed under the... Zymonic Public License 1.0 The full text of the license can be found in the LICENSE file included with this module. Other licenses may be acceptable if including parts of Zymonic in larger projects, please contact Zednax for details. =head1 SEE ALSO perl(1). =cut #################### main pod documentation end ################### # modules use Data::Dumper; use Time::HiRes qw(time); use Zymonic; use Zymonic::Auth; use Zymonic::Config; use Zymonic::Session; use Zymonic::Utils qw(death_handler random_string); # use all field subclasses to avoid module loading times use Zymonic::Field::Choice; use Zymonic::Field::DateTime; use Zymonic::Field::DecryptorCrypt; use Zymonic::Field::DecryptorEncrypted; use Zymonic::Field::Email; use Zymonic::Field::File; use Zymonic::Field::FileChooser; use Zymonic::Field::Glob; use Zymonic::Field::LinkedField; use Zymonic::Field::MultipleChoiceLinkedField; use Zymonic::Field::PAN; use Zymonic::Field::PasswordCapture; use Zymonic::Field::Stylesheet; use Zymonic::Field::SubForm; use Zymonic::Field::SubFilter; # death handler $main::SIG{__DIE__} = \&death_handler; if ( $ENV{ZZDEBUG} ) { open( my $fh, ">", $ENV{ZZDEBUG} ) || die "Couldn't open debug file: $ENV{ZZDEBUG}"; $Zymonic::Utils::debugfile = $fh; } # setup the field factory $Zymonic::field_factory = Zymonic::FieldFactory->new(); # enable cache for table includes and relationship permissions $Zymonic::object_cache = { 'Zymonic::TableInclude' => {}, 'Zymonic::RelationshipPermissions' => {}, }; # Create a session (CGI only) $Zymonic::session = Zymonic::Session->new(); print "Loaded Session\n"; unless ( $ENV{ZZDEBUG} ) { # force no debugs $Zymonic::Utils::debugs_written = 1; } # set system $Zymonic::system = $ARGV[0]; usage() unless $Zymonic::system; # create config $Zymonic::ZCONFIG{$Zymonic::system} = Zymonic::Config->new( system_name => $Zymonic::system, config_dir => "/etc/zymonic", ); print "Loaded Config\n"; # set session db $Zymonic::session->{DB} = $Zymonic::ZCONFIG{$Zymonic::system}->{DB}; # create auth my $auth = Zymonic::Auth->new( config => $Zymonic::ZCONFIG{$Zymonic::system}, session => $Zymonic::session, DB => $Zymonic::ZCONFIG{$Zymonic::system}->{DB}, user => '', credentials => '', logged_in => '', ); main(); exit(0); #################### subroutine header begin #################### =head2 main Usage : main(); Purpose : main script functionality Returns : nothing Argument : nothing Throws : nothing Comment : See Also : =cut #################### subroutine header end ################### sub main { my $table_zname = $ARGV[1]; usage() unless $table_zname; my $number_of_records = $ARGV[2] || 1; my %field_values = map { my ( $key, $val ) = split( /=/, $_ ); ( $key => $val ); } @ARGV[ 3 .. $#ARGV ]; # load the table my $start = time() * 1000; my $table = Zymonic::Table->new( parent => $Zymonic::session, zname => $table_zname, ident => 'add_random_record_script_' . $table_zname . '_', config => $Zymonic::ZCONFIG{$Zymonic::system}, auth => $auth, DB => $Zymonic::ZCONFIG{$Zymonic::system}->{DB}, get_user_values => 'true', ); my $end = time() * 1000; my $time_taken = ( $end - $start ); print "Loaded Table $table->{zname}: " . sprintf( '%5.6f', $time_taken ) . " ms\n"; # keep track of what was added my @ids = (); # get fields to add data to my $table_fields = table_fields( $table, \%field_values ); # create the random records my $number_of_fields = keys %{ $table->get_fields() }; my $load_total = 0; my $user_value_total = 0; my $add_total = 0; my $update_total = 0; my $validate_total = 0; print "Saving $number_of_records random records with $number_of_fields table fields, of which " . ( scalar keys %{$table_fields} ) . " have user values\nValues below are:\tloading \tuser values \tpresave add \tvalidation \tupdating \ttotal\n"; foreach my $i ( 1 .. $number_of_records ) { print "Saving record $i:\t"; my $record = $table->setup_placeholder(); random_record_data( $table, $record, $table_fields, \%field_values ); # load the fields and get user values to time that my $total = 0; my $start = time() * 1000; $table->get_fields( $record, 'objects' ); my $end = time() * 1000; my $time_taken = ( $end - $start ); print '' . sprintf( '%5.6f', $time_taken ) . " ms \t"; $load_total += $time_taken; $total += $time_taken; # get user values to time that $start = time() * 1000; map { $table->get_field_ref_value($_); } values %{ $table->get_fields($record) }; $end = time() * 1000; $time_taken = ( $end - $start ); print '' . sprintf( '%5.6f', $time_taken ) . " ms \t"; $user_value_total += $time_taken; $total += $time_taken; # presave add the record $start = time() * 1000; $table->before_add('no_perms'); my $id = $table->add_record( 'no_perms', 'no_new_placeholder', 'presave' ); $table->after_add($id); $end = time() * 1000; $time_taken = ( $end - $start ); print '' . sprintf( '%5.6f', $time_taken ) . " ms \t"; $add_total += $time_taken; $total += $time_taken; # validate each field, time the field loading $start = time() * 1000; foreach my $field ( values %{ $table->get_fields( $record, 'objects' ) } ) { $field->validate(); } $end = time() * 1000; $time_taken = ( $end - $start ); $validate_total += $time_taken; $total += $time_taken; print '' . sprintf( '%5.6f', $time_taken ) . " ms \t"; # update the record to save all values $start = time() * 1000; $table->before_update( $id, 'no_perms' ); $id = $table->update_record( $id, 'no_perms', '', $record ); $table->after_update($id); $end = time() * 1000; $time_taken = ( $end - $start ); $update_total += $time_taken; $total += $time_taken; print '' . sprintf( '%5.6f', $time_taken ) . " ms \t" . sprintf( '%5.6f', $total ) . " ms\n"; push( @ids, $id ); } print "Total Times: \t" . sprintf( '%5.6f', $load_total ) . " ms \t" . sprintf( '%5.6f', $user_value_total ) . " ms \t" . sprintf( '%5.6f', $add_total ) . " ms \t" . sprintf( '%5.6f', $validate_total ) . " ms \t" . sprintf( '%5.6f', $update_total ) . " ms \t" . sprintf( '%5.6f', $load_total + $user_value_total + $add_total + $validate_total + $update_total ) . " ms\n"; print "Avg. Times (per rec):\t" . sprintf( '%5.6f', $load_total / $number_of_records ) . " ms \t" . sprintf( '%5.6f', $user_value_total / $number_of_records ) . " ms \t" . sprintf( '%5.6f', $add_total / $number_of_records ) . " ms \t" . sprintf( '%5.6f', $validate_total / $number_of_records ) . " ms \t" . sprintf( '%5.6f', $update_total / $number_of_records ) . " ms \t" . sprintf( '%5.6f', ( $load_total + $user_value_total + $add_total + $validate_total + $update_total ) / $number_of_records ) . " ms\n"; print "Avg. Times (per field):\t" . sprintf( '%5.6f', $load_total / ( $number_of_records * $number_of_fields ) ) . " ms \t" . sprintf( '%5.6f', $user_value_total / ( $number_of_records * $number_of_fields ) ) . " ms \t" . sprintf( '%5.6f', $add_total / ( $number_of_records * $number_of_fields ) ) . " ms \t" . sprintf( '%5.6f', $validate_total / ( $number_of_records * $number_of_fields ) ) . " ms \t" . sprintf( '%5.6f', $update_total / ( $number_of_records * $number_of_fields ) ) . " ms \t" . sprintf( '%5.6f', ( $load_total + $user_value_total + $add_total + $validate_total + $update_total ) / ( $number_of_records * $number_of_fields ) ) . " ms\n"; print "Deleting random records.\n"; clear_random_records( $table, \@ids ); print "Done.\n"; } #################### subroutine header begin #################### =head2 table_fields Usage : table_fields($table); Purpose : Returns name of fields we can set random data on on in this table Returns : hashref of field zname => length of random data Argument : table and hashrefof field values Throws : nothing Comment : See Also : =cut #################### subroutine header end ################### sub table_fields { my $table = shift; my $field_values = shift || {}; my $auto_inc_key; eval { $auto_inc_key = $table->keyfields('auto_inc'); 1; } or do { my $exception = $@; if ( ref($exception) && $exception->isa('Zymonic::Exception::Db::No_Auto_Inc_Primary_Key') ) { $auto_inc_key = undef; } else { rethrow_exception($exception); } }; # get table fields my @table_fields = values %{ $table->get_fields() }; my $count = 0; my %fields = (); foreach my $field_ref (@table_fields) { # if setting fields, then always use those fields and ignore others, otherwise # ignore auto fields, auto id, any special fields, anything other than number/text if ( keys %{$field_values} ) { next unless defined $field_values->{ $field_ref->{zname} }; } else { next if $field_ref->{zname} =~ /sec_id|parent_process_id|deleted|autocreated|posix_process_id|zz_updated|zz_updated_timestamp/ || $auto_inc_key && $field_ref->{field} eq $auto_inc_key || $field_ref->{class} || $field_ref->{field_type} !~ /int|float|double|varchar|char|text/; } # get field length my $len; if ( $field_ref->{field_type} =~ /int|float|double/ ) { # max int is 2147483647, so don't generate num bigger $len = ( ( $field_ref->{max_length} > 9 && $field_ref->{field_type} eq 'int' ) ? 9 : $field_ref->{max_length} ); } else { # limit strings to length 10 $len = ( $field_ref->{max_length} > 10 ? 10 : $field_ref->{max_length} ); } $fields{ $field_ref->{zname} } = $len; ++$count; } return \%fields; } #################### subroutine header begin #################### =head2 clear_random_records Usage : clear_random_records($table, \@ids); Purpose : deletes the random records from the db Returns : nothing Argument : table and ids Throws : nothing Comment : See Also : =cut #################### subroutine header end ################### sub clear_random_records { my $table = shift; my $ids = shift; foreach my $id ( @{$ids} ) { $table->delete_record( $id, 'no_perms', 'actual_delete' ); } } #################### subroutine header begin #################### =head2 random_record_data Usage : random_record_data($table, $record); Purpose : Adds random data to the record Returns : nothing Argument : Table and record Throws : nothing Comment : See Also : =cut #################### subroutine header end ################### sub random_record_data { my $table = shift; my $record = shift; my $table_fields = shift; my $field_values = shift || {}; foreach my $field_zname ( keys %{$table_fields} ) { # get data to set my $data; if ( defined $field_values->{$field_zname} && $field_values->{$field_zname} ne 'zz_random' ) { if ( $field_values->{$field_zname} =~ /,/ ) { my @values = split( /,/, $field_values->{$field_zname} ); $data = $values[ rand(@values) ]; } else { $data = $field_values->{$field_zname}; } } else { # generate random data $data = random_string( $table_fields->{$field_zname}, ( 0 .. 9 ) ); } # set random data as cgi param $Zymonic::session->set_cgi_param( $record->{ident} . $field_zname, $data ); } } #################### subroutine header begin #################### =head2 usage Usage : usage(); Purpose : Prints script usage. Returns : nothing Argument : nothing Throws : nothing Comment : See Also : =cut #################### subroutine header end ################### sub usage { print "Usage: save_random_record.pl [system] [table] [number of records (defaults to 1)] " . "[values to use, multiple entries allowed with form field_zname=value or field_zname=value1,value2 or field_zname=zz_random]\n"; exit(1); }