diff --git a/pkg/metric/BUILD b/pkg/metric/BUILD index c155021ca..695def5f6 100644 --- a/pkg/metric/BUILD +++ b/pkg/metric/BUILD @@ -5,6 +5,9 @@ package(licenses = ["notice"]) go_library( name = "metric", srcs = [ + "condmetric_profiling.go", + "condmetric_profiling_fake.go", + "fake_metric.go", "metric.go", "metric_unsafe.go", ], @@ -31,7 +34,10 @@ proto_library( go_test( name = "metric_test", - srcs = ["metric_test.go"], + srcs = [ + "metric_test.go", + "utils_test.go", + ], library = ":metric", deps = [ ":metric_go_proto", @@ -40,3 +46,18 @@ go_test( "@org_golang_google_protobuf//proto:go_default_library", ], ) + +go_test( + name = "condmetric_test", + srcs = [ + "condmetric_profiling_disabled_test.go", + "condmetric_profiling_enabled_test.go", + "utils_test.go", + ], + library = ":metric", + deps = [ + ":metric_go_proto", + "//pkg/eventchannel", + "@org_golang_google_protobuf//proto:go_default_library", + ], +) diff --git a/pkg/metric/condmetric_profiling.go b/pkg/metric/condmetric_profiling.go new file mode 100644 index 000000000..b1b4adb55 --- /dev/null +++ b/pkg/metric/condmetric_profiling.go @@ -0,0 +1,64 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build condmetric_profiling +// +build condmetric_profiling + +package metric + +// This file defines conditional metrics that are meant to be used when profiling +// runsc during benchmark tests. + +// ProfilingUint64Metric is a metric type that is registered and used only when +// the "condmetric_profiling" go tag is specified for go build. +// +// Otherwise it is exactly like a Uint64Metric. +type ProfilingUint64Metric = Uint64Metric + +// ProfilingDistributionMetric is a metric type that is registered and used only +// when the "condmetric_profiling" go tag is specified for go build. +// +// Otherwise it is exactly like a DistributionMetric. +type ProfilingDistributionMetric = DistributionMetric + +// ProfilingTimerMetric is a metric type that is registered and used only when +// the "condmetric_profiling" go tag is specified for go build. +// +// Otherwise it is exactly like a TimerMetric. +type ProfilingTimerMetric = TimerMetric + +// NewProfilingUint64Metric is equivalent to NewUint64Metric except it creates a +// ProfilingUint64Metric +var NewProfilingUint64Metric = NewUint64Metric + +// MustCreateNewProfilingUint64Metric is equivalent to MustCreateNewUint64Metric +// except it creates a ProfilingUint64Metric. +var MustCreateNewProfilingUint64Metric = MustCreateNewUint64Metric + +// NewProfilingDistributionMetric is equivalent to NewDistributionMetric except +// it creates a ProfilingDistributionMetric. +var NewProfilingDistributionMetric = NewDistributionMetric + +// MustCreateNewProfilingDistributionMetric is equivalent to +// MustCreateNewDistributionMetric except it creates a +// ProfilingDistributionMetric. +var MustCreateNewProfilingDistributionMetric = MustCreateNewDistributionMetric + +// NewProfilingTimerMetric is equivalent to NewTimerMetric except it creates a +// ProfilingTimerMetric. +var NewProfilingTimerMetric = NewTimerMetric + +// MustCreateNewProfilingTimerMetric is equivalent to MustCreateNewTimerMetric +// except it creates a ProfilingTimerMetric. +var MustCreateNewProfilingTimerMetric = MustCreateNewTimerMetric diff --git a/pkg/metric/condmetric_profiling_disabled_test.go b/pkg/metric/condmetric_profiling_disabled_test.go new file mode 100644 index 000000000..ac02caacb --- /dev/null +++ b/pkg/metric/condmetric_profiling_disabled_test.go @@ -0,0 +1,61 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !condmetric_profiling +// +build !condmetric_profiling + +package metric + +import ( + "testing" + + pb "gvisor.dev/gvisor/pkg/metric/metric_go_proto" +) + +func TestProfilingMetricsDisabled(t *testing.T) { + defer resetTest() + + _, err := NewProfilingUint64Metric("counterM", false, pb.MetricMetadata_UNITS_NONE, "One uint64 metric") + if err != nil { + t.Fatalf("NewProfilingUint64Metric got err %v want nil", err) + } + + bucketer := NewExponentialBucketer(3, 2, 0, 1) + _, err = NewProfilingDistributionMetric("distribM", false, bucketer, pb.MetricMetadata_UNITS_NANOSECONDS, "One distribution metric") + if err != nil { + t.Fatalf("NewProfilingDistributionMetric got err %v want nil", err) + } + + _, err = NewProfilingTimerMetric("timerM", bucketer, "One timer metric") + if err != nil { + t.Fatalf("NewProfilingTimerMetric got err %v want nil", err) + } + + if err := Initialize(); err != nil { + t.Fatalf("Initialize(): %s", err) + } + + if len(emitter) != 1 { + t.Fatalf("Initialize emitted %d events want 1", len(emitter)) + } + + mr, ok := emitter[0].(*pb.MetricRegistration) + if !ok { + t.Fatalf("emitter %v got %T want pb.MetricRegistration", emitter[0], emitter[0]) + } + + if len(mr.Metrics) != 0 { + t.Errorf("MetricRegistration got %d metrics want %d", len(mr.Metrics), 0) + } +} diff --git a/pkg/metric/condmetric_profiling_enabled_test.go b/pkg/metric/condmetric_profiling_enabled_test.go new file mode 100644 index 000000000..db64fe3b0 --- /dev/null +++ b/pkg/metric/condmetric_profiling_enabled_test.go @@ -0,0 +1,61 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build condmetric_profiling +// +build condmetric_profiling + +package metric + +import ( + "testing" + + pb "gvisor.dev/gvisor/pkg/metric/metric_go_proto" +) + +func TestProfilingMetricsEnabled(t *testing.T) { + defer resetTest() + + _, err := NewProfilingUint64Metric("counterM", false, pb.MetricMetadata_UNITS_NONE, "One uint64 metric") + if err != nil { + t.Fatalf("NewProfilingUint64Metric got err %v want nil", err) + } + + bucketer := NewExponentialBucketer(3, 2, 0, 1) + _, err = NewProfilingDistributionMetric("distribM", false, bucketer, pb.MetricMetadata_UNITS_NANOSECONDS, "One distribution metric") + if err != nil { + t.Fatalf("NewProfilingDistributionMetric got err %v want nil", err) + } + + _, err = NewProfilingTimerMetric("timerM", bucketer, "One timer metric") + if err != nil { + t.Fatalf("NewProfilingTimerMetric got err %v want nil", err) + } + + if err := Initialize(); err != nil { + t.Fatalf("Initialize(): %s", err) + } + + if len(emitter) != 1 { + t.Fatalf("Initialize emitted %d events want 1", len(emitter)) + } + + mr, ok := emitter[0].(*pb.MetricRegistration) + if !ok { + t.Fatalf("emitter %v got %T want pb.MetricRegistration", emitter[0], emitter[0]) + } + + if len(mr.Metrics) != 3 { + t.Errorf("MetricRegistration got %d metrics want %d", len(mr.Metrics), 3) + } +} diff --git a/pkg/metric/condmetric_profiling_fake.go b/pkg/metric/condmetric_profiling_fake.go new file mode 100644 index 000000000..30433a13e --- /dev/null +++ b/pkg/metric/condmetric_profiling_fake.go @@ -0,0 +1,61 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !condmetric_profiling +// +build !condmetric_profiling + +package metric + +// ProfilingUint64Metric is a metric type that is registered and used only when +// the "condmetric_profiling" go tag is specified when building runsc. +// +// Otherwise it is exactly like a Uint64Metric. +type ProfilingUint64Metric = FakeUint64Metric + +// ProfilingDistributionMetric is a metric type that is registered and used only +// when the "condmetric_profiling" go tag is specified when building runsc. +// +// Otherwise it is exactly like a DistributionMetric. +type ProfilingDistributionMetric = FakeDistributionMetric + +// ProfilingTimerMetric is a metric type that is registered and used only when +// the "condmetric_profiling" go tag is specified when building runsc. +// +// Otherwise it is exactly like a TimerMetric. +type ProfilingTimerMetric = FakeTimerMetric + +// NewProfilingUint64Metric is equivalent to NewUint64Metric except it creates a +// ProfilingUint64Metric +var NewProfilingUint64Metric = NewFakeUint64Metric + +// MustCreateNewProfilingUint64Metric is equivalent to MustCreateNewUint64Metric +// except it creates a ProfilingUint64Metric. +var MustCreateNewProfilingUint64Metric = MustCreateNewFakeUint64Metric + +// NewProfilingDistributionMetric is equivalent to NewDistributionMetric except +// it creates a ProfilingDistributionMetric. +var NewProfilingDistributionMetric = NewFakeDistributionMetric + +// MustCreateNewProfilingDistributionMetric is equivalent to +// MustCreateNewDistributionMetric except it creates a +// ProfilingDistributionMetric. +var MustCreateNewProfilingDistributionMetric = MustCreateNewFakeDistributionMetric + +// NewProfilingTimerMetric is equivalent to NewTimerMetric except it creates a +// ProfilingTimerMetric. +var NewProfilingTimerMetric = NewFakeTimerMetric + +// MustCreateNewProfilingTimerMetric is equivalent to MustCreateNewTimerMetric +// except it creates a ProfilingTimerMetric. +var MustCreateNewProfilingTimerMetric = MustCreateNewFakeTimerMetric diff --git a/pkg/metric/fake_metric.go b/pkg/metric/fake_metric.go new file mode 100644 index 000000000..58b45ba7d --- /dev/null +++ b/pkg/metric/fake_metric.go @@ -0,0 +1,104 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import pb "gvisor.dev/gvisor/pkg/metric/metric_go_proto" + +// FakeUint64Metric is a type that implements all the methods of a Uint64Metric +// as a no-op. +type FakeUint64Metric struct{} + +// FakeDistributionMetric is a type that implements all the methods of a +// DistributionMetric as a no-op. +type FakeDistributionMetric struct{} + +// FakeTimerMetric is a type that implements all the methods of a TimerMetric +// as a no-op. +type FakeTimerMetric struct{} + +// FakeTimedOperation is a type that implements all the methods of a +// TimedOperation as a no-op. +type FakeTimedOperation struct{} + +// Value from a FakeUint64Metric always returns a meaningless value. +// +//go:nosplit +func (m *FakeUint64Metric) Value(fieldValues ...string) uint64 { + return 0 +} + +// Increment on a FakeUint64Metric does nothing. +// +//go:nosplit +func (m *FakeUint64Metric) Increment(fieldValues ...string) {} + +// IncrementBy on a FakeUint64Metric does nothing. +// +//go:nosplit +func (m *FakeUint64Metric) IncrementBy(v uint64, fieldValues ...string) {} + +// AddSample on a FakeUint64Metric does nothing. +// +//go:nosplit +func (d *FakeDistributionMetric) AddSample(sample int64, fields ...string) {} + +// Start on a FakeUint64Metric returns a FakeTimedOperation struct, which does +// nothing and does not keep the time. +// +//go:nosplit +func (t *FakeTimerMetric) Start(fields ...string) FakeTimedOperation { + return FakeTimedOperation{} +} + +// Finish on a FakeTimedOperation does nothing. +// +//go:nosplit +func (o FakeTimedOperation) Finish(extraFields ...string) {} + +// NewFakeUint64Metric is equivalent to NewUint64Metric except it creates a +// FakeUint64Metric +func NewFakeUint64Metric(name string, sync bool, units pb.MetricMetadata_Units, description string, fields ...Field) (*FakeUint64Metric, error) { + return &FakeUint64Metric{}, nil +} + +// MustCreateNewFakeUint64Metric is equivalent to MustCreateNewUint64Metric +// except it creates a FakeUint64Metric. +func MustCreateNewFakeUint64Metric(name string, sync bool, description string, fields ...Field) *FakeUint64Metric { + return &FakeUint64Metric{} +} + +// NewFakeDistributionMetric is equivalent to NewDistributionMetric except +// it creates a FakeDistributionMetric. +func NewFakeDistributionMetric(name string, sync bool, bucketer Bucketer, unit pb.MetricMetadata_Units, description string, fields ...Field) (*FakeDistributionMetric, error) { + return &FakeDistributionMetric{}, nil +} + +// MustCreateNewFakeDistributionMetric is equivalent to +// MustCreateNewDistributionMetric except it creates a FakeDistributionMetric. +func MustCreateNewFakeDistributionMetric(name string, sync bool, bucketer Bucketer, unit pb.MetricMetadata_Units, description string, fields ...Field) *FakeDistributionMetric { + return &FakeDistributionMetric{} +} + +// NewFakeTimerMetric is equivalent to NewTimerMetric except it creates a +// FakeTimerMetric. +func NewFakeTimerMetric(name string, nanoBucketer Bucketer, description string, fields ...Field) (*FakeTimerMetric, error) { + return &FakeTimerMetric{}, nil +} + +// MustCreateNewFakeTimerMetric is equivalent to MustCreateNewTimerMetric +// except it creates a FakeTimerMetric. +func MustCreateNewFakeTimerMetric(name string, nanoBucketer Bucketer, description string, fields ...Field) *FakeTimerMetric { + return &FakeTimerMetric{} +} diff --git a/pkg/metric/metric.go b/pkg/metric/metric.go index c598c82f2..aaa286f64 100644 --- a/pkg/metric/metric.go +++ b/pkg/metric/metric.go @@ -827,8 +827,8 @@ type metricSet struct { } // makeMetricSet returns a new metricSet. -func makeMetricSet() metricSet { - return metricSet{ +func makeMetricSet() *metricSet { + return &metricSet{ uint64Metrics: make(map[string]customUint64Metric), distributionMetrics: make(map[string]*DistributionMetric), finished: make([]stageTiming, 0, len(allStages)), diff --git a/pkg/metric/metric_test.go b/pkg/metric/metric_test.go index a7adc7390..a8904e658 100644 --- a/pkg/metric/metric_test.go +++ b/pkg/metric/metric_test.go @@ -22,49 +22,10 @@ import ( "time" "google.golang.org/protobuf/proto" - "gvisor.dev/gvisor/pkg/eventchannel" pb "gvisor.dev/gvisor/pkg/metric/metric_go_proto" "gvisor.dev/gvisor/pkg/sync" ) -// sliceEmitter implements eventchannel.Emitter by appending all messages to a -// slice. -type sliceEmitter []proto.Message - -// Emit implements eventchannel.Emitter.Emit. -func (s *sliceEmitter) Emit(msg proto.Message) (bool, error) { - *s = append(*s, msg) - return false, nil -} - -// Emit implements eventchannel.Emitter.Close. -func (s *sliceEmitter) Close() error { - return nil -} - -// Reset clears all events in s. -func (s *sliceEmitter) Reset() { - *s = nil -} - -// emitter is the eventchannel.Emitter used for all tests. Package eventchannel -// doesn't allow removing Emitters, so we must use one global emitter for all -// test cases. -var emitter sliceEmitter - -func init() { - reset() - - eventchannel.AddEmitter(&emitter) -} - -// reset clears all global state in the metric package. -func reset() { - initialized = false - allMetrics = makeMetricSet() - emitter.Reset() -} - const ( fooDescription = "Foo!" barDescription = "Bar Baz" @@ -73,7 +34,7 @@ const ( ) func TestInitialize(t *testing.T) { - defer reset() + defer resetTest() _, err := NewUint64Metric("/foo", false, pb.MetricMetadata_UNITS_NONE, fooDescription) if err != nil { @@ -181,7 +142,7 @@ func TestInitialize(t *testing.T) { } func TestDisable(t *testing.T) { - defer reset() + defer resetTest() _, err := NewUint64Metric("/foo", false, pb.MetricMetadata_UNITS_NONE, fooDescription) if err != nil { @@ -217,7 +178,7 @@ func TestDisable(t *testing.T) { } func TestEmitMetricUpdate(t *testing.T) { - defer reset() + defer resetTest() foo, err := NewUint64Metric("/foo", false, pb.MetricMetadata_UNITS_NONE, fooDescription) if err != nil { @@ -411,7 +372,7 @@ func TestEmitMetricUpdate(t *testing.T) { } func TestEmitMetricUpdateWithFields(t *testing.T) { - defer reset() + defer resetTest() field := Field{ name: "weirdness_type", @@ -498,7 +459,7 @@ func TestEmitMetricUpdateWithFields(t *testing.T) { } func TestMetricUpdateStageTiming(t *testing.T) { - defer reset() + defer resetTest() expectedTimings := map[InitStage]struct{ min, max time.Duration }{} measureStage := func(stage InitStage, body func()) { @@ -642,7 +603,7 @@ func TestMetricUpdateStageTiming(t *testing.T) { } func TestTimerMetric(t *testing.T) { - defer reset() + defer resetTest() // This bucketer just has 2 finite buckets: [0, 500ms) and [500ms, 1s). bucketer := NewExponentialBucketer(2, uint64((500 * time.Millisecond).Nanoseconds()), 0, 1) field1 := NewField("field1", []string{"foo", "bar"}) diff --git a/pkg/metric/utils_test.go b/pkg/metric/utils_test.go new file mode 100644 index 000000000..a390a3999 --- /dev/null +++ b/pkg/metric/utils_test.go @@ -0,0 +1,57 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "google.golang.org/protobuf/proto" + "gvisor.dev/gvisor/pkg/eventchannel" +) + +// sliceEmitter implements eventchannel.Emitter by appending all messages to a +// slice. +type sliceEmitter []proto.Message + +// Emit implements eventchannel.Emitter.Emit. +func (s *sliceEmitter) Emit(msg proto.Message) (bool, error) { + *s = append(*s, msg) + return false, nil +} + +// Emit implements eventchannel.Emitter.Close. +func (s *sliceEmitter) Close() error { + return nil +} + +// Reset clears all events in s. +func (s *sliceEmitter) Reset() { + *s = nil +} + +// emitter is the eventchannel.Emitter used for all tests. Package eventchannel +// doesn't allow removing Emitters, so we must use one global emitter for all +// test cases. +var emitter sliceEmitter + +func init() { + resetTest() + + eventchannel.AddEmitter(&emitter) +} + +func resetTest() { + initialized = false + allMetrics = makeMetricSet() + emitter.Reset() +} diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index 8deda92f1..73e46fb65 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -600,12 +600,12 @@ type Task struct { var ( // syscallCounter is a metric that tracks how many syscalls the sentry has // executed. - syscallCounter = metric.MustCreateNewUint64Metric( + syscallCounter = metric.MustCreateNewProfilingUint64Metric( "/task/syscalls", false, "The number of syscalls the sentry has executed for the user.") // faultCounter is a metric that tracks how many faults the sentry has had to // handle. - faultCounter = metric.MustCreateNewUint64Metric( + faultCounter = metric.MustCreateNewProfilingUint64Metric( "/task/faults", false, "The number of faults the sentry has handled.") ) diff --git a/pkg/sentry/platform/kvm/machine.go b/pkg/sentry/platform/kvm/machine.go index bd97a3a20..8b4e2e985 100644 --- a/pkg/sentry/platform/kvm/machine.go +++ b/pkg/sentry/platform/kvm/machine.go @@ -106,32 +106,32 @@ const ( var ( // hostExitCounter is a metric that tracks how many times the sentry // performed a host to guest world switch. - hostExitCounter = metric.MustCreateNewUint64Metric( + hostExitCounter = metric.MustCreateNewProfilingUint64Metric( "/kvm/host_exits", false, "The number of times the sentry performed a host to guest world switch.") // userExitCounter is a metric that tracks how many times the sentry has // had an exit from userspace. Analogous to vCPU.userExits. - userExitCounter = metric.MustCreateNewUint64Metric( + userExitCounter = metric.MustCreateNewProfilingUint64Metric( "/kvm/user_exits", false, "The number of times the sentry has had an exit from userspace.") // interruptCounter is a metric that tracks how many times execution returned // to the KVM host to handle a pending signal. - interruptCounter = metric.MustCreateNewUint64Metric( + interruptCounter = metric.MustCreateNewProfilingUint64Metric( "/kvm/interrupts", false, "The number of times the signal handler was invoked.") // mmapCallCounter is a metric that tracks how many times the function // seccompMmapSyscall has been called. - mmapCallCounter = metric.MustCreateNewUint64Metric( + mmapCallCounter = metric.MustCreateNewProfilingUint64Metric( "/kvm/mmap_calls", false, "The number of times seccompMmapSyscall has been called.") // getVCPUCounter is a metric that tracks how many times different paths of // machine.Get() are triggered. - getVCPUCounter = metric.MustCreateNewUint64Metric( + getVCPUCounter = metric.MustCreateNewProfilingUint64Metric( "/kvm/get_vcpu", false, "The number of times that machine.Get() was called, split by path the function took.", metric.NewField("acquisition_type", []string{"fast_reused", "reused", "unused", "stolen"})) // asInvalidateDuration are durations of calling addressSpace.invalidate(). - asInvalidateDuration = metric.MustCreateNewTimerMetric("/kvm/address_space_invalidate", + asInvalidateDuration = metric.MustCreateNewProfilingTimerMetric("/kvm/address_space_invalidate", metric.NewExponentialBucketer(15, uint64(time.Nanosecond*100), 1, 2), "Duration of calling addressSpace.invalidate().") )