├── .gitignore
├── README.md
├── LICENSE
└── checkprovider
/.gitignore:
--------------------------------------------------------------------------------
1 | /blib/
2 | /.build/
3 | _build/
4 | cover_db/
5 | inc/
6 | Build
7 | !Build/
8 | Build.bat
9 | .last_cover_stats
10 | /Makefile
11 | /Makefile.old
12 | /MANIFEST.bak
13 | /META.yml
14 | /META.json
15 | /MYMETA.*
16 | nytprof.out
17 | /pm_to_blib
18 | *.o
19 | *.bs
20 | /_eumm/
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # checkprovider
2 | checkprovider - Generate timing information for HTTP requests.
3 |
4 | # Description
5 | checkprovider returns a breakdown of timing information for requests to
6 | a backend By default, the script runs 10
7 | queries at a rate of one per second, and outputs timing information for each
8 | request to stdout.
9 |
10 | The timing results are presented in tabulated form, with the following column
11 | headings: (all times are in milliseconds)
12 |
13 | ## Timestamp
14 |
15 | The timestamp of when the request started
16 |
17 | ## DNS
18 |
19 | The time taken to perform the DNS lookup for the server.
20 |
21 | ## Socket
22 |
23 | The time required to create a socket for this request.
24 |
25 | ## Connect
26 |
27 | The time taken to connect to the TCP endpoint.
28 |
29 | ## TLS
30 |
31 | The time taken to complete the TLS handshake.
32 |
33 | ## Write
34 |
35 | The time spent writing the HTTP request to the socket.
36 |
37 | ## 1st Byte
38 |
39 | This is the time spent waiting for the first byte of the response.
40 |
41 | ## Read
42 |
43 | The total time spent reading the response from the HTTP server.
44 |
45 | ## Close
46 |
47 | The time to close the socket after reading the full response.
48 |
49 | ## Total
50 |
51 | The total time elapsed during the request.
52 |
53 | ## Bytes
54 |
55 | The number of bytes read in the response (including response headers).
56 |
57 | ## Status
58 |
59 | This column shows either `OK` or `NOT OK` to indicate whether the request
60 | was successful (i.e. returned an HTTP 200 response). If any other response
61 | was obtained, or the initial connection was unsuccessful then all fields from
62 | `Connect` through to `Close` will be empty.
63 |
64 | checkprovider will die with an error if the initial DNS lookup or socket creation fail.
65 |
66 | ## Term
67 |
68 | This column shows the query term used for the request.
69 |
70 | ## Example
71 |
72 | Here is an example of the output:
73 |
74 |
75 | $ checkprovider https://www.yahoo.com/
76 | Request Times (ms)
77 | Timestamp DNS Socket Connect TLS Write 1stByte Read Close Total Bytes Status
78 | 2016-01-30 06:54:53 UTC 1.02 0.10 1.54 19.38 0.10 67.87 227.48 0.32 318.14 432019 OK (200)
79 | 2016-01-30 06:54:54 UTC 1.15 0.13 1.31 18.40 0.10 70.84 403.08 0.24 495.38 439123 OK (200)
80 | 2016-01-30 06:54:55 UTC 1.07 0.10 1.47 18.15 0.09 66.33 234.07 0.22 321.65 437772 OK (200)
81 | 2016-01-30 06:54:56 UTC 0.98 0.09 1.39 19.93 0.09 65.90 258.00 0.34 346.88 433950 OK (200)
82 | 2016-01-30 06:54:57 UTC 0.99 0.10 1.32 17.78 0.09 104.22 268.77 0.26 393.73 433018 OK (200)
83 | 2016-01-30 06:54:58 UTC 1.05 0.10 1.42 18.88 0.06 78.30 279.99 0.22 380.18 433947 OK (200)
84 | 2016-01-30 06:54:59 UTC 1.01 0.09 1.42 17.44 0.10 63.76 241.61 0.30 325.90 441893 OK (200)
85 | 2016-01-30 06:55:00 UTC 1.27 0.12 1.38 19.11 0.07 74.45 279.24 0.51 376.29 439629 OK (200)
86 | 2016-01-30 06:55:01 UTC 8.97 0.00 1.69 12.00 0.00 85.00 213.00 6.00 326.96 439342 OK (200)
87 | 2016-01-30 06:55:02 UTC 1.00 0.00 2.00 23.01 0.00 68.99 264.00 1.00 360.00 440608 OK (200)
88 |
89 |
90 | # Dependencies
91 |
92 | The current version is written in Perl and depends on the following non-core modules:
93 | * IO::Socket::SSL
94 |
95 | The following core modules are also required:
96 | * File::Basename
97 | * Getopt::Long
98 | * Pod::Usage
99 | * POSIX
100 | * Socket
101 | * Time::HiRes
102 |
103 | The folloring modules are optional:
104 | * URI::Encode (Note: only used for `-T` option)
105 |
106 | # Installation
107 |
108 | For now this code is not packaged (see also TODO). To use you install the dependencies and then fetch this repo. For example on redhat-based systems:
109 |
110 | $ sudo yum install perl-IO-Socket-SSL perl-URI-Encode
111 | $ git clone -o upstream git@github.com:yahoo/checkprovider.git
112 | $ export PATH=$PATH:`pwd`/checkprovider
113 |
114 | # Options
115 |
116 | The following command line options are supported by checkprovider:
117 |
118 |
119 | - --no-blank
120 | - Suppress the blank line at the end of the list of timing results.
121 |
122 | - -d|--debug
123 | - Print some debug information to stdout.
124 |
125 | - -F feed|--feed feed
126 | - Set the "feed" part of the url, eg -f /forecastxml
127 |
128 | - -f frequency|--frequency frequency
129 | - Set the frequency of the requests in seconds. This is the inverse of the query rate, and takes precendence over any rate specified with `--rate`.
130 |
131 | - -h|--no-header
132 | - Suppress the display of header at the top of the list of timing results.
133 |
134 | - -H header|--header header
135 | - Specify an additional HTTP request header. Note: this option can be specified multiple times to provide multiple request headers.
136 |
137 | - -?|--help
138 | - Display full help information.
139 |
140 | - -p period|--period period
141 | - This specifies the period, in whole seconds, over which the requests are to be made. The default is 10 seconds.
142 |
143 | - -r rate|--rate rate
144 | - This specifies the integer rate of requests per second that should be made by checkprovider. The rate and period determine the number of requests sent to the HTTP server. The default rate is 1 request per second.
145 |
146 | - -Q|--query string
147 | - The query part of the URL, eg. `-q "q=london"`.
148 |
149 | - -q|--quiet
150 | - Suppress the blank line at the end of the results as well as the header. This has the same effect as using `--no-header` and `--no-blank`.
151 |
152 | - -T|--termsfile filename
153 | - A file containing a list of terms to be used in place of the `[TERM]` string in the URL. The format of the file is one term per line.
154 |
155 | - -v|--version
156 | - Display the version information then quit.
157 |
158 | - -w host|--host host
159 | - Specify the HTTP Host header.
160 |
161 | - -U useragent|--useragent useragent
162 | - Specify the User-Agent HTTP header.
163 |
164 |
165 | # TODO
166 |
167 | This is a work in progress and we welcome pull requests. Here are a few things that still need work:
168 |
169 | * Packaging (rpm/deb/other?)
170 | * Protocol support (HTTP/1.0 and HTTP/2)
171 | * pretty graph generation
172 |
173 | # License
174 |
175 | Code licensed under the Apache License Version 2.0. See LICENSE file for terms.
176 |
177 | # Contributors
178 |
179 | * Richard Misenheimer
180 | * Ian Brayshaw
181 | * Leong-Kui Lim
182 | * Peter Ellehauge
183 | * Scott Beardsley
184 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/checkprovider:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | #
3 | # Copyright 2016, Yahoo Inc.
4 | # Copyrights licensed under the Apache 2.0 License.
5 | # See the accompanying LICENSE file for terms.
6 | #
7 | use strict;
8 | use warnings;
9 |
10 | use POSIX qw( :sys_wait_h strftime BUFSIZ );
11 | use Socket;
12 | use IO::Socket::SSL;
13 | use Pod::Usage;
14 | use Time::HiRes qw( time sleep );
15 | use Getopt::Long;
16 | use File::Basename;
17 |
18 | use constant VERSION => '1.1';
19 |
20 | #
21 | # configuration
22 | #
23 |
24 | # what's the script name?
25 | use constant NAME => basename $0;
26 |
27 | # define the configuration for this script
28 | # - what are the details of the test request?
29 | use constant PORT => 80;
30 | use constant TLSPORT => 443;
31 |
32 | # - what is the rate (per second) of requests, and how long should
33 | # the test run for?
34 | use constant RATE => 1; # 1 request per second
35 | use constant PERIOD => 10; # 10 second test duration
36 |
37 | my %UAs = (
38 | 'default' => NAME . "/" . VERSION,
39 | 'smartphone' =>
40 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25',
41 | 'tablet' =>
42 | 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10',
43 | 'desktop' =>
44 | 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
45 | 'ie6' =>
46 | 'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)',
47 | 'ie7' => 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)',
48 | 'ie8' =>
49 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)',
50 | 'ie9' => 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US)',
51 | 'ie10' =>
52 | 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0; InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)',
53 | 'ie11' =>
54 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko',
55 | 'chrome' =>
56 | 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
57 | 'firefox' =>
58 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1',
59 | 'ios6' =>
60 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25',
61 | 'ios7' =>
62 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/7.0 Mobile/10A5376e Safari/8536.25',
63 | 'ios8' =>
64 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25',
65 | 'android' =>
66 | 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19'
67 | );
68 |
69 | my $UA = "default";
70 |
71 | # - define the EOL sequence (CRLF) for HTTP
72 | use constant EOL => "\015\012";
73 |
74 | # - what's the format for displaying time?
75 | use constant TIMEFMT => "%Y-%m-%d %T %Z";
76 |
77 | #
78 | # subroutine prototypes
79 | #
80 |
81 | sub time_request ($); # time a single request
82 | sub show_header (); # display the output header
83 | sub show_response ($); # display the statistics of a single request
84 |
85 | #
86 | # process the command line options
87 | #
88 | my $version = undef; # display the script version?
89 | my $help = undef; # display the script help?
90 | my $no_header = undef; # suppress the header?
91 | my $no_blank = undef; # suppress the trailing blank line?
92 | my $output_format = "def"; #
93 | my $quiet = undef; # suppress the header and the trailing blank line?
94 | my $rate = RATE; # the rate of requests
95 | my $period = PERIOD; # the period of the requests
96 | my $feed = "/"; # the feed
97 | my $query = ""; # the query part
98 | my $termsfile = undef; # the query term list in a file
99 | my $port = PORT; # the port number for the above hostname
100 | my $frequency = undef; # the frequency of the queries
101 | my $debug = 0; # no debugging
102 | my $url = undef; #
103 | my $inputurl = undef; #
104 | my $usernotice = undef;
105 | my $httphost = undef; # the HTTP header Host:
106 | my $hostip = undef; # ip address
107 | my @headers = (); # additional request headers
108 | my @terms = (); # list of terms to replace in the query string
109 | my $position = 0; # used to keep track of which term is being used
110 |
111 | pod2usage( { -verbose => 2 } ) if ( $#ARGV < 0 );
112 |
113 | Getopt::Long::Configure('bundling');
114 | GetOptions(
115 | 'version|v' => \$version,
116 | 'no-header|h' => \$no_header,
117 | 'no-blank|b' => \$no_blank,
118 | 'format|O=s' => \$output_format,
119 | 'quiet|q' => \$quiet,
120 | 'rate|r=f' => \$rate,
121 | 'frequency|f=f' => \$frequency,
122 | 'period|p=i' => \$period,
123 | 'feed|F=s' => \$feed,
124 | 'query|Q=s' => \$query,
125 | 'termsfile|T=s' => \$termsfile,
126 | 'host|w=s' => \$httphost,
127 | 'wip=s' => \$hostip,
128 | 'port=s' => \$port,
129 | 'debug=i' => \$debug,
130 | 'd+' => \$debug,
131 | 'url|u=s' => \$inputurl,
132 | 'useragent|U=s' => \$UA,
133 | 'help|?' => \$help,
134 | 'header|H=s' => \@headers
135 | ) or pod2usage;
136 |
137 | if ( $#ARGV >= 0 ) {
138 | $inputurl = $ARGV[0];
139 | }
140 |
141 | if ( defined $inputurl ) {
142 | $url = $inputurl;
143 | $url =~ m/^(http[s])*:\/\//; # match http[s]://
144 | if ( $1 && $1 eq 'https' ) {
145 | $port = TLSPORT;
146 | }
147 | $url =~ s/^http[s]*:\/\///; # strip http[s]://
148 | if ( $url =~ /([^\/]*)(\/[^\?]*)\?(.*)/ ) {
149 | $hostip = $1;
150 | $feed = $2;
151 | $query = $3;
152 | }
153 | elsif ( $url =~ /([^\/]*)(\/[^\?]*)/ ) {
154 | $hostip = $1;
155 | $feed = $2;
156 | }
157 | else {
158 | $hostip = $url;
159 | }
160 | }
161 |
162 | # read query terms from a file store in @terms array
163 | if ( defined $termsfile ) {
164 | if ( -f $termsfile ) {
165 | open my $fh, '<:encoding(UTF-8)', $termsfile or die "cannot open file: $termsfile";
166 | chomp(@terms = <$fh>);
167 | close $fh;
168 | } else {
169 | print "FATAL: cannot find file: $termsfile\n";
170 | exit(1);
171 | }
172 | }
173 |
174 | if ( $hostip =~ m/([^:]*):(\d*)/ ) {
175 | $hostip = $1;
176 | $port = $2;
177 | }
178 |
179 | # should we display the help information?
180 | pod2usage( { -verbose => 2 } ) if ($help);
181 |
182 | # should we report the script version?
183 | print NAME . ' v' . VERSION . "\n"
184 | and exit 0
185 | if ($version);
186 |
187 | #
188 | # run the timing tests
189 | #
190 |
191 | # should we show the header?
192 | show_header unless ( $no_header || $quiet );
193 |
194 | # how many tests should we run?
195 | my $total =
196 | ( !$period ) ? 0
197 | : ( defined $frequency ) ? $period / $frequency
198 | : $period * $rate;
199 | for ( my $test = 0 ; !$period || $test < $total ; $total && $test++ ) {
200 |
201 | # fork a child process
202 | my $pid = fork;
203 |
204 | # if we are the parent process then we should commence a non-blocking wait
205 | # for child processes
206 | if ($pid) {
207 |
208 | # advance the term list position for the next child
209 | if ( ($position + 1) > $#terms ) {
210 | $position = 0;
211 | } else {
212 | $position = $position + 1;
213 | }
214 |
215 | # sleep to set the required request rate
216 | ( defined $frequency ) and sleep $frequency
217 | or sleep 1. / $rate;
218 |
219 | # don't actually need to do anything other than catch the CHLD signals
220 | 1 while ( ( my $child = waitpid( -1 => WNOHANG ) ) > 0 );
221 |
222 | # otherwise, if we are the child process, then perform the test
223 | }
224 | elsif ( defined $pid ) {
225 | show_response
226 | time_request $hostip; # run a request and display the results
227 | exit 0; # that's all folks
228 |
229 | # else, there's a problem, so give up
230 | }
231 | else {
232 | die NAME . ": could not fork(): $!\n";
233 | }
234 | }
235 |
236 | # wait for child processes to terminate
237 | 1 while ( ( my $child = waitpid( -1 => 0 ) ) > 0 );
238 |
239 | # give a blank line after this set of results (if required)
240 | print "\n" unless ( $no_blank || $quiet );
241 |
242 | #
243 | # that's all folks
244 | #
245 |
246 | exit 0;
247 |
248 | # subroutines only beyond this point
249 | ############################################################################
250 |
251 | # time_request( )
252 | #
253 | # Return a hash of component times for a single request of the host.
254 | sub time_request($) {
255 | my $hostip = shift; # what IP should we query?
256 | my %stats = (); # the collected timing statistics
257 | my $tls = 0; # secure or insecure?
258 |
259 | local $!;
260 |
261 | # perform initialisation before we start timing
262 | my $bytes;
263 | undef $bytes; # bytes read
264 | my $error;
265 | undef $error; # has an error occurred?
266 | my $socket;
267 | undef $socket; # socket handle
268 | my $content;
269 | undef $content; # the response content
270 | my @request;
271 | undef @request; # the lines of the request
272 | if ( $query eq "" ) {
273 | push @request, sprintf( "GET %s HTTP/1.1", $feed );
274 | }
275 | else {
276 | # replace query term and save off term in stats hash
277 | if ( defined $termsfile && $query =~ m/(\[TERM\])/ ) {
278 | my $term = $terms[$position];
279 | # try to encode the term if URI::Encode is available
280 | if ( eval "use URI::Encode; 1" ) {
281 | $term = (URI::Encode->new( { encode_reserved => 1 } ))->encode($term);
282 | }
283 | $query =~ s/\[TERM\]/$term/g;
284 | $stats{term} = $term;
285 | }
286 | push @request, sprintf( "GET %s?%s HTTP/1.1", $feed, $query );
287 | }
288 | push @request,
289 | sprintf( "Host: %s",
290 | defined($httphost)
291 | ? $httphost
292 | : ( $port ne PORT ? "$hostip:$port" : $hostip ) );
293 | $tls = ( $port eq TLSPORT );
294 | if ( $UAs{$UA} ) {
295 | push @request, sprintf("User-Agent: $UAs{$UA}");
296 | }
297 | else {
298 | push @request, sprintf("User-Agent: $UA");
299 | }
300 | push @request, sprintf("Connection: close");
301 | push @request, @headers;
302 | my $request = join EOL, @request, '', '';
303 | if ($debug) {
304 | print "$request";
305 | print "doing TLS? $tls\n";
306 | }
307 | my $proto = getprotobyname('tcp');
308 |
309 | # set up a routine for handling the termination of time_request()
310 | my $done = sub {
311 |
312 | # set the total response time (if it's not already set)
313 | $stats{total} ||= time - $stats{time};
314 |
315 | # set the error string (if there is one)
316 | $stats{error} ||= $_[0] || '';
317 | $stats{ip} ||= $_[1] || '';
318 |
319 | # return the response statistics
320 | return \%stats;
321 | }; # $done
322 |
323 | # record the time the test was run
324 | my $start = $stats{time} = time;
325 |
326 | # perform DNS lookup
327 | my $ip;
328 |
329 | $ip = inet_aton($hostip)
330 | or return $done->( "DNS lookup failed for " . $hostip );
331 | my $addr = sockaddr_in( $port, $ip );
332 | $stats{dns} = time - $start;
333 | $ip = inet_ntoa($ip);
334 |
335 | # create the socket
336 | $start = time;
337 | socket( $socket, PF_INET, SOCK_STREAM, $proto )
338 | or return $done->( "socket creation failed: $!", $ip );
339 | $stats{socket} = time - $start;
340 |
341 | # TCP connect
342 | $start = time;
343 | connect( $socket => $addr )
344 | or return $done->( "connection failed: " . $!, $ip );
345 | $stats{connect} = time - $start;
346 |
347 | # turn on unbuffered I/O for this socket
348 | select( ( select($socket), $| = 1 )[0] );
349 |
350 | # upgrade to TLS
351 | if ($tls) {
352 | $start = time;
353 | IO::Socket::SSL->start_SSL(
354 | $socket,
355 | {
356 | SSL_hostname => $httphost,
357 | SSL_ca_path => '/etc/ssl/certs',
358 | SSL_verify_mode => SSL_VERIFY_NONE
359 | }
360 | )
361 | or return $done->( "tls handshake failed: " . $! . $SSL_ERROR, $ip );
362 | $stats{tls} = time - $start;
363 | }
364 | else {
365 | $stats{tls} = 0;
366 | }
367 |
368 | # send the request and close the socket for writing
369 | $start = time;
370 | ( syswrite( $socket, $request ) == length $request )
371 | or return $done->( "write failed: $!", $ip );
372 |
373 | #shutdown $socket => 1;
374 | $stats{write} = time - $start;
375 |
376 | # now we want to time how long it takes to get the first byte
377 | $start = time;
378 | ( defined( sysread $socket, $content, 1 ) )
379 | or return $done->( "read failed: $!", $ip );
380 | $stats{firstbyte} = time - $start;
381 |
382 | # read the rest of the response
383 | $content .= $_ while ( sysread $socket, $_, BUFSIZ );
384 | $stats{read} = time - ( $start + $stats{firstbyte} );
385 |
386 | if ( $debug > 1 ) {
387 | print STDERR "$content\n";
388 | }
389 |
390 | # close the socket
391 | $start = time;
392 | close $socket;
393 | $stats{close} = time - $start;
394 | $stats{total} = time - $stats{time};
395 |
396 | # store the response length
397 | $stats{bytes} = ( defined $content ) ? length $content : 0;
398 |
399 | $stats{content} = $content;
400 |
401 | # was this response successful?
402 | $stats{status} = ( $content =~ m#HTTP/\S+\s+(\d+)\s+#o )[0] if ($content);
403 |
404 | # return the reference to the statistics array
405 | return $done->( "", $ip );
406 | } # time_request()
407 |
408 | # show_header()
409 | #
410 | # Display the timing information header.
411 | sub show_header() {
412 |
413 | # determine the length of the time display
414 | my $length = length strftime( TIMEFMT, localtime );
415 |
416 | # how much indentation should there be for the time?
417 | # - we want it centred above each timestamp
418 | my $time = 'Timestamp';
419 | my $indent = int( ( $length - length $time ) / 2 );
420 | $time = sprintf( "%s%-*s", ' ' x $indent, $length - $indent, $time );
421 |
422 | # generate the times header
423 | my $times = join( ' ',
424 | ' DNS',
425 | ' Socket',
426 | ' Connect',
427 | ' TLS',
428 | ' Write ',
429 | '1stByte',
430 | ' Read ',
431 | ' Close ',
432 | ' Total ' );
433 |
434 | # generate a header centred over the component time headers
435 | my $units = "Request Times (ms)";
436 | $indent = int( ( length($times) - length($units) ) / 2 );
437 | $units = sprintf( "%s%s", ' ' x ( $length + $indent ), $units );
438 |
439 | # generate the complete header
440 | my $termsheader = ( defined $termsfile ) ? " [Term]" : "";
441 | print "$units\n$time $times Bytes Status$termsheader\n";
442 | } # show_header()
443 |
444 | # show_response($)
445 | #
446 | # Display the timing information.
447 | sub show_response($) {
448 | my $stats = shift;
449 |
450 | # generate a string representation of the time of the request
451 | my $time = strftime( TIMEFMT, localtime( int $stats->{time} ) );
452 |
453 | # generate the output for this set of timings
454 | # - time
455 | # - DNS lookup
456 | # - socket creation
457 | # - connect
458 | # - TLS Handshake
459 | # - request write
460 | # - 1st byte of response
461 | # - response total
462 | # - close
463 | # - total time
464 | # - bytes
465 | # - status
466 | # - term
467 | # if the connect failed, then all other values will be undefined
468 | $stats->{$_} =
469 | ( defined $stats->{$_} )
470 | ? sprintf( "%8.2f", $stats->{$_} * 1000. )
471 | : ' ' x 8 foreach (
472 | qw( dns socket connect tls write firstbyte
473 | read close total )
474 | );
475 |
476 | # convert the number of bytes into a well presented number
477 | $stats->{bytes} =
478 | ( defined $stats->{bytes} )
479 | ? sprintf( "%6d", $stats->{bytes} )
480 | : ' ' x 6;
481 |
482 | # what status should we display?
483 | # - if we got a 200 response code, then simply display OK, otherwise, if
484 | # we got something other than OK, show NOT OK and include the response
485 | # code in the error message (provided the error message is not already
486 | # defined)
487 | if ( defined $stats->{status} ) {
488 | if ( $stats->{status} == 200 ) {
489 | $stats->{error} = 'OK';
490 | }
491 | else {
492 | $stats->{error} = 'NOT OK';
493 | }
494 | }
495 | else {
496 | $stats->{error} = 'NOT OK';
497 | }
498 |
499 | # generate the output for this response
500 | if ( $output_format eq "def" ) {
501 | print join(
502 | " ", $time,
503 | map { $stats->{$_} }
504 | qw( dns socket connect tls write firstbyte read
505 | close total )
506 | );
507 | print " ";
508 | print join( " ", map { $stats->{$_} } qw( bytes error ) );
509 | print " (" . $stats->{status} . ")";
510 | if ( defined $stats->{term} ) {
511 | print " [" . $stats->{term} . "]";
512 | }
513 | }
514 | else {
515 | print strftime( "%s ", localtime( int $stats->{time} ) );
516 | print join( " ", map { $stats->{$_} } ( split /,/, $output_format ) );
517 | }
518 | print "\n";
519 | } # show_response()
520 |
521 | __END__
522 | =head1 NAME
523 |
524 | B - Generate timing information for HTTP requests.
525 |
526 | =head1 SYNOPSIS
527 |
528 | % ./checkprovider { url | options }
529 | % ./checkprovider --help
530 |
531 | =head1 DESCRIPTION
532 |
533 | B returns a breakdown of timing information for requests to
534 | a backend By default, the script runs 10
535 | queries at a rate of one per second, and outputs timing information for each
536 | request to C.
537 |
538 | The timing results are presented in tabulated form, with the following column
539 | headings: (all times are in milliseconds)
540 |
541 | =over 4
542 |
543 | =item C
544 |
545 | This is the timestamp of when the request starts.
546 |
547 |
548 | =item C
549 |
550 | The time taken to perform the DNS lookup for the server.
551 |
552 |
553 | =item C
554 |
555 | The time required to create a socket for this request.
556 |
557 |
558 | =item C
559 |
560 | The time taken to connect to the TCP endpoint.
561 |
562 |
563 | =item C
564 |
565 | The time taken to complete the TLS handshake.
566 |
567 |
568 | =item C
569 |
570 | The time spent writing the HTTP request to the socket.
571 |
572 |
573 | =item C<1st Byte>
574 |
575 | This is the time spent waiting for the first byte of the response.
576 |
577 |
578 | =item C
579 |
580 | The total time spent reading the response from the HTTP server.
581 |
582 |
583 | =item C
584 |
585 | The time to close the socket after reading the full response.
586 |
587 |
588 | =item C
589 |
590 | The total time elapsed during the request.
591 |
592 |
593 | =item C
594 |
595 | The number of bytes read in the response (including response headers).
596 |
597 |
598 | =item C
599 |
600 | This column shows either C or C to indicate whether the request
601 | was successful (i.e. returned an HTTP 200 response). If any other response
602 | was obtained, or the initial connection was unsuccessful then all fields from
603 | C through to C will be empty.
604 |
605 | =back
606 |
607 | B will die with an error if the initial DNS lookup or socket
608 | creation fail.
609 |
610 |
611 | =head1 OPTIONS
612 |
613 | The following command line options are supported by B:
614 |
615 | =over 4
616 |
617 | =item --no-blank
618 |
619 | Suppress the blank line at the end of the list of timing results.
620 |
621 |
622 | =item -d
623 |
624 | =item --debug
625 |
626 | Print some debug information to stdout.
627 |
628 |
629 | =item -F I
630 |
631 | =item --feed I
632 |
633 | Set the "feed" part of the url, eg -f /forecastxml
634 |
635 |
636 | =item -f I
637 |
638 | =item --frequency I
639 |
640 | Set the frequency of the requests in seconds. This is the inverse of the
641 | query I, and takes precendence over any rate specified with C<--rate>.
642 |
643 |
644 | =item -h
645 |
646 | =item --no-header
647 |
648 | Suppress the display of header at the top of the list of timing results.
649 |
650 |
651 | =item -?
652 |
653 | =item --help
654 |
655 | Display this full help information
656 |
657 |
658 | =item -p I
659 |
660 | =item --period I
661 |
662 | This specifies the period, in whole seconds, over which the requests are to be
663 | made. The default is 10 seconds.
664 |
665 |
666 | =item -r I
667 |
668 | =item --rate I
669 |
670 | This specifies the integer rate of requests per second that should be made by
671 | B. The I and I determine the number of requests
672 | sent to the HTTP server. The default rate is 1 request per second.
673 |
674 |
675 | =item -Q
676 |
677 | =item --query
678 |
679 | The query part of the URL, eg. -q "q=london".
680 |
681 |
682 | =item -q
683 |
684 | =item --quiet
685 |
686 | Suppress the blank line at the end of the results as well as the header. This
687 | has the same effect as using C<--no-header> and C<--no-blank>.
688 |
689 |
690 | =item -v
691 |
692 | =item --version
693 |
694 | Display the version information then quit.
695 |
696 |
697 | =item -w I
698 |
699 | =item --host I
700 |
701 | Specify the HTTP Host header.
702 |
703 |
704 | =item -U I
705 |
706 | =item -useragent I
707 |
708 | Specify the User-Agent HTTP header.
709 |
710 |
711 | =back
712 |
713 | =head1 EXAMPLES
714 |
715 | =over 4
716 |
717 |
718 | =item checkprovider "https://example.org/"
719 |
720 | Sends the request to example.org over port 443 (since the URI scheme is https).
721 |
722 |
723 | =item checkprovider -H "Accept-Encoding: gzip, deflate" "https://example.org/"
724 |
725 | Sends the request to example.org over port 443 with the "Accept-Encoding: gzip, deflate" request header.
726 |
727 |
728 | =item checkprovider -r 20 -p 5 "https://example.org/search?q=beer"
729 |
730 | Sends the request to example.org over port 443 sending at a rate of 20 qps for a duration of 5 seconds.
731 |
732 |
733 | =item checkprovider -T termlist "https://example.org/search?p=[TERM]"
734 |
735 | Iterate through the lines in file termlist and replace [TERM] with the line before sending the request.
736 |
737 |
738 | =back
739 |
740 | =head1 ACKNOWLEDGEMENTS
741 |
742 | Derived from goto-metric.pl by Richard Misenheimer.
743 |
744 |
745 | =head1 COPYRIGHT
746 |
747 | Copyright 2016, Yahoo Inc.
748 | Copyrights licensed under the Apache 2.0 License.
749 | See the accompanying LICENSE file for terms.
750 |
751 | =head1 CONTRIBUTORS
752 |
753 | =over
754 |
755 | =item Richard Misenheimer
756 |
757 | =item Ian Brayshaw
758 |
759 | =item Leong-Kui Lim
760 |
761 | =item Peter Ellehauge
762 |
763 | =item Scott Beardsley
764 |
765 |
766 | =back
767 |
768 | =cut
769 |
--------------------------------------------------------------------------------