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"
)
type Options struct {
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint"`
}
type prometheus struct {
client apiv1.API
}
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
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
}
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
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
}
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
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
}
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
}