1package collectors
2
3import (
4	"fmt"
5	"time"
6
7	"bosun.org/slog"
8
9	"bosun.org/metadata"
10	"bosun.org/opentsdb"
11	"github.com/BurntSushi/toml"
12	"github.com/StackExchange/httpunit"
13)
14
15func HTTPUnitTOML(filename string, freq time.Duration) error {
16	var plans httpunit.Plans
17	if _, err := toml.DecodeFile(filename, &plans); err != nil {
18		return err
19	}
20	HTTPUnitPlans(filename, &plans, freq)
21	return nil
22}
23
24func HTTPUnitHiera(filename string, freq time.Duration) error {
25	plans, err := httpunit.ExtractHiera(filename)
26	if err != nil {
27		return err
28	}
29	HTTPUnitPlans(filename, &httpunit.Plans{Plans: plans}, freq)
30	return nil
31}
32
33func HTTPUnitPlans(name string, plans *httpunit.Plans, freq time.Duration) {
34	collectors = append(collectors, &IntervalCollector{
35		F: func() (opentsdb.MultiDataPoint, error) {
36			return cHTTPUnit(plans)
37		},
38		name:     fmt.Sprintf("c_httpunit_%s", name),
39		Interval: freq,
40	})
41}
42
43func cHTTPUnit(plans *httpunit.Plans) (opentsdb.MultiDataPoint, error) {
44	ch, _, err := plans.Test("", false, nil, nil)
45	if err != nil {
46		return nil, err
47	}
48	unix_now := time.Now().Unix()
49	var md opentsdb.MultiDataPoint
50	for r := range ch {
51		if r == nil || r.Case == nil || r.Plan == nil {
52			slog.Errorf("httpunit: unexpected test plan on channel, the plan result, the case, and/or the plan result is missing.")
53			continue
54		}
55		tags := opentsdb.TagSet{
56			"protocol":     r.Case.URL.Scheme,
57			"ip":           r.Case.IP.String(),
58			"url_host":     r.Case.URL.Host,
59			"hc_test_case": r.Plan.Label,
60		}
61		metadata.AddMeta("", opentsdb.TagSet{"hc_test_case": r.Plan.Label}, "url", r.Case.URL.String(), false)
62		if r.Result == nil || r.Result.Result != nil {
63			Add(&md, "hu.error", true, tags, metadata.Gauge, metadata.Bool, descHTTPUnitError)
64			if r.Result == nil {
65				continue
66			}
67		}
68		Add(&md, "hu.error", false, tags, metadata.Gauge, metadata.Bool, descHTTPUnitError)
69		ms := int64(r.Result.TimeTotal / time.Millisecond)
70		Add(&md, "hu.socket_connected", r.Result.Connected, tags, metadata.Gauge, metadata.Bool, descHTTPUnitSocketConnected)
71		Add(&md, "hu.time_total", ms, tags, metadata.Gauge, metadata.MilliSecond, descHTTPUnitTotalTime)
72		code := 0
73		if r.Result.Resp != nil {
74			code = r.Result.Resp.StatusCode
75		}
76		Add(&md, "hu.response_code", code, tags, metadata.Gauge, metadata.StatusCode, descHTTPUnitStatusCode)
77		switch r.Case.URL.Scheme {
78		case "http", "https":
79			Add(&md, "hu.http.got_expected_code", r.Result.GotCode, tags, metadata.Gauge, metadata.Bool, descHTTPUnitExpectedCode)
80			Add(&md, "hu.http.got_expected_text", r.Result.GotText, tags, metadata.Gauge, metadata.Bool, descHTTPUnitExpectedText)
81			Add(&md, "hu.http.got_expected_regex", r.Result.GotRegex, tags, metadata.Gauge, metadata.Bool, descHTTPUnitExpectedRegex)
82			if r.Case.URL.Scheme == "https" {
83				Add(&md, "hu.cert.valid", !r.Result.InvalidCert, tags, metadata.Gauge, metadata.Bool, descHTTPUnitCertValid)
84				if resp := r.Result.Resp; resp != nil && resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 {
85					expiration := resp.TLS.PeerCertificates[0].NotAfter.Unix()
86					Add(&md, "hu.cert.expires", expiration, tags, metadata.Gauge, metadata.Timestamp, descHTTPUnitCertExpires)
87					Add(&md, "hu.cert.valid_for", expiration-unix_now, tags, metadata.Gauge, metadata.Second, descHTTPUnitCertValidFor)
88				}
89			}
90		}
91	}
92	return md, nil
93}
94
95const (
96	descHTTPUnitError           = "1 if any error (no connection, unexpected code or text) occurred, else 0."
97	descHTTPUnitSocketConnected = "1 if a connection was made, else 0."
98	descHTTPUnitExpectedCode    = "1 if the HTTP status code was expected, else 0."
99	descHTTPUnitExpectedText    = "1 if the response contained expected text, else 0."
100	descHTTPUnitExpectedRegex   = "1 if the response matched expected regex, else 0."
101	descHTTPUnitCertValid       = "1 if the SSL certificate is valid, else 0."
102	descHTTPUnitCertExpires     = "Unix epoch time of the certificate expiration."
103	descHTTPUnitCertValidFor    = "Number of seconds until certificate expiration."
104	descHTTPUnitTotalTime       = "Total time consumed by test case."
105	descHTTPUnitStatusCode      = "The HTTP status response code of the response, or 0 if no response."
106)
107