diff options
Diffstat (limited to 'bind-exporter/main.go')
-rw-r--r-- | bind-exporter/main.go | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/bind-exporter/main.go b/bind-exporter/main.go new file mode 100644 index 0000000..55b1eac --- /dev/null +++ b/bind-exporter/main.go | |||
@@ -0,0 +1,141 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "encoding/json" | ||
6 | "fmt" | ||
7 | "net/http" | ||
8 | "net/url" | ||
9 | "strconv" | ||
10 | "strings" | ||
11 | "time" | ||
12 | |||
13 | "github.com/prometheus/client_golang/prometheus" | ||
14 | "github.com/prometheus/client_golang/prometheus/promhttp" | ||
15 | ) | ||
16 | |||
17 | type BindStats struct { | ||
18 | Version string `json:"json-stats-version"` | ||
19 | BindVersion string `json:"version"` | ||
20 | BootTime time.Time `json:"boot-time"` | ||
21 | ConfigTime time.Time `json:"config-time"` | ||
22 | OperationCodeCount map[string]int `json:"opcodes"` | ||
23 | ResponseCodeCount map[string]int `json:"rcodes"` | ||
24 | QueryTypeCount map[string]int `json:"qtypes"` | ||
25 | NameserverStats map[string]int `json:"nsstats"` | ||
26 | ZoneStats map[string]int `json:"zonestats"` | ||
27 | MemoryStats MemoryStats `json:"memory"` | ||
28 | ResolverStats struct{ Mismatch int } `json:"resstats"` | ||
29 | SocketStats map[string]int `json:"sockstats"` | ||
30 | TrafficHistograms map[string]Histogram `json:"traffic"` | ||
31 | Views map[string]View `json:"views"` | ||
32 | } | ||
33 | |||
34 | type Histogram struct { | ||
35 | Buckets []HistogramBucket | ||
36 | } | ||
37 | |||
38 | func (h *Histogram) UnmarshalJSON(v []byte) error { | ||
39 | mv := map[string]int{} | ||
40 | |||
41 | if err := json.Unmarshal(v, &mv); err != nil { | ||
42 | return err | ||
43 | } | ||
44 | |||
45 | if h.Buckets == nil { | ||
46 | h.Buckets = make([]HistogramBucket, 0, len(mv)) | ||
47 | } | ||
48 | |||
49 | for k, v := range mv { | ||
50 | var min, max int | ||
51 | |||
52 | if strings.HasSuffix(k, "+") { | ||
53 | min, _ = strconv.Atoi(k[:len(k)-1]) | ||
54 | max = 0 | ||
55 | } else { | ||
56 | mm := strings.Split(k, "-") | ||
57 | min, _ = strconv.Atoi(mm[0]) | ||
58 | max, _ = strconv.Atoi(mm[1]) | ||
59 | } | ||
60 | h.Buckets = append(h.Buckets, HistogramBucket{ | ||
61 | Min: min, | ||
62 | Max: max, | ||
63 | Observations: v, | ||
64 | }) | ||
65 | } | ||
66 | |||
67 | return nil | ||
68 | } | ||
69 | |||
70 | type HistogramBucket struct { | ||
71 | Min int | ||
72 | Max int | ||
73 | Observations int | ||
74 | } | ||
75 | |||
76 | type View struct { | ||
77 | Zones []Zone `json:"zones"` | ||
78 | Resolver ViewResolverStats `json:"resolver"` | ||
79 | } | ||
80 | |||
81 | type Zone struct { | ||
82 | Name string `json:"name"` | ||
83 | Class string `json:"class"` | ||
84 | Serial int `json:"serial"` | ||
85 | Type string `json:"type"` | ||
86 | LoadTime time.Time `json:"loaded"` | ||
87 | ResponseCodes map[string]int `json:"rcodes"` | ||
88 | QueryTypes map[string]int `json:"qtypes"` | ||
89 | } | ||
90 | |||
91 | type ViewResolverStats struct { | ||
92 | Stats map[string]int `json:"stats"` | ||
93 | CacheStats map[string]int `json:"cachestats"` | ||
94 | QueryTypes map[string]int `json:"qtypes"` | ||
95 | CacheRecordTypes map[string]int `json:"cache"` | ||
96 | ADB struct { | ||
97 | NumberOfEntries int `json:"nentries"` | ||
98 | EntryCount int `json:"entriescnt"` | ||
99 | NumberOfNames int `json:"nnames"` | ||
100 | NamesCount int `json:"namescnt"` | ||
101 | } `json:"adb"` | ||
102 | } | ||
103 | |||
104 | type MemoryStats struct { | ||
105 | TotalUse int `json:"TotalUse"` | ||
106 | InUse int `json:"InUse"` | ||
107 | Malloced int `json:"Malloced"` | ||
108 | BlockSize int `json:"BlockSize"` | ||
109 | ContextSize int `json:"ContextSize"` | ||
110 | Lost int `json:"Lost"` | ||
111 | } | ||
112 | |||
113 | func main() { | ||
114 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
115 | target := r.URL.Query().Get("target") | ||
116 | if target == "" { | ||
117 | http.Error(w, "'target' must be specified exactly once", http.StatusBadRequest) | ||
118 | return | ||
119 | } | ||
120 | |||
121 | u, err := url.Parse(fmt.Sprintf("http://%s", target)) | ||
122 | if err != nil { | ||
123 | http.Error(w, "unable to parse target URL", http.StatusBadRequest) | ||
124 | return | ||
125 | } | ||
126 | |||
127 | u.Scheme = "http" | ||
128 | u.Path = "/json" | ||
129 | |||
130 | ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) | ||
131 | defer cancel() | ||
132 | |||
133 | registry := prometheus.NewRegistry() | ||
134 | c := NewBindCollector(ctx, u.String()) | ||
135 | registry.MustRegister(c) | ||
136 | h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) | ||
137 | h.ServeHTTP(w, r) | ||
138 | }) | ||
139 | |||
140 | http.ListenAndServe(":8080", nil) | ||
141 | } | ||