app/vminsert: split vm_rows_inserted_total into per-(accountID, projectID) metrics

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/59
This commit is contained in:
Aliaksandr Valialkin 2019-06-07 21:16:05 +03:00
parent 8b2a6c6182
commit 8cf0a0e59c
6 changed files with 172 additions and 8 deletions

View file

@ -13,11 +13,12 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fastjson/fastfloat"
)
var rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="graphite"}`)
var rowsInserted = tenantmetrics.NewCounterMap(`vm_rows_inserted_total{type="graphite"}`)
// insertHandler processes remote write for graphite plaintext protocol.
//
@ -66,7 +67,8 @@ func (ctx *pushCtx) InsertRows(at *auth.Token) error {
return err
}
}
rowsInserted.Add(len(rows))
// Assume that all the rows for a single connection belong to the same (AccountID, ProjectID).
rowsInserted.Get(&atCopy).Add(len(rows))
return ic.FlushBufs()
}

View file

@ -15,10 +15,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
"github.com/VictoriaMetrics/metrics"
)
var rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="influx"}`)
var rowsInserted = tenantmetrics.NewCounterMap(`vm_rows_inserted_total{type="influx"}`)
// InsertHandler processes remote write for influx line protocol.
//
@ -76,6 +77,7 @@ func (ctx *pushCtx) InsertRows(at *auth.Token, db string) error {
rows := ctx.Rows.Rows
ic := &ctx.Common
ic.Reset()
rowsAdded := 0
for i := range rows {
r := &rows[i]
ic.Labels = ic.Labels[:0]
@ -103,8 +105,9 @@ func (ctx *pushCtx) InsertRows(at *auth.Token, db string) error {
return err
}
}
rowsInserted.Add(len(r.Fields))
rowsAdded += len(r.Fields)
}
rowsInserted.Get(at).Add(rowsAdded)
return ic.FlushBufs()
}

View file

@ -13,11 +13,12 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fastjson/fastfloat"
)
var rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="opentsdb"}`)
var rowsInserted = tenantmetrics.NewCounterMap(`vm_rows_inserted_total{type="opentsdb"}`)
// insertHandler processes remote write for OpenTSDB put protocol.
//
@ -66,7 +67,8 @@ func (ctx *pushCtx) InsertRows(at *auth.Token) error {
return err
}
}
rowsInserted.Add(len(rows))
// Assume that all the rows for a single connection belong to the same (AccountID, ProjectID).
rowsInserted.Get(&atCopy).Add(len(rows))
return ic.FlushBufs()
}

View file

@ -11,10 +11,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
"github.com/VictoriaMetrics/metrics"
)
var rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="prometheus"}`)
var rowsInserted = tenantmetrics.NewCounterMap(`vm_rows_inserted_total{type="prometheus"}`)
// InsertHandler processes remote write for prometheus.
func InsertHandler(at *auth.Token, r *http.Request, maxSize int64) error {
@ -46,7 +47,7 @@ func insertHandlerInternal(at *auth.Token, r *http.Request, maxSize int64) error
return err
}
}
rowsInserted.Add(len(ts.Samples))
rowsInserted.Get(at).Add(len(ts.Samples))
}
return ic.FlushBufs()
}

View file

