diff options
| author | Gabriel A. Giovanini <mail@gabrielgio.me> | 2024-03-22 18:28:31 +0100 | 
|---|---|---|
| committer | Gabriel A. Giovanini <mail@gabrielgio.me> | 2024-03-22 18:28:31 +0100 | 
| commit | b29ad0afa15f8f34f9825cadc31ee97559ebcfd7 (patch) | |
| tree | 2f470576755d3b11ce1c2137053f1ac35badeaae | |
| parent | b3d0af2de29711abfe6da373786d365d9a6de198 (diff) | |
| download | jnfilter-b29ad0afa15f8f34f9825cadc31ee97559ebcfd7.tar.gz jnfilter-b29ad0afa15f8f34f9825cadc31ee97559ebcfd7.tar.bz2 jnfilter-b29ad0afa15f8f34f9825cadc31ee97559ebcfd7.zip | |
feat: Add metricsv0.1.4
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | ext.go | 75 | ||||
| -rw-r--r-- | go.mod | 15 | ||||
| -rw-r--r-- | go.sum | 18 | ||||
| -rw-r--r-- | main.go | 90 | 
5 files changed, 184 insertions, 19 deletions
| @@ -21,6 +21,5 @@ compress: build  compress_into_oblivion: build  	upx --best --ultra-brute $(OUT) -run: sass tmpl -	$(GO_RUN) $(SERVER) - +run: +	$(GO_RUN) . @@ -0,0 +1,75 @@ +package main + +import ( +	"io" +	"net/http" +) + +type ResponseWriter interface { +	http.ResponseWriter +	Status() int +} + +type responseWriter struct { +	http.ResponseWriter +	pendingStatus int +	status        int +	size          int +} + +func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { +	return &responseWriter{ +		ResponseWriter: rw, +	} +} + +func (rw *responseWriter) WriteHeader(s int) { +	if rw.Written() { +		return +	} + +	rw.pendingStatus = s + +	if rw.Written() { +		return +	} + +	rw.status = s +	rw.ResponseWriter.WriteHeader(s) +} + +func (rw *responseWriter) Write(b []byte) (int, error) { +	if !rw.Written() { +		// The status will be StatusOK if WriteHeader has not been called yet +		rw.WriteHeader(http.StatusOK) +	} +	size, err := rw.ResponseWriter.Write(b) +	rw.size += size +	return size, err +} + +func (rw *responseWriter) ReadFrom(r io.Reader) (n int64, err error) { +	if !rw.Written() { +		// The status will be StatusOK if WriteHeader has not been called yet +		rw.WriteHeader(http.StatusOK) +	} +	n, err = io.Copy(rw.ResponseWriter, r) +	rw.size += int(n) +	return +} + +func (rw *responseWriter) Unwrap() http.ResponseWriter { +	return rw.ResponseWriter +} + +func (rw *responseWriter) Status() int { +	if rw.Written() { +		return rw.status +	} + +	return rw.pendingStatus +} + +func (rw *responseWriter) Written() bool { +	return rw.status != 0 +} @@ -2,4 +2,17 @@ module git.sr.ht/~gabrielgio/jnfilter  go 1.21.7 -require github.com/beevik/etree v1.3.0 +require ( +	github.com/beevik/etree v1.3.0 +	github.com/prometheus/client_golang v1.19.0 +) + +require ( +	github.com/beorn7/perks v1.0.1 // indirect +	github.com/cespare/xxhash/v2 v2.2.0 // indirect +	github.com/prometheus/client_model v0.5.0 // indirect +	github.com/prometheus/common v0.48.0 // indirect +	github.com/prometheus/procfs v0.12.0 // indirect +	golang.org/x/sys v0.16.0 // indirect +	google.golang.org/protobuf v1.32.0 // indirect +) @@ -1,2 +1,20 @@  github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU=  github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= @@ -8,28 +8,52 @@ import (  	"io"  	"net/http"  	"regexp" +	"strconv"  	"strings" +	"time"  	"github.com/beevik/etree" +	"github.com/prometheus/client_golang/prometheus" +	"github.com/prometheus/client_golang/prometheus/promauto" +	"github.com/prometheus/client_golang/prometheus/promhttp"  )  type ErrorRequestHandler func(w http.ResponseWriter, r *http.Request) error -var RegexCollection = map[string]string{ -	"nerdcast":     "NerdCast [0-9]+[a-c]* -", -	"empreendedor": "Empreendedor [0-9]+ -", -	"mamicas":      "Caneca de Mamicas [0-9]+ -", -	"english":      "Speak English [0-9]+ -", -	"nerdcash":     "NerdCash [0-9]+ -", -	"bunker":       "Lá do Bunker [0-9]+ -", -	"tech":         "NerdTech [0-9]+ -", -	"genera":       "Generacast [0-9]+ -", -} -  const (  	FeedUrl = "https://api.jovemnerd.com.br/feed-nerdcast/"  ) +var ( +	RegexCollection = map[string]string{ +		"nerdcast":     "NerdCast [0-9]+[a-c]* -", +		"empreendedor": "Empreendedor [0-9]+ -", +		"mamicas":      "Caneca de Mamicas [0-9]+ -", +		"english":      "Speak English [0-9]+ -", +		"nerdcash":     "NerdCash [0-9]+ -", +		"bunker":       "Lá do Bunker [0-9]+ -", +		"tech":         "NerdTech [0-9]+ -", +		"genera":       "Generacast [0-9]+ -", +	} + +	feedRequest = promauto.NewHistogramVec(prometheus.HistogramOpts{ +		Name:    "feed_request", +		Help:    "How long jovemnerd takes to answer", +		Buckets: []float64{.01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}, +	}, []string{"status_code"}) + +	httpRequest = promauto.NewHistogramVec(prometheus.HistogramOpts{ +		Name:    "http_request", +		Help:    "How long the application takes to complete the request", +		Buckets: []float64{.01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}, +	}, []string{"status_code", "user_agent"}) + +	seriesCount = promauto.NewCounterVec(prometheus.CounterOpts{ +		Name: "serie_count", +		Help: "How often a serie is called", +	}, []string{"serie"}) +) +  func getSeries(r *http.Request) []string {  	query := r.URL.Query().Get("q") @@ -59,13 +83,24 @@ func match(title string, series []string) bool {  }  func fetchXML(_ context.Context) ([]byte, error) { +	t := time.Now() +	c := http.StatusInternalServerError + +	defer func() { +		since := time.Since(t).Seconds() +		code := strconv.Itoa(c) +		feedRequest.WithLabelValues(code).Observe(since) +	}() +  	res, err := http.Get(FeedUrl)  	if err != nil {  		return nil, err  	}  	defer res.Body.Close() -	if res.StatusCode == http.StatusOK { +	c = res.StatusCode + +	if c == http.StatusOK {  		return io.ReadAll(res.Body)  	} @@ -109,7 +144,7 @@ func filterBySeries(series []string, xml []byte, temper bool) ([]byte, error) {  	return doc.WriteToBytes()  } -func wrap(next ErrorRequestHandler) http.HandlerFunc { +func handleError(next ErrorRequestHandler) http.HandlerFunc {  	return func(w http.ResponseWriter, r *http.Request) {  		if err := next(w, r); err != nil {  			w.WriteHeader(http.StatusInternalServerError) @@ -117,6 +152,30 @@ func wrap(next ErrorRequestHandler) http.HandlerFunc {  	}  } +func observe(next http.HandlerFunc) http.HandlerFunc { +	return func(w http.ResponseWriter, r *http.Request) { +		t := time.Now() + +		next(w, r) + +		rw := w.(*responseWriter) +		since := time.Since(t).Seconds() +		code := strconv.Itoa(rw.Status()) +		userAgent := r.Header.Get("user-agent") +		httpRequest.WithLabelValues(code, userAgent).Observe(float64(since)) + +		for _, s := range getSeries(r) { +			seriesCount.WithLabelValues(s).Inc() +		} +	} +} + +func wrap(next http.HandlerFunc) http.HandlerFunc { +	return func(w http.ResponseWriter, r *http.Request) { +		next(NewResponseWriter(w), r) +	} +} +  func titles(w http.ResponseWriter, r *http.Request) error {  	xml, err := fetchXML(r.Context())  	if err != nil { @@ -173,8 +232,9 @@ func main() {  	flag.Parse()  	mux := http.NewServeMux() -	mux.HandleFunc("/titles", wrap(titles)) -	mux.HandleFunc("/", wrap(podcast)) +	mux.Handle("/metrics", promhttp.Handler()) +	mux.HandleFunc("/titles", wrap(handleError(titles))) +	mux.HandleFunc("/", wrap(observe(handleError(podcast))))  	server := http.Server{  		Handler: mux, | 
