Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/supervisor/pkg/metrics/reporter.go
2500 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package metrics
6
7
import (
8
"bytes"
9
"context"
10
"encoding/json"
11
"fmt"
12
"io/ioutil"
13
"net/http"
14
"time"
15
16
"github.com/gitpod-io/gitpod/common-go/log"
17
api "github.com/gitpod-io/gitpod/ide-metrics-api"
18
"github.com/prometheus/client_golang/prometheus"
19
dto "github.com/prometheus/client_model/go"
20
)
21
22
type GrpcMetricsReporter struct {
23
Registry *prometheus.Registry
24
supportedMetrics map[string]bool
25
26
values map[string]float64
27
addCounter func(name string, labels map[string]string, count uint64)
28
addHistogram func(name string, labels map[string]string, count uint64, sum float64, buckets []uint64)
29
30
onUnexpected func(family *dto.MetricFamily)
31
}
32
33
func NewGrpcMetricsReporter(gitpodHost string) *GrpcMetricsReporter {
34
return &GrpcMetricsReporter{
35
Registry: prometheus.NewRegistry(),
36
supportedMetrics: map[string]bool{
37
"grpc_server_handled_total": true,
38
"grpc_server_msg_received_total": true,
39
"grpc_server_msg_sent_total": true,
40
"grpc_server_started_total": true,
41
"grpc_server_handling_seconds": true,
42
"supervisor_ide_ready_duration_total": true,
43
"supervisor_initializer_bytes_second": true,
44
"supervisor_client_handled_total": true,
45
"supervisor_client_handling_seconds": true,
46
"supervisor_ssh_tunnel_opened_total": true,
47
"supervisor_ssh_tunnel_closed_total": true,
48
},
49
values: make(map[string]float64),
50
addCounter: func(name string, labels map[string]string, value uint64) {
51
doAddCounter(gitpodHost, name, labels, value)
52
},
53
addHistogram: func(name string, labels map[string]string, count uint64, sum float64, buckets []uint64) {
54
doAddHistogram(gitpodHost, name, labels, count, sum, buckets)
55
},
56
onUnexpected: logUnexpectedMetric,
57
}
58
}
59
60
func (r *GrpcMetricsReporter) Report(ctx context.Context) {
61
ticker := time.NewTicker(5 * time.Second)
62
for {
63
select {
64
case <-ctx.Done():
65
return
66
case <-ticker.C:
67
r.gather()
68
}
69
}
70
}
71
72
func (r *GrpcMetricsReporter) gather() {
73
families, err := r.Registry.Gather()
74
if err != nil {
75
log.WithError(err).Error("supervisor: failed to gather grpc metrics")
76
return
77
}
78
for _, family := range families {
79
if family != nil {
80
r.reportFamily(family)
81
}
82
}
83
}
84
85
func (r *GrpcMetricsReporter) isSuppored(family *dto.MetricFamily) bool {
86
metricName := family.GetName()
87
supported, expected := r.supportedMetrics[metricName]
88
if !expected {
89
r.supportedMetrics[metricName] = false
90
r.onUnexpected(family)
91
return false
92
}
93
return supported
94
}
95
96
func (r *GrpcMetricsReporter) reportFamily(family *dto.MetricFamily) {
97
if !r.isSuppored(family) {
98
return
99
}
100
metricName := family.GetName()
101
for _, metric := range family.Metric {
102
if metric == nil {
103
continue
104
}
105
if metric.Histogram != nil {
106
r.reportHistogram(metricName, metric)
107
} else if metric.Counter != nil {
108
r.reportCounter(metricName, metric)
109
}
110
}
111
}
112
113
func (r *GrpcMetricsReporter) reportHistogram(name string, metric *dto.Metric) {
114
key, labels := computeKey(name, metric)
115
count := uint64(r.increase(key+"count", float64(metric.Histogram.GetSampleCount())))
116
if count <= 0 {
117
return
118
}
119
sum := r.increase(key, metric.Histogram.GetSampleSum())
120
var buckets []uint64
121
for i, bucket := range metric.Histogram.GetBucket() {
122
buckets = append(buckets, uint64(r.increase(fmt.Sprintf("%s%d", key, i), float64(bucket.GetCumulativeCount()))))
123
}
124
r.addHistogram(name, labels, count, sum, buckets)
125
}
126
127
func (r *GrpcMetricsReporter) reportCounter(name string, metric *dto.Metric) {
128
key, labels := computeKey(name, metric)
129
value := uint64(r.increase(key, metric.Counter.GetValue()))
130
if value > 0 {
131
r.addCounter(name, labels, value)
132
}
133
}
134
135
func (r *GrpcMetricsReporter) increase(key string, value float64) float64 {
136
prev := r.updateValue(key, value)
137
return value - prev
138
}
139
140
func (r *GrpcMetricsReporter) updateValue(key string, value float64) float64 {
141
prev := r.values[key]
142
r.values[key] = value
143
return prev
144
}
145
146
func computeKey(name string, metric *dto.Metric) (string, map[string]string) {
147
key := name
148
labelPairs := metric.GetLabel()
149
labels := make(map[string]string, len(labelPairs))
150
for _, labelPair := range metric.GetLabel() {
151
labelName := labelPair.GetName()
152
labelValue := labelPair.GetValue()
153
labels[labelName] = labelValue
154
key = key + labelName + labelValue
155
}
156
return key, labels
157
}
158
159
func logUnexpectedMetric(family *dto.MetricFamily) {
160
log.WithField("metric", family.String()).Error("supervisor: unexpected gprc metric")
161
}
162
163
// TODO(ak) refactor to use grpc when ide-proxy supports it
164
func doAddCounter(gitpodHost string, name string, labels map[string]string, value uint64) {
165
req := &api.AddCounterRequest{
166
Name: name,
167
Labels: labels,
168
Value: int32(value),
169
}
170
log.WithField("req", req).Debug("supervisor: gprc metric: add counter")
171
172
body, err := json.Marshal(req)
173
if err != nil {
174
log.WithField("req", req).WithError(err).Error("supervisor: grpc metric: failed to marshal request")
175
return
176
}
177
url := fmt.Sprintf("https://ide.%s/metrics-api/metrics/counter/add/%s", gitpodHost, name)
178
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
179
defer cancel()
180
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
181
if err != nil {
182
log.WithError(err).Error("supervisor: grpc metric: failed to create request")
183
return
184
}
185
request.Header.Set("Content-Type", "application/json")
186
request.Header.Set("X-Client", "supervisor")
187
resp, err := http.DefaultClient.Do(request)
188
var statusCode int
189
if resp != nil {
190
statusCode = resp.StatusCode
191
}
192
if err == nil && statusCode == http.StatusOK {
193
return
194
}
195
var respBody string
196
var status string
197
if resp != nil {
198
status = resp.Status
199
body, _ := ioutil.ReadAll(resp.Body)
200
if body != nil {
201
respBody = string(body)
202
}
203
}
204
log.WithField("url", url).
205
WithField("req", req).
206
WithField("statusCode", statusCode).
207
WithField("status", status).
208
WithField("respBody", respBody).
209
WithError(err).
210
Error("supervisor: grpc metric: failed to add counter")
211
}
212
213
// TODO(ak) refactor to use grpc when ide-proxy supports it
214
func doAddHistogram(gitpodHost string, name string, labels map[string]string, count uint64, sum float64, buckets []uint64) {
215
req := &api.AddHistogramRequest{
216
Name: name,
217
Labels: labels,
218
Count: count,
219
Sum: sum,
220
Buckets: buckets,
221
}
222
log.WithField("req", req).Debug("supervisor: gprc metric: add histogram")
223
224
body, err := json.Marshal(req)
225
if err != nil {
226
log.WithField("req", req).WithError(err).Error("supervisor: grpc metric: failed to marshal request")
227
return
228
}
229
url := fmt.Sprintf("https://ide.%s/metrics-api/metrics/histogram/add/%s", gitpodHost, name)
230
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
231
defer cancel()
232
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
233
if err != nil {
234
log.WithError(err).Error("supervisor: grpc metric: failed to create request")
235
return
236
}
237
request.Header.Set("Content-Type", "application/json")
238
request.Header.Set("X-Client", "supervisor")
239
resp, err := http.DefaultClient.Do(request)
240
var statusCode int
241
if resp != nil {
242
statusCode = resp.StatusCode
243
}
244
if err == nil && statusCode == http.StatusOK {
245
return
246
}
247
var respBody string
248
var status string
249
if resp != nil {
250
status = resp.Status
251
body, _ := ioutil.ReadAll(resp.Body)
252
if body != nil {
253
respBody = string(body)
254
}
255
}
256
log.WithField("url", url).
257
WithField("req", req).
258
WithField("statusCode", statusCode).
259
WithField("status", status).
260
WithField("respBody", respBody).
261
WithError(err).
262
Error("supervisor: grpc metric: failed to add histogram")
263
}
264
265