@ -0,0 +1,69 @@
package tenantmetrics
import (
"fmt"
"sync/atomic"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/metrics"
)
type apKey struct {
accountID uint32
projectID uint32
}
// CounterMap is a map of counters keyed by tenant.
type CounterMap struct {
metric string
m atomic.Value
}
// NewCounterMap creates new CounterMap for the given metricTemplate.
//
func NewCounterMap(metric string) *CounterMap {
cm := &CounterMap{
metric: metric,
}
cm.m.Store(make(map[apKey]*metrics.Counter))
return cm
}
// Get returns counter for the given at.
//
// It always returns non-nil counter.
func (cm *CounterMap) Get(at *auth.Token) *metrics.Counter {
key := apKey{
accountID: at.AccountID,
projectID: at.ProjectID,
}
m := cm.m.Load().(map[apKey]*metrics.Counter)
if c := m[key]; c != nil {
// Fast path - the counter for k already exists.
return c
}
// Slow path - create missing counter for k and re-create m.
newM := make(map[apKey]*metrics.Counter, len(m)+1)
for k, c := range m {
newM[k] = c
}
metricName := createMetricName(cm.metric, at)
c := metrics.GetOrCreateCounter(metricName)
newM[key] = c
cm.m.Store(newM)
return c
}
func createMetricName(metric string, at *auth.Token) string {
if len(metric) == 0 {
logger.Panicf("BUG: metric cannot be empty")
}
if metric[len(metric)-1] != '}' {
// Metric without labels.
return fmt.Sprintf(`%s{accountID="%d",projectID="%d"}`, metric, at.AccountID, at.ProjectID)
}
// Metric with labels.
return fmt.Sprintf(`%s,accountID="%d",projectID="%d"}`, metric[:len(metric)-1], at.AccountID, at.ProjectID)
}

View file

@ -0,0 +1,87 @@
package tenantmetrics
import (
"fmt"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
)
func TestCreateMetricNameError(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expecting non-nil panic")
}
}()
_ = createMetricName("", &auth.Token{})
}
func TestCreateMetricNameSuccess(t *testing.T) {
f := func(s string, at *auth.Token, metricExpected string) {
t.Helper()
metric := createMetricName(s, at)
if metric != metricExpected {
t.Fatalf("unexpected result for createMetricName(%q, %v); got %q; want %q", s, at, metric, metricExpected)
}
}
f(`a`, &auth.Token{AccountID: 1, ProjectID: 2}, `a{accountID="1",projectID="2"}`)
f(`foo{bar="baz"}`, &auth.Token{AccountID: 33, ProjectID: 41}, `foo{bar="baz",accountID="33",projectID="41"}`)
f(`foo{bar="baz",a="aa"}`, &auth.Token{AccountID: 33, ProjectID: 41}, `foo{bar="baz",a="aa",accountID="33",projectID="41"}`)
}
func TestCounterMap(t *testing.T) {
cm := NewCounterMap("foobar")
cm.Get(&auth.Token{AccountID: 1, ProjectID: 2}).Inc()
cm.Get(&auth.Token{AccountID: 4, ProjectID: 0}).Add(12)
if n := cm.Get(&auth.Token{AccountID: 1, ProjectID: 2}).Get(); n != 1 {
t.Fatalf("unexpected counter value; got %d; want %d", n, 1)
}
if n := cm.Get(&auth.Token{AccountID: 4, ProjectID: 0}).Get(); n != 12 {
t.Fatalf("unexpected counter value; got %d; want %d", n, 12)
}
if n := cm.Get(&auth.Token{}).Get(); n != 0 {
t.Fatalf("unexpected counter value; got %d; want %d", n, 0)
}
}
func TestCounterMapConcurrent(t *testing.T) {
cm := NewCounterMap(`aaa{bb="cc"}`)
f := func() error {
for i := 0; i < 10; i++ {
cm.Get(&auth.Token{AccountID: 1, ProjectID: 2}).Inc()
if n := cm.Get(&auth.Token{AccountID: 3, ProjectID: 4}).Get(); n != 0 {
return fmt.Errorf("unexpected counter value; got %d; want %d", n, 0)
}
cm.Get(&auth.Token{AccountID: 1, ProjectID: 3}).Add(5)
}
return nil
}
const concurrency = 5
ch := make(chan error, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
ch <- f()
}()
}
for i := 0; i < concurrency; i++ {
select {
case err := <-ch:
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
if n := cm.Get(&auth.Token{AccountID: 1, ProjectID: 2}).Get(); n != concurrency*10 {
t.Fatalf("unexpected counter value; got %d; want %d", n, concurrency*10)
}
if n := cm.Get(&auth.Token{AccountID: 1, ProjectID: 3}).Get(); n != concurrency*10*5 {
t.Fatalf("unexpected counter value; got %d; want %d", n, concurrency*10*5)
}
}