summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2015-01-02 13:27:02 +0100
committerPaul Buetow <paul@buetow.org>2015-01-02 13:27:02 +0100
commit336c6c8a415603c772f97ccd63912d05209f3795 (patch)
tree1a0febb81031d77fa8bec857333cce0a6d87436e
initial1.0.0
-rw-r--r--Makefile51
-rw-r--r--README.pod217
-rw-r--r--debian/README7
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control15
-rw-r--r--debian/copyright30
-rw-r--r--debian/files1
-rw-r--r--debian/pingdomfetch.debhelper.log45
-rw-r--r--debian/pingdomfetch.manpages1
-rw-r--r--debian/pingdomfetch.substvars2
-rwxr-xr-xdebian/rules13
-rw-r--r--debian/source/format1
-rw-r--r--docs/pingdomfetch.1337
-rw-r--r--docs/pingdomfetch.pod217
-rw-r--r--docs/pingdomfetch.txt206
-rw-r--r--lib/PINGDOMFETCH/Config.pm301
-rw-r--r--lib/PINGDOMFETCH/DateHelper.pm179
-rw-r--r--lib/PINGDOMFETCH/Display.pm157
-rw-r--r--lib/PINGDOMFETCH/Notify.pm163
-rw-r--r--lib/PINGDOMFETCH/Pingdom.pm191
-rw-r--r--lib/PINGDOMFETCH/Pingdomfetch.pm245
-rw-r--r--lib/PINGDOMFETCH/Result.pm120
-rw-r--r--lib/PINGDOMFETCH/Service.pm105
-rw-r--r--lib/PINGDOMFETCH/TLS.pm166
-rw-r--r--lib/PINGDOMFETCH/Utils.pm72
-rwxr-xr-xpingdomfetch91
-rw-r--r--pingdomfetch.conf.sample84
28 files changed, 3023 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d7f7486
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,51 @@
+NAME=pingdomfetch
+all: version documentation perltidy
+version:
+ cut -d' ' -f2 debian/changelog | head -n 1 | sed 's/(//;s/)//' > .version
+perltidy:
+ find . -name \*.pm | xargs perltidy -b
+ perltidy -b $(NAME)
+ find . -name \*.bak -delete
+documentation:
+ pod2man --release="$(NAME) $$(cat .version)" \
+ --center="User Commands" ./docs/$(NAME).pod > ./docs/$(NAME).1
+ pod2text ./docs/$(NAME).pod > ./docs/$(NAME).txt
+ cp ./docs/${NAME}.pod ./README.pod
+install:
+ test ! -d $(DESTDIR)/usr/bin && mkdir -p $(DESTDIR)/usr/bin || exit 0
+ test ! -d $(DESTDIR)/var/run/pingdomfetch && mkdir -p $(DESTDIR)/var/run/pingdomfetch || exit 0
+ test ! -d $(DESTDIR)/usr/share/$(NAME)/examples && mkdir -p $(DESTDIR)/usr/share/$(NAME)/examples || exit 0
+ cp $(NAME) $(DESTDIR)/usr/bin
+ cp -r ./lib $(DESTDIR)/usr/share/$(NAME)/lib
+ cp ./.version $(DESTDIR)/usr/share/$(NAME)/version
+ cp ./pingdomfetch.conf.sample $(DESTDIR)/usr/share/$(NAME)/examples/pingdomfetch.conf.sample
+deinstall:
+ test ! -z "$(DESTDIR)" && test -f $(DESTDIR)/usr/bin/$(NAME) && rm $(DESTDIR)/usr/bin/$(NAME) || exit 0
+ test ! -z "$(DESTDIR)/usr/share/$(NAME)" && -d $(DESTDIR)/usr/share/$(NAME) && rm -r $(DESTDIR)/usr/share/$(NAME) || exit 0
+clean:
+ test -d $(DESTDIR) && rm -Rf $(DESTDIR)
+dch:
+ dch -i
+deb:
+ dpkg-buildpackage -uc -us
+dput: deb
+ bash -c "dput -u incoming-debrepo ../$(NAME)_$$(cat ./debian/pingdomfetch/usr/share/pingdomfetch/version)_amd64.changes"
+release: all dch deb dput
+ git commit -a -m 'New release'
+ bash -c "git tag $$(cat ./debian/pingdomfetch/usr/share/pingdomfetch/version)"
+ git push origin master
+ git push --tags
+clean-top:
+ rm ../$(NAME)_*.tar.gz
+ rm ../$(NAME)_*.dsc
+ rm ../$(NAME)_*.changes
+ rm ../$(NAME)_*.deb
+tmp-top:
+ mv ../$(NAME)_*.tar.gz /tmp
+ mv ../$(NAME)_*.dsc /tmp
+ mv ../$(NAME)_*.changes /tmp
+ mv ../$(NAME)_*.deb /tmp
+testrun:
+ ./pingdomfetch --all-tls --config pingdomfetch.conf.test
+testrun_verbose:
+ ./pingdomfetch --all-tls --config pingdomfetch.conf.test --verbose
diff --git a/README.pod b/README.pod
new file mode 100644
index 0000000..42e49db
--- /dev/null
+++ b/README.pod
@@ -0,0 +1,217 @@
+=head1 NAME
+
+pingdomfetch - A small and humble tool to fetch availability stats from Pingdom and notify via E-Mail
+
+=head1 SYNOPSIS
+
+pingdomfetch
+ [--all-services]
+ [--all-tls]
+ [--checkid <STRING>]
+ [--config <STRING>]
+ [--flatten <STRING>]
+ [--from <STRING>]
+ [--help]
+ [--list-services]
+ [--list-tls]
+ [--notify]
+ [--notify-dummy]
+ [--notify-info]
+ [--service <STRING>]
+ [--sort-reverse]
+ [--tls <STRING>]
+ [--to <STRING>]
+ [--verbose]
+ [--version]
+
+
+=head1 DESCRIPTION
+
+pingdomfetch is a tool to fetch availability stats from www.Pingdom.com and notifies the results via Email. You may use this script to extend it to do other things with the results as well. Pingdom already provides notification emailing.
+
+Pingdomfetch also knows about 'so called' top level services (one top level service consists of several services).
+
+=head1 CONFIG
+
+=head2 Possible locations
+
+Create a config at one of the following (or into several) location:
+
+ /etc/pingdomfetch.conf
+ ./pingdomfetch.conf
+ /etc/pingdomfetch.d/*.conf
+ ~/.pingdomfetch.conf
+ ~/.pingdomfetch.d/*.conf
+
+The last config file always overwrites the values configured in the previous config files. For this use the sample configuration file F</usr/share/pingdomfetch/examples/pingdomfetch.conf.sample>. Please read that sample configuration file carefully since it also describes all available config options.
+
+It makes sense to have one global config F</etc/pingdomfetch.conf> containing all general configurations and for each top level service a separate config in F</etc/pingdomfetch.d/TLSNAME.conf>.
+
+=head2 Top level services and services
+
+=head3 Configure a top level service:
+
+Each top level service consists of many services. Since Pingdom does not know about top level services but just about separate checks (which are separate services) pingdomfetch fetches the availability of all services and calculates an average availability of all services which belong to a top level service.
+
+For each top level service you monitor via Pingdom you must create checks at Pingdom (manually). The check names should be of the form of
+
+ PROCOCOL://FQDN
+
+For example:
+
+ http://paul.buetow.org
+
+ https://paul.buetow.org
+
+just in order to have it uniform to all the other Pingdom checks (there is no technical reason though).
+
+Afterwards create a new file F</etc/pingdomfetch.d/TLSNAME.conf> (for example F</etc/pingdomfetch.d/buetoworg_tls.conf>) with the following content:
+
+ [tls.TLSNAME]
+ PROCOCOL1://FQDN1
+ PROCOCOL2://FQDN2
+
+to be specific it should be like this:
+
+ [tls.buetoworg_paul]
+ http://paul.buetow.org
+ https://paul.buetow.org
+
+The buetoworg_tls will be used by pingdomfetch to identify the top level service (e.g. using --list-tls) and can be freely chosen.
+
+=head3 Fetch stats of the top level service:
+
+The command
+
+ pingdomfetch --tls buetoworg_tls
+
+fetches the availability of both services from Pingdom and calculates the average availability which is the tls availability and prints out the results to stdout.
+
+ pingdomfetch --from yesterday --to yesterday --flatten bod:eod --all-tls
+
+fetches the availability of yesteday of all configured tls. There are many other options available.
+
+=head3 Service options
+
+It is possible to configure special options for special services:
+
+ [tls.TLSNAME]
+ PROCOCOL1://FQDN1[=option1:value1[,option2:value2]
+ PROCOCOL2://FQDN2[=option1:value1[,option2:value2]
+
+The possible options are:
+
+=over
+
+=item weight
+
+If FQDN1 has twice as much traffic as FQDN2 it makes sense to increase its weight. The standard weight is 1. Its used to calculate the tls availability.
+
+=item warning
+
+This overwrites the global warning threshold. It makes sense to use to avoid staus mails just because one specific service is under the specified global warning threshold.
+
+=back
+
+to be specific it should be like this:
+
+ [tls.buetoworg_tls.4352844]
+ http://paul.buetow.org=weight:2,warning:98
+ https://paul.buetow.org=warning:100
+
+and means that the availability of plain http will count twice as much as much. A warning mail will be sent only if the availability is less than 98% for http or less than 100% for https.
+
+=head1 PINGDOMFETCH OPTIONS
+
+=over
+
+=item --all-services
+
+Fetch availability of all services.
+
+=item --all-tls
+
+Fetch availability of all top level services
+
+=item --checkid <NUMBER>
+
+Fetch availability of a specific check ID. The check ID is the Pingdom check ID.
+
+=item --config <STRING>
+
+Also read a specific config file.
+
+=item --flatten <STRING>
+
+Flatten the time interval to fetch availabilities for. E.g.:
+
+ --from yesterday --to yesterday --flatten bod:eod
+
+fetches the availability from begin of day (yesterday) until the end of the day (yesterday).
+
+=item --from <STRING>
+
+Set time interval start time to fetch availabilities for. All formats supported by Time::ParseDate can be used. See L<http://search.cpan.org/~muir/Time-modules-2003.0211/lib/Time/ParseDate.pm>.
+
+For example:
+
+ --from today
+
+ --from '03.02.2013 12:34:56'
+
+ --from 'last week'
+
+=item --help
+
+Print out a brief help.
+
+=item --list-services
+
+List all configured services.
+
+=item --list-tls
+
+List all configured top level services.
+
+=item --notify
+
+Write a mail to all addresses specified in notify.email.to if at least one service or top level service is in state warning or critical. The warning threshold in % is warning.if.avail.is.less, the critical threshold in % is critical.if.avail.is.less.
+
+=item --notify-dummy
+
+In conjunction with --notify or --notify-info don't actually send mails but print them to stdout.
+
+=item --notify-info
+
+Write a mail to all addresses specified in notify.info.mail.to regardless of warning and critical services and top level services.
+
+=item --service <STRING>
+
+Fetch availability of a specific service name. The service name can be taken from --list-services.
+
+=item --sort-reverse
+
+Reverse the availability output list. Affects only status mails and stdout.
+
+=item --tls <STRING>
+
+Fetch availability of a specific top level service. The top level service name can be taken from --list-tls.
+
+=item --to <STRING>
+
+Same as --from, but specifies the end time to fetch availabilities for.
+
+=item --verbose
+
+Turns on verbose mode. Enables some extra output to stdout.
+
+=item --version
+
+Prints out the current version of pingdomfetch.
+
+=back
+
+=head1 AUTHOR
+
+Paul Buetow - <paul@buetow.org>
+
diff --git a/debian/README b/debian/README
new file mode 100644
index 0000000..e741a32
--- /dev/null
+++ b/debian/README
@@ -0,0 +1,7 @@
+The Debian Package pingdomfetch
+----------------------------
+
+This is just a hobby project. Not sure if everything meets the debian
+policy though.
+
+ -- Paul Buetow <paul@buetow.org> Sun, 08 Apr 2012 15:23:53 +0200
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..7dfa797
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+pingdomfetch (1.0.0) wheezy; urgency=low
+
+ * Initial release
+
+ -- Paul Buetow <paul@buetow.org> Thu, 11 Dec 2014 16:24:50 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..45a4fb7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+8
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..8a71a22
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,15 @@
+Source: pingdomfetch
+Section: utils
+Priority: optional
+Maintainer: Paul Buetow <paul@buetoe.org
+Build-Depends: perl, perltidy
+Standards-Version: 3.9.2
+Homepage: http://pingdomfetch.buetow.org
+Vcs-Git: https://github.com/ratnanplan/pingdomfetch
+Vcs-Browser: https://github.com/rantanplan/pingdomfetch
+
+Package: pingdomfetch
+Architecture: all
+Depends: ${perl:Depends}, libwww-perl, libtime-modules-perl, libconfig-json-perl, libio-socket-ssl-perl, libmime-lite-perl, curl, libio-captureoutput-perl
+Description: Pingdom fetch utility
+ Fetches service availability from Pingdom and notifies via E-Mail
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..9353cd5
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,30 @@
+Format: http://dep.debian.net/deps/dep5
+Upstream-Name: pingdomfetch
+Source: http://pingdomfetch.buetow.org
+
+Files: *
+Copyright: 2012 Paul Buetow <paul@buetow.org>
+License: GPL-3.0+
+
+Files: debian/*
+Copyright: 2012 Paul Buetow <paul@buetow.org>
+License: GPL-3.0+
+
+License: GPL-3.0+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
+
+
diff --git a/debian/files b/debian/files
new file mode 100644
index 0000000..6625ca6
--- /dev/null
+++ b/debian/files
@@ -0,0 +1 @@
+pingdomfetch_1.0.0_all.deb utils optional
diff --git a/debian/pingdomfetch.debhelper.log b/debian/pingdomfetch.debhelper.log
new file mode 100644
index 0000000..545a50f
--- /dev/null
+++ b/debian/pingdomfetch.debhelper.log
@@ -0,0 +1,45 @@
+dh_auto_configure
+dh_auto_build
+dh_auto_test
+dh_prep
+dh_installdirs
+dh_auto_install
+dh_install
+dh_installdocs
+dh_installchangelogs
+dh_installexamples
+dh_installman
+dh_installcatalogs
+dh_installcron
+dh_installdebconf
+dh_installemacsen
+dh_installifupdown
+dh_installinfo
+dh_pysupport
+dh_installinit
+dh_installmenu
+dh_installmime
+dh_installmodules
+dh_installlogcheck
+dh_installlogrotate
+dh_installpam
+dh_installppp
+dh_installudev
+dh_installwm
+dh_installxfonts
+dh_installgsettings
+dh_bugfiles
+dh_ucf
+dh_lintian
+dh_gconf
+dh_icons
+dh_perl
+dh_usrlocal
+dh_link
+dh_compress
+dh_fixperms
+dh_installdeb
+dh_gencontrol
+dh_md5sums
+dh_builddeb
+dh_builddeb
diff --git a/debian/pingdomfetch.manpages b/debian/pingdomfetch.manpages
new file mode 100644
index 0000000..f52d634
--- /dev/null
+++ b/debian/pingdomfetch.manpages
@@ -0,0 +1 @@
+docs/pingdomfetch.1
diff --git a/debian/pingdomfetch.substvars b/debian/pingdomfetch.substvars
new file mode 100644
index 0000000..bcb0957
--- /dev/null
+++ b/debian/pingdomfetch.substvars
@@ -0,0 +1,2 @@
+perl:Depends=perl
+misc:Depends=
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..b760bee
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/docs/pingdomfetch.1 b/docs/pingdomfetch.1
new file mode 100644
index 0000000..fad010e
--- /dev/null
+++ b/docs/pingdomfetch.1
@@ -0,0 +1,337 @@
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.el \{\
+. de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "PINGDOMFETCH 1"
+.TH PINGDOMFETCH 1 "2015-01-02" "pingdomfetch " "User Commands"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+pingdomfetch \- A small and humble tool to fetch availability stats from Pingdom and notify via E\-Mail
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+pingdomfetch
+ [\-\-all\-services]
+ [\-\-all\-tls]
+ [\-\-checkid <\s-1STRING\s0>]
+ [\-\-config <\s-1STRING\s0>]
+ [\-\-flatten <\s-1STRING\s0>]
+ [\-\-from <\s-1STRING\s0>]
+ [\-\-help]
+ [\-\-list\-services]
+ [\-\-list\-tls]
+ [\-\-notify]
+ [\-\-notify\-dummy]
+ [\-\-notify\-info]
+ [\-\-service <\s-1STRING\s0>]
+ [\-\-sort\-reverse]
+ [\-\-tls <\s-1STRING\s0>]
+ [\-\-to <\s-1STRING\s0>]
+ [\-\-verbose]
+ [\-\-version]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+pingdomfetch is a tool to fetch availability stats from www.Pingdom.com and notifies the results via Email. You may use this script to extend it to do other things with the results as well. Pingdom already provides notification emailing.
+.PP
+Pingdomfetch also knows about 'so called' top level services (one top level service consists of several services).
+.SH "CONFIG"
+.IX Header "CONFIG"
+.SS "Possible locations"
+.IX Subsection "Possible locations"
+Create a config at one of the following (or into several) location:
+.PP
+.Vb 5
+\& /etc/pingdomfetch.conf
+\& ./pingdomfetch.conf
+\& /etc/pingdomfetch.d/*.conf
+\& ~/.pingdomfetch.conf
+\& ~/.pingdomfetch.d/*.conf
+.Ve
+.PP
+The last config file always overwrites the values configured in the previous config files. For this use the sample configuration file \fI/usr/share/pingdomfetch/examples/pingdomfetch.conf.sample\fR. Please read that sample configuration file carefully since it also describes all available config options.
+.PP
+It makes sense to have one global config \fI/etc/pingdomfetch.conf\fR containing all general configurations and for each top level service a separate config in \fI/etc/pingdomfetch.d/TLSNAME.conf\fR.
+.SS "Top level services and services"
+.IX Subsection "Top level services and services"
+\fIConfigure a top level service:\fR
+.IX Subsection "Configure a top level service:"
+.PP
+Each top level service consists of many services. Since Pingdom does not know about top level services but just about separate checks (which are separate services) pingdomfetch fetches the availability of all services and calculates an average availability of all services which belong to a top level service.
+.PP
+For each top level service you monitor via Pingdom you must create checks at Pingdom (manually). The check names should be of the form of
+.PP
+.Vb 1
+\& PROCOCOL://FQDN
+.Ve
+.PP
+For example:
+.PP
+.Vb 1
+\& http://paul.buetow.org
+\&
+\& https://paul.buetow.org
+.Ve
+.PP
+just in order to have it uniform to all the other Pingdom checks (there is no technical reason though).
+.PP
+Afterwards create a new file \fI/etc/pingdomfetch.d/TLSNAME.conf\fR (for example \fI/etc/pingdomfetch.d/buetoworg_tls.conf\fR) with the following content:
+.PP
+.Vb 3
+\& [tls.TLSNAME]
+\& PROCOCOL1://FQDN1
+\& PROCOCOL2://FQDN2
+.Ve
+.PP
+to be specific it should be like this:
+.PP
+.Vb 3
+\& [tls.buetoworg_paul]
+\& http://paul.buetow.org
+\& https://paul.buetow.org
+.Ve
+.PP
+The buetoworg_tls will be used by pingdomfetch to identify the top level service (e.g. using \-\-list\-tls) and can be freely chosen.
+.PP
+\fIFetch stats of the top level service:\fR
+.IX Subsection "Fetch stats of the top level service:"
+.PP
+The command
+.PP
+.Vb 1
+\& pingdomfetch \-\-tls buetoworg_tls
+.Ve
+.PP
+fetches the availability of both services from Pingdom and calculates the average availability which is the tls availability and prints out the results to stdout.
+.PP
+.Vb 1
+\& pingdomfetch \-\-from yesterday \-\-to yesterday \-\-flatten bod:eod \-\-all\-tls
+.Ve
+.PP
+fetches the availability of yesteday of all configured tls. There are many other options available.
+.PP
+\fIService options\fR
+.IX Subsection "Service options"
+.PP
+It is possible to configure special options for special services:
+.PP
+.Vb 3
+\& [tls.TLSNAME]
+\& PROCOCOL1://FQDN1[=option1:value1[,option2:value2]
+\& PROCOCOL2://FQDN2[=option1:value1[,option2:value2]
+.Ve
+.PP
+The possible options are:
+.IP "weight" 4
+.IX Item "weight"
+If \s-1FQDN1\s0 has twice as much traffic as \s-1FQDN2\s0 it makes sense to increase its weight. The standard weight is 1. Its used to calculate the tls availability.
+.IP "warning" 4
+.IX Item "warning"
+This overwrites the global warning threshold. It makes sense to use to avoid staus mails just because one specific service is under the specified global warning threshold.
+.PP
+to be specific it should be like this:
+.PP
+.Vb 3
+\& [tls.buetoworg_tls.4352844]
+\& http://paul.buetow.org=weight:2,warning:98
+\& https://paul.buetow.org=warning:100
+.Ve
+.PP
+and means that the availability of plain http will count twice as much as much. A warning mail will be sent only if the availability is less than 98% for http or less than 100% for https.
+.SH "PINGDOMFETCH OPTIONS"
+.IX Header "PINGDOMFETCH OPTIONS"
+.IP "\-\-all\-services" 4
+.IX Item "--all-services"
+Fetch availability of all services.
+.IP "\-\-all\-tls" 4
+.IX Item "--all-tls"
+Fetch availability of all top level services
+.IP "\-\-checkid <\s-1NUMBER\s0>" 4
+.IX Item "--checkid <NUMBER>"
+Fetch availability of a specific check \s-1ID\s0. The check \s-1ID\s0 is the Pingdom check \s-1ID\s0.
+.IP "\-\-config <\s-1STRING\s0>" 4
+.IX Item "--config <STRING>"
+Also read a specific config file.
+.IP "\-\-flatten <\s-1STRING\s0>" 4
+.IX Item "--flatten <STRING>"
+Flatten the time interval to fetch availabilities for. E.g.:
+.Sp
+.Vb 1
+\& \-\-from yesterday \-\-to yesterday \-\-flatten bod:eod
+.Ve
+.Sp
+fetches the availability from begin of day (yesterday) until the end of the day (yesterday).
+.IP "\-\-from <\s-1STRING\s0>" 4
+.IX Item "--from <STRING>"
+Set time interval start time to fetch availabilities for. All formats supported by Time::ParseDate can be used. See http://search.cpan.org/~muir/Time\-modules\-2003.0211/lib/Time/ParseDate.pm <http://search.cpan.org/~muir/Time-modules-2003.0211/lib/Time/ParseDate.pm>.
+.Sp
+For example:
+.Sp
+.Vb 1
+\& \-\-from today
+\&
+\& \-\-from \*(Aq03.02.2013 12:34:56\*(Aq
+\&
+\& \-\-from \*(Aqlast week\*(Aq
+.Ve
+.IP "\-\-help" 4
+.IX Item "--help"
+Print out a brief help.
+.IP "\-\-list\-services" 4
+.IX Item "--list-services"
+List all configured services.
+.IP "\-\-list\-tls" 4
+.IX Item "--list-tls"
+List all configured top level services.
+.IP "\-\-notify" 4
+.IX Item "--notify"
+Write a mail to all addresses specified in notify.email.to if at least one service or top level service is in state warning or critical. The warning threshold in % is warning.if.avail.is.less, the critical threshold in % is critical.if.avail.is.less.
+.IP "\-\-notify\-dummy" 4
+.IX Item "--notify-dummy"
+In conjunction with \-\-notify or \-\-notify\-info don't actually send mails but print them to stdout.
+.IP "\-\-notify\-info" 4
+.IX Item "--notify-info"
+Write a mail to all addresses specified in notify.info.mail.to regardless of warning and critical services and top level services.
+.IP "\-\-service <\s-1STRING\s0>" 4
+.IX Item "--service <STRING>"
+Fetch availability of a specific service name. The service name can be taken from \-\-list\-services.
+.IP "\-\-sort\-reverse" 4
+.IX Item "--sort-reverse"
+Reverse the availability output list. Affects only status mails and stdout.
+.IP "\-\-tls <\s-1STRING\s0>" 4
+.IX Item "--tls <STRING>"
+Fetch availability of a specific top level service. The top level service name can be taken from \-\-list\-tls.
+.IP "\-\-to <\s-1STRING\s0>" 4
+.IX Item "--to <STRING>"
+Same as \-\-from, but specifies the end time to fetch availabilities for.
+.IP "\-\-verbose" 4
+.IX Item "--verbose"
+Turns on verbose mode. Enables some extra output to stdout.
+.IP "\-\-version" 4
+.IX Item "--version"
+Prints out the current version of pingdomfetch.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Paul Buetow \- <paul@buetow.org>
diff --git a/docs/pingdomfetch.pod b/docs/pingdomfetch.pod
new file mode 100644
index 0000000..42e49db
--- /dev/null
+++ b/docs/pingdomfetch.pod
@@ -0,0 +1,217 @@
+=head1 NAME
+
+pingdomfetch - A small and humble tool to fetch availability stats from Pingdom and notify via E-Mail
+
+=head1 SYNOPSIS
+
+pingdomfetch
+ [--all-services]
+ [--all-tls]
+ [--checkid <STRING>]
+ [--config <STRING>]
+ [--flatten <STRING>]
+ [--from <STRING>]
+ [--help]
+ [--list-services]
+ [--list-tls]
+ [--notify]
+ [--notify-dummy]
+ [--notify-info]
+ [--service <STRING>]
+ [--sort-reverse]
+ [--tls <STRING>]
+ [--to <STRING>]
+ [--verbose]
+ [--version]
+
+
+=head1 DESCRIPTION
+
+pingdomfetch is a tool to fetch availability stats from www.Pingdom.com and notifies the results via Email. You may use this script to extend it to do other things with the results as well. Pingdom already provides notification emailing.
+
+Pingdomfetch also knows about 'so called' top level services (one top level service consists of several services).
+
+=head1 CONFIG
+
+=head2 Possible locations
+
+Create a config at one of the following (or into several) location:
+
+ /etc/pingdomfetch.conf
+ ./pingdomfetch.conf
+ /etc/pingdomfetch.d/*.conf
+ ~/.pingdomfetch.conf
+ ~/.pingdomfetch.d/*.conf
+
+The last config file always overwrites the values configured in the previous config files. For this use the sample configuration file F</usr/share/pingdomfetch/examples/pingdomfetch.conf.sample>. Please read that sample configuration file carefully since it also describes all available config options.
+
+It makes sense to have one global config F</etc/pingdomfetch.conf> containing all general configurations and for each top level service a separate config in F</etc/pingdomfetch.d/TLSNAME.conf>.
+
+=head2 Top level services and services
+
+=head3 Configure a top level service:
+
+Each top level service consists of many services. Since Pingdom does not know about top level services but just about separate checks (which are separate services) pingdomfetch fetches the availability of all services and calculates an average availability of all services which belong to a top level service.
+
+For each top level service you monitor via Pingdom you must create checks at Pingdom (manually). The check names should be of the form of
+
+ PROCOCOL://FQDN
+
+For example:
+
+ http://paul.buetow.org
+
+ https://paul.buetow.org
+
+just in order to have it uniform to all the other Pingdom checks (there is no technical reason though).
+
+Afterwards create a new file F</etc/pingdomfetch.d/TLSNAME.conf> (for example F</etc/pingdomfetch.d/buetoworg_tls.conf>) with the following content:
+
+ [tls.TLSNAME]
+ PROCOCOL1://FQDN1
+ PROCOCOL2://FQDN2
+
+to be specific it should be like this:
+
+ [tls.buetoworg_paul]
+ http://paul.buetow.org
+ https://paul.buetow.org
+
+The buetoworg_tls will be used by pingdomfetch to identify the top level service (e.g. using --list-tls) and can be freely chosen.
+
+=head3 Fetch stats of the top level service:
+
+The command
+
+ pingdomfetch --tls buetoworg_tls
+
+fetches the availability of both services from Pingdom and calculates the average availability which is the tls availability and prints out the results to stdout.
+
+ pingdomfetch --from yesterday --to yesterday --flatten bod:eod --all-tls
+
+fetches the availability of yesteday of all configured tls. There are many other options available.
+
+=head3 Service options
+
+It is possible to configure special options for special services:
+
+ [tls.TLSNAME]
+ PROCOCOL1://FQDN1[=option1:value1[,option2:value2]
+ PROCOCOL2://FQDN2[=option1:value1[,option2:value2]
+
+The possible options are:
+
+=over
+
+=item weight
+
+If FQDN1 has twice as much traffic as FQDN2 it makes sense to increase its weight. The standard weight is 1. Its used to calculate the tls availability.
+
+=item warning
+
+This overwrites the global warning threshold. It makes sense to use to avoid staus mails just because one specific service is under the specified global warning threshold.
+
+=back
+
+to be specific it should be like this:
+
+ [tls.buetoworg_tls.4352844]
+ http://paul.buetow.org=weight:2,warning:98
+ https://paul.buetow.org=warning:100
+
+and means that the availability of plain http will count twice as much as much. A warning mail will be sent only if the availability is less than 98% for http or less than 100% for https.
+
+=head1 PINGDOMFETCH OPTIONS
+
+=over
+
+=item --all-services
+
+Fetch availability of all services.
+
+=item --all-tls
+
+Fetch availability of all top level services
+
+=item --checkid <NUMBER>
+
+Fetch availability of a specific check ID. The check ID is the Pingdom check ID.
+
+=item --config <STRING>
+
+Also read a specific config file.
+
+=item --flatten <STRING>
+
+Flatten the time interval to fetch availabilities for. E.g.:
+
+ --from yesterday --to yesterday --flatten bod:eod
+
+fetches the availability from begin of day (yesterday) until the end of the day (yesterday).
+
+=item --from <STRING>
+
+Set time interval start time to fetch availabilities for. All formats supported by Time::ParseDate can be used. See L<http://search.cpan.org/~muir/Time-modules-2003.0211/lib/Time/ParseDate.pm>.
+
+For example:
+
+ --from today
+
+ --from '03.02.2013 12:34:56'
+
+ --from 'last week'
+
+=item --help
+
+Print out a brief help.
+
+=item --list-services
+
+List all configured services.
+
+=item --list-tls
+
+List all configured top level services.
+
+=item --notify
+
+Write a mail to all addresses specified in notify.email.to if at least one service or top level service is in state warning or critical. The warning threshold in % is warning.if.avail.is.less, the critical threshold in % is critical.if.avail.is.less.
+
+=item --notify-dummy
+
+In conjunction with --notify or --notify-info don't actually send mails but print them to stdout.
+
+=item --notify-info
+
+Write a mail to all addresses specified in notify.info.mail.to regardless of warning and critical services and top level services.
+
+=item --service <STRING>
+
+Fetch availability of a specific service name. The service name can be taken from --list-services.
+
+=item --sort-reverse
+
+Reverse the availability output list. Affects only status mails and stdout.
+
+=item --tls <STRING>
+
+Fetch availability of a specific top level service. The top level service name can be taken from --list-tls.
+
+=item --to <STRING>
+
+Same as --from, but specifies the end time to fetch availabilities for.
+
+=item --verbose
+
+Turns on verbose mode. Enables some extra output to stdout.
+
+=item --version
+
+Prints out the current version of pingdomfetch.
+
+=back
+
+=head1 AUTHOR
+
+Paul Buetow - <paul@buetow.org>
+
diff --git a/docs/pingdomfetch.txt b/docs/pingdomfetch.txt
new file mode 100644
index 0000000..9b65153
--- /dev/null
+++ b/docs/pingdomfetch.txt
@@ -0,0 +1,206 @@
+NAME
+ pingdomfetch - A small and humble tool to fetch availability stats from
+ Pingdom and notify via E-Mail
+
+SYNOPSIS
+ pingdomfetch [--all-services] [--all-tls] [--checkid <STRING>] [--config
+ <STRING>] [--flatten <STRING>] [--from <STRING>] [--help]
+ [--list-services] [--list-tls] [--notify] [--notify-dummy]
+ [--notify-info] [--service <STRING>] [--sort-reverse] [--tls <STRING>]
+ [--to <STRING>] [--verbose] [--version]
+
+DESCRIPTION
+ pingdomfetch is a tool to fetch availability stats from www.Pingdom.com
+ and notifies the results via Email. You may use this script to extend it
+ to do other things with the results as well. Pingdom already provides
+ notification emailing.
+
+ Pingdomfetch also knows about 'so called' top level services (one top
+ level service consists of several services).
+
+CONFIG
+ Possible locations
+ Create a config at one of the following (or into several) location:
+
+ /etc/pingdomfetch.conf
+ ./pingdomfetch.conf
+ /etc/pingdomfetch.d/*.conf
+ ~/.pingdomfetch.conf
+ ~/.pingdomfetch.d/*.conf
+
+ The last config file always overwrites the values configured in the
+ previous config files. For this use the sample configuration file
+ /usr/share/pingdomfetch/examples/pingdomfetch.conf.sample. Please read
+ that sample configuration file carefully since it also describes all
+ available config options.
+
+ It makes sense to have one global config /etc/pingdomfetch.conf
+ containing all general configurations and for each top level service a
+ separate config in /etc/pingdomfetch.d/TLSNAME.conf.
+
+ Top level services and services
+ Configure a top level service:
+ Each top level service consists of many services. Since Pingdom does not
+ know about top level services but just about separate checks (which are
+ separate services) pingdomfetch fetches the availability of all services
+ and calculates an average availability of all services which belong to a
+ top level service.
+
+ For each top level service you monitor via Pingdom you must create
+ checks at Pingdom (manually). The check names should be of the form of
+
+ PROCOCOL://FQDN
+
+ For example:
+
+ http://paul.buetow.org
+
+ https://paul.buetow.org
+
+ just in order to have it uniform to all the other Pingdom checks (there
+ is no technical reason though).
+
+ Afterwards create a new file /etc/pingdomfetch.d/TLSNAME.conf (for
+ example /etc/pingdomfetch.d/buetoworg_tls.conf) with the following
+ content:
+
+ [tls.TLSNAME]
+ PROCOCOL1://FQDN1
+ PROCOCOL2://FQDN2
+
+ to be specific it should be like this:
+
+ [tls.buetoworg_paul]
+ http://paul.buetow.org
+ https://paul.buetow.org
+
+ The buetoworg_tls will be used by pingdomfetch to identify the top level
+ service (e.g. using --list-tls) and can be freely chosen.
+
+ Fetch stats of the top level service:
+ The command
+
+ pingdomfetch --tls buetoworg_tls
+
+ fetches the availability of both services from Pingdom and calculates
+ the average availability which is the tls availability and prints out
+ the results to stdout.
+
+ pingdomfetch --from yesterday --to yesterday --flatten bod:eod --all-tls
+
+ fetches the availability of yesteday of all configured tls. There are
+ many other options available.
+
+ Service options
+ It is possible to configure special options for special services:
+
+ [tls.TLSNAME]
+ PROCOCOL1://FQDN1[=option1:value1[,option2:value2]
+ PROCOCOL2://FQDN2[=option1:value1[,option2:value2]
+
+ The possible options are:
+
+ weight
+ If FQDN1 has twice as much traffic as FQDN2 it makes sense to
+ increase its weight. The standard weight is 1. Its used to calculate
+ the tls availability.
+
+ warning
+ This overwrites the global warning threshold. It makes sense to use
+ to avoid staus mails just because one specific service is under the
+ specified global warning threshold.
+
+ to be specific it should be like this:
+
+ [tls.buetoworg_tls.4352844]
+ http://paul.buetow.org=weight:2,warning:98
+ https://paul.buetow.org=warning:100
+
+ and means that the availability of plain http will count twice as much
+ as much. A warning mail will be sent only if the availability is less
+ than 98% for http or less than 100% for https.
+
+PINGDOMFETCH OPTIONS
+ --all-services
+ Fetch availability of all services.
+
+ --all-tls
+ Fetch availability of all top level services
+
+ --checkid <NUMBER>
+ Fetch availability of a specific check ID. The check ID is the
+ Pingdom check ID.
+
+ --config <STRING>
+ Also read a specific config file.
+
+ --flatten <STRING>
+ Flatten the time interval to fetch availabilities for. E.g.:
+
+ --from yesterday --to yesterday --flatten bod:eod
+
+ fetches the availability from begin of day (yesterday) until the end
+ of the day (yesterday).
+
+ --from <STRING>
+ Set time interval start time to fetch availabilities for. All
+ formats supported by Time::ParseDate can be used. See
+ <http://search.cpan.org/~muir/Time-modules-2003.0211/lib/Time/ParseD
+ ate.pm>.
+
+ For example:
+
+ --from today
+
+ --from '03.02.2013 12:34:56'
+
+ --from 'last week'
+
+ --help
+ Print out a brief help.
+
+ --list-services
+ List all configured services.
+
+ --list-tls
+ List all configured top level services.
+
+ --notify
+ Write a mail to all addresses specified in notify.email.to if at
+ least one service or top level service is in state warning or
+ critical. The warning threshold in % is warning.if.avail.is.less,
+ the critical threshold in % is critical.if.avail.is.less.
+
+ --notify-dummy
+ In conjunction with --notify or --notify-info don't actually send
+ mails but print them to stdout.
+
+ --notify-info
+ Write a mail to all addresses specified in notify.info.mail.to
+ regardless of warning and critical services and top level services.
+
+ --service <STRING>
+ Fetch availability of a specific service name. The service name can
+ be taken from --list-services.
+
+ --sort-reverse
+ Reverse the availability output list. Affects only status mails and
+ stdout.
+
+ --tls <STRING>
+ Fetch availability of a specific top level service. The top level
+ service name can be taken from --list-tls.
+
+ --to <STRING>
+ Same as --from, but specifies the end time to fetch availabilities
+ for.
+
+ --verbose
+ Turns on verbose mode. Enables some extra output to stdout.
+
+ --version
+ Prints out the current version of pingdomfetch.
+
+AUTHOR
+ Paul Buetow - <paul@buetow.org>
+
diff --git a/lib/PINGDOMFETCH/Config.pm b/lib/PINGDOMFETCH/Config.pm
new file mode 100644
index 0000000..1d1e4eb
--- /dev/null
+++ b/lib/PINGDOMFETCH/Config.pm
@@ -0,0 +1,301 @@
+package PINGDOMFETCH::Config;
+
+use strict;
+use warnings;
+
+use IO::File;
+
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Notify;
+use PINGDOMFETCH::Service;
+use PINGDOMFETCH::TLS;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, $opts ) = @_;
+
+ my %vals = map {
+ my $k = $_;
+ $k =~ s/_/\./g;
+ "arg.$k" => $opts->{$_}{val};
+
+ } keys %$opts;
+
+ my $self = bless \%vals, $class;
+
+ $self->SUPER::init();
+
+ $self->read_config('/etc/pingdomfetch.conf');
+ $self->read_config('pingdomfetch.conf');
+ $self->read_config($_) for sort glob("/etc/pingdomfetch.d/*.conf");
+
+ $self->read_config("$ENV{HOME}/.pingdomfetch.conf");
+ $self->read_config($_) for sort glob("$ENV{HOME}/.pingdomfetch.d/*.conf");
+
+ $self->read_config( $self->{'arg.config'} );
+
+ unless ( exists $self->{config_was_read} ) {
+ $self->warning("No config file found. Use --verbose or --help");
+ }
+
+ $self->{notify} = PINGDOMFETCH::Notify->new( config => $self );
+ $self->{has_warnings} = 0;
+
+ return $self;
+}
+
+sub read_config {
+ my ( $self, $config_file ) = @_;
+
+ return undef unless -f $config_file;
+
+ my $fh = new IO::File( $config_file, 'r' );
+ $self->error("Could not open file $config_file") unless defined $fh;
+
+ $self->verbose("Reading config $config_file");
+
+ my $section = undef;
+ my $tls = exists $self->{tls} && ref $self->{tls} ? $self->{tls} : {};
+
+ while ( my $line = $fh->getline() ) {
+
+ # Ignore comments
+ $line =~ s/(.*);.*/$1/;
+
+ if ( $line =~ /^\[(.*)\]/ ) {
+ $section = $1;
+ next;
+ }
+
+ next unless defined $section;
+
+ if ( $section eq 'pingdom'
+ or $section eq 'misc'
+ or $section eq 'notify' )
+ {
+
+ # Parse only matching lines
+ if ( $line =~ /^(.*)=(.*)/ ) {
+ my ( $key, $val ) = ( lc trim $1, trim $2);
+ $self->verbose("Reading conf value $key");
+ $self->set( $key, $val );
+ }
+
+ }
+ elsif ( $section =~ /^tls\.(.*)/ ) {
+ my ($tlsname) = ($1);
+
+ next if $line !~ /\w/;
+
+ my ( $servicename, $opts ) = split '=', trim($line);
+
+ $servicename = lc trim($servicename);
+ $opts = trim($opts) if defined $opts;
+
+ $tls->{$tlsname} = PINGDOMFETCH::TLS->new(
+ name => $tlsname,
+ config => $self,
+ services => {},
+
+ ) unless exists $tls->{$tlsname};
+
+ my %opts;
+
+ if ( defined $opts ) {
+ for ( split ',', $opts ) {
+ my ( $k, $v ) = split ':';
+ $opts{$k} = $v;
+ }
+ }
+
+ $self->verbose("TLS $tlsname includes service $servicename");
+ $tls->{$tlsname}{services}{$servicename} = { opts => \%opts };
+ }
+ }
+
+ $fh->close();
+ $self->{tls} = $tls;
+
+ $self->{config_was_read} = 1;
+
+ return undef;
+}
+
+sub read_services {
+ my ( $self, $pingdom ) = @_;
+
+ $self->verbose('Reading all the services');
+
+ my $j = $pingdom->fetch_all_checks_json();
+ my $checks = $pingdom->safe_get( $j, 'checks' );
+
+ my %services = map {
+ my $name = lc $pingdom->safe_get( $_, 'name' );
+ my $checkid = $pingdom->safe_get( $_, 'id' );
+
+ $self->verbose("$name has check id $checkid");
+
+ $name => PINGDOMFETCH::Service->new(
+ config => $self,
+ name => $name,
+ checkid => $checkid,
+ resolution => $pingdom->safe_get( $_, 'resolution' ),
+ );
+
+ } @$checks;
+
+ $self->{services} = \%services;
+
+ return undef;
+}
+
+sub read_tls {
+ my ($self) = @_;
+
+ my $services = $self->{services};
+ my $tls = $self->{tls};
+
+ for my $tlsname ( keys %$tls ) {
+ my $tlsservices = $tls->{$tlsname}{services};
+ my @tlsservicenames = keys %$tlsservices;
+
+ $self->verbose("Validating services for TLS $tlsname");
+
+ my @delete;
+
+ for ( sort @tlsservicenames ) {
+ if ( exists $services->{$_} ) {
+ $services->{$_}{opts} = $tlsservices->{$_}{opts};
+ $tlsservices->{$_} = $services->{$_};
+
+ }
+ else {
+ $self->warning(
+ "Service $_ not configured in Pingdom, ignoring it");
+ push @delete, $_;
+ }
+ }
+
+ delete $tlsservices->{$_} for @delete;
+ }
+
+ return undef;
+}
+
+sub get {
+ my ( $self, $key ) = @_;
+ $key = lc $key;
+
+ $self->{$key} //= do {
+ my $key = uc $key;
+ $key =~ s/\./_/g;
+
+ exists $ENV{$key} ? $ENV{$key} : undef;
+ };
+
+ if ( not exists $self->{$key}
+ or not defined $self->{$key}
+ or $self->{$key} eq '' )
+ {
+ $self->error("$key not configured");
+ }
+
+ $self->verbose("Getting config value $key=$self->{$key}");
+ return $self->{$key};
+}
+
+sub has {
+ my ( $self, $key ) = @_;
+ $key = lc $key;
+
+ $self->{$key} //= do {
+ my $key = uc $key;
+ $key =~ s/\./_/g;
+
+ exists $ENV{$key} ? $ENV{$key} : undef;
+ };
+
+ if ( not exists $self->{$key}
+ or not defined $self->{$key}
+ or $self->{$key} eq '' )
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+sub bool {
+ my ( $self, $key ) = @_;
+
+ my $val = $self->get($key);
+
+ return $val != 0;
+}
+
+sub array {
+ my ( $self, $key ) = @_;
+
+ my $val = $self->get($key);
+
+ return map { trim $_ } split ',', $val;
+}
+
+sub set {
+ my ( $self, $key, $val ) = @_;
+ $key = lc $key;
+
+ $self->warning("$key already configured, overwriting it with its new value")
+ if exists $self->{$key};
+
+ return $self->{$key} = $val;
+}
+
+sub get_opts_str {
+ my ( $self, $opts ) = @_;
+
+ return '' unless defined $opts;
+
+ my $opts_str = '';
+
+ if (%$opts) {
+ $opts_str = ' [';
+ $opts_str .= join ',', map { "$_:$opts->{$_}" }
+ sort keys %$opts;
+ $opts_str .= ']';
+ }
+
+ return $opts_str;
+}
+
+sub print_services {
+ my ($self) = @_;
+
+ for ( sort keys %{ $self->{services} } ) {
+ my $opts_str = $self->get_opts_str( $self->{services}{$_}{opts} );
+ $self->info( $_ . $opts_str );
+ }
+
+ return 0;
+}
+
+sub print_tls {
+ my ($self) = @_;
+
+ for my $k ( sort keys %{ $self->{tls} } ) {
+ my $v = $self->{tls}{$k};
+ $self->info($k);
+ $self->inc();
+ for ( sort keys %{ $v->{services} } ) {
+ my $opts_str = $self->get_opts_str( $v->{services}{$_}{opts} );
+ $self->info( $_ . $opts_str );
+ }
+ $self->dec();
+ }
+
+ return 0;
+}
+
+1;
diff --git a/lib/PINGDOMFETCH/DateHelper.pm b/lib/PINGDOMFETCH/DateHelper.pm
new file mode 100644
index 0000000..43c2499
--- /dev/null
+++ b/lib/PINGDOMFETCH/DateHelper.pm
@@ -0,0 +1,179 @@
+package PINGDOMFETCH::DateHelper;
+
+use strict;
+use warnings;
+
+use Date::Format;
+use Time::ParseDate;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+use overload '""' => sub {
+ my ($self) = @_;
+ $self->full_str();
+};
+
+sub new ($;$) {
+ my ( $class, $config, $time ) = @_;
+
+ my $self = bless { config => $config }, $class;
+
+ $self->time($time);
+
+ return $self;
+}
+
+sub time {
+ my ( $self, $time ) = @_;
+
+ my $config = $self->{config};
+
+ $time = $self->{time} if not defined $time or $time eq '';
+
+ if ( not defined $time or $time eq '' ) {
+ $time //= time();
+ return $self->{time} = $time;
+
+ }
+ elsif ( $time !~ /^\d+$/ ) {
+ my $parsed = parsedate($time);
+ $self->error("Can't parse time '$time'") unless defined $parsed;
+ $time = $parsed;
+ }
+
+ return $self->{time} = $time;
+}
+
+sub flatten {
+ my ( $self, $flatten ) = @_;
+
+ if ( $flatten eq 'bod' ) {
+ return $self->begin_of_day();
+ }
+ elsif ( $flatten eq 'eod' ) {
+ return $self->end_of_day();
+ }
+ elsif ( $flatten eq 'boh' ) {
+ return $self->begin_of_hour();
+ }
+ elsif ( $flatten eq 'eoh' ) {
+ return $self->end_of_hour();
+ }
+ else {
+ $self->error("Can't parse flatten method '$flatten'");
+ }
+}
+
+sub localtime {
+ my ( $self, $time ) = @_;
+
+ return localtime( $self->time($time) );
+}
+
+sub prev_day {
+ my ( $self, $time ) = @_;
+
+ return $self->time( $self->time($time) - 86400 );
+}
+
+sub next_day {
+ my ( $self, $time ) = @_;
+
+ return $self->time( $self->time($time) + 86400 );
+}
+
+sub begin_of_day {
+ my ( $self, $time ) = @_;
+
+ my @localtime = $self->localtime($time);
+ my ( $sec, $min, $hour, @rest ) = @localtime;
+
+ return $self->time( $self->time() - $sec - 60 * ( $min + 60 * $hour ) );
+}
+
+sub end_of_day {
+ my ( $self, $time ) = @_;
+
+ my @localtime = $self->localtime($time);
+ my ( $sec, $min, $hour, @rest ) = @localtime;
+
+ return $self->time( $self->begin_of_day() + 86399 );
+}
+
+sub begin_of_hour {
+ my ( $self, $time ) = @_;
+
+ my @localtime = $self->localtime($time);
+ my ( $sec, $min, $hour, @rest ) = @localtime;
+
+ return $self->time( $self->time() - $sec - 60 * $min );
+}
+
+sub end_of_hour {
+ my ( $self, $time ) = @_;
+
+ return $self->time( $self->begin_of_hour() + 59 * ( 1 + 60 ) );
+}
+
+sub is_a_day {
+ my ($self) = @_;
+
+ return $self->time() == 86400;
+}
+
+sub is_begin_of_a_day {
+ my ($self) = @_;
+
+ my @localtime = $self->localtime( $self->time() );
+ my ( $sec, $min, $hour, @rest ) = @localtime;
+
+ return $sec == 0 and $min == 0 and $hour == 0;
+}
+
+sub is_in_future {
+ my ($self) = @_;
+
+ my $dh = PINGDOMFETCH::DateHelper->new( $self->{config} );
+
+ return $self->time() > $dh->time() ? 1 : 0;
+}
+
+sub days_until {
+ my ( $self, $dh ) = @_;
+
+ return ( $dh->time() - $self->time() ) / 86400;
+}
+
+sub day_str {
+ my ( $self, $time ) = @_;
+
+ my @localtime = $self->localtime($time);
+
+ #return strftime( "%D", @localtime );
+ return strftime( "%d.%m.%Y", @localtime );
+}
+
+sub full_str {
+ my ( $self, $time ) = @_;
+
+ my @localtime = $self->localtime($time);
+
+ #return strftime( "%c", @localtime );
+ return strftime( "%d.%m.%Y %H:%M:%S", @localtime );
+}
+
+sub print {
+ my ( $self, $time ) = @_;
+
+ $self->time($time);
+
+ say $self->full_str();
+
+ return undef;
+}
+
+1;
diff --git a/lib/PINGDOMFETCH/Display.pm b/lib/PINGDOMFETCH/Display.pm
new file mode 100644
index 0000000..3838a82
--- /dev/null
+++ b/lib/PINGDOMFETCH/Display.pm
@@ -0,0 +1,157 @@
+package PINGDOMFETCH::Display;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::Utils;
+
+our $INDENT = 0;
+our $VERBOSE = 0;
+
+use overload
+ '""' => sub { shift->indents(); },
+ '++' => sub { shift->inc(); },
+ '--' => sub { shift->dec(); };
+
+sub init {
+ my ($self) = @_;
+
+ $VERBOSE = $self->{'arg.verbose'} == 1;
+
+ return undef;
+}
+
+sub inc {
+ my ($self) = @_;
+
+ return ++$INDENT;
+}
+
+sub dec {
+ my ($self) = @_;
+
+ return --$INDENT;
+}
+
+sub indents {
+ my ($self) = @_;
+
+ return ' ' x $INDENT;
+}
+
+sub display {
+ my ( $self, $msging ) = @_;
+
+ print $msging;
+
+ return undef;
+}
+
+sub is_verbose {
+ my ($self) = @_;
+
+ return $VERBOSE == 1;
+}
+
+sub info_no_nl {
+ my ( $self, $msg ) = @_;
+
+ $self->display("$msg");
+
+ return undef;
+}
+
+sub info {
+ my ( $self, $msg, $notify ) = @_;
+
+ my $str = " $self $msg\n";
+
+ $self->display($str);
+ $notify->message_push($str) if defined $notify;
+
+ return undef;
+}
+
+sub nl {
+ my ( $self, $notify ) = @_;
+
+ $self->display("\n");
+ $notify->message_push("\n") if defined $notify;
+
+ return undef;
+}
+
+sub error {
+ my ( $self, $msg ) = @_;
+
+ $self->display("! ERROR: $self $msg\n");
+
+ exit 666;
+
+ return undef;
+}
+
+sub warning {
+ my ( $self, $msg, $notify ) = @_;
+
+ my $str = "! $self $msg\n";
+
+ $self->display($str);
+
+ if ( defined $notify ) {
+ $notify->message_push($str);
+ $notify->{warnings}++;
+ }
+
+ return undef;
+}
+
+sub critical {
+ my ( $self, $msg, $notify ) = @_;
+
+ my $str = "!! $self $msg\n";
+
+ $self->display($str);
+
+ if ( defined $notify ) {
+ $notify->message_push($str);
+ $notify->{criticals}++;
+ }
+
+ return undef;
+}
+
+sub dump {
+ my ( $self, $msg ) = @_;
+
+ $self->display( Dumper $msg );
+
+ return undef;
+}
+
+sub diedump {
+ my ( $self, $msg ) = @_;
+
+ die Dumper $msg;
+
+ return undef;
+}
+
+sub verbose {
+ my ( $self, $msg, $notify ) = @_;
+
+ if ( $self->is_verbose() ) {
+ my $str = " $self $msg\n";
+
+ $self->display($str);
+ $notify->message_push($str) if defined $notify;
+ }
+
+ return undef;
+}
+
+1;
+
diff --git a/lib/PINGDOMFETCH/Notify.pm b/lib/PINGDOMFETCH/Notify.pm
new file mode 100644
index 0000000..07301fd
--- /dev/null
+++ b/lib/PINGDOMFETCH/Notify.pm
@@ -0,0 +1,163 @@
+package PINGDOMFETCH::Notify;
+
+use strict;
+use warnings;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::DateHelper;
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Utils;
+
+use MIME::Lite;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, %vals ) = @_;
+
+ my $self = bless \%vals, $class;
+
+ my $config = $self->{config};
+
+ $self->{message} = [];
+ $self->{warnings} = 0;
+ $self->{criticals} = 0;
+
+ return $self;
+}
+
+sub message_push {
+ my ( $self, $message ) = @_;
+
+ push @{ $self->{message} }, $message;
+
+ return undef;
+}
+
+sub message_unshift {
+ my ( $self, $message ) = @_;
+
+ my $config = $self->{config};
+ unshift @{ $self->{message} }, $message;
+
+ return undef;
+}
+
+sub has_messages {
+ my ($self) = @_;
+
+ return @{ $self->{message} } > 0 ? 1 : 0;
+}
+
+sub has_warnings {
+ my ($self) = @_;
+
+ return $self->{warnings} > 0 ? 1 : 0;
+}
+
+sub has_criticals {
+ my ($self) = @_;
+
+ return $self->{criticals} > 0 ? 1 : 0;
+}
+
+sub info_notification_send {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+ $self->notification_send_to( $config->array('notify.info.email.to') );
+
+ return undef;
+}
+
+sub notification_send {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+ $self->notification_send_to( $config->array('notify.email.to') );
+
+ return undef;
+}
+
+sub notification_send_to {
+ my ( $self, @email_to ) = @_;
+
+ return if !$self->has_messages();
+
+ my $config = $self->{config};
+ my $from = $config->get('notify.email.sender');
+ my $warning_less = $config->get('warning.if.avail.is.less');
+ my $critical_less = $config->get('critical.if.avail.is.less');
+
+ my ( $dh_from, $dh_to ) = ( $config->{'dh_from'}, $config->{'dh_to'} );
+ my $message = join '', @{ $self->{message} };
+
+ my $subject = do {
+ if ( $self->has_criticals() ) {
+ '!! ';
+ }
+ elsif ( $self->has_warnings() ) {
+ '! ';
+ }
+ else {
+ ' ';
+ }
+ };
+
+ $subject .= 'Availability stats for ';
+
+ if ( $dh_from->is_begin_of_a_day()
+ and $dh_to->is_begin_of_a_day()
+ and 1 == $dh_from->days_until($dh_to) )
+ {
+ $subject .= $dh_from->day_str();
+ }
+ else {
+ $subject .= "'$dh_from' - '$dh_to'";
+ }
+
+ $message .= "Legend:\n";
+ $message .=
+"'!' means: TLS or Service Availability is less than $warning_less% (Exception: Threshold is non-standard)\n";
+ $message .= "'!!' means: TLS Availability is less than $critical_less%\n\n";
+ $message .=
+"Response times are not reasonable (collected from all over the world)!\n";
+
+ $message .= "\n" . get_version_full();
+
+ unless ( $config->bool('arg.notify-dummy') ) {
+ $self->send_mail( $from, $_, $subject, $message ) for @email_to;
+
+ }
+ else {
+ $self->info("Dummy-Email to stdout");
+
+ say $subject;
+ say "";
+ say $message;
+ }
+
+ $self->{messages} = [];
+
+ return undef;
+}
+
+sub send_mail {
+ my ( $self, $from, $to, $subject, $message ) = @_;
+
+ my $email = MIME::Lite->new(
+ From => $from,
+ To => $to,
+ Subject => $subject,
+ Type => 'TEXT',
+ Data => $message,
+ );
+
+ $self->info("Sending email '$subject' to '$to'");
+ $email->send();
+
+ return undef;
+}
+
+1;
+
diff --git a/lib/PINGDOMFETCH/Pingdom.pm b/lib/PINGDOMFETCH/Pingdom.pm
new file mode 100644
index 0000000..00f78ff
--- /dev/null
+++ b/lib/PINGDOMFETCH/Pingdom.pm
@@ -0,0 +1,191 @@
+package PINGDOMFETCH::Pingdom;
+
+use strict;
+use warnings;
+
+use JSON;
+use Data::Dumper;
+use IO::CaptureOutput qw(capture_exec);
+
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::Result;
+use PINGDOMFETCH::Service;
+use PINGDOMFETCH::DateHelper;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, $config ) = @_;
+
+ my $app_key = $config->get('pingdom.api.app.key');
+ my $host = $config->get('pingdom.api.host');
+ my $port = $config->get('pingdom.api.port');
+ my $protocol = $config->get('pingdom.api.protocol');
+
+ my $json = JSON->new()->allow_nonref();
+
+ #$ua->credentials( "$host:$port", $realm, $username, $password );
+
+ my $headers = {
+ 'App-key' => $app_key,
+ 'User-Agent' => 'pingdomfetch',
+ };
+
+ my $url_base = "$protocol://$host:$port";
+
+ my $self = bless {
+ config => $config,
+ json => $json,
+ url_base => $url_base,
+ headers => $headers,
+ }, $class;
+
+ return $self;
+}
+
+sub safe_get {
+ my ( $self, $j, @keys ) = @_;
+
+ my $pos = $j;
+
+ for (@keys) {
+ if ( exists $pos->{$_} ) {
+ $pos = $pos->{$_};
+
+ }
+ else {
+ local $" = '.';
+ $self->error(
+ "Could not get key '@keys' from JSON result: " . Dumper($j) );
+ }
+ }
+
+ return $pos;
+}
+
+sub fetch {
+ my ( $self, $url ) = @_;
+
+ my $config = $self->{config};
+ my $json = $self->{json};
+ my $headers = $self->{headers};
+
+ my $curl = $config->get('curl.path');
+ my $retry = $config->get('pingdom.api.failed.retry.after');
+ my $giveup = $config->get('pingdom.api.failed.giveup.after');
+
+ my $password = $config->get('pingdom.auth.password');
+ my $username = $config->get('pingdom.auth.username');
+
+ my $proxy = '';
+ $proxy = ' -p -x ' . $config->get('pingdom.proxy.address')
+ if $config->bool('pingdom.proxy.use');
+
+ my $cmd = "$curl '$url'$proxy --user '$username:$password'";
+ $cmd .= " --header '$_: $headers->{$_}'" for keys %$headers;
+
+ my ( $stdout, $stderr, $success, $exit_code );
+
+ for ( my $i = 0 ; $i < $giveup ; ++$i ) {
+ $self->verbose("Using URL $url");
+ $self->verbose("$cmd");
+ ( $stdout, $stderr, $success, $exit_code ) = capture_exec($cmd);
+
+ if ( $exit_code == 0 ) {
+ last;
+
+ }
+ else {
+ $self->warning( "Pingdom: stdout=" . $stdout );
+ $self->warning( "Pingdom: stderr=" . $stderr );
+ $self->warning( "Pingdom: success=" . $success );
+ $self->warning( "Pingdom: exit_code=" . $exit_code );
+ $self->warning("Retrying $url after $retry seconds");
+ sleep $retry;
+ }
+ }
+
+ return $json->decode($stdout);
+}
+
+sub fetch_avail_json {
+ my ( $self, $service, $from, $to ) = @_;
+
+ my $config = $self->{config};
+ my $checkid = $service->{checkid};
+ my $url_base = $self->{url_base};
+ my $action = $config->get('pingdom.api.average.action');
+ my $url = "$url_base/$action/$checkid?includeuptime=true&from=$from&to=$to";
+
+ $self->verbose(
+"Fetching availability for service $service->{name} (checkid $checkid) from Pingdom"
+ );
+
+ return $self->fetch($url);
+}
+
+sub fetch_avail_result {
+ my ( $self, $service ) = @_;
+
+ my $config = $self->{config};
+ my $dh_from = $config->{dh_from};
+ my $dh_to = $config->{dh_to};
+
+ if ( $dh_from->is_in_future() ) {
+ $self->verbose("'from' is in future");
+ $dh_from = PINGDOMFETCH::DateHelper->new( $self->{config} );
+ }
+
+ if ( $dh_to->is_in_future() ) {
+ $self->verbose("'to' is in future");
+ $dh_to = PINGDOMFETCH::DateHelper->new( $self->{config} );
+ }
+
+ my $j =
+ $self->fetch_avail_json( $service, $dh_from->time(), $dh_to->time() );
+
+ return PINGDOMFETCH::Result->new(
+ config => $config,
+ service => $service,
+ totalup => $self->safe_get( $j, qw(summary status totalup) ),
+ totalup => $self->safe_get( $j, qw(summary status totalup) ),
+ totaldown => $self->safe_get( $j, qw(summary status totaldown) ),
+ totalunknown => $self->safe_get( $j, qw(summary status totalunknown) ),
+ avgresponse =>
+ $self->safe_get( $j, qw(summary responsetime avgresponse) ),
+ );
+}
+
+sub fetch_all_checks_json {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ my $url_base = $self->{url_base};
+ my $action = $config->get('pingdom.api.all.checks.action');
+
+ my $url = "$url_base/$action";
+
+ $self->verbose("Fetching all checks from Pingdom");
+
+ return $self->fetch($url);
+}
+
+sub fetch_all_subscriptions_json {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ my $url_base = $self->{url_base};
+ my $action = $config->get('pingdom.api.all.report.subscriptions');
+
+ my $url = "$url_base/$action";
+
+ $self->verbose("Fetching all report subscriptions from Pingdom");
+
+ return $self->fetch($url);
+}
+
+1;
diff --git a/lib/PINGDOMFETCH/Pingdomfetch.pm b/lib/PINGDOMFETCH/Pingdomfetch.pm
new file mode 100644
index 0000000..3aa0046
--- /dev/null
+++ b/lib/PINGDOMFETCH/Pingdomfetch.pm
@@ -0,0 +1,245 @@
+package PINGDOMFETCH::Pingdomfetch;
+
+use strict;
+use warnings;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::DateHelper;
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Notify;
+use PINGDOMFETCH::Pingdom;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, $opts ) = @_;
+
+ my $config = PINGDOMFETCH::Config->new($opts);
+ my $pingdom = PINGDOMFETCH::Pingdom->new($config);
+
+ my $self = bless {
+ config => $config,
+ pingdom => $pingdom,
+ dots_counter => 0,
+ }, $class;
+
+ $self->init_from_to_interval();
+
+ return $self;
+}
+
+sub init_from_to_interval {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ # Yeah, Hash Slices are hellworks!
+ my ( $from, $to ) = @{$config}{qw(arg.from arg.to)};
+
+ my $dh_from = $config->{dh_from} =
+ PINGDOMFETCH::DateHelper->new( $config, $from );
+ my $dh_to = $config->{dh_to} =
+ PINGDOMFETCH::DateHelper->new( $config, $to );
+
+ $dh_from->begin_of_day() if $from eq '';
+
+ # Handle the --flatten switcht
+ my $flatten = $config->{'arg.flatten'};
+ my ( $flatten_from, $flatten_to ) = split ':', $flatten;
+
+ if ( defined $flatten_from ) {
+ $dh_from->flatten($flatten_from)
+ if $flatten_from ne '';
+
+ $dh_to->flatten($flatten_to)
+ if defined $flatten_to and $flatten_to ne '';
+ }
+
+ $self->error(
+"Interval '$dh_from' - '$dh_to' is negative or zero. 'from' must be < 'to'."
+ ) if $dh_from->time() >= $dh_to->time();
+
+ $config->{interval_is_in_future} = $dh_to->is_in_future();
+
+ $self->{dh_from} = $dh_from;
+ $self->{dh_to} = $dh_to;
+
+ return undef;
+}
+
+sub get_checkid_avail {
+ my ( $self, $checkid ) = @_;
+
+ my $config = $self->{config};
+ my $services = $config->{services};
+
+ while ( my ( $k, $v ) = each %$services ) {
+ if ( $v->{checkid} eq $checkid ) {
+ $self->verbose("Checkid $checkid belongs to service $k");
+ $self->get_all_services_avail( { $k => $v } );
+
+ return ($v);
+ }
+ }
+
+ $self->error("No such service with checkid '$checkid'");
+
+ return ();
+}
+
+sub get_service_avail {
+ my ( $self, $servicename ) = @_;
+
+ my $config = $self->{config};
+ my $services = $config->{services};
+
+ if ( exists $services->{$servicename} ) {
+ my $service = $services->{$servicename};
+ $self->get_all_services_avail( { $servicename => $service } );
+
+ return $service;
+ }
+
+ $self->error("No such service '$servicename'");
+
+ return ();
+}
+
+sub get_tls_avail {
+ my ( $self, $tlsname ) = @_;
+
+ my $config = $self->{config};
+
+ my @results;
+
+ if ( ref $config->{tls}{$tlsname} ) {
+ my $tls = $config->{tls}{$tlsname};
+ my $services = $tls->{services};
+
+ $self->get_all_services_avail($services);
+ $tls->acc();
+
+ return ($tls);
+
+ }
+ else {
+ $self->error("No such TLS '$tlsname'");
+ }
+
+ return ();
+}
+
+sub get_all_services_avail {
+ my ( $self, $services ) = @_;
+
+ my $pingdom = $self->{pingdom};
+ my $config = $self->{config};
+
+ my @return;
+
+ while ( my ( $k, $v ) = each %$services ) {
+ unless ( $config->is_verbose() ) {
+ $self->{dots_counter}++;
+
+ if ( $self->{dots_counter} == 3 ) {
+ print '...';
+
+ }
+ elsif ( $self->{dots_counter} > 3 ) {
+ print '.';
+ }
+ }
+ $v->{result} = $pingdom->fetch_avail_result($v);
+ push @return, $v;
+ }
+
+ return @return;
+}
+
+sub run {
+ my ($self) = @_;
+ my $retval = 0;
+
+ my $config = $self->{config};
+ my $pingdom = $self->{pingdom};
+
+ $config->read_services($pingdom);
+ $config->read_tls();
+
+ return $config->print_services() if $config->{'arg.list-services'};
+
+ return $config->print_tls() if $config->{'arg.list-tls'};
+
+ $self->info(
+ "Fetching stats of interval '$self->{dh_from}' - '$self->{dh_to}'");
+
+ my @data;
+
+ push @data, $self->get_checkid_avail( $config->{'arg.checkid'} )
+ if $config->{'arg.checkid'} ne '';
+
+ push @data, $self->get_service_avail( $config->{'arg.service'} )
+ if $config->{'arg.service'} ne '';
+
+ if ( $config->{'arg.tls'} ne '' ) {
+ if ( $config->{'arg.tls'} =~ /,/ ) {
+ push @data, $self->get_tls_avail($_)
+ for split ',', $config->{'arg.tls'};
+ }
+ else {
+ push @data, $self->get_tls_avail( $config->{'arg.tls'} );
+ }
+ }
+
+ push @data, $self->get_all_services_avail( $config->{services} )
+ if $config->{'arg.all-services'};
+
+ if ( $config->{'arg.all-tls'} ) {
+ push @data, $self->get_tls_avail($_) for sort keys %{ $config->{tls} };
+ }
+
+ if (@data) {
+ my @sorted_data =
+ sort { $b->{result}{avail_perc} <=> $a->{result}{avail_perc} } @data;
+ @sorted_data = reverse @sorted_data
+ if $config->bool('arg.sort-reverse');
+
+ if ( $self->is_verbose() ) {
+ $self->error("--notify* can not be used together with --verbose")
+ if $config->bool('arg.notify-info')
+ or $config->bool('arg.notify');
+
+ for (@sorted_data) {
+ $_->print_full();
+ $self->nl();
+ }
+
+ }
+ else {
+ print "\n" if $self->{dots_counter} > 2;
+
+ my $notify = $config->{notify};
+
+ for (@sorted_data) {
+ $_->print();
+ $self->nl($notify);
+ }
+
+ $notify->info_notification_send()
+ if $config->bool('arg.notify-info');
+
+ $notify->notification_send()
+ if $config->bool('arg.notify')
+ and ( $notify->has_warnings(), or $notify->has_criticals() );
+ }
+ }
+ else {
+ $self->warning(
+ "No results found. Use --all-tls for all TLS or --help for help!");
+ }
+
+ return 0;
+}
+
+1;
diff --git a/lib/PINGDOMFETCH/Result.pm b/lib/PINGDOMFETCH/Result.pm
new file mode 100644
index 0000000..582c354
--- /dev/null
+++ b/lib/PINGDOMFETCH/Result.pm
@@ -0,0 +1,120 @@
+package PINGDOMFETCH::Result;
+
+use strict;
+use warnings;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::DateHelper;
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, %vals ) = @_;
+
+ my $self = bless \%vals, $class;
+
+ $self->compute();
+
+ return $self;
+}
+
+sub acc {
+ my ( $self, $service, $acc ) = @_;
+
+ $acc->( $service, $self );
+
+ return undef;
+}
+
+sub compute {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ my ( $up, $down, $total ) = $self->compute_up_down();
+ $self->{avail_perc} = $self->compute_avail_perc( $up, $down, $total );
+
+ if ( $config->bool('interval_is_in_future') ) {
+ my $remaining = do {
+
+ # It's a Service result and not a TLS
+ if ( exists $self->{service} ) {
+
+ # Total seconds in the current interval
+ my $seconds =
+ $config->{dh_to}->time() - $config->{dh_from}->time();
+ $self->{remaining} = $seconds - $total;
+
+ }
+ else {
+
+ # It's a TLS result
+ $self->{remaining};
+ }
+ };
+
+ $self->{possible_avail_perc_best} =
+ $self->compute_avail_perc( $up + $remaining, $down );
+
+ $self->{possible_avail_perc_worst} =
+ $self->compute_avail_perc( $up, $down + $remaining );
+ }
+
+ return undef;
+}
+
+sub compute_up_down {
+ my ( $self, $totalup, $totalunknown, $totaldown ) = @_;
+
+ my $config = $self->{config};
+ my $unknown = $config->get('interpret.unknown.status.as.up');
+
+ $totalup = $self->{totalup} unless defined $totalup;
+ $totaldown = $self->{totaldown} unless defined $totaldown;
+ $totalunknown = $self->{totalunknown} unless defined $totalunknown;
+
+ my $total = $totalup + $totaldown + $totalunknown;
+
+ return $unknown =~ /true/i
+ ? ( $totalup + $totalunknown, $totaldown, $total )
+ : ( $totalup, $totaldown + $totalunknown, $total );
+}
+
+sub compute_avail_perc {
+ my ( $self, $up, $down ) = @_;
+
+ my $config = $self->{config};
+ my $zero = $config->get('interpret.zero.results.as.up');
+
+ my $total = $up + $down;
+
+ if ( $total > 0 ) {
+ return 100 * $up / $total;
+ }
+ else {
+ return $zero =~ /true/i ? 100 : 0;
+ }
+}
+
+sub print {
+ my ($self) = @_;
+
+ $self->print_full();
+
+ return undef;
+}
+
+sub print_full {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ $self->info("$_: $self->{$_}")
+ for sort grep { not ref $self->{$_} } keys %$self;
+
+ return undef;
+}
+
+1;
diff --git a/lib/PINGDOMFETCH/Service.pm b/lib/PINGDOMFETCH/Service.pm
new file mode 100644
index 0000000..98633f2
--- /dev/null
+++ b/lib/PINGDOMFETCH/Service.pm
@@ -0,0 +1,105 @@
+package PINGDOMFETCH::Service;
+
+use strict;
+use warnings;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Result;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, %vals ) = @_;
+
+ my $self = bless \%vals, $class;
+
+ return $self;
+}
+
+sub acc {
+ my ( $self, $acc ) = @_;
+
+ $self->{result}->acc( $self, $acc ) if exists $self->{result};
+
+ return undef;
+}
+
+sub print {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+ my $is_in_future = $config->bool('interval_is_in_future');
+ my $notify = $config->{notify};
+
+ my $avail_perc = do {
+ if ( exists $self->{result} ) {
+ }
+ else {
+ '';
+ }
+ };
+ my $str = do {
+ if ($is_in_future) {
+ sprintf(
+"Service: %03.3f%%; %s (Best: %03.3f%%; Worst: %03.3f%%; Avgresponse: %dms)",
+ $self->{result}{avail_perc},
+ $self->{name},
+ $self->{result}{possible_avail_perc_best},
+ $self->{result}{possible_avail_perc_worst},
+ $self->{result}{avgresponse}
+ );
+ }
+ else {
+ sprintf(
+ "Service: %03.3f%%; %s (Avgresponse: %dms)",
+ $self->{result}{avail_perc},
+ $self->{name}, $self->{result}{avgresponse}
+ );
+ }
+ };
+
+ my @opts;
+ my $opts = $self->{opts};
+ my $opts_str = $config->get_opts_str($opts);
+
+ my $warning_less =
+ exists $self->{opts}{warning}
+ ? $self->{opts}{warning}
+ : $config->get('warning.if.avail.is.less');
+
+ my $critical_less =
+ exists $self->{opts}{critical}
+ ? $self->{opts}{critical}
+ : $config->get('critical.if.avail.is.less');
+
+ if ( $self->{result}{avail_perc} < $critical_less ) {
+ $self->critical( $str . $opts_str, $notify );
+ }
+ elsif ( $self->{result}{avail_perc} < $warning_less ) {
+ $self->warning( $str . $opts_str, $notify );
+ }
+ else {
+ $self->info( $str . $opts_str, $notify );
+ }
+
+ return undef;
+}
+
+sub print_full {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ $self->info("Service: $self->{name}");
+
+ $self->inc();
+ $self->{result}->print_full() if exists $self->{result};
+ $self->dec();
+
+ return undef;
+}
+
+1;
+
diff --git a/lib/PINGDOMFETCH/TLS.pm b/lib/PINGDOMFETCH/TLS.pm
new file mode 100644
index 0000000..e5f1325
--- /dev/null
+++ b/lib/PINGDOMFETCH/TLS.pm
@@ -0,0 +1,166 @@
+package PINGDOMFETCH::TLS;
+
+use strict;
+use warnings;
+
+use PINGDOMFETCH::Config;
+use PINGDOMFETCH::Display;
+use PINGDOMFETCH::Result;
+use PINGDOMFETCH::Utils;
+
+our @ISA = ('PINGDOMFETCH::Display');
+
+sub new {
+ my ( $class, %vals ) = @_;
+
+ my $self = bless \%vals, $class;
+ $self->{is_critical} = 0;
+
+ return $self;
+}
+
+sub acc {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+ my $is_in_future = $config->bool('interval_is_in_future');
+
+ my $count = 0;
+ my $tls_result = PINGDOMFETCH::Result->new(
+ config => $config,
+ totaldown => 0,
+ totalup => 0,
+ totalunknown => 0,
+ avgresponse => 0,
+ remaining => 0,
+ );
+ $tls_result->{remaining} = 0 if $is_in_future;
+
+ my $acc = sub {
+ my ( $service, $result ) = @_;
+
+ $count++;
+ my $weight =
+ exists $service->{opts}{weight}
+ ? $service->{opts}{weight}
+ : 1;
+
+ $tls_result->{$_} += $result->{$_} * $weight
+ for qw(totaldown totalup totalunknown);
+
+ $tls_result->{$_} += $result->{$_} for qw(avgresponse);
+
+ $tls_result->{remaining} += $result->{remaining} * $weight
+ if $is_in_future;
+ };
+
+ if ( exists $self->{services} ) {
+ $self->{services}{$_}->acc($acc) for keys %{ $self->{services} };
+ }
+
+ if ( $count > 0 ) {
+ $tls_result->{avgresponse} /= $count;
+ $tls_result->compute();
+ $self->{result} = $tls_result;
+ }
+
+ $self->{is_critical} = 1
+ if $self->{result}{avail_perc} <
+ $config->get('critical.if.avail.is.less');
+
+ return undef;
+}
+
+sub print {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+ my $is_in_future = $config->bool('interval_is_in_future');
+ my $notify = $config->{notify};
+
+ my $str = do {
+ if ($is_in_future) {
+ sprintf(
+"TLS: %03.3f%%; %s (Best: %03.3f%%; Worst: %03.3f%%; Avgresponse: %dms)",
+ $self->{result}{avail_perc},
+ $self->{name},
+ $self->{result}{possible_avail_perc_best},
+ $self->{result}{possible_avail_perc_worst},
+ $self->{result}{avgresponse}
+ );
+ }
+ else {
+ sprintf(
+ "TLS: %03.3f%%; %s (Avgresponse: %dms)",
+ $self->{result}{avail_perc},
+ $self->{name}, $self->{result}{avgresponse}
+ );
+ }
+ };
+
+ if ( $self->{result}{avail_perc} <
+ $config->get('critical.if.avail.is.less') )
+ {
+ $self->critical( $str, $notify );
+
+ }
+ elsif (
+ $self->{result}{avail_perc} < $config->get('warning.if.avail.is.less') )
+ {
+ $self->warning( $str, $notify );
+
+ }
+ else {
+ $self->info( $str, $notify );
+ }
+
+ if ( exists $self->{services} ) {
+ $self->inc();
+
+ my @sorted_data =
+ sort { $b->{result}{avail_perc} <=> $a->{result}{avail_perc} }
+ values %{ $self->{services} };
+ @sorted_data = reverse @sorted_data
+ if $config->bool('arg.sort-reverse');
+
+ $_->print() for @sorted_data;
+ $self->dec();
+ }
+
+ return undef;
+}
+
+sub print_full {
+ my ($self) = @_;
+
+ my $config = $self->{config};
+
+ $self->info("TLS $self->{name}");
+ $self->inc();
+
+ if ( exists $self->{result} ) {
+ $self->{result}->print_full();
+ }
+
+ $self->info("$_: $self->{$_}")
+ for sort grep { not ref $self->{$_} and $_ ne 'name' } keys %$self;
+
+ if ( exists $self->{services} ) {
+ $self->inc();
+ while ( my ( $k, $v ) = each %{ $self->{services} } ) {
+ $v->print_full();
+ }
+ $self->dec();
+
+ }
+ else {
+ $self->warning("No services for this TLS");
+ }
+
+ $self->dec();
+
+ return undef;
+}
+
+1;
+
diff --git a/lib/PINGDOMFETCH/Utils.pm b/lib/PINGDOMFETCH/Utils.pm
new file mode 100644
index 0000000..f66792f
--- /dev/null
+++ b/lib/PINGDOMFETCH/Utils.pm
@@ -0,0 +1,72 @@
+package PINGDOMFETCH::Utils;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+use Exporter;
+
+use base 'Exporter';
+
+our @EXPORT = qw (
+ d
+ dumper
+ get_version
+ get_version_full
+ newline
+ notnull
+ null
+ remove_spaces
+ say
+ sum
+ trim
+);
+
+sub say (@) { print "$_\n" for @_; return undef }
+sub newline () { say ''; return undef }
+sub sum (@) { my $sum = 0; $sum += $_ for @_; return $sum }
+sub null ($) { defined $_[0] ? $_[0] : 0 }
+sub notnull ($) { $_[0] != 0 ? $_[0] : 1 }
+sub dumper (@) { die Dumper @_ }
+sub d (@) { dumper @_ }
+
+sub trim ($) {
+ my $trimit = shift;
+
+ $trimit =~ s/^[\s\t]+//;
+ $trimit =~ s/[\s\t]+$//;
+
+ return $trimit;
+}
+
+sub remove_spaces ($) {
+ my $str = shift;
+
+ $str =~ s/[\s\t]//g;
+
+ return $str;
+}
+
+sub get_version () {
+ my $versionfile = do {
+ if ( -f '.version' ) {
+ '.version';
+ }
+ else {
+ '/usr/share/pingdomfetch/version';
+ }
+ };
+
+ open my $fh, $versionfile or error("$!: $versionfile");
+ my $version = <$fh>;
+ close $fh;
+
+ chomp $version;
+ return $version;
+}
+
+sub get_version_full () {
+ return "This is Pingdomfetch Version " . get_version() . "\n";
+}
+
+1;
diff --git a/pingdomfetch b/pingdomfetch
new file mode 100755
index 0000000..2b7e6ed
--- /dev/null
+++ b/pingdomfetch
@@ -0,0 +1,91 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+
+$| = 1;
+my $lib;
+
+BEGIN {
+ if ( -d './lib/PINGDOMFETCH' ) {
+ $lib = './lib';
+
+ }
+ else {
+ $lib = '/usr/share/pingdomfetch/lib';
+ }
+}
+
+use lib $lib;
+
+use PINGDOMFETCH::Pingdomfetch;
+use PINGDOMFETCH::Utils;
+use PINGDOMFETCH::Config;
+
+sub synopsis ($) {
+ my ($opts) = @_;
+
+ my %tstr = (
+ '=s' => ' <STRING>',
+ '' => undef,
+ );
+
+ say "Synopsis: $0";
+
+ for ( sort keys %$opts ) {
+ my $tstr = $tstr{ $opts->{$_}{ty} };
+ $tstr //= '';
+
+ say " [--$_$tstr]";
+ }
+
+ say "";
+ say "Examples:";
+ say " $0 --config pingdomfetch.conf --checkid=710776";
+ say " $0 --service http://paul.buetow.org --from=02:12:12 --to=14:00:00";
+ say " $0 --config pingdomfetch.conf --all-tls --verbose";
+ say
+ " $0 --all-services --from='10.12.12 12:13:14' --to='10.12.12 20:00:00'";
+ say "";
+ say "Read the manual page for detailed descriptions";
+
+ return undef;
+}
+
+my %opts = (
+ 'all-services' => { ty => '', val => 0 },
+ 'all-tls' => { ty => '', val => 0 },
+ 'checkid' => { ty => '=s', val => '' },
+ 'config' => { ty => '=s', val => '' },
+ 'flatten' => { ty => '=s', val => '' },
+ 'from' => { ty => '=s', val => '' },
+ 'help' => { ty => '', val => 0 },
+ 'list-services' => { ty => '', val => 0 },
+ 'list-tls' => { ty => '', val => 0 },
+ 'notify-dummy' => { ty => '', val => 0 },
+ 'notify-info' => { ty => '', val => 0 },
+ 'notify' => { ty => '', val => 0 },
+ 'service' => { ty => '=s', val => '' },
+ 'sort-reverse' => { ty => '', val => 0 },
+ 'tls' => { ty => '=s', val => '' },
+ 'to' => { ty => '=s', val => '' },
+ 'verbose' => { ty => '', val => 0 },
+ 'version' => { ty => '', val => 0 },
+);
+
+my $result =
+ GetOptions( map { $_ . $opts{$_}{ty} => \$opts{$_}{val} } keys %opts );
+
+if ( $opts{help}{val} == 1 ) {
+ synopsis \%opts;
+ exit 0;
+}
+
+if ( $opts{version}{val} == 1 ) {
+ print get_version_full();
+ exit 0;
+}
+
+exit PINGDOMFETCH::Pingdomfetch->new( \%opts )->run();
diff --git a/pingdomfetch.conf.sample b/pingdomfetch.conf.sample
new file mode 100644
index 0000000..7ebdfea
--- /dev/null
+++ b/pingdomfetch.conf.sample
@@ -0,0 +1,84 @@
+; Comments start with semicolon
+
+; Misc options
+[misc]
+; If pingdom delivers unknown status interpret that as up
+interpret.unknown.status.as.up = true
+
+; If pingdom delivers zero results interpret that as up
+interpret.zero.results.as.up = true
+
+; and notify with '! ' in the mail and/or stdout.
+warning.if.avail.is.less = 100
+
+; And notify with '!!' in the mail and/or stdout.
+critical.if.avail.is.less = 10
+
+; Options for automatic notifications via email
+[notify]
+; The status mail sender address
+notify.email.sender = Pingdomfetch <pingdomfetch@mx.buetow.org>
+
+; Warnings and criticals are sent to those email addreses (comma separated)
+notify.email.to = pingdomfetch@mx.buetow.org
+
+; Infos (no warnings and no criticals) are sent to those email addreses
+; (comma separated)
+notify.info.email.to = pingdomfetch@mx.buetow.org
+
+; Pingdom stuff, used to pull results from Pingdom via JSON API
+[pingdom]
+; Pingdom API generic configurations
+pingdom.api.host = api.pingdom.com
+pingdom.api.port = 443
+pingdom.api.protocol = https
+
+; Pingdom API username
+pingdom.auth.username = mypingdomuser@example.com
+
+; Pingdom API SSL auth realm
+pingdom.auth.realm = Pingdom API
+
+; Pingdom API password
+pingdom.auth.password = secret
+
+; Leave this unless you know what you are doing
+pingdom.api.app.key = 7gh8edm89vtyk8s2nqzfdobrw9n72l3k
+pingdom.api.average.action = api/2.0/summary.average
+pingdom.api.all.checks.action = api/2.0/checks
+pingdom.api.all.report.subscriptions = api/2.0/reports.email
+
+; If the Pingdom API timeouts retry that every 3 seconds...
+pingdom.api.failed.retry.after = 3
+
+; ... and try that 10 times
+pingdom.api.failed.giveup.after = 10
+
+; Set to 1 if you want to use a proxy to reach the Pingdom API
+pingdom.proxy.use = 0
+
+; Proxy server to use to reach the Pingdom API
+pingdom.proxy.address = http://useproxy.buetow.invalid
+
+; TLS configurations. It makes sense to create a separate file inside of
+; /etc/pingdomfetch.d/TLSNAME.conf for reach top level service to configure.
+
+; The format is like this:
+; [tls.PINGDOMFETCHNAME(FreeToChoose)]
+; List
+; Of
+; Pingdom
+; Check-Names[=option1:value1[,option2:value2]]
+
+; A few examples:
+[tls.buetow]
+http://paul.buetow.org = weight:50
+https://dev.buetow.org = weight:50
+http://lists.buetow.org = weight:2,warning:98
+https://info.gmx.net = weight:2,warning:98
+http://info.web.de = weight:2,warning:98
+https://info.web.de = weight:2,warning:98
+
+[tls.ninja]
+http://paul.buetow.ninja
+https://paul.buetow.ninja