Skip to content

Commit b347350

Browse files
committed
initial commit
Tool: gitpod/catfood.gitpod.cloud
1 parent fb5461d commit b347350

File tree

5 files changed

+301
-1
lines changed

5 files changed

+301
-1
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
1-
# prometheus-decoder
1+
# prometheus-decoder
2+
3+
A simplistic tool to turn Prometheus metric records as persisted by Gitpod Dedicated into a readable form.
4+
5+
## Usage
6+
7+
```
8+
$ ./prometheus-decoder
9+
Error: Input file is required
10+
Usage of ./prometheus-decoder:
11+
-human-time
12+
Show human-readable timestamps in output
13+
-input string
14+
Input file containing PrometheusRecord entries (one per line)
15+
-output string
16+
Output file for JSON results (default: stdout)
17+
-pretty
18+
Enable pretty-printing of JSON output (default true)
19+
```

go.mod

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module github.com/gitpod-io/prometheus-decoder
2+
3+
go 1.23.1
4+
5+
require (
6+
github.com/gogo/protobuf v1.3.2
7+
github.com/golang/snappy v1.0.0
8+
github.com/prometheus/prometheus v0.302.1
9+
)
10+
11+
require (
12+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
13+
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
14+
github.com/prometheus/client_model v0.6.1 // indirect
15+
github.com/prometheus/common v0.62.0 // indirect
16+
golang.org/x/text v0.21.0 // indirect
17+
google.golang.org/protobuf v1.36.4 // indirect
18+
)

