github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/metricsdebug/collectmetrics.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 "strings" 9 "time" 10 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 "launchpad.net/gnuflag" 15 16 actionapi "github.com/juju/juju/api/action" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/cmd/juju/action" 19 "github.com/juju/juju/cmd/modelcmd" 20 ) 21 22 // TODO(bogdanteleaga): update this once querying for actions by name is implemented. 23 const collectMetricsDoc = ` 24 collect-metrics 25 trigger metrics collection 26 27 Collect metrics infinitely waits for the command to finish. 28 However if you cancel the command while waiting, you can still 29 look for the results under 'juju action status'. 30 ` 31 32 const ( 33 // commandTimeout represents the timeout for executing the command itself 34 commandTimeout = 3 * time.Second 35 ) 36 37 // collectMetricsCommand retrieves metrics stored in the juju controller. 38 type collectMetricsCommand struct { 39 modelcmd.ModelCommandBase 40 units []string 41 services []string 42 } 43 44 // NewCollectMetricsCommand creates a new collectMetricsCommand. 45 func NewCollectMetricsCommand() cmd.Command { 46 return modelcmd.Wrap(&collectMetricsCommand{}) 47 } 48 49 // Info implements Command.Info. 50 func (c *collectMetricsCommand) Info() *cmd.Info { 51 return &cmd.Info{ 52 Name: "collect-metrics", 53 Args: "[service or unit]", 54 Purpose: "collect metrics on the given unit/service", 55 Doc: collectMetricsDoc, 56 } 57 } 58 59 // Init reads and verifies the cli arguments for the collectMetricsCommand 60 func (c *collectMetricsCommand) Init(args []string) error { 61 if len(args) == 0 { 62 return errors.New("you need to specify a unit or service.") 63 } 64 if names.IsValidUnit(args[0]) { 65 c.units = []string{args[0]} 66 } else if names.IsValidService(args[0]) { 67 c.services = []string{args[0]} 68 } else { 69 return errors.Errorf("%q is not a valid unit or service", args[0]) 70 } 71 if err := cmd.CheckEmpty(args[1:]); err != nil { 72 return errors.Errorf("unknown command line arguments: " + strings.Join(args, ",")) 73 } 74 return nil 75 } 76 77 // SetFlags implements Command.SetFlags. 78 func (c *collectMetricsCommand) SetFlags(f *gnuflag.FlagSet) { 79 c.ModelCommandBase.SetFlags(f) 80 } 81 82 type runClient interface { 83 action.APIClient 84 Run(run params.RunParams) ([]params.ActionResult, error) 85 } 86 87 var newRunClient = func(env modelcmd.ModelCommandBase) (runClient, error) { 88 root, err := env.NewAPIRoot() 89 if err != nil { 90 return nil, errors.Trace(err) 91 } 92 return actionapi.NewClient(root), errors.Trace(err) 93 } 94 95 func parseRunOutput(result params.ActionResult) (string, string, error) { 96 if result.Error != nil { 97 return "", "", result.Error 98 } 99 stdout, ok := result.Output["Stdout"].(string) 100 if !ok { 101 return "", "", errors.New("could not read stdout") 102 } 103 stderr, ok := result.Output["Stderr"].(string) 104 if !ok { 105 return "", "", errors.New("could not read stderr") 106 } 107 return strings.Trim(stdout, " \t\n"), strings.Trim(stderr, " \t\n"), nil 108 } 109 110 func parseActionResult(result params.ActionResult) (string, error) { 111 stdout, stderr, err := parseRunOutput(result) 112 if err != nil { 113 return "", errors.Trace(err) 114 } 115 tag, err := names.ParseUnitTag(result.Action.Receiver) 116 if err != nil { 117 return "", errors.Trace(err) 118 } 119 if stdout == "ok" { 120 return tag.Id(), nil 121 } 122 if strings.Contains(stderr, "No such file or directory") { 123 return "", errors.New("not a metered charm") 124 } 125 return tag.Id(), nil 126 } 127 128 // Run implements Command.Run. 129 func (c *collectMetricsCommand) Run(ctx *cmd.Context) error { 130 runnerClient, err := newRunClient(c.ModelCommandBase) 131 if err != nil { 132 return errors.Trace(err) 133 } 134 defer runnerClient.Close() 135 136 runParams := params.RunParams{ 137 Timeout: commandTimeout, 138 Units: c.units, 139 Services: c.services, 140 Commands: "nc -U ../metrics-collect.socket", 141 } 142 143 // trigger metrics collection 144 runResults, err := runnerClient.Run(runParams) 145 if err != nil { 146 return errors.Trace(err) 147 } 148 149 // We want to wait for the action results indefinitely. Discard the tick. 150 wait := time.NewTimer(0 * time.Second) 151 _ = <-wait.C 152 // trigger sending metrics in parallel 153 resultChannel := make(chan string, len(runResults)) 154 for _, result := range runResults { 155 r := result 156 if r.Error != nil { 157 fmt.Fprintf(ctx.Stdout, "failed to collect metrics: %v\n", err) 158 resultChannel <- "invalid id" 159 continue 160 } 161 tag, err := names.ParseActionTag(r.Action.Tag) 162 if err != nil { 163 fmt.Fprintf(ctx.Stdout, "failed to collect metrics: %v\n", err) 164 resultChannel <- "invalid id" 165 continue 166 } 167 actionResult, err := getActionResult(runnerClient, tag.Id(), wait) 168 if err != nil { 169 fmt.Fprintf(ctx.Stdout, "failed to collect metrics: %v\n", err) 170 resultChannel <- "invalid id" 171 continue 172 } 173 unitId, err := parseActionResult(actionResult) 174 if err != nil { 175 fmt.Fprintf(ctx.Stdout, "failed to collect metrics: %v\n", err) 176 resultChannel <- "invalid id" 177 continue 178 } 179 go func() { 180 defer func() { 181 resultChannel <- unitId 182 }() 183 sendParams := params.RunParams{ 184 Timeout: commandTimeout, 185 Units: []string{unitId}, 186 Commands: "nc -U ../metrics-send.socket", 187 } 188 sendResults, err := runnerClient.Run(sendParams) 189 if err != nil { 190 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v: %v\n", unitId, err) 191 return 192 } 193 if len(sendResults) != 1 { 194 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v\n", unitId) 195 return 196 } 197 if sendResults[0].Error != nil { 198 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v: %v\n", unitId, sendResults[0].Error) 199 return 200 } 201 tag, err := names.ParseActionTag(sendResults[0].Action.Tag) 202 if err != nil { 203 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v: %v\n", unitId, err) 204 return 205 } 206 actionResult, err := getActionResult(runnerClient, tag.Id(), wait) 207 if err != nil { 208 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v: %v\n", unitId, err) 209 return 210 } 211 stdout, stderr, err := parseRunOutput(actionResult) 212 if err != nil { 213 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v: %v\n", unitId, err) 214 return 215 } 216 if stdout != "ok" { 217 fmt.Fprintf(ctx.Stdout, "failed to send metrics for unit %v: %v\n", unitId, errors.New(stderr)) 218 } 219 }() 220 } 221 222 for _ = range runResults { 223 // The default is to wait forever for the command to finish. 224 select { 225 case <-resultChannel: 226 } 227 } 228 return nil 229 } 230 231 // getActionResult abstracts over the action CLI function that we use here to fetch results 232 var getActionResult = func(c runClient, actionId string, wait *time.Timer) (params.ActionResult, error) { 233 return action.GetActionResult(c, actionId, wait) 234 }