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  }