diff --git a/receiver/hostmetricsreceiver/factory.go b/receiver/hostmetricsreceiver/factory.go index bb07cc0dea8..8e2c3688e98 100644 --- a/receiver/hostmetricsreceiver/factory.go +++ b/receiver/hostmetricsreceiver/factory.go @@ -33,6 +33,7 @@ import ( "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/virtualmemoryscraper" ) @@ -61,6 +62,7 @@ func NewFactory() *Factory { filesystemscraper.TypeStr: &filesystemscraper.Factory{}, memoryscraper.TypeStr: &memoryscraper.Factory{}, networkscraper.TypeStr: &networkscraper.Factory{}, + processesscraper.TypeStr: &processesscraper.Factory{}, virtualmemoryscraper.TypeStr: &virtualmemoryscraper.Factory{}, }, resourceScraperFactories: map[string]internal.ResourceScraperFactory{ diff --git a/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go b/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go index 4b7d69dab27..5231abd27b3 100644 --- a/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go +++ b/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go @@ -36,6 +36,7 @@ import ( "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processesscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/processscraper" "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/virtualmemoryscraper" ) @@ -66,10 +67,10 @@ var resourceMetrics = []string{ } var systemSpecificMetrics = map[string][]string{ - "linux": {"system.filesystem.inodes.usage", "system.swap.page_faults"}, - "darwin": {"system.filesystem.inodes.usage", "system.swap.page_faults"}, - "freebsd": {"system.filesystem.inodes.usage", "system.swap.page_faults"}, - "openbsd": {"system.filesystem.inodes.usage", "system.swap.page_faults"}, + "linux": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "darwin": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "freebsd": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, + "openbsd": {"system.filesystem.inodes.usage", "system.processes.running", "system.processes.blocked", "system.swap.page_faults"}, "solaris": {"system.filesystem.inodes.usage", "system.swap.page_faults"}, } @@ -80,6 +81,7 @@ var factories = map[string]internal.ScraperFactory{ loadscraper.TypeStr: &loadscraper.Factory{}, memoryscraper.TypeStr: &memoryscraper.Factory{}, networkscraper.TypeStr: &networkscraper.Factory{}, + processesscraper.TypeStr: &processesscraper.Factory{}, virtualmemoryscraper.TypeStr: &virtualmemoryscraper.Factory{}, } @@ -100,6 +102,7 @@ func TestGatherMetrics_EndToEnd(t *testing.T) { memoryscraper.TypeStr: &memoryscraper.Config{}, networkscraper.TypeStr: &networkscraper.Config{}, processscraper.TypeStr: &processscraper.Config{}, + processesscraper.TypeStr: &processesscraper.Config{}, virtualmemoryscraper.TypeStr: &virtualmemoryscraper.Config{}, }, } @@ -329,6 +332,7 @@ func Benchmark_ScrapeDefaultMetrics(b *testing.B) { loadscraper.TypeStr: (&loadscraper.Factory{}).CreateDefaultConfig(), memoryscraper.TypeStr: (&memoryscraper.Factory{}).CreateDefaultConfig(), networkscraper.TypeStr: (&networkscraper.Factory{}).CreateDefaultConfig(), + processesscraper.TypeStr: (&processesscraper.Factory{}).CreateDefaultConfig(), virtualmemoryscraper.TypeStr: (&virtualmemoryscraper.Factory{}).CreateDefaultConfig(), }, } @@ -345,8 +349,9 @@ func Benchmark_ScrapeAllMetrics(b *testing.B) { loadscraper.TypeStr: &loadscraper.Config{}, memoryscraper.TypeStr: &memoryscraper.Config{}, networkscraper.TypeStr: &networkscraper.Config{}, - processscraper.TypeStr: &processscraper.Config{}, + processesscraper.TypeStr: &processesscraper.Config{}, virtualmemoryscraper.TypeStr: &virtualmemoryscraper.Config{}, + processscraper.TypeStr: &processscraper.Config{}, }, } diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go new file mode 100644 index 00000000000..1abf79a7940 --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry 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 processesscraper + +import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" + +// Config relating to Processes Metric Scraper. +type Config struct { + internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go new file mode 100644 index 00000000000..f719ef01f0b --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry 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 processesscraper + +import ( + "context" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +// This file implements Factory for Processes scraper. + +const ( + // The value of "type" key in configuration. + TypeStr = "processes" +) + +// Factory is the Factory for scraper. +type Factory struct { +} + +// CreateDefaultConfig creates the default configuration for the Scraper. +func (f *Factory) CreateDefaultConfig() internal.Config { + return &Config{} +} + +// CreateMetricsScraper creates a scraper based on provided config. +func (f *Factory) CreateMetricsScraper( + ctx context.Context, + _ *zap.Logger, + config internal.Config, +) (internal.Scraper, error) { + cfg := config.(*Config) + return newProcessesScraper(ctx, cfg), nil +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go new file mode 100644 index 00000000000..58475e0c7af --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/factory_test.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry 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 processesscraper + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := &Factory{} + cfg := factory.CreateDefaultConfig() + assert.IsType(t, &Config{}, cfg) +} + +func TestCreateMetricsScraper(t *testing.T) { + factory := &Factory{} + cfg := &Config{} + + scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg) + + assert.NoError(t, err) + assert.NotNil(t, scraper) +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go new file mode 100644 index 00000000000..deb80ebb0a4 --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_metadata.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry 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 processesscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +// descriptors + +var processesRunningDescriptor = func() pdata.MetricDescriptor { + descriptor := pdata.NewMetricDescriptor() + descriptor.InitEmpty() + descriptor.SetName("system.processes.running") + descriptor.SetDescription("Total number of running processes.") + descriptor.SetUnit("1") + descriptor.SetType(pdata.MetricTypeInt64) + return descriptor +}() + +var processesBlockedDescriptor = func() pdata.MetricDescriptor { + descriptor := pdata.NewMetricDescriptor() + descriptor.InitEmpty() + descriptor.SetName("system.processes.blocked") + descriptor.SetDescription("Total number of blocked processes.") + descriptor.SetUnit("1") + descriptor.SetType(pdata.MetricTypeInt64) + return descriptor +}() diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go new file mode 100644 index 00000000000..07cf368df8b --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry 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 processesscraper + +import ( + "context" + + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/load" + "go.opencensus.io/trace" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +// scraper for Processes Metrics +type scraper struct { + config *Config + startTime pdata.TimestampUnixNano + + // for mocking gopsutil load.Misc + misc getMiscStats +} + +type getMiscStats func() (*load.MiscStat, error) + +// newProcessesScraper creates a set of Processes related metrics +func newProcessesScraper(_ context.Context, cfg *Config) *scraper { + return &scraper{config: cfg, misc: load.Misc} +} + +// Initialize +func (s *scraper) Initialize(_ context.Context) error { + bootTime, err := host.BootTime() + if err != nil { + return err + } + + s.startTime = pdata.TimestampUnixNano(bootTime) + return nil +} + +// Close +func (s *scraper) Close(_ context.Context) error { + return nil +} + +// ScrapeMetrics +func (s *scraper) ScrapeMetrics(ctx context.Context) (pdata.MetricSlice, error) { + _, span := trace.StartSpan(ctx, "processesscraper.ScrapeMetrics") + defer span.End() + + metrics := pdata.NewMetricSlice() + err := appendSystemSpecificProcessesMetrics(metrics, 0, s.misc) + return metrics, err +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go new file mode 100644 index 00000000000..7859222f724 --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_fallback.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry 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. + +// +build !linux,!darwin,!freebsd,!openbsd + +package processesscraper + +import ( + "go.opentelemetry.io/collector/consumer/pdata" +) + +func appendSystemSpecificProcessesMetrics(metrics pdata.MetricSlice, startIndex int, miscFunc getMiscStats) error { + return nil +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_others.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_others.go new file mode 100644 index 00000000000..9b0185f11a2 --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_others.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry 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. + +// +build linux darwin freebsd openbsd + +package processesscraper + +import ( + "time" + + "github.com/shirou/gopsutil/load" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func appendSystemSpecificProcessesMetrics(metrics pdata.MetricSlice, startIndex int, miscFunc getMiscStats) error { + misc, err := miscFunc() + if err != nil { + return err + } + + metrics.Resize(startIndex + 2) + initializeProcessesRunningMetric(metrics.At(startIndex+0), misc) + initializeProcessesBlockedMetric(metrics.At(startIndex+1), misc) + return nil +} + +func initializeProcessesRunningMetric(metric pdata.Metric, misc *load.MiscStat) { + processesRunningDescriptor.CopyTo(metric.MetricDescriptor()) + + ddps := metric.Int64DataPoints() + ddps.Resize(1) + ddps.At(0).SetTimestamp(pdata.TimestampUnixNano(uint64(time.Now().UnixNano()))) + ddps.At(0).SetValue(int64(misc.ProcsRunning)) +} + +func initializeProcessesBlockedMetric(metric pdata.Metric, misc *load.MiscStat) { + processesBlockedDescriptor.CopyTo(metric.MetricDescriptor()) + + ddps := metric.Int64DataPoints() + ddps.Resize(1) + ddps.At(0).SetTimestamp(pdata.TimestampUnixNano(uint64(time.Now().UnixNano()))) + ddps.At(0).SetValue(int64(misc.ProcsBlocked)) +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go new file mode 100644 index 00000000000..2548e037a68 --- /dev/null +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/processes_scraper_test.go @@ -0,0 +1,88 @@ +// Copyright The OpenTelemetry 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 processesscraper + +import ( + "context" + "errors" + "runtime" + "testing" + + "github.com/shirou/gopsutil/load" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal" +) + +var systemSpecificMetrics = map[string][]pdata.MetricDescriptor{ + "linux": {processesRunningDescriptor, processesBlockedDescriptor}, + "darwin": {processesRunningDescriptor, processesBlockedDescriptor}, + "freebsd": {processesRunningDescriptor, processesBlockedDescriptor}, + "openbsd": {processesRunningDescriptor, processesBlockedDescriptor}, +} + +func TestScrapeMetrics(t *testing.T) { + type testCase struct { + name string + miscFunc func() (*load.MiscStat, error) + expectedErr string + } + + testCases := []testCase{ + { + name: "Standard", + }, + { + name: "Error", + miscFunc: func() (*load.MiscStat, error) { return nil, errors.New("err1") }, + expectedErr: "err1", + }, + } + + expectedMetrics := systemSpecificMetrics[runtime.GOOS] + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + scraper := newProcessesScraper(context.Background(), &Config{}) + if test.miscFunc != nil { + scraper.misc = test.miscFunc + } + + err := scraper.Initialize(context.Background()) + require.NoError(t, err, "Failed to initialize processes scraper: %v", err) + defer func() { assert.NoError(t, scraper.Close(context.Background())) }() + + metrics, err := scraper.ScrapeMetrics(context.Background()) + if len(expectedMetrics) > 0 && test.expectedErr != "" { + assert.EqualError(t, err, test.expectedErr) + return + } + require.NoError(t, err, "Failed to scrape metrics: %v", err) + + assert.Equal(t, len(expectedMetrics), metrics.Len()) + for i, expectedMetricDescriptor := range expectedMetrics { + assertProcessesMetricValid(t, metrics.At(i), expectedMetricDescriptor) + } + }) + } +} + +func assertProcessesMetricValid(t *testing.T, metric pdata.Metric, descriptor pdata.MetricDescriptor) { + internal.AssertDescriptorEqual(t, descriptor, metric.MetricDescriptor()) + assert.Equal(t, metric.Int64DataPoints().Len(), 1) + assert.Equal(t, metric.Int64DataPoints().At(0).LabelsMap().Len(), 0) +}