go.sum

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
4+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
6+
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
7+
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
8+
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
9+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
10+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
11+
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
12+
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
13+
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
14+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
15+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
16+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17+
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
18+
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
19+
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
20+
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
21+
github.com/prometheus/prometheus v0.302.1 h1:xqVdrwrB4WNpdgJqxsz5loqFWNUZitsK8myqLuSZ6Ag=
22+
github.com/prometheus/prometheus v0.302.1/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg=
23+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
24+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
25+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
26+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
27+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
28+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
29+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
30+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
31+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
32+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
33+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
34+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
35+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
36+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
37+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
40+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
41+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
42+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
43+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
44+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
45+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
46+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
47+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
48+
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
49+
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
50+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
51+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
52+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
53+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
54+
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
55+
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
56+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
57+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
58+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
59+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"os"
9+
"time"
10+
11+
"github.com/gogo/protobuf/proto"
12+
"github.com/golang/snappy"
13+
"github.com/prometheus/prometheus/prompb"
14+
)
15+
16+
// PrometheusRecord is the ndjson-encoded format used for transporting metrics through firehose
17+
type PrometheusRecord struct {
18+
Body []byte `json:"b"`
19+
}
20+
21+
// TimeSeries represents a decoded Prometheus time series in a more readable format
22+
type TimeSeries struct {
23+
Labels map[string]string `json:"labels"`
24+
Timestamps []int64 `json:"timestamps"`
25+
Values []float64 `json:"values"`
26+
}
27+
28+
// WriteRequestJSON is a more readable representation of prompb.WriteRequest
29+
type WriteRequestJSON struct {
30+
Timeseries []TimeSeries `json:"timeseries"`
31+
}
32+
33+
// decodePrompbWriteReq decodes the wrapped prompb.WriteRequest
34+
func decodePrompbWriteReq(record *PrometheusRecord) (*prompb.WriteRequest, error) {
35+
// Decompress the snappy-compressed data
36+
data, err := snappy.Decode(nil, record.Body)
37+
if err != nil {
38+
return nil, fmt.Errorf("snappy decode error: %w", err)
39+
}
40+
41+
// Unmarshal the protobuf message
42+
var req prompb.WriteRequest
43+
if err := proto.Unmarshal(data, &req); err != nil {
44+
return nil, fmt.Errorf("protobuf unmarshal error: %w", err)
45+
}
46+
47+
return &req, nil
48+
}
49+
50+
// convertToReadableJSON converts a prompb.WriteRequest to a more readable JSON structure
51+
func convertToReadableJSON(wreq *prompb.WriteRequest) *WriteRequestJSON {
52+
result := &WriteRequestJSON{
53+
Timeseries: make([]TimeSeries, len(wreq.Timeseries)),
54+
}
55+
56+
for i, ts := range wreq.Timeseries {
57+
// Convert labels
58+
labels := make(map[string]string)
59+
for _, label := range ts.Labels {
60+
labels[label.Name] = label.Value
61+
}
62+
63+
// Extract timestamps and values
64+
timestamps := make([]int64, len(ts.Samples))
65+
values := make([]float64, len(ts.Samples))
66+
for j, sample := range ts.Samples {
67+
timestamps[j] = sample.Timestamp
68+
values[j] = sample.Value
69+
}
70+
71+
result.Timeseries[i] = TimeSeries{
72+
Labels: labels,
73+
Timestamps: timestamps,
74+
Values: values,
75+
}
76+
}
77+
78+
return result
79+
}
80+
81+
// humanReadableTime converts a Prometheus timestamp to a human-readable format
82+
func humanReadableTime(timestamp int64) string {
83+
// Prometheus uses milliseconds since epoch
84+
return time.Unix(timestamp/1000, (timestamp%1000)*1000000).Format(time.RFC3339Nano)
85+
}
86+
87+
// streamingJSONDecoder reads and processes a stream of JSON objects without requiring them to be newline-delimited
88+
type streamingJSONDecoder struct {
89+
decoder *json.Decoder
90+
count int
91+
}
92+
93+
func newStreamingJSONDecoder(r io.Reader) *streamingJSONDecoder {
94+
decoder := json.NewDecoder(r)
95+
// Configure the decoder to support streams of concatenated JSON objects
96+
decoder.UseNumber()
97+
return &streamingJSONDecoder{
98+
decoder: decoder,
99+
count: 0,
100+
}
101+
}
102+
103+
func (s *streamingJSONDecoder) next() (*PrometheusRecord, error) {
104+
var record PrometheusRecord
105+
if err := s.decoder.Decode(&record); err != nil {
106+
return nil, err
107+
}
108+
s.count++
109+
return &record, nil
110+
}
111+
112+
func main() {
113+
inputFile := flag.String("input", "", "Input file containing PrometheusRecord entries (one per line)")
114+
outputFile := flag.String("output", "", "Output file for JSON results (default: stdout)")
115+
prettyPrint := flag.Bool("pretty", true, "Enable pretty-printing of JSON output")
116+
humanTime := flag.Bool("human-time", false, "Show human-readable timestamps in output")
117+
flag.Parse()
118+
119+
if *inputFile == "" {
120+
fmt.Println("Error: Input file is required")
121+
flag.Usage()
122+
os.Exit(1)
123+
}
124+
125+
// Open input file
126+
file, err := os.Open(*inputFile)
127+
if err != nil {
128+
fmt.Printf("Error opening input file: %v\n", err)
129+
os.Exit(1)
130+
}
131+
defer file.Close()
132+
133+
// Prepare output
134+
var output *os.File
135+
if *outputFile == "" {
136+
output = os.Stdout
137+
} else {
138+
output, err = os.Create(*outputFile)
139+
if err != nil {
140+
fmt.Printf("Error creating output file: %v\n", err)
141+
os.Exit(1)
142+
}
143+
defer output.Close()
144+
}
145+
146+
// Create JSON stream decoder
147+
decoder := newStreamingJSONDecoder(file)
148+
149+
// Process each JSON object in the stream
150+
for {
151+
record, err := decoder.next()
152+
if err == io.EOF {
153+
break
154+
}
155+
if err != nil {
156+
fmt.Printf("Error parsing JSON object #%d: %v\n", decoder.count, err)
157+
continue
158+
}
159+
160+
// Decode the PrometheusRecord
161+
wreq, err := decodePrompbWriteReq(record)
162+
if err != nil {
163+
fmt.Printf("Error decoding JSON object #%d: %v\n", decoder.count, err)
164+
continue
165+
}
166+
167+
// Convert to our more readable format
168+
jsonStruct := convertToReadableJSON(wreq)
169+
170+
// Apply human-readable time conversion if requested
171+
if *humanTime {
172+
for i := range jsonStruct.Timeseries {
173+
humanTimes := make([]string, len(jsonStruct.Timeseries[i].Timestamps))
174+
for j, ts := range jsonStruct.Timeseries[i].Timestamps {
175+
humanTimes[j] = humanReadableTime(ts)
176+
}
177+
// We need to output this differently, so create a custom marshaling
178+
// This would require a custom struct and marshaling approach
179+
// For simplicity, we'll just add a note about it
180+
fmt.Fprintf(output, "# Object %d: Human-readable timestamps for reference:\n", decoder.count)
181+
for j, humanTime := range humanTimes {
182+
fmt.Fprintf(output, "# Sample %d: %s\n", j, humanTime)
183+
}
184+
}
185+
}
186+
187+
// Output the JSON
188+
var jsonData []byte
189+
if *prettyPrint {
190+
jsonData, err = json.MarshalIndent(jsonStruct, "", " ")
191+
} else {
192+
jsonData, err = json.Marshal(jsonStruct)
193+
}
194+
195+
if err != nil {
196+
fmt.Printf("Error encoding JSON object #%d to JSON: %v\n", decoder.count, err)
197+
continue
198+
}
199+
200+
fmt.Fprintf(output, "# Object %d\n", decoder.count)
201+
fmt.Fprintln(output, string(jsonData))
202+
}
203+
204+
fmt.Printf("Successfully processed %d JSON objects\n", decoder.count)
205+
}

prometheus-decoder

6.96 MB
Binary file not shown.

0 commit comments

Comments
 (0)