github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/metricsdebug/metrics.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package metricsdebug 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/gosuri/uitable" 14 "github.com/juju/cmd" 15 "github.com/juju/errors" 16 "github.com/juju/gnuflag" 17 "gopkg.in/juju/names.v2" 18 19 "github.com/juju/juju/api/metricsdebug" 20 "github.com/juju/juju/apiserver/params" 21 jujucmd "github.com/juju/juju/cmd" 22 "github.com/juju/juju/cmd/modelcmd" 23 ) 24 25 const metricsDoc = ` 26 Display recently collected metrics. 27 ` 28 29 // MetricsCommand retrieves metrics stored in the juju controller. 30 type MetricsCommand struct { 31 modelcmd.ModelCommandBase 32 out cmd.Output 33 34 Tags []string 35 All bool 36 } 37 38 // New creates a new MetricsCommand. 39 func New() cmd.Command { 40 return modelcmd.Wrap(&MetricsCommand{}) 41 } 42 43 // Info implements Command.Info. 44 func (c *MetricsCommand) Info() *cmd.Info { 45 return jujucmd.Info(&cmd.Info{ 46 Name: "metrics", 47 Args: "[tag1[...tagN]]", 48 Purpose: "Retrieve metrics collected by specified entities.", 49 Doc: metricsDoc, 50 }) 51 } 52 53 // Init reads and verifies the cli arguments for the MetricsCommand 54 func (c *MetricsCommand) Init(args []string) error { 55 if !c.All && len(args) == 0 { 56 return errors.New("you need to specify at least one unit or application") 57 } else if c.All && len(args) > 0 { 58 return errors.New("cannot use --all with additional entities") 59 } 60 c.Tags = make([]string, len(args)) 61 for i, arg := range args { 62 if names.IsValidUnit(arg) { 63 c.Tags[i] = names.NewUnitTag(arg).String() 64 } else if names.IsValidApplication(arg) { 65 c.Tags[i] = names.NewApplicationTag(arg).String() 66 } else { 67 return errors.Errorf("%q is not a valid unit or application", args[0]) 68 } 69 } 70 return nil 71 } 72 73 // SetFlags implements cmd.Command.SetFlags. 74 func (c *MetricsCommand) SetFlags(f *gnuflag.FlagSet) { 75 c.ModelCommandBase.SetFlags(f) 76 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 77 "tabular": formatTabular, 78 "json": cmd.FormatJson, 79 "yaml": cmd.FormatYaml, 80 }) 81 f.BoolVar(&c.All, "all", false, "retrieve metrics collected by all units in the model") 82 } 83 84 type GetMetricsClient interface { 85 GetMetrics(tags ...string) ([]params.MetricResult, error) 86 Close() error 87 } 88 89 var newClient = func(env modelcmd.ModelCommandBase) (GetMetricsClient, error) { 90 state, err := env.NewAPIRoot() 91 if err != nil { 92 return nil, errors.Trace(err) 93 } 94 return metricsdebug.NewClient(state), nil 95 } 96 97 type metricSlice []metric 98 99 // Len implements the sort.Interface. 100 func (slice metricSlice) Len() int { 101 return len(slice) 102 } 103 104 // Less implements the sort.Interface. 105 func (slice metricSlice) Less(i, j int) bool { 106 if slice[i].Metric == slice[j].Metric { 107 return renderLabels(slice[i].Labels) < renderLabels(slice[j].Labels) 108 } 109 return slice[i].Metric < slice[j].Metric 110 } 111 112 // Swap implements the sort.Interface. 113 func (slice metricSlice) Swap(i, j int) { 114 slice[i], slice[j] = slice[j], slice[i] 115 } 116 117 type metric struct { 118 Unit string `json:"unit" yaml:"unit"` 119 Timestamp time.Time `json:"timestamp" yaml:"timestamp"` 120 Metric string `json:"metric" yaml:"metric"` 121 Value string `json:"value" yaml:"value"` 122 Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` 123 } 124 125 // Run implements Command.Run. 126 func (c *MetricsCommand) Run(ctx *cmd.Context) error { 127 client, err := newClient(c.ModelCommandBase) 128 if err != nil { 129 return errors.Trace(err) 130 } 131 var metrics []params.MetricResult 132 if c.All { 133 metrics, err = client.GetMetrics() 134 } else { 135 metrics, err = client.GetMetrics(c.Tags...) 136 } 137 if err != nil { 138 return errors.Trace(err) 139 } 140 defer client.Close() 141 if len(metrics) == 0 { 142 return nil 143 } 144 results := make([]metric, len(metrics)) 145 for i, m := range metrics { 146 results[i] = metric{ 147 Unit: m.Unit, 148 Timestamp: m.Time, 149 Metric: m.Key, 150 Value: m.Value, 151 Labels: m.Labels, 152 } 153 } 154 sortedResults := metricSlice(results) 155 sort.Sort(sortedResults) 156 157 return errors.Trace(c.out.Write(ctx, results)) 158 } 159 160 // formatTabular returns a tabular view of collected metrics. 161 func formatTabular(writer io.Writer, value interface{}) error { 162 metrics, ok := value.([]metric) 163 if !ok { 164 return errors.Errorf("expected value of type %T, got %T", metrics, value) 165 } 166 table := uitable.New() 167 table.MaxColWidth = 50 168 table.Wrap = true 169 for _, col := range []int{1, 2, 3, 4} { 170 table.RightAlign(col) 171 } 172 table.AddRow("UNIT", "TIMESTAMP", "METRIC", "VALUE", "LABELS") 173 for _, m := range metrics { 174 table.AddRow(m.Unit, m.Timestamp.Format(time.RFC3339), m.Metric, m.Value, renderLabels(m.Labels)) 175 } 176 _, err := fmt.Fprint(writer, table.String()) 177 return errors.Trace(err) 178 } 179 180 func renderLabels(m map[string]string) string { 181 var result []string 182 for k, v := range m { 183 result = append(result, fmt.Sprintf("%s=%s", k, v)) 184 } 185 sort.Strings(result) 186 return strings.Join(result, ",") 187 }