Skip to content

Instantly share code, notes, and snippets.

@smijar
Last active March 22, 2023 18:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smijar/c4c91854b3e2a686a600404ce64205dd to your computer and use it in GitHub Desktop.
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
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)
}
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)
}
}
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