-
Notifications
You must be signed in to change notification settings - Fork 487
/
http.go
164 lines (141 loc) · 5.12 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package metrics
import (
"fmt"
"net/http"
"net/url"
"sort"
"time"
"github.com/go-kit/log/level"
"github.com/gorilla/mux"
"github.com/grafana/agent/pkg/metrics/cluster/configapi"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/scrape"
"github.com/prometheus/prometheus/storage/remote"
)
// WireAPI adds API routes to the provided mux router.
func (a *Agent) WireAPI(r *mux.Router) {
a.cluster.WireAPI(r)
r.HandleFunc("/agent/api/v1/instances", a.ListInstancesHandler).Methods("GET")
r.HandleFunc("/agent/api/v1/targets", a.ListTargetsHandler).Methods("GET")
r.HandleFunc("/agent/api/v1/metrics/instance/{instance}/write", a.PushMetricsHandler).Methods("POST")
}
// ListInstancesHandler writes the set of currently running instances to the http.ResponseWriter.
func (a *Agent) ListInstancesHandler(w http.ResponseWriter, _ *http.Request) {
cfgs := a.mm.ListConfigs()
instanceNames := make([]string, 0, len(cfgs))
for k := range cfgs {
instanceNames = append(instanceNames, k)
}
sort.Strings(instanceNames)
err := configapi.WriteResponse(w, http.StatusOK, instanceNames)
if err != nil {
level.Error(a.logger).Log("msg", "failed to write response", "err", err)
}
}
// ListTargetsHandler retrieves the full set of targets across all instances and shows
// information on them.
func (a *Agent) ListTargetsHandler(w http.ResponseWriter, r *http.Request) {
instances := a.mm.ListInstances()
allTagets := make(map[string]TargetSet, len(instances))
for instName, inst := range instances {
allTagets[instName] = inst.TargetsActive()
}
ListTargetsHandler(allTagets).ServeHTTP(w, r)
}
// ListTargetsHandler renders a mapping of instance to target set.
func ListTargetsHandler(targets map[string]TargetSet) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
resp := ListTargetsResponse{}
for instance, tset := range targets {
for key, targets := range tset {
for _, tgt := range targets {
var lastError string
if scrapeError := tgt.LastError(); scrapeError != nil {
lastError = scrapeError.Error()
}
resp = append(resp, TargetInfo{
InstanceName: instance,
TargetGroup: key,
Endpoint: tgt.URL().String(),
State: string(tgt.Health()),
DiscoveredLabels: tgt.DiscoveredLabels(),
Labels: tgt.Labels(),
LastScrape: tgt.LastScrape(),
ScrapeDuration: tgt.LastScrapeDuration().Milliseconds(),
ScrapeError: lastError,
})
}
}
}
sort.Slice(resp, func(i, j int) bool {
// sort by instance, then target group, then job label, then instance label
var (
iInstance = resp[i].InstanceName
iTargetGroup = resp[i].TargetGroup
iJobLabel = resp[i].Labels.Get(model.JobLabel)
iInstanceLabel = resp[i].Labels.Get(model.InstanceLabel)
jInstance = resp[j].InstanceName
jTargetGroup = resp[j].TargetGroup
jJobLabel = resp[j].Labels.Get(model.JobLabel)
jInstanceLabel = resp[j].Labels.Get(model.InstanceLabel)
)
switch {
case iInstance != jInstance:
return iInstance < jInstance
case iTargetGroup != jTargetGroup:
return iTargetGroup < jTargetGroup
case iJobLabel != jJobLabel:
return iJobLabel < jJobLabel
default:
return iInstanceLabel < jInstanceLabel
}
})
_ = configapi.WriteResponse(rw, http.StatusOK, resp)
})
}
// TargetSet is a set of targets for an individual scraper.
type TargetSet map[string][]*scrape.Target
// ListTargetsResponse is returned by the ListTargetsHandler.
type ListTargetsResponse []TargetInfo
// TargetInfo describes a specific target.
type TargetInfo struct {
InstanceName string `json:"instance"`
TargetGroup string `json:"target_group"`
Endpoint string `json:"endpoint"`
State string `json:"state"`
Labels labels.Labels `json:"labels"`
DiscoveredLabels labels.Labels `json:"discovered_labels"`
LastScrape time.Time `json:"last_scrape"`
ScrapeDuration int64 `json:"scrape_duration_ms"`
ScrapeError string `json:"scrape_error"`
}
// PushMetricsHandler provides a way to POST data directly into
// an instance's WAL.
func (a *Agent) PushMetricsHandler(w http.ResponseWriter, r *http.Request) {
// Get instance name.
instanceName, err := getInstanceName(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Get the metrics instance and serve the request.
managedInstance, err := a.InstanceManager().GetInstance(instanceName)
if err != nil || managedInstance == nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
handler := remote.NewWriteHandler(a.logger, managedInstance)
handler.ServeHTTP(w, r)
}
// getInstanceName uses gorilla/mux's route variables to extract the
// "instance" variable. If not found, getInstanceName will return an error.
func getInstanceName(r *http.Request) (string, error) {
vars := mux.Vars(r)
name := vars["instance"]
name, err := url.PathUnescape(name)
if err != nil {
return "", fmt.Errorf("could not decode instance name: %w", err)
}
return name, nil
}