Merge pull request #2007 from huanggze/dev-custom

feat: custom monitoring
This commit is contained in:
KubeSphere CI Bot
2020-04-20 20:04:22 +08:00
committed by GitHub
181 changed files with 37758 additions and 357 deletions

View File

@@ -3,8 +3,9 @@ package monitoring
import "time"
type Interface interface {
GetMetrics(exprs []string, time time.Time) []Metric
GetMetricsOverTime(exprs []string, start, end time.Time, step time.Duration) []Metric
GetMetric(expr string, time time.Time) Metric
GetMetricOverTime(expr string, start, end time.Time, step time.Duration) Metric
GetNamedMetrics(metrics []string, time time.Time, opt QueryOption) []Metric
GetNamedMetricsOverTime(metrics []string, start, end time.Time, step time.Duration, opt QueryOption) []Metric
GetMetadata(namespace string) []Metadata
}

View File

@@ -2,6 +2,7 @@ package prometheus
import (
"context"
"fmt"
"github.com/prometheus/client_golang/api"
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/model"
@@ -24,14 +25,35 @@ func NewPrometheus(options *Options) (monitoring.Interface, error) {
return prometheus{client: apiv1.NewAPI(client)}, err
}
// TODO(huanggze): reserve for custom monitoring
func (p prometheus) GetMetrics(stmts []string, time time.Time) []monitoring.Metric {
panic("implement me")
func (p prometheus) GetMetric(expr string, ts time.Time) monitoring.Metric {
var parsedResp monitoring.Metric
value, err := p.client.Query(context.Background(), expr, ts)
if err != nil {
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryResp(value)
}
return parsedResp
}
// TODO(huanggze): reserve for custom monitoring
func (p prometheus) GetMetricsOverTime(stmts []string, start, end time.Time, step time.Duration) []monitoring.Metric {
panic("implement me")
func (p prometheus) GetMetricOverTime(expr string, start, end time.Time, step time.Duration) monitoring.Metric {
timeRange := apiv1.Range{
Start: start,
End: end,
Step: step,
}
value, err := p.client.QueryRange(context.Background(), expr, timeRange)
var parsedResp monitoring.Metric
if err != nil {
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryRangeResp(value)
}
return parsedResp
}
func (p prometheus) GetNamedMetrics(metrics []string, ts time.Time, o monitoring.QueryOption) []monitoring.Metric {
@@ -49,7 +71,7 @@ func (p prometheus) GetNamedMetrics(metrics []string, ts time.Time, o monitoring
value, err := p.client.Query(context.Background(), makeExpr(metric, *opts), ts)
if err != nil {
parsedResp.Error = err.(*apiv1.Error).Msg
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryResp(value)
}
@@ -88,7 +110,7 @@ func (p prometheus) GetNamedMetricsOverTime(metrics []string, start, end time.Ti
value, err := p.client.QueryRange(context.Background(), makeExpr(metric, *opts), timeRange)
if err != nil {
parsedResp.Error = err.(*apiv1.Error).Msg
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryRangeResp(value)
}
@@ -106,6 +128,26 @@ func (p prometheus) GetNamedMetricsOverTime(metrics []string, start, end time.Ti
return res
}
func (p prometheus) GetMetadata(namespace string) []monitoring.Metadata {
var meta []monitoring.Metadata
// Filter metrics available to members of this namespace
matchTarget := fmt.Sprintf("{namespace=\"%s\"}", namespace)
items, err := p.client.TargetsMetadata(context.Background(), matchTarget, "", "")
if err != nil {
return meta
}
for _, item := range items {
meta = append(meta, monitoring.Metadata{
Metric: item.Metric,
Type: string(item.Type),
Help: item.Help,
})
}
return meta
}
func parseQueryRangeResp(value model.Value) monitoring.MetricData {
res := monitoring.MetricData{MetricType: monitoring.MetricTypeMatrix}

View File

@@ -24,7 +24,8 @@ func TestGetNamedMetrics(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected, err := jsonFromFile(tt.expected)
expected := make([]monitoring.Metric, 0)
err := jsonFromFile(tt.expected, &expected)
if err != nil {
t.Fatal(err)
}
@@ -53,7 +54,8 @@ func TestGetNamedMetricsOverTime(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected, err := jsonFromFile(tt.expected)
expected := make([]monitoring.Metric, 0)
err := jsonFromFile(tt.expected, &expected)
if err != nil {
t.Fatal(err)
}
@@ -70,6 +72,44 @@ func TestGetNamedMetricsOverTime(t *testing.T) {
}
}
func TestGetMetadata(t *testing.T) {
tests := []struct {
fakeResp string
expected string
}{
{
fakeResp: "metadata-prom.json",
expected: "metadata-res.json",
},
{
fakeResp: "metadata-notfound-prom.json",
expected: "metadata-notfound-res.json",
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
expected := make([]monitoring.Metadata, 0)
err := jsonFromFile(tt.expected, &expected)
if err != nil {
t.Fatal(err)
}
if len(expected) == 0 {
expected = nil
}
srv := mockPrometheusService("/api/v1/targets/metadata", tt.fakeResp)
defer srv.Close()
client, _ := NewPrometheus(&Options{Endpoint: srv.URL})
result := client.GetMetadata("default")
if diff := cmp.Diff(result, expected); diff != "" {
t.Fatalf("%T differ (-got, +want): %s", expected, diff)
}
})
}
}
func mockPrometheusService(pattern, fakeResp string) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc(pattern, func(res http.ResponseWriter, req *http.Request) {
@@ -79,17 +119,15 @@ func mockPrometheusService(pattern, fakeResp string) *httptest.Server {
return httptest.NewServer(mux)
}
func jsonFromFile(expectedFile string) ([]monitoring.Metric, error) {
expectedJson := []monitoring.Metric{}
func jsonFromFile(expectedFile string, expectedJsonPtr interface{}) error {
json, err := ioutil.ReadFile(fmt.Sprintf("./testdata/%s", expectedFile))
if err != nil {
return expectedJson, err
return err
}
err = jsoniter.Unmarshal(json, &expectedJson)
err = jsoniter.Unmarshal(json, expectedJsonPtr)
if err != nil {
return expectedJson, err
return err
}
return expectedJson, nil
return nil
}

View File

@@ -0,0 +1,5 @@
{
"status":"error",
"errorType":"not_found",
"error":"specified metadata not found"
}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,25 @@
{
"status": "success",
"data": [
{
"target": {
"instance": "127.0.0.1:9090",
"job": "prometheus"
},
"metric": "prometheus_treecache_zookeeper_failures_total",
"type": "counter",
"help": "The total number of ZooKeeper failures.",
"unit": ""
},
{
"target": {
"instance": "127.0.0.1:9090",
"job": "prometheus"
},
"metric": "prometheus_tsdb_reloads_total",
"type": "counter",
"help": "Number of times the database reloaded block data from disk.",
"unit": ""
}
]
}

View File

@@ -0,0 +1,12 @@
[
{
"metric": "prometheus_treecache_zookeeper_failures_total",
"type": "counter",
"help": "The total number of ZooKeeper failures."
},
{
"metric": "prometheus_tsdb_reloads_total",
"type": "counter",
"help": "Number of times the database reloaded block data from disk."
}
]

View File

@@ -1,6 +1,6 @@
[
{
"metric_name": "cluster_cpu_utilisation",
"error": "inconsistent body for response code"
"error": "bad_response: inconsistent body for response code"
}
]

View File

@@ -5,6 +5,12 @@ const (
MetricTypeVector = "vector"
)
type Metadata struct {
Metric string `json:"metric,omitempty" description:"metric name"`
Type string `json:"type,omitempty" description:"metric type"`
Help string `json:"help,omitempty" description:"metric description"`
}
type Metric struct {
MetricName string `json:"metric_name,omitempty" description:"metric name, eg. scheduler_up_sum"`
MetricData `json:"data,omitempty" description:"actual metric result"`