Rainbond/api/client/prometheus/prometheus.go

275 lines
6.6 KiB
Go

// RAINBOND, Application Management Platform
// Copyright (C) 2020-2020 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package prometheus
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/prometheus/client_golang/api"
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/model"
)
//Options prometheus options
type Options struct {
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint"`
}
// prometheus implements monitoring interface backed by Prometheus
type prometheus struct {
client apiv1.API
}
//NewPrometheus new prometheus monitor
func NewPrometheus(options *Options) (Interface, error) {
if options.Endpoint == "" {
options.Endpoint = "http://rbd-monitor:9999"
} else if !strings.HasPrefix(options.Endpoint, "http") {
options.Endpoint = fmt.Sprintf("http://%s", options.Endpoint)
}
cfg := api.Config{
Address: options.Endpoint,
RoundTripper: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
},
}
client, err := api.NewClient(cfg)
return prometheus{client: apiv1.NewAPI(client)}, err
}
func (p prometheus) GetMetric(expr string, ts time.Time) Metric {
var parsedResp Metric
value, _, err := p.client.Query(context.Background(), expr, ts)
if err != nil {
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryResp(value)
}
return parsedResp
}
func (p prometheus) GetMetricOverTime(expr string, start, end time.Time, step time.Duration) Metric {
timeRange := apiv1.Range{
Start: start,
End: end,
Step: step,
}
value, _, err := p.client.QueryRange(context.Background(), expr, timeRange)
var parsedResp Metric
if err != nil {
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryRangeResp(value)
}
return parsedResp
}
func (p prometheus) GetMetadata(namespace string) []Metadata {
var meta []Metadata
// Filter metrics available to members of this namespace
matchTarget := fmt.Sprintf("{namespace=\"%s\"}", namespace)
fmt.Println(matchTarget)
items, err := p.client.TargetsMetadata(context.Background(), matchTarget, "", "")
if err != nil {
logrus.Error(err)
return meta
}
// Deduplication
set := make(map[string]bool)
for _, item := range items {
_, ok := set[item.Metric]
if !ok {
set[item.Metric] = true
meta = append(meta, Metadata{
Metric: item.Metric,
Type: string(item.Type),
Help: item.Help,
})
}
}
return meta
}
func (p prometheus) GetAppMetadata(namespace, appID string) []Metadata {
var meta []Metadata
// Filter metrics available to members of this namespace
matchTarget := fmt.Sprintf("{namespace=\"%s\",app_id=\"%s\"}", namespace, appID)
items, err := p.client.TargetsMetadata(context.Background(), matchTarget, "", "")
if err != nil {
logrus.Error(err)
return meta
}
// Deduplication
set := make(map[string]bool)
for _, item := range items {
_, ok := set[item.Metric]
if !ok {
set[item.Metric] = true
meta = append(meta, Metadata{
Metric: item.Metric,
Type: string(item.Type),
Help: item.Help,
})
}
}
return meta
}
func (p prometheus) GetComponentMetadata(namespace, componentID string) []Metadata {
ctx, cancel := context.WithTimeout(context.Background(), 15 * time.Second)
defer cancel()
var meta []Metadata
// Filter metrics available to members of this namespace
matchTarget := fmt.Sprintf("{namespace=\"%s\",service_id=\"%s\"}", namespace, componentID)
items, err := p.client.TargetsMetadata(ctx, matchTarget, "", "")
if err != nil {
logrus.Error(err)
return meta
}
// Deduplication
set := make(map[string]bool)
for _, item := range items {
_, ok := set[item.Metric]
if !ok {
set[item.Metric] = true
meta = append(meta, Metadata{
Metric: item.Metric,
Type: string(item.Type),
Help: item.Help,
})
}
}
commonItems, err := p.client.TargetsMetadata(ctx, "{job=~\"gateway|cadvisor\"}", "", "")
if err != nil {
logrus.Error(err)
return meta
}
for _, item := range commonItems {
if !strings.HasPrefix(item.Metric, "container") && !strings.HasPrefix(item.Metric, "gateway") {
continue
}
_, ok := set[item.Metric]
if !ok {
set[item.Metric] = true
meta = append(meta, Metadata{
Metric: item.Metric,
Type: string(item.Type),
Help: item.Help,
})
}
}
return meta
}
func (p prometheus) GetMetricLabelSet(expr string, start, end time.Time) []map[string]string {
var res []map[string]string
labelSet, _, err := p.client.Series(context.Background(), []string{expr}, start, end)
if err != nil {
logrus.Error(err)
return []map[string]string{}
}
for _, item := range labelSet {
var tmp = map[string]string{}
for key, val := range item {
if key == "__name__" {
continue
}
tmp[string(key)] = string(val)
}
res = append(res, tmp)
}
return res
}
func parseQueryRangeResp(value model.Value) MetricData {
res := MetricData{MetricType: MetricTypeMatrix}
data, _ := value.(model.Matrix)
for _, v := range data {
mv := MetricValue{
Metadata: make(map[string]string),
}
for k, v := range v.Metric {
mv.Metadata[string(k)] = string(v)
}
for _, k := range v.Values {
mv.Series = append(mv.Series, Point{float64(k.Timestamp) / 1000, float64(k.Value)})
}
res.MetricValues = append(res.MetricValues, mv)
}
return res
}
func parseQueryResp(value model.Value) MetricData {
res := MetricData{MetricType: MetricTypeVector}
data, _ := value.(model.Vector)
for _, v := range data {
mv := MetricValue{
Metadata: make(map[string]string),
}
for k, v := range v.Metric {
mv.Metadata[string(k)] = string(v)
}
mv.Sample = &Point{float64(v.Timestamp) / 1000, float64(v.Value)}
res.MetricValues = append(res.MetricValues, mv)
}
return res
}