influxdb/gather/scraper_test.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)
}),
}