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 <code@hector.link>
This commit is contained in:
Hector Sanjuan 2018-10-23 20:21:27 +02:00
parent 53a16fba8f
commit 19b1124999
9 changed files with 85 additions and 16 deletions

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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) {

View File

@ -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: "<metric name>",
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
},

View File

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

View File

@ -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))

View File

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