func annotated(start time.Time, typ string, fn func() (common.MapStr, []TaskRunner, error)) TaskRunner { return MakeCont(func() (common.MapStr, []TaskRunner, error) { event, cont, err := fn() if err != nil { if event == nil { event = common.MapStr{} } event["error"] = look.Reason(err) } if event != nil { event.Update(common.MapStr{ "@timestamp": look.Timestamp(start), "duration": look.RTT(time.Now().Sub(start)), "type": typ, "up": err == nil, }) } for i := range cont { if fcont, ok := cont[i].(funcTask); ok { cont[i] = fcont.annotated(start, typ) } else { cont[i] = annotated(start, typ, cont[i].Run) } } return event, cont, nil }) }
func createPingFactory( config *Config, hostname string, port uint16, tls *transport.TLSConfig, request *http.Request, body []byte, validator RespCheck, ) func(*net.IPAddr) monitors.TaskRunner { fields := common.MapStr{ "scheme": request.URL.Scheme, "port": port, "url": request.URL.String(), } timeout := config.Timeout isTLS := request.URL.Scheme == "https" checkRedirect := makeCheckRedirect(config.MaxRedirects) return monitors.MakePingIPFactory(fields, func(ip *net.IPAddr) (common.MapStr, error) { addr := net.JoinHostPort(ip.String(), strconv.Itoa(int(port))) d := &dialchain.DialerChain{ Net: dialchain.ConstAddrDialer("tcp_connect_rtt", addr, timeout), } if isTLS { d.AddLayer(dialchain.TLSLayer("tls_handshake_rtt", tls, timeout)) } measures := common.MapStr{} dialer, err := d.BuildWithMeasures(measures) if err != nil { return nil, err } var httpStart, httpEnd time.Time client := &http.Client{ CheckRedirect: checkRedirect, Timeout: timeout, Transport: &SimpleTransport{ Dialer: dialer, OnStartWrite: func() { httpStart = time.Now() }, OnStartRead: func() { httpEnd = time.Now() }, }, } event, err := execPing(client, request, body, timeout, validator) if event == nil { event = measures } else { event.Update(measures) } if !httpEnd.IsZero() { event["http_rtt"] = look.RTT(httpEnd.Sub(httpStart)) } return event, err }) }
func createPingIPFactory(config *Config) func(*net.IPAddr) (common.MapStr, error) { return func(ip *net.IPAddr) (common.MapStr, error) { rtt, _, err := loop.ping(ip, config.Timeout, config.Wait) if err != nil { return nil, err } return common.MapStr{ "icmp_rtt": look.RTT(rtt), }, nil } }
func withStart(field string, start time.Time, r TaskRunner) TaskRunner { return MakeCont(func() (common.MapStr, []TaskRunner, error) { event, cont, err := r.Run() if event != nil { event[field] = look.RTT(time.Now().Sub(start)) } for i := range cont { cont[i] = withStart(field, start, cont[i]) } return event, cont, err }) }
func pingHost( dialer transport.Dialer, host string, timeout time.Duration, validator ConnCheck, ) (common.MapStr, reason.Reason) { start := time.Now() deadline := start.Add(timeout) conn, err := dialer.Dial("tcp", host) if err != nil { debugf("dial failed with: %v", err) return nil, reason.IOFailed(err) } defer conn.Close() if validator == nil { // no additional validation step => ping success return common.MapStr{}, nil } if err := conn.SetDeadline(deadline); err != nil { debugf("setting connection deadline failed with: %v", err) return nil, reason.IOFailed(err) } validateStart := time.Now() err = validator.Validate(conn) if err != nil && err != errRecvMismatch { debugf("check failed with: %v", err) return nil, reason.IOFailed(err) } end := time.Now() event := common.MapStr{ "validate_rtt": look.RTT(end.Sub(validateStart)), } if err != nil { event["error"] = reason.FailValidate(err) } return event, nil }
func execPing( client *http.Client, req *http.Request, body []byte, timeout time.Duration, validator func(*http.Response) error, ) (common.MapStr, reason.Reason) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() req = req.WithContext(ctx) if len(body) > 0 { req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) req.ContentLength = int64(len(body)) } start := time.Now() resp, err := client.Do(req) end := time.Now() if err != nil { return nil, reason.IOFailed(err) } defer resp.Body.Close() if err := validator(resp); err != nil { return nil, reason.ValidateFailed(err) } rtt := end.Sub(start) event := common.MapStr{ "response": common.MapStr{ "status": resp.StatusCode, }, "rtt": look.RTT(rtt), } return event, nil }
func MakeByHostJob( name, typ string, host string, settings IPSettings, pingFactory func(ip *net.IPAddr) TaskRunner, ) (Job, error) { network := settings.Network() if network == "" { return nil, errors.New("pinging hosts requires ipv4 or ipv6 mode enabled") } mode := settings.Mode if mode == PingAny { return MakeJob(name, typ, func() (common.MapStr, []TaskRunner, error) { event := common.MapStr{"host": host} dnsStart := time.Now() ip, err := net.ResolveIPAddr(network, host) if err != nil { return event, nil, err } dnsEnd := time.Now() dnsRTT := dnsEnd.Sub(dnsStart) event["resolve_rtt"] = look.RTT(dnsRTT) event["ip"] = ip.String() return WithFields(event, pingFactory(ip)).Run() }), nil } filter := makeIPFilter(network) return MakeJob(name, typ, func() (common.MapStr, []TaskRunner, error) { event := common.MapStr{"host": host} // TODO: check for better DNS IP lookup support: // - The net.LookupIP drops ipv6 zone index // dnsStart := time.Now() ips, err := net.LookupIP(host) if err != nil { return event, nil, err } dnsEnd := time.Now() dnsRTT := dnsEnd.Sub(dnsStart) event["resolve_rtt"] = look.RTT(dnsRTT) if filter != nil { ips = filterIPs(ips, filter) } if len(ips) == 0 { err := fmt.Errorf("no %v address resolvable for host %v", network, host) return event, nil, err } // create ip ping tasks cont := make([]TaskRunner, len(ips)) for i, ip := range ips { addr := &net.IPAddr{IP: ip} fields := event.Clone() fields["ip"] = ip.String() cont[i] = WithFields(fields, pingFactory(addr)) } return nil, cont, nil }), nil }
func measureEventRTT(event common.MapStr, name string) DialLayerCallback { return &MeasureLayerRTTCB{Callback: func(start, end time.Time) { event[name] = look.RTT(end.Sub(start)) }} }