github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/api/v1/model_handlers.go (about)

     1  // Copyright 2015 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package v1
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"net/http"
    21  	"strings"
    22  	"time"
    23  
    24  	restful "github.com/emicklei/go-restful"
    25  
    26  	"k8s.io/heapster/metrics/api/v1/types"
    27  	"k8s.io/heapster/metrics/core"
    28  	"k8s.io/heapster/metrics/util/metrics"
    29  )
    30  
    31  // for testing
    32  var nowFunc = time.Now
    33  
    34  // errModelNotActivated is the error that is returned by the API handlers
    35  // when manager.model has not been initialized.
    36  var errModelNotActivated = errors.New("the model is not activated")
    37  
    38  // Deprecated - clients should switch to full metric names ASAP.
    39  var deprecatedMetricNamesConversion = map[string]string{
    40  	"cpu-usage":      "cpu/usage_rate",
    41  	"cpu-limit":      "cpu/limit",
    42  	"memory-limit":   "memory/limit",
    43  	"memory-usage":   "memory/usage",
    44  	"memory-working": "memory/working_set",
    45  }
    46  
    47  type clusterMetricsFetcher interface {
    48  	availableClusterMetrics(request *restful.Request, response *restful.Response)
    49  	clusterMetrics(request *restful.Request, response *restful.Response)
    50  
    51  	nodeList(request *restful.Request, response *restful.Response)
    52  	availableNodeMetrics(request *restful.Request, response *restful.Response)
    53  	nodeMetrics(request *restful.Request, response *restful.Response)
    54  
    55  	namespaceList(request *restful.Request, response *restful.Response)
    56  	availableNamespaceMetrics(request *restful.Request, response *restful.Response)
    57  	namespaceMetrics(request *restful.Request, response *restful.Response)
    58  
    59  	namespacePodList(request *restful.Request, response *restful.Response)
    60  	availablePodMetrics(request *restful.Request, response *restful.Response)
    61  	podMetrics(request *restful.Request, response *restful.Response)
    62  
    63  	podContainerList(request *restful.Request, response *restful.Response)
    64  
    65  	availablePodContainerMetrics(request *restful.Request, response *restful.Response)
    66  	podContainerMetrics(request *restful.Request, response *restful.Response)
    67  
    68  	nodeSystemContainerList(request *restful.Request, response *restful.Response)
    69  	availableFreeContainerMetrics(request *restful.Request, response *restful.Response)
    70  	freeContainerMetrics(request *restful.Request, response *restful.Response)
    71  
    72  	podListMetrics(request *restful.Request, response *restful.Response)
    73  
    74  	isRunningInKubernetes() bool
    75  }
    76  
    77  // addClusterMetricsRoutes adds all the standard model routes to a WebService.
    78  // It should already have a base path registered.
    79  func addClusterMetricsRoutes(a clusterMetricsFetcher, ws *restful.WebService) {
    80  	// The /metrics/ endpoint returns a list of all available metrics for the Cluster entity of the model.
    81  	ws.Route(ws.GET("/metrics/").
    82  		To(metrics.InstrumentRouteFunc("availableClusterMetrics", a.availableClusterMetrics)).
    83  		Doc("Get a list of all available metrics for the Cluster entity").
    84  		Operation("availableClusterMetrics"))
    85  
    86  	// The /metrics/{metric-name} endpoint exposes an aggregated metric for the Cluster entity of the model.
    87  	ws.Route(ws.GET("/metrics/{metric-name:*}").
    88  		To(metrics.InstrumentRouteFunc("clusterMetrics", a.clusterMetrics)).
    89  		Doc("Export an aggregated cluster-level metric").
    90  		Operation("clusterMetrics").
    91  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
    92  		Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")).
    93  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
    94  		Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
    95  		Writes(types.MetricResult{}))
    96  
    97  	// The /nodes/{node-name}/metrics endpoint returns a list of all nodes with some metrics.
    98  	ws.Route(ws.GET("/nodes/").
    99  		To(metrics.InstrumentRouteFunc("nodeList", a.nodeList)).
   100  		Doc("Get a list of all nodes that have some current metrics").
   101  		Operation("nodeList"))
   102  
   103  	// The /nodes/{node-name}/metrics endpoint returns a list of all available metrics for a Node entity.
   104  	ws.Route(ws.GET("/nodes/{node-name}/metrics/").
   105  		To(metrics.InstrumentRouteFunc("availableNodeMetrics", a.availableNodeMetrics)).
   106  		Doc("Get a list of all available metrics for a Node entity").
   107  		Operation("availableNodeMetrics").
   108  		Param(ws.PathParameter("node-name", "The name of the node to lookup").DataType("string")))
   109  
   110  	// The /nodes/{node-name}/metrics/{metric-name} endpoint exposes a metric for a Node entity of the model.
   111  	// The {node-name} parameter is the hostname of a specific node.
   112  	ws.Route(ws.GET("/nodes/{node-name}/metrics/{metric-name:*}").
   113  		To(metrics.InstrumentRouteFunc("nodeMetrics", a.nodeMetrics)).
   114  		Doc("Export a node-level metric").
   115  		Operation("nodeMetrics").
   116  		Param(ws.PathParameter("node-name", "The name of the node to lookup").DataType("string")).
   117  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   118  		Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")).
   119  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   120  		Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   121  		Writes(types.MetricResult{}))
   122  
   123  	if a.isRunningInKubernetes() {
   124  
   125  		ws.Route(ws.GET("/namespaces/").
   126  			To(metrics.InstrumentRouteFunc("namespaceList", a.namespaceList)).
   127  			Doc("Get a list of all namespaces that have some current metrics").
   128  			Operation("namespaceList"))
   129  
   130  		// The /namespaces/{namespace-name}/metrics endpoint returns a list of all available metrics for a Namespace entity.
   131  		ws.Route(ws.GET("/namespaces/{namespace-name}/metrics").
   132  			To(metrics.InstrumentRouteFunc("availableNamespaceMetrics", a.availableNamespaceMetrics)).
   133  			Doc("Get a list of all available metrics for a Namespace entity").
   134  			Operation("availableNamespaceMetrics").
   135  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")))
   136  
   137  		// The /namespaces/{namespace-name}/metrics/{metric-name} endpoint exposes an aggregated metrics
   138  		// for a Namespace entity of the model.
   139  		ws.Route(ws.GET("/namespaces/{namespace-name}/metrics/{metric-name:*}").
   140  			To(metrics.InstrumentRouteFunc("namespaceMetrics", a.namespaceMetrics)).
   141  			Doc("Export an aggregated namespace-level metric").
   142  			Operation("namespaceMetrics").
   143  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   144  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   145  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   146  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   147  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   148  			Writes(types.MetricResult{}))
   149  
   150  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/").
   151  			To(metrics.InstrumentRouteFunc("namespacePodList", a.namespacePodList)).
   152  			Doc("Get a list of pods from the given namespace that have some metrics").
   153  			Operation("namespacePodList").
   154  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")))
   155  
   156  		// The /namespaces/{namespace-name}/pods/{pod-name}/metrics endpoint returns a list of all available metrics for a Pod entity.
   157  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/metrics").
   158  			To(metrics.InstrumentRouteFunc("availablePodMetrics", a.availablePodMetrics)).
   159  			Doc("Get a list of all available metrics for a Pod entity").
   160  			Operation("availablePodMetrics").
   161  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   162  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")))
   163  
   164  		// The /namespaces/{namespace-name}/pods/{pod-name}/metrics/{metric-name} endpoint exposes
   165  		// an aggregated metric for a Pod entity of the model.
   166  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/metrics/{metric-name:*}").
   167  			To(metrics.InstrumentRouteFunc("podMetrics", a.podMetrics)).
   168  			Doc("Export an aggregated pod-level metric").
   169  			Operation("podMetrics").
   170  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   171  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")).
   172  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   173  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   174  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   175  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   176  			Writes(types.MetricResult{}))
   177  
   178  		// The /namespaces/{namespace-name}/pods/{pod-name}/containers endpoint
   179  		// returns a list of all containers for a Pod entity.
   180  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers").
   181  			To(metrics.InstrumentRouteFunc("podContainerList", a.podContainerList)).
   182  			Doc("Get a list of containers for a Pod entity ").
   183  			Operation("podContainerList").
   184  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   185  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")))
   186  
   187  		// The /namespaces/{namespace-name}/pods/{pod-name}/containers/metrics/{container-name}/metrics endpoint
   188  		// returns a list of all available metrics for a Pod Container entity.
   189  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics").
   190  			To(metrics.InstrumentRouteFunc("availableContainerMetrics", a.availablePodContainerMetrics)).
   191  			Doc("Get a list of all available metrics for a Pod entity").
   192  			Operation("availableContainerMetrics").
   193  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   194  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")).
   195  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")))
   196  
   197  		// The /namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics/{metric-name} endpoint exposes
   198  		// a metric for a Container entity of the model.
   199  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics/{metric-name:*}").
   200  			To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerMetrics)).
   201  			Doc("Export an aggregated metric for a Pod Container").
   202  			Operation("podContainerMetrics").
   203  			Param(ws.PathParameter("namespace-name", "The name of the namespace to use").DataType("string")).
   204  			Param(ws.PathParameter("pod-name", "The name of the pod to use").DataType("string")).
   205  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")).
   206  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   207  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   208  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   209  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   210  			Writes(types.MetricResult{}))
   211  	}
   212  
   213  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/").
   214  		To(metrics.InstrumentRouteFunc("systemContainerList", a.nodeSystemContainerList)).
   215  		Doc("Get a list of all non-pod containers with some metrics").
   216  		Operation("systemContainerList").
   217  		Param(ws.PathParameter("node-name", "The name of the namespace to lookup").DataType("string")))
   218  
   219  	// The /nodes/{node-name}/freecontainers/{container-name}/metrics endpoint
   220  	// returns a list of all available metrics for a Free Container entity.
   221  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/{container-name}/metrics").
   222  		To(metrics.InstrumentRouteFunc("availableMetrics", a.availableFreeContainerMetrics)).
   223  		Doc("Get a list of all available metrics for a free Container entity").
   224  		Operation("availableMetrics").
   225  		Param(ws.PathParameter("node-name", "The name of the namespace to lookup").DataType("string")).
   226  		Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")))
   227  
   228  	// The /nodes/{node-name}/freecontainers/{container-name}/metrics/{metric-name} endpoint exposes
   229  	// a metric for a free Container entity of the model.
   230  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/{container-name}/metrics/{metric-name:*}").
   231  		To(metrics.InstrumentRouteFunc("freeContainerMetrics", a.freeContainerMetrics)).
   232  		Doc("Export a container-level metric for a free container").
   233  		Operation("freeContainerMetrics").
   234  		Param(ws.PathParameter("node-name", "The name of the node to use").DataType("string")).
   235  		Param(ws.PathParameter("container-name", "The name of the container to use").DataType("string")).
   236  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   237  		Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   238  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   239  		Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   240  		Writes(types.MetricResult{}))
   241  
   242  	if a.isRunningInKubernetes() {
   243  		// The /namespaces/{namespace-name}/pod-list/{pod-list}/metrics/{metric-name} endpoint exposes
   244  		// metrics for a list of pods of the model.
   245  		ws.Route(ws.GET("/namespaces/{namespace-name}/pod-list/{pod-list}/metrics/{metric-name:*}").
   246  			To(metrics.InstrumentRouteFunc("podListMetric", a.podListMetrics)).
   247  			Doc("Export a metric for all pods from the given list").
   248  			Operation("podListMetric").
   249  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   250  			Param(ws.PathParameter("pod-list", "Comma separated list of pod names to lookup").DataType("string")).
   251  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   252  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   253  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   254  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   255  			Writes(types.MetricResult{}))
   256  	}
   257  }
   258  
   259  func (a *Api) isRunningInKubernetes() bool {
   260  	return a.runningInKubernetes
   261  }
   262  
   263  // RegisterModel registers the Model API endpoints.
   264  // All endpoints that end with a {metric-name} also receive a start time query parameter.
   265  // The start and end times should be specified as a string, formatted according to RFC 3339.
   266  func (a *Api) RegisterModel(container *restful.Container) {
   267  	ws := new(restful.WebService)
   268  	ws.Path("/api/v1/model").
   269  		Doc("Root endpoint of the stats model").
   270  		Consumes("*/*").
   271  		Produces(restful.MIME_JSON)
   272  
   273  	addClusterMetricsRoutes(a, ws)
   274  
   275  	ws.Route(ws.GET("/debug/allkeys").
   276  		To(metrics.InstrumentRouteFunc("debugAllKeys", a.allKeys)).
   277  		Doc("Get keys of all metric sets available").
   278  		Operation("debugAllKeys"))
   279  	container.Add(ws)
   280  }
   281  
   282  // availableMetrics returns a list of available cluster metric names.
   283  func (a *Api) availableClusterMetrics(request *restful.Request, response *restful.Response) {
   284  	a.processMetricNamesRequest(core.ClusterKey(), response)
   285  }
   286  
   287  // availableMetrics returns a list of available node metric names.
   288  func (a *Api) availableNodeMetrics(request *restful.Request, response *restful.Response) {
   289  	a.processMetricNamesRequest(core.NodeKey(request.PathParameter("node-name")), response)
   290  }
   291  
   292  // availableMetrics returns a list of available namespace metric names.
   293  func (a *Api) availableNamespaceMetrics(request *restful.Request, response *restful.Response) {
   294  	a.processMetricNamesRequest(core.NamespaceKey(request.PathParameter("namespace-name")), response)
   295  }
   296  
   297  // availableMetrics returns a list of available pod metric names.
   298  func (a *Api) availablePodMetrics(request *restful.Request, response *restful.Response) {
   299  	a.processMetricNamesRequest(
   300  		core.PodKey(request.PathParameter("namespace-name"),
   301  			request.PathParameter("pod-name")), response)
   302  }
   303  
   304  // availableMetrics returns a list of available pod metric names.
   305  func (a *Api) availablePodContainerMetrics(request *restful.Request, response *restful.Response) {
   306  	a.processMetricNamesRequest(
   307  		core.PodContainerKey(request.PathParameter("namespace-name"),
   308  			request.PathParameter("pod-name"),
   309  			request.PathParameter("container-name"),
   310  		), response)
   311  }
   312  
   313  // availableMetrics returns a list of available pod metric names.
   314  func (a *Api) availableFreeContainerMetrics(request *restful.Request, response *restful.Response) {
   315  	a.processMetricNamesRequest(
   316  		core.NodeContainerKey(request.PathParameter("node-name"),
   317  			request.PathParameter("container-name"),
   318  		), response)
   319  }
   320  
   321  func (a *Api) nodeList(request *restful.Request, response *restful.Response) {
   322  	response.WriteEntity(a.metricSink.GetNodes())
   323  }
   324  
   325  func (a *Api) namespaceList(request *restful.Request, response *restful.Response) {
   326  	response.WriteEntity(a.metricSink.GetNamespaces())
   327  }
   328  
   329  func (a *Api) namespacePodList(request *restful.Request, response *restful.Response) {
   330  	response.WriteEntity(a.metricSink.GetPodsFromNamespace(request.PathParameter("namespace-name")))
   331  }
   332  
   333  func (a *Api) podContainerList(request *restful.Request, response *restful.Response) {
   334  	response.WriteEntity(a.metricSink.GetContainersForPodFromNamespace(request.PathParameter("namespace-name"), request.PathParameter("pod-name")))
   335  }
   336  
   337  func (a *Api) nodeSystemContainerList(request *restful.Request, response *restful.Response) {
   338  	response.WriteEntity(a.metricSink.GetSystemContainersFromNode(request.PathParameter("node-name")))
   339  }
   340  
   341  func (a *Api) allKeys(request *restful.Request, response *restful.Response) {
   342  	response.WriteEntity(a.metricSink.GetMetricSetKeys())
   343  }
   344  
   345  // clusterMetrics returns a metric timeseries for a metric of the Cluster entity.
   346  func (a *Api) clusterMetrics(request *restful.Request, response *restful.Response) {
   347  	a.processMetricRequest(core.ClusterKey(), request, response)
   348  }
   349  
   350  // nodeMetrics returns a metric timeseries for a metric of the Node entity.
   351  func (a *Api) nodeMetrics(request *restful.Request, response *restful.Response) {
   352  	a.processMetricRequest(core.NodeKey(request.PathParameter("node-name")),
   353  		request, response)
   354  }
   355  
   356  // namespaceMetrics returns a metric timeseries for a metric of the Namespace entity.
   357  func (a *Api) namespaceMetrics(request *restful.Request, response *restful.Response) {
   358  	a.processMetricRequest(core.NamespaceKey(request.PathParameter("namespace-name")),
   359  		request, response)
   360  }
   361  
   362  // podMetrics returns a metric timeseries for a metric of the Pod entity.
   363  func (a *Api) podMetrics(request *restful.Request, response *restful.Response) {
   364  	a.processMetricRequest(
   365  		core.PodKey(request.PathParameter("namespace-name"),
   366  			request.PathParameter("pod-name")),
   367  		request, response)
   368  }
   369  
   370  func (a *Api) podListMetrics(request *restful.Request, response *restful.Response) {
   371  	start, end, err := getStartEndTime(request)
   372  	if err != nil {
   373  		response.WriteError(http.StatusBadRequest, err)
   374  		return
   375  	}
   376  	ns := request.PathParameter("namespace-name")
   377  	keys := []string{}
   378  	metricName := request.PathParameter("metric-name")
   379  	convertedMetricName := convertMetricName(metricName)
   380  	for _, podName := range strings.Split(request.PathParameter("pod-list"), ",") {
   381  		keys = append(keys, core.PodKey(ns, podName))
   382  	}
   383  
   384  	labels, err := getLabels(request)
   385  	if err != nil {
   386  		response.WriteError(http.StatusBadRequest, err)
   387  		return
   388  	}
   389  
   390  	var metrics map[string][]core.TimestampedMetricValue
   391  	if labels != nil {
   392  		metrics = a.metricSink.GetLabeledMetric(convertedMetricName, labels, keys, start, end)
   393  	} else {
   394  		metrics = a.metricSink.GetMetric(convertedMetricName, keys, start, end)
   395  	}
   396  
   397  	result := types.MetricResultList{
   398  		Items: make([]types.MetricResult, 0, len(keys)),
   399  	}
   400  	for _, key := range keys {
   401  		result.Items = append(result.Items, exportTimestampedMetricValue(metrics[key]))
   402  	}
   403  	response.PrettyPrint(false)
   404  	response.WriteEntity(result)
   405  }
   406  
   407  // podContainerMetrics returns a metric timeseries for a metric of a Pod Container entity.
   408  // podContainerMetrics uses the namespace-name/pod-name/container-name path.
   409  func (a *Api) podContainerMetrics(request *restful.Request, response *restful.Response) {
   410  	a.processMetricRequest(
   411  		core.PodContainerKey(request.PathParameter("namespace-name"),
   412  			request.PathParameter("pod-name"),
   413  			request.PathParameter("container-name"),
   414  		),
   415  		request, response)
   416  }
   417  
   418  // freeContainerMetrics returns a metric timeseries for a metric of the Container entity.
   419  // freeContainerMetrics addresses only free containers, by using the node-name/container-name path.
   420  func (a *Api) freeContainerMetrics(request *restful.Request, response *restful.Response) {
   421  	a.processMetricRequest(
   422  		core.NodeContainerKey(request.PathParameter("node-name"),
   423  			request.PathParameter("container-name"),
   424  		),
   425  		request, response)
   426  }
   427  
   428  // parseRequestParam parses a time.Time from a named QueryParam, using the RFC3339 format.
   429  func parseTimeParam(queryParam string, defaultValue time.Time) (time.Time, error) {
   430  	if queryParam != "" {
   431  		reqStamp, err := time.Parse(time.RFC3339, queryParam)
   432  		if err != nil {
   433  			return time.Time{}, fmt.Errorf("timestamp argument cannot be parsed: %s", err)
   434  		}
   435  		return reqStamp, nil
   436  	}
   437  	return defaultValue, nil
   438  }
   439  
   440  func (a *Api) processMetricRequest(key string, request *restful.Request, response *restful.Response) {
   441  	start, end, err := getStartEndTime(request)
   442  	if err != nil {
   443  		response.WriteError(http.StatusBadRequest, err)
   444  		return
   445  	}
   446  	metricName := request.PathParameter("metric-name")
   447  	convertedMetricName := convertMetricName(metricName)
   448  	labels, err := getLabels(request)
   449  	if err != nil {
   450  		response.WriteError(http.StatusBadRequest, err)
   451  		return
   452  	}
   453  
   454  	var metrics map[string][]core.TimestampedMetricValue
   455  	if labels != nil {
   456  		metrics = a.metricSink.GetLabeledMetric(convertedMetricName, labels, []string{key}, start, end)
   457  	} else {
   458  		metrics = a.metricSink.GetMetric(convertedMetricName, []string{key}, start, end)
   459  	}
   460  	converted := exportTimestampedMetricValue(metrics[key])
   461  	response.WriteEntity(converted)
   462  }
   463  
   464  func (a *Api) processMetricNamesRequest(key string, response *restful.Response) {
   465  	metricNames := a.metricSink.GetMetricNames(key)
   466  	response.WriteEntity(metricNames)
   467  }
   468  
   469  func convertMetricName(metricName string) string {
   470  	if convertedMetricName, ok := deprecatedMetricNamesConversion[metricName]; ok {
   471  		return convertedMetricName
   472  	}
   473  	return metricName
   474  }
   475  
   476  func getStartEndTime(request *restful.Request) (time.Time, time.Time, error) {
   477  	start, err := parseTimeParam(request.QueryParameter("start"), time.Time{})
   478  	if err != nil {
   479  		return time.Time{}, time.Time{}, err
   480  	}
   481  	end, err := parseTimeParam(request.QueryParameter("end"), nowFunc())
   482  	if err != nil {
   483  		return time.Time{}, time.Time{}, err
   484  	}
   485  	return start, end, nil
   486  }
   487  
   488  func exportTimestampedMetricValue(values []core.TimestampedMetricValue) types.MetricResult {
   489  	result := types.MetricResult{
   490  		Metrics: make([]types.MetricPoint, 0, len(values)),
   491  	}
   492  	for _, value := range values {
   493  		if result.LatestTimestamp.Before(value.Timestamp) {
   494  			result.LatestTimestamp = value.Timestamp
   495  		}
   496  		// TODO: clean up types in model api
   497  		var intValue int64
   498  		if value.ValueType == core.ValueInt64 {
   499  			intValue = value.IntValue
   500  		} else {
   501  			intValue = int64(value.FloatValue)
   502  		}
   503  
   504  		result.Metrics = append(result.Metrics, types.MetricPoint{
   505  			Timestamp: value.Timestamp,
   506  			Value:     uint64(intValue),
   507  		})
   508  	}
   509  	return result
   510  }
   511  
   512  func getLabels(request *restful.Request) (map[string]string, error) {
   513  	labelsRaw := request.QueryParameter("labels")
   514  	if labelsRaw == "" {
   515  		return nil, nil
   516  	}
   517  
   518  	kvPairs := strings.Split(labelsRaw, ",")
   519  	labels := make(map[string]string, len(kvPairs))
   520  	for _, kvPair := range kvPairs {
   521  		kvSplit := strings.SplitN(kvPair, ":", 2)
   522  		if len(kvSplit) != 2 || kvSplit[0] == "" || kvSplit[1] == "" {
   523  			return nil, fmt.Errorf("invalid label pair %q", kvPair)
   524  		}
   525  		labels[kvSplit[0]] = kvSplit[1]
   526  	}
   527  
   528  	return labels, nil
   529  }