This commit is contained in:
root
2025-11-07 11:31:06 +00:00
commit 2859f93882
407 changed files with 99769 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
After installing the package you have to edit the files in
/etc/smokeping/config.d to set your preferences.
Note that the domain names are bogus to avoid all the people who
install the package DoSing the same servers, but at the same time an
fping probe in localhost is enabled, so you can check if smokeping
works for you.
You can also edit the file /etc/smokeping/basepage.html to suit your
needs.
A FastCGI configuration might be desirable for large sites. Simply add
this to the Apache configuration, in /etc/apache2/conf-available/smokeping.conf:
<Location /smokeping/smokeping.cgi>
SetHandler fcgid-script
</Location>
Please refer to the smokeping_config man page to see all the available
options.
-- Antoine Beaupré <anarcat@debian.org>, Sat, 12 May 2018 16:02:25 -0400

View File

@@ -0,0 +1,36 @@
```
____ _ ____ _
/ ___| _ __ ___ ___ | | _____| _ \(_)_ __ __ _
\___ \| '_ ` _ \ / _ \| |/ / _ \ |_) | | '_ \ / _` |
___) | | | | | | (_) | < __/ __/| | | | | (_| |
|____/|_| |_| |_|\___/|_|\_\___|_| |_|_| |_|\__, |
|___/
```
Original Authors: Tobias Oetiker <tobi of oetiker.ch> and Niko Tyni <ntyni with iki.fi>
[![Build Test](https://github.com/oetiker/SmokePing/actions/workflows/build-test.yaml/badge.svg)](https://github.com/oetiker/SmokePing/actions/workflows/build-test.yaml)
SmokePing is a latency logging and graphing and
alerting system. It consists of a daemon process which
organizes the latency measurements and a CGI which
presents the graphs.
SmokePing is ...
================
* extensible through plug-in modules
* easy to customize through a webtemplate and an extensive
configuration file.
* written in perl and should readily port to any unix system
* an RRDtool frontend
* able to deal with DYNAMIC IP addresses as used with
Cable and ADSL internet.
cheers
tobi

View File

@@ -0,0 +1,12 @@
The following issues need to be fixed:
* fast-cgi should be default
* review upstream changes since last release, esp. config changes
* do not prompt for modifications on the files:
- Slaves
- General
- basepage.html
- this is very hard. we would need to fix the checksum in
/var/lib/dpkg/status, which doesn't seem to be doable
programmatically. See dpkg/src/configure.c:336 to see how this
works better.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,156 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: SmokePing
Source: https://github.com/oetiker/SmokePing
Comment:
The upstream source tarball is repacked to drop vendored javascript libraries.
Files-Excluded:
htdocs/js/cropper/*
htdocs/js/prototype.js
htdocs/js/scriptaculous/*
Files: *
Copyright: 2001-2005 Tobias Oetiker <tobi@oetiker.ch>
License: GPL-2+
Files: bin/tSmoke
Copyright: 2003 by Dan McGinn-Combs. All right reserved.
License: GPL-2+
Files: lib/Smokeping/Examples.pm lib/Smokeping/RRDtools.pm
Copyright: 2005 by Niko Tyni.
License: GPL-2+
Files: lib/Smokeping/matchers/Avgratio.pm lib/Smokeping/matchers/Median.pm lib/Smokeping/matchers/base.pm
Copyright: 2004 by OETIKER+PARTNER AG. All rights reserved.
License: GPL-2+
Files: lib/Smokeping/matchers/CheckLatency.pm lib/Smokeping/matchers/CheckLoss.pm
Copyright: 2006 Dylan C Vanderhoof, Semaphore Corporation
License: GPL-2+
Files: lib/BER.pm lib/SNMP_Session.pm
Copyright: 1995-2008, Simon Leinen.
.
This program is free software; you can redistribute it under the
"Artistic License 2.0" included in this distribution
(file "Artistic").
License: Artistic-2.0
Comment: These files are not used in the Debian version. The mentioned
"Artistic" license file is not present in the source repository.
Files: debian/*
Copyright: Wed, 13 Feb 2002 23:11:07 +0100 Jose Carlos Garcia Sogo <jsogo@debian.org>
2018-2020 Gabriel Filion <gabster@lelutin.ca>
License: GPL-2+
Comment: The copyright mention was not updated throughout the years and there
are more authors and contributors that donated their time to maintaining those
files.
License: GPL-2+
On Debian systems, the full text of the GNU General Public
License version 2 can be found in the file
`/usr/share/common-licenses/GPL-2'.
License: Artistic-2.0
Copyright (c) 2000-2006, The Perl Foundation.
.
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
.
This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software.
.
You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement.
Definitions
.
"Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package.
.
"Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures.
.
"You" and "your" means any person who would like to copy, distribute, or modify the Package.
.
"Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version.
.
"Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization.
.
"Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees.
.
"Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder.
.
"Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder.
.
"Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future.
.
"Source" form means the source code, documentation source, and configuration files for the Package.
.
"Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form.
Permission for Use and Modification Without Distribution
.
(1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version.
Permissions for Redistribution of the Standard Version
.
(2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package.
.
(3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License.
Distribution of Modified Versions of the Package as Source
.
(4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following:
.
(a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version.
(b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version.
(c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under
(i) the Original License or
(ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed.
Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source
.
(5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license.
.
(6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version.
Aggregating or Linking the Package
.
(7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation.
.
(8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package.
Items That are Not Considered Part of a Modified Version
.
(9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license.
General Provisions
.
(10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.
.
(11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.
.
(12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.
.
(13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.
.
(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License: BSD-3-clause
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
.
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.
http://www.opensource.org/licenses/bsd-license.php
License: Expat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,241 @@
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
*** General ***
owner = Peter Random
contact = some@address.nowhere
mailhost = my.mail.host
sendmail = /usr/sbin/sendmail
# NOTE: do not put the Image Cache below cgi-bin
# since all files under cgi-bin will be executed ... this is not
# good for images.
imgcache = /usr/cache
imgurl = cache
datadir = /usr/data
piddir = /usr/var
cgiurl = http://some.url/smokeping.cgi
smokemail = /usr/etc/smokemail.dist
tmail = /usr/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
# disable this to revert to the old behaviour
# concurrentprobes = no
*** Alerts ***
to = alertee@address.somewhere
from = smokealert@company.xy
+someloss
type = loss
# in percent
pattern = >0%,*12*,>0%,*12*,>0%
comment = loss 3 times in a row
*** Database ***
step = 300
pings = 20
# consfn mrhb steps total
AVERAGE 0.5 1 28800
AVERAGE 0.5 12 9600
MIN 0.5 12 9600
MAX 0.5 12 9600
AVERAGE 0.5 144 2400
MAX 0.5 144 2400
MIN 0.5 144 2400
*** Presentation ***
template = /usr/etc/basepage.html.dist
htmltitle = yes
graphborders = no
# If enabled, treat all filter menu queries as literal strings instead of regex
literalsearch = no
+ charts
menu = Charts
title = The most interesting destinations
++ stddev
sorter = StdDev(entries=>4)
title = Top Standard Deviation
menu = Std Deviation
format = Standard Deviation %f
++ max
sorter = Max(entries=>5)
title = Top Max Roundtrip Time
menu = by Max
format = Max Roundtrip Time %f seconds
++ loss
sorter = Loss(entries=>5)
title = Top Packet Loss
menu = Loss
format = Packets Lost %f
++ median
sorter = Median(entries=>5)
title = Top Median Roundtrip Time
menu = by Median
format = Median RTT %f seconds
+ overview
width = 600
height = 50
range = 10h
+ detail
width = 600
height = 200
unison_tolerance = 2
"Last 3 Hours" 3h
"Last 30 Hours" 30h
"Last 10 Days" 10d
"Last 360 Days" 360d
#+ hierarchies
#++ owner
#title = Host Owner
#++ location
#title = Location
# (The actual example starts here.)
*** Probes ***
# This example shows most of the echoping-derived probes in action.
+ FPing
binary = /usr/bin/fping
# these expect to find echoping in /usr/bin
# if not, you'll have to specify the location separately for each probe
# + EchoPing # uses TCP or UDP echo (port 7)
# + EchoPingDiscard # uses TCP or UDP discard (port 9)
# + EchoPingChargen # uses TCP chargen (port 19)
+ EchoPingSmtp # SMTP (25/tcp) for mail servers
+ EchoPingHttps # HTTPS (443/tcp) for web servers
+ EchoPingHttp # HTTP (80/tcp) for web servers and caches
+ EchoPingIcp # ICP (3130/udp) for caches
# these need at least echoping 6 with the corresponding plugins
+ EchoPingDNS # DNS (53/udp or tcp) servers
+ EchoPingLDAP # LDAP (389/tcp) servers
+ EchoPingWhois # Whois (43/tcp) servers
*** Targets ***
# All the servers are pinged both with ICMP (the FPing probe)
# and their respective echoping probe. The proxy server, www-cache,
# is probed with both HTTP requests and ICP requests for the same
# URL.
# default probe
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ MyServers
menu = My Servers
title = My Servers
++ www-server
menu = www-server
title = Web Server (www-server) / ICMP
# probe = FPing propagated from top
host = www-server.example
+++ http
menu = http
title = Web Server (www-server) / HTTP
probe = EchoPingHttp
host = www-server.example
# default url is /
+++ https
menu = https
title = Web Server (www-server) / HTTPS
probe = EchoPingHttps
host = www-server.example
++ cache
menu = www-cache
title = Web Cache (www-cache) / ICMP
host = www-cache.example
+++ http
menu = http
title = www-cache / HTTP
probe = EchoPingHttp
host = www-cache.example
port = 8080 # use the squid port
url = http://www.somehost.example/
+++ icp
menu = icp
title = www-cache / ICP
probe = EchoPingIcp
host = www-cache.example
url = http://www.somehost.example/
++ mail
menu = mail-server
title = Mail Server (mail-server) / ICMP
host = mail-server.example
+++ smtp
menu = mail-server / SMTP
title = Mail Server (mail-server) / SMTP
probe = EchoPingSmtp
host = mail-server.example
++ ldap-server
menu = ldap-server
title = ldap-server / ICMP
host = ldap-server.example
+++ ldap
menu = ldap-server / LDAP
title = LDAP Server (ldap-server) / LDAP
probe = EchoPingLDAP
ldap_request = (objectclass=*)
host = ldap-server.example
++ name-server
menu = name-server
title = name-server / ICMP
host = name-server.example
+++ DNS
menu = name-server / DNS
title = DNS Server (name-server) / DNS
probe = EchoPingDNS
dns_request = name.example
host = name-server.example
++ whois-server
menu = whois-server
title = whois-server / ICMP
host = whois-server.example
+++ Whois
menu = whois-server / Whois
title = Whois Server (whois-server) / Whois
probe = EchoPingWhois
whois_request = domain.example
host = whois-server.example

View File

@@ -0,0 +1,185 @@
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
*** General ***
owner = Peter Random
contact = some@address.nowhere
mailhost = my.mail.host
sendmail = /usr/sbin/sendmail
# NOTE: do not put the Image Cache below cgi-bin
# since all files under cgi-bin will be executed ... this is not
# good for images.
imgcache = /usr/cache
imgurl = cache
datadir = /usr/data
piddir = /usr/var
cgiurl = http://some.url/smokeping.cgi
smokemail = /usr/etc/smokemail.dist
tmail = /usr/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
# disable this to revert to the old behaviour
# concurrentprobes = no
*** Alerts ***
to = alertee@address.somewhere
from = smokealert@company.xy
+someloss
type = loss
# in percent
pattern = >0%,*12*,>0%,*12*,>0%
comment = loss 3 times in a row
*** Database ***
step = 300
pings = 20
# consfn mrhb steps total
AVERAGE 0.5 1 28800
AVERAGE 0.5 12 9600
MIN 0.5 12 9600
MAX 0.5 12 9600
AVERAGE 0.5 144 2400
MAX 0.5 144 2400
MIN 0.5 144 2400
*** Presentation ***
template = /usr/etc/basepage.html.dist
htmltitle = yes
graphborders = no
# If enabled, treat all filter menu queries as literal strings instead of regex
literalsearch = no
+ charts
menu = Charts
title = The most interesting destinations
++ stddev
sorter = StdDev(entries=>4)
title = Top Standard Deviation
menu = Std Deviation
format = Standard Deviation %f
++ max
sorter = Max(entries=>5)
title = Top Max Roundtrip Time
menu = by Max
format = Max Roundtrip Time %f seconds
++ loss
sorter = Loss(entries=>5)
title = Top Packet Loss
menu = Loss
format = Packets Lost %f
++ median
sorter = Median(entries=>5)
title = Top Median Roundtrip Time
menu = by Median
format = Median RTT %f seconds
+ overview
width = 600
height = 50
range = 10h
+ detail
width = 600
height = 200
unison_tolerance = 2
"Last 3 Hours" 3h
"Last 30 Hours" 30h
"Last 10 Days" 10d
"Last 360 Days" 360d
#+ hierarchies
#++ owner
#title = Host Owner
#++ location
#title = Location
# (The actual example starts here.)
*** Probes ***
# This example demonstrates the concept of probe instances. The FPingLarge
# and FPingNormal probes are independent of each other, they just use
# the same module, FPing. FPingNormal uses the default parameters, and
# so does FPingLarge except for the 5 kilobyte packetsize. Both use the
# same fping binary, and its path is configured FPing top section.
#
# The 'offset' parameters make sure the probes don't run at the same time -
# FPingNormal is run every 'full' 5 minutes (eg. 8:00, 8:05, 8:10 and so on,
# in wallclock time) while FPingLarge is run halfway through these intervals
# (eg. 8:02:30, 8:07:30 etc.)
#
# The top FPing section does not define a probe in itself because it
# has subsections. If we really wanted to have one probe named "FPing",
# we could do so by making a subsection by that name.
+ FPing
binary = /usr/bin/fping
++ FPingNormal
offset = 0%
++ FPingLarge
packetsize = 5000
offset = 50%
*** Targets ***
# The target section shows two host, myhost1.example and myhost2.example,
# being pinged with two differently sized ICMP packets. This time the tree
# is divided by the target host rather than the probe.
probe = FPingNormal
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ network
menu = Net latency
title = Network latency (ICMP pings)
++ myhost1
menu = myhost1
title = ICMP latency for myhost1
+++ normal
title = Normal packetsize (56 bytes)
probe = FPingNormal
host = myhost1.example
+++ large
title = Large packetsize (5000 bytes)
probe = FPingLarge
host = myhost1.example
++ myhost2
menu = myhost2
title = ICMP latency for myhost2
+++ normal
title = Normal packetsize (56 bytes)
probe = FPingNormal
host = myhost2.example
+++ large
title = Large packetsize (5000 bytes)
probe = FPingLarge
host = myhost2.example

View File

@@ -0,0 +1,189 @@
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
*** General ***
owner = Peter Random
contact = some@address.nowhere
mailhost = my.mail.host
sendmail = /usr/sbin/sendmail
# NOTE: do not put the Image Cache below cgi-bin
# since all files under cgi-bin will be executed ... this is not
# good for images.
imgcache = /usr/cache
imgurl = cache
datadir = /usr/data
piddir = /usr/var
cgiurl = http://some.url/smokeping.cgi
smokemail = /usr/etc/smokemail.dist
tmail = /usr/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
# disable this to revert to the old behaviour
# concurrentprobes = no
*** Alerts ***
to = alertee@address.somewhere
from = smokealert@company.xy
+someloss
type = loss
# in percent
pattern = >0%,*12*,>0%,*12*,>0%
comment = loss 3 times in a row
*** Database ***
step = 300
pings = 20
# consfn mrhb steps total
AVERAGE 0.5 1 28800
AVERAGE 0.5 12 9600
MIN 0.5 12 9600
MAX 0.5 12 9600
AVERAGE 0.5 144 2400
MAX 0.5 144 2400
MIN 0.5 144 2400
*** Presentation ***
template = /usr/etc/basepage.html.dist
htmltitle = yes
graphborders = no
# If enabled, treat all filter menu queries as literal strings instead of regex
literalsearch = no
+ charts
menu = Charts
title = The most interesting destinations
++ stddev
sorter = StdDev(entries=>4)
title = Top Standard Deviation
menu = Std Deviation
format = Standard Deviation %f
++ max
sorter = Max(entries=>5)
title = Top Max Roundtrip Time
menu = by Max
format = Max Roundtrip Time %f seconds
++ loss
sorter = Loss(entries=>5)
title = Top Packet Loss
menu = Loss
format = Packets Lost %f
++ median
sorter = Median(entries=>5)
title = Top Median Roundtrip Time
menu = by Median
format = Median RTT %f seconds
+ overview
width = 600
height = 50
range = 10h
+ detail
width = 600
height = 200
unison_tolerance = 2
"Last 3 Hours" 3h
"Last 30 Hours" 30h
"Last 10 Days" 10d
"Last 360 Days" 360d
#+ hierarchies
#++ owner
#title = Host Owner
#++ location
#title = Location
# (The actual example starts here.)
*** Probes ***
# Here we have three probes: FPing for the regular ICMP pings,
# DNS for name server latency measurement and EchoPingHttp
# for web servers.
#
# The FPing probe runs with the default parameters, except that the ICMP
# packet size is 1000 bytes instead of the default 56 bytes.
#
# The DNS and EchoPingHttp probes have been configured to be a bit more
# gentle with the servers, as they only do 5 queries (pings) instead of the
# default 20 (or whatever is specified in the Database section). However,
# DNS queries are made more often: 5 queries every 3 minutes instead of
# every 5 minutes.
+ FPing
binary = /usr/bin/fping
packetsize = 1000
+ DNS
binary = /usr/bin/dig
lookup = name.example
pings = 5
step = 180
+ EchoPingHttp
pings = 5
url = /test-url
*** Targets ***
# The target tree has been divided by the probe used. This does not have
# to be the case: every target (sub)section can use a different probe,
# and the same probe can be used in different parts of the config tree.
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ network
menu = Net latency
title = Network latency (ICMP pings)
++ myhost1
host = myhost1.example
++ myhost2
host = myhost2.example
+ services
menu = Service latency
title = Service latency (DNS, HTTP)
++ DNS
probe = DNS
menu = DNS latency
title = Service latency (DNS)
+++ dns1
host = dns1.example
+++ dns2
host = dns2.example
++ HTTP
menu = HTTP latency
title = Service latency (HTTP)
+++ www1
host = www1.example
+++ www2
host = www2.example

View File

@@ -0,0 +1,156 @@
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
*** General ***
owner = Peter Random
contact = some@address.nowhere
mailhost = my.mail.host
sendmail = /usr/sbin/sendmail
# NOTE: do not put the Image Cache below cgi-bin
# since all files under cgi-bin will be executed ... this is not
# good for images.
imgcache = /usr/cache
imgurl = cache
datadir = /usr/data
piddir = /usr/var
cgiurl = http://some.url/smokeping.cgi
smokemail = /usr/etc/smokemail.dist
tmail = /usr/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
# disable this to revert to the old behaviour
# concurrentprobes = no
*** Alerts ***
to = alertee@address.somewhere
from = smokealert@company.xy
+someloss
type = loss
# in percent
pattern = >0%,*12*,>0%,*12*,>0%
comment = loss 3 times in a row
*** Database ***
step = 300
pings = 20
# consfn mrhb steps total
AVERAGE 0.5 1 28800
AVERAGE 0.5 12 9600
MIN 0.5 12 9600
MAX 0.5 12 9600
AVERAGE 0.5 144 2400
MAX 0.5 144 2400
MIN 0.5 144 2400
*** Presentation ***
template = /usr/etc/basepage.html.dist
htmltitle = yes
graphborders = no
# If enabled, treat all filter menu queries as literal strings instead of regex
literalsearch = no
+ charts
menu = Charts
title = The most interesting destinations
++ stddev
sorter = StdDev(entries=>4)
title = Top Standard Deviation
menu = Std Deviation
format = Standard Deviation %f
++ max
sorter = Max(entries=>5)
title = Top Max Roundtrip Time
menu = by Max
format = Max Roundtrip Time %f seconds
++ loss
sorter = Loss(entries=>5)
title = Top Packet Loss
menu = Loss
format = Packets Lost %f
++ median
sorter = Median(entries=>5)
title = Top Median Roundtrip Time
menu = by Median
format = Median RTT %f seconds
+ overview
width = 600
height = 50
range = 10h
+ detail
width = 600
height = 200
unison_tolerance = 2
"Last 3 Hours" 3h
"Last 30 Hours" 30h
"Last 10 Days" 10d
"Last 360 Days" 360d
#+ hierarchies
#++ owner
#title = Host Owner
#++ location
#title = Location
# (The actual example starts here.)
*** Probes ***
# Here we have just one probe, fping, pinging four hosts.
#
# The fping probe is using the default parameters, some of them supplied
# from the Database section ("step" and "pings"), and some of them by
# the probe module.
+FPing
binary = /usr/bin/fping
*** Targets ***
# The hosts are located in two sites of two hosts each, and the
# configuration has been divided to site sections ('+') and host subsections
# ('++') accordingly.
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ mysite1
menu = Site 1
title = Hosts in Site 1
++ myhost1
host = myhost1.mysite1.example
++ myhost2
host = myhost2.mysite1.example
+ mysite2
menu = Site 2
title = Hosts in Site 2
++ myhost3
host = myhost3.mysite2.example
++ myhost4
host = myhost4.mysite2.example

View File

@@ -0,0 +1,210 @@
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
*** General ***
owner = Peter Random
contact = some@address.nowhere
mailhost = my.mail.host
sendmail = /usr/sbin/sendmail
# NOTE: do not put the Image Cache below cgi-bin
# since all files under cgi-bin will be executed ... this is not
# good for images.
imgcache = /usr/cache
imgurl = cache
datadir = /usr/data
piddir = /usr/var
cgiurl = http://some.url/smokeping.cgi
smokemail = /usr/etc/smokemail.dist
tmail = /usr/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
# disable this to revert to the old behaviour
# concurrentprobes = no
*** Alerts ***
to = alertee@address.somewhere
from = smokealert@company.xy
+someloss
type = loss
# in percent
pattern = >0%,*12*,>0%,*12*,>0%
comment = loss 3 times in a row
*** Database ***
step = 300
pings = 20
# consfn mrhb steps total
AVERAGE 0.5 1 28800
AVERAGE 0.5 12 9600
MIN 0.5 12 9600
MAX 0.5 12 9600
AVERAGE 0.5 144 2400
MAX 0.5 144 2400
MIN 0.5 144 2400
*** Presentation ***
template = /usr/etc/basepage.html.dist
htmltitle = yes
graphborders = no
# If enabled, treat all filter menu queries as literal strings instead of regex
literalsearch = no
+ charts
menu = Charts
title = The most interesting destinations
++ stddev
sorter = StdDev(entries=>4)
title = Top Standard Deviation
menu = Std Deviation
format = Standard Deviation %f
++ max
sorter = Max(entries=>5)
title = Top Max Roundtrip Time
menu = by Max
format = Max Roundtrip Time %f seconds
++ loss
sorter = Loss(entries=>5)
title = Top Packet Loss
menu = Loss
format = Packets Lost %f
++ median
sorter = Median(entries=>5)
title = Top Median Roundtrip Time
menu = by Median
format = Median RTT %f seconds
+ overview
width = 600
height = 50
range = 10h
+ detail
width = 600
height = 200
unison_tolerance = 2
"Last 3 Hours" 3h
"Last 30 Hours" 30h
"Last 10 Days" 10d
"Last 360 Days" 360d
#+ hierarchies
#++ owner
#title = Host Owner
#++ location
#title = Location
# (The actual example starts here.)
*** Probes ***
# This example explains the difference between probe- and target-specific
# variables. We use the Curl probe for this.
#
# Every probe supports at least some probe-specific variables. The values
# of these variables are common to all the targets of the probe, and
# they can only be configured in the Probes section. In this case,
# the probe-specific variables are "binary" and "step".
#
# Target-specific variables are supported by most probes, the most notable
# exception being the FPing probe and its derivatives. Target-specific
# variables can have different values for different targets. They can be
# configured in both Probes and Targets sections. The values assigned in the
# Probes section function become default values that can be overridden
# in the Targets section.
#
# The documentation of each probe states which of its variables are
# probe-specific and which are target-specific.
#
# In this case the "urlformat" variable is a target-specific one. It is
# also quite uncommon, because it can contain a placeholder for the "host"
# variable in the Targets section. This is not a general feature, its
# usage is only limited to the "urlformat" variable and the "%host%" escape.
#
# (The reason why the FPing probe does not support target-specific variables
# is simply the fact that the fping program measures all its targets in one
# go, so they all have the same parameters. The other probes ping their targets
# one at a time.)
+ Curl
# probe-specific variables
binary = /usr/bin/curl
step = 60
# a default for this target-specific variable
urlformat = http://%host%/
*** Targets ***
# The target tree is divided into an HTTP branch and an FTP one.
# The servers "myhost1.example" and "myhost2.example" are probed
# in both. The third server, "myhost3.example", only has an HTTP
# server, and it's in a non-standard port (8080).
#
# The "urlformat" variable is specified for the whole FTP branch
# as "ftp://%host%/". For the HTTP branch, the default from the
# Probes section is used, except for myhost3, which overrides
# it to tag the port number into the URL.
#
# The myhost3 assignment could just as well have included the hostname
# verbatim (ie. urlformat = http://myhost3.example:8080/) instead of
# using the %host% placeholder, but the host variable would still have
# been required (even though it wouldn't have been used for anything).
probe = Curl
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ HTTP
menu = http
title = HTTP latency
++ myhost1
menu = myhost1
title = HTTP latency for myhost1
host = myhost1.example
++ myhost2
menu = myhost2
title = HTTP latency for myhost2
host = myhost2.example
++ myhost3
menu = myhost3
title = HTTP latency for myhost3 (port 8080!)
host = myhost3.example
urlformat = http://%host%:8080/
+ FTP
menu = ftp
title = FTP latency
urlformat = ftp://%host%/
++ myhost1
menu = myhost1
title = FTP latency for myhost1
host = myhost1.example
++ myhost2
menu = myhost2
title = FTP latency for myhost2
host = myhost2.example

View File

@@ -0,0 +1,168 @@
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
*** General ***
owner = Peter Random
contact = some@address.nowhere
mailhost = my.mail.host
sendmail = /usr/sbin/sendmail
# NOTE: do not put the Image Cache below cgi-bin
# since all files under cgi-bin will be executed ... this is not
# good for images.
imgcache = /usr/cache
imgurl = cache
datadir = /usr/data
piddir = /usr/var
cgiurl = http://some.url/smokeping.cgi
smokemail = /usr/etc/smokemail.dist
tmail = /usr/etc/tmail.dist
# specify this to get syslog logging
syslogfacility = local0
# each probe is now run in its own process
# disable this to revert to the old behaviour
# concurrentprobes = no
*** Alerts ***
to = alertee@address.somewhere
from = smokealert@company.xy
+someloss
type = loss
# in percent
pattern = >0%,*12*,>0%,*12*,>0%
comment = loss 3 times in a row
*** Database ***
step = 300
pings = 20
# consfn mrhb steps total
AVERAGE 0.5 1 28800
AVERAGE 0.5 12 9600
MIN 0.5 12 9600
MAX 0.5 12 9600
AVERAGE 0.5 144 2400
MAX 0.5 144 2400
MIN 0.5 144 2400
*** Presentation ***
template = /usr/etc/basepage.html.dist
htmltitle = yes
graphborders = no
# If enabled, treat all filter menu queries as literal strings instead of regex
literalsearch = no
+ charts
menu = Charts
title = The most interesting destinations
++ stddev
sorter = StdDev(entries=>4)
title = Top Standard Deviation
menu = Std Deviation
format = Standard Deviation %f
++ max
sorter = Max(entries=>5)
title = Top Max Roundtrip Time
menu = by Max
format = Max Roundtrip Time %f seconds
++ loss
sorter = Loss(entries=>5)
title = Top Packet Loss
menu = Loss
format = Packets Lost %f
++ median
sorter = Median(entries=>5)
title = Top Median Roundtrip Time
menu = by Median
format = Median RTT %f seconds
+ overview
width = 600
height = 50
range = 10h
+ detail
width = 600
height = 200
unison_tolerance = 2
"Last 3 Hours" 3h
"Last 30 Hours" 30h
"Last 10 Days" 10d
"Last 360 Days" 360d
#+ hierarchies
#++ owner
#title = Host Owner
#++ location
#title = Location
# (The actual example starts here.)
*** Probes ***
# This is the template configuration file distributed with Smokeping.
# It is included in the examples as well for the sake of completeness.
+ FPing
binary = /usr/sbin/fping
*** Slaves ***
secrets=/usr/etc/smokeping_secrets.dist
+boomer
display_name=boomer
color=0000ff
+slave2
display_name=another
color=00ff00
*** Targets ***
# This is the template configuration file distributed with Smokeping.
# It is included in the examples as well for the sake of completeness.
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to the SmokePing website of xxx Company. \
Here you will learn all about the latency of our network.
+ Test
menu= Targets
#parents = owner:/Test/James location:/
++ James
menu = James
title =James
alerts = someloss
slaves = boomer slave2
host = james.address
++ MultiHost
menu = Multihost
title = James and James as seen from Boomer
host = /Test/James /Test/James~boomer

View File

@@ -0,0 +1,22 @@
# /etc/lighttpd/conf-available/45-smokeping.conf
# Configuration for smokeping CGI program
server.modules += ( "mod_fastcgi", "mod_rewrite" )
$HTTP["url"] =~ "^/smokeping/" {
server.document-root = "/usr/share/"
url.rewrite-once = (
"^/smokeping/($|\?)" => "/smokeping/smokeping.cgi?${qsa}",
"^/smokeping/(.*)" => "/smokeping/www/$1",
)
fastcgi.server = (
"/smokeping/smokeping.cgi" => (
"localhost" => (
"bin-path" => "/usr/lib/cgi-bin/smokeping.cgi",
"socket" => "/run/lighttpd/smokeping-fcgi.socket",
)
),
)
}
# vim: set ts=4 sw=4 et:

View File

@@ -0,0 +1,3 @@
host1:mysecret
host2:yoursecret
boomer:lkasdf93uhhfdfddf

View File

@@ -0,0 +1,11 @@
# If you need to run smokeping in a master/slave setup, you can create a
# drop-in override to add the required parameters to the daemon like shown
# below.
#
# To enable this, you'd need to create a directory
# /etc/systemd/system/smokeping.service.d/ and copy this file in that
# directory. Then, modify it according to your requirements.
#
[Service]
ExecStart=
ExecStart=/usr/sbin/smokeping --master-url=http://127.0.0.1/smokeping.fcgi --cache-dir=/var/lib/smokeping --shared-secret=/etc/smokeping/smokeping_secrets --pid-dir=/run/smokeping

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
# -*- perl -*-
package Smokeping::Colorspace;
=head1 NAME
Smokeping::Colorspace - Simple Colorspace Conversion methods
=head1 OVERVIEW
This module provides simple colorspace conversion methods, primarily allowing
conversion from RGB (red, green, blue) to and from HSL (hue, saturation, luminosity).
=head1 COPYRIGHT
Copyright 2006 by Grahame Bowland.
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 AUTHOR
Grahame Bowland <grahame.bowland@uwa.edu.au>
=cut
sub web_to_rgb {
my $web = shift;
$web =~ s/^#//;
my @rgb = (hex(substr($web, 0, 2)) / 255,
hex(substr($web, 2, 2)) / 255,
hex(substr($web, 4, 2)) / 255) ;
return @rgb;
}
sub rgb_to_web {
my @rgb = @_;
return sprintf("#%.2x%.2x%.2x", 255 * $rgb[0], 255 * $rgb[1], 255 * $rgb[2]);
}
sub min_max_indexes {
my $idx = 0;
my ($min_idx, $min, $max_idx, $max);
my @l = @_;
foreach my $i (@l) {
if (not defined($min) or ($i < $min)) {
$min = $i;
$min_idx = $idx;
}
if (not defined($max) or ($i > $max)) {
$max = $i;
$max_idx = $idx;
}
$idx++;
}
return ($min_idx, $min, $max_idx, $max);
}
# source for conversion algorithm is:
# http://www.easyrgb.com/math.php?MATH=M18#text18
sub rgb_to_hsl {
my @rgb = @_;
my ($h, $l, $s);
my ($min_idx, $min, $max_idx, $max) = min_max_indexes(@rgb);
my $delta_max = $max - $min;
$l = ($max + $min) / 2;
if ($delta_max == 0) {
my $h = 0;
my $s = 0;
} else {
if ($l < 0.5) {
$s = $delta_max / ($max + $min);
} else {
$s = $delta_max / (2 - $max - $min);
}
my $delta_r = ((($max - $rgb[0]) / 6) + ($max / 2)) / $delta_max;
my $delta_g = ((($max - $rgb[1]) / 6) + ($max / 2)) / $delta_max;
my $delta_b = ((($max - $rgb[2]) / 6) + ($max / 2)) / $delta_max;
if ($max_idx == 0) {
$h = $delta_b - $delta_g;
} elsif ($max_idx == 1) {
$h = (1/3) + $delta_r - $delta_b;
} else {
$h = (2/3) + $delta_g - $delta_r;
}
if ($h < 0) {
$h += 1;
} elsif ($h > 1) {
$h -= 1;
}
}
return ($h, $s, $l);
}
sub hue_to_rgb {
my ($v1, $v2, $vh) = @_;
if ($vh < 0) {
$vh += 1;
} elsif ($vh > 1) {
$vh -= 1;
}
if ($vh * 6 < 1) {
return $v1 + ($v2 - $v1) * 6 * $vh;
} elsif ($vh * 2 < 1) {
return $v2;
} elsif ($vh * 3 < 2) {
return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
} else {
return $v1;
}
}
sub hsl_to_rgb {
my ($h, $s, $l) = @_;
my ($r, $g, $b);
if ($s == 0) {
$r = $g = $b = $l;
} else {
my $ls;
if ($l < 0.5) {
$ls = $l * (1 + $s);
} else {
$ls = ($l + $s) - ($s * $l);
}
$l = 2 * $l - $ls;
$r = hue_to_rgb($l, $ls, $h + 1/3);
$g = hue_to_rgb($l, $ls, $h);
$b = hue_to_rgb($l, $ls, $h - (1/3));
}
return ($r, $g, $b);
}
1;

View File

@@ -0,0 +1,15 @@
# provide backward compatibility for Config::Grammar
package Smokeping::Config;
BEGIN {
require Config::Grammar;
if($Config::Grammar::VERSION ge '1.10') {
require Config::Grammar::Dynamic;
@ISA = qw(Config::Grammar::Dynamic);
}
else {
@ISA = qw(Config::Grammar);
}
}
1;

View File

@@ -0,0 +1,678 @@
# -*- perl -*-
package Smokeping::Examples;
use strict;
use Smokeping;
=head1 NAME
Smokeping::Examples - A module for generating the smokeping_examples document
=head1 OVERVIEW
This module generates L<smokeping_examples> and the example
configuration files distributed with Smokeping. It is supposed to be
invoked from the smokeping distribution top directory, as it will need
the C<etc/config.dist> template configuration file and will create files
in the directories C<doc> and C<doc/examples>.
=head1 DESCRIPTION
The entry point to the module is the C<make> subroutine. It takes one optional
parameter, C<check>, that makes the module run a syntax check for all the
created example configuration files.
=head1 BUGS
This module uses more or less internal functions from L<Smokeping.pm|Smokeping>. It's a
separate module only because the latter is much too big already.
It should be possible to include POD markup in the configuration explanations
and have this module filter them away for the config files.
It might be nice for the probe module authors to be able to provide an
example configuration as part of the probe module instead of having to
modify Smokeping::Examples too.
=head1 COPYRIGHT
Copyright 2005 by Niko Tyni.
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 AUTHOR
Niko Tyni <ntyni@iki.fi>
=cut
use strict;
sub read_config_template {
my $file = "../etc/config.dist";
my $h = {
common => "", # everything up to the Probes section
probes => "", # the Probes section, without the *** Probes *** line
targets => "", # the Targets section, without the *** Targets *** line
};
open(F, "<$file") or die("open template configuration file $file for reading: $!");
my %found;
while (<F>) {
/\*\*\*\s*(Probes|Targets)\s*\*\*\*/ and $found{$1} = 1, next;
$h->{common} .= $_ and next unless $found{Probes};
$h->{probes} .= $_ and next unless $found{Targets};
$h->{targets} .= $_;
}
close F;
return $h;
}
sub prologue {
my $e = "=";
return <<DOC;
${e}head1 NAME
smokeping_examples - Examples of Smokeping configuration
${e}head1 OVERVIEW
This document provides some examples of Smokeping configuration files.
All the examples can be found in the C<examples> directory in the
Smokeping documentation. Note that the DNS names in the examples are
non-functional.
Details of the syntax and all the variables are found in
L<smokeping_config> and in the documentation of the
corresponding probe, if applicable.
This manual is automatically generated from the Smokeping source code,
specifically the L<Smokeping::Examples|Smokeping::Examples> module.
${e}head1 DESCRIPTION
Currently the examples differ only in the C<Probes> and C<Targets>
sections. The other sections are taken from the C<etc/config.dist>
configuration template in the Smokeping distribution so that the example
files are complete.
If you would like to provide more examples, document the other sections
or enhance the existing examples, please do so, preferably by sending
the proposed changes to the smokeping-users mailing list.
DOC
}
sub epilogue {
my $e = "=";
return <<DOC;
${e}head1 COPYRIGHT
Copyright 2005 by Niko Tyni.
${e}head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
${e}head1 AUTHOR
Niko Tyni <ntyni\@iki.fi>
${e}head1 SEE ALSO
The other Smokeping documents, especially L<smokeping_config>.
DOC
}
sub make {
print "Generating example files...\n";
my $check = shift; # check the syntax of the generated config files
my $template = read_config_template();
my $examples = examples($template);
my $manual = prologue();
for my $ex (sort { $examples->{$a}{order} <=> $examples->{$b}{order} } keys %$examples) {
my $h = $examples->{$ex};
$manual .= "\n=head2 Example $h->{order}: config.$ex\n\n"
. genpod($h);
my $cfgfile = "examples/config.$ex";
print "\t$cfgfile ...\n";
writecfg($cfgfile, $template, $h);
if ($check) {
local $Smokeping::cfg = undef;
eval {
Smokeping::verify_cfg($cfgfile);
};
die("Syntax check for $cfgfile failed: $@") if $@;
}
}
$manual .= epilogue();
writemanual($manual);
print "done.\n";
}
sub writemanual {
my $text = shift;
my $filename = "smokeping_examples.pod";
print "\t$filename ...\n";
open(F, ">$filename") or die("open $filename for writing: $!");
print F $text;
close F;
}
sub genpod {
my $h = shift;
my $text = "";
$text .= "=over\n\n";
$text .= "=item Probe configuration\n\n";
$text .= " *** Probes ***\n";
$text .= join("\n", map { " $_" } split(/\n/, $h->{probes}));
$text .= "\n\n=item Probe explanation\n\n";
$text .= $h->{probedoc} || "No probedoc found !";
$text .= "\n\n=item Target configuration\n\n";
$text .= " *** Targets ***\n";
$text .= join("\n", map { " $_" } split(/\n/, $h->{targets}));
$text .= "\n\n=item Target explanation\n\n";
$text .= $h->{targetdoc} || "No targetdoc found !";
$text .= "\n\n=back\n\n";
return $text;
}
sub writecfg {
my $file = shift;
my $template = shift;
my $h = shift;
open(F, ">$file") or die("open $file for writing: $!");
print F <<DOC;
# This Smokeping example configuration file was automatically generated.
#
# Everything up to the Probes section is derived from a common template file.
# See the Probes and Targets sections for the actual example.
#
# This example is included in the smokeping_examples document.
DOC
print F $template->{common};
print F "# (The actual example starts here.)\n";
print F "\n*** Probes ***\n\n";
print F join("\n", map { "# $_" } split(/\n/, $h->{probedoc} || 'No probedoc found!'));
print F "\n\n";
print F $h->{probes};
print F "\n*** Targets ***\n\n";
print F join("\n", map { "# $_" } split(/\n/, $h->{targetdoc} || 'No targetdoc found'));
print F "\n\n";
print F $h->{targets};
close F;
}
sub examples {
my $template = shift;
return {
simple => {
order => 1,
probes => <<DOC,
+FPing
binary = /usr/bin/fping
DOC
targets => <<DOC,
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ mysite1
menu = Site 1
title = Hosts in Site 1
++ myhost1
host = myhost1.mysite1.example
++ myhost2
host = myhost2.mysite1.example
+ mysite2
menu = Site 2
title = Hosts in Site 2
++ myhost3
host = myhost3.mysite2.example
++ myhost4
host = myhost4.mysite2.example
DOC
probedoc => <<DOC,
Here we have just one probe, fping, pinging four hosts.
The fping probe is using the default parameters, some of them supplied
from the Database section ("step" and "pings"), and some of them by
the probe module.
DOC
targetdoc => <<DOC,
The hosts are located in two sites of two hosts each, and the
configuration has been divided to site sections ('+') and host subsections
('++') accordingly.
DOC
}, # simple
"multiple-probes" => {
order => 2,
probes => <<DOC,
+ FPing
binary = /usr/bin/fping
packetsize = 1000
+ DNS
binary = /usr/bin/dig
lookup = name.example
pings = 5
step = 180
+ EchoPingHttp
pings = 5
url = /test-url
DOC
targets => <<DOC,
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ network
menu = Net latency
title = Network latency (ICMP pings)
++ myhost1
host = myhost1.example
++ myhost2
host = myhost2.example
+ services
menu = Service latency
title = Service latency (DNS, HTTP)
++ DNS
probe = DNS
menu = DNS latency
title = Service latency (DNS)
+++ dns1
host = dns1.example
+++ dns2
host = dns2.example
++ HTTP
menu = HTTP latency
title = Service latency (HTTP)
+++ www1
host = www1.example
+++ www2
host = www2.example
DOC
probedoc => <<DOC,
Here we have three probes: FPing for the regular ICMP pings,
DNS for name server latency measurement and EchoPingHttp
for web servers.
The FPing probe runs with the default parameters, except that the ICMP
packet size is 1000 bytes instead of the default 56 bytes.
The DNS and EchoPingHttp probes have been configured to be a bit more
gentle with the servers, as they only do 5 queries (pings) instead of the
default 20 (or whatever is specified in the Database section). However,
DNS queries are made more often: 5 queries every 3 minutes instead of
every 5 minutes.
DOC
targetdoc => <<DOC,
The target tree has been divided by the probe used. This does not have
to be the case: every target (sub)section can use a different probe,
and the same probe can be used in different parts of the config tree.
DOC
}, # multiple-probes
"fping-instances" => {
order => 3,
probes => <<DOC,
+ FPing
binary = /usr/bin/fping
++ FPingNormal
offset = 0%
++ FPingLarge
packetsize = 5000
offset = 50%
DOC
probedoc => <<DOC,
This example demonstrates the concept of probe instances. The FPingLarge
and FPingNormal probes are independent of each other, they just use
the same module, FPing. FPingNormal uses the default parameters, and
so does FPingLarge except for the 5 kilobyte packetsize. Both use the
same fping binary, and its path is configured FPing top section.
The 'offset' parameters make sure the probes don't run at the same time -
FPingNormal is run every 'full' 5 minutes (eg. 8:00, 8:05, 8:10 and so on,
in wallclock time) while FPingLarge is run halfway through these intervals
(eg. 8:02:30, 8:07:30 etc.)
The top FPing section does not define a probe in itself because it
has subsections. If we really wanted to have one probe named "FPing",
we could do so by making a subsection by that name.
DOC
targets => <<DOC,
probe = FPingNormal
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ network
menu = Net latency
title = Network latency (ICMP pings)
++ myhost1
menu = myhost1
title = ICMP latency for myhost1
+++ normal
title = Normal packetsize (56 bytes)
probe = FPingNormal
host = myhost1.example
+++ large
title = Large packetsize (5000 bytes)
probe = FPingLarge
host = myhost1.example
++ myhost2
menu = myhost2
title = ICMP latency for myhost2
+++ normal
title = Normal packetsize (56 bytes)
probe = FPingNormal
host = myhost2.example
+++ large
title = Large packetsize (5000 bytes)
probe = FPingLarge
host = myhost2.example
DOC
targetdoc => <<DOC,
The target section shows two host, myhost1.example and myhost2.example,
being pinged with two differently sized ICMP packets. This time the tree
is divided by the target host rather than the probe.
DOC
}, # fping-instances
"targetvars-with-Curl" => {
order => 4,
probes => <<DOC,
+ Curl
# probe-specific variables
binary = /usr/bin/curl
step = 60
# a default for this target-specific variable
urlformat = http://%host%/
DOC
probedoc => <<DOC,
This example explains the difference between probe- and target-specific
variables. We use the Curl probe for this.
Every probe supports at least some probe-specific variables. The values
of these variables are common to all the targets of the probe, and
they can only be configured in the Probes section. In this case,
the probe-specific variables are "binary" and "step".
Target-specific variables are supported by most probes, the most notable
exception being the FPing probe and its derivatives. Target-specific
variables can have different values for different targets. They can be
configured in both Probes and Targets sections. The values assigned in the
Probes section function become default values that can be overridden
in the Targets section.
The documentation of each probe states which of its variables are
probe-specific and which are target-specific.
In this case the "urlformat" variable is a target-specific one. It is
also quite uncommon, because it can contain a placeholder for the "host"
variable in the Targets section. This is not a general feature, its
usage is only limited to the "urlformat" variable and the "%host%" escape.
(The reason why the FPing probe does not support target-specific variables
is simply the fact that the fping program measures all its targets in one
go, so they all have the same parameters. The other probes ping their targets
one at a time.)
DOC
targets => <<DOC,
probe = Curl
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ HTTP
menu = http
title = HTTP latency
++ myhost1
menu = myhost1
title = HTTP latency for myhost1
host = myhost1.example
++ myhost2
menu = myhost2
title = HTTP latency for myhost2
host = myhost2.example
++ myhost3
menu = myhost3
title = HTTP latency for myhost3 (port 8080!)
host = myhost3.example
urlformat = http://%host%:8080/
+ FTP
menu = ftp
title = FTP latency
urlformat = ftp://%host%/
++ myhost1
menu = myhost1
title = FTP latency for myhost1
host = myhost1.example
++ myhost2
menu = myhost2
title = FTP latency for myhost2
host = myhost2.example
DOC
targetdoc => <<DOC,
The target tree is divided into an HTTP branch and an FTP one.
The servers "myhost1.example" and "myhost2.example" are probed
in both. The third server, "myhost3.example", only has an HTTP
server, and it's in a non-standard port (8080).
The "urlformat" variable is specified for the whole FTP branch
as "ftp://%host%/". For the HTTP branch, the default from the
Probes section is used, except for myhost3, which overrides
it to tag the port number into the URL.
The myhost3 assignment could just as well have included the hostname
verbatim (ie. urlformat = http://myhost3.example:8080/) instead of
using the %host% placeholder, but the host variable would still have
been required (even though it wouldn't have been used for anything).
DOC
}, # targetvars-with-Curl
echoping => {
order => 5,
probes => <<DOC,
+ FPing
binary = /usr/bin/fping
# these expect to find echoping in /usr/bin
# if not, you'll have to specify the location separately for each probe
# + EchoPing # uses TCP or UDP echo (port 7)
# + EchoPingDiscard # uses TCP or UDP discard (port 9)
# + EchoPingChargen # uses TCP chargen (port 19)
+ EchoPingSmtp # SMTP (25/tcp) for mail servers
+ EchoPingHttps # HTTPS (443/tcp) for web servers
+ EchoPingHttp # HTTP (80/tcp) for web servers and caches
+ EchoPingIcp # ICP (3130/udp) for caches
# these need at least echoping 6 with the corresponding plugins
+ EchoPingDNS # DNS (53/udp or tcp) servers
+ EchoPingLDAP # LDAP (389/tcp) servers
+ EchoPingWhois # Whois (43/tcp) servers
DOC
probedoc => <<DOC,
This example shows most of the echoping-derived probes in action.
DOC
targets => <<DOC,
# default probe
probe = FPing
menu = Top
title = Network Latency Grapher
remark = Welcome to this SmokePing website.
+ MyServers
menu = My Servers
title = My Servers
++ www-server
menu = www-server
title = Web Server (www-server) / ICMP
# probe = FPing propagated from top
host = www-server.example
+++ http
menu = http
title = Web Server (www-server) / HTTP
probe = EchoPingHttp
host = www-server.example
# default url is /
+++ https
menu = https
title = Web Server (www-server) / HTTPS
probe = EchoPingHttps
host = www-server.example
++ cache
menu = www-cache
title = Web Cache (www-cache) / ICMP
host = www-cache.example
+++ http
menu = http
title = www-cache / HTTP
probe = EchoPingHttp
host = www-cache.example
port = 8080 # use the squid port
url = http://www.somehost.example/
+++ icp
menu = icp
title = www-cache / ICP
probe = EchoPingIcp
host = www-cache.example
url = http://www.somehost.example/
++ mail
menu = mail-server
title = Mail Server (mail-server) / ICMP
host = mail-server.example
+++ smtp
menu = mail-server / SMTP
title = Mail Server (mail-server) / SMTP
probe = EchoPingSmtp
host = mail-server.example
++ ldap-server
menu = ldap-server
title = ldap-server / ICMP
host = ldap-server.example
+++ ldap
menu = ldap-server / LDAP
title = LDAP Server (ldap-server) / LDAP
probe = EchoPingLDAP
ldap_request = (objectclass=*)
host = ldap-server.example
++ name-server
menu = name-server
title = name-server / ICMP
host = name-server.example
+++ DNS
menu = name-server / DNS
title = DNS Server (name-server) / DNS
probe = EchoPingDNS
dns_request = name.example
host = name-server.example
++ whois-server
menu = whois-server
title = whois-server / ICMP
host = whois-server.example
+++ Whois
menu = whois-server / Whois
title = Whois Server (whois-server) / Whois
probe = EchoPingWhois
whois_request = domain.example
host = whois-server.example
DOC
targetdoc => <<DOC,
All the servers are pinged both with ICMP (the FPing probe)
and their respective echoping probe. The proxy server, www-cache,
is probed with both HTTP requests and ICP requests for the same
URL.
DOC
}, # echoping
template => {
order => 6, # last
probes => $template->{probes},
targets => $template->{targets},
probedoc => <<DOC,
This is the template configuration file distributed with Smokeping.
It is included in the examples as well for the sake of completeness.
DOC
targetdoc => <<DOC,
This is the template configuration file distributed with Smokeping.
It is included in the examples as well for the sake of completeness.
DOC
},
}; # return
} # sub examples
1;

View File

@@ -0,0 +1,403 @@
# -*- perl -*-
package Smokeping::Graphs;
use strict;
use Smokeping;
=head1 NAME
Smokeping::Graphs - Functions used in Smokeping for creating graphs
=head1 OVERVIEW
This module currently only contains the code for generating the 'multi target' graphs.
Code for the other graphs will be moved here too in time.
=head2 IMPLEMENTATION
=head3 get_multi_detail
A version of get_detail for multi host graphs where there is data from
multiple targets shown in one graph. The look of the graph is modeld after
the graphs shown in the overview page, except for the size.
=cut
sub get_colors ($){
my $cfg = shift;
my @colorList = ();
my $colorText = $cfg->{Presentation}{colortext};
my $colorBorder = $cfg->{Presentation}{colorborder};
my $colorBackground = $cfg->{Presentation}{colorbackground};
# If graphborders set to no, and no color override, then return default colors as before
if (($cfg->{Presentation}{graphborders} eq 'no') && !($colorText||$colorBorder||$colorBackground)) {
return '--border', '0',
'--color', 'BACK#ffffff00',
'--color', 'CANVAS#ffffff00';
};
# If there are any overrides, use them
if ($cfg->{Presentation}{graphborders} eq 'no') {
push(@colorList, '--border', '0');
};
if ($colorText) {
push(@colorList, '--color', "FONT#${colorText}");
};
if ($colorBorder) {
push(@colorList, '--color', "FRAME#${colorBorder}");
};
if ($colorBackground) {
push(@colorList, '--color', "SHADEA#${colorBackground}");
push(@colorList, '--color', "SHADEB#${colorBackground}");
push(@colorList, '--color', "BACK#${colorBackground}");
push(@colorList, '--color', "CANVAS#${colorBackground}");
};
if (@colorList) { return @colorList[0..$#colorList] };
# Otherwise use rrdtool defaults
return
}
sub get_multi_detail ($$$$;$){
# a) 's' classic with several static graphs on the page
# b) 'n' navigator mode with one graph. below the graph one can specify the end time
# and the length of the graph.
# c) 'a' ajax mode, generate image based on given url and dump in on stdout
#
my $cfg = shift;
my $q = shift;
my $tree = shift;
my $open = shift;
my $mode = shift || $q->param('displaymode') || 's';
my $phys_open = $open;
if ($tree->{__tree_link}){
$tree=$tree->{__tree_link};
$phys_open = $tree->{__real_path};
}
my @dirs = @{$phys_open};
return "<div>ERROR: ".(join ".", @dirs)." has no probe defined</div>"
unless $tree->{probe};
return "<div>ERROR: ".(join ".", @dirs)." $tree->{probe} is not known</div>"
unless $cfg->{__probes}{$tree->{probe}};
return "<div>ERROR: ".(join ".", @dirs)." ist no multi host</div>"
unless $tree->{host} =~ m|^/|;
return "<div>ERROR: unknown displaymode $mode</div>"
unless $mode =~ /^[snca]$/;
my $dir = "";
for (@dirs) {
$dir .= "/$_";
mkdir $cfg->{General}{imgcache}.$dir, 0755
unless -d $cfg->{General}{imgcache}.$dir;
die "ERROR: creating $cfg->{General}{imgcache}$dir: $!\n"
unless -d $cfg->{General}{imgcache}.$dir;
}
my $page;
my $file = pop @dirs;
my @hosts = split /\s+/, $tree->{host};
my $ProbeDesc;
my $ProbeUnit;
my $imgbase;
my $imghref;
my @tasks;
my %lastheight;
my $max = {};
if ($mode eq 's'){
# in nav mode there is only one graph, so the height calculation
# is not necessary.
$imgbase = $cfg->{General}{imgcache}."/".(join "/", @dirs)."/${file}";
$imghref = $cfg->{General}{imgurl}."/".(join "/", @dirs)."/${file}";
@tasks = @{$cfg->{Presentation}{detail}{_table}};
if (open (HG,"<${imgbase}.maxheight")){
while (<HG>){
chomp;
my @l = split / /;
$lastheight{$l[0]} = $l[1];
}
close HG;
}
for my $rrd (@hosts){
my $newmax = Smokeping::findmax($cfg, $cfg->{General}{datadir}.$rrd.".rrd");
map {$max->{$_} = $newmax->{$_} if not $max->{$_} or $newmax->{$_} > $max->{$_} } keys %{$newmax};
}
if (open (HG,">${imgbase}.maxheight")){
foreach my $size (keys %{$max}){
print HG "$size $max->{$size}\n";
}
close HG;
}
}
elsif ($mode eq 'n' or $mode eq 'a') {
if ($mode eq 'n') {
$imgbase =$cfg->{General}{imgcache}."/__navcache/".time()."$$";
$imghref =$cfg->{General}{imgurl}."/__navcache/".time()."$$";
} else {
my $serial = int(rand(2000));
$imgbase =$cfg->{General}{imgcache}."/__navcache/".$serial;
$imghref =$cfg->{General}{imgurl}."/__navcache/".$serial;
}
mkdir $cfg->{General}{imgcache}."/__navcache",0755 unless -d $cfg->{General}{imgcache}."/__navcache";
# remove old images after one hour
my $pattern = $cfg->{General}{imgcache}."/__navcache/*.png";
for (glob $pattern){
unlink $_ if time - (stat $_)[9] > 3600;
}
@tasks = (["Navigator Graph", Smokeping::parse_datetime($q->param('start')),Smokeping::parse_datetime($q->param('end'))]);
} else {
# chart mode
mkdir $cfg->{General}{imgcache}."/__chartscache",0755 unless -d $cfg->{General}{imgcache}."/__chartscache";
# remove old images after one hour
my $pattern = $cfg->{General}{imgcache}."/__chartscache/*.png";
for (glob $pattern){
unlink $_ if time - (stat $_)[9] > 3600;
}
my $desc = join "/",@{$open};
@tasks = ([$desc , time()-3600, time()]);
$imgbase = $cfg->{General}{imgcache}."/__chartscache/".(join ".", @dirs).".${file}";
$imghref = $cfg->{General}{imgurl}."/__chartscache/".(join ".", @dirs).".${file}";
}
if ($mode =~ /[anc]/){
my $val = 0;
for my $host (@hosts){
my ($graphret,$xs,$ys) = RRDs::graph
("dummy",
'--start', $tasks[0][1],
'--end', $tasks[0][2],
"DEF:maxping=$cfg->{General}{datadir}${host}.rrd:median:AVERAGE",
'PRINT:maxping:MAX:%le' );
my $ERROR = RRDs::error();
return "<div>RRDtool did not understand your input: $ERROR.</div>" if $ERROR;
$val = $graphret->[0] if $val < $graphret->[0];
}
$val = 1e-6 if $val =~ /nan/i;
$max = { $tasks[0][1] => $val * 1.5 };
}
for (@tasks) {
my ($desc,$start,$end) = @{$_};
my $xs;
my $ys;
my $sigtime = ($end and $end =~ /^\d+$/) ? $end : time;
my $date = $cfg->{Presentation}{detail}{strftime} ?
POSIX::strftime($cfg->{Presentation}{detail}{strftime}, localtime($sigtime)) : scalar localtime($sigtime);
if ( $RRDs::VERSION >= 1.199908 ){
$date =~ s|:|\\:|g;
}
$end ||= 'last';
$start = Smokeping::exp2seconds($start) if $mode =~ /[s]/;
my $startstr = $start =~ /^\d+$/ ? POSIX::strftime("%Y-%m-%d %H:%M",localtime($mode eq 'n' ? $start : time-$start)) : $start;
my $endstr = $end =~ /^\d+$/ ? POSIX::strftime("%Y-%m-%d %H:%M",localtime($mode eq 'n' ? $end : time)) : $end;
my $realstart = ( $mode =~ /[sc]/ ? '-'.$start : $start);
my @G;
my @colors = split /\s+/, $cfg->{Presentation}{multihost}{colors};
my $i = 0;
for my $host (@hosts){
$i++;
my $swidth = $max->{$start} / $cfg->{Presentation}{detail}{height};
my $rrd = $cfg->{General}{datadir}.$host.".rrd";
next unless -r $rrd; # skip things that do not exist;
my $medc = shift @colors;
my @tree_path = split /\//,$host;
shift @tree_path;
my ($host,$real_slave) = split /~/, $tree_path[-1]; #/
$tree_path[-1] = $host;
my $tree = Smokeping::get_tree($cfg,\@tree_path);
my $label = $tree->{menu};
if ($real_slave){
$label .= "<". $cfg->{Slaves}{$real_slave}{display_name};
}
my $probe = $cfg->{__probes}{$tree->{probe}};
my $XProbeDesc = $probe->ProbeDesc();
if (not $ProbeDesc or $ProbeDesc eq $XProbeDesc){
$ProbeDesc = $XProbeDesc;
}
else {
$ProbeDesc = "various probes";
}
my $XProbeUnit = $probe->ProbeUnit();
if (not $ProbeUnit or $ProbeUnit eq $XProbeUnit){
$ProbeUnit = $XProbeUnit;
}
else {
$ProbeUnit = "various units";
}
my $pings = $probe->_pings($tree);
$label = sprintf("%-20s",$label);
$label =~ s/:/\\:/g;
push @colors, $medc;
my $sdc = $medc;
my $stddev = Smokeping::RRDhelpers::get_stddev($rrd,'median','AVERAGE',$realstart,$sigtime) || 0;
$sdc =~ s/^(......).*/${1}30/;
push @G,
"DEF:median$i=${rrd}:median:AVERAGE",
"DEF:loss$i=${rrd}:loss:AVERAGE",
"CDEF:ploss$i=loss$i,$pings,/,100,*",
"CDEF:dm$i=median$i,0,".$max->{$start}.",LIMIT",
Smokeping::calc_stddev($rrd,$i,$pings),
"CDEF:dmlow$i=dm$i,sdev$i,2,/,-",
"CDEF:s2d$i=sdev$i",
# "CDEF:dm2=median,1.5,*,0,$max,LIMIT",
# "LINE1:dm2", # this is for kicking things down a bit
"AREA:dmlow$i",
"AREA:s2d${i}#${sdc}::STACK",
"LINE1:dm$i#${medc}:${label}",
"VDEF:avmed$i=median$i,AVERAGE",
"VDEF:avsd$i=sdev$i,AVERAGE",
"CDEF:msr$i=median$i,POP,avmed$i,avsd$i,/",
"VDEF:avmsr$i=msr$i,AVERAGE",
"GPRINT:avmed$i:%5.1lf %ss av md ",
"GPRINT:ploss$i:AVERAGE:%5.1lf %% av ls",
sprintf('COMMENT:%5.1lf ms sd',$stddev*1000.0),
"GPRINT:avmsr$i:%5.1lf %s am/as\\l";
};
my @task;
push @task, "--logarithmic" if $cfg->{Presentation}{detail}{logarithmic} and
$cfg->{Presentation}{detail}{logarithmic} eq 'yes';
push @task, '--lazy' if $mode eq 's' and $lastheight{$start} == $max->{$start};
push @task,
"${imgbase}_${end}_${start}.png",
'--start',$realstart,
($end ne 'last' ? ('--end',$end) : ()),
'--height',$cfg->{Presentation}{detail}{height},
'--width',$cfg->{Presentation}{detail}{width},
'--title',$cfg->{Presentation}{htmltitle} ne 'yes' ? $desc : '',
'--rigid','--upper-limit', $max->{$start},
'--lower-limit',($cfg->{Presentation}{detail}{logarithmic} ? ($max->{$start} > 0.01) ? '0.001' : '0.0001' : '0'),
'--vertical-label',$ProbeUnit,
'--imgformat','PNG',
Smokeping::Graphs::get_colors($cfg),
@G,
"COMMENT:$ProbeDesc",
"COMMENT:$date\\j";
my $graphret;
($graphret,$xs,$ys) = RRDs::graph @task;
# print "<div>INFO:".join("<br/>",@task)."</div>";
my $ERROR = RRDs::error();
if ($ERROR) {
return "<div>ERROR: $ERROR</div><div>".join("<br/>",@task)."</div>";
};
if ($mode eq 'a'){ # ajax mode
open my $img, "${imgbase}_${end}_${start}.png";
binmode $img;
print "Content-Type: image/png\n";
my $data;
read($img,$data,(stat($img))[7]);
close $img;
print "Content-Length: ".length($data)."\n\n";
print $data;
unlink "${imgbase}_${end}_${start}.png";
return undef;
}
elsif ($mode eq 'n'){ # navigator mode
$page .= "<div class=\"panel\">";
$page .= "<div class=\"panel-heading\"><h2>$desc</h2></div>"
if $cfg->{Presentation}{htmltitle} eq 'yes';
$page .= "<div class=\"panel-body\">";
$page .= qq|<IMG id="zoom" alt="" width="$xs" height="$ys" SRC="${imghref}_${end}_${start}.png">| ;
$page .= $q->start_form(-method=>'GET', -id=>'range_form')
. "<p>Time range: "
. $q->textfield(-name=>'start',-default=>$startstr)
. "&nbsp;&nbsp;to&nbsp;&nbsp;".$q->textfield(-name=>'end',-default=>$endstr)
. $q->hidden(-name=>'epoch_start',-id=>'epoch_start',-default=>$start)
. $q->hidden(-name=>'epoch_end',-id=>'epoch_end',-default=>time())
. $q->hidden(-name=>'target',-id=>'target' )
. $q->hidden(-name=>'hierarchy',-id=>'hierarchy' )
. $q->hidden(-name=>'displaymode',-default=>$mode )
. "&nbsp;"
. $q->submit(-name=>'Generate!')
. "</p>"
. $q->end_form();
$page .= "</div></div>\n";
} elsif ($mode eq 's') { # classic mode
$startstr =~ s/\s/%20/g;
$endstr =~ s/\s/%20/g;
$page .= "<div class=\"panel\">";
# $page .= (time-$timer_start)."<br/>";
# $page .= join " ",map {"'$_'"} @task;
$page .= "<div class=\"panel-heading\"><h2>$desc</h2></div>"
if $cfg->{Presentation}{htmltitle} eq 'yes';
$page .= "<div class=\"panel-body\">";
$page .= ( qq{<a href="?displaymode=n;start=$startstr;end=now;}."target=".$q->param('target').'">'
. qq{<IMG ALT="" SRC="${imghref}_${end}_${start}.png" class="img-responsive">}."</a>" ); #"
$page .= "</div></div>\n";
} else { # chart mode
$page .= "<div class=\"panel\">";
$page .= "<div class=\"panel-heading\"><h2>$desc</h2></div>"
if $cfg->{Presentation}{htmltitle} eq 'yes';
$page .= "<div class=\"panel-body\">";
$page .= ( qq{<a href="}.lnk($q, (join ".", @$open)).qq{">}
. qq{<IMG ALT="" SRC="${imghref}_${end}_${start}.png" class="img-responsive">}."</a>" ); #"
$page .= "</div></div>\n";
}
}
return $page;
}
1;
__END__
=head1 COPYRIGHT
Copyright 2007 by Tobias Oetiker
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 AUTHOR
Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
=cut

View File

@@ -0,0 +1,204 @@
# -*- perl -*-
package Smokeping::Info;
use warnings;
use strict;
use RRDs;
use Smokeping;
use Carp;
use Data::Dumper;
sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = { cfg_file => shift };
bless $self, $class;
my $parser = Smokeping::get_parser();
$self->{cfg_hash} = $parser->parse( $self->{cfg_file} )
or croak "ERROR reading config file $parser->{err}";
$self->{probe_hash} = Smokeping::load_probes $self->{cfg_hash};
return $self;
}
# get a list of all rrd files in the config file
sub __flatten_targets;
sub __flatten_targets {
my $probes = shift;
my $root = shift;
my $prefix = shift;
my @paths;
for my $target ( sort {$root->{$a}{_order} <=> $root->{$b}{_order}}
grep { ref $root->{$_} eq 'HASH' } keys %$root ) {
push @paths, __flatten_targets($probes,$root->{$target},$prefix.'/'.$target);
};
if (exists $root->{host} and not $root->{host} =~ m|/|){
my $probe = $probes->{$root->{probe}};
my $pings = $probe->_pings($root);
if (not $root->{nomasterpoll} or $root->{nomasterpoll} eq 'no') {
push @paths, { path => $prefix, pings=>$pings };
};
if ($root->{slaves}) {
for my $slave (split /\s+/,$root->{slaves}){
push @paths, { path => $prefix.'~'.$slave, pings=>$pings };
}
}
};
return @paths;
}
sub fetch_nodes {
my $self = shift;
my %args = ( 'mode' => 'plain', @_); # no mode is default
my %valid = ( pattern=>1, mode => 1 );
my %valid_modes = ( plain=>1, recursive=>1, regexp=>1);
map {
croak "Invalid fetch nodes argument '$_'"
if not $valid{$_};
} keys %args;
croak "Invalid fetch mode $args{mode}"
if not $valid_modes{$args{mode}};
my $cfg = $self->{cfg_hash};
my @flat = __flatten_targets($self->{probe_hash},$cfg->{Targets},'');
my $rx = qr{.*};
if ( defined $args{pattern} ) {
if ( $args{mode} eq 'recursive' ) {
$rx = qr{^\Q$args{pattern}\E};
}
elsif ( $args{mode} eq 'regexp' ) {
$rx = qr{$args{pattern}};
}
else {
$rx = qr{^\Q$args{pattern}\E[^/]*$};
}
}
return [ grep { $_->{path} =~ /${rx}/ } @flat ];
}
sub stat_node {
my $self = shift;
my $path = shift;
my $start = shift;
my $end = shift;
my $cfg = $self->{cfg_hash};
my ($graphret,$xs,$ys) = RRDs::graph (
'/tmp/dummy',
'--start'=>$start,
'--end'=>$end,
'DEF:loss_avg_r='.$cfg->{General}{datadir}.$path->{path}.'.rrd:loss:AVERAGE',
'CDEF:loss_avg=loss_avg_r,'.$path->{pings}.',/',
'VDEF:loss_avg_tot=loss_avg,AVERAGE',
'PRINT:loss_avg_tot:%.8le',
'DEF:loss_max_r='.$cfg->{General}{datadir}.$path->{path}.'.rrd:loss:MAX',
'CDEF:loss_max=loss_max_r,'.$path->{pings}.',/',
'VDEF:loss_max_tot=loss_max,MAXIMUM',
'PRINT:loss_max_tot:%.8le',
'VDEF:loss_now=loss_avg,LAST',
'PRINT:loss_now:%.8le',
'DEF:median_avg='.$cfg->{General}{datadir}.$path->{path}.'.rrd:median:AVERAGE',
'VDEF:median_avg_tot=median_avg,AVERAGE',
'PRINT:median_avg_tot:%.8le',
'DEF:median_min='.$cfg->{General}{datadir}.$path->{path}.'.rrd:median:MIN',
'VDEF:median_min_tot=median_min,MINIMUM',
'PRINT:median_min_tot:%.8le',
'DEF:median_max='.$cfg->{General}{datadir}.$path->{path}.'.rrd:median:MAX',
'VDEF:median_max_tot=median_max,MAXIMUM',
'PRINT:median_max_tot:%.8le',
'VDEF:median_now=median_avg,LAST',
'PRINT:median_now:%.8le'
);
my %data;
if (my $ERROR = RRDs::error()){
carp "$path->{path}: $ERROR";
} else {
@data{qw(loss_avg loss_max loss_now med_avg med_min med_max med_now)} = @$graphret;
}
return \%data;
};
1;
__END__
=head1 NAME
Smokeping::Info - Pull numerical info out of the rrd databases
=head1 OVERVIEW
This module provides methods to further process information contained in
smokeping rrd files. The smokeinfo tool is a simple wrapper around the
functionality contained in here.
my $si = Smokeping::Info->new("config/file/path");
my $array_ref = $si->fetch_nodes(pattern=>'/node/path',
mode=>'recursive');
my $hash_ref = $si->stat_node(path,start,end);
=head1 IMPLEMENTATION
=head2 new(path)
Create a new Smokeping::Info instance. Instantiating Smokeping::Info entails
reading the configuration file. This is a compute heavy procedure. So you may
want to use a single info object to handle multiple requests.
=head2 fetch_nodes(pattern=>'/...',mode=>{recursive|regexp})
The fetch_nodes method will find all nodes sitting in the given pattern
(absolute path) including the path itself. By setting the recursive mode,
all rrd files in paths below will be returned as well. In regexp mode, all
rrd paths matching the given expression will be returned.
=head2 stat_node(node,start,end)
Return a hash pointer to statistics based on the data stored in the given
rrd path.
med_avg - average median
med_min - minimal median
med_max - maximal median
med_now - current median
loss_avg - average loss
loss_max - maximum loss
loss_now - current loss
=head1 COPYRIGHT
Copyright 2009 by OETIKER+PARTNER AG
=head1 LICENSE
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 2 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 675 Mass
Ave, Cambridge, MA 02139, USA.
=head1 AUTHOR
Tobias Oetiker E<lt>tobi@oetiker.chE<gt>, development sponsored by Swisscom Hospitality
=cut
# Emacs Configuration
#
# Local Variables:
# mode: cperl
# eval: (cperl-set-style "PerlStyle")
# mode: flyspell
# mode: flyspell-prog
# End:
#
# vi: sw=4

View File

@@ -0,0 +1,339 @@
# -*- perl -*-
package Smokeping::Master;
use Data::Dumper;
use Storable qw(nstore dclone fd_retrieve);
use strict;
use warnings;
use Fcntl qw(:flock);
use Digest::HMAC_MD5 qw(hmac_md5_hex);
use File::Basename qw(dirname);
use File::Path qw(make_path);
# keep this in sync with the Slave.pm part
# only update if you have to force a parallel upgrade
my $PROTOCOL = "2";
=head1 NAME
Smokeping::Master - Master Functionality for Smokeping
=head1 OVERVIEW
This module handles all special functionality required by smokeping running
in master mode.
=head2 IMPLEMENTATION
=head3 slave_cfg=extract_config(cfg,slave)
Extract the relevant configuration information for the selected slave. The
configuration will only contain the information that is relevant for the
slave. Any parameters overwritten in the B<Slaves> section of the configuration
file will be patched for the slave.
=cut
sub get_targets;
sub get_targets {
my $trg = shift;
my $slave = shift;
my %return;
my $ok;
foreach my $key (keys %{$trg}){
# dynamic hosts can only be queried from the
# master
next if $key eq 'host' and $trg->{$key} eq 'DYNAMIC';
next if $key eq 'host' and $trg->{$key} =~ m|^/|; # skip multi targets
next if $key eq 'host' and not ( defined $trg->{slaves} and $trg->{slaves} =~ /\b${slave}\b/);
if (ref $trg->{$key} eq 'HASH'){
$return{$key} = get_targets ($trg->{$key},$slave);
$ok = 1 if defined $return{$key};
} else {
$ok = 1 if $key eq 'host';
$return{$key} = $trg->{$key};
}
}
$return{nomasterpoll} = 'no'; # slaves poll always
return ($ok ? \%return : undef);
}
sub extract_config {
my $cfg = shift;
my $slave = shift;
# get relevant Targets
my %slave_config;
$slave_config{Database} = dclone $cfg->{Database};
$slave_config{General} = dclone $cfg->{General};
$slave_config{Probes} = dclone $cfg->{Probes};
$slave_config{Targets} = get_targets($cfg->{Targets},$slave);
$slave_config{__last} = $cfg->{__last};
if ($cfg->{Slaves} and $cfg->{Slaves}{$slave} and $cfg->{Slaves}{$slave}{override}){
for my $override (keys %{$cfg->{Slaves}{$slave}{override}}){
my $node = \%slave_config;
my @keys = split /\./, $override;
my $last_key = pop @keys;
for my $key (@keys){
$node->{$key} = {}
unless $node->{$key} and ref $node->{$key} eq 'HASH';
$node = $node->{$key};
}
$node->{$last_key} = $cfg->{Slaves}{$slave}{override}{$override};
}
}
if ($slave_config{Targets}){
return Dumper \%slave_config;
} else {
return undef;
}
}
=head3 save_updates (updates)
When the cgi gets updates from a client, these updates are saved away, for
each 'target' so that the updates can be integrated into the relevant rrd
database by the rrd daemon as the next round of updates is processed. This
two stage process is chosen so that all results flow through the same code
path in the daemon.
The updates are stored in the directory configured as 'dyndir' in the 'General'
configuration section, defaulting to the value of 'datadir' from the same section
if 'dyndir' is not present.
=cut
sub slavedatadir ($) {
my $cfg = shift;
my $dir = $cfg->{General}{dyndir} ||
$cfg->{General}{datadir};
$dir =~ s{/*$}{};
return $dir;
}
sub make_slavedatadir ($) {
my $file = shift;
my $dir = dirname($file);
if (! -d $dir) {
make_path($dir, {'error' => \my $err});
if ($err && @$err) {
for my $diag (@$err) {
my ($f,$m) = %$diag;
warn "Failed to create slave cache directory [$f]: $m";
}
} else {
warn "Slave cache directory $dir created\n";
}
}
}
sub save_updates {
my $cfg = shift;
my $slave = shift;
my $updates = shift;
# name\ttime\tupdatestring
# name\ttime\tupdatestring
my %u;
for my $update (split /\n/, $updates){
my ($name, $time, $updatestring) = split /\t/, $update;
if ( ${name} =~ m{(^|/)\.\.($|/)} ){
warn "Skipping update for ${name}.${slave}.slave_cache since ".
"you seem to try todo some directory magic here. Don't!";
} else {
push @{$u{$name}}, [$time,$updatestring];
}
}
for my $name (sort keys %u){
my $file = slavedatadir($cfg) ."/${name}.${slave}.slave_cache";
for (my $i = 2; $i >= 0; $i--){
my $fh;
make_slavedatadir($file);
if ( open ($fh, '+>>' , $file) and flock($fh, LOCK_EX) ){
my $existing = [];
if (! -e $file) { # the reader unlinked it from under us
flock($fh, LOCK_UN);
close $fh;
next;
}
seek $fh, 0, 0;
if ( -s _ ){
my $in = eval { fd_retrieve $fh };
if ($@) { #error
warn "Loading $file: $@";
} else {
$existing = $in;
};
};
map {
push @{$existing}, [ $slave, $_->[0], $_->[1] ];
} @{$u{$name}};
nstore($existing, $file.$$);
rename $file.$$,$file;
flock($fh, LOCK_UN);
close $fh;
last;
} elsif ($i > 0) {
warn "Could not lock $file ($!). Trying again $i more times.\n";
sleep rand(2);
next;
}
warn "Could not update $file, giving up for now.";
close $fh;
}
}
};
=head3 get_slaveupdates
Read in all updates provided by the selected slave and return an array reference.
=cut
sub get_slaveupdates {
my $cfg = shift;
my $name = shift;
my $slave = shift;
my $file = $name . "." . $slave. ".slave_cache";
my $empty = [];
my $data;
my $datadir = $cfg->{General}{datadir};
my $dir = slavedatadir($cfg);
$file =~ s/^\Q$datadir\E/$dir/;
my $fh;
if ( open ($fh, '<', $file) ) {
if ( flock $fh, LOCK_SH ){
eval { $data = fd_retrieve $fh };
unlink $file;
flock $fh, LOCK_UN;
if ($@) { #error
warn "Loading $file: $@";
close $fh;
return $empty;
}
} else {
warn "Could not lock $file. Will skip and try again in the next round. No harm done!\n";
}
close $fh;
return $data;
}
return $empty;
}
=head3 get_secret
Read the secrets file and figure the secret for the slave which is talking to us.
=cut
sub get_secret {
my $cfg = shift;
my $slave = shift;
if (open my $hand, "<", $cfg->{Slaves}{secrets}){
while (<$hand>){
next unless /^${slave}:(\S+)/;
close $hand;
return $1;
}
} else {
print "Content-Type: text/plain\n\n";
print "WARNING: Opening secrets file $cfg->{Slaves}{secrets}: $!\n";
return '__HORRIBLE_INLINE_SIGNALING__';
}
return;
}
=head3 answer_slave
Answer the requests from the slave by accepting the data, verifying the secrets
and providing updated config information if necessary.
=cut
sub answer_slave {
my $cfg = shift;
my $q = shift;
my $slave = $q->param('slave');
my $secret = get_secret($cfg,$slave);
return if $secret eq '__HORRIBLE_INLINE_SIGNALING__';
if (not $secret){
print "Content-Type: text/plain\n\n";
print "WARNING: No secret found for slave ${slave}\n";
return;
}
my $protocol = $q->param('protocol') || '?';
if (not $protocol eq $PROTOCOL){
print "Content-Type: text/plain\n\n";
print "WARNING: I expected protocol $PROTOCOL and got $protocol from slave ${slave}. I will skip this.\n";
return;
}
my $key = $q->param('key');
my $data = $q->param('data');
my $config_time = $q->param('config_time');
if (not ref $cfg->{Slaves}{$slave} eq 'HASH'){
print "Content-Type: text/plain\n\n";
print "WARNING: I don't know the slave ${slave} ignoring it";
return;
}
# lets make sure the we share a secret
if (hmac_md5_hex($data,$secret) eq $key){
save_updates $cfg, $slave, $data;
} else {
print "Content-Type: text/plain\n\n";
print "WARNING: Data from $slave was signed with $key which does not match our expectation\n";
return;
}
# does the client need new config ?
if ($config_time < $cfg->{__last}){
my $config = extract_config $cfg, $slave;
if ($config){
print "Content-Type: application/smokeping-config\n";
print "Protocol: $PROTOCOL\n";
print "Key: ".hmac_md5_hex($config,$secret)."\n\n";
print $config;
} else {
print "Content-Type: text/plain\n\n";
print "WARNING: No targets found for slave '$slave'\n";
return;
}
} else {
print "Content-Type: text/plain\n\nOK\n";
};
return;
}
1;
__END__
=head1 COPYRIGHT
Copyright 2007 by Tobias Oetiker
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 AUTHOR
Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
=cut

View File

@@ -0,0 +1,92 @@
# -*- perl -*-
package Smokeping::RRDhelpers;
=head1 NAME
Smokeping::RRDhelpers - Functions for doing 'interesting things' with RRDs.
=head1 OVERVIEW
This module holds a collection of functions for doing advanced calculations
and effects on rrd files.
=cut
use strict;
use RRDs;
=head2 IMPLEMENTATION
=head3 get_stddev(rrd,ds,cf,start,end[,step])
Pull the data values off the rrd file and calculate the standard deviation. Nan
values get ignored in this process.
=cut
sub get_stddev{
my $rrd = shift;
my $ds = shift;
my $cf = shift;
my $start = shift;
my $end = shift;
my $step = shift;
my ($realstart,$realstep,$names,$array) = RRDs::fetch $rrd, $cf, '--start',$start, '--end',$end,($step ? ('--resolution',$step):());
if (my $err = RRDs::error){
warn $err
};
my $idx = 0;
for (@$names){
last if $ds eq $_;
$idx ++;
}
my $sum = 0;
my $sqsum = 0;
my $cnt = 0;
foreach my $line (@$array){
my $val = $line->[$idx];
if (defined $val){
$cnt++;
$sum += $val;
$sqsum += $val**2;
}
}
return undef unless $cnt;
my $sqdev = 1.0 / $cnt * ( $sqsum - $sum**2 / $cnt );
return $sqdev < 0.0 ? 0.0 : sqrt($sqdev);
}
1;
__END__
=head1 COPYRIGHT
Copyright 2007 by Tobias Oetiker
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 AUTHOR
Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
=cut

View File

@@ -0,0 +1,225 @@
package Smokeping::RRDtools;
=head1 NAME
Smokeping::RRDtools - Tools for RRD file handling
=head1 SYNOPSIS
use Smokeping::RRDtools;
use RRDs;
my $file = '/path/to/file.rrd';
# get the create arguments that $file was created with
my $create = Smokeping::RRDtools::info2create($file);
# use them to create a new file
RRDs::create('/path/to/file2.rrd', @$create);
# or compare them against another create list
my @create = ('--step', 60, 'DS:ds0:GAUGE:120:0:U', 'RRA:AVERAGE:0.5:1:1008');
my ($fatal, $comparison) = Smokeping::RRDtools::compare($file, \@create);
print "Fatal: " if $fatal;
print "Create arguments didn't match: $comparison\n" if $comparison;
Smokeping::RRDtools::tuneds($file, \@create);
=head1 DESCRIPTION
This module offers three functions, C<info2create>, C<compare> and
C<tuneds>. The first can be used to recreate the arguments that an RRD file
was created with. The second checks if an RRD file was created with the
given arguments. The thirds tunes the DS parameters according to the
supplied create string.
The function C<info2create> must be called with one argument:
the path to the interesting RRD file. It will return an array
reference of the argument list that can be fed to C<RRDs::create>.
Note that this list will never contain the C<start> parameter,
but it B<will> contain the C<step> parameter.
The function C<compare> must be called with two arguments: the path to the
interesting RRD file, and a reference to an argument list that could be fed
to C<RRDs::create>. The function will then simply compare the result of
C<info2create> with this argument list. It will return an array of two values:
C<(fatal, text)> where C<fatal> is 1 if it found a fatal difference, and 0 if not.
The C<text> will contain an error message if C<fatal == 1> and a possible warning
message if C<fatal == 0>. If C<fatal == 0> and C<text> is C<undef>, all the
arguments matched.
Note that if there is a C<start> parameter in the argument list,
C<compare> disregards it. If C<step> isn't specified, C<compare> will use
the C<rrdtool> default of 300 seconds. C<compare> ignores non-matching DS
parameters since C<tuneds> will fix them.
C<tuneds> talks on stderr about the parameters it fixes.
=head1 NOTES
This module is not particularly specific to Smokeping, it is just
distributed with it.
=head1 BUGS
Probably.
=head1 COPYRIGHT
Copyright (c) 2005 by Niko Tyni.
=head1 AUTHOR
Niko Tyni <ntyni@iki.fi>
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 SEE ALSO
RRDs(3)
=cut
use strict;
use RRDs;
# take an RRD file and make a create list out of it
sub info2create {
my $file = shift;
my @create;
# check for Perl version 5.8.0, it's buggy
# no more v-strings
my $buggy_perl_version = 1 if abs($] - 5.008000) < .0000005;
my $info = RRDs::info($file);
my $error = RRDs::error;
die("RRDs::info $file: ERROR: $error") if $error;
die("$file: unknown RRD version: $info->{rrd_version}")
unless $info->{rrd_version} eq '0001'
or $info->{rrd_version} eq '0003';
my $cf = $info->{"rra[0].cf"};
die("$file: no RRAs found?")
unless defined $cf;
my @fetch = RRDs::fetch($file, $cf, "-s 0", "-e 0");
$error = RRDs::error;
die("RRDs::fetch $file $cf: ERROR: $error") if $error;
my @ds = @{$fetch[2]};
push @create, '--step', $info->{step};
for my $ds (@ds) {
my @s = ("DS", $ds);
for (qw(type minimal_heartbeat min max)) {
die("$file: missing $_ for DS $ds?")
unless exists $info->{"ds[$ds].$_"}
or $buggy_perl_version;
my $val = $info->{"ds[$ds].$_"};
push @s, defined $val ? $val : "U";
}
push @create, join(":", @s);
}
for (my $i=0; exists $info->{"rra[$i].cf"}; $i++) {
my @s = ("RRA", $info->{"rra[$i].cf"});
for (qw(xff pdp_per_row rows)) {
die("$file: missing $_ for RRA $i")
unless exists $info->{"rra[$i].$_"}
or $buggy_perl_version;
push @s, $info->{"rra[$i].$_"};
}
push @create, join(":", @s);
}
return \@create;
}
sub compare {
my $file = shift;
my $create = shift;
my @create2 = @{info2create($file)};
my @create = @$create; # copy because we change it
# we don't compare the '--start' param
if ($create[0] eq '--start') {
shift @create;
shift @create;
}
# special check for the optional 'step' parameter
die("Internal error: didn't get the step parameter from info2create?")
unless ("--step" eq shift @create2);
my $step = shift @create2;
my $step2;
if ($create[0] eq '--step') {
shift @create;
$step2 = shift @create;
} else {
$step2 = 300; # default value
}
return (1, "Wrong value of step: $file has $step, create string has $step2")
unless $step == $step2;
my $dscount = grep /^DS/, @create;
my $dscount2 = grep /^DS/, @create2;
return (1, "Different number of data sources: $file has $dscount2, create string has $dscount")
unless $dscount == $dscount2;
my $rracount = grep /^RRA/, @create;
my $rracount2 = grep /^RRA/, @create2;
return (1, "Different number of RRAs: $file has $rracount2, create string has $rracount")
unless $rracount == $rracount2;
my $warning;
while (my $arg = shift @create) {
my $arg2 = shift @create2;
my @ds = split /:/, $arg;
my @ds2 = split /:/, $arg2;
next if $ds[0] eq 'DS' and $ds[0] eq $ds2[0] and $ds[1] eq $ds2[1] and $ds[2] eq $ds2[2];
if ($arg ne $arg2) {
if ($ds[0] eq 'RRA' and $ds[0] eq $ds2[0] and $ds[1] eq $ds2[1]) {
# non-fatal: CF is the same, but xff/steps/rows differ
$warning .= "Different RRA parameters: $file has $arg2, create string has $arg";
} else {
return (1, "Different arguments: $file has $arg2, create string has $arg");
}
}
}
return (0, $warning);
}
sub tuneds {
my $file = shift;
my $create = shift;
my @create2 = sort grep /^DS/, @{info2create($file)};
my @create = sort grep /^DS/, @$create;
while (@create){
my @ds = split /:/, shift @create;
my @ds2 = split /:/, shift @create2;
next unless $ds[1] eq $ds2[1] and $ds[2] eq $ds[2];
if ($ds[3] ne $ds2[3]){
warn "## Updating $file DS:$ds[1] heartbeat $ds2[3] -> $ds[3]\n";
RRDs::tune $file,"--heartbeat","$ds[1]:$ds[3]" unless $ds[3] eq $ds2[3];
}
if ($ds[4] ne $ds2[4]){
warn "## Updating $file DS:$ds[1] minimum $ds2[4] -> $ds[4]\n";
RRDs::tune $file,"--minimum","$ds[1]:$ds[4]" unless $ds[4] eq $ds2[4];
}
if ($ds[5] ne $ds2[5]){
warn "## Updating $file DS:$ds[1] maximum $ds2[5] -> $ds[5]\n";
RRDs::tune $file,"--maximum","$ds[1]:$ds[5]" unless $ds[5] eq $ds2[5];
}
}
}
1;

View File

@@ -0,0 +1,167 @@
# -*- perl -*-
package Smokeping::Slave;
use warnings;
use strict;
use Data::Dumper;
use Storable qw(nstore retrieve);
use Digest::HMAC_MD5 qw(hmac_md5_hex);
use LWP::UserAgent;
use Safe;
use Smokeping;
# keep this in sync with the Slave.pm part
# only update if you have to force a parallel upgrade
my $PROTOCOL = "2";
=head1 NAME
Smokeping::Slave - Slave functionality for Smokeping
=head1 OVERVIEW
The Module implements the functionality required to run in slave mode.
=head2 IMPLEMENTATION
=head3 submit_results
In slave mode we just hit our targets and submit the results to the server.
If we can not get to the server, we submit the results in the next round.
The server in turn sends us new config information if it sees that ours is
out of date.
=cut
sub get_results;
sub get_results {
my $slave_cfg = shift;
my $cfg = shift;
my $probes = shift;
my $tree = shift;
my $name = shift;
my $justthisprobe = shift; # if defined, update only the targets probed by this probe
my $probe = $tree->{probe};
my $results = [];
return [] unless $cfg;
foreach my $prop (keys %{$tree}) {
if (ref $tree->{$prop} eq 'HASH'){
my $subres = get_results $slave_cfg, $cfg, $probes, $tree->{$prop}, $name."/$prop", $justthisprobe;
push @{$results}, @{$subres};
}
next unless defined $probe;
next if defined $justthisprobe and $probe ne $justthisprobe;
my $probeobj = $probes->{$probe};
if ($prop eq 'host') {
#print "update $name\n";
my $updatestring = $probeobj->rrdupdate_string($tree);
push @$results, "$name\t".time()."\t$updatestring";
}
}
return $results;
}
sub submit_results {
my $slave_cfg = shift;
my $cfg = shift;
my $myprobe = shift;
my $probes = shift;
my $store = $slave_cfg->{cache_dir}."/data";
$store .= "_$myprobe" if $myprobe;
$store .= ".cache";
my $restore = -f $store ? retrieve $store : [];
unlink $store;
my $new = get_results($slave_cfg, $cfg, $probes, $cfg->{Targets}, '', $myprobe);
push @$restore, @$new;
my $data_dump = join("\n",@{$restore}) || "";
my $ua = LWP::UserAgent->new(
agent => 'smokeping-slave/1.0',
timeout => 60,
env_proxy => 1 );
my $response = $ua->post(
$slave_cfg->{master_url},
Content_Type => 'form-data',
Content => [
slave => $slave_cfg->{slave_name},
key => hmac_md5_hex($data_dump,$slave_cfg->{shared_secret}),
protocol => $PROTOCOL,
data => $data_dump,
config_time => $cfg->{__last} || 0,
],
);
if ($response->is_success){
my $data = $response->content;
my $key = $response->header('Key');
my $protocol = $response->header('Protocol') || '?';
if ($response->header('Content-Type') ne 'application/smokeping-config'){
warn "$data\n" unless $data =~ /OK/;
Smokeping::do_debuglog("Sent data to Server. Server said $data");
return undef;
};
if ($protocol ne $PROTOCOL){
warn "WARNING $slave_cfg->{master_url} sent data with protocol $protocol. Expected $PROTOCOL.";
return undef;
}
if (hmac_md5_hex($data,$slave_cfg->{shared_secret}) ne $key){
warn "WARNING $slave_cfg->{master_url} sent data with wrong key";
return undef;
}
# Safe seems to reset SIG on at least FreeBSD, causing slave to crash after first reload
# since all handlers are gone.
my %sig_backup = %SIG;
my $zone = new Safe;
# $zone->permit_only(???); #input welcome as to good settings
my $config = $zone->reval($data);
%SIG = %sig_backup;
if ($@){
warn "WARNING evaluating new config from server failed: $@ --\n$data";
} elsif (defined $config and ref $config eq 'HASH'){
$config->{General}{piddir} = $slave_cfg->{pid_dir};
Smokeping::do_log("Sent data to Server and got new config in response.");
return $config;
}
} else {
# ok did not manage to get our data to the server.
# we store the result so that we can try again later.
warn "WARNING Master said ".$response->status_line()."\n";
nstore $restore, $store;
}
return undef;
}
1;
__END__
=head1 COPYRIGHT
Copyright 2007 by Tobias Oetiker
=head1 LICENSE
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 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
02139, USA.
=head1 AUTHOR
Tobias Oetiker E<lt>tobi@oetiker.chE<gt>
=cut

View File

@@ -0,0 +1,111 @@
#
#
# a few variable definitions to use ciscoRttMonMIB
#
# Joerg Kummer, 10/9/03
#
package Smokeping::ciscoRttMonMIB;
require 5.004;
use vars qw($VERSION);
use Exporter;
use BER;
use SNMP_Session;
use SNMP_util "0.89";
$VERSION = '0.2';
@ISA = qw(Exporter);
sub version () { $VERSION; };
snmpmapOID("rttMonApplVersion", "1.3.6.1.4.1.9.9.42.1.1.1.0");
snmpmapOID("rttMonApplSupportedRttTypesValid", "1.3.6.1.4.1.9.9.42.1.1.7.1.2");
# generic variables for all measurement types
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonCtrl(2).rttMonCtrlAdminTable(1).rttMonCtrlAdminEntry(1)
snmpmapOID("rttMonCtrlAdminIndex", "1.3.6.1.4.1.9.9.42.1.2.1.1.1");
snmpmapOID("rttMonCtrlAdminOwner", "1.3.6.1.4.1.9.9.42.1.2.1.1.2");
snmpmapOID("rttMonCtrlAdminTag", "1.3.6.1.4.1.9.9.42.1.2.1.1.3");
snmpmapOID("rttMonCtrlAdminRttType", "1.3.6.1.4.1.9.9.42.1.2.1.1.4");
snmpmapOID("rttMonCtrlAdminThreshold", "1.3.6.1.4.1.9.9.42.1.2.1.1.5");
snmpmapOID("rttMonCtrlAdminFrequency", "1.3.6.1.4.1.9.9.42.1.2.1.1.6");
snmpmapOID("rttMonCtrlAdminTimeout", "1.3.6.1.4.1.9.9.42.1.2.1.1.7");
snmpmapOID("rttMonCtrlAdminVerifyData", "1.3.6.1.4.1.9.9.42.1.2.1.1.8");
snmpmapOID("rttMonCtrlAdminStatus", "1.3.6.1.4.1.9.9.42.1.2.1.1.9");
snmpmapOID("rttMonCtrlAdminNvgen", "1.3.6.1.4.1.9.9.42.1.2.1.1.10");
#1. For echo, pathEcho and dlsw operations
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonCtrl(2).rttMonEchoAdminTable(2).rttMonEchoAdminEntry (1)
snmpmapOID("rttMonEchoAdminProtocol", "1.3.6.1.4.1.9.9.42.1.2.2.1.1");
snmpmapOID("rttMonEchoAdminTargetAddress", "1.3.6.1.4.1.9.9.42.1.2.2.1.2");
snmpmapOID("rttMonEchoAdminPktDataRequestSize", "1.3.6.1.4.1.9.9.42.1.2.2.1.3");
snmpmapOID("rttMonEchoAdminPktDataResponseSize", "1.3.6.1.4.1.9.9.42.1.2.2.1.4");
snmpmapOID("rttMonEchoAdminTargetPort", "1.3.6.1.4.1.9.9.42.1.2.2.1.5");
snmpmapOID("rttMonEchoAdminSourceAddress", "1.3.6.1.4.1.9.9.42.1.2.2.1.6");
snmpmapOID("rttMonEchoAdminSourcePort", "1.3.6.1.4.1.9.9.42.1.2.2.1.7");
snmpmapOID("rttMonEchoAdminControlEnable", "1.3.6.1.4.1.9.9.42.1.2.2.1.8");
snmpmapOID("rttMonEchoAdminTOS", "1.3.6.1.4.1.9.9.42.1.2.2.1.9");
snmpmapOID("rttMonEchoAdminLSREnable", "1.3.6.1.4.1.9.9.42.1.2.2.1.10");
snmpmapOID("rttMonEchoAdminTargetAddressString", "1.3.6.1.4.1.9.9.42.1.2.2.1.11");
snmpmapOID("rttMonEchoAdminNameServer", "1.3.6.1.4.1.9.9.42.1.2.2.1.12");
snmpmapOID("rttMonEchoAdminOperation", "1.3.6.1.4.1.9.9.42.1.2.2.1.13");
snmpmapOID("rttMonEchoAdminHTTPVersion", "1.3.6.1.4.1.9.9.42.1.2.2.1.14");
snmpmapOID("rttMonEchoAdminURL", "1.3.6.1.4.1.9.9.42.1.2.2.1.15");
snmpmapOID("rttMonEchoAdminCache", "1.3.6.1.4.1.9.9.42.1.2.2.1.16");
snmpmapOID("rttMonEchoAdminInterval", "1.3.6.1.4.1.9.9.42.1.2.2.1.17");
snmpmapOID("rttMonEchoAdminNumPackets", "1.3.6.1.4.1.9.9.42.1.2.2.1.18");
snmpmapOID("rttMonEchoAdminProxy", "1.3.6.1.4.1.9.9.42.1.2.2.1.19");
snmpmapOID("rttMonEchoAdminString1", "1.3.6.1.4.1.9.9.42.1.2.2.1.20");
snmpmapOID("rttMonEchoAdminString2", "1.3.6.1.4.1.9.9.42.1.2.2.1.21");
snmpmapOID("rttMonEchoAdminString3", "1.3.6.1.4.1.9.9.42.1.2.2.1.22");
snmpmapOID("rttMonEchoAdminString4", "1.3.6.1.4.1.9.9.42.1.2.2.1.231");
snmpmapOID("rttMonEchoAdminString5", "1.3.6.1.4.1.9.9.42.1.2.2.1.24");
snmpmapOID("rttMonEchoAdminMode", "1.3.6.1.4.1.9.9.42.1.2.2.1.25");
snmpmapOID("rttMonEchoAdminVrfName", "1.3.6.1.4.1.9.9.42.1.2.2.1.26");
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonCtrl(2).rttMonScheduleAdminTable(5).rttMonScheduleAdminEntry(1)
snmpmapOID("rttMonScheduleAdminRttLife", "1.3.6.1.4.1.9.9.42.1.2.5.1.1");
snmpmapOID("rttMonScheduleAdminRttStartTime", "1.3.6.1.4.1.9.9.42.1.2.5.1.2");
snmpmapOID("rttMonScheduleAdminConceptRowAgeout", "1.3.6.1.4.1.9.9.42.1.2.5.1.3");
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonCtrl(2).rttMonScheduleAdminTable(5).rttMonScheduleAdminEntry(1)
snmpmapOID("rttMonScheduleAdminRttLife", "1.3.6.1.4.1.9.9.42.1.2.5.1.1");
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonCtrl(2).rttMonHistoryAdminTable(8).rttMonHistoryAdminEntry(1)
snmpmapOID("rttMonHistoryAdminNumLives", "1.3.6.1.4.1.9.9.42.1.2.8.1.1");
snmpmapOID("rttMonHistoryAdminNumBuckets", "1.3.6.1.4.1.9.9.42.1.2.8.1.2");
snmpmapOID("rttMonHistoryAdminNumSamples", "1.3.6.1.4.1.9.9.42.1.2.8.1.3");
snmpmapOID("rttMonHistoryAdminFilter", "1.3.6.1.4.1.9.9.42.1.2.8.1.4");
snmpmapOID("rttMonCtrlOperConnectionLostOccurred", "1.3.6.1.4.1.9.9.42.1.2.9.1.5");
snmpmapOID("rttMonCtrlOperTimeoutOccurred", "1.3.6.1.4.1.9.9.42.1.2.9.1.6");
snmpmapOID("rttMonCtrlOperOverThresholdOccurred", "1.3.6.1.4.1.9.9.42.1.2.9.1.7");
snmpmapOID("rttMonCtrlOperNumRtts", "1.3.6.1.4.1.9.9.42.1.2.9.1.8");
snmpmapOID("rttMonCtrlOperRttLife", "1.3.6.1.4.1.9.9.42.1.2.9.1.9");
snmpmapOID("rttMonCtrlOperState", "1.3.6.1.4.1.9.9.42.1.2.9.1.10");
snmpmapOID("rttMonCtrlOperVerifyErrorOccurred", "1.3.6.1.4.1.9.9.42.1.2.9.1.11");
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonHistory(4).rttMonHistoryCollectionTable(1).rttMonHistoryCollectionEntry(1)
snmpmapOID("rttMonStatisticsAdminNumPaths", "1.3.6.1.4.1.9.9.42.1.2.7.1.2");
snmpmapOID("rttMonStatisticsAdminNumHops", "1.3.6.1.4.1.9.9.42.1.2.7.1.3");
# cisco(9).ciscoMgmt(9).ciscoRttMonMIB(42).ciscoRttMonObjects(1).rttMonHistory(4).rttMonHistoryCollectionTable(1).rttMonHistoryCollectionEntry(1)
snmpmapOID("rttMonHistoryCollectionLifeIndex", "1.3.6.1.4.1.9.9.42.1.4.1.1.1");
snmpmapOID("rttMonHistoryCollectionBucketIndex", "1.3.6.1.4.1.9.9.42.1.4.1.1.2");
snmpmapOID("rttMonHistoryCollectionSampleIndex", "1.3.6.1.4.1.9.9.42.1.4.1.1.3");
snmpmapOID("rttMonHistoryCollectionSampleTime", "1.3.6.1.4.1.9.9.42.1.4.1.1.4");
snmpmapOID("rttMonHistoryCollectionAddress", "1.3.6.1.4.1.9.9.42.1.4.1.1.5");
snmpmapOID("rttMonHistoryCollectionCompletionTime", "1.3.6.1.4.1.9.9.42.1.4.1.1.6");
snmpmapOID("rttMonHistoryCollectionSense", "1.3.6.1.4.1.9.9.42.1.4.1.1.7");
snmpmapOID("rttMonHistoryCollectionApplSpecificSense", "1.3.6.1.4.1.9.9.42.1.4.1.1.8");
snmpmapOID("rttMonHistoryCollectionSenseDescription", "1.3.6.1.4.1.9.9.42.1.4.1.1.9");
# return 1 to indicate that all is ok..
1;

View File

@@ -0,0 +1,148 @@
package Smokeping::matchers::Avgratio;
=head1 NAME
Smokeping::matchers::Avgratio - detect changes in average median latency
=head1 OVERVIEW
The Avgratio matcher establishes a historic average median latency over
several measurement rounds. It compares this average, against a second
average latency value again build over several rounds of measurement.
=head1 DESCRIPTION
Call the matcher with the following sequence:
type = matcher
pattern = Avgratio(historic=>a,current=>b,comparator=>o,percentage=>p)
=over
=item historic
The number of median values to use for building the 'historic' average.
=item current
The number of median values to use for building the 'current' average.
=item comparator
Which comparison operator should be used to compare current/historic with percentage.
=item percentage
Right hand side of the comparison.
=back
old <--- historic ---><--- current ---> now
=head1 EXAMPLE
Take build the average median latency over 10 samples, use this to divide the
current average latency built over 2 samples and check if it is bigger than
150%.
Avgratio(historic=>10,current=>2,comparator=>'>',percentage=>150);
avg(current)/avg(historic) > 150/100
This means the matcher will activate when the current latency average is
more than 1.5 times the historic latency average established over the last
10 rounds of measurement.
=head1 COPYRIGHT
Copyright (c) 2004 by OETIKER+PARTNER AG. All rights reserved.
=head1 SPONSORSHIP
The development of this matcher has been sponsored by Virtela Communications, L<http://www.virtela.net/>.
=head1 LICENSE
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 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
=head1 AUTHOR
Tobias Oetiker <tobi@oetiker.ch>
=cut
use vars qw($VERSION);
$VERSION = 1.0;
use strict;
use base qw(Smokeping::matchers::base);
use Carp;
sub new(@)
{
my $class = shift;
my $rules = {
historic=>'\d+',
current=>'\d+',
comparator=>'(<|>|<=|>=|==)',
percentage=>'\d+(\.\d+)?' };
my $self = $class->SUPER::new($rules,@_);
$self->{param}{sub} = eval "sub {\$_[0] ".$self->{param}{comparator}." \$_[1]}";
croak "compiling comparator $self->{param}{comparator}: $@" if $@;
$self->{param}{value} = $self->{param}{percentage}/100;
return $self;
}
sub Length($)
{
my $self = shift;
return $self->{param}{historic} + $self->{param}{current};
}
sub Desc ($) {
croak "Detect changes in average median latency";
}
sub avg(@){
my $sum=0;
my $cnt=0;
for (@_){
next unless defined $_;
$sum += $_;
$cnt ++;
}
return $sum/$cnt if $cnt;
return undef;
}
sub Test($$)
{ my $self = shift;
my $data = shift; # @{$data->{rtt}} and @{$data->{loss}}
my $len = $self->Length;
my $rlen = scalar @{$data->{rtt}};
return undef
if $rlen < $len
or (defined $data->{rtt}[-$len] and $data->{rtt}[-$len] eq 'S');
my $ac = $self->{param}{historic};
my $bc = $self->{param}{current};
my $cc = $ac +$bc;
my $ha = avg(@{$data->{rtt}}[-$cc..-$bc-1]);
my $ca = avg(@{$data->{rtt}}[-$bc..-1]);
return undef unless $ha and $ca;
return &{$self->{param}{sub}}($ca/$ha,$self->{param}{value});
}

View File

@@ -0,0 +1,102 @@
package Smokeping::matchers::CheckLatency;
=head1 NAME
Smokeping::matchers::CheckLatency - Edge triggered alert to check latency is under a value for x number of samples
=head1 DESCRIPTION
Call the matcher with the following sequence:
type = matcher
edgetrigger = yes
pattern = CheckLatency(l=>latency to check against,x=>num samples required for a match)
This will create a matcher which checks for "l" latency or greater over "x" samples before raising,
and will hold the alert until "x" samples under "l" before clearing
=head1 COPYRIGHT
Copyright (c) 2006 Dylan C Vanderhoof, Semaphore Corporation
=head1 LICENSE
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 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
=head1 AUTHOR
Dylan Vanderhoof <dylanv@semaphore.com>
=cut
use strict;
use base qw(Smokeping::matchers::base);
use vars qw($VERSION);
$VERSION = 1.0;
use Carp;
use List::Util qw(min max);
# I never checked why Median works, but for some reason the first part of the hash was being passed as the rules instead
sub new(@) {
my $class = shift;
my $rules = {
l => '\d+',
x => '\d+'
};
my $self = $class->SUPER::new( $rules, @_ );
return $self;
}
# how many values should we require before raising?
sub Length($) {
my $self = shift;
return $self->{param}{x}; # Because we're edge triggered, we don't need any more than the required samples
}
sub Desc ($) {
croak "Monitor latency with a cooldown period for clearing the alert";
}
sub Test($$) {
my $self = shift;
my $data = shift; # @{$data->{rtt}} and @{$data->{loss}}
my $target = $self->{param}{l} / 1000; # Smokeping reports in seconds
my $count = 0;
my $rtt;
my @rtts = @{ $data->{rtt} };
my $x = min($self->{param}{x}, scalar @{ $data->{rtt} });
#Iterate thru last x number of samples, starting with the most recent
for (my $i=1;$i<=$x;$i++) {
$rtt = $data->{rtt}[$_-$i];
# If there's an S in the array anywhere, return prevmatch
if ( $rtt =~ /S/ ) { return $data->{prevmatch}; }
if ( $data->{prevmatch} ) {
# Alert has already been raised. Evaluate and count consecutive rtt values that are below threshold.
if ( $rtt < $target ) { $count++; }
} else {
# Alert is not raised. Evaluate and count consecutive rtt values that are above threshold.
if ( $rtt >= $target ) { $count++; }
}
}
if ( $count >= $self->{param}{x} ) {
return !$data->{prevmatch};
}
return $data->{prevmatch};
}

View File

@@ -0,0 +1,100 @@
package Smokeping::matchers::CheckLoss;
=head1 NAME
Smokeping::matchers::CheckLoss - Edge triggered alert to check loss is under a value for x number of samples
=head1 DESCRIPTION
Call the matcher with the following sequence:
type = matcher
edgetrigger = yes
pattern = CheckLoss(l=>loss to check against,x=>num samples required for a match)
This will create a matcher which checks for "l" loss or greater over "x" samples before raising,
and will hold the alert until "x" samples under "l" before clearing
=head1 COPYRIGHT
Copyright (c) 2006 Dylan C Vanderhoof, Semaphore Corporation
=head1 LICENSE
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 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
=head1 AUTHOR
Dylan Vanderhoof <dylanv@semaphore.com>
=cut
use strict;
use base qw(Smokeping::matchers::base);
use vars qw($VERSION);
$VERSION = 1.0;
use Carp;
use List::Util qw(min max);
# I never checked why Median works, but for some reason the first part of the hash was being passed as the rules instead
sub new(@) {
my $class = shift;
my $rules = {
l => '\d+',
x => '\d+'
};
my $self = $class->SUPER::new( $rules, @_ );
return $self;
}
# how many values should we require before raising?
sub Length($) {
my $self = shift;
return $self->{param}{x}; # Because we're edge triggered, we don't need any more than the required samples
}
sub Desc ($) {
croak "Monitor loss with a cooldown period for clearing the alert";
}
sub Test($$) {
my $self = shift;
my $data = shift; # @{$data->{rtt}} and @{$data->{loss}}
my $target = $self->{param}{l};
my $count = 0;
my $loss;
my $x = min($self->{param}{x}, scalar @{ $data->{loss} });
#Iterate thru last x number of samples, starting with the most recent
for (my $i=1;$i<=$x;$i++) {
$loss = $data->{loss}[$_-$i];
# If there's an S in the array anywhere, return prevmatch
if ( $loss =~ /S/ ) { return $data->{prevmatch}; }
if ( $data->{prevmatch} ) {
# Alert has already been raised. Evaluate and count consecutive loss values that are below threshold.
if ( $loss < $target ) { $count++; }
} else {
# Alert is not raised. Evaluate and count consecutive loss values that are above threshold.
if ( $loss >= $target ) { $count++; }
}
}
if ( $count >= $self->{param}{x} ) {
return !$data->{prevmatch};
}
return $data->{prevmatch};
}

View File

@@ -0,0 +1,158 @@
package Smokeping::matchers::ConsecutiveLoss;
=head1 NAME
Smokeping::matchers::ConsecutiveLoss - Raise/clear alerts according to your choice of threshold and consecutive values
=head1 DESCRIPTION
Use this matcher to raise and clear alerts according to your choice of threshold and consecutive values.
As an example, you can raise an alert on first occurrence of 50% packet loss, but choose to hold the alert
active until packet loss stays below 10% for 5 consecutive measurements.
Add the matcher to your config file using below syntax:
type = matcher
edgetrigger = yes
pattern = ConsecutiveLoss(pctlossraise=>##,stepsraise=>##,pctlossclear=>##,stepsclear=>##)
Replace the ## with integers of your choice, see below for reference:
pctlossraise - Loss values at or above this percentage will raise an alert when...
stepsraise - ... number of consecutive values have been collected
pctlossclear - Loss values below this percentage will clear an alert when...
stepsclear - ... number of consecutive values have been collected
In my environment, I define four alerts for levels like:
+packetloss_significant_instantalert
type = matcher
pattern = ConsecutiveLoss(pctlossraise=>10,stepsraise=>1,pctlossclear=>3,stepsclear=>3)
comment = Instant alert - Significant packet loss detected (At least 10% over 1 cycle). Alert will clear when loss stays at max 2% for 3 cycles
priority = 30
+packetloss_major_instantalert
type = matcher
pattern = ConsecutiveLoss(pctlossraise=>25,stepsraise=>1,pctlossclear=>3,stepsclear=>3)
comment = Instant alert - Major packet loss detected (At least 25% over 1 cycle). Alert will clear when loss stays at max 2% for 3 cycles
priority = 20
+packetloss_significant_consecutivealert
type = matcher
pattern = ConsecutiveLoss(pctlossraise=>10,stepsraise=>3,pctlossclear=>3,stepsclear=>5)
comment = Consecutive occurrence of significant packet loss detected (At least 10% over 3 cycles). Alert will clear when loss stays at max 2% for 5 cycles.
priority = 10
+packetloss_major_consecutivealert
type = matcher
pattern = ConsecutiveLoss(pctlossraise=>25,stepsraise=>3,pctlossclear=>3,stepsclear=>5)
comment = Consecutive occurrence of significant packet loss detected (At least 25% over 3 cycles). Alert will clear when loss stays at max 2% for 5 cycles.
priority = 5
=head1 COPYRIGHT
Copyright (c) 2017 Rickard Borgmaster
=head1 LICENSE
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 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
=head1 AUTHOR
Rickard Borgmaster. 2017.
Based on the CheckLoss/Checklatency matchers by Dylan Vanderhoof 2006.
=cut
use strict;
use base qw(Smokeping::matchers::base);
use vars qw($VERSION);
$VERSION = 1.0;
use Carp;
use List::Util qw(min max);
# I never checked why Median works, but for some reason the first part of the hash was being passed as the rules instead
sub new(@) {
my $class = shift;
my $rules = {
pctlossraise => '\d+',
stepsraise => '\d+',
pctlossclear => '\d+',
stepsclear => '\d+'
};
my $self = $class->SUPER::new( $rules, @_ );
return $self;
}
# how many values should we require before raising?
sub Length($) {
my $self = shift;
return max($self->{param}{stepsraise},$self->{param}{stepsclear}); # Minimum number of samples required is the greater of stepsraise/stepsclear
}
sub Desc ($) {
croak "Monitor loss with a cooldown period for clearing the alert";
}
sub Test($$) {
my $self = shift;
my $data = shift; # @{$data->{rtt}} and @{$data->{loss}}
my $count = 0;
my $loss;
my $x;
my $debug = 0; # 0 will suppress debug messages
if ($debug) { print "------------------------------------------------------------------------------------------\n"; }
# Determine number of iterations for the for-loop. if we at all have enough values yet.
if ( $data->{prevmatch} ) {
# Alert state true
if (scalar @{ $data->{loss} } < $self->{param}{stepsclear}) { return $data->{prevmatch}; } # Cannot consider $stepsclear values unless so many values actually exist in array
$x = $self->{param}{stepsclear};
} else {
# Alert state false
if (scalar @{ $data->{loss} } < $self->{param}{stepsraise}) { return $data->{prevmatch}; } # Cannot consider $stepsraise values unless so many values actually exist in array
$x = $self->{param}{stepsraise};
}
if ($debug) { print "Will evaluate $x values because previous alert state= $data->{prevmatch}\n"; }
## Start iterating thru the array
for (my $i=1;$i<=$x;$i++) {
$loss = $data->{loss}[$_-$i];
# If there's an S in the array anywhere, return prevmatch. We do not have enough values yet.
if ( $loss =~ /S/ ) { return $data->{prevmatch}; }
if ( $data->{prevmatch} ) {
# Alert has already been raised. Evaluate and count consecutive loss values that are below threshold.
if ( $loss < $self->{param}{pctlossclear} ) { $count++; }
} else {
# Alert is not raised. Evaluate and count consecutive loss values that are above threshold.
if ( $loss >= $self->{param}{pctlossraise} ) { $count++; }
}
if ($debug) { print "i: $i x: $x count: $count loss: $loss previous alarm state: $data->{prevmatch}\n"; }
}
if ( $count >= $x ) { return !$data->{prevmatch} };
return $data->{prevmatch};
}

Some files were not shown because too many files have changed in this diff Show More