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