310 lines
7.4 KiB
Go
310 lines
7.4 KiB
Go
package gather
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/influxdata/influxdb"
|
|
)
|
|
|
|
var (
|
|
orgID, _ = influxdb.IDFromString("020f755c3c082000")
|
|
bucketID, _ = influxdb.IDFromString("020f755c3c082001")
|
|
)
|
|
|
|
func TestPrometheusScraper(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
ms []Metrics
|
|
handler *mockHTTPHandler
|
|
hasErr bool
|
|
}{
|
|
{
|
|
name: "bad request",
|
|
hasErr: true,
|
|
},
|
|
{
|
|
name: "empty request",
|
|
handler: &mockHTTPHandler{
|
|
responseMap: map[string]string{
|
|
"/metrics": "",
|
|
},
|
|
},
|
|
hasErr: true,
|
|
},
|
|
{
|
|
name: "regular metrics",
|
|
handler: &mockHTTPHandler{
|
|
responseMap: map[string]string{
|
|
"/metrics": sampleResp,
|
|
},
|
|
},
|
|
ms: []Metrics{
|
|
{
|
|
Name: "go_gc_duration_seconds",
|
|
Type: MetricTypeSummary,
|
|
Fields: map[string]interface{}{
|
|
"count": float64(326),
|
|
"sum": 0.07497837,
|
|
"0": 3.6257e-05,
|
|
"0.25": 0.0001434,
|
|
"0.5": 0.000194491,
|
|
"0.75": 0.000270339,
|
|
"1": 0.000789365,
|
|
},
|
|
Tags: map[string]string{},
|
|
},
|
|
{
|
|
Name: "go_goroutines",
|
|
Type: MetricTypeGauge,
|
|
Tags: map[string]string{},
|
|
Fields: map[string]interface{}{
|
|
"gauge": float64(36),
|
|
},
|
|
},
|
|
{
|
|
Name: "go_info",
|
|
Type: MetricTypeGauge,
|
|
Tags: map[string]string{
|
|
"version": "go1.10.3",
|
|
},
|
|
Fields: map[string]interface{}{
|
|
"gauge": float64(1),
|
|
},
|
|
},
|
|
{
|
|
Name: "go_memstats_alloc_bytes",
|
|
Type: MetricTypeGauge,
|
|
Tags: map[string]string{},
|
|
Fields: map[string]interface{}{
|
|
"gauge": 2.0091312e+07,
|
|
},
|
|
},
|
|
{
|
|
Name: "go_memstats_alloc_bytes_total",
|
|
Type: MetricTypeCounter,
|
|
Fields: map[string]interface{}{
|
|
"counter": 4.183173328e+09,
|
|
},
|
|
Tags: map[string]string{},
|
|
},
|
|
{
|
|
Name: "go_memstats_buck_hash_sys_bytes",
|
|
Type: MetricTypeGauge,
|
|
Tags: map[string]string{},
|
|
Fields: map[string]interface{}{
|
|
"gauge": 1.533852e+06,
|
|
},
|
|
},
|
|
{
|
|
Name: "go_memstats_frees_total",
|
|
Type: MetricTypeCounter,
|
|
Tags: map[string]string{},
|
|
Fields: map[string]interface{}{
|
|
"counter": 1.8944339e+07,
|
|
},
|
|
},
|
|
{
|
|
Name: "go_memstats_gc_cpu_fraction",
|
|
Type: MetricTypeGauge,
|
|
Tags: map[string]string{},
|
|
Fields: map[string]interface{}{
|
|
"gauge": 1.972734963012756e-05,
|
|
},
|
|
},
|
|
},
|
|
hasErr: false,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
scraper := new(prometheusScraper)
|
|
var url string
|
|
if c.handler != nil {
|
|
ts := httptest.NewServer(c.handler)
|
|
defer ts.Close()
|
|
url = ts.URL
|
|
}
|
|
results, err := scraper.Gather(context.Background(), influxdb.ScraperTarget{
|
|
URL: url + "/metrics",
|
|
OrgID: *orgID,
|
|
BucketID: *bucketID,
|
|
})
|
|
if err != nil && !c.hasErr {
|
|
t.Fatalf("scraper parse err in testing %s: %v", c.name, err)
|
|
}
|
|
if len(c.ms) != len(results.MetricsSlice) {
|
|
t.Fatalf("scraper parse metrics incorrect length, want %d, got %d",
|
|
len(c.ms), len(results.MetricsSlice))
|
|
}
|
|
for _, m := range results.MetricsSlice {
|
|
for _, cm := range c.ms {
|
|
if m.Name == cm.Name {
|
|
if diff := cmp.Diff(m, cm, metricsCmpOption); diff != "" {
|
|
t.Fatalf("scraper parse metrics want %v, got %v", cm, m)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
const sampleResp = `
|
|
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
|
|
# TYPE go_gc_duration_seconds summary
|
|
go_gc_duration_seconds{quantile="0"} 3.6257e-05
|
|
go_gc_duration_seconds{quantile="0.25"} 0.0001434
|
|
go_gc_duration_seconds{quantile="0.5"} 0.000194491
|
|
go_gc_duration_seconds{quantile="0.75"} 0.000270339
|
|
go_gc_duration_seconds{quantile="1"} 0.000789365
|
|
go_gc_duration_seconds_sum 0.07497837
|
|
go_gc_duration_seconds_count 326
|
|
# HELP go_goroutines Number of goroutines that currently exist.
|
|
# TYPE go_goroutines gauge
|
|
go_goroutines 36
|
|
# HELP go_info Information about the Go environment.
|
|
# TYPE go_info gauge
|
|
go_info{version="go1.10.3"} 1
|
|
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
|
|
# TYPE go_memstats_alloc_bytes gauge
|
|
go_memstats_alloc_bytes 2.0091312e+07
|
|
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
|
|
# TYPE go_memstats_alloc_bytes_total counter
|
|
go_memstats_alloc_bytes_total 4.183173328e+09
|
|
# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
|
|
# TYPE go_memstats_buck_hash_sys_bytes gauge
|
|
go_memstats_buck_hash_sys_bytes 1.533852e+06
|
|
# HELP go_memstats_frees_total Total number of frees.
|
|
# TYPE go_memstats_frees_total counter
|
|
go_memstats_frees_total 1.8944339e+07
|
|
# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started.
|
|
# TYPE go_memstats_gc_cpu_fraction gauge
|
|
go_memstats_gc_cpu_fraction 1.972734963012756e-05
|
|
`
|
|
|
|
// mockStorage implement recorder interface
|
|
// and influxdb.ScraperTargetStoreService interface.
|
|
type mockStorage struct {
|
|
sync.RWMutex
|
|
influxdb.UserResourceMappingService
|
|
influxdb.OrganizationService
|
|
TotalGatherJobs chan struct{}
|
|
Metrics map[time.Time]Metrics
|
|
Targets []influxdb.ScraperTarget
|
|
}
|
|
|
|
func (s *mockStorage) Record(collected MetricsCollection) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
for _, m := range collected.MetricsSlice {
|
|
s.Metrics[m.Timestamp] = m
|
|
}
|
|
s.TotalGatherJobs <- struct{}{}
|
|
return nil
|
|
}
|
|
|
|
func (s *mockStorage) ListTargets(ctx context.Context, filter influxdb.ScraperTargetFilter) (targets []influxdb.ScraperTarget, err error) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
if s.Targets == nil {
|
|
s.Lock()
|
|
s.Targets = make([]influxdb.ScraperTarget, 0)
|
|
s.Unlock()
|
|
}
|
|
return s.Targets, nil
|
|
}
|
|
|
|
func (s *mockStorage) AddTarget(ctx context.Context, t *influxdb.ScraperTarget, userID influxdb.ID) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
if s.Targets == nil {
|
|
s.Targets = make([]influxdb.ScraperTarget, 0)
|
|
}
|
|
s.Targets = append(s.Targets, *t)
|
|
return nil
|
|
}
|
|
|
|
func (s *mockStorage) RemoveTarget(ctx context.Context, id influxdb.ID) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
if s.Targets == nil {
|
|
return nil
|
|
}
|
|
for k, v := range s.Targets {
|
|
if v.ID == id {
|
|
s.Targets = append(s.Targets[:k], s.Targets[k+1:]...)
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *mockStorage) GetTargetByID(ctx context.Context, id influxdb.ID) (target *influxdb.ScraperTarget, err error) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
|
|
for k, v := range s.Targets {
|
|
if v.ID == id {
|
|
target = &s.Targets[k]
|
|
break
|
|
}
|
|
}
|
|
|
|
return target, err
|
|
|
|
}
|
|
|
|
func (s *mockStorage) UpdateTarget(ctx context.Context, update *influxdb.ScraperTarget, userID influxdb.ID) (target *influxdb.ScraperTarget, err error) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
for k, v := range s.Targets {
|
|
if v.ID.String() == string(update.ID) {
|
|
s.Targets[k] = *update
|
|
break
|
|
}
|
|
}
|
|
|
|
return update, err
|
|
}
|
|
|
|
type mockHTTPHandler struct {
|
|
unauthorized bool
|
|
noContent bool
|
|
responseMap map[string]string
|
|
}
|
|
|
|
func (h mockHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if h.unauthorized {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
if h.noContent {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
s, ok := h.responseMap[r.URL.Path]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
|
w.Write([]byte(s))
|
|
}
|
|
|
|
var metricsCmpOption = cmp.Options{
|
|
cmp.Comparer(func(x, y Metrics) bool {
|
|
return x.Name == y.Name &&
|
|
x.Type == y.Type &&
|
|
reflect.DeepEqual(x.Tags, y.Tags) &&
|
|
reflect.DeepEqual(x.Fields, y.Fields)
|
|
}),
|
|
}
|