mirror of https://github.com/knative/pkg.git
Add RecordN method to permit recording of N measurements simultaneously. (#1025)
* Add RecordN method to permit recording of N measurements simultaneously. In serving we have many places where we try to record several metrics at the same time. Besides very repetetive code, this also brings inefficiences, since metrics code has to do _some_ work for every call, which is completely avoidable. This way we can record N measurements with one hit. * tests and nits * review items per discussion with Evan: - remove ros - make measurements ... * review
This commit is contained in:
parent
d0787646f1
commit
92c9752a4c
|
@ -84,7 +84,7 @@ type metricsConfig struct {
|
||||||
|
|
||||||
// recorder provides a hook for performing custom transformations before
|
// recorder provides a hook for performing custom transformations before
|
||||||
// writing the metrics to the stats.RecordWithOptions interface.
|
// writing the metrics to the stats.RecordWithOptions interface.
|
||||||
recorder func(context.Context, stats.Measurement, ...stats.Options) error
|
recorder func(context.Context, []stats.Measurement, ...stats.Options) error
|
||||||
|
|
||||||
// ---- OpenCensus specific below ----
|
// ---- OpenCensus specific below ----
|
||||||
// collectorAddress is the address of the collector, if not `localhost:55678`
|
// collectorAddress is the address of the collector, if not `localhost:55678`
|
||||||
|
@ -144,13 +144,13 @@ func NewStackdriverClientConfigFromMap(config map[string]string) *StackdriverCli
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record applies the `ros` Options to `ms` and then records the resulting
|
// record applies the `ros` Options to each measurement in `mss` and then records the resulting
|
||||||
// measurements in the metricsConfig's designated backend.
|
// measurements in the metricsConfig's designated backend.
|
||||||
func (mc *metricsConfig) Record(ctx context.Context, ms stats.Measurement, ros ...stats.Options) error {
|
func (mc *metricsConfig) record(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error {
|
||||||
if mc == nil || mc.recorder == nil {
|
if mc == nil || mc.recorder == nil {
|
||||||
return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(ms))...)
|
return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...)
|
||||||
}
|
}
|
||||||
return mc.recorder(ctx, ms, ros...)
|
return mc.recorder(ctx, mss, ros...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMetricsConfig(ops ExporterOptions, logger *zap.SugaredLogger) (*metricsConfig, error) {
|
func createMetricsConfig(ops ExporterOptions, logger *zap.SugaredLogger) (*metricsConfig, error) {
|
||||||
|
@ -235,14 +235,24 @@ func createMetricsConfig(ops ExporterOptions, logger *zap.SugaredLogger) (*metri
|
||||||
if !allowCustomMetrics {
|
if !allowCustomMetrics {
|
||||||
servingOrEventing := metricskey.KnativeRevisionMetrics.Union(
|
servingOrEventing := metricskey.KnativeRevisionMetrics.Union(
|
||||||
metricskey.KnativeTriggerMetrics).Union(metricskey.KnativeBrokerMetrics)
|
metricskey.KnativeTriggerMetrics).Union(metricskey.KnativeBrokerMetrics)
|
||||||
mc.recorder = func(ctx context.Context, ms stats.Measurement, ros ...stats.Options) error {
|
mc.recorder = func(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error {
|
||||||
metricType := path.Join(mc.stackdriverMetricTypePrefix, ms.Measure().Name())
|
// Perform array filtering in place using two indices: w(rite)Index and r(ead)Index.
|
||||||
|
wIdx := 0
|
||||||
if servingOrEventing.Has(metricType) {
|
for rIdx := 0; rIdx < len(mss); rIdx++ {
|
||||||
return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(ms))...)
|
metricType := path.Join(mc.stackdriverMetricTypePrefix, mss[rIdx].Measure().Name())
|
||||||
|
if servingOrEventing.Has(metricType) {
|
||||||
|
mss[wIdx] = mss[rIdx]
|
||||||
|
wIdx++
|
||||||
|
}
|
||||||
|
// Otherwise, skip the measurement (because it won't be accepted).
|
||||||
}
|
}
|
||||||
// Otherwise, skip (because it won't be accepted)
|
// Found no matched metrics.
|
||||||
return nil
|
if wIdx == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Trim the list to the number of written objects.
|
||||||
|
mss = mss[:wIdx]
|
||||||
|
return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,12 @@ import (
|
||||||
|
|
||||||
// Record stores the given Measurement from `ms` in the current metrics backend.
|
// Record stores the given Measurement from `ms` in the current metrics backend.
|
||||||
func Record(ctx context.Context, ms stats.Measurement, ros ...stats.Options) {
|
func Record(ctx context.Context, ms stats.Measurement, ros ...stats.Options) {
|
||||||
mc := getCurMetricsConfig()
|
getCurMetricsConfig().record(ctx, []stats.Measurement{ms}, ros...)
|
||||||
|
}
|
||||||
|
|
||||||
mc.Record(ctx, ms, ros...)
|
// RecordBatch stores the given Measurements from `mss` in the current metrics backend.
|
||||||
|
func RecordBatch(ctx context.Context, mss ...stats.Measurement) {
|
||||||
|
getCurMetricsConfig().record(ctx, mss)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buckets125 generates an array of buckets with approximate powers-of-two
|
// Buckets125 generates an array of buckets with approximate powers-of-two
|
||||||
|
|
|
@ -38,68 +38,87 @@ type cases struct {
|
||||||
|
|
||||||
func TestRecordServing(t *testing.T) {
|
func TestRecordServing(t *testing.T) {
|
||||||
measure := stats.Int64("request_count", "Number of reconcile operations", stats.UnitNone)
|
measure := stats.Int64("request_count", "Number of reconcile operations", stats.UnitNone)
|
||||||
shouldReportCases := []cases{
|
// Increase the measurement value for each test case so that checking
|
||||||
// Increase the measurement value for each test case so that checking
|
// the last value ensures the measurement has been recorded.
|
||||||
// the last value ensures the measurement has been recorded.
|
shouldReportCases := []cases{{
|
||||||
{
|
name: "none stackdriver backend",
|
||||||
name: "none stackdriver backend",
|
metricsConfig: &metricsConfig{},
|
||||||
metricsConfig: &metricsConfig{},
|
measurement: measure.M(1),
|
||||||
measurement: measure.M(1),
|
}, {
|
||||||
}, {
|
name: "stackdriver backend with supported metric",
|
||||||
name: "stackdriver backend with supported metric",
|
metricsConfig: &metricsConfig{
|
||||||
metricsConfig: &metricsConfig{
|
isStackdriverBackend: true,
|
||||||
isStackdriverBackend: true,
|
stackdriverMetricTypePrefix: "knative.dev/internal/serving/activator",
|
||||||
stackdriverMetricTypePrefix: "knative.dev/internal/serving/activator",
|
|
||||||
},
|
|
||||||
measurement: measure.M(2),
|
|
||||||
}, {
|
|
||||||
name: "stackdriver backend with unsupported metric and allow custom metric",
|
|
||||||
metricsConfig: &metricsConfig{
|
|
||||||
isStackdriverBackend: true,
|
|
||||||
stackdriverMetricTypePrefix: "knative.dev/unsupported",
|
|
||||||
},
|
|
||||||
measurement: measure.M(3),
|
|
||||||
}, {
|
|
||||||
name: "empty metricsConfig",
|
|
||||||
measurement: measure.M(4),
|
|
||||||
},
|
},
|
||||||
}
|
measurement: measure.M(2),
|
||||||
|
}, {
|
||||||
|
name: "stackdriver backend with unsupported metric and allow custom metric",
|
||||||
|
metricsConfig: &metricsConfig{
|
||||||
|
isStackdriverBackend: true,
|
||||||
|
stackdriverMetricTypePrefix: "knative.dev/unsupported",
|
||||||
|
},
|
||||||
|
measurement: measure.M(3),
|
||||||
|
}, {
|
||||||
|
name: "empty metricsConfig",
|
||||||
|
measurement: measure.M(4),
|
||||||
|
}}
|
||||||
testRecord(t, measure, shouldReportCases)
|
testRecord(t, measure, shouldReportCases)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordEventing(t *testing.T) {
|
func TestRecordEventing(t *testing.T) {
|
||||||
measure := stats.Int64("event_count", "Number of event received", stats.UnitNone)
|
measure := stats.Int64("event_count", "Number of event received", stats.UnitNone)
|
||||||
shouldReportCases := []cases{
|
// Increase the measurement value for each test case so that checking
|
||||||
// Increase the measurement value for each test case so that checking
|
// the last value ensures the measurement has been recorded.
|
||||||
// the last value ensures the measurement has been recorded.
|
shouldReportCases := []cases{{
|
||||||
{
|
name: "none stackdriver backend",
|
||||||
name: "none stackdriver backend",
|
metricsConfig: &metricsConfig{},
|
||||||
metricsConfig: &metricsConfig{},
|
measurement: measure.M(1),
|
||||||
measurement: measure.M(1),
|
}, {
|
||||||
}, {
|
name: "stackdriver backend with supported metric",
|
||||||
name: "stackdriver backend with supported metric",
|
metricsConfig: &metricsConfig{
|
||||||
metricsConfig: &metricsConfig{
|
isStackdriverBackend: true,
|
||||||
isStackdriverBackend: true,
|
stackdriverMetricTypePrefix: "knative.dev/eventing/broker",
|
||||||
stackdriverMetricTypePrefix: "knative.dev/eventing/broker",
|
|
||||||
},
|
|
||||||
measurement: measure.M(5),
|
|
||||||
}, {
|
|
||||||
name: "stackdriver backend with unsupported metric and allow custom metric",
|
|
||||||
metricsConfig: &metricsConfig{
|
|
||||||
isStackdriverBackend: true,
|
|
||||||
stackdriverMetricTypePrefix: "knative.dev/unsupported",
|
|
||||||
},
|
|
||||||
measurement: measure.M(3),
|
|
||||||
}, {
|
|
||||||
name: "empty metricsConfig",
|
|
||||||
measurement: measure.M(4),
|
|
||||||
},
|
},
|
||||||
}
|
measurement: measure.M(2),
|
||||||
|
}, {
|
||||||
|
name: "stackdriver backend with unsupported metric and allow custom metric",
|
||||||
|
metricsConfig: &metricsConfig{
|
||||||
|
isStackdriverBackend: true,
|
||||||
|
stackdriverMetricTypePrefix: "knative.dev/unsupported",
|
||||||
|
},
|
||||||
|
measurement: measure.M(3),
|
||||||
|
}, {
|
||||||
|
name: "empty metricsConfig",
|
||||||
|
measurement: measure.M(4),
|
||||||
|
}}
|
||||||
testRecord(t, measure, shouldReportCases)
|
testRecord(t, measure, shouldReportCases)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRecordBatch(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
measure1 := stats.Int64("count1", "First counter", stats.UnitNone)
|
||||||
|
measure2 := stats.Int64("count2", "Second counter", stats.UnitNone)
|
||||||
|
v := []*view.View{&view.View{
|
||||||
|
Measure: measure1,
|
||||||
|
Aggregation: view.LastValue(),
|
||||||
|
}, &view.View{
|
||||||
|
Measure: measure2,
|
||||||
|
Aggregation: view.LastValue(),
|
||||||
|
}}
|
||||||
|
view.Register(v...)
|
||||||
|
defer view.Unregister(v...)
|
||||||
|
metricsConfig := &metricsConfig{}
|
||||||
|
measurement1 := measure1.M(1984)
|
||||||
|
measurement2 := measure2.M(42)
|
||||||
|
setCurMetricsConfig(metricsConfig)
|
||||||
|
RecordBatch(ctx, measurement1, measurement2)
|
||||||
|
metricstest.CheckLastValueData(t, measurement1.Measure().Name(), map[string]string{}, 1984)
|
||||||
|
metricstest.CheckLastValueData(t, measurement2.Measure().Name(), map[string]string{}, 42)
|
||||||
|
}
|
||||||
|
|
||||||
func testRecord(t *testing.T, measure *stats.Int64Measure, shouldReportCases []cases) {
|
func testRecord(t *testing.T, measure *stats.Int64Measure, shouldReportCases []cases) {
|
||||||
ctx := context.TODO()
|
t.Helper()
|
||||||
|
ctx := context.Background()
|
||||||
v := &view.View{
|
v := &view.View{
|
||||||
Measure: measure,
|
Measure: measure,
|
||||||
Aggregation: view.LastValue(),
|
Aggregation: view.LastValue(),
|
||||||
|
@ -117,30 +136,28 @@ func testRecord(t *testing.T, measure *stats.Int64Measure, shouldReportCases []c
|
||||||
name string
|
name string
|
||||||
metricsConfig *metricsConfig
|
metricsConfig *metricsConfig
|
||||||
measurement stats.Measurement
|
measurement stats.Measurement
|
||||||
}{
|
}{{ // Use a different value for the measurement other than the last one of shouldReportCases
|
||||||
// Use a different value for the measurement other than the last one of shouldReportCases
|
name: "stackdriver backend with unsupported metric but not allow custom metric",
|
||||||
{
|
metricsConfig: &metricsConfig{
|
||||||
name: "stackdriver backend with unsupported metric but not allow custom metric",
|
isStackdriverBackend: true,
|
||||||
metricsConfig: &metricsConfig{
|
stackdriverMetricTypePrefix: "knative.dev/unsupported",
|
||||||
isStackdriverBackend: true,
|
recorder: func(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error {
|
||||||
stackdriverMetricTypePrefix: "knative.dev/unsupported",
|
metricType := path.Join("knative.dev/unsupported", mss[0].Measure().Name())
|
||||||
recorder: func(ctx context.Context, ms stats.Measurement, ros ...stats.Options) error {
|
if metricskey.KnativeRevisionMetrics.Has(metricType) || metricskey.KnativeTriggerMetrics.Has(metricType) {
|
||||||
metricType := path.Join("knative.dev/unsupported", ms.Measure().Name())
|
ros = append(ros, stats.WithMeasurements(mss[0]))
|
||||||
if metricskey.KnativeRevisionMetrics.Has(metricType) || metricskey.KnativeTriggerMetrics.Has(metricType) {
|
return stats.RecordWithOptions(ctx, ros...)
|
||||||
ros = append(ros, stats.WithMeasurements(ms))
|
}
|
||||||
return stats.RecordWithOptions(ctx, ros...)
|
return nil
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
measurement: measure.M(5),
|
|
||||||
},
|
},
|
||||||
}
|
measurement: measure.M(5),
|
||||||
|
}}
|
||||||
|
|
||||||
for _, test := range shouldNotReportCases {
|
for _, test := range shouldNotReportCases {
|
||||||
setCurMetricsConfig(test.metricsConfig)
|
setCurMetricsConfig(test.metricsConfig)
|
||||||
Record(ctx, test.measurement)
|
Record(ctx, test.measurement)
|
||||||
metricstest.CheckLastValueData(t, test.measurement.Measure().Name(), map[string]string{}, 4) // The value is still the last one of shouldReportCases
|
metricstest.CheckLastValueData(t, test.measurement.Measure().Name(), map[string]string{},
|
||||||
|
float64(len(shouldReportCases))) // The value is still the last one of shouldReportCases
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue