cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.62.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/metadata.go

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