Merge branch 'master' of github.com:sixt/gomodproxy

This commit is contained in:
Serge Zaitsev 2018-10-16 12:12:20 +02:00
commit 2ace4f1c80
2 changed files with 80 additions and 5 deletions

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
@ -13,13 +14,56 @@ import (
"path/filepath"
"strings"
"time"
"unicode"
"github.com/sixt/gomodproxy/pkg/api"
_ "expvar"
"expvar"
_ "net/http/pprof"
)
func prometheusExpose(w io.Writer, name string, v interface{}) {
// replace all invalid symbols with underscores
name = strings.Map(func(r rune) rune {
r = unicode.ToLower(r)
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
return r
}
return '_'
}, name)
// expvar does not have concepts of counters and gauges,
// so we tell one from another based on the name suffix.
counter := strings.HasSuffix(name, "_total")
if f, ok := v.(float64); ok {
if counter {
fmt.Fprintf(w, "# TYPE %s counter\n", name)
} else {
fmt.Fprintf(w, "# TYPE %s gauge\n", name)
}
fmt.Fprintf(w, "%s %f\n", name, f)
} else if m, ok := v.(map[string]interface{}); ok {
for k, v := range m {
// for composite maps we construct metric names by joining the parent map
// name and the key name.
s := strings.TrimSuffix(name, "_total") + "_" + k
if counter {
s = s + "_total"
}
prometheusExpose(w, s, v)
}
}
}
func prometheusHandler(w http.ResponseWriter, r *http.Request) {
expvar.Do(func(kv expvar.KeyValue) {
var v interface{}
if err := json.Unmarshal([]byte(kv.Value.String()), &v); err != nil {
return
}
prometheusExpose(w, kv.Key, v)
})
}
func prettyLog(v ...interface{}) {
s := ""
msg := ""
@ -56,6 +100,7 @@ func main() {
addr := flag.String("addr", ":0", "http server address")
verbose := flag.Bool("v", false, "verbose logging")
prometheus := flag.String("prometheus", "", "prometheus address")
debug := flag.Bool("debug", false, "enable debug HTTP API (pprof/expvar)")
json := flag.Bool("json", false, "json structured logging")
dir := flag.String("dir", filepath.Join(os.Getenv("HOME"), ".gomodproxy/cache"), "modules cache directory")
@ -103,6 +148,14 @@ func main() {
mux := http.NewServeMux()
mux.Handle("/", api.New(options...))
if *prometheus != "" {
if *prometheus == *addr {
mux.HandleFunc("/metrics", prometheusHandler)
} else {
srv := &http.Server{Handler: http.HandlerFunc(prometheusHandler), Addr: *prometheus}
go srv.ListenAndServe()
}
}
if *debug {
mux.Handle("/debug/vars", http.DefaultServeMux)
mux.Handle("/debug/pprof/heap", http.DefaultServeMux)

View File

@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
"expvar"
"fmt"
"io"
"net/http"
@ -42,6 +43,14 @@ var (
apiZip = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).zip$`)
)
var (
cacheHits = expvar.NewMap("cache_hits_total")
cacheMisses = expvar.NewMap("cache_misses_total")
httpRequests = expvar.NewMap("http_requests_total")
httpErrors = expvar.NewMap("http_errors_total")
httpRequestDurations = expvar.NewMap("http_request_duration_seconds")
)
// New returns a configured http.Handler which implements GOPROXY API.
func New(options ...Option) http.Handler {
api := &api{log: func(...interface{}) {}}
@ -113,13 +122,14 @@ func (api *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() { api.log("api.ServeHTTP", "method", r.Method, "url", r.URL, "time", time.Since(now)) }()
for _, route := range []struct {
id string
regexp *regexp.Regexp
handler func(w http.ResponseWriter, r *http.Request, module, version string)
}{
{apiList, api.list},
{apiInfo, api.info},
{apiMod, api.mod},
{apiZip, api.zip},
{"list", apiList, api.list},
{"info", apiInfo, api.info},
{"api", apiMod, api.mod},
{"zip", apiZip, api.zip},
} {
if m := route.regexp.FindStringSubmatch(r.URL.Path); m != nil {
module, version := m[1], ""
@ -127,11 +137,18 @@ func (api *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
version = m[2]
}
module = decodeBangs(module)
httpRequests.Add(route.id, 1)
defer func() {
v := &expvar.Float{}
v.Set(time.Since(now).Seconds())
httpRequestDurations.Set(route.id, v)
}()
route.handler(w, r, module, version)
return
}
}
httpRequests.Add("not_found", 1)
http.NotFound(w, r)
}
@ -147,9 +164,11 @@ func (api *api) vcs(ctx context.Context, module string) vcs.VCS {
func (api *api) module(ctx context.Context, module string, version vcs.Version) ([]byte, time.Time, error) {
for _, store := range api.stores {
if snapshot, err := store.Get(ctx, module, version); err == nil {
cacheHits.Add(module, 1)
return snapshot.Data, snapshot.Timestamp, nil
}
}
cacheMisses.Add(module, 1)
timestamp, err := api.vcs(ctx, module).Timestamp(ctx, version)
if err != nil {
@ -187,6 +206,7 @@ func (api *api) list(w http.ResponseWriter, r *http.Request, module, version str
list, err := api.vcs(r.Context(), module).List(r.Context())
if err != nil {
api.log("api.list", "module", module, "error", err)
httpErrors.Add(module, 1)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@ -202,6 +222,7 @@ func (api *api) info(w http.ResponseWriter, r *http.Request, module, version str
if err != nil {
api.log("api.info", "module", module, "version", version, "error", err)
httpErrors.Add(module, 1)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@ -236,6 +257,7 @@ func (api *api) zip(w http.ResponseWriter, r *http.Request, module, version stri
b, _, err := api.module(r.Context(), module, vcs.Version(version))
if err != nil {
api.log("api.zip", "module", module, "version", version, "error", err)
httpErrors.Add(module, 1)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}