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:
parent
53a16fba8f
commit
19b1124999
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -205,9 +206,12 @@ func (c *defaultClient) GetConnectGraph() (api.ConnectGraphSerial, error) {
|
||||||
return graphS, err
|
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.
|
// for the current cluster peers.
|
||||||
func (c *defaultClient) Metrics(name string) ([]api.Metric, error) {
|
func (c *defaultClient) Metrics(name string) ([]api.Metric, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, errors.New("bad metric name")
|
||||||
|
}
|
||||||
var metrics []api.Metric
|
var metrics []api.Metric
|
||||||
err := c.do("GET", fmt.Sprintf("/monitor/metrics/%s", name), nil, nil, &metrics)
|
err := c.do("GET", fmt.Sprintf("/monitor/metrics/%s", name), nil, nil, &metrics)
|
||||||
return metrics, err
|
return metrics, err
|
||||||
|
|
|
@ -92,9 +92,10 @@ func (c *defaultClient) handleResponse(resp *http.Response, obj interface{}) err
|
||||||
var apiErr api.Error
|
var apiErr api.Error
|
||||||
err = json.Unmarshal(body, &apiErr)
|
err = json.Unmarshal(body, &apiErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// not json. 404s etc.
|
||||||
return &api.Error{
|
return &api.Error{
|
||||||
Code: resp.StatusCode,
|
Code: resp.StatusCode,
|
||||||
Message: err.Error(),
|
Message: string(body),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &apiErr
|
return &apiErr
|
||||||
|
|
53
api/types.go
53
api/types.go
|
@ -9,6 +9,8 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -847,10 +849,10 @@ func (n *NodeWithMeta) Size() uint64 {
|
||||||
// the Value, which should be interpreted by the PinAllocator.
|
// the Value, which should be interpreted by the PinAllocator.
|
||||||
type Metric struct {
|
type Metric struct {
|
||||||
Name string
|
Name string
|
||||||
Peer peer.ID // filled-in by Cluster.
|
Peer peer.ID
|
||||||
Value string
|
Value string
|
||||||
Expire int64 // UnixNano
|
Expire int64
|
||||||
Valid bool // if the metric is not valid it will be discarded
|
Valid bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTTL sets Metric to expire after the given time.Duration
|
// SetTTL sets Metric to expire after the given time.Duration
|
||||||
|
@ -876,6 +878,51 @@ func (m *Metric) Discard() bool {
|
||||||
return !m.Valid || m.Expired()
|
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.
|
// Alert carries alerting information about a peer. WIP.
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
Peer peer.ID
|
Peer peer.ID
|
||||||
|
|
|
@ -275,7 +275,10 @@ func (c *Cluster) alertsHandler() {
|
||||||
// only the leader handles alerts
|
// only the leader handles alerts
|
||||||
leader, err := c.consensus.Leader()
|
leader, err := c.consensus.Leader()
|
||||||
if err == nil && leader == c.id {
|
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 {
|
switch alrt.MetricName {
|
||||||
case pingMetricName:
|
case pingMetricName:
|
||||||
c.repinFromPeer(alrt.Peer)
|
c.repinFromPeer(alrt.Peer)
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ipfs/ipfs-cluster/api"
|
"github.com/ipfs/ipfs-cluster/api"
|
||||||
|
peer "github.com/libp2p/go-libp2p-peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func jsonFormatObject(resp interface{}) {
|
func jsonFormatObject(resp interface{}) {
|
||||||
|
@ -216,7 +218,8 @@ func textFormatPrintAddedOutput(obj *api.AddedOutput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func textFormatPrintMetric(obj *api.Metric) {
|
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) {
|
func textFormatPrintError(obj *api.Error) {
|
||||||
|
|
|
@ -768,10 +768,21 @@ graph of the connections. Output is a dot file encoding the cluster's connectio
|
||||||
Description: `
|
Description: `
|
||||||
This commands displays the latest valid metrics of the given type logged
|
This commands displays the latest valid metrics of the given type logged
|
||||||
by this peer for all current cluster peers.
|
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 {
|
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)
|
formatResponse(c, resp, cerr)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
|
@ -71,14 +71,14 @@ func (mtrs *Store) Latest(name string) []api.Metric {
|
||||||
|
|
||||||
// PeerMetrics returns the latest metrics for a given peer ID for
|
// PeerMetrics returns the latest metrics for a given peer ID for
|
||||||
// all known metrics types. It may return expired metrics.
|
// 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()
|
mtrs.mux.RLock()
|
||||||
defer mtrs.mux.RUnlock()
|
defer mtrs.mux.RUnlock()
|
||||||
|
|
||||||
result := make([]api.Metric, 0)
|
result := make([]api.Metric, 0)
|
||||||
|
|
||||||
for _, byPeer := range mtrs.byName {
|
for _, byPeer := range mtrs.byName {
|
||||||
window, ok := byPeer[peer]
|
window, ok := byPeer[pid]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
// peerset
|
// peerset
|
||||||
func PeersetFilter(metrics []api.Metric, peerset []peer.ID) []api.Metric {
|
func PeersetFilter(metrics []api.Metric, peerset []peer.ID) []api.Metric {
|
||||||
peerMap := make(map[peer.ID]struct{})
|
peerMap := make(map[peer.ID]struct{})
|
||||||
for _, peer := range peerset {
|
for _, pid := range peerset {
|
||||||
peerMap[peer] = struct{}{}
|
peerMap[pid] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered := make([]api.Metric, 0, len(metrics))
|
filtered := make([]api.Metric, 0, len(metrics))
|
||||||
|
|
|
@ -205,12 +205,12 @@ func (rpcapi *RPCAPI) BlockAllocate(ctx context.Context, in api.PinSerial, out *
|
||||||
// Returned metrics are Valid and belong to current
|
// Returned metrics are Valid and belong to current
|
||||||
// Cluster peers.
|
// Cluster peers.
|
||||||
metrics := rpcapi.c.monitor.LatestMetrics(pingMetricName)
|
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 {
|
for i, m := range metrics {
|
||||||
peers[i] = m.Peer
|
peers[i] = peer.IDB58Encode(m.Peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
*out = api.PeersToStrings(peers)
|
*out = peers
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user