diff --git a/cmd/realm/main.go b/cmd/realm/main.go index b662671..1ea5270 100644 --- a/cmd/realm/main.go +++ b/cmd/realm/main.go @@ -9,8 +9,9 @@ import ( ) var args struct { - Zones []string `arg:"--zone,positional,help:DNS zone files to serve from this server"` - Bind string `arg:"help:[]: to bind too"` + Zones []string `arg:"--zone,positional,help:DNS zone files to serve from this server"` + Bind string `arg:"help:[]: to bind too"` + StatsD string `arg:"--statsd,help:: to send StatsD metrics to"` } func main() { @@ -40,6 +41,12 @@ func main() { // Create and start the server log.Printf("starting the server on \"%s\"\n", args.Bind) - var server *realm.Server = realm.NewServer(args.Bind, registry) + var server *realm.Server + var err error + server, err = realm.NewServer(args.Bind, registry, args.StatsD) + if err != nil { + log.Fatal(err) + } + log.Fatal(server.ListenAndServe()) } diff --git a/server.go b/server.go index 2b7a647..08f433a 100644 --- a/server.go +++ b/server.go @@ -1,15 +1,23 @@ package realm -import "github.com/miekg/dns" +import ( + "fmt" + "time" + + "github.com/DataDog/datadog-go/statsd" + "github.com/miekg/dns" +) // A Server listens for DNS requests over UDP and responds with answers from the provided Zone. type Server struct { server *dns.Server registry *Registry + statsd *statsd.Client } // NewServer returns a new initialized *Server that will bind to listen and will look up answers from zone. -func NewServer(listen string, registry *Registry) *Server { +func NewServer(listen string, registry *Registry, statsdHost string) (*Server, error) { + var err error var s *Server s = &Server{registry: registry} s.server = &dns.Server{ @@ -17,7 +25,14 @@ func NewServer(listen string, registry *Registry) *Server { Net: "udp", Handler: s, } - return s + if statsdHost != "" { + s.statsd, err = statsd.New(statsdHost) + if err != nil { + return nil, err + } + s.statsd.Namespace = "realm." + } + return s, err } // ListenAndServe will start the nameserver on the configured address. @@ -28,6 +43,12 @@ func (s *Server) ListenAndServe() error { // ServeDNS will be called for every DNS request to this server. // It will attempt to provide answers to all questions from the configured zone. func (s *Server) ServeDNS(w dns.ResponseWriter, request *dns.Msg) { + // Capture starting time for measuring message response time + var start float64 + var end float64 + var elapsed float64 + start = float64(time.Now().Unix()) * 1000.0 + // Setup the default response var response *dns.Msg response = &dns.Msg{} @@ -36,11 +57,45 @@ func (s *Server) ServeDNS(w dns.ResponseWriter, request *dns.Msg) { // Lookup answers to any of the questions for _, question := range request.Question { + // Capture starting time for measuring lookup + var lookupStart float64 + var lookupEnd float64 + var lookupElapsed float64 + lookupStart = float64(time.Now().Unix()) * 1000.0 + + // Perform lookup for this question var records []dns.RR records = s.registry.Lookup(question.Name, question.Qtype, question.Qclass) + + // Capture ending and elapsed time + lookupEnd = float64(time.Now().Unix()) * 1000.0 + lookupElapsed = lookupEnd - lookupStart + + // Append results to the response response.Answer = append(response.Answer, records...) + + // If StatsD is enabled, record some metrics + if s.statsd != nil { + s.statsd.TimeInMilliseconds("lookup.time", lookupElapsed, nil, 1) + s.statsd.Histogram("lookup.answer", float64(len(records)), nil, 1) + + var tags []string + tags = []string{ + fmt.Sprintf("name:%s", question.Name), + fmt.Sprintf("qtype:%s", dns.TypeToString[question.Qtype]), + fmt.Sprintf("qclass:%s", dns.ClassToString[question.Qclass]), + } + s.statsd.Count("request.question", 1, tags, 1) + } } // Respond to the request w.WriteMsg(response) + + // Record any ending metrics + end = float64(time.Now().Unix()) * 1000.0 + elapsed = end - start + if s.statsd != nil { + s.statsd.TimeInMilliseconds("request.time", elapsed, nil, 1) + } }