lotus/cmd/lotus/rpc.go
Raúl Kripalani 656ece06e5 fix metrics wiring.
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.)
2020-11-02 17:56:49 +00:00

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
}
}
}