mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-05 08:10:50 +00:00
This should help prevent a data race. SetBit can, in some cases, cause an asynchronous task to run which tries to update the stats counter. But if that task runs while we are modifying the stats counter itself, we have a data race.
254 lines
7.6 KiB
Go
254 lines
7.6 KiB
Go
// Copyright 2021 Molecula Corp. All rights reserved.
|
|
package stats_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
pilosa "github.com/molecula/featurebase/v3"
|
|
"github.com/molecula/featurebase/v3/logger"
|
|
"github.com/molecula/featurebase/v3/stats"
|
|
"github.com/molecula/featurebase/v3/test"
|
|
)
|
|
|
|
// TestMultiStatClient_Expvar run the multistat client with exp var
|
|
// since the EXPVAR data is stored in a global we should run these in one test function
|
|
func TestMultiStatClient_Expvar(t *testing.T) {
|
|
hldr := test.MustOpenHolder(t)
|
|
defer hldr.Close()
|
|
|
|
c := stats.NewExpvarStatsClient()
|
|
ms := make(stats.MultiStatsClient, 1)
|
|
ms[0] = c
|
|
hldr.Stats = ms
|
|
|
|
hldr.SetBit("d", "f", 0, 0)
|
|
hldr.SetBit("d", "f", 0, 1)
|
|
hldr.SetBit("d", "f", 0, pilosa.ShardWidth)
|
|
hldr.SetBit("d", "f", 0, pilosa.ShardWidth+2)
|
|
hldr.ClearBit("d", "f", 0, 1)
|
|
|
|
indexStats := fmt.Sprintf(`{"%s": %d, "%s": %d}`, pilosa.MetricClearBit, 1, pilosa.MetricSetBit, 4)
|
|
|
|
if stats.Expvar.String() != `{"index:d": `+indexStats+`}` {
|
|
t.Fatalf("unexpected expvar : %s", stats.Expvar.String())
|
|
}
|
|
|
|
hldr.Stats.CountWithCustomTags("cc", 1, 1.0, []string{"foo:bar"})
|
|
if stats.Expvar.String() != `{"cc": 1, "index:d": `+indexStats+`}` {
|
|
t.Fatalf("unexpected expvar : %s", stats.Expvar.String())
|
|
}
|
|
|
|
// Gauge creates a unique key, subsequent Gauge calls will overwrite
|
|
hldr.Stats.Gauge("g", 5, 1.0)
|
|
hldr.Stats.Gauge("g", 8, 1.0)
|
|
if stats.Expvar.String() != `{"cc": 1, "g": 8, "index:d": `+indexStats+`}` {
|
|
t.Fatalf("unexpected expvar : %s", stats.Expvar.String())
|
|
}
|
|
|
|
// Set creates a unique key, subsequent sets will overwrite
|
|
hldr.Stats.Set("s", "4", 1.0)
|
|
hldr.Stats.Set("s", "7", 1.0)
|
|
if stats.Expvar.String() != `{"cc": 1, "g": 8, "index:d": `+indexStats+`, "s": "7"}` {
|
|
t.Fatalf("unexpected expvar : %s", stats.Expvar.String())
|
|
}
|
|
|
|
// Record timing duration and a uniquely Set key/value
|
|
dur, _ := time.ParseDuration("123us")
|
|
hldr.Stats.Timing("tt", dur, 1.0)
|
|
if stats.Expvar.String() != `{"cc": 1, "g": 8, "index:d": `+indexStats+`, "s": "7", "tt": 123µs}` {
|
|
t.Fatalf("unexpected expvar : %s", stats.Expvar.String())
|
|
}
|
|
|
|
// Expvar histogram is implemented as a gauge
|
|
hldr.Stats.Histogram("hh", 3, 1.0)
|
|
if stats.Expvar.String() != `{"cc": 1, "g": 8, "hh": 3, "index:d": `+indexStats+`, "s": "7", "tt": 123µs}` {
|
|
t.Fatalf("unexpected expvar : %s", stats.Expvar.String())
|
|
}
|
|
|
|
// Expvar should ignore earlier set tags from setbit
|
|
if hldr.Stats.Tags() != nil {
|
|
t.Fatalf("unexpected tag")
|
|
}
|
|
}
|
|
|
|
func TestStatsCount_TopN(t *testing.T) {
|
|
c := test.MustRunCluster(t, 1)
|
|
defer c.Close()
|
|
hldr := test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
|
|
// Execute query.
|
|
called := false
|
|
hldr.Holder.Stats = &MockStats{
|
|
mockCountWithTags: func(name string, value int64, rate float64, tags []string) {
|
|
if name != "query_topn_total" {
|
|
t.Errorf("Expected query_topn_total, Results %s", name)
|
|
}
|
|
|
|
if tags[0] != "index:d" {
|
|
t.Errorf("Expected index, Results %s", tags[0])
|
|
}
|
|
|
|
called = true
|
|
},
|
|
}
|
|
|
|
hldr.SetBit("d", "f", 0, 0)
|
|
hldr.SetBit("d", "f", 0, 1)
|
|
hldr.SetBit("d", "f", 0, pilosa.ShardWidth)
|
|
hldr.SetBit("d", "f", 0, pilosa.ShardWidth+2)
|
|
|
|
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "d", Query: `TopN(field=f, n=2)`}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !called {
|
|
t.Error("CountWithCustomTags name isn't called")
|
|
}
|
|
}
|
|
|
|
func TestStatsCount_Bitmap(t *testing.T) {
|
|
c := test.MustRunCluster(t, 1)
|
|
defer c.Close()
|
|
hldr := test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
|
|
hldr.SetBit("d", "f", 0, 0)
|
|
hldr.SetBit("d", "f", 0, 1)
|
|
called := false
|
|
hldr.Holder.Stats = &MockStats{
|
|
mockCountWithTags: func(name string, value int64, rate float64, tags []string) {
|
|
if name != pilosa.MetricRow {
|
|
t.Errorf("Expected %s, Results %s", pilosa.MetricRow, name)
|
|
}
|
|
|
|
if tags[0] != "index:d" {
|
|
t.Errorf("Expected index, Results %s", tags[0])
|
|
}
|
|
|
|
called = true
|
|
},
|
|
}
|
|
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "d", Query: `Row(f=0)`}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !called {
|
|
t.Error("CountWithCustomTags name isn't called")
|
|
}
|
|
}
|
|
|
|
func TestStatsCount_APICalls(t *testing.T) {
|
|
cluster := test.MustRunCluster(t, 1)
|
|
defer cluster.Close()
|
|
cmd := cluster.GetNode(0)
|
|
h := cmd.Handler.(*pilosa.Handler).Handler
|
|
holder := cmd.Server.Holder()
|
|
hldr := test.Holder{Holder: holder}
|
|
|
|
t.Run("create index", func(t *testing.T) {
|
|
called := false
|
|
hldr.Stats = &MockStats{
|
|
mockCount: func(name string, value int64, rate float64) {
|
|
if name != pilosa.MetricCreateIndex {
|
|
t.Errorf("Expected %v, Results %s", pilosa.MetricCreateIndex, name)
|
|
}
|
|
called = true
|
|
},
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i", strings.NewReader("")))
|
|
if !called {
|
|
t.Error("Count isn't called")
|
|
}
|
|
})
|
|
|
|
t.Run("create field", func(t *testing.T) {
|
|
called := false
|
|
hldr.Stats = &MockStats{
|
|
mockCountWithTags: func(name string, value int64, rate float64, index []string) {
|
|
if name != pilosa.MetricCreateField {
|
|
t.Errorf("Expected %v, Results %s", pilosa.MetricCreateField, name)
|
|
}
|
|
if index[0] != "index:i" {
|
|
t.Errorf("Expected index:i, Results %s", index)
|
|
}
|
|
|
|
called = true
|
|
},
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/field/f", strings.NewReader("")))
|
|
if !called {
|
|
t.Error("Count isn't called")
|
|
}
|
|
})
|
|
|
|
t.Run("delete field", func(t *testing.T) {
|
|
called := false
|
|
hldr.Stats = &MockStats{
|
|
mockCountWithTags: func(name string, value int64, rate float64, index []string) {
|
|
if name != pilosa.MetricDeleteField {
|
|
t.Errorf("Expected %v, Results %s", pilosa.MetricDeleteField, name)
|
|
}
|
|
if index[0] != "index:i" {
|
|
t.Errorf("Expected index:i, Results %s", index)
|
|
}
|
|
|
|
called = true
|
|
},
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i/field/f", strings.NewReader("")))
|
|
if !called {
|
|
t.Error("Count isn't called")
|
|
}
|
|
})
|
|
|
|
t.Run("delete index", func(t *testing.T) {
|
|
called := false
|
|
hldr.Stats = &MockStats{
|
|
mockCount: func(name string, value int64, rate float64) {
|
|
if name != pilosa.MetricDeleteIndex {
|
|
t.Errorf("Expected %v, Results %s", pilosa.MetricDeleteIndex, name)
|
|
}
|
|
|
|
called = true
|
|
},
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i", strings.NewReader("")))
|
|
if !called {
|
|
t.Error("Count isn't called")
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
type MockStats struct {
|
|
mockCount func(name string, value int64, rate float64)
|
|
mockCountWithTags func(name string, value int64, rate float64, tags []string)
|
|
}
|
|
|
|
func (s *MockStats) Count(name string, value int64, rate float64) {
|
|
if s.mockCount != nil {
|
|
s.mockCount(name, value, rate)
|
|
}
|
|
}
|
|
|
|
func (s *MockStats) CountWithCustomTags(name string, value int64, rate float64, tags []string) {
|
|
if s.mockCountWithTags != nil {
|
|
s.mockCountWithTags(name, value, rate, tags)
|
|
}
|
|
}
|
|
|
|
func (c *MockStats) Tags() []string { return nil }
|
|
func (c *MockStats) WithTags(tags ...string) stats.StatsClient { return c }
|
|
func (c *MockStats) Gauge(name string, value float64, rate float64) {}
|
|
func (c *MockStats) Histogram(name string, value float64, rate float64) {}
|
|
func (c *MockStats) Set(name string, value string, rate float64) {}
|
|
func (c *MockStats) Timing(name string, value time.Duration, rate float64) {}
|
|
func (c *MockStats) SetLogger(logger logger.Logger) {}
|
|
func (c *MockStats) Open() {}
|
|
func (c *MockStats) Close() error { return nil }
|