Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/openvsx-proxy/pkg/prometheus.go
2498 views
1
// Copyright (c) 2021 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 pkg
6
7
import (
8
"fmt"
9
"net/http"
10
"strings"
11
12
"github.com/gitpod-io/gitpod/common-go/log"
13
"github.com/prometheus/client_golang/prometheus"
14
"github.com/prometheus/client_golang/prometheus/collectors"
15
"github.com/prometheus/client_golang/prometheus/promhttp"
16
)
17
18
type Prometheus struct {
19
reg *prometheus.Registry
20
BackupCacheHitCounter prometheus.Counter
21
BackupCacheMissCounter prometheus.Counter
22
BackupCacheServeCounter prometheus.Counter
23
RegularCacheHitServeCounter prometheus.Counter
24
RegularCacheMissCounter prometheus.Counter
25
RequestsCounter *prometheus.CounterVec
26
DurationOverallHistogram prometheus.Histogram
27
DurationRequestProcessingHistogram prometheus.Histogram
28
DurationUpstreamCallHistorgram prometheus.Histogram
29
DurationResponseProcessingHistogram prometheus.Histogram
30
}
31
32
func (p *Prometheus) Start(cfg *Config) {
33
p.reg = prometheus.NewRegistry()
34
35
if cfg.PrometheusAddr != "" {
36
p.reg.MustRegister(
37
collectors.NewGoCollector(),
38
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
39
)
40
41
handler := http.NewServeMux()
42
handler.Handle("/metrics", promhttp.HandlerFor(p.reg, promhttp.HandlerOpts{}))
43
44
go func() {
45
err := http.ListenAndServe(cfg.PrometheusAddr, handler)
46
if err != nil {
47
log.WithError(err).Error("Prometheus metrics server failed")
48
}
49
}()
50
log.WithField("addr", cfg.PrometheusAddr).Debug("started Prometheus metrics server")
51
}
52
53
p.createMetrics()
54
collectors := []prometheus.Collector{
55
p.BackupCacheHitCounter,
56
p.BackupCacheMissCounter,
57
p.BackupCacheServeCounter,
58
p.RegularCacheHitServeCounter,
59
p.RegularCacheMissCounter,
60
p.RequestsCounter,
61
p.DurationOverallHistogram,
62
p.DurationRequestProcessingHistogram,
63
p.DurationUpstreamCallHistorgram,
64
p.DurationResponseProcessingHistogram,
65
}
66
for _, c := range collectors {
67
err := p.reg.Register(c)
68
if err != nil {
69
log.WithError(err).Error("register Prometheus metric failed")
70
}
71
}
72
}
73
74
func (p *Prometheus) createMetrics() {
75
namespace := "gitpod"
76
subsystem := "openvsx_proxy"
77
p.BackupCacheHitCounter = prometheus.NewCounter(prometheus.CounterOpts{
78
Namespace: namespace,
79
Subsystem: subsystem,
80
Name: "backup_cache_hit_total",
81
Help: "The total amount of requests where we had a cached response that we could use as backup when the upstream server is down.",
82
})
83
p.BackupCacheMissCounter = prometheus.NewCounter(prometheus.CounterOpts{
84
Namespace: namespace,
85
Subsystem: subsystem,
86
Name: "backup_cache_miss_total",
87
Help: "The total amount of requests where we haven't had a cached response that we could use as backup when the upstream server is down.",
88
})
89
p.BackupCacheServeCounter = prometheus.NewCounter(prometheus.CounterOpts{
90
Namespace: namespace,
91
Subsystem: subsystem,
92
Name: "backup_cache_serve_total",
93
Help: "The total amount of requests where we actually answered with a cached response because the upstream server is down.",
94
})
95
p.RegularCacheHitServeCounter = prometheus.NewCounter(prometheus.CounterOpts{
96
Namespace: namespace,
97
Subsystem: subsystem,
98
Name: "regular_cache_hit_and_serve_total",
99
Help: "The total amount or requests where we answered with a cached response for performance reasons.",
100
})
101
p.RegularCacheMissCounter = prometheus.NewCounter(prometheus.CounterOpts{
102
Namespace: namespace,
103
Subsystem: subsystem,
104
Name: "regular_cache_miss_total",
105
Help: "The total amount or requests we haven't had a young enough cached requests to use it for performance reasons.",
106
})
107
p.RequestsCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
108
Namespace: namespace,
109
Subsystem: subsystem,
110
Name: "requests_total",
111
Help: "The total amount of requests by response status.",
112
}, []string{"status", "path"})
113
p.DurationOverallHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
114
Namespace: namespace,
115
Subsystem: subsystem,
116
Name: "duration_overall_seconds",
117
Help: "The duration in seconds of the HTTP requests.",
118
})
119
p.DurationRequestProcessingHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
120
Namespace: namespace,
121
Subsystem: subsystem,
122
Name: "duration_request_processing_seconds",
123
Help: "The duration in seconds of the processing of the HTTP requests before we call the upstream.",
124
})
125
p.DurationUpstreamCallHistorgram = prometheus.NewHistogram(prometheus.HistogramOpts{
126
Namespace: namespace,
127
Subsystem: subsystem,
128
Name: "duration_upstream_call_seconds",
129
Help: "The duration in seconds of the call of the upstream server.",
130
})
131
p.DurationResponseProcessingHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
132
Namespace: namespace,
133
Subsystem: subsystem,
134
Name: "duration_response_processing_seconds",
135
Help: "The duration in seconds of the processing of the HTTP responses after we have called the upstream.",
136
})
137
}
138
139
var expectedPaths = map[string]struct{}{
140
"/api/-/query": {},
141
"/vscode/asset": {},
142
"/vscode/gallery/extensionquery": {},
143
"/vscode/gallery/itemName": {},
144
"/vscode/gallery/publishers": {},
145
}
146
147
func (p *Prometheus) IncStatusCounter(r *http.Request, status string) {
148
path := r.URL.Path
149
if strings.HasPrefix(path, "/vscode/asset/") {
150
// remove everything after /vscode/asset/ to decrease the unique numbers of paths
151
path = path[:len("/vscode/asset/")]
152
}
153
if strings.HasPrefix(path, "/vscode/gallery/itemName/") {
154
// remove everything after /vscode/gallery/itemName/ to decrease the unique numbers of paths
155
path = path[:len("/vscode/gallery/itemName/")]
156
}
157
// just to make sure that a long path doesn't slip through cut after 3 segements
158
// since path starts with a / the first segment is an emtpy string, therefore len > 4 and not len > 3
159
if s := strings.SplitN(path, "/", 5); len(s) > 4 {
160
path = strings.Join(s[:4], "/")
161
}
162
// don't track unexepected paths (e.g. requests from crawlers/bots)
163
if _, ok := expectedPaths[strings.TrimSuffix(path, "/")]; !ok {
164
log.WithField("path", path).Debug("unexpected path")
165
path = "(other)"
166
}
167
p.RequestsCounter.WithLabelValues(status, fmt.Sprintf("%s %s", r.Method, path)).Inc()
168
}
169
170