656ece06e5
Some components like go-ds-measure, go-ipfs-blockstore and go-bitswap expose metrics via ipfs/go-metrics-interface, but Lotus never injects the Prometheus exporter (ipfs/go-metrics-prometheus). Therefore, those metrics never surface in instrumentation. Instead, Lotus uses OpenCensus directly. This commit injects the Prometheus exporter for go-metrics-interface, and instructs the OpenCensus Prometheus exporter to use the DefaultRegistry. This has the effect of exposing blending the metrics of both metrics libraries. With this patch, the datastore, cache utilisation, and bitswap metrics are now exported via the /debug/metrics endpoint. This commit also fixes an issue where the metrics scope was empty, making go-metrics-interface default to "<no-scope>". Angle brackets are inadmissible characters for Prometheus, so it was refusing to export the affected metrics. (These were the ARC cache metrics.)
141 lines
3.8 KiB
Go
141 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
logging "github.com/ipfs/go-log/v2"
|
|
"github.com/multiformats/go-multiaddr"
|
|
manet "github.com/multiformats/go-multiaddr/net"
|
|
promclient "github.com/prometheus/client_golang/prometheus"
|
|
"go.opencensus.io/tag"
|
|
"golang.org/x/xerrors"
|
|
|
|
"contrib.go.opencensus.io/exporter/prometheus"
|
|
|
|
"github.com/filecoin-project/go-jsonrpc"
|
|
"github.com/filecoin-project/go-jsonrpc/auth"
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/api/apistruct"
|
|
"github.com/filecoin-project/lotus/metrics"
|
|
"github.com/filecoin-project/lotus/node"
|
|
"github.com/filecoin-project/lotus/node/impl"
|
|
)
|
|
|
|
var log = logging.Logger("main")
|
|
|
|
func serveRPC(a api.FullNode, stop node.StopFunc, addr multiaddr.Multiaddr, shutdownCh <-chan struct{}) error {
|
|
rpcServer := jsonrpc.NewServer()
|
|
rpcServer.Register("Filecoin", apistruct.PermissionedFullAPI(metrics.MetricedFullAPI(a)))
|
|
|
|
ah := &auth.Handler{
|
|
Verify: a.AuthVerify,
|
|
Next: rpcServer.ServeHTTP,
|
|
}
|
|
|
|
http.Handle("/rpc/v0", ah)
|
|
|
|
importAH := &auth.Handler{
|
|
Verify: a.AuthVerify,
|
|
Next: handleImport(a.(*impl.FullNodeAPI)),
|
|
}
|
|
|
|
http.Handle("/rest/v0/import", importAH)
|
|
|
|
// Prometheus globals are exposed as interfaces, but the prometheus
|
|
// OpenCensus exporter expects a concrete *Registry. The concrete type of
|
|
// the globals are actually *Registry, so we downcast them, staying
|
|
// defensive in case things change under the hood.
|
|
registry, ok := promclient.DefaultRegisterer.(*promclient.Registry)
|
|
if !ok {
|
|
log.Warnf("failed to export default prometheus registry; some metrics will be unavailable; unexpected type: %T", promclient.DefaultRegisterer)
|
|
}
|
|
exporter, err := prometheus.NewExporter(prometheus.Options{
|
|
Registry: registry,
|
|
Namespace: "lotus",
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("could not create the prometheus stats exporter: %v", err)
|
|
}
|
|
|
|
http.Handle("/debug/metrics", exporter)
|
|
|
|
lst, err := manet.Listen(addr)
|
|
if err != nil {
|
|
return xerrors.Errorf("could not listen: %w", err)
|
|
}
|
|
|
|
srv := &http.Server{
|
|
Handler: http.DefaultServeMux,
|
|
BaseContext: func(listener net.Listener) context.Context {
|
|
ctx, _ := tag.New(context.Background(), tag.Upsert(metrics.APIInterface, "lotus-daemon"))
|
|
return ctx
|
|
},
|
|
}
|
|
|
|
sigCh := make(chan os.Signal, 2)
|
|
shutdownDone := make(chan struct{})
|
|
go func() {
|
|
select {
|
|
case sig := <-sigCh:
|
|
log.Warnw("received shutdown", "signal", sig)
|
|
case <-shutdownCh:
|
|
log.Warn("received shutdown")
|
|
}
|
|
|
|
log.Warn("Shutting down...")
|
|
if err := srv.Shutdown(context.TODO()); err != nil {
|
|
log.Errorf("shutting down RPC server failed: %s", err)
|
|
}
|
|
if err := stop(context.TODO()); err != nil {
|
|
log.Errorf("graceful shutting down failed: %s", err)
|
|
}
|
|
log.Warn("Graceful shutdown successful")
|
|
_ = log.Sync() //nolint:errcheck
|
|
close(shutdownDone)
|
|
}()
|
|
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
|
|
|
|
err = srv.Serve(manet.NetListener(lst))
|
|
if err == http.ErrServerClosed {
|
|
<-shutdownDone
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func handleImport(a *impl.FullNodeAPI) func(w http.ResponseWriter, r *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "PUT" {
|
|
w.WriteHeader(404)
|
|
return
|
|
}
|
|
if !auth.HasPerm(r.Context(), nil, apistruct.PermWrite) {
|
|
w.WriteHeader(401)
|
|
_ = json.NewEncoder(w).Encode(struct{ Error string }{"unauthorized: missing write permission"})
|
|
return
|
|
}
|
|
|
|
c, err := a.ClientImportLocal(r.Context(), r.Body)
|
|
if err != nil {
|
|
w.WriteHeader(500)
|
|
_ = json.NewEncoder(w).Encode(struct{ Error string }{err.Error()})
|
|
return
|
|
}
|
|
w.WriteHeader(200)
|
|
err = json.NewEncoder(w).Encode(struct{ Cid cid.Cid }{c})
|
|
if err != nil {
|
|
log.Errorf("/rest/v0/import: Writing response failed: %+v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|