Last active
March 22, 2023 18:26
-
-
Save smijar/c4c91854b3e2a686a600404ce64205dd to your computer and use it in GitHub Desktop.
golang example to instrument code to generate and client library to Parse (extra) Prometheus metrics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package metrics_examples | |
import ( | |
"net/http" | |
"time" | |
"github.com/prometheus/client_golang/prometheus" | |
"github.com/prometheus/client_golang/prometheus/promauto" | |
"github.com/prometheus/client_golang/prometheus/promhttp" | |
) | |
func recordMetrics() { | |
go func() { | |
for { | |
opsProcessed.Inc() | |
helloworld_1000_invocation_counter.Inc() | |
helloworld_2000_invocation_counter.Inc() | |
time.Sleep(2 * time.Second) | |
} | |
}() | |
} | |
var ( | |
opsProcessed = promauto.NewCounter(prometheus.CounterOpts{ | |
Name: "helloworld1", | |
Help: "The total number of requests", | |
}) | |
helloworld_1000_invocation_counter = promauto.NewCounter(prometheus.CounterOpts{ | |
Name: "helloworld_1000_http_requests_total", | |
Help: "The total number of requests made to helloworld_1234", | |
}) | |
helloworld_2000_invocation_counter = promauto.NewCounter(prometheus.CounterOpts{ | |
Name: "helloworld_2000_http_requests_total", | |
Help: "The total number of requests made to helloworld_1234", | |
}) | |
) | |
func main1() { | |
recordMetrics() | |
http.Handle("/metrics", promhttp.Handler()) | |
http.ListenAndServe(":2112", nil) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
"fmt" | |
"io" | |
"log" | |
"math/rand" | |
"net/http" | |
"os" | |
"time" | |
"github.com/prometheus/client_golang/prometheus" | |
"github.com/prometheus/client_golang/prometheus/promauto" | |
"github.com/prometheus/client_golang/prometheus/promhttp" | |
) | |
var ( | |
invocation_counter = promauto.NewCounterVec(prometheus.CounterOpts{ | |
Name: "fn_http_requests_total", | |
Help: "The total number of requests made to the function", | |
}, | |
[]string{"function"}, | |
) | |
last_request_duration_ms = promauto.NewGaugeVec(prometheus.GaugeOpts{ | |
Name: "fn_last_request_duration", | |
Help: "Last request duration for function", | |
}, | |
[]string{"function"}, | |
) | |
request_histogram_vec = promauto.NewHistogramVec(prometheus.HistogramOpts{ | |
Name: "fn_request_histogram", | |
Help: "Request latency for function", | |
}, | |
[]string{"function"}, | |
) | |
request_summary_vec = promauto.NewSummaryVec(prometheus.SummaryOpts{ | |
Name: "fn_request_summary", | |
Help: "Request summary for function", | |
}, | |
[]string{"function"}, | |
) | |
// helloworld_1000_invocation_counter = promauto.NewCounterVec(prometheus.CounterOpts{ | |
// Name: "fn_helloworld_1000_http_requests_total", | |
// Help: "The total number of requests made to helloworld_1234", | |
// }, | |
// []string{"function"}, | |
// ) | |
// helloworld_1000_duration_ms = promauto.NewGaugeVec(prometheus.GaugeOpts{ | |
// Name: "fn_helloworld_1000_request_duration_ms", | |
// Help: "Last request duration for helloworld_1000", | |
// }, | |
// []string{"function"}, | |
// ) | |
// helloworld_2000_invocation_counter = promauto.NewCounterVec(prometheus.CounterOpts{ | |
// Name: "fn_helloworld_2000_http_requests_total", | |
// Help: "The total number of requests made to helloworld_2000", | |
// }, | |
// []string{"function"}, | |
// ) | |
// helloworld_2000_duration_ms = promauto.NewGaugeVec(prometheus.GaugeOpts{ | |
// Name: "fn_helloworld_2000_request_duration_ms", | |
// Help: "Last request duration for helloworld_2000", | |
// }, | |
// []string{"function"}, | |
// ) | |
) | |
func getHello1000(w http.ResponseWriter, r *http.Request) { | |
invocation_counter.WithLabelValues("helloworld-1000").Inc() | |
timer := prometheus.NewTimer(prometheus.ObserverFunc(last_request_duration_ms.WithLabelValues("helloworld-1000").Set)) | |
defer timer.ObserveDuration() | |
start := time.Now().UnixNano() / int64(time.Millisecond) | |
fmt.Printf("got / request\n") | |
io.WriteString(w, "This is my website!\n") | |
max := 250 | |
min := 125 | |
duration := time.Duration(rand.Intn(max-min) + min) | |
time.Sleep(duration * time.Millisecond) | |
end := time.Now().UnixNano() / int64(time.Millisecond) | |
diff := end - start | |
log.Printf("Duration(ms): %d", diff) | |
//request_duration_ms.WithLabelValues("helloworld-1000", fmt.Sprintf("%d", time.Now().UnixMilli())).Set(float64(diff)) | |
} | |
func getHello2000(w http.ResponseWriter, r *http.Request) { | |
invocation_counter.WithLabelValues("helloworld-2000").Inc() | |
timer := prometheus.NewTimer(prometheus.ObserverFunc(last_request_duration_ms.WithLabelValues("helloworld-2000").Set)) | |
defer timer.ObserveDuration() | |
start := time.Now().UnixNano() / int64(time.Millisecond) | |
fmt.Printf("got /hello request\n") | |
io.WriteString(w, "Hello, HTTP!\n") | |
max := 250 | |
min := 125 | |
duration := time.Duration(rand.Intn(max-min) + min) | |
time.Sleep(duration * time.Millisecond) | |
end := time.Now().UnixNano() / int64(time.Millisecond) | |
diff := end - start | |
log.Printf("Duration(ms): %d", diff) | |
//request_duration_ms.WithLabelValues("helloworld-2000", fmt.Sprintf("%d", time.Now().UnixMilli())).Set(float64(diff)) | |
} | |
func main() { | |
fmt.Println("server started") | |
http.HandleFunc("/hello1000", getHello1000) | |
http.HandleFunc("/hello2000", getHello2000) | |
http.Handle("/metrics", promhttp.Handler()) | |
err := http.ListenAndServe(":2112", nil) | |
if errors.Is(err, http.ErrServerClosed) { | |
fmt.Printf("server closed\n") | |
} else if err != nil { | |
fmt.Printf("error starting server: %s\n", err) | |
os.Exit(1) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/json" | |
"flag" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"os" | |
"strings" | |
"time" | |
"github.com/kamva/mgm/v3" | |
dto "github.com/prometheus/client_model/go" | |
"github.com/prometheus/common/expfmt" | |
"go.mongodb.org/mongo-driver/mongo" | |
"go.mongodb.org/mongo-driver/mongo/options" | |
) | |
type FnMetric struct { | |
mgm.DefaultModel `bson:",inline"` | |
FunctionName string `json:"functionName"` | |
MetricName string `json:"metricName"` | |
HttpRequestsTotal int64 `json:"totalHttpRequests"` | |
RequestDurationMs float64 `json:"requestDurationMs"` | |
} | |
const ( | |
FnMetricName_HttpRequestsTotal string = "fn_http_requests_total" | |
FnMetricName_RequestDurationMs string = "fn_request_duration_ms" | |
) | |
// var metrics map[string]FnMetric | |
var metricTypes = map[int]string{0: "counter", 1: "gauge", 2: "histogram", 3: "summary"} | |
func ToFnMetric(metricName string, promVecType dto.MetricType, promMetric *dto.Metric) FnMetric { | |
fnMetric := FnMetric{} | |
labelPairs := promMetric.GetLabel() | |
// assume only 1 label pair at this point | |
fnMetric.FunctionName = labelPairs[0].GetValue() | |
fnMetric.MetricName = metricName | |
if promVecType == dto.MetricType_COUNTER && metricName == FnMetricName_HttpRequestsTotal { | |
fnMetric.HttpRequestsTotal = int64(promMetric.GetCounter().GetValue()) | |
} else if promVecType == dto.MetricType_GAUGE && metricName == FnMetricName_RequestDurationMs { | |
fnMetric.RequestDurationMs = float64(promMetric.GetGauge().GetValue()) | |
} | |
return fnMetric | |
} | |
func ToFnMetrics(mfVal *dto.MetricFamily) []FnMetric { | |
fnMetricsList := make([]FnMetric, len(mfVal.GetMetric())) | |
for i, m := range mfVal.GetMetric() { | |
fnMetricsList[i] = ToFnMetric(mfVal.GetName(), mfVal.GetType(), m) | |
} | |
return fnMetricsList | |
} | |
func fatal(err error) { | |
if err != nil { | |
log.Fatalln(err) | |
} | |
} | |
func parseMF(reader io.Reader) (map[string]*dto.MetricFamily, error) { | |
var parser expfmt.TextParser | |
mf, err := parser.TextToMetricFamilies(reader) | |
if err != nil { | |
return nil, err | |
} | |
return mf, nil | |
} | |
func parseFile(f string) error { | |
reader, err := os.Open(f) | |
if err != nil { | |
return err | |
} | |
defer reader.Close() | |
mf, err := parseMF(reader) | |
if err != nil { | |
return err | |
} | |
fnMetricsList := []FnMetric{} | |
for k, v := range mf { | |
if strings.HasPrefix(k, "fn_") { | |
value, _ := json.Marshal(v) | |
// fmt.Println("KEY: ", k) | |
fmt.Println(string(value)) | |
fnMetrics := ToFnMetrics(v) | |
for _, fnMetric := range fnMetrics { | |
fnMetricsList = append(fnMetricsList, fnMetric) | |
} | |
} | |
// if v.GetType() == dto.MetricType_GAUGE { | |
// var value *float64 = v.GetMetric()[0].Gauge.Value | |
// fmt.Println("VALUE: ", *value) | |
// } else if v.GetType() == dto.MetricType_COUNTER { | |
// var value *float64 = v.GetMetric()[0].Counter.Value | |
// fmt.Println("VALUE: ", *value) | |
// } | |
} | |
print(len(fnMetricsList)) | |
return nil | |
} | |
func parseUrl(url string) ([]FnMetric, error) { | |
resp, err := http.Get(url) | |
if err != nil { | |
return []FnMetric{}, err | |
} | |
defer resp.Body.Close() | |
mf, err := parseMF(resp.Body) | |
if err != nil { | |
return []FnMetric{}, err | |
} | |
fnMetricsList := []FnMetric{} | |
for k, v := range mf { | |
if strings.HasPrefix(k, "fn_") { | |
value, _ := json.Marshal(v) | |
// fmt.Println("KEY: ", k) | |
fmt.Println(string(value)) | |
fnMetrics := ToFnMetrics(v) | |
for _, fnMetric := range fnMetrics { | |
fnMetricsList = append(fnMetricsList, fnMetric) | |
} | |
} | |
} | |
print(len(fnMetricsList)) | |
return fnMetricsList, nil | |
} | |
func AddFnMetrics(ctx context.Context, fnMetricsList []FnMetric) { | |
for _, fnMetric := range fnMetricsList { | |
mgm.Coll(&FnMetric{}).CreateWithCtx(ctx, &fnMetric) | |
} | |
} | |
func connectDB() { | |
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017")) | |
mgm.SetDefaultConfig(nil, "metrics", options.Client().ApplyURI("mongodb://localhost:27017")) | |
if err != nil { | |
log.Fatal(err) | |
} | |
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) | |
err = client.Connect(ctx) | |
if err != nil { | |
log.Fatal(err) | |
} | |
//ping the database | |
err = client.Ping(ctx, nil) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Println("Connected to MongoDB") | |
} | |
// Usage: go run main.go -f metrics.txt | |
func main() { | |
if len(os.Args) <= 1 { | |
fmt.Println("Usage: go run main.go -f <metrics.txt file path>") | |
os.Exit(-1) | |
} | |
f := flag.String("f", "", "set filepath") | |
url := flag.String("u", "", "set url") | |
flag.Parse() | |
//fmt.Println("file option:", *f) | |
//fmt.Println("url option:", *url) | |
if len(*f) > 0 { | |
//fmt.Printf("file option: %s", *f) | |
err := parseFile(*f) | |
fatal(err) | |
return | |
} | |
if len(*url) > 0 { | |
//fmt.Printf("url option %s", *url) | |
connectDB() | |
fnMetricsList, _ := parseUrl(*url) | |
AddFnMetrics(context.Background(), fnMetricsList) | |
return | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment