#!@@PERL@@ # -*- cperl -*- # # Copyright (c) 2009 Erik Inge Bolsø, Redpill Linpro AS # # 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 dated June, # 1991. # # 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. =pod =encoding UTF-8 =head1 NAME zimbra_ - Plugin to monitor all sorts of Zimbra statistics =head1 APPLICABLE SYSTEMS Zimbra 5+ with zmstat statistics enabled. Text::CSV_XS (perl-Text-CSV_XS from EPEL, for example) =head1 CONFIGURATION Add this to the relevant munin-node config file: [zimbra*] group zimbra The following environment variables are used by this plugin: =over 4 =item csvpath Path to zmstat csv files (default /opt/zimbra/zmstat) =back =head1 BUGS/GOTCHAS Only reads latest value from the csv, which may be a "last 30 seconds" or "last minute" value, so not a representative "last five minutes" value. The original Zimbra stat charts often use a moving average of some sort. =head1 AUTHOR Erik Inge Bolsø , Redpill Linpro AS =head1 COPYRIGHT Copyright (c) 2009 Erik Inge Bolsø, Redpill Linpro AS All rights reserved. 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 dated June, 1991. =head1 MAGIC MARKERS #%# family=contrib #%# capabilities=autoconf suggest =cut use strict; use warnings; use Fcntl; use Tie::File; my $ret = ""; if (!eval "require Text::CSV_XS;") { $ret = "No Text::CSV_XS"; } my $csvpath = ($ENV{'csvpath'} || '/opt/zimbra/zmstat'); # The possible measurements my %ops = ( 'client_active_connections' => { 'vlabel' => 'connections', 'title' => "Client active connections", 'info' => "Mailboxd Active Connections by Client Protocol according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'imap', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'imap_conn', }, { 'label' => 'imap_ssl', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'imap_ssl_conn', }, { 'label' => 'pop3', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'pop_conn', }, { 'label' => 'pop3_ssl', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'pop_ssl_conn', }, ], }, 'client_request_rate' => { 'vlabel' => 'requests/min', 'title' => "Client request rate", 'info' => "Mailboxd Request Rate by Client Protocol according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'soap', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'soap_count', }, { 'label' => 'imap', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'imap_count', }, { 'label' => 'pop3', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'pop_count', }, ], }, 'client_response_time' => { 'vlabel' => 'ms', 'title' => "Client response time", 'info' => "Mailboxd Response Time by Client Protocol according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'soap', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'soap_ms_avg', }, { 'label' => 'imap', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'imap_ms_avg', }, { 'label' => 'pop3', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'pop_ms_avg', }, ], }, 'connpool_latency' => { 'vlabel' => 'latency (ms)', 'title' => "Connection pool latency", 'info' => "Mailboxd connection pool get latencies according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'database', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'db_conn_ms_avg', }, { 'label' => 'ldap', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'ldap_dc_ms_avg', }, ], }, 'jvm_heap_free' => { 'vlabel' => 'bytes', 'title' => "JVM heap free", 'info' => "Mailboxd JVM Heap Free according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'total', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'heap_free', }, { 'label' => 'ps_old_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_old_gen_free', }, { 'label' => 'cms_old_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_cms_old_gen_free', }, { 'label' => 'tenured_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_tenured_gen_free', }, { 'label' => 'train_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_train_gen_free', }, { 'label' => 'ps_eden', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_eden_space_free', }, { 'label' => 'ps_survivor', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_survivor_space_free', }, { 'label' => 'par_eden', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_par_eden_space_free', }, { 'label' => 'par_survivor', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_par_survivor_space_free', }, { 'label' => 'eden', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_eden_space_free', }, { 'label' => 'survivor', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_survivor_space_free', }, ], }, 'jvm_heap_used' => { 'vlabel' => 'bytes', 'title' => "JVM heap used", 'info' => "Mailboxd JVM Heap Used according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'total', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'heap_used', }, { 'label' => 'ps_old_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_old_gen_used', }, { 'label' => 'cms_old_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_cms_old_gen_used', }, { 'label' => 'tenured_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_tenured_gen_used', }, { 'label' => 'train_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_train_gen_used', }, { 'label' => 'ps_eden', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_eden_space_used', }, { 'label' => 'ps_survivor', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_survivor_space_used', }, { 'label' => 'par_eden', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_par_eden_space_used', }, { 'label' => 'par_survivor', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_par_survivor_space_used', }, { 'label' => 'eden', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_eden_space_used', }, { 'label' => 'survivor', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_survivor_space_used', }, ], }, 'jvm_major_gc_count' => { 'vlabel' => 'Count pr ${graph_period}', 'title' => "JVM major GC count", 'info' => "Mailboxd JVM Major GC Count according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'major', 'type' => 'DERIVE', 'csv' => 'mailboxd.csv', 'csvfield' => 'gc_major_count', }, ], }, 'jvm_major_gc_time' => { 'vlabel' => 'Time (ms) pr ${graph_period}', 'title' => "JVM major GC time", 'info' => "Mailboxd JVM Major GC Time according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'major', 'type' => 'DERIVE', 'csv' => 'mailboxd.csv', 'csvfield' => 'gc_major_ms', }, ], }, 'jvm_minor_gc_count' => { 'vlabel' => 'Count pr ${graph_period}', 'title' => "JVM minor GC count", 'info' => "Mailboxd JVM Minor GC Count according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'minor', 'type' => 'DERIVE', 'csv' => 'mailboxd.csv', 'csvfield' => 'gc_minor_count', }, ], }, 'jvm_minor_gc_time' => { 'vlabel' => 'Time (ms) pr ${graph_period}', 'title' => "JVM minor GC time", 'info' => "Mailboxd JVM Minor GC Time according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'minor', 'type' => 'DERIVE', 'csv' => 'mailboxd.csv', 'csvfield' => 'gc_minor_ms', }, ], }, 'jvm_permgen' => { 'vlabel' => 'bytes', 'title' => "JVM Perm Gen", 'info' => "Mailboxd JVM Permanent Generation and Code Cache according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'ps_perm_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_perm_gen_used', }, { 'label' => 'perm_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_perm_gen_used', }, { 'label' => 'cms_perm_gen', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_cms_perm_gen_used', }, { 'label' => 'code_cache', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_code_cache_used', }, { 'label' => 'perm_gen_shared_ro', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_perm_gen_[shared-ro]_used', }, { 'label' => 'perm_gen_shared_rw', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_perm_gen_[shared-rw]_used', }, { 'label' => 'ps_perm_gen_free', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_ps_perm_gen_free', }, { 'label' => 'perm_gen_free', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_perm_gen_free', }, { 'label' => 'cms_perm_gen_free', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_cms_perm_gen_free', }, { 'label' => 'code_cache_free', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_code_cache_free', }, { 'label' => 'perm_gen_shared_ro_free', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_perm_gen_[shared-ro]_free', }, { 'label' => 'perm_gen_shared_rw_free', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mpool_perm_gen_[shared-rw]_free', }, ], }, 'lmtp_throughput' => { 'vlabel' => 'bytes/min', 'title' => "LMTP throughput", 'info' => "Mailboxd LMTP Throughput according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'received', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'lmtp_rcvd_bytes', }, { 'label' => 'delivered', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'lmtp_dlvd_bytes', }, ], }, 'lmtp_delivery_rate' => { 'vlabel' => 'msgs/min', 'title' => "LMTP delivery rate", 'info' => "Mailboxd LMTP Delivery Rate according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'received', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'lmtp_rcvd_msgs', }, { 'label' => 'delivered', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'lmtp_dlvd_msgs', }, { 'label' => 'rcvd_rcpt', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'lmtp_rcvd_rcpt', 'info' => 'received messages * recipients', }, ], }, 'lucene_cachehit' => { 'vlabel' => 'hitrate %', 'title' => "Lucene cache hitrate", 'info' => "Mailboxd Lucene IndexWriterCache hitrate according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'total', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'idx_wrt_opened', 'csvfield2'=> 'idx_wrt_opened_cache_hit', 'graph' => 'off' }, { 'label' => 'hit', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'idx_wrt_opened_cache_hit', 'draw' => 'AREA', 'cdef' => 'hit,total,/,100,*' }, ], }, 'lucene_io' => { 'vlabel' => 'bytes', 'title' => "Lucene I/O", 'info' => "Mailboxd lucene I/O according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'write', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'idx_bytes_written', }, { 'label' => 'read', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'idx_bytes_read', }, ], }, 'lucene_writers' => { 'vlabel' => 'writers', 'title' => "Lucene index writers", 'info' => "Mailboxd dirty lucene index writers according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'writers', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'idx_wrt_avg', }, ], }, 'mailbox_add_latency' => { 'vlabel' => 'latency (ms)', 'title' => "Mailbox add latency", 'info' => "Mailboxd Mailbox Add Latency (Delivery Speed) according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'latency', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_add_msg_ms_avg', }, ], }, 'mailbox_add_rate' => { 'vlabel' => 'msgs/min', 'title' => "Mailbox add rate", 'info' => "Mailboxd Mailbox Add Rate (Delivery Rate) according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'adds', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_add_msg_count', }, ], }, 'mailbox_cachehit' => { 'vlabel' => 'hitrate %', 'title' => "Mailbox cache hitrate", 'info' => "Mailboxd Mailbox Cache hitrate according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'hit', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_cache', }, ], }, 'mailbox_itemblob_cachehit' => { 'vlabel' => 'hitrate %', 'title' => "Mailbox item/blob cache", 'info' => "Mailboxd Mailbox Item/Blob Cache hitrate according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'blob', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_msg_cache', }, { 'label' => 'metadata', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_item_cache', }, ], }, # what does this actually measure? Hovers around 1 on an idle test server. 'mailbox_get_count' => { 'vlabel' => 'count', 'title' => "Mailbox get count", 'info' => "Mailboxd Mailbox Get Count according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'get', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_get_count', }, ], }, 'mailbox_get_latency' => { 'vlabel' => 'latency (ms)', 'title' => "Mailbox get latency", 'info' => "Mailboxd Mailbox Get Latency according to zmstat/mailboxd.csv", 'fields' => [ { 'label' => 'latency', 'type' => 'GAUGE', 'csv' => 'mailboxd.csv', 'csvfield' => 'mbox_get_ms_avg', }, ], }, 'postfix_queue' => { 'vlabel' => 'Messages queued', 'title' => "Postfix messages queued", 'info' => "Postfix messages queued according to zmstat/mtaqueue.csv", 'fields' => [ { 'label' => 'messages', 'type' => 'GAUGE', 'csv' => 'mtaqueue.csv', 'csvfield' => 'requests', }, ], }, 'process_cpu' => { 'vlabel' => 'percent cpu', 'title' => "Process CPU usage", 'info' => "Process CPU usage according to zmstat/proc.csv", 'fields' => [ { 'label' => 'mailbox', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'mailbox-total-cpu', }, { 'label' => 'mysql', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'mysql-total-cpu', }, { 'label' => 'convertd', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'convertd-total-cpu', }, { 'label' => 'ldap', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'ldap-total-cpu', }, { 'label' => 'postfix', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'postfix-total-cpu', }, { 'label' => 'amavis', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'amavis-total-cpu', }, { 'label' => 'clam', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'clam-total-cpu', }, { 'label' => 'stats', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'stats-total-cpu', }, ], }, 'process_mem_total' => { 'vlabel' => 'MB', 'title' => "Process Total Memory", 'info' => "Process Total memory usage according to zmstat/proc.csv", 'fields' => [ { 'label' => 'mailbox', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'mailbox-totalMB', }, { 'label' => 'mysql', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'mysql-totalMB', }, { 'label' => 'convertd', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'convertd-totalMB', }, { 'label' => 'ldap', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'ldap-totalMB', }, { 'label' => 'postfix', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'postfix-totalMB', }, { 'label' => 'amavis', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'amavis-totalMB', }, { 'label' => 'clam', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'clam-totalMB', }, { 'label' => 'stats', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'stats-totalMB', }, ], }, 'process_mem_rss' => { 'vlabel' => 'MB', 'title' => "Process Resident Memory", 'info' => "Process resident memory (RSS) according to zmstat/proc.csv", 'fields' => [ { 'label' => 'mailbox', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'mailbox-rssMB', }, { 'label' => 'mysql', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'mysql-rssMB', }, { 'label' => 'convertd', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'convertd-rssMB', }, { 'label' => 'ldap', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'ldap-rssMB', }, { 'label' => 'postfix', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'postfix-rssMB', }, { 'label' => 'amavis', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'amavis-rssMB', }, { 'label' => 'clam', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'clam-rssMB', }, { 'label' => 'stats', 'type' => 'GAUGE', 'csv' => 'proc.csv', 'csvfield' => 'stats-rssMB', }, ], }, ); # Config subroutine sub config { my $action = shift; if (!(exists $ops{$action})) { die "wrong symlinked name, $action is not implemented\n"; } print <{'vlabel'} graph_title $ops{$action}->{'title'} graph_category Zimbra graph_info $ops{$action}->{'info'} EOF my $tmpfield = $ops{$action}->{'fields'}; foreach my $field ( @$tmpfield ) { my $label = $field->{'label'}; print "$label.label $label\n"; print "$label.type $field->{'type'}\n"; print "$label.min 0\n"; if (exists $field->{'graph'}) { print "$label.graph $field->{'graph'}\n"; } if (exists $field->{'info'}) { print "$label.info $field->{'info'}\n"; } if (exists $field->{'draw'}) { print "$label.draw $field->{'draw'}\n"; } if (exists $field->{'cdef'}) { print "$label.cdef $field->{'cdef'}\n"; } } } my $op; if ($0 =~ /zimbra_([\w\d_]+)$/) { $op = $1; } if ($ARGV[0]) { if ($ARGV[0] eq "config") { if ($op) { &config ($op); } else { die ("Can't run config without a symlinked name\n"); } } elsif ($ARGV[0] eq "autoconf") { if ($ret ne "") { print "no ($ret)\n"; } elsif (chdir $csvpath) { print "yes\n"; } else { print "no (chdir to $csvpath failed)\n"; } } elsif ($ARGV[0] eq "suggest") { foreach my $key ( keys %ops ) { print "$key\n"; } } exit 0; } # We won't run this without parameters. die ("Can't run without a symlinked name\n") if $0 =~ /zimbra_$/; my @cached_csvfields = (); my $tmpfield = $ops{$op}->{'fields'}; foreach my $field ( @$tmpfield ) { my $label = $field->{'label'}; my $csv = Text::CSV_XS->new ( { 'allow_whitespace' => 1 } ); my $error; if (tie my @file, 'Tie::File', $csvpath . "/" . $field->{'csv'}, mode => O_RDONLY) { # parse the csv header line for the interesting field my $status = $csv->parse($file[0]); my @csvfields = $csv->fields(); my $csvindex = 0; $csvindex++ until $csvindex > $#csvfields or $csvfields[$csvindex] eq $field->{'csvfield'}; if ($csvindex > $#csvfields) { $error = 1; goto FIELDERR; } my $csvindex2; if (exists $field->{'csvfield2'}) { $csvindex2 = 0; $csvindex2++ until $csvindex2 > $#csvfields or $csvfields[$csvindex2] eq $field->{'csvfield2'}; if ($csvindex2 > $#csvfields) { $error = 1; goto FIELDERR; } } unless ($#cached_csvfields > -1) { # blindly use last line of the csv, for now $status = $csv->parse($file[-1]); @cached_csvfields = $csv->fields(); } # empty csv field => undef if ($cached_csvfields[$csvindex] eq "") { $error = 1; goto FIELDERR; } if (defined $csvindex2) { print "$label.value " . ( $cached_csvfields[$csvindex] + $cached_csvfields[$csvindex2] ). "\n"; } else { print "$label.value " . $cached_csvfields[$csvindex] . "\n"; } } else { $error = 1; } FIELDERR: if ($error) { print "$label.value UNDEF\n" ; } }