Speeding up a ton of short-lived `psql` sessions on Windows - bash

I have inherited an application which is written in dozens (maybe hundreds, I haven't counted exactly) of PostgreSQL functions. In order to check the application code into git and be able to easily work on specific functions, I used pg_extractor to export the database into a separate file for each function.
In order to easily apply updates from git (both on developer machines and in production), I wrote a bash script that uses the psql command line client to run all of the function files, which causes the database server to be updated to match the files from git.
The gist of it looks like this (with some initialization code replaced by comments for brevity):
#!/bin/bash
# Check if a .env file is present and load it to set the PGHOST, PGPORT, PGUSER, PGPASSWORD, and PGDATABASE environment variables
# Check that `psql` is on the PATH
# Check the the database set in PGDATABASE exists on the server
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
makeFunction () {
echo -n "CREATE OR REPLACE FUNCTION $2"
psql -q -x < "$DIR/$1/functions/$2.sql" > /dev/null
if [ $? -eq 0 ]; then
echo -e " - ${GREEN}COMPLETE${NC}"
else
echo -e " - ${RED}FAILED${NC}"
exit 1
fi
}
for schema in admin admin_internal main main_internal
do
if [ -d "$DIR/$schema/functions" ]; then
for function in $DIR/$schema/functions/*.sql
do
makeFunction $schema $(basename "$function" .sql)
done
fi
done
On most of our Linux machines (development and production, Ubuntu 16.04 and 18.04) this script takes 15-20 seconds. Example:
real 0m14.324s
user 0m6.894s
sys 0m1.742s
However, on our Windows development machines (when run using git-bash) it usually takes around three minutes to run the same script. Example:
real 3m0.825s
user 0m3.525s
sys 0m11.943s
(Thinking the issue might be with Bash on Windows, I tried converting the script to PowerShell, only to see the same issue. Thankfully I saw that it wouldn't make a difference while doing partial testing before spending too much time on it.)
It turns out that the problem is in actually making the connection to the PostgreSQL server. For example, using time psql -lqt to time listing all databases on the server (these are example numbers, but dozens of test runs have shown that they are consistently the similar to these):
On Ubuntu:
real 0m0.055s
user 0m0.032s
sys 0m0.020s
On Windows:
real 0m0.852s
user 0m0.000s
sys 0m0.030s
As you can see, it takes 15 times longer on Windows. Extend that out to all the times we are calling psql in the update script, and it's no wonder that it takes 9 times longer to run the full script on Windows than on Linux.
It is well known that Postgres performance will never be as good on Windows as Linux because of the one-process-per-connection model and the lack of fork() on Windows, but that should be a bottleneck in the creation of the connection, not in the execution of the commands. (That the bottleneck is in the connection and not the query execution is evidenced by the fact that a single example command is consistently 15x slower, but the whole script with much larger queries being run is only 9-12x slower.)
Is there a way to make this faster for our Windows users? Is there some way to reuse an existing psql session and pipe additional files into it? Or is my only option to rewrite this in some other language and write my own database communication code that reads the files and pipes them to PostgreSQL myself?

Running the connection through PgBouncer instead of directly to Postgres makes a huge difference.
Using it, my script run on Windows is reduced to around 30 seconds.
It's still not quite as fast as on Linux, but I can live with "only" a 6x improvement.
real 0m33.232s
user 0m2.740s
sys 0m9.785s

Related

sending an ssh -t command to multiple systems simultaneously (without ansible)

Because of the nature of the script (done at work, on a work RHEL machine) I cannot show the code, but I can at least provide pseudocode to help with a starting point. Currently:
start loop
1) read in the first line of a host text file (then the next and such per
the loop) of a file and assign it to a variable (host name)
2) send ssh -t command to the host (which takes anywhere between 2 to 6
minutes to receive a response back)
3) log response to a text file (repeat loop with new host from read in
text file)
end loop
Currently I have to run this script over night because of how many systems this script hits.
I want to be able to achieve the same goal and get the response from the command in that file per host, but I want the command to be sent out at the same time so that it takes anywhere between 2 to 6 minutes all together.
But because this is for work, I am not allowed to install ansible on the system; would there be another way to achieve this goal? If so please provide some areas or point me in the right direction.
With GNU Parallel:
parallel -j0 --slf hosts.txt --nonall mycommand > out.txt
But maybe you want a bit more info:
parallel -j0 --slf hosts.txt --joblog my.log --tag --nonall mycommand > out.txt
I did this using sh years ago using something like:
while true
do
if [ numberOfFileinSomeDir -lt N ]
then
(touch SomeDir/hostname; ssh hostname ... > someotherDir/hostname.txt ; rm SomeDir/hostname) &
...
But this stops working after ~100 hosts. It sucks - don't do it. If less than about ~500 hosts pssh may be the easiest - maybe you can install in your home directory?
Google something like python parallel execute process multiple and someone's bound to have a script that will do what you need already.
More than ~500 hosts and you really need to start installing some tools as others have mentioned in the comments.

linux script to send me an email every time a log file changes

I am looking for a simple way to constantly monitor a log file, and send me an email notification every time thhis log file has changed (new lines have been added to it).
The system runs on a Raspberry Pi 2 (OS Raspbian /Debian Stretch) and the log monitors a GPIO python script running as daemon.
I need something very simple and lightweight, don't even care to have the text of the new log entry, because I know what it says, it is always the same. 24 lines of text at the end.
Also, the log.txt file gets recreated every day at midnight, so that might represent another issue.
I already have a working python script to send me a simple email via gmail (called it sendmail.py)
What I tried so far was creating and running the following bash script:
monitorlog.sh
#!/bin/bash
tail -F log.txt | python ./sendmail.py
The problem is that it just sends an email every time I execute it, but when the log actually changes, it just quits.
I am really new to linux so apologies if I missed something.
Cheers
You asked for simple:
#!/bin/bash
cur_line_count="$(wc -l myfile.txt)"
while true
do
new_line_count="$(wc -l myfile.txt)"
if [ "$cur_line_count" != "$new_line_count" ]
then
python ./sendmail.py
fi
cur_line_count="$new_line_count"
sleep 5
done
I've done this a bunch of different ways. If you run a cron job every minute that counts the number of lines (wc -l) compares that to a stored count (e.g. in /tmp/myfilecounter) and sends the emails when the numbers are different.
If you have inotify, there are more direct ways to get "woken up" when the file changes, e.g https://serverfault.com/a/780522/97447 or https://serverfault.com/search?q=inotifywait.
If you don't mind adding a package to the system, incron is a very convenient way to run a script whenever a file or directory is modified, and it looks like it's supported on raspbian (internally it uses inotify). https://www.linux.com/learn/how-use-incron-monitor-important-files-and-folders. Looks like it's as simple as:
sudo apt-get install incron
sudo vi /etc/incron.allow # Add your userid to this file (or just rm /etc/incron.allow to let everyone use incron)
incron -e # Add the following line to the "cron" file
/path/to/log.txt IN_MODIFY python ./sendmail.py
And you'd be done!

Timing a sourced bash script

Normally I simply call the time program when I want to profile a script or a command it runs.
But this time (no pun intended), I want to measure the time it takes to execute a sourced script, such as:
/usr/bin/time source myscript.sh
But I get the following error:
/usr/bin/time: cannot run source: No such file or directory
And sourcing it this way:
/usr/bin/time . myscript.sh
Simply gives me this error:
/usr/bin/time: cannot run .: Permission denied
I can call it in the following two ways:
/usr/bin/time ./myscript.sh
/usr/bin/time bash myscript.sh
But it's really important that I source my script. Anyone know how I can do this?
Additional Context:
We have many, many scripts which are somewhat complex and these scripts know how to source a specific 'set' of other 'client' scripts. We'll call these scripts 'bundles'.
bundle-a.sh
bundle-b.sh
...
bundle-z.sh
That's not exactly how they are named, but it serves the purpose of showing 26 of them in this example. Each bundle contain a lot of logic and each bundle invoke a specific set of client scripts.
There are hundreds of client scripts, some with a few lines of code and others that are really complex and take hours to execute.
client-000.sh
client-001.sh
...
client-300.sh
We also provide a script which serves as an API library for both our bundle scripts and our client scripts. Many, many functions in this API.
api.sh
So the bundle scripts source the API library and so does each client script. We have it this way so that a bundle can be called (which call a set of client scripts) or at times we only need to call a specific client script directly. It is implemented in a way that if the bundle is executed, the API library is only sourced once (and not again from the client script).
So all of this has worked well for a long time. Its just that now I'm trying to add the /usr/bin/time to each sourcing of our client scripts from the bundle scripts.
If you really, really need to get the system and user times of a sourced script, you can use strace from a different shell to watch the one you're timing in, trace calls to getrusage, and inspect the return values.
Shell 1 (where you will time the script)
$ echo $$
12345
Shell 2 (where you will trace)
$ strace -etrace=getrusage -v -p 12345
Process 12345 attached - interrupt to quit
Shell 1:
$ time : ; . myscript.sh ; time :
real 0m0.000s
(( time output and script output, then time output again)
Shell 2: will output something like
getrusage(RUSAGE_SELF, {ru_utime={0, 12000}, ru_stime={0, 16001}, ru_maxrss=2364, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=1551, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=1032, ru_nivcsw=3}) = 0
getrusage(RUSAGE_CHILDREN, {ru_utime={0, 0}, ru_stime={0, 4000}, ru_maxrss=1216, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=5143, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=57, ru_nivcsw=21}) = 0
getrusage(RUSAGE_SELF, {ru_utime={0, 448028}, ru_stime={0, 16001}, ru_maxrss=2364, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=1552, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=2141, ru_nivcsw=22}) = 0
getrusage(RUSAGE_CHILDREN, {ru_utime={0, 0}, ru_stime={0, 4000}, ru_maxrss=1216, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=5143, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=57, ru_nivcsw=21}) = 0
Notice how the values in the ru_utime structure element changed? The difference there is how much user CPU time the shell itself used. See man getrusage for the details on exactly what you are seeing. From there, it's just a automatically extracting those and finding the difference.
It's possibly just the lack of a ./ in your reference to myscript.sh (which I note you use in your working examples, but not elsewhere)
Have tested under Ubuntu (Trusty64), and Darwnin/BSD - just using the following works fine:
time source ./myscript.sh
FWIW, I used the following script to just confirm that the source command was actually executing correctly:
#!/usr/bin/env sh
V_FIRST=1
export V_SECOND=2
Execution under darwin:
$ time source ./test.sh ; set | grep -i V_
real 0m0.000s
user 0m0.000s
sys 0m0.000s
RBENV_SHELL=bash
V_FIRST=1
V_SECOND=2
Execution under Ubuntu:
$ time source ./test.sh ; set | grep -i V_
real 0m0.000s
user 0m0.000s
sys 0m0.000s
V_FIRST=1
V_SECOND=2

Why ftam service will start and return prompt from terminal but not from bash script?

I am starting ftam server (ft820.rc on CentOS 5) using bash version bash 3.0 and I am having an issue with starting it from the script, namely in the script I do
ssh -nq root#$ip /etc/init.d/ft820.rc start
and the script won't continue after this line, although when I do on the machine defined by $ip
/etc/init.d/ft820.rc start
I will get the prompt back just after the service is started.
This is the code for start in ft820.rc
SPOOLPATH=/usr/spool/vertel
BINPATH=/usr/bin/osi/ft820
CONFIGFILE=${SPOOLPATH}/ffs.cfg
# Set DBUSERID to any value at all. Just need to make sure it is non-null for
# lockclr to work properly.
DBUSERID=
export DBUSERID
# if startup requested then ...
if [ "$1" = "start" ]
then
mask=`umask`
umask 0000
# startup the lock manager
${BINPATH}/lockmgr -u 16
# update attribute database
${BINPATH}/fua ${CONFIGFILE} > /dev/null
# clear concurrency locks
${BINPATH}/finit -cy ${CONFIGFILE} >/dev/null
# startup filestore
${BINPATH}/ffs ${CONFIGFILE}
if [ $? = 0 ]
then
echo Vertel FT-820 Filestore running.
else
echo Error detected while starting Vertel FT-820 Filestore.
fi
umask $mask
I repost here (on request of #Patryk) what I put in the comments on the question:
"is it the same when doing the ssh... in the commandline? ie, can you indeed connect without entering a password, using the pair of private_local_key and the corresponding public_key that you previously inserted in the destination root#$ip:~/.ssh/authorized_keys file ? – Olivier Dulac 20 hours ago "
"you say that, at the commandline (and NOT in the script) you can ssh root#.... and it works without asking for your pwd ? (ie, it can then be run from a script?) – Olivier Dulac 20 hours ago "
" try the ssh without the '-n' and even without -nq at all : ssh root#$ip /etc/init.d/ft820.rc start (you could even add ssh -v , which will show you local (1:) and remote (2:) events in a very verbose way, helping in knowing where it gets stuck exactly) – Olivier Dulac 19 hours ago "
"also : before the "ssh..." line in the script, make another line with, for example: ssh root#ip "set ; pwd ; id ; whoami" and see if that works and shows the correct information. This may help be sure the ssh part is working. The "set" part will also show you the running shell (ex: if it contains BASH= , you're running bash. Otherwise SHELL=... should give a good hint (sometimes not correct) about which shell gets invoked) – Olivier Dulac 19 hours ago "
" please try without the '-n' (= run in background and wait, instead of just run and then quit). It it doesn't work, try adding -t -t -t (3 times) to the ssh, to force it to allocate a tty. But first, please drop the '-n'. – Olivier Dulac 18 hours ago "
Apparently what worked was to add the -t option to the ssh command. (you can go up to put '-t -t -t' to further force it to try to allocate the tty, depending on the situation)
I guess it's because the invoked command expected to be run within an interactive session, and so needed a "tty" to be the stdout
A possibility (but just a wild guess) : the invoked rc script outputs information, but in a buffered environment (ie, when not launched via your terminal), the calling script couldn't see enough lines to fill the buffer and start printing anything out (like when you do a "grep something | somethings else" in a buffered environment and ctrl+c before the buffer was big enough to display anything : you end up thinking no lines were foudn by the grep, whereas there was maybe a few lines already in the buffer). There is tons to be said about buffering, and I am just beginning to read about it all. forcing ssh to allocate a tty made the called command think it was outputting to a live terminal session, and that may have turned off the buffering and allowed the result to show. Maybe in the first case, it worked too, but you could never see the output?

Can Cron Jobs Use Gnome-Open?

I am running Ubuntu 11.10 (Unity interface) and I created a Bash script that uses 'gnome-open' to open a series of web pages I use every morning. When I manually execute the script in the Terminal, the bash script works just fine. Here's a sample of the script (it's all the same so I've shortened it):
#!/bin/bash
gnome-open 'https://docs.google.com';
gnome-open 'https://mail.google.com';
Since it seemed to be working well, I added a job to my crontab (mine, not root's) to execute every weekday at a specific time.
Here's the crontab entry:
30 10 * * 1,2,3,4,5 ~/bin/webcheck.sh
The problem is this error gets returned for every single 'gnome-open' command in the bash script:
GConf-WARNING **: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: No D-BUS daemon running
Error: no display specified
I did some searching to try and figure this out. The first thing I tried was relaunching the daemon using SIGHUP:
killall -s SIGHUP gconfd-2
That didn't work so I tried launching the dbus-daemon using this code from the manpage for dbus-launch:
## test for an existing bus daemon, just to be safe
if test -z "$DBUS_SESSION_BUS_ADDRESS" ; then
## if not found, launch a new one
eval `dbus-launch --sh-syntax --exit-with-session`
echo "D-Bus per-session daemon address is: $DBUS_SESSION_BUS_ADDRESS"
fi
But that didn't do anything.
I tried adding simply 'dbus-launch' at the top of my bash script and that didn't work either.
I also tried editing the crontab to include the path to Bash, because I saw that suggestion on another thread but that didn't work.
Any ideas on how I can get this up and running?
Here is how the problem was solved. It turns out the issue was primarily caused by Bash not having access to an X window session (or at least that's how I understood it). So my problem was solved by editing my crontab like so:
30 10 * * 1,2,3,4,5 export DISPLAY=:0 && ~/bin/webcheck.sh
The "export DISPLAY=:0" statement told cron which display to use. I found the answer on this archived Ubuntu forum after searching for "no display specified" or something like that:
http://ubuntuforums.org/archive/index.php/t-105250.html
So now, whenever I'm logged in, exactly at 10:30 my system will automatically launch a series of webpages that I need to look at every day. Saves me having to go through the arduous process of typing in my three-letter alias every time :)
Glad you asked!
It depends on when it is run.
If the Gnome GDM Greeter is live, you can use the DBUS session from the logon dialog, if you will. You can, e.g., use this to send notifications to the logon screen, if no-one is logged in:
function do_notification
{
for pid in $(pgrep gnome-session); do
unset COOKIE
COOKIE="$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$pid/environ|cut -d= -f2-)"
GNUSER="$(ps --no-heading -o uname $pid)"
echo "Notifying user $GNUSER (gnome-session $pid) with '$#'"
sudo -u "$GNUSER" DBUS_SESSION_BUS_ADDRESS="$COOKIE" /usr/bin/notify-send -c "From CRON:" "$#"
done
unset COOKIE
}
As you can see the above code simply runs the same command (notify-send) on all available gnome-sessions, when called like:
do_notification "I wanted to let you guys know"
You can probably pick this apart and put it to use for your own purposes.

Resources