From 19b1124999cc9879156fdedc60073eb9e21f815f Mon Sep 17 00:00:00 2001 From: Hector Sanjuan Date: Tue, 23 Oct 2018 20:21:27 +0200 Subject: [PATCH] Make metrics human Issue #572 exposes metrics but they carry the peer ID in binary. This was ok with our internal codecs but it doesn't seem to work very well with json, and makes the output format unusable. This makes the Metric.Peer field a string. Additinoally, fixes calling the command without arguments and displaying the date in the right format. License: MIT Signed-off-by: Hector Sanjuan --- api/rest/client/methods.go | 6 +++- api/rest/client/request.go | 3 +- api/types.go | 53 ++++++++++++++++++++++++++++-- cluster.go | 5 ++- cmd/ipfs-cluster-ctl/formatters.go | 5 ++- cmd/ipfs-cluster-ctl/main.go | 15 +++++++-- monitor/metrics/store.go | 4 +-- monitor/metrics/util.go | 4 +-- rpc_api.go | 6 ++-- 9 files changed, 85 insertions(+), 16 deletions(-) diff --git a/api/rest/client/methods.go b/api/rest/client/methods.go index 2fcba6dc..2ddf36b5 100644 --- a/api/rest/client/methods.go +++ b/api/rest/client/methods.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/url" "os" @@ -205,9 +206,12 @@ func (c *defaultClient) GetConnectGraph() (api.ConnectGraphSerial, error) { return graphS, err } -// Metrics returns a map with the latest metrics of matching name +// Metrics returns a map with the latest valid metrics of the given name // for the current cluster peers. func (c *defaultClient) Metrics(name string) ([]api.Metric, error) { + if name == "" { + return nil, errors.New("bad metric name") + } var metrics []api.Metric err := c.do("GET", fmt.Sprintf("/monitor/metrics/%s", name), nil, nil, &metrics) return metrics, err diff --git a/api/rest/client/request.go b/api/rest/client/request.go index 5714ba4e..13a978ca 100644 --- a/api/rest/client/request.go +++ b/api/rest/client/request.go @@ -92,9 +92,10 @@ func (c *defaultClient) handleResponse(resp *http.Response, obj interface{}) err var apiErr api.Error err = json.Unmarshal(body, &apiErr) if err != nil { + // not json. 404s etc. return &api.Error{ Code: resp.StatusCode, - Message: err.Error(), + Message: string(body), } } return &apiErr diff --git a/api/types.go b/api/types.go index 8ee54be0..c4d0bb2d 100644 --- a/api/types.go +++ b/api/types.go @@ -9,6 +9,8 @@ package api import ( + "bytes" + "encoding/json" "fmt" "regexp" "sort" @@ -847,10 +849,10 @@ func (n *NodeWithMeta) Size() uint64 { // the Value, which should be interpreted by the PinAllocator. type Metric struct { Name string - Peer peer.ID // filled-in by Cluster. + Peer peer.ID Value string - Expire int64 // UnixNano - Valid bool // if the metric is not valid it will be discarded + Expire int64 + Valid bool } // SetTTL sets Metric to expire after the given time.Duration @@ -876,6 +878,51 @@ func (m *Metric) Discard() bool { return !m.Valid || m.Expired() } +// helper for JSON marshaling. The Metric type is already +// serializable, but not pretty to humans (API). +type metricSerial struct { + Name string `json:"name"` + Peer string `json:"peer"` + Value string `json:"value"` + Expire int64 `json:"expire"` + Valid bool `json:"valid"` +} + +// MarshalJSON allows a Metric to produce a JSON representation +// of itself. +func (m *Metric) MarshalJSON() ([]byte, error) { + return json.Marshal(&metricSerial{ + Name: m.Name, + Peer: peer.IDB58Encode(m.Peer), + Value: m.Value, + Expire: m.Expire, + }) +} + +// UnmarshalJSON decodes JSON on top of the Metric. +func (m *Metric) UnmarshalJSON(j []byte) error { + if bytes.Equal(j, []byte("null")) { + return nil + } + + ms := &metricSerial{} + err := json.Unmarshal(j, ms) + if err != nil { + return err + } + + p, err := peer.IDB58Decode(ms.Peer) + if err != nil { + return err + } + + m.Name = ms.Name + m.Peer = p + m.Value = ms.Value + m.Expire = ms.Expire + return nil +} + // Alert carries alerting information about a peer. WIP. type Alert struct { Peer peer.ID diff --git a/cluster.go b/cluster.go index 4982a3a3..0efa21a8 100644 --- a/cluster.go +++ b/cluster.go @@ -275,7 +275,10 @@ func (c *Cluster) alertsHandler() { // only the leader handles alerts leader, err := c.consensus.Leader() if err == nil && leader == c.id { - logger.Warningf("Peer %s received alert for %s in %s", c.id, alrt.MetricName, alrt.Peer.Pretty()) + logger.Warningf( + "Peer %s received alert for %s in %s", + c.id, alrt.MetricName, alrt.Peer, + ) switch alrt.MetricName { case pingMetricName: c.repinFromPeer(alrt.Peer) diff --git a/cmd/ipfs-cluster-ctl/formatters.go b/cmd/ipfs-cluster-ctl/formatters.go index 3d149784..3cd08168 100644 --- a/cmd/ipfs-cluster-ctl/formatters.go +++ b/cmd/ipfs-cluster-ctl/formatters.go @@ -6,8 +6,10 @@ import ( "fmt" "sort" "strings" + "time" "github.com/ipfs/ipfs-cluster/api" + peer "github.com/libp2p/go-libp2p-peer" ) func jsonFormatObject(resp interface{}) { @@ -216,7 +218,8 @@ func textFormatPrintAddedOutput(obj *api.AddedOutput) { } func textFormatPrintMetric(obj *api.Metric) { - fmt.Printf("%s: %s | Expire : %d\n", obj.Peer.Pretty(), obj.Value, obj.Expire) + date := time.Unix(0, obj.Expire).UTC().Format(time.RFC3339) + fmt.Printf("%s: %s | Expire: %s\n", peer.IDB58Encode(obj.Peer), obj.Value, date) } func textFormatPrintError(obj *api.Error) { diff --git a/cmd/ipfs-cluster-ctl/main.go b/cmd/ipfs-cluster-ctl/main.go index 02ed42c3..b69ca6a3 100644 --- a/cmd/ipfs-cluster-ctl/main.go +++ b/cmd/ipfs-cluster-ctl/main.go @@ -768,10 +768,21 @@ graph of the connections. Output is a dot file encoding the cluster's connectio Description: ` This commands displays the latest valid metrics of the given type logged by this peer for all current cluster peers. + +Currently supported metrics depend on the informer component used, +but usually are: + +- freespace +- ping `, - ArgsUsage: "Metric name", + ArgsUsage: "", Action: func(c *cli.Context) error { - resp, cerr := globalClient.Metrics(c.Args().First()) + metric := c.Args().First() + if metric == "" { + checkErr("", errors.New("provide a metric name")) + } + + resp, cerr := globalClient.Metrics(metric) formatResponse(c, resp, cerr) return nil }, diff --git a/monitor/metrics/store.go b/monitor/metrics/store.go index 34a73694..1bc1683c 100644 --- a/monitor/metrics/store.go +++ b/monitor/metrics/store.go @@ -71,14 +71,14 @@ func (mtrs *Store) Latest(name string) []api.Metric { // PeerMetrics returns the latest metrics for a given peer ID for // all known metrics types. It may return expired metrics. -func (mtrs *Store) PeerMetrics(peer peer.ID) []api.Metric { +func (mtrs *Store) PeerMetrics(pid peer.ID) []api.Metric { mtrs.mux.RLock() defer mtrs.mux.RUnlock() result := make([]api.Metric, 0) for _, byPeer := range mtrs.byName { - window, ok := byPeer[peer] + window, ok := byPeer[pid] if !ok { continue } diff --git a/monitor/metrics/util.go b/monitor/metrics/util.go index 17527027..79c77931 100644 --- a/monitor/metrics/util.go +++ b/monitor/metrics/util.go @@ -10,8 +10,8 @@ import ( // peerset func PeersetFilter(metrics []api.Metric, peerset []peer.ID) []api.Metric { peerMap := make(map[peer.ID]struct{}) - for _, peer := range peerset { - peerMap[peer] = struct{}{} + for _, pid := range peerset { + peerMap[pid] = struct{}{} } filtered := make([]api.Metric, 0, len(metrics)) diff --git a/rpc_api.go b/rpc_api.go index f6068ec3..5c553109 100644 --- a/rpc_api.go +++ b/rpc_api.go @@ -205,12 +205,12 @@ func (rpcapi *RPCAPI) BlockAllocate(ctx context.Context, in api.PinSerial, out * // Returned metrics are Valid and belong to current // Cluster peers. metrics := rpcapi.c.monitor.LatestMetrics(pingMetricName) - peers := make([]peer.ID, len(metrics), len(metrics)) + peers := make([]string, len(metrics), len(metrics)) for i, m := range metrics { - peers[i] = m.Peer + peers[i] = peer.IDB58Encode(m.Peer) } - *out = api.PeersToStrings(peers) + *out = peers return nil }