<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Perl &#8211; Johnny Morano&#039;s Tech Articles</title>
	<atom:link href="https://jmorano.moretrix.com/category/development/perl/feed/" rel="self" type="application/rss+xml" />
	<link>https://jmorano.moretrix.com</link>
	<description>Ramblings of an old-fashioned space cowboy</description>
	<lastBuildDate>Mon, 25 Apr 2022 10:52:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>

<image>
	<url>https://jmorano.moretrix.com/wp-content/uploads/2022/04/cropped-jmorano_emblem-32x32.png</url>
	<title>Perl &#8211; Johnny Morano&#039;s Tech Articles</title>
	<link>https://jmorano.moretrix.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Read the HAProxy UNIX socket file using Perl</title>
		<link>https://jmorano.moretrix.com/2022/04/read-the-haproxy-unix-socket-file-using-perl/</link>
					<comments>https://jmorano.moretrix.com/2022/04/read-the-haproxy-unix-socket-file-using-perl/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Mon, 25 Apr 2022 10:52:45 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[HAProxy]]></category>
		<category><![CDATA[Monitoring]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1515</guid>

					<description><![CDATA[HAProxy provides a socket file which can be used to do maintenance (enable/ disable backends, retrieve information and&#8230;]]></description>
										<content:encoded><![CDATA[
<p><a rel="noreferrer noopener" href="http://www.haproxy.org/" data-type="URL" data-id="http://www.haproxy.org/" target="_blank">HAProxy</a> provides a <a href="http://docs.haproxy.org/2.5/management.html#9.3" data-type="URL" data-id="http://docs.haproxy.org/2.5/management.html#9.3" target="_blank" rel="noreferrer noopener">socket file</a> which can be used to do maintenance (enable/ disable backends, retrieve information and statistics, &#8230;).</p>



<p>The statistics part contains quite some interesting information for monitoring and alerting.</p>



<p>The below Perl code snippit will loop over a <code>glob</code> of socket files (for instance when you have multiple HAProxy configurations running as separate processes) and print out the values returned by the &#8220;<code>show info</code>&#8221; command.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use IO::Socket::UNIX;

foreach my $socket_file (glob("/run/haproxy/*.sock")){
    print "- Reading socket: $socket_file\n";
    my $client = IO::Socket::UNIX->new(
        Type => SOCK_STREAM(),
        Peer => $socket_file,
    );

    print "- show info\n";
    print $client "show info\n";
    my $header = &lt;$client>;
    chomp($header);

    $header =~ s/^#\s+//;
    my @keys = split ',', $header;
    print "- header:$header\n";

    while (my $line = &lt;$client>){
        next unless $line =~ /^.+/;

        chomp($line);
        my @values = split ',', $line;
        print " - Got $line\n";
        print "   $keys[$_]: ".($values[$_]//'')."\n" foreach 0..$#keys;
    }

    close $client;
}</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/read-the-haproxy-unix-socket-file-using-perl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Managing LDAP passwords with Perl</title>
		<link>https://jmorano.moretrix.com/2022/04/managing-ldap-passwords-with-perl/</link>
					<comments>https://jmorano.moretrix.com/2022/04/managing-ldap-passwords-with-perl/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Mon, 25 Apr 2022 09:30:40 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[OpenmLDAP]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1511</guid>

					<description><![CDATA[OpenLDAP Software is an open source implementation of the Lightweight Directory Access Protocol. Many graphical interfaces are available&#8230;]]></description>
										<content:encoded><![CDATA[
<p><a href="https://openldap.org/" data-type="URL" data-id="https://openldap.org/" target="_blank" rel="noreferrer noopener">OpenLDAP</a> Software is an open source implementation of the Lightweight Directory Access Protocol.</p>



<p>Many graphical interfaces are available for managing user accounts in OpenLDAP like PHPLDAPAdmin (<a rel="noreferrer noopener" href="http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page" target="_blank">http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page</a>) or LAM (<a rel="noreferrer noopener" href="https://www.ldap-account-manager.org/lamcms/" target="_blank">https://www.ldap-account-manager.org/lamcms/</a>).</p>



<p>When generating a bulk amount of accounts with automation or just managing user details with a simple script, allows much more flexibility and can be even quicker.</p>



<p>LDAP passwords can be stored or changed by using an LDIF file. This LDIF file needs 3 required lines:</p>



<ol class="wp-block-list"><li>The &#8220;<code>dn</code>&#8221; you are about to change</li><li>the &#8220;<code>changetype</code>&#8221; set to &#8220;<code>modify</code>&#8220;</li><li>A &#8220;<code>replace</code>&#8221; line containing the field you want to change (in our case, since we are changing the password, this will be &#8220;<code>userPassword</code>&#8220;)</li></ol>



<p>Your LDAP password can be stored either in clear-text (which is not advisable) or by sending a <code>SHA-hash</code>. The <code>SHA-hash</code> must include the salt at the end and must be <code>base64</code> encoded.</p>



<p>The code snippit below will call a subroutine called <code>generate_password()</code> which comes from a previous article (<a href="https://jmorano.moretrix.com/2013/08/secure-password-generator-perl/" data-type="post" data-id="953">Secure Password Generator in Perl</a>).</p>



<p>At the end of the script, it will print out the LDIF file content, which needs to be saved to <code>change.ldif</code>. As last, it will print the <code>ldapmodify</code> command to make the actual change. You will need to know the <code>admin</code> password for this. Alternatively, you could also make this change using your own <code>dn</code> for authentication.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use Digest::SHA;
use MIME::Base64;

my $random_password = generate_password(24);
my $random_salt     = generate_password(3);

my $ctx = Digest::SHA->new;
$ctx->add($random_password);
$ctx->add($random_salt);
my $hashedPasswd = encode_base64($ctx->digest . $random_salt, '');

print "password: $random_password\n";
print "salt: $random_salt\n";
print &lt;&lt;EOF;
# LDIF
dn: uid=user1,ou=users,dc=shihai-corp,dc=at
changetype: modify
replace: userPassword
userPassword: {SSHA}$hashedPasswd
EOF

print "\n";
print q{LDAP cmd: ldapmodify -H "ldap://ldap_server01" -Z -x -W -D "cn=ldapadmin,ou=admins,dc=shihai-corp,dc=at" -f change.ldif} . "\n\n"</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/managing-ldap-passwords-with-perl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Perl script to monitor the rate of logs</title>
		<link>https://jmorano.moretrix.com/2022/04/perl-script-to-monitor-the-rate-of-logs/</link>
					<comments>https://jmorano.moretrix.com/2022/04/perl-script-to-monitor-the-rate-of-logs/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 07 Apr 2022 12:39:50 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[IPTables]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Logging]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1399</guid>

					<description><![CDATA[In a previous article (IPTables Logging in JSON with NFLOG and ulogd2) we learned how to log certain&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In a previous article (<a href="https://jmorano.moretrix.com/2022/03/logging-in-iptables-with-nflog-and-ulogd2/" data-type="post" data-id="1308">IPTables Logging in JSON with NFLOG and ulogd2</a>) we learned how to log certain IPTables rules to JSON log files.</p>



<p>Monitoring the logs in real-time on the command line, can also be very useful when debugging either the rules themselves or when analyzing certain issues. Rather than just looking at the logs, in some situations it might be useful to track the rate of the log messages. A self-written Perl script can be useful as it allows to be flexible when it comes to:</p>



<ul class="wp-block-list"><li>parsing logs</li><li>formatting the output (with colors or tables or &#8230;)</li><li>calculating statistics</li><li>&#8230;</li></ul>



<p>The following Perl script uses a few modules which need to be present:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use IO::Async::Timer::Periodic;
use IO::Async::Loop;
use Time::HiRes qw/time/;
use Term::ANSIColor qw(:constants);
use Getopt::Long;</pre>



<p>The first two modules can be installed on Debian systems with:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">apt install libio-async-perl</pre>



<p>The others are part of the normal Perl packages and do not require any extra installation.</p>



<p>Next the script will use a polling mechanism to read from standard output at fixed intervals, to calculate the rate of the unique log lines. The default polling rate is set to 2 seconds but it can be managed through command line parameters:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">my $last_poll_time = time;

my $poll_rate = 2;
GetOptions (
    'p|pollrate=i' => \$poll_rate,
);

my $loop = IO::Async::Loop->new;
my $timer = IO::Async::Timer::Periodic->new(
   interval => $poll_rate,
   on_tick  => \&amp;log_rate
);

$timer->start;
$loop->add( $timer );
$loop->run;</pre>



<p>Finally, the script will define a subroutine called <code>log_rate</code>, which will read from standard output (or even a file) at each poll interval. Important is of course that the log lines from standard output do not contain unique data such as timestamps. The output must be as generic as possible.</p>



<p>Example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">tail -qf /var/log/ulog/blocked_detailed.json /var/log/ulog/blocked.json /var/log/ulog/passed.json  | jq -r --unbuffered '."oob.prefix"' 
blocked: invalid state
blocked: invalid state
blocked: invalid state
blocked: invalid state
blocked: invalid state
action=blocked
action=blocked
action=blocked
action=blocked
action=blocked
action=passed
action=passed
action=passed
action=passed</pre>



<p>The code snippit for <code>log_rate</code> could contain:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub log_rate {
    local $SIG{ALRM} = sub { die time, " time exceeded to read STDIN\n" };

    alarm($poll_rate);
    my $h;
    eval {
        local $| = 1;
        while (my $line = &lt;>) {
            chomp($line);
            $h->{$line}++;
        }
    };
    alarm(0);

    return unless keys %$h;

    my $delta_time = time - $last_poll_time;
    print DARK WHITE . sprintf("%d: ", time) . RESET;
    print( BOLD WHITE . $_ ." [" . GREEN . sprintf("%.2f/s", $h->{$_}/$delta_time) . BOLD WHITE "] | " . RESET) foreach keys %$h; 
    print "\n";

    $last_poll_time = time;
}</pre>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 2</mark> will start with declaring the &#8220;<code>ALARM</code>&#8221; signal. This signal is called when the <code>alarm</code> timeout has been reached (see further below).</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 4</mark> defines the <code>alarm</code> timeout in seconds: meaning: if everything below<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color"> line 4</mark> (until the next <code>alarm</code> line) takes longer than the defined timeout in seconds, the &#8220;ALRM&#8221; signal handler defined at <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">line 2</mark> will be called, which basically stops the code execution with a <code>die</code> (which in theory should stop the script with an <code>exit 1</code>).</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 5</mark> defines a hash reference which is required down below, to temporarily store unique log lines.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 6</mark> until <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">12</mark> define an <code>eval</code> block. The <code>eval</code> block will catch the ALRM signal <code>die</code> (once reached) without stopping the script with an <code>exit 1</code>. Inside the <code>eval</code> block, the standard output will be read with the diamond operator (<code>&lt;></code>) and unique lines will be counted and stored in the <code>$h</code> hash reference.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 13</mark>, right after the <code>eval</code> block, sets to <code>alarm</code> timeout to 0 again, which means it is disabled. This allows that only execution of the <code>eval</code> block will be evaluated for timeout. </p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 15</mark> ensures that only when log lines were discovered and stored in the temporary hash-ref<code> $h</code>, that rates will be printed to the screen.</p>



<p>The rest of the code will take care of printing the discovered log lines with their rates to the screen. Colors from <code>Term::ANSIColor</code> are used to make the output more vivid.</p>



<p>Example output:</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="911" height="285" src="https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00.png" alt="" class="wp-image-1405" srcset="https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00.png 911w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00-300x94.png 300w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00-768x240.png 768w" sizes="(max-width: 911px) 100vw, 911px" /></figure>



<p>The full version of the script can be found at: <a href="https://github.com/insani4c/perl_tools/tree/master/log_rate" target="_blank" rel="noreferrer noopener">https://github.com/insani4c/perl_tools/tree/master/log_rate</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/perl-script-to-monitor-the-rate-of-logs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Perl: Archive E-Mails in an IMAP Folder</title>
		<link>https://jmorano.moretrix.com/2015/11/perl-archive-e-mails-in-an-imap-folder/</link>
					<comments>https://jmorano.moretrix.com/2015/11/perl-archive-e-mails-in-an-imap-folder/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 05 Nov 2015 09:33:47 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[IMAP]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1199</guid>

					<description><![CDATA[IMAP folders are really because you can have your e-mails synchronized on multiple devices, without losing e-mails across&#8230;]]></description>
										<content:encoded><![CDATA[
<p>IMAP folders are really because you can have your e-mails synchronized on multiple devices, without losing e-mails across your devices when retrieving your new e-mails. IMAP folders actually also aren&#8217;t that cool because e-mails are usually never deleted or even archived. Having millions of e-mails can make some e-mail readers on certain devices really slow.</p>



<p>The below script is an example how to clean and archive e-mails. The script was written in Perl and tested on a Courier IMAP server. Remember the Perl devise: there are million ways to write Perl scripts.</p>



<p>The following modules were used:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use Net::IMAP::Simple::SSL;
use Email::Simple;
use Getopt::Long qw/:config bundling/;
use DateTime;
use YAML qw/LoadFile/;
use Log::Log4perl;
use Pod::Usage;
use Data::Dumper;</pre>



<p>The configuration file is written in YAML and the logging of the script is handled by Log4Perl.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">log4perl.logger.shihai= DEBUG, shihaiLogfile
log4perl.logger.hostlistdb= DEBUG, shihaiLogfile

log4perl.appender.shihaiLogfile          = Log::Log4perl::Appender::File
log4perl.appender.shihaiLogfile.filename = /var/tmp/shihai.log
log4perl.appender.shihaiLogfile.layout   = Log::Log4perl::Layout::PatternLayout
log4perl.appender.shihaiLogfile.layout.ConversionPattern = %d %p [%x][%r millis][%c][%F{1}:%L][%M] %m%n</pre>



<p>The configuration file contains the login credentials and the threshold values:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">imap:
    host: 'mail.example.com'
    user: 'bob@example.com'
    pass: 'myultrasecretpassword'

threshold:
    archive:
        years: 3
    delete:
        years: 8
</pre>



<p>The threshold values are in fact the same parameters that can be used in the DateTime method &#8216;subtract()&#8217;;</p>



<p>The main loop of the program will:</p>



<ul class="wp-block-list"><li>Connect to the IMAP server</li><li>Retrieve all IMAP folders and loop through them</li><li>Loop through all messages in each mailbox</li></ul>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Connect to the IMAP server
my $imap = connect_imap( $config-&amp;gt;{imap} );

# get all mailboxes and loop through them
my @mailboxes = get_mailboxes($imap);
foreach my $mailbox_name (@mailboxes){

    # Skip all Archive boxes 
    next if $mailbox_name =~ /Archive/;

    # select the mailbox and get the number of messages
    my $mb = $imap-&amp;gt;select($mailbox_name);
    unless(defined $mb){
        $logger-&amp;gt;error("Mailbox [$mailbox_name] doesn't exist: ", $imap-&amp;gt;errstr());
        next;
    }

    $logger-&amp;gt;info("Scanning $mailbox_name");

    # loop through the messages
    foreach my $i (1 .. $mb){
        my ($from, $subject, $date, $year) = get_mail_header($imap, $i);

        if(defined $date){
            if($date &amp;lt; $delete_date ){
                delete_mail($imap, $i);
            }
            elsif($date &amp;lt; $archive_date){
                $logger-&amp;gt;info("Archiving [$i][$from][$subject][$date]");
                my $archive_box = get_archive_box($imap, $mailbox_name);
                move_mail($imap, $i, $archive_box)
            }
        }
    }

    $imap-&amp;gt;expunge_mailbox($mailbox_name);
}

$imap-&amp;gt;quit;
</pre>



<p>Let&#8217;s go through the different subroutines called in the mainloop.</p>



<p><strong>connect_imap()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub connect_imap {
    my ($cfg) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    my $imap = Net::IMAP::Simple::SSL-&amp;gt;new($cfg-&amp;gt;{host})
        or $logger-&amp;gt;logdie("Unable to connect to IMAP server: $Net::IMAP::Simple::errstr");
    $logger-&amp;gt;info("Connected to IMAP host $cfg-&amp;gt;{host}");

    unless( $imap-&amp;gt;login($cfg-&amp;gt;{user}, $cfg-&amp;gt;{pass}) ){
        $logger-&amp;gt;logdie("Login failed: ", $imap-&amp;gt;errstr);

    }
    $logger-&amp;gt;info("Logged to IMAP host $cfg-&amp;gt;{host} as user '$cfg-&amp;gt;{user}'");

    return $imap;
}
</pre>



<p>The parameters expected in the &#8216;$cfg&#8217; hash are:</p>



<ul class="wp-block-list"><li>host</li><li>user</li><li>pass</li></ul>



<p><strong>get_mailboxes()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub get_mailboxes {
    my ($imap) = @_;
    my @mailboxes = $imap-&amp;gt;mailboxes;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    return @mailboxes;
}
</pre>



<p>Ok, I admit, I shouldn&#8217;t have created an extra subroutine for it&#8230; but i was kind of in a flow!</p>



<p><strong>get_mail_header()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub get_mail_header {
    my ($imap, $i) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    my $header = $imap-&amp;gt;top($i);
    unless( $header ){
        $logger-&amp;gt;error("No header found for message $i in ", $imap-&amp;gt;current_box);
    }
    
    my $email = Email::Simple-&amp;gt;new(join '', @{ $header });
    unless( $email ){
        $logger-&amp;gt;error("No Email::Simple object, skipping...");
        return
    }

    my ($subject) = $email-&amp;gt;header('Subject');
    my ($date)    = $email-&amp;gt;header('Date');
    my ($from)    = $email-&amp;gt;header('From');

    # $logger-&amp;gt;debug("Got e-mail [$from] [$subject] [$date]");

    unless(defined $date){
        $logger-&amp;gt;error("No date found: ", $email-&amp;gt;header_obj-&amp;gt;as_string);
        delete_mail($imap, $i);
        return;
    }
    
    my($junk, $day, $month, $year) = ( $date =~ m/(...,\s+)?([0-9]{1,2})\s+(...)\s+(\d{4})/ );

    my $date_obj;
    if(defined $year &amp;amp;&amp;amp; defined $month &amp;amp;&amp;amp; defined $day){
        $date_obj = DateTime-&amp;gt;new(
            year  =&amp;gt; $year,
            month =&amp;gt; $months{$month},
            day   =&amp;gt; $day,
        );
    }    

    return ($from, $subject, $date_obj, $year, $month, $day);
}
</pre>



<p>This subroutine takes the &#8216;$imap&#8217; object and the message number as input parameters. It will then retrieve the mail header and convert it to an &#8216;Email::Simple&#8217; object. I&#8217;ve chosen this module so I can easily extract header fields.<br />If no &#8216;Date:&#8217; field was found in the e-mail, then the tool will just delete the email. I don&#8217;t like e-mails with wrong or missing headers <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (they&#8217;re usually spam anyway).</p>



<p><strong>delete_mail()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub delete_mail {
    my($imap, $i) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    if( $imap-&amp;gt;delete($i) ){
        $logger-&amp;gt;info("Deleted message number $i from ", $imap-&amp;gt;current_box);
    }
}
</pre>



<p>Pretty straightforward.</p>



<p><strong>get_archive_box()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub get_archive_box{
    my($imap, $mailbox_name) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    my ($archive_box) = ($mailbox_name);
    $archive_box =~ s/INBOX/INBOX.Archive/;

    if( not grep /^$archive_box$/, @mailboxes) {
        create_mailbox($imap, $archive_box);
        subscribe($imap, $archive_box);
    }

    return $archive_box;       
}
</pre>



<p>This subroutine will assemble the archive mailbox name. It will then check if the mailbox already exists and otherwise create it and subscribe to it.</p>



<p><strong>create_mailbox()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub create_mailbox {
    my($imap, $mb) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    $imap-&amp;gt;create_mailbox($mb) or $logger-&amp;gt;logdie("Mailbox creation '$mb' failed: ", $imap-&amp;gt;errstr());
    $logger-&amp;gt;info("Created mailbox $mb");
}
</pre>



<p>It will basically just create the mailbox and log about it.</p>



<p><strong>subscribe()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub subscribe {
    my($imap, $box) =@_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    $imap-&amp;gt;folder_subscribe($box);
    $logger-&amp;gt;info("Subscribed to mailbox $box");
}
</pre>



<p><strong>move_mail()</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub move_mail {
    my($imap, $i, $new_box) = @_;
    my $logger = Log::Log4perl-&amp;gt;get_logger('shihai.archive_mail');

    if( $imap-&amp;gt;copy($i, $new_box) ){
        $logger-&amp;gt;info("Copied message number [$i] from ", $imap-&amp;gt;current_box ," to [$new_box]");
        delete_mail($imap, $i);
    }
}
</pre>



<p>Moving an e-mail consists of copying it first to the new mailbox and then afterwards removing it from the old mailbox.</p>



<p>And that&#8217;s basically it!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2015/11/perl-archive-e-mails-in-an-imap-folder/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Perl: SSL Communication in web applications</title>
		<link>https://jmorano.moretrix.com/2014/11/perl-ssl-communication-in-web-applications/</link>
					<comments>https://jmorano.moretrix.com/2014/11/perl-ssl-communication-in-web-applications/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 06 Nov 2014 10:27:34 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[OpenSSL]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1142</guid>

					<description><![CDATA[The following demonstrates how to create a strict SSL communication between client and server, using HTTP.This setup could&#8230;]]></description>
										<content:encoded><![CDATA[
<p>The following demonstrates how to create a strict SSL communication between client and server, using HTTP.<br />This setup could used when creating a web API which requires strong encryption and only allows clients which have a properly signed certificate.</p>



<p>The Apache configuration in the below example will actually require 2 web servers:</p>



<ul class="wp-block-list"><li>one proxy host, which will accept the SSL connection, verify, check for ACLs and then forward the connection unencrypted internally</li><li>one internal web server which will actually contain the WebAPI scripts</li></ul>



<p>This article explains how to use <a href="http://mojolicio.us/" target="_blank" rel="noopener">Mojolicious</a> for the WebAPI side and <a href="http://search.cpan.org/~mschilli/libwww-perl-6.08/lib/LWP/UserAgent.pm" target="_blank" rel="noopener">LWP::UserAgent</a> to send and receive the WebAPI calls. We will furthermore use <a href="http://search.cpan.org/~makamaka/JSON-2.90/lib/JSON.pm" target="_blank" rel="noopener">JSON</a> to send and receive information.</p>



<p>First we need to have or create a set of OpenSSL certificates.<br />The below example uses self signed certificates, since they don&#8217;t cost any money and suit perfect for the purpose of this example.<br />There a million howto&#8217;s on the internet which explains these steps very thoroughly, so I won&#8217;t reinvent the wheel. I&#8217;m just going to post the steps I took to create:</p>



<ul class="wp-block-list"><li>a CA certificate</li><li>a client certificate</li><li>a server certificate</li></ul>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">cd /path/to/SSL
cp /etc/ssl/openssl.cnf example.cnf
vim example.cnf  # Edit the file to your needs
openssl genrsa -aes256 -out private/example_com_ca.key 4096
openssl req -config example.cnf -new -x509 -extensions v3_ca -key private/example_com_ca.key -out certs/example_com_ca.crt -days 3650
openssl req -config example.cnf -new -nodes -keyout private/client01.key -out client01.csr -days 365
openssl ca -config example.cnf -policy policy_anything -out certs/client01.crt -infiles client01.csr
openssl req -config example.cnf -new -nodes -keyout private/server.key -out server.csr -days 365
openssl ca -config example.cnf -policy policy_anything -out certs/server.crt -infiles server.csr</pre>



<p>Next we will need to configure our web server (this example uses the Apache web server) in order to use our self signed certificates, and to proxy forward our WebAPI calls.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="apache" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">SSLEngine on
SSLCertificateFile       /path/to/SSL/certs/server.crt
SSLCertificateKeyFile    /path/to/SSL/private/server.key
SSLCertificateChainFile  /path/to/SSL/certs/example_com_ca.crt
SSLCACertificateFile     /path/to/SSL/certs/example_com_ca.crt
SSLVerifyClient require

ProxyPass /send/         http://internal-host/send.pl/
ProxyPassReverse /send/  http://internal-host/send.pl/

&lt;Proxy *>
            Options FollowSymLinks MultiViews
            AllowOverride All
            Order deny,allow
            allow from localhost
            allow from 8.8.8.8 # The client IP address
            deny from all
&lt;/Proxy></pre>



<p>The above the configuration for the external proxy server. The internal web server should have a pretty straight-forward configuration:</p>



<ul class="wp-block-list"><li>a cgi-handler for the Perl extension &#8216;.pl&#8217;</li></ul>



<p>I could have also send those proxy requests to an internal Mojolicious application, listening on a specific port. I&#8217;ll leave that for another article.</p>



<p>The test client script is going to make a SSL connection to the external web server, send some JSON and wait for the server to send some JSON data back. The interesting part in the below script is how to set up the SSL connection.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/usr/bin/perl
use strict; use warnings;

use HTTP::Request;
use LWP::UserAgent;
use IO::Socket::SSL;
use JSON;

my $data = {
    username  => 'skipper',
    password  => 'secret',
    variable  => 'value',
};

my $uri = 'https://example.com/send/event';
my $json = encode_json( $data );
my $req = HTTP::Request->new( 'POST', $uri );
$req->header( 'Content-Type' => 'application/json' );
$req->content( $json );
 
my $lwp = LWP::UserAgent->new(
    ssl_opts => {
        SSL_use_cert    => 1,
        SSL_version     => 'TLSv12',
        SSL_verify_mode => SSL_VERIFY_PEER,
        SSL_ca_file     => "/path/to/SSL/certs/example_com_ca.crt",
        SSL_cert_file   => "/path/to/SSL/certs/client01.crt",
        SSL_key_file    => "/path/to/SSL/private/client01.key",
    },
) or die "SSL Connection failed: $!";
my $res = $lwp->request( $req );
if ($res->is_success) {
    print "RESPONSE:", $res->content . "\n";
} 
else {
    print "ERROR: ", $res->status_line . "\n";
}</pre>



<p>The server example uses the Mojolicious frame work. Mojolicious is the porn for every Perl WebAPI developer. If you don&#8217;t know it, you should be ashamed and start reading about it right away.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/usr/bin/env perl
 
use Mojolicious::Lite;

# A helper to identify visitors
helper whois => sub {
    my $c               = shift;
    my $headers         = $c->req->headers;
    my $agent           = $c->req->headers->user_agent || 'Anonymous';
    my $local_ip        = $c->tx->remote_address;
    my $remote_ip       = $headers->header('x-forwarded-for');

    return { 
        agent      => $agent, 
        local_ip   => $local_ip,
        remote_ip  => $remote_ip,
   };
};

any '/' => sub {
  my $c = shift;
  $c->render( text => "There is nothing to see here, move along" );
};
 
post '/event' => sub {
    my $c = shift;
    my $json = $c->req->json;
    my $data = {
        username        => $json->{username},
        password        => $json->{password},
        whois           => $c->whois,
    };
    $c-&amp;gt;render( json => $data );
};



### IMPORTANT
app->secret('some_cool_secret');
app->start;
</pre>



<p>Example output of the test client script:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ perl test_send.pl 
RESPONSE:
{"whois":{"remote_ip":"176.9.64.17","agent":"libwww-perl\/6.04","local_ip":"176.9.64.17"},"password":"secret","username":"skipper"}</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2014/11/perl-ssl-communication-in-web-applications/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Perl: Create schema backups in PostgreSQL</title>
		<link>https://jmorano.moretrix.com/2014/08/perl-create-schema-backups-in-postgresql/</link>
					<comments>https://jmorano.moretrix.com/2014/08/perl-create-schema-backups-in-postgresql/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Fri, 22 Aug 2014 09:09:20 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[Postgresql]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1114</guid>

					<description><![CDATA[At my recent job, I was asked to create a backup procedure, which would dump a PostgreSQL schema&#8230;]]></description>
										<content:encoded><![CDATA[
<p>At my recent job, I was asked to create a backup procedure, which would dump a PostgreSQL schema to a compressed file and which was able to create weekly and daily backups.<br />The backups had to be full backups each time a backup was made and the amount of daily and weekly backups should be defined through thresholds.</p>



<p>The PostgreSQL tool used for those backups is &#8216;<code>pg_dump</code>&#8216; and I have used Perl to script all the interesting stuff together.</p>



<p>The script will basically go through the following steps:</p>



<ul class="wp-block-list"><li>Check the backup path for the required directories (and if not, create them)</li><li>Rotate old backups based on thresholds</li><li>Create a new backup</li></ul>



<p>The script shown below is just an example and probably needs to be adopted for your own needs. The script works for me and the environment it was created in.</p>



<p>First things first.<br />The script uses the following Perl modules:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use DateTime;
use Pod::Usage;
use YAML qw/LoadFile/;
use File::Path qw/make_path/;
use File::Copy;
use Data::Dumper;
use POSIX qw/setuid/;
</pre>



<p>A YAML configuration file is used to provide the script with essential information. An example configuration file looks like the following:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">thresholds:
    daily: 7
    weekly: 4

backup_path: /data/backup/schema_backups

database: my_db

daily_to_weekly_pattern: sunday

schemas:
    - my_cool_schema
    - my_not_so_cool_schema
</pre>



<p>Remember: YAML is sensitive about tabs!</p>



<p>Command line arguments are set up in the script by using Getopt::Long.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">my ($help, $cfg_file, $schema, $verbose, $debug) = @_;
# Check command line arguments
GetOptions(
    "help"     =&amp;gt; \$help,
    "verbose"  =&amp;gt; \$verbose,
    "debug"    =&amp;gt; \$debug,
    "cfg=s"    =&amp;gt; \$cfg_file,
    "schema=s" =&amp;gt; \$schema,
);
pod2usage(1) if $help;
</pre>



<p>The script needs to run as the &#8216;postgres&#8217; user. Should it be executed by another user (for instance root), then script will try to switch to the &#8216;postgres&#8217; user.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">my ($user) = ( split /\c/, getpwuid($&amp;lt;) )[0]; 
unless ($user eq 'postgres') { 
    p_info("Script $0 needs to run as 'postgres', switching user..."); 
    setuid(scalar getpwnam 'postgres'); 
}</pre>



<p>Next we will load the configuration file and check if a schema name was supplied on the command line. If one was defined, then we will override the schema names which were set in the configuration, and only create a backup of that one schema name.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if(defined $cfg_file){
    if( -f $cfg_file ){
        p_info("Loading configuration file '$cfg_file'");
        $cfg = LoadFile($cfg_file);
    }
    else {
        die "No such configuration file '$cfg_file'\n";
    }
}

$cfg-&amp;gt;{schemas} = [$schema] if defined $schema;
</pre>



<p>And now we are ready for the mainloop of the script:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">foreach my $s (@{ $cfg-&amp;gt;{schemas} }){
    check_current_backups($s);
    create_backup($s);
}
</pre>



<p>For each schema, we will first check if the required directories are in place and otherwise create them. Afterwards we will check those directories for older backups.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub check_current_backups {
    my($schema) = @_;

    check_directory_structure($schema);
    check_backups('daily', $schema);
    check_backups('weekly', $schema);
}

sub check_directory_structure {
    my($schema) = @_;

    foreach my $period (qw/daily weekly/){
        my $_path = return_backup_path($period, $schema);;
        p_info("Checking path '$_path'");
        unless(-d $_path){
            make_path($_path);
            p_info("Created path '$_path'");
        }
    }
}

# check if older backups need rotation / deletion
sub check_backups {
    my($period, $schema) = @_;

    my $path = return_backup_path($period, $schema);

    my @files = glob("$path/*");
    my @sorted = sort { get_date($b) &amp;lt;=&amp;gt; get_date($a) } @files;

    if(scalar @sorted &amp;gt;= $cfg-&amp;gt;{thresholds}{$period}){
        p_info("Rotating backups for period '$period'");
        rotate_backups($period, \@sorted);
    }
}
</pre>



<p>The rotation of the backups works like follows:<br />&#8211; If the day threshold has been reached (for instance 7 daily backups), then those files will be nominated for rotation or deletion</p>



<p>The rotation itself is custom designed for my current job. Each backup filename is appended the day name (Monday, Tuesday, &#8230;). Backup files matching a certain pattern (in my situation &#8216;sunday&#8217;) will be moved into the &#8216;weekly&#8217; backup path, other old files will be deleted.</p>



<p>Since rotation is done before a backup is created, we will delete one more as required file (since a new backup file is going to be created in a few lines further).</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub rotate_backups {
    my($period, $files) = @_;

    p_debug("All Files: ".Dumper($files));
    p_debug("$period threshold: ".$cfg-&amp;gt;{thresholds}{$period});

    # make a true copy
    my (@to_move_files) = (@{ $files });
    # The @files contains all backup files, with the youngest as element 0, the oldest 
    # backup as last element.
    # @to_move_files is a slice of @files, starting from the position threshold - 1, 
    # until the end of the array. Those files will be either rotated or removed
    @to_move_files = @to_move_files[ $cfg-&amp;gt;{thresholds}{$period} -1 .. $#to_move_files ];
    p_debug("TO MOVE FILES: ".Dumper(\@to_move_files));

    if($period eq 'daily'){
        foreach my $file (@to_move_files){
            # move backups to weekly
            if($file =~ /$cfg-&amp;gt;{daily_to_weekly_pattern}/){
                p_info("Moving daily backup '$file' to weekly");
                move($file, return_backup_path('weekly', $schema) . '/' . $file)
            }
            else {
                p_info("Removing backup '$file'");
                unlink($file);
            }
        }
    }

    if($period eq 'weekly'){
        foreach my $file (@to_move_files){
            # remove files
            p_info("Removing backup '$file'");
            unlink($file);
        }
    }
}
</pre>



<p>At this point now, the required directory structure has been checked and is present and older backup files have been rotated or deleted.<br />Finally we can create the actual backup:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub create_backup {
    my($schema) =@_;

    p_info("Creating backup for schema '$schema', database:" . $cfg-&amp;gt;{database});
    my $now = DateTime-&amp;gt;now;
    my $path = return_backup_path('daily', $schema) 
                . '/' . $now-&amp;gt;ymd('') . $now-&amp;gt;hms('')
                . '_' .lc($now-&amp;gt;day_name) 
                . '.dump.sql';

    # Create the dump file
    my $dump_output = do{
        local $/;
        open my $c, '-|', "pg_dump -v -n $schema -f $path $cfg-&amp;gt;{database} 2&amp;gt;&amp;amp;1" 
            or die "pg_dump for '$schema' failed: $!";
        &amp;lt;$c&amp;gt;;
    };
    p_debug('pg_dump output: ', $dump_output);

    # GZIP the dump file
    my $gzip_output = do{
        local $/;
        open my $c, '-|', "gzip $path 2&amp;gt;&amp;amp;1" 
            or die "gzip for '$path' failed: $!";
        &amp;lt;$c&amp;gt;;
    };
    p_debug('gzip output: ', $gzip_output);

    # change the permissions
    chmod 0660, "$path.gz";

    p_info("Created backup for schema '$schema' in '$path.gz'");
}
</pre>



<p>The backup is created by issuing <code>pg_dump</code> for that schema and it will produce a normal text SQL file. This file will be compressed with <code>gzip</code> and afterwards the file permissions will be changed to 0660. This means that, since the backup file is created by the <code>postgres</code> user, only the <code>postgres</code> user will have access to this file.</p>



<p>The full script and configuration file can be found at <a title="Github Repositry" href="https://github.com/insani4c/perl_tools/tree/master/backup_schema" target="_blank" rel="noopener">https://github.com/insani4c/perl_tools/tree/master/backup_schema</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2014/08/perl-create-schema-backups-in-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Monitor running processes with Perl</title>
		<link>https://jmorano.moretrix.com/2014/05/monitor-running-processes-with-perl/</link>
					<comments>https://jmorano.moretrix.com/2014/05/monitor-running-processes-with-perl/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 15 May 2014 12:33:22 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[CPAN]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1081</guid>

					<description><![CDATA[Update: This article is updated thanks to Colin Keith his excellent comment. I was extremely inspired by it&#8230;]]></description>
										<content:encoded><![CDATA[
<p><strong>Update:</strong> This article is updated thanks to Colin Keith his excellent comment. I was extremely inspired by it</p>



<p>Maintaining a large number of servers cannot be done without proper programming skills. Each good system administrator must therefor make sure he knows how to automate his daily works.</p>



<p>Although many many programming languages exist, most persons will only write code in one. I happen to like Perl.</p>



<p>In this next blog post, I am going to show how to create a script which can be deployed on all the Linux servers you need to maintain and need to check for certain running services.</p>



<p>Of course, a tool as Nagios together with NRPE and a configured event-handler could also be used, but lately I was often in the situation that the &#8216;nrpe daemon&#8217; crashed, Nagios was spewing a lot of errors and the event-handler&#8230; well, since nrpe was down, the event-handler of course couldn&#8217;t connect or do anything. So why rely on a remote triggered action, when a simple script could be used.</p>



<p>The following script will check a default list of services and can additionally load or overwrite these services. A regular expression can be used to check for running processes, and of course, a startup command needs to be defined. And that is all the script will and should do.</p>



<p>The script uses three CPAN modules:</p>



<ul class="wp-block-list"><li><a title="Proc::ProcessTable" href="http://search.cpan.org/~jwb/Proc-ProcessTable-0.50/ProcessTable.pm">Proc::ProcessTable</a></li><li><a title="YAML" href="http://search.cpan.org/~ingy/YAML-0.90/lib/YAML.pm">YAML</a></li><li><a title="File::Slurp" href="http://search.cpan.org/~uri/File-Slurp-9999.19/lib/File/Slurp.pm">File::Slurp</a></li></ul>



<p>The first one will be used to get a full listing of all running processes and the second one will provide us a means for using configuration files.</p>



<p>So let&#8217;s start our script:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/usr/bin/env perl 
use strict; use warnings;
use utf8;

use Proc::ProcessTable;
use YAML qw/LoadFile/;
use File::Slurp;

# Default set of processes to watch
my %default_services = (
    'NRPE' =&amp;gt; {
        'cmd'     =&amp;gt; '/etc/init.d/nagios-nrpe-server restart',
        're'      =&amp;gt; '/usr/sbin/nrpe -c /etc/nagios/nrpe.cfg -d',
	'pidfile' =&amp;gt; '/var/tmp/nagios-nrpe-server.pid',
    },
    'Freshclam' =&amp;gt; {
        'cmd'     =&amp;gt; '/etc/init.d/clamav-freshclam restart',
        're'      =&amp;gt; '/usr/bin/freshclam -d --quiet',
	'pidfile' =&amp;gt; '/var/tmp/clamav-freshclam.pid',
    },
    'Syslog-NG' =&amp;gt; {
        'cmd'     =&amp;gt; '/etc/init.d/syslog-ng restart',
        're'      =&amp;gt; '/usr/sbin/syslog-ng -p /var/run/syslog-ng.pid',
	'pidfile' =&amp;gt; '/var/run/syslog-ng.pid',     
    },
    'VMToolsD' =&amp;gt; {
        'cmd'     =&amp;gt; '/etc/init.d/vmware-tools restart',
        're'      =&amp;gt; '/usr/sbin/vmtoolsd',
	'pidfile' =&amp;gt; '/var/tmp/vmtoolsd.pid',
    },
    'Munin-Node' =&amp;gt; {
        'cmd'     =&amp;gt; '/etc/init.d/munin-node restart',
        're'      =&amp;gt; '/usr/sbin/munin-node',
	'pidfile' =&amp;gt; '/var/tmp/munin-node.pid',
    },
);

my (%services) = (%default_services);
</pre>



<p>Until now, no rocket science. We load the required modules, we defined our default services that need to be checked.</p>



<p>Next part, check if there is a configuration file on disk. The script looks for a hard-coded path &#8221;/etc/default/watchdog.yaml&#8221;:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Check if there is a local config file and if yes, load them in the services hash
if( -f '/etc/default/watchdog.yaml' ){
    my $local_config = LoadFile '/etc/default/watchdog.yaml';

    %services = (%default_services, %{ $local_config->{services} });
}
</pre>



<p>The last Perl statement actually allows to overwrite one or more (or even all) the default defined services.</p>



<p>Now let&#8217;s see if these processes are actually running. The following code was hugely inspired by Colin Keith&#8217;s comment below. I have combined his examples together with my code.</p>



<p>Let&#8217;s first have a look at the code:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Get current process table
my $processes = Proc::ProcessTable-&amp;gt;new;
my %procs; 
my %matched_procs;
foreach my $p (@{ $processes }){
    $procs{ $p-&amp;gt;{pid} } = $p-&amp;gt;{cmndline};
    foreach my $s (keys %services){
        if($p-&amp;gt;{cmndline} =~ m#$services{$s}-&amp;gt;{re}#){
            $matched_procs{$s}++;
            last;
        }
    }
}

# Search the process table for not running services
foreach my $service ( keys %services ) {
    if(exists($services{$service}-&amp;gt;{pidfile}) &amp;amp;&amp;amp; -f $services{$service}-&amp;gt;{pidfile} ) {
        my $pid = read_file( glob($services{$service}-&amp;gt;{pidfile}) );
 
        # If we get a pid ensure that it is running, and that we can signal it
        $pid &amp;amp;&amp;amp; exists($procs{$pid}) &amp;amp;&amp;amp; kill(0, $pid) &amp;amp;&amp;amp; next;  
        
        # Remove the stale PID file because no running process for this PID file
        unlink( $services{$service}-&amp;gt;{pidfile} );
    }
    else {
        # check if the configured process regex matches
        if( exists($matched_procs{$service}) ){
            # process is running but has no PID file
            next;
        }
    }
	
    # Execute the service command
    system( $services{$service}-&amp;gt;{'cmd'} );

    # Check the exit code of the service command
    if ($? == -1) {
        print "Failed to restart '$service' with '$services{$service}-&amp;gt;{cmd}': $!\n";
    }
    elsif ($? &amp;amp; 127) {
        printf "Restart of '$service' died with signal %d, %s coredump\n", ($? &amp;amp; 127),  ($? &amp;amp; 128) ? 'with':'without';
    }
    else {
        printf "Process '$service' successfully restarted, exit status:  %d\n", $? &amp;gt;&amp;gt; 8;
    }
}
</pre>



<p>Lines 2 retrieves the current process list. We will save that information in two hashes with a little less information, because we actually only need the PID and the actual &#8216;command line&#8217; of each process.</p>



<p>At line 16 we will start looping through the processes we have defined in the <code>%services</code> hash.<br />Inspired by Colins post, we will check if the process&#8217; PID file is still there and if one is configured. If it still exists, we will then verify if the PID stored in the PID file, exists in the process list, which we have stored in <code>%procs</code>. This happens in lines 18-21.<br />At line 21, if the process is still running and the PID matches, we will check the next service to check (<code>&amp;&amp; next</code> part)<br />If the process is not running anymore but the PID file was still in the defined path, then it will be removed at line 24.</p>



<p>Otherwise, if no PID file was found or no PID file was configured, we will check the process list with the regular expression defined for that process. We have already created a hash, <code>%matched_procs</code> between lines 7 and 10, which we will use for this checking. If the process exists in the hash, we will skip and check the next process to be checked.</p>



<p>Now, if there was no PID file or the PID file was removed at line 24, the process will be started again. This happens at line 35.<br />I&#8217;ve executed it with the &#8216;system&#8217; function since I want to have the output of this command directly in STDOUT. And of course, the last thing to do is to check if the process started up correctly or not by checking its exit code.</p>



<p>Now save that script to for instance &#8216;watchdog.pl&#8217; and configure it in a cron job.<br />Example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">*/5 * * * * root /usr/local/bin/watchdog.pl
</pre>



<p>And here&#8217;s an example of the configuration file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">services:
    Exim-Mailserver:
        cmd: /etc/init.d/exim4 restart
        re: /usr/sbin/exim4 -bd -q30m
    Ossec-Agent:
        cmd: /etc/init.d/ossec restart
        re: !!perl/regexp '(?:ossec-agentd|ossec-logcollector|ossec-syscheckd)'

</pre>



<p>Link to script source code: <a href="https://github.com/insani4c/perl_tools/tree/master/watchdog" target="_blank" rel="noopener">https://github.com/insani4c/perl_tools/tree/master/watchdog</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2014/05/monitor-running-processes-with-perl/feed/</wfw:commentRss>
			<slash:comments>15</slash:comments>
		
		
			</item>
		<item>
		<title>Postgresql: Monitor sequence scans with Perl</title>
		<link>https://jmorano.moretrix.com/2014/02/postgresql-monitor-sequence-scans-perl/</link>
					<comments>https://jmorano.moretrix.com/2014/02/postgresql-monitor-sequence-scans-perl/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Wed, 12 Feb 2014 07:33:26 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Postgresql]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1065</guid>

					<description><![CDATA[Not using indexes or huge tables without indexes, can have a very negative impact on the duration of&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Not using indexes or huge tables without indexes, can have a very negative impact on the duration of a SQL query. The query planner will decide to make a sequence scan, which means that the query will go through the table sequentially to search for the required data. When this table is only 100 rows big, you will probably not even notice it is making a sequence scans, but if your table is 1,000,000 rows big or even more, you can probably optimize your table to use indexes to result in faster searches.</p>



<p>In the example script we will be using a <em>Storable</em> state file and we will the statistics as a JSON object in the PostgreSQL database.</p>



<p>First let&#8217;s take a look at the query we will be executing:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">SELECT schemaname, relname, seq_tup_read 
FROM pg_stat_all_tables 
WHERE seq_tup_read &amp;gt; '0' 
      AND relname NOT LIKE 'pg_%'
ORDER BY seq_tup_read desc
</pre>



<p>As you can see, PostgreSQL stores all the information we need about our tables in just one table, called <em>pg_stat_all_tables</em>. In this table there is a column called <em>seq_tup_read</em>, which will contain the information we need.</p>



<p>Just reading out this information is not going to be enough, because it contains information since the startup of your PostgreSQL database. Since production databases aren&#8217;t restarted (that often), we will have to compare this information with some previous information (hence the <em>Storable</em> state file).<br />Our plan is to run the script in a cronjob, each 5 minutes.</p>



<p>The statistics are also stored in as a JSON object in a database, just so that we could build some web interface for the statistics, in a later stage. And we want to keep a history of these statistics.</p>



<p>Furthermore the script will <em>setuid</em> to postgres (same like <em>su &#8211; postgres</em> on the command line), so that it could connect to the PostgreSQL UNIX socket file.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use strict;
use warnings;
use utf8;

use DBI;
use DateTime;
use POSIX qw/setuid/;
use Text::ASCIITable;
use JSON;

my $db   = 'mydatabase';
if(scalar @ARGV){
    $db = shift @ARGV;
}

my $host = '/var/run/postgresql';
my $user = 'postgres';
my $pass = 'undef';

my $state_db   = 'database_statistics';
my $state_host = '192.168.1.1';
my $state_user = 'skeletor';
my $state_pass = 'he-manisawhimp';

my $state_file = '/var/tmp/sequence_read.state';

# suid to postgres
setuid(scalar getpwnam 'postgres');

# define and open up the state file
my $state = {};
$state = retrieve $state_file if -f $state_file;

my $now      = DateTime-&amp;gt;now;

# Connect to the database which we want to monitor
my $dbh = DBI-&amp;gt;connect("dbi:Pg:dbname=$db;host=$host", $user, $pass) 
                or die "Could not connect to database: $!\n";

# Connect to the database that will be used to store the statistics
my $state_dbh = DBI-&amp;gt;connect("dbi:Pg:dbname=$state_db;host=$state_host", $state_user, $state_pass) 
                or die "Could not connect to the State database '$state_db': $!\n";

my $sql = &amp;lt;&amp;lt;EOF;
SELECT schemaname, relname, seq_tup_read 
FROM pg_stat_all_tables 
WHERE seq_tup_read &amp;gt; '0' 
      AND relname NOT LIKE 'pg_%'
ORDER BY seq_tup_read desc
EOF

# Get the statistics
my $results = $dbh-&amp;gt;selectall_arrayref( $sql, undef);

# Store the statistics as a JSON object in the second databse
eval {
    $state_dbh-&amp;gt;do('INSERT INTO mydbschema.seq_tup_read (data) VALUES(?)', undef, encode_json($results));
};
if($@){
    print "Insert into state-db failed: $@\n";
}

# Prepare a nice ASCII table for output
my $t = Text::ASCIITable-&amp;gt;new({ headingText =&amp;gt; 'Seq Tup Read ' . $now-&amp;gt;ymd('-')     . ' ' . $now-&amp;gt;hms(':')});
$t-&amp;gt;setCols('Schema Name','Relation Name ', 'Seq Tup Read', 'Increase (delta)');

my $row_count = 0;
foreach my $r (@{$results}){
    last if $row_count &amp;gt; 25;

    my (@values) = (@{$r});
    my ($increase, $delta) = (0, 0);
    # Calculate the increase and its delta
    if(defined $state-&amp;gt;{last}{$r-&amp;gt;[0].':'.$r-&amp;gt;[1]}{seq_tup_read}){
        $increase = $r-&amp;gt;[2] - $state-&amp;gt;{last}{$r-&amp;gt;[0].':'.$r-&amp;gt;[1]}{seq_tup_read};
        $delta    = $increase / $state-&amp;gt;{last}{$r-&amp;gt;[0].':'.$r-&amp;gt;[1]}{seq_tup_read} * 100;
        my $str = sprintf '%.0f (%.4f %%)', $increase, $delta;
        push @values, ($str);
    }
    else {
        push @values, '0 (0%)';
    }
    # Store this information for the next run of the script
    $state-&amp;gt;{last}{$r-&amp;gt;[0].':'.$r-&amp;gt;[1]}{seq_tup_read} = $r-&amp;gt;[2];
    $state-&amp;gt;{last}{$r-&amp;gt;[0].':'.$r-&amp;gt;[1]}{delta}        = $delta;
    $state-&amp;gt;{last}{$r-&amp;gt;[0].':'.$r-&amp;gt;[1]}{increase}     = $increase;

    # Only add the information to ASCII output table if there was an increase
    next unless $increase &amp;gt; 0;
    $t-&amp;gt;addRow(@values);
    $row_count++;
}
# Print out the ASCII table
print $t;

nstore $state, $state_file;

</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2014/02/postgresql-monitor-sequence-scans-perl/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Postgresql: Monitor unused indexes</title>
		<link>https://jmorano.moretrix.com/2014/02/postgresql-monitor-unused-indexes/</link>
					<comments>https://jmorano.moretrix.com/2014/02/postgresql-monitor-unused-indexes/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Tue, 11 Feb 2014 09:09:08 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Postgresql]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1057</guid>

					<description><![CDATA[Working on large database systems, with many tables and many indexes, it is easy to loose the overview&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Working on large database systems, with many tables and many indexes, it is easy to loose the overview on what is actually being used and what is just consuming unwanted disk space.<br />If indexes are not closely monitored, they could end up using undesired space and moreover, they will consume unnecessary CPU cycles.</p>



<p>Statistics about indexes can be easily retrieved from the PostgreSQL database system. All required information is stored in two tables:</p>



<ul class="wp-block-list"><li>pg_stat_user_indexes</li><li>pg_index</li></ul>



<p>When joining these two tables, interesting information can be read in the following columns:</p>



<ul class="wp-block-list"><li>idx_scan: has the query planner used this index for an &#8216;Index Scan&#8217;, the number returned is the amount of times it was used</li><li>idx_tup_read: how many tuples have been read by using the index</li><li>idx_tup_fetch: how many tuples have been fetch by using the index</li></ul>



<p>A neat function called <em>pg_relation_size()</em> allows to fetch the on-disk size of a relation, in this case the index.</p>



<p>Based on this information, the monitoring query will be built up as follows:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">SELECT 
    relid::regclass AS table, 
    indexrelid::regclass AS index, 
    pg_size_pretty(pg_relation_size(indexrelid::regclass)) AS index_size, 
    idx_tup_read, 
    idx_tup_fetch, 
    idx_scan
FROM 
    pg_stat_user_indexes 
    JOIN pg_index USING (indexrelid) 
WHERE 
    idx_scan = 0 
    AND indisunique IS FALSE
</pre>



<p>Now, all we need to do is write a script which stores this information in some kind of file and periodically report about the statistics.</p>



<p>First of all we will need a configuration file, which contains the database credentials.<br />I&#8217;ve chosen YAML because it is so versatile.</p>



<p>It will contain two important sets of information:</p>



<ul class="wp-block-list"><li>The database credentials</li><li>path to the state file</li></ul>



<p>Example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">dsn: "dbi:Pg:host=/var/run/postgresql;database=testdb"
user: postgres
pass:
state_file: /var/tmp/monitor_unused_indexes.state
</pre>



<p>As you can see, we will be connect to the PostgreSQL database by using its UNIX socket.</p>



<p>The script will use <em>Text::ASCIITable</em> to output the statistics in a nice table. <em>Storable</em> is used to save our statistics to disk.</p>



<p>In the below script, we will check if an index was unused in a timespan of 30 days. If yes, the script will report this index to STDOUT.<br />Therefore, we will store a score and timestamp for each unused index in the state file.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use DBI;
use Storable qw/nstore retrieve/;
use YAML qw/LoadFile/;
use POSIX qw/setuid/;
use Getopt::Long;
use DateTime;
use Text::ASCIITable;

my $cfg_file = './monitor_unused_indexes.yaml';
my $verbose = 0;
GetOptions("cfg=s" =&amp;gt; \$cfg_file,
           "verbose|v" =&amp;gt; \$verbose, 
        );

my $sql = &amp;lt;&amp;lt;EOS;
SELECT 
    relid::regclass AS table, 
    indexrelid::regclass AS index, 
    pg_size_pretty(pg_relation_size(indexrelid::regclass)) AS index_size, 
    idx_tup_read, 
    idx_tup_fetch, 
    idx_scan
FROM 
    pg_stat_user_indexes 
    JOIN pg_index USING (indexrelid) 
WHERE 
    idx_scan = 0 
    AND indisunique IS FALSE
EOS

my ($cfg) = LoadFile($cfg_file);

# suid to postgres, other whatever user is configured in the config.yaml file
setuid(scalar getpwnam $cfg-&amp;gt;{user});

# Connect to the database
my $dbh = DBI-&amp;gt;connect($cfg-&amp;gt;{dsn}, $cfg-&amp;gt;{user}, $cfg-&amp;gt;{pass}) 
            or die "Could not connect to database: $! (DBI ERROR: ".$DBI::errstr.")\n";

my $state;
if(-f $cfg-&amp;gt;{state_file}){
    $state = retrieve $cfg-&amp;gt;{state_file};
}

# Fetch the statistics
my $results = $dbh-&amp;gt;selectall_arrayref( $sql, undef );

my $now_dt   = DateTime-&amp;gt;now;

# Initialize the ASCII table
my $t = Text::ASCIITable-&amp;gt;new({ headingText =&amp;gt; 'INDEX STATISTICS'});
$t-&amp;gt;setCols(qw/Table Index Index_Size idx_tup_read idx_tup_fetch idx_scan/);

# Analyze the results
foreach my $r (@$results){
    if($verbose){
        $t-&amp;gt;addRow(@{$r});
    }
    # Only update the state file if --verbose was not specified.
    # This way the script can be check manually with --verbose many times and executed for instance
    # from a cronjob once a day without --verbose
    else {
        if(defined $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}){
            my $first_dt = DateTime-&amp;gt;from_epoch( epoch =&amp;gt; $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{first_hit} );
            if($first_dt-&amp;gt;add(days =&amp;gt; $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{score})-&amp;gt;day == $now_dt-&amp;gt;day ) {
                $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{score}++;
            }
            else {
                $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{score}     = 1;
                $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{first_hit} = $now_dt-&amp;gt;epoch;;
            }
        }
        else {
            $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{score}     = 1;
            $state-&amp;gt;{unused_indexes}{$r-&amp;gt;[1]}{first_hit} = $now_dt-&amp;gt;epoch;;
        }
    }
}

# Print out the statistics table, if --verbose was specified
print $t if $verbose; 

# Store the statistics to disk in a state file
nstore $state, $cfg-&amp;gt;{state_file};

foreach my $idx (keys %{ $state-&amp;gt;{unused_indexes} }){
    my $first_dt = DateTime-&amp;gt;from_epoch( epoch =&amp;gt; $state-&amp;gt;{unused_indexes}{$idx}{first_hit} );
    if( $first_dt-&amp;gt;add(days =&amp;gt; 30) &amp;lt;= $now_dt ){
        my $line = "Index: $idx ready for deletion";
        $line .= " (score:" . $state-&amp;gt;{unused_indexes}{$idx}{score};
        $line .= " (|first_hit:" . DateTime-&amp;gt;from_epoch(epoch =&amp;gt; $state-&amp;gt;{unused_indexes}{$idx}{first_hit})-&amp;gt;ymd . ")";

        print $line."\n" if $verbose;
    }
}
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2014/02/postgresql-monitor-unused-indexes/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Postgresql 9.3: Creating an index on a JSON attribute</title>
		<link>https://jmorano.moretrix.com/2013/12/postgresql-9-3-creating-index-json-attribute/</link>
					<comments>https://jmorano.moretrix.com/2013/12/postgresql-9-3-creating-index-json-attribute/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Fri, 27 Dec 2013 10:28:25 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[Postgresql]]></category>
		<category><![CDATA[SQL]]></category>
		<guid isPermaLink="false">http://jmorano.moretrix.com/?p=1036</guid>

					<description><![CDATA[Recently I&#8217;ve discovered some very interesting new features in the PostgreSQL 9.3 database.First of all, a new data&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Recently I&#8217;ve discovered some very interesting new features in the PostgreSQL 9.3 database.<br />First of all, a new data type has been introduced: <a title="Datatype JSON" href="http://www.postgresql.org/docs/9.3/static/datatype-json.html" target="_blank" rel="noopener">JSON</a>. Together with this new data type, <a title="JSON Functions" href="http://www.postgresql.org/docs/9.3/static/functions-json.html" target="_blank" rel="noopener">new functions</a> were also introduced.</p>



<p>These new features now simply for instance saving web forms in your Postgresql database. Or actually any kind of dynamic data, such as for instance Perl hashes. Plus, thanks to the new JSON functions, this data can be easily searched and indexed.</p>



<p>Let&#8217;s start with creating a test table.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CREATE SEQUENCE data_seq    
    START WITH 1    
    INCREMENT BY 1    
    NO MINVALUE    
    NO MAXVALUE    
    CACHE 1;

CREATE TABLE data (    
    id bigint DEFAULT nextval('data_seq'::regclass) NOT NULL,
    form_name TEXT,
    form_data JSON
);
</pre>



<p>I&#8217;ve inserted into this table 100k rows of test data with a very simple Perl script.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/usr/bin/perl
use strict;
use DBI;
use AnyEvent;
use AnyEvent::Util;
$AnyEvent::Util::MAX_FORKS = 25;

print "Inserting test data...\n";
my $cv = AnyEvent-&amp;gt;condvar;
$cv-&amp;gt;begin;
foreach my $d (0..100000){
    $cv-&amp;gt;begin;
    fork_call {
        my($d) = @_;
        my $name = do{local $/; open my $c, '-|', 'pwgen -B -s -c1 64'; &amp;lt;$c&amp;gt;};
        chomp($name);
        my $dbh = DBI-&amp;gt;connect("dbi:Pg:host=/var/run/postgresql;dbname=test;port=5432",'postgres', undef);
        $dbh-&amp;gt;do(qq{insert into data (form_name,form_data) VALUES('test_form', '{"c":{"d":"ddddd"},"name":"$name","b":"bbbbb", "count":$d}')});
        $dbh-&amp;gt;disconnect;
        return $d;
    } $d,
    sub {
        my ($count) = @_;
        print "$d ";
        $cv-&amp;gt;end;
    }
} 
$cv-&amp;gt;end;
$cv-&amp;gt;recv;
print "\n\nDone\n";
</pre>



<p>Now let&#8217;s assume that the JSON data we are going to insert (or have inserted) always contains the attribute field &#8216;name&#8217;. On this attribute we will create the following database index:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CREATE INDEX ON data USING btree (form_name, json_extract_path_text(form_data,'name'));
</pre>



<p>The above example creates a multi-column index.</p>



<p>Now let&#8217;s a make our first test.<br />The first test will not use the index we have created previously.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">EXPLAIN ANALYZE VERBOSE SELECT * FROM data WHERE form_name = 'test_form' AND form_data-&amp;gt;&amp;gt;'name' = 'cbcO5twuPnAYJ1VLV6gsEv9zWs2AbQxQ9PoALLr2w6Rwpr2PtoQHCCK0hyOMuIME';
                                                                             QUERY PLAN                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on data  (cost=0.00..4337.28 rows=500 width=102) (actual time=28.608..129.945 rows=1 loops=1)
   Filter: ((data.form_name = 'test_form'::text) AND ((data.form_data -&amp;gt;&amp;gt; 'name'::text) = 'cbcO5twuPnAYJ1VLV6gsEv9zWs2AbQxQ9PoALLr2w6Rwpr2PtoQHCCK0hyOMuIME'::text))
   Rows Removed by Filter: 100000
 Total runtime: 129.968 ms
(5 rows)

</pre>



<p>130ms for searching through 100k rows, is actually quite ok.</p>



<p>Now let&#8217;s see how we can speed up this query by using the index we&#8217;ve created.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">EXPLAIN ANALYZE VERBOSE SELECT * FROM data WHERE form_name = 'test_form' AND json_extract_path_text(form_data,'name') = 'cbcO5twuPnAYJ1VLV6gsEv9zWs2AbQxQ9PoALLr2w6Rwpr2PtoQHCCK0hyOMuIME';
                                                                             QUERY PLAN                                                                                                
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using data_form_name_json_extract_path_text_idx on data  (cost=0.42..8.44 rows=1 width=102) (actual time=0.056..0.057 rows=1 loops=1)
   Index Cond: ((data.form_name = 'test_form'::text) AND (json_extract_path_text(data.form_data, VARIADIC '{name}'::text[]) = 'cbcO5twuPnAYJ1VLV6gsEv9zWs2AbQxQ9PoALLr2w6Rwpr2PtoQHCCK0hyOMuIME'::text))
 Total runtime: 0.084 ms
(4 rows)

</pre>



<p>0.084ms! That&#8217;s is about 1625 times faster! What makes this index extremely interesting is that the index has only been created on one attribute of the JSON data and not on the entire JSON data. This will keep the index data small and thus will be kept longer in your database&#8217; memory.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2013/12/postgresql-9-3-creating-index-json-attribute/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
