github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/api/v1/historical_handlers.go (about) 1 // Copyright 2016 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 "fmt" 19 "net/http" 20 "strconv" 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 // HistoricalApi wraps the standard API to overload the fetchers to use the 32 // one of the persistent storage sinks instead of the in-memory metrics sink 33 type HistoricalApi struct { 34 *Api 35 } 36 37 // metricsAggregationFetcher represents the capability to fetch aggregations for metrics 38 type metricsAggregationFetcher interface { 39 clusterAggregations(request *restful.Request, response *restful.Response) 40 nodeAggregations(request *restful.Request, response *restful.Response) 41 namespaceAggregations(request *restful.Request, response *restful.Response) 42 podAggregations(request *restful.Request, response *restful.Response) 43 podContainerAggregations(request *restful.Request, response *restful.Response) 44 freeContainerAggregations(request *restful.Request, response *restful.Response) 45 podListAggregations(request *restful.Request, response *restful.Response) 46 47 isRunningInKubernetes() bool 48 } 49 50 // addAggregationRoutes adds routes to a webservice which point to a metricsAggregationFetcher's methods 51 func addAggregationRoutes(a metricsAggregationFetcher, ws *restful.WebService) { 52 // The /metrics-aggregated/{aggregations}/{metric-name} endpoint exposes some aggregations for the Cluster entity of the historical API. 53 ws.Route(ws.GET("/metrics-aggregated/{aggregations}/{metric-name:*}"). 54 To(metrics.InstrumentRouteFunc("clusterMetrics", a.clusterAggregations)). 55 Doc("Export some cluster-level metric aggregations"). 56 Operation("clusterAggregations"). 57 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 58 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 59 Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")). 60 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 61 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 62 Writes(types.MetricAggregationResult{})) 63 64 // The /nodes/{node-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes some aggregations for a Node entity of the historical API. 65 // The {node-name} parameter is the hostname of a specific node. 66 ws.Route(ws.GET("/nodes/{node-name}/metrics-aggregated/{aggregations}/{metric-name:*}"). 67 To(metrics.InstrumentRouteFunc("nodeMetrics", a.nodeAggregations)). 68 Doc("Export a node-level metric"). 69 Operation("nodeAggregations"). 70 Param(ws.PathParameter("node-name", "The name of the node to lookup").DataType("string")). 71 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 72 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 73 Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")). 74 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 75 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 76 Writes(types.MetricAggregationResult{})) 77 78 if a.isRunningInKubernetes() { 79 // The /namespaces/{namespace-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes some aggregations 80 // for a Namespace entity of the historical API. 81 ws.Route(ws.GET("/namespaces/{namespace-name}/metrics-aggregated/{aggregations}/{metric-name:*}"). 82 To(metrics.InstrumentRouteFunc("namespaceMetrics", a.namespaceAggregations)). 83 Doc("Export some namespace-level metric aggregations"). 84 Operation("namespaceAggregations"). 85 Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")). 86 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 87 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 88 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 89 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 90 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 91 Writes(types.MetricAggregationResult{})) 92 93 // The /namespaces/{namespace-name}/pods/{pod-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 94 // some aggregations for a Pod entity of the historical API. 95 ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/metrics-aggregated/{aggregations}/{metric-name:*}"). 96 To(metrics.InstrumentRouteFunc("podMetrics", a.podAggregations)). 97 Doc("Export some pod-level metric aggregations"). 98 Operation("podAggregations"). 99 Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")). 100 Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")). 101 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 102 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 103 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 104 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 105 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 106 Writes(types.MetricAggregationResult{})) 107 108 // The /namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 109 // some aggregations for a Container entity of the historical API. 110 ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name:*}"). 111 To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerAggregations)). 112 Doc("Export some aggregations for a Pod Container"). 113 Operation("podContainerAggregations"). 114 Param(ws.PathParameter("namespace-name", "The name of the namespace to use").DataType("string")). 115 Param(ws.PathParameter("pod-name", "The name of the pod to use").DataType("string")). 116 Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")). 117 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 118 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 119 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 120 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 121 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 122 Writes(types.MetricAggregationResult{})) 123 124 // The /pod-id/{pod-id}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 125 // some aggregations for a Pod entity of the historical API. 126 ws.Route(ws.GET("/pod-id/{pod-id}/metrics-aggregated/{aggregations}/{metric-name:*}"). 127 To(metrics.InstrumentRouteFunc("podMetrics", a.podAggregations)). 128 Doc("Export some pod-level metric aggregations"). 129 Operation("podAggregations"). 130 Param(ws.PathParameter("pod-id", "The UID of the pod to lookup").DataType("string")). 131 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 132 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 133 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 134 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 135 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 136 Writes(types.MetricAggregationResult{})) 137 138 // The /pod-id/{pod-id}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 139 // some aggregations for a Container entity of the historical API. 140 ws.Route(ws.GET("/pod-id/{pod-id}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name:*}"). 141 To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerAggregations)). 142 Doc("Export some aggregations for a Pod Container"). 143 Operation("podContainerAggregations"). 144 Param(ws.PathParameter("pod-id", "The name of the pod to use").DataType("string")). 145 Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")). 146 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 147 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 148 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 149 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 150 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 151 Writes(types.MetricAggregationResult{})) 152 } 153 154 // The /nodes/{node-name}/freecontainers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 155 // some aggregations for a free Container entity of the historical API. 156 ws.Route(ws.GET("/nodes/{node-name}/freecontainers/{container-name}/metrics-aggregated/{aggregations}/{metric-name:*}"). 157 To(metrics.InstrumentRouteFunc("freeContainerMetrics", a.freeContainerAggregations)). 158 Doc("Export a contsome iner-level metric aggregations for a free container"). 159 Operation("freeContainerAggregations"). 160 Param(ws.PathParameter("node-name", "The name of the node to use").DataType("string")). 161 Param(ws.PathParameter("container-name", "The name of the container to use").DataType("string")). 162 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 163 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 164 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 165 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 166 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 167 Writes(types.MetricAggregationResult{})) 168 169 if a.isRunningInKubernetes() { 170 // The /namespaces/{namespace-name}/pod-list/{pod-list}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 171 // metrics for a list of pods of the historical API. 172 ws.Route(ws.GET("/namespaces/{namespace-name}/pod-list/{pod-list}/metrics-aggregated/{aggregations}/{metric-name:*}"). 173 To(metrics.InstrumentRouteFunc("podListAggregations", a.podListAggregations)). 174 Doc("Export some aggregations for all pods from the given list"). 175 Operation("podListAggregations"). 176 Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")). 177 Param(ws.PathParameter("pod-list", "Comma separated list of pod names to lookup").DataType("string")). 178 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 179 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 180 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 181 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 182 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 183 Writes(types.MetricAggregationResultList{})) 184 185 // The /pod-id-list/{pod-id-list}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 186 // metrics for a list of pod ids of the historical API. 187 ws.Route(ws.GET("/pod-id-list/{pod-id-list}/metrics-aggregated/{aggregations}/{metric-name:*}"). 188 To(metrics.InstrumentRouteFunc("podListAggregations", a.podListAggregations)). 189 Doc("Export an aggregation for all pods from the given list"). 190 Operation("podListAggregations"). 191 Param(ws.PathParameter("pod-id-list", "Comma separated list of pod UIDs to lookup").DataType("string")). 192 Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")). 193 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 194 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 195 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 196 Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")). 197 Writes(types.MetricAggregationResultList{})) 198 } 199 } 200 201 // RegisterHistorical registers the Historical API endpoints. It will register the same endpoints 202 // as those in the model API, plus endpoints for aggregation retrieval, and endpoints to retrieve pod 203 // metrics by using the pod id. 204 func (normalApi *Api) RegisterHistorical(container *restful.Container) { 205 ws := new(restful.WebService) 206 ws.Path("/api/v1/historical"). 207 Doc("Root endpoint of the historical access API"). 208 Consumes("*/*"). 209 Produces(restful.MIME_JSON) 210 211 a := &HistoricalApi{normalApi} 212 addClusterMetricsRoutes(a, ws) 213 addAggregationRoutes(a, ws) 214 215 // register the endpoint for fetching raw metrics based on pod id 216 if a.isRunningInKubernetes() { 217 // The /pod-id/{pod-id}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 218 // some aggregations for a Pod entity of the historical API. 219 ws.Route(ws.GET("/pod-id/{pod-id}/metrics/{metric-name:*}"). 220 To(metrics.InstrumentRouteFunc("podMetrics", a.podMetrics)). 221 Doc("Export some pod-level metric aggregations"). 222 Operation("podAggregations"). 223 Param(ws.PathParameter("pod-id", "The UID of the pod to lookup").DataType("string")). 224 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 225 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 226 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 227 Writes(types.MetricResult{})) 228 229 // The /pod-id/{pod-id}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 230 // some aggregations for a Container entity of the historical API. 231 ws.Route(ws.GET("/pod-id/{pod-id}/containers/{container-name}/metrics/{metric-name:*}"). 232 To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerMetrics)). 233 Doc("Export some aggregations for a Pod Container"). 234 Operation("podContainerAggregations"). 235 Param(ws.PathParameter("pod-id", "The uid of the pod to use").DataType("string")). 236 Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")). 237 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 238 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 239 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 240 Writes(types.MetricResult{})) 241 242 // The /pod-id-list/{pod-id-list}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes 243 // metrics for a list of pod ids of the historical API. 244 ws.Route(ws.GET("/pod-id-list/{pod-id-list}/metrics/{metric-name:*}"). 245 To(metrics.InstrumentRouteFunc("podListAggregations", a.podListMetrics)). 246 Doc("Export an aggregation for all pods from the given list"). 247 Operation("podListAggregations"). 248 Param(ws.PathParameter("pod-id-list", "Comma separated list of pod UIDs to lookup").DataType("string")). 249 Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")). 250 Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")). 251 Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")). 252 Writes(types.MetricResultList{})) 253 } 254 255 container.Add(ws) 256 } 257 258 // availableClusterMetrics returns a list of available cluster metric names. 259 func (a *HistoricalApi) availableClusterMetrics(request *restful.Request, response *restful.Response) { 260 key := core.HistoricalKey{ObjectType: core.MetricSetTypeCluster} 261 a.processMetricNamesRequest(key, response) 262 } 263 264 // availableNodeMetrics returns a list of available node metric names. 265 func (a *HistoricalApi) availableNodeMetrics(request *restful.Request, response *restful.Response) { 266 key := core.HistoricalKey{ 267 ObjectType: core.MetricSetTypeNode, 268 NodeName: request.PathParameter("node-name"), 269 } 270 a.processMetricNamesRequest(key, response) 271 } 272 273 // availableNamespaceMetrics returns a list of available namespace metric names. 274 func (a *HistoricalApi) availableNamespaceMetrics(request *restful.Request, response *restful.Response) { 275 key := core.HistoricalKey{ 276 ObjectType: core.MetricSetTypeNamespace, 277 NamespaceName: request.PathParameter("namespace-name"), 278 } 279 a.processMetricNamesRequest(key, response) 280 } 281 282 // availablePodMetrics returns a list of available pod metric names. 283 func (a *HistoricalApi) availablePodMetrics(request *restful.Request, response *restful.Response) { 284 key := core.HistoricalKey{ 285 ObjectType: core.MetricSetTypePod, 286 NamespaceName: request.PathParameter("namespace-name"), 287 PodName: request.PathParameter("pod-name"), 288 } 289 a.processMetricNamesRequest(key, response) 290 } 291 292 // availablePodContainerMetrics returns a list of available pod container metric names. 293 func (a *HistoricalApi) availablePodContainerMetrics(request *restful.Request, response *restful.Response) { 294 key := core.HistoricalKey{ 295 ObjectType: core.MetricSetTypePodContainer, 296 NamespaceName: request.PathParameter("namespace-name"), 297 PodName: request.PathParameter("pod-name"), 298 ContainerName: request.PathParameter("container-name"), 299 } 300 a.processMetricNamesRequest(key, response) 301 } 302 303 // availableFreeContainerMetrics returns a list of available pod metric names. 304 func (a *HistoricalApi) availableFreeContainerMetrics(request *restful.Request, response *restful.Response) { 305 key := core.HistoricalKey{ 306 ObjectType: core.MetricSetTypeSystemContainer, 307 NodeName: request.PathParameter("node-name"), 308 ContainerName: request.PathParameter("container-name"), 309 } 310 a.processMetricNamesRequest(key, response) 311 } 312 313 // nodeList lists all nodes for which we have metrics 314 func (a *HistoricalApi) nodeList(request *restful.Request, response *restful.Response) { 315 if resp, err := a.historicalSource.GetNodes(); err != nil { 316 response.WriteError(http.StatusInternalServerError, err) 317 } else { 318 response.WriteEntity(resp) 319 } 320 } 321 322 // namespaceList lists all namespaces for which we have metrics 323 func (a *HistoricalApi) namespaceList(request *restful.Request, response *restful.Response) { 324 if resp, err := a.historicalSource.GetNamespaces(); err != nil { 325 response.WriteError(http.StatusInternalServerError, err) 326 } else { 327 response.WriteEntity(resp) 328 } 329 } 330 331 // namespacePodList lists all pods for which we have metrics in a particular namespace 332 func (a *HistoricalApi) namespacePodList(request *restful.Request, response *restful.Response) { 333 if resp, err := a.historicalSource.GetPodsFromNamespace(request.PathParameter("namespace-name")); err != nil { 334 response.WriteError(http.StatusInternalServerError, err) 335 } else { 336 response.WriteEntity(resp) 337 } 338 } 339 340 // nodeSystemContainerList lists all system containers on a node for which we have metrics 341 func (a *HistoricalApi) nodeSystemContainerList(request *restful.Request, response *restful.Response) { 342 if resp, err := a.historicalSource.GetSystemContainersFromNode(request.PathParameter("node-name")); err != nil { 343 response.WriteError(http.StatusInternalServerError, err) 344 } else { 345 response.WriteEntity(resp) 346 } 347 } 348 349 // clusterMetrics returns a metric timeseries for a metric of the Cluster entity. 350 func (a *HistoricalApi) clusterMetrics(request *restful.Request, response *restful.Response) { 351 key := core.HistoricalKey{ObjectType: core.MetricSetTypeCluster} 352 a.processMetricRequest(key, request, response) 353 } 354 355 // nodeMetrics returns a metric timeseries for a metric of the Node entity. 356 func (a *HistoricalApi) nodeMetrics(request *restful.Request, response *restful.Response) { 357 key := core.HistoricalKey{ 358 ObjectType: core.MetricSetTypeNode, 359 NodeName: request.PathParameter("node-name"), 360 } 361 a.processMetricRequest(key, request, response) 362 } 363 364 // namespaceMetrics returns a metric timeseries for a metric of the Namespace entity. 365 func (a *HistoricalApi) namespaceMetrics(request *restful.Request, response *restful.Response) { 366 key := core.HistoricalKey{ 367 ObjectType: core.MetricSetTypeNamespace, 368 NamespaceName: request.PathParameter("namespace-name"), 369 } 370 a.processMetricRequest(key, request, response) 371 } 372 373 // podMetrics returns a metric timeseries for a metric of the Pod entity. 374 func (a *HistoricalApi) podMetrics(request *restful.Request, response *restful.Response) { 375 var key core.HistoricalKey 376 if request.PathParameter("pod-id") != "" { 377 key = core.HistoricalKey{ 378 ObjectType: core.MetricSetTypePod, 379 PodId: request.PathParameter("pod-id"), 380 } 381 } else { 382 key = core.HistoricalKey{ 383 ObjectType: core.MetricSetTypePod, 384 NamespaceName: request.PathParameter("namespace-name"), 385 PodName: request.PathParameter("pod-name"), 386 } 387 } 388 a.processMetricRequest(key, request, response) 389 } 390 391 // freeContainerMetrics returns a metric timeseries for a metric of the Container entity. 392 // freeContainerMetrics addresses only free containers. 393 func (a *HistoricalApi) freeContainerMetrics(request *restful.Request, response *restful.Response) { 394 key := core.HistoricalKey{ 395 ObjectType: core.MetricSetTypeSystemContainer, 396 NodeName: request.PathParameter("node-name"), 397 ContainerName: request.PathParameter("container-name"), 398 } 399 a.processMetricRequest(key, request, response) 400 } 401 402 // podListMetrics returns a list of metric timeseries for each for the listed nodes 403 func (a *HistoricalApi) podListMetrics(request *restful.Request, response *restful.Response) { 404 start, end, err := getStartEndTimeHistorical(request) 405 if err != nil { 406 response.WriteError(http.StatusBadRequest, err) 407 return 408 } 409 410 keys := []core.HistoricalKey{} 411 if request.PathParameter("pod-id-list") != "" { 412 for _, podId := range strings.Split(request.PathParameter("pod-id-list"), ",") { 413 key := core.HistoricalKey{ 414 ObjectType: core.MetricSetTypePod, 415 PodId: podId, 416 } 417 keys = append(keys, key) 418 } 419 } else { 420 for _, podName := range strings.Split(request.PathParameter("pod-list"), ",") { 421 key := core.HistoricalKey{ 422 ObjectType: core.MetricSetTypePod, 423 NamespaceName: request.PathParameter("namespace-name"), 424 PodName: podName, 425 } 426 keys = append(keys, key) 427 } 428 } 429 430 labels, err := getLabels(request) 431 if err != nil { 432 response.WriteError(http.StatusBadRequest, err) 433 return 434 } 435 436 metricName := request.PathParameter("metric-name") 437 convertedMetricName := convertMetricName(metricName) 438 439 var metrics map[core.HistoricalKey][]core.TimestampedMetricValue 440 if labels != nil { 441 metrics, err = a.historicalSource.GetLabeledMetric(convertedMetricName, labels, keys, start, end) 442 } else { 443 metrics, err = a.historicalSource.GetMetric(convertedMetricName, keys, start, end) 444 } 445 446 if err != nil { 447 response.WriteError(http.StatusInternalServerError, err) 448 return 449 } 450 451 result := types.MetricResultList{ 452 Items: make([]types.MetricResult, 0, len(keys)), 453 } 454 for _, key := range keys { 455 result.Items = append(result.Items, exportTimestampedMetricValue(metrics[key])) 456 } 457 response.PrettyPrint(false) 458 response.WriteEntity(result) 459 } 460 461 // podContainerMetrics returns a metric timeseries for a metric of a Pod Container entity. 462 func (a *HistoricalApi) podContainerMetrics(request *restful.Request, response *restful.Response) { 463 var key core.HistoricalKey 464 if request.PathParameter("pod-id") != "" { 465 key = core.HistoricalKey{ 466 ObjectType: core.MetricSetTypePodContainer, 467 PodId: request.PathParameter("pod-id"), 468 ContainerName: request.PathParameter("container-name"), 469 } 470 } else { 471 key = core.HistoricalKey{ 472 ObjectType: core.MetricSetTypePodContainer, 473 NamespaceName: request.PathParameter("namespace-name"), 474 PodName: request.PathParameter("pod-name"), 475 ContainerName: request.PathParameter("container-name"), 476 } 477 } 478 a.processMetricRequest(key, request, response) 479 } 480 481 // clusterAggregations returns a metric timeseries for a metric of the Cluster entity. 482 func (a *HistoricalApi) clusterAggregations(request *restful.Request, response *restful.Response) { 483 key := core.HistoricalKey{ObjectType: core.MetricSetTypeCluster} 484 a.processAggregationRequest(key, request, response) 485 } 486 487 // nodeAggregations returns a metric timeseries for a metric of the Node entity. 488 func (a *HistoricalApi) nodeAggregations(request *restful.Request, response *restful.Response) { 489 key := core.HistoricalKey{ 490 ObjectType: core.MetricSetTypeNode, 491 NodeName: request.PathParameter("node-name"), 492 } 493 a.processAggregationRequest(key, request, response) 494 } 495 496 // namespaceAggregations returns a metric timeseries for a metric of the Namespace entity. 497 func (a *HistoricalApi) namespaceAggregations(request *restful.Request, response *restful.Response) { 498 key := core.HistoricalKey{ 499 ObjectType: core.MetricSetTypeNamespace, 500 NamespaceName: request.PathParameter("namespace-name"), 501 } 502 a.processAggregationRequest(key, request, response) 503 } 504 505 // podAggregations returns a metric timeseries for a metric of the Pod entity. 506 func (a *HistoricalApi) podAggregations(request *restful.Request, response *restful.Response) { 507 var key core.HistoricalKey 508 if request.PathParameter("pod-id") != "" { 509 key = core.HistoricalKey{ 510 ObjectType: core.MetricSetTypePod, 511 PodId: request.PathParameter("pod-id"), 512 } 513 } else { 514 key = core.HistoricalKey{ 515 ObjectType: core.MetricSetTypePod, 516 NamespaceName: request.PathParameter("namespace-name"), 517 PodName: request.PathParameter("pod-name"), 518 } 519 } 520 a.processAggregationRequest(key, request, response) 521 } 522 523 // podContainerAggregations returns a metric timeseries for a metric of a Pod Container entity. 524 func (a *HistoricalApi) podContainerAggregations(request *restful.Request, response *restful.Response) { 525 var key core.HistoricalKey 526 if request.PathParameter("pod-id") != "" { 527 key = core.HistoricalKey{ 528 ObjectType: core.MetricSetTypePodContainer, 529 PodId: request.PathParameter("pod-id"), 530 ContainerName: request.PathParameter("container-name"), 531 } 532 } else { 533 key = core.HistoricalKey{ 534 ObjectType: core.MetricSetTypePodContainer, 535 NamespaceName: request.PathParameter("namespace-name"), 536 PodName: request.PathParameter("pod-name"), 537 ContainerName: request.PathParameter("container-name"), 538 } 539 } 540 a.processAggregationRequest(key, request, response) 541 } 542 543 // freeContainerAggregations returns a metric timeseries for a metric of the Container entity. 544 // freeContainerAggregations addresses only free containers. 545 func (a *HistoricalApi) freeContainerAggregations(request *restful.Request, response *restful.Response) { 546 key := core.HistoricalKey{ 547 ObjectType: core.MetricSetTypeSystemContainer, 548 NodeName: request.PathParameter("node-name"), 549 ContainerName: request.PathParameter("container-name"), 550 } 551 a.processAggregationRequest(key, request, response) 552 } 553 554 // podListAggregations returns a list of metric timeseries for the specified pods. 555 func (a *HistoricalApi) podListAggregations(request *restful.Request, response *restful.Response) { 556 start, end, err := getStartEndTimeHistorical(request) 557 if err != nil { 558 response.WriteError(http.StatusBadRequest, err) 559 return 560 } 561 bucketSize, err := getBucketSize(request) 562 if err != nil { 563 response.WriteError(http.StatusBadRequest, err) 564 return 565 } 566 aggregations, err := getAggregations(request) 567 if err != nil { 568 response.WriteError(http.StatusBadRequest, err) 569 return 570 } 571 labels, err := getLabels(request) 572 if err != nil { 573 response.WriteError(http.StatusBadRequest, err) 574 return 575 } 576 keys := []core.HistoricalKey{} 577 if request.PathParameter("pod-id-list") != "" { 578 for _, podId := range strings.Split(request.PathParameter("pod-id-list"), ",") { 579 key := core.HistoricalKey{ 580 ObjectType: core.MetricSetTypePod, 581 PodId: podId, 582 } 583 keys = append(keys, key) 584 } 585 } else { 586 for _, podName := range strings.Split(request.PathParameter("pod-list"), ",") { 587 key := core.HistoricalKey{ 588 ObjectType: core.MetricSetTypePod, 589 NamespaceName: request.PathParameter("namespace-name"), 590 PodName: podName, 591 } 592 keys = append(keys, key) 593 } 594 } 595 metricName := request.PathParameter("metric-name") 596 convertedMetricName := convertMetricName(metricName) 597 var metrics map[core.HistoricalKey][]core.TimestampedAggregationValue 598 if labels != nil { 599 metrics, err = a.historicalSource.GetLabeledAggregation(convertedMetricName, labels, aggregations, keys, start, end, bucketSize) 600 } else { 601 metrics, err = a.historicalSource.GetAggregation(convertedMetricName, aggregations, keys, start, end, bucketSize) 602 } 603 if err != nil { 604 response.WriteError(http.StatusInternalServerError, err) 605 return 606 } 607 608 result := types.MetricAggregationResultList{ 609 Items: make([]types.MetricAggregationResult, 0, len(keys)), 610 } 611 for _, key := range keys { 612 result.Items = append(result.Items, exportTimestampedAggregationValue(metrics[key])) 613 } 614 response.PrettyPrint(false) 615 response.WriteEntity(result) 616 } 617 618 // processMetricRequest retrieves a metric for the object at the requested key. 619 func (a *HistoricalApi) processMetricRequest(key core.HistoricalKey, request *restful.Request, response *restful.Response) { 620 start, end, err := getStartEndTimeHistorical(request) 621 if err != nil { 622 response.WriteError(http.StatusBadRequest, err) 623 return 624 } 625 labels, err := getLabels(request) 626 if err != nil { 627 response.WriteError(http.StatusBadRequest, err) 628 return 629 } 630 metricName := request.PathParameter("metric-name") 631 convertedMetricName := convertMetricName(metricName) 632 633 var metrics map[core.HistoricalKey][]core.TimestampedMetricValue 634 if labels != nil { 635 metrics, err = a.historicalSource.GetLabeledMetric(convertedMetricName, labels, []core.HistoricalKey{key}, start, end) 636 } else { 637 metrics, err = a.historicalSource.GetMetric(convertedMetricName, []core.HistoricalKey{key}, start, end) 638 } 639 if err != nil { 640 response.WriteError(http.StatusInternalServerError, err) 641 return 642 } 643 644 converted := exportTimestampedMetricValue(metrics[key]) 645 response.WriteEntity(converted) 646 } 647 648 // processMetricNamesRequest retrieves the available metrics for the object at the specified key. 649 func (a *HistoricalApi) processMetricNamesRequest(key core.HistoricalKey, response *restful.Response) { 650 if resp, err := a.historicalSource.GetMetricNames(key); err != nil { 651 response.WriteError(http.StatusInternalServerError, err) 652 } else { 653 response.WriteEntity(resp) 654 } 655 } 656 657 // processAggregationRequest retrieves one or more aggregations (across time) of a metric for the object specified at the given key. 658 func (a *HistoricalApi) processAggregationRequest(key core.HistoricalKey, request *restful.Request, response *restful.Response) { 659 start, end, err := getStartEndTimeHistorical(request) 660 if err != nil { 661 response.WriteError(http.StatusBadRequest, err) 662 return 663 } 664 bucketSize, err := getBucketSize(request) 665 if err != nil { 666 response.WriteError(http.StatusBadRequest, err) 667 return 668 } 669 aggregations, err := getAggregations(request) 670 if err != nil { 671 response.WriteError(http.StatusBadRequest, err) 672 return 673 } 674 labels, err := getLabels(request) 675 if err != nil { 676 response.WriteError(http.StatusBadRequest, err) 677 return 678 } 679 680 metricName := request.PathParameter("metric-name") 681 convertedMetricName := convertMetricName(metricName) 682 var metrics map[core.HistoricalKey][]core.TimestampedAggregationValue 683 if labels != nil { 684 metrics, err = a.historicalSource.GetLabeledAggregation(convertedMetricName, labels, aggregations, []core.HistoricalKey{key}, start, end, bucketSize) 685 } else { 686 metrics, err = a.historicalSource.GetAggregation(convertedMetricName, aggregations, []core.HistoricalKey{key}, start, end, bucketSize) 687 } 688 if err != nil { 689 response.WriteError(http.StatusInternalServerError, err) 690 return 691 } 692 693 converted := exportTimestampedAggregationValue(metrics[key]) 694 response.WriteEntity(converted) 695 } 696 697 // getBucketSize parses the bucket size specifier into a 698 func getBucketSize(request *restful.Request) (time.Duration, error) { 699 rawSize := request.QueryParameter("bucket") 700 if rawSize == "" { 701 return 0, nil 702 } 703 704 if len(rawSize) < 2 { 705 return 0, fmt.Errorf("unable to parse bucket size: %q is too short to be a duration", rawSize) 706 } 707 var multiplier time.Duration 708 var num string 709 710 switch rawSize[len(rawSize)-1] { 711 case 's': 712 // could be s or ms 713 if len(rawSize) < 3 || rawSize[len(rawSize)-2] != 'm' { 714 multiplier = time.Second 715 num = rawSize[:len(rawSize)-1] 716 } else { 717 multiplier = time.Millisecond 718 num = rawSize[:len(rawSize)-2] 719 } 720 case 'h': 721 multiplier = time.Hour 722 num = rawSize[:len(rawSize)-1] 723 case 'd': 724 multiplier = 24 * time.Hour 725 num = rawSize[:len(rawSize)-1] 726 case 'm': 727 multiplier = time.Minute 728 num = rawSize[:len(rawSize)-1] 729 default: 730 return 0, fmt.Errorf("unable to parse bucket size: %q has no known duration suffix", rawSize) 731 } 732 733 parsedNum, err := strconv.ParseUint(num, 10, 64) 734 if err != nil { 735 return 0, err 736 } 737 738 return time.Duration(parsedNum) * multiplier, nil 739 } 740 741 // getAggregations extracts and validates the list of requested aggregations 742 func getAggregations(request *restful.Request) ([]core.AggregationType, error) { 743 aggregationsRaw := strings.Split(request.PathParameter("aggregations"), ",") 744 if len(aggregationsRaw) == 0 { 745 return nil, fmt.Errorf("No aggregations specified") 746 } 747 748 aggregations := make([]core.AggregationType, len(aggregationsRaw)) 749 750 for ind, aggNameRaw := range aggregationsRaw { 751 aggName := core.AggregationType(aggNameRaw) 752 if _, ok := core.AllAggregations[aggName]; !ok { 753 return nil, fmt.Errorf("Unknown aggregation %q", aggName) 754 } 755 aggregations[ind] = aggName 756 } 757 758 return aggregations, nil 759 } 760 761 // exportMetricValue converts a core.MetricValue into an API MetricValue 762 func exportMetricValue(value *core.MetricValue) *types.MetricValue { 763 if value == nil { 764 return nil 765 } 766 767 if value.ValueType == core.ValueInt64 { 768 return &types.MetricValue{ 769 IntValue: &value.IntValue, 770 } 771 } else { 772 floatVal := float64(value.FloatValue) 773 return &types.MetricValue{ 774 FloatValue: &floatVal, 775 } 776 } 777 } 778 779 // extractMetricValue checks to see if the given metric was present in the results, and if so, 780 // returns it in API form 781 func extractMetricValue(aggregations *core.AggregationValue, aggName core.AggregationType) *types.MetricValue { 782 if inputVal, ok := aggregations.Aggregations[aggName]; ok { 783 return exportMetricValue(&inputVal) 784 } else { 785 return nil 786 } 787 } 788 789 // exportTimestampedAggregationValue converts a core.TimestampedAggregationValue into an API MetricAggregationResult 790 func exportTimestampedAggregationValue(values []core.TimestampedAggregationValue) types.MetricAggregationResult { 791 result := types.MetricAggregationResult{ 792 Buckets: make([]types.MetricAggregationBucket, 0, len(values)), 793 BucketSize: 0, 794 } 795 for _, value := range values { 796 // just use the largest bucket size, since all bucket sizes should be uniform 797 // (except for the last one, which may be smaller) 798 if result.BucketSize < value.BucketSize { 799 result.BucketSize = value.BucketSize 800 } 801 802 bucket := types.MetricAggregationBucket{ 803 Timestamp: value.Timestamp, 804 805 Count: value.Count, 806 807 Average: extractMetricValue(&value.AggregationValue, core.AggregationTypeAverage), 808 Maximum: extractMetricValue(&value.AggregationValue, core.AggregationTypeMaximum), 809 Minimum: extractMetricValue(&value.AggregationValue, core.AggregationTypeMinimum), 810 Median: extractMetricValue(&value.AggregationValue, core.AggregationTypeMedian), 811 812 Percentiles: make(map[string]types.MetricValue, 3), 813 } 814 815 if val, ok := value.Aggregations[core.AggregationTypePercentile50]; ok { 816 bucket.Percentiles["50"] = *exportMetricValue(&val) 817 } 818 if val, ok := value.Aggregations[core.AggregationTypePercentile95]; ok { 819 bucket.Percentiles["95"] = *exportMetricValue(&val) 820 } 821 if val, ok := value.Aggregations[core.AggregationTypePercentile99]; ok { 822 bucket.Percentiles["99"] = *exportMetricValue(&val) 823 } 824 825 result.Buckets = append(result.Buckets, bucket) 826 } 827 return result 828 } 829 830 // getStartEndTimeHistorical fetches the start and end times of the request. Unlike 831 // getStartEndTime, this function returns an error if the start time is not passed 832 // (or is zero, since many things in go use time.Time{} as an empty value) -- 833 // different sinks have different defaults, and certain sinks have issues if an actual 834 // time of zero (i.e. the epoch) is used (because that would be too many data points 835 // to consider in certain cases). Require applications to pass an explicit start time 836 // that they can deal with. 837 func getStartEndTimeHistorical(request *restful.Request) (time.Time, time.Time, error) { 838 start, end, err := getStartEndTime(request) 839 if err != nil { 840 return start, end, err 841 } 842 843 if start.IsZero() { 844 return start, end, fmt.Errorf("no start time (or a start time of zero) provided") 845 } 846 847 return start, end, err 848 }