cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.59.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/promapi/metadata.go

134lines · modecode

1package promapi
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log/slog"
9 "net/http"
10 "net/url"
11 "time"
12
13 v1 "github.com/prometheus/client_golang/api/prometheus/v1"
14 "github.com/prymitive/current"
15)
16
17type MetadataResult struct {
18 URI string
19 PublicURI string
20 Metadata []v1.Metadata
21}
22
23type metadataQuery struct {
24 timestamp time.Time
25 ctx context.Context
26 prom *Prometheus
27 metric string
28}
29
30func (q metadataQuery) Run() queryResult {
31 slog.Debug(
32 "Getting prometheus metrics metadata",
33 slog.String("uri", q.prom.safeURI),
34 slog.String("metric", q.metric),
35 )
36
37 ctx, cancel := q.prom.requestContext(q.ctx)
38 defer cancel()
39
40 var qr queryResult
41
42 args := url.Values{}
43 args.Set("metric", q.metric)
44 resp, err := q.prom.doRequest(ctx, http.MethodGet, q.Endpoint(), args)
45 if err != nil {
46 qr.err = fmt.Errorf("failed to query Prometheus metrics metadata: %w", err)
47 return qr
48 }
49 defer resp.Body.Close()
50
51 if resp.StatusCode/100 != 2 {
52 qr.err = tryDecodingAPIError(resp)
53 return qr
54 }
55
56 meta, err := streamMetadata(resp.Body)
57 qr.value, qr.err = meta, err
58 return qr
59}
60
61func (q metadataQuery) Endpoint() string {
62 return "/api/v1/metadata"
63}
64
65func (q metadataQuery) String() string {
66 return q.metric
67}
68
69func (q metadataQuery) CacheKey() uint64 {
70 return hash(q.prom.unsafeURI, q.Endpoint(), q.metric)
71}
72
73func (q metadataQuery) CacheTTL() time.Duration {
74 return time.Minute * 10
75}
76
77func (p *Prometheus) Metadata(ctx context.Context, metric string) (*MetadataResult, error) {
78 slog.Debug("Scheduling Prometheus metrics metadata query", slog.String("uri", p.safeURI), slog.String("metric", metric))
79
80 key := fmt.Sprintf("/api/v1/metadata/%s", metric)
81 p.locker.lock(key)
82 defer p.locker.unlock(key)
83
84 resultChan := make(chan queryResult)
85 p.queries <- queryRequest{
86 query: metadataQuery{prom: p, ctx: ctx, metric: metric, timestamp: time.Now()},
87 result: resultChan,
88 }
89
90 result := <-resultChan
91 if result.err != nil {
92 return nil, QueryError{err: result.err, msg: decodeError(result.err)}
93 }
94
95 metadata := MetadataResult{
96 URI: p.safeURI,
97 PublicURI: p.publicURI,
98 Metadata: result.value.(map[string][]v1.Metadata)[metric],
99 }
100
101 return &metadata, nil
102}
103
104func streamMetadata(r io.Reader) (meta map[string][]v1.Metadata, err error) {
105 defer dummyReadAll(r)
106
107 var status, errType, errText string
108 meta = map[string][]v1.Metadata{}
109 decoder := current.Object(
110 current.Key("status", current.Value(func(s string, _ bool) {
111 status = s
112 })),
113 current.Key("error", current.Value(func(s string, _ bool) {
114 errText = s
115 })),
116 current.Key("errorType", current.Value(func(s string, _ bool) {
117 errType = s
118 })),
119 current.Key("data", current.Map(func(k string, v []v1.Metadata) {
120 meta[k] = v
121 })),
122 )
123
124 dec := json.NewDecoder(r)
125 if err = decoder.Stream(dec); err != nil {
126 return nil, APIError{Status: status, ErrorType: v1.ErrBadResponse, Err: fmt.Sprintf("JSON parse error: %s", err)}
127 }
128
129 if status != "success" {
130 return nil, APIError{Status: status, ErrorType: decodeErrorType(errType), Err: errText}
131 }
132
133 return meta, nil
134}
135