1#!/usr/local/bin/bash
2#
3# Plugin to graph response times of the specified websites/URLs.
4#
5# Parameters:
6#
7#       config   (required)
8#       autoconf (optional - used by lrrd-config)
9#
10# Configuration example:
11#
12# [wget_page]
13# timeout 30
14# env.names url1 url2
15# env.timeout 20
16# env.error_value 60
17# env.max 120
18#
19# env.url_url1 http://www1.example.com/path1/page1
20# env.label_url1 Example URL#1
21# env.timeout_url1 10
22# env.warning_url1 5
23# env.critical_url1 8
24#
25# env.url_url2 https://www2.example.com/path2/page2
26# env.label_url2 Example URL#2
27# env.timeout_url2 30
28# env.warning_url2 15
29# env.critical_url2 20
30# env.wget_opts_url2 --no-cache --tries=1 --no-check-certificate
31#
32# URL options:
33#
34# You can define the following options for each specified URL
35# as seen in the above example.
36#
37# - url: the URL to be downloaded with Wget
38# - label: the label assigned to the line of the given in URL in the graph
39# - timeout: the value passed to Wget through the "--timeout" option.
40# - warning: the value for the given URL that stands for the warning level
41# - critical: the value for the given URL that stands for the critical level
42# - max: the maximum value for the given URL (values above this will be
43#     discarded)
44# - error_value: the value for the given URL that will be used to mark when
45#     Wget returned an error. A zero error_value causes the plugin to ignore
46#     Wget's return value.
47# - regex_error_value: the value for the given URL that will be used to mark
48#     when a regular expression match failed (either for the HTTP response
49#     headers or the body).
50# - regex_header_<n>: a regular expression that the HTTP response header must
51#     match or the plugin will return regex_error_value for the given URL.
52#     By default the plugin uses egrep, thus extended regexps are expected.
53#     You can define any number of regexps, but you've to start the index at
54#     "1" and increase it sequentially.
55#     I.e. regex_header_1, regex_header_2, ...
56# - regex_body_<n>: same as regex_header_<n>, but matches the HTTP response
57#     body.
58# - grep_opts: various options supplied to grep for regexp matches for the
59#     given URL. By default these are: -E (use of extended regexps)
60#     and -i (case insensitive regexp matching)
61# - wget_opts: various options supplied to the Wget command for the given URL.
62# - join_lines: if "true" and regexp matching is applied, the HTTP response body
63#     is stripped of newline ("\n") characters. This helps with complex regexps
64#     since grep can match only in a single line at a time and it would not be
65#     possible to match on complex HTML/XML structures otherwise.
66#     This is enabled by default.
67#
68# $Log$
69#
70# Revision 1.0  2006/07/11 08:49:43 cipixul@gmail.com
71# Initial version
72#
73# Revision 2.0  2010/03/25 13:46:13 muzso@muzso.hu
74# Rewrote most of the code. Added multips-like options.
75#
76# Revision 2.1  2010/04/22 11:43:53 muzso@muzso.hu
77# Added regular expression matching against the contents of the checked URL.
78#
79# Revision 2.2  2010/04/23 15:21:12 muzso@muzso.hu
80# Bugfix. Regexp matching on HTTP response bodies with a trailing newline
81# was flawed.
82#
83#%# family=auto
84#%# capabilities=autoconf
85
86[ -n "${wget_bin}" ] || wget_bin=$(which wget)
87[ -n "${time_bin}" ] || time_bin=$(which time)
88[ -n "${mktemp_bin}" ] || mktemp_bin=$(which mktemp)
89[ -n "${grep_bin}" ] || grep_bin=$(which grep)
90[ -n "${tail_bin}" ] || tail_bin=$(which tail)
91
92default_error_value=30
93default_regex_error_value=40
94default_grep_opts="-E -i"
95default_wget_opts="--no-cache --tries=1"
96default_timeout=20
97default_join_lines=true
98
99if [ "${1}" = "autoconf" ]; then
100  if [ -z "$wget_bin" ] || [ ! -f "$wget_bin" ] || [ ! -x "$wget_bin" ]; then
101    echo "no (missing 'wget' executable)"
102  elif [ -z "$time_bin" ] || [ ! -f "$time_bin" ] || [ ! -x "$time_bin" ]; then
103    echo "no (missing 'time' executable)"
104  elif [ -z "$mktemp_bin" ] || [ ! -f "$mktemp_bin" ] || [ ! -x "$mktemp_bin" ]; then
105    echo "no (missing 'mktemp' executable)"
106  elif [ -z "$grep_bin" ] || [ ! -f "$grep_bin" ] || [ ! -x "$grep_bin" ]; then
107    echo "no (missing 'grep' executable)"
108  elif [ -z "$tail_bin" ] || [ ! -f "$tail_bin" ] || [ ! -x "$tail_bin" ]; then
109    echo "no (missing 'tail' executable)"
110  else
111    echo "yes"
112  fi
113  exit 0
114fi
115
116if [ -z "${names}" ]; then
117  echo "Configuration required"
118  exit 1
119fi
120
121[ -n "${error_value}" ] || error_value=${default_error_value}
122[ -n "${regex_error_value}" ] || regex_error_value=${default_regex_error_value}
123[ -n "${grep_opts}" ] || grep_opts=${default_grep_opts}
124[ -n "${wget_opts}" ] || wget_opts=${default_wget_opts}
125[ -n "${timeout}" ] || timeout=${default_timeout}
126[ -n "${join_lines}" ] || join_lines=${default_join_lines}
127[ -n "${warning}" ] || warning=$((timeout/2))
128[ -n "${critical}" ] || critical=${timeout}
129[ -n "${max}" ] || max=$((timeout*2))
130
131if [ "${1}" = "config" ]; then
132  echo "graph_title wget loadtime of webpages"
133  echo "graph_args --base 1000 -l 0"
134  echo "graph_scale no"
135  echo "graph_vlabel Load time in seconds"
136  echo "graph_category webserver"
137  echo "graph_info This graph shows load time in seconds of one or more urls"
138  I=1
139  for name in ${names}; do
140    eval iurl='${url_'${name}'}'
141    if [ -n "${iurl}" ]; then
142      eval ilabel='${label_'${name}':-url${I}}'
143      eval iwarning='${warning_'${name}':-${warning}}'
144      eval icritical='${critical_'${name}':-${critical}}'
145      eval imax='${max_'${name}':-${max}}'
146      cat << EOH
147loadtime${I}.label ${ilabel}
148loadtime${I}.info Load time for ${iurl}
149loadtime${I}.min 0
150loadtime${I}.max ${imax}
151EOH
152      [ ${iwarning} -gt 0 ] && echo "loadtime${I}.warning ${iwarning}"
153      [ ${icritical} -gt 0 ] && echo "loadtime${I}.critical ${icritical}"
154      I=$((I+1))
155    fi
156  done
157  exit 0
158fi
159
160I=1
161for name in ${names}; do
162  eval iurl='${url_'${name}'}'
163  if [ -n "${iurl}" ]; then
164    eval ierror_value='${error_value_'${name}':-${error_value}}'
165    eval iregex_error_value='${regex_error_value_'${name}':-${regex_error_value}}'
166    eval igrep_opts='${grep_opts_'${name}':-${grep_opts}}'
167    eval iwget_opts='${wget_opts_'${name}':-${wget_opts}}'
168    eval iwget_post_data='${wget_post_data_'${name}':-${wget_post_data}}'
169    eval ijoin_lines='${join_lines_'${name}':-${join_lines}}'
170    eval itimeout='${timeout_'${name}':-${timeout}}'
171    loadtime=""
172    tempfile=$(mktemp)
173    if [ -z "${iwget_post_data}" ]; then
174      timing=$(${time_bin} -p ${wget_bin} --save-headers --no-directories --output-document "${tempfile}" --timeout ${itimeout} ${iwget_opts} "${iurl}" 2>&1)
175    else
176      timing=$(${time_bin} -p ${wget_bin} --post-data "${iwget_post_data}" --save-headers --no-directories --output-document "${tempfile}" --timeout ${itimeout} ${iwget_opts} "${iurl}" 2>&1)
177    fi
178    wget_result=$?
179    if [ -f "${tempfile}" ]; then
180      if [ ${wget_result} -ne 0 -a ${ierror_value} -gt 0 ]; then
181        loadtime=${ierror_value}
182      else
183        tempheader=""
184        tempbody=""
185        K=0
186        while [ -z "${loadtime}" ]; do
187          K=$((K+1))
188          eval iregex_header='${regex_header_'${K}'_'${name}':-${regex_header_'${K}'}}'
189          eval iregex_body='${regex_body_'${K}'_'${name}':-${regex_body_'${K}'}}'
190          [ -z "${iregex_header}" -a -z "${iregex_body}" ] && break
191          if [ ${K} -eq 1 ]; then
192            OIFS="${IFS}"
193            # we skip carriage return characters from the end of header lines
194            IFS=$(echo -en "\r")
195            inheader=0
196            # The "read" command reads only lines terminated by a specific
197            # character (which by default the newline char).
198            # To read the end of the file (the bytes after the last newline) too
199            # we append a newline.
200            echo "" >> "${tempfile}"
201            while read -r line; do
202              if [ -z "${line}" ]; then
203                inheader=1
204                # We reached the border of the header and the body.
205                # Setting IFS to an empty string puts the entire read lines from
206                # the body into our "line" variable.
207                IFS=""
208              else
209                if [ ${inheader} -eq 0 ]; then
210                  tempheader="${tempheader}${line}
211"
212                else
213                  if [ "${ijoin_lines}" = "true" ]; then
214                    tempbody="${tempbody}${line}"
215                  else
216                    tempbody="${tempbody}${line}
217"
218                  fi
219                fi
220              fi
221            done < "${tempfile}"
222            IFS="${OIFS}"
223          fi
224          if [ -n "${iregex_header}" ] && ! echo "${tempheader}" | ${grep_bin} -qs ${igrep_opts} "${iregex_header}" 2> /dev/null; then
225            loadtime=${iregex_error_value}
226          else
227            if [ -n "${iregex_body}" ] && ! echo "${tempbody}" | ${grep_bin} -qs ${igrep_opts} "${iregex_body}" 2> /dev/null; then
228              loadtime=${iregex_error_value}
229            fi
230          fi
231        done
232        if [ -z "${loadtime}" ]; then
233          loadtime=$(echo "${timing}" | grep "^real *[0-9]" | cut -d ' ' -f 2)
234        fi
235      fi
236      rm -f "${tempfile}" > /dev/null 2>&1
237    else
238      loadtime=$((ierror_value*2))
239    fi
240    echo "loadtime${I}.value ${loadtime}"
241    I=$((I+1))
242  fi
243done
244
245