github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/metricsdebug/metricsdebug.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package metricsdebug contains the implementation of an api endpoint
     5  // for metrics debug functionality.
     6  package metricsdebug
     7  
     8  import (
     9  	"fmt"
    10  	"sort"
    11  
    12  	"github.com/juju/errors"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/facade"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/state"
    19  )
    20  
    21  func init() {
    22  	common.RegisterStandardFacade("MetricsDebug", 2, NewMetricsDebugAPI)
    23  }
    24  
    25  type metricsDebug interface {
    26  	// MetricBatchesForUnit returns metric batches for the given unit.
    27  	MetricBatchesForUnit(unit string) ([]state.MetricBatch, error)
    28  
    29  	// MetricBatchesForApplication returns metric batches for the given application.
    30  	MetricBatchesForApplication(application string) ([]state.MetricBatch, error)
    31  
    32  	//MetricBatchesForModel returns all metrics batches in the model.
    33  	MetricBatchesForModel() ([]state.MetricBatch, error)
    34  
    35  	// Unit returns the unit based on its name.
    36  	Unit(string) (*state.Unit, error)
    37  
    38  	// Application returns the application based on its name.
    39  	Application(string) (*state.Application, error)
    40  }
    41  
    42  // MetricsDebug defines the methods on the metricsdebug API end point.
    43  type MetricsDebug interface {
    44  	// GetMetrics returns all metrics stored by the state server.
    45  	GetMetrics(arg params.Entities) (params.MetricResults, error)
    46  
    47  	// SetMeterStatus will set the meter status on the given entity tag.
    48  	SetMeterStatus(params.MeterStatusParams) (params.ErrorResults, error)
    49  }
    50  
    51  // MetricsDebugAPI implements the metricsdebug interface and is the concrete
    52  // implementation of the api end point.
    53  type MetricsDebugAPI struct {
    54  	state metricsDebug
    55  }
    56  
    57  var _ MetricsDebug = (*MetricsDebugAPI)(nil)
    58  
    59  // NewMetricsDebugAPI creates a new API endpoint for calling metrics debug functions.
    60  func NewMetricsDebugAPI(
    61  	st *state.State,
    62  	resources facade.Resources,
    63  	authorizer facade.Authorizer,
    64  ) (*MetricsDebugAPI, error) {
    65  	if !authorizer.AuthClient() {
    66  		return nil, common.ErrPerm
    67  	}
    68  
    69  	return &MetricsDebugAPI{
    70  		state: st,
    71  	}, nil
    72  }
    73  
    74  // GetMetrics returns all metrics stored by the state server.
    75  func (api *MetricsDebugAPI) GetMetrics(args params.Entities) (params.MetricResults, error) {
    76  	results := params.MetricResults{
    77  		Results: make([]params.EntityMetrics, len(args.Entities)),
    78  	}
    79  	if len(args.Entities) == 0 {
    80  		batches, err := api.state.MetricBatchesForModel()
    81  		if err != nil {
    82  			return results, errors.Annotate(err, "failed to get metrics")
    83  		}
    84  		return params.MetricResults{
    85  			Results: []params.EntityMetrics{{
    86  				Metrics: api.filterLastValuePerKeyPerUnit(batches),
    87  			}},
    88  		}, nil
    89  	}
    90  	for i, arg := range args.Entities {
    91  		tag, err := names.ParseTag(arg.Tag)
    92  		if err != nil {
    93  			results.Results[i].Error = common.ServerError(err)
    94  			continue
    95  		}
    96  		var batches []state.MetricBatch
    97  		switch tag.Kind() {
    98  		case names.UnitTagKind:
    99  			batches, err = api.state.MetricBatchesForUnit(tag.Id())
   100  			if err != nil {
   101  				err = errors.Annotate(err, "failed to get metrics")
   102  				results.Results[i].Error = common.ServerError(err)
   103  				continue
   104  			}
   105  		case names.ApplicationTagKind:
   106  			batches, err = api.state.MetricBatchesForApplication(tag.Id())
   107  			if err != nil {
   108  				err = errors.Annotate(err, "failed to get metrics")
   109  				results.Results[i].Error = common.ServerError(err)
   110  				continue
   111  			}
   112  		default:
   113  			err := errors.Errorf("invalid tag %v", arg.Tag)
   114  			results.Results[i].Error = common.ServerError(err)
   115  		}
   116  		results.Results[i].Metrics = api.filterLastValuePerKeyPerUnit(batches)
   117  	}
   118  	return results, nil
   119  }
   120  
   121  type byUnit []params.MetricResult
   122  
   123  func (t byUnit) Len() int      { return len(t) }
   124  func (t byUnit) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
   125  func (t byUnit) Less(i, j int) bool {
   126  	return t[i].Unit < t[j].Unit
   127  }
   128  
   129  func (api *MetricsDebugAPI) filterLastValuePerKeyPerUnit(batches []state.MetricBatch) []params.MetricResult {
   130  	metrics := []params.MetricResult{}
   131  	for _, mb := range batches {
   132  		for _, m := range mb.UniqueMetrics() {
   133  			metrics = append(metrics, params.MetricResult{
   134  				Key:   m.Key,
   135  				Value: m.Value,
   136  				Time:  m.Time,
   137  				Unit:  mb.Unit(),
   138  			})
   139  		}
   140  	}
   141  	uniq := map[string]params.MetricResult{}
   142  	for _, m := range metrics {
   143  		// we want unique keys per unit
   144  		uniq[fmt.Sprintf("%s-%s", m.Key, m.Unit)] = m
   145  	}
   146  	results := make([]params.MetricResult, len(uniq))
   147  	i := 0
   148  	for _, m := range uniq {
   149  		results[i] = m
   150  		i++
   151  	}
   152  	sort.Sort(byUnit(results))
   153  	return results
   154  }
   155  
   156  // SetMeterStatus sets meter statuses for entities.
   157  func (api *MetricsDebugAPI) SetMeterStatus(args params.MeterStatusParams) (params.ErrorResults, error) {
   158  	results := params.ErrorResults{
   159  		Results: make([]params.ErrorResult, len(args.Statuses)),
   160  	}
   161  	for i, arg := range args.Statuses {
   162  		tag, err := names.ParseTag(arg.Tag)
   163  		if err != nil {
   164  			results.Results[i].Error = common.ServerError(err)
   165  			continue
   166  		}
   167  		err = api.setEntityMeterStatus(tag, state.MeterStatus{
   168  			Code: state.MeterStatusFromString(arg.Code),
   169  			Info: arg.Info,
   170  		})
   171  		if err != nil {
   172  			results.Results[i].Error = common.ServerError(err)
   173  			continue
   174  		}
   175  	}
   176  	return results, nil
   177  }
   178  
   179  func (api *MetricsDebugAPI) setEntityMeterStatus(entity names.Tag, status state.MeterStatus) error {
   180  	switch entity := entity.(type) {
   181  	case names.UnitTag:
   182  		unit, err := api.state.Unit(entity.Id())
   183  		if err != nil {
   184  			return errors.Trace(err)
   185  		}
   186  		chURL, found := unit.CharmURL()
   187  		if !found {
   188  			return errors.New("no charm url")
   189  		}
   190  		if chURL.Schema != "local" {
   191  			return errors.New("not a local charm")
   192  		}
   193  		err = unit.SetMeterStatus(status.Code.String(), status.Info)
   194  		if err != nil {
   195  			return errors.Trace(err)
   196  		}
   197  	case names.ApplicationTag:
   198  		application, err := api.state.Application(entity.Id())
   199  		if err != nil {
   200  			return errors.Trace(err)
   201  		}
   202  		chURL, _ := application.CharmURL()
   203  		if chURL.Schema != "local" {
   204  			return errors.New("not a local charm")
   205  		}
   206  		units, err := application.AllUnits()
   207  		if err != nil {
   208  			return errors.Trace(err)
   209  		}
   210  		for _, unit := range units {
   211  			err := unit.SetMeterStatus(status.Code.String(), status.Info)
   212  			if err != nil {
   213  				return errors.Trace(err)
   214  			}
   215  		}
   216  	default:
   217  		return errors.Errorf("expected application or unit tag, got %T", entity)
   218  	}
   219  	return nil
   220  }