From eee0daf88dec900b51df416d1964f6426428a65e Mon Sep 17 00:00:00 2001 From: James Moessis Date: Tue, 8 Nov 2022 11:54:04 +1100 Subject: [PATCH] [receiver/hostmetrics] Add an optional root_path configuration (#16026) - Make the receiver configurable to be aware of the root filesystem, for Linux only. - Give the ability to the filesystem scraper to translate mountpoints between host and container mountpoint attribute emitted without the root path prefix, i.e. from the host's perspective - No longer requires specification of where to find the mountinfo file through env var in order to avoid errors. - All scrapers can see RootPath - useful for other scrapers e.g. pagingscraper as discussed in Ability to scrape filesystem host metrics from a container - Set the environment variables for the user if they are not set. - Validate that the root_path aligns with the gopsutil env vars (if they are previously set). --- .chloggen/root-path-1.yaml | 4 + receiver/hostmetricsreceiver/README.md | 30 ++++++- receiver/hostmetricsreceiver/config.go | 12 ++- receiver/hostmetricsreceiver/factory.go | 22 ++++++ receiver/hostmetricsreceiver/go.mod | 2 +- .../hostmetricsreceiver/hostmetrics_linux.go | 71 +++++++++++++++++ .../hostmetrics_linux_test.go | 79 +++++++++++++++++++ .../hostmetricsreceiver/hostmetrics_others.go | 30 +++++++ .../hostmetrics_others_test.go | 31 ++++++++ .../hostmetrics_receiver_test.go | 18 +++++ .../hostmetricsreceiver/internal/scraper.go | 9 +++ .../internal/scraper/cpuscraper/config.go | 2 + .../internal/scraper/diskscraper/config.go | 2 + .../scraper/filesystemscraper/config.go | 4 + .../filesystemscraper/filesystem_scraper.go | 11 ++- .../filesystem_scraper_test.go | 38 ++++++++- .../internal/scraper/loadscraper/config.go | 2 + .../internal/scraper/memoryscraper/config.go | 2 + .../internal/scraper/networkscraper/config.go | 2 + .../internal/scraper/pagingscraper/config.go | 2 + .../scraper/processesscraper/config.go | 2 + .../internal/scraper/processscraper/config.go | 2 + .../testdata/config-bad-root-path.yaml | 18 +++++ .../testdata/config-root-path.yaml | 18 +++++ 24 files changed, 405 insertions(+), 8 deletions(-) create mode 100755 .chloggen/root-path-1.yaml create mode 100644 receiver/hostmetricsreceiver/hostmetrics_linux.go create mode 100644 receiver/hostmetricsreceiver/hostmetrics_linux_test.go create mode 100644 receiver/hostmetricsreceiver/hostmetrics_others.go create mode 100644 receiver/hostmetricsreceiver/hostmetrics_others_test.go create mode 100644 receiver/hostmetricsreceiver/testdata/config-bad-root-path.yaml create mode 100644 receiver/hostmetricsreceiver/testdata/config-root-path.yaml diff --git a/.chloggen/root-path-1.yaml b/.chloggen/root-path-1.yaml new file mode 100755 index 000000000000..89501fcd841f --- /dev/null +++ b/.chloggen/root-path-1.yaml @@ -0,0 +1,4 @@ +change_type: enhancement +component: hostmetricsreceiver +note: "Added `root_path` config option, allowing the user to specify where the host filesystem is." +issues: [5879, 16026] diff --git a/receiver/hostmetricsreceiver/README.md b/receiver/hostmetricsreceiver/README.md index de27c7eb8445..3b792a1703b8 100644 --- a/receiver/hostmetricsreceiver/README.md +++ b/receiver/hostmetricsreceiver/README.md @@ -12,12 +12,13 @@ deployed as an agent. ## Getting Started -The collection interval and the categories of metrics to be scraped can be +The collection interval, root path, and the categories of metrics to be scraped can be configured: ```yaml hostmetrics: collection_interval: # default = 1m + root_path: scrapers: : : @@ -141,6 +142,33 @@ service: receivers: [hostmetrics, hostmetrics/disk] ``` +### Collecting host metrics from inside a container (Linux only) + +Host metrics are collected from the Linux system directories on the filesystem. +You likely want to collect metrics about the host system and not the container. +This is achievable by following these steps: + +#### 1. Bind mount the host filesystem + +The simplest configuration is to mount the entire host filesystem when running +the container. e.g. `docker run -v /:/hostfs ...`. + +You can also choose which parts of the host filesystem to mount, if you know +exactly what you'll need. e.g. `docker run -v /proc:/hostfs/proc`. + +#### 2. Configure `root_path` + +Configure `root_path` so the hostmetrics receiver knows where the root filesystem is. +Note: if running multiple instances of the host metrics receiver, they must all have +the same `root_path`. + +Example: +```yaml +receivers: + hostmetrics: + root_path: /hostfs +``` + ## Resource attributes Currently, the hostmetrics receiver does not set any Resource attributes on the exported metrics. However, if you want to set Resource attributes, you can provide them via environment variables via the [resourcedetection](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourcedetectionprocessor#environment-variable) processor. For example, you can add the following resource attributes to adhere to [Resource Semantic Conventions](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/): diff --git a/receiver/hostmetricsreceiver/config.go b/receiver/hostmetricsreceiver/config.go index e7f23b84f94a..a966deb514b3 100644 --- a/receiver/hostmetricsreceiver/config.go +++ b/receiver/hostmetricsreceiver/config.go @@ -21,6 +21,7 @@ import ( "go.opentelemetry.io/collector/config" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/multierr" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" ) @@ -33,6 +34,8 @@ const ( type Config struct { scraperhelper.ScraperControllerSettings `mapstructure:",squash"` Scrapers map[string]internal.Config `mapstructure:"-"` + // RootPath is the host's root directory (linux only). + RootPath string `mapstructure:"root_path"` } var _ config.Receiver = (*Config)(nil) @@ -40,11 +43,12 @@ var _ confmap.Unmarshaler = (*Config)(nil) // Validate checks the receiver configuration is valid func (cfg *Config) Validate() error { + var err error if len(cfg.Scrapers) == 0 { - return errors.New("must specify at least one scraper when using hostmetrics receiver") + err = multierr.Append(err, errors.New("must specify at least one scraper when using hostmetrics receiver")) } - - return nil + err = multierr.Append(err, validateRootPath(cfg.RootPath, &osEnv{})) + return err } // Unmarshal a config.Parser into the config struct. @@ -84,6 +88,8 @@ func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error { return fmt.Errorf("error reading settings for scraper type %q: %w", key, err) } + collectorCfg.SetRootPath(cfg.RootPath) + cfg.Scrapers[key] = collectorCfg } diff --git a/receiver/hostmetricsreceiver/factory.go b/receiver/hostmetricsreceiver/factory.go index d47e8f9f77e1..e625ddd97fdb 100644 --- a/receiver/hostmetricsreceiver/factory.go +++ b/receiver/hostmetricsreceiver/factory.go @@ -17,6 +17,7 @@ package hostmetricsreceiver // import "github.com/open-telemetry/opentelemetry-c import ( "context" "fmt" + "os" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config" @@ -93,6 +94,10 @@ func createMetricsReceiver( return nil, err } + if err = setGoPsutilEnvVars(oCfg.RootPath, &osEnv{}); err != nil { + return nil, err + } + return scraperhelper.NewScraperControllerReceiver( &oCfg.ScraperControllerSettings, set, @@ -137,3 +142,20 @@ func createHostMetricsScraper(ctx context.Context, set component.ReceiverCreateS scraper, err = factory.CreateMetricsScraper(ctx, set, cfg) return } + +type environment interface { + Lookup(k string) (string, bool) + Set(k, v string) error +} + +type osEnv struct{} + +var _ environment = (*osEnv)(nil) + +func (e *osEnv) Set(k, v string) error { + return os.Setenv(k, v) +} + +func (e *osEnv) Lookup(k string) (string, bool) { + return os.LookupEnv(k) +} diff --git a/receiver/hostmetricsreceiver/go.mod b/receiver/hostmetricsreceiver/go.mod index 6620b30679a4..7bfd179f5a44 100644 --- a/receiver/hostmetricsreceiver/go.mod +++ b/receiver/hostmetricsreceiver/go.mod @@ -10,6 +10,7 @@ require ( go.opentelemetry.io/collector v0.63.2-0.20221104003159-6b27644724d8 go.opentelemetry.io/collector/pdata v0.63.2-0.20221104003159-6b27644724d8 go.opentelemetry.io/collector/semconv v0.63.2-0.20221104003159-6b27644724d8 + go.uber.org/multierr v1.8.0 go.uber.org/zap v1.23.0 golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 @@ -63,7 +64,6 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.33.0 // indirect go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect golang.org/x/text v0.4.0 // indirect google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect diff --git a/receiver/hostmetricsreceiver/hostmetrics_linux.go b/receiver/hostmetricsreceiver/hostmetrics_linux.go new file mode 100644 index 000000000000..89e2acc66dac --- /dev/null +++ b/receiver/hostmetricsreceiver/hostmetrics_linux.go @@ -0,0 +1,71 @@ +// 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. + +//go:build linux + +package hostmetricsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver" + +import ( + "fmt" + "os" + "path/filepath" +) + +var gopsutilEnvVars = map[string]string{ + "HOST_PROC": "/proc", + "HOST_SYS": "/sys", + "HOST_ETC": "/etc", + "HOST_VAR": "/var", + "HOST_RUN": "/run", + "HOST_DEV": "/dev", +} + +// This exists to validate that different instances of the hostmetricsreceiver do not +// have inconsistent root_path configurations. The root_path is passed down to gopsutil +// through env vars, so it must be consistent across the process. +var globalRootPath string + +func validateRootPath(rootPath string, env environment) error { + if rootPath == "" || rootPath == "/" { + return nil + } + + if globalRootPath != "" && rootPath != globalRootPath { + return fmt.Errorf("inconsistent root_path configuration detected between hostmetricsreceivers: `%s` != `%s`", globalRootPath, rootPath) + } + globalRootPath = rootPath + + if _, err := os.Stat(rootPath); err != nil { + return fmt.Errorf("invalid root_path: %w", err) + } + + return nil +} + +func setGoPsutilEnvVars(rootPath string, env environment) error { + if rootPath == "" || rootPath == "/" { + return nil + } + + for envVarKey, defaultValue := range gopsutilEnvVars { + _, ok := env.Lookup(envVarKey) + if ok { + continue // don't override if existing env var is set + } + if err := env.Set(envVarKey, filepath.Join(rootPath, defaultValue)); err != nil { + return err + } + } + return nil +} diff --git a/receiver/hostmetricsreceiver/hostmetrics_linux_test.go b/receiver/hostmetricsreceiver/hostmetrics_linux_test.go new file mode 100644 index 000000000000..f0d1e1be7364 --- /dev/null +++ b/receiver/hostmetricsreceiver/hostmetrics_linux_test.go @@ -0,0 +1,79 @@ +// 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. + +//go:build linux + +package hostmetricsreceiver + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/service/servicetest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper" +) + +func TestConsistentRootPaths(t *testing.T) { + env := &testEnv{env: map[string]string{"HOST_PROC": "testdata"}} + // use testdata because it's a directory that exists - don't actually use any files in it + assert.Nil(t, testValidate("testdata", env)) + assert.Nil(t, testValidate("", env)) + assert.Nil(t, testValidate("/", env)) +} + +func TestInconsistentRootPaths(t *testing.T) { + globalRootPath = "foo" + err := testValidate("testdata", &testEnv{}) + assert.EqualError(t, err, "inconsistent root_path configuration detected between hostmetricsreceivers: `foo` != `testdata`") +} + +func TestLoadConfigRootPath(t *testing.T) { + t.Setenv("HOST_PROC", "testdata") + factories, _ := componenttest.NopFactories() + factory := NewFactory() + factories.Receivers[typeStr] = factory + cfg, err := servicetest.LoadConfigAndValidate(filepath.Join("testdata", "config-root-path.yaml"), factories) + require.NoError(t, err) + globalRootPath = "" + + r := cfg.Receivers[config.NewComponentID(typeStr)].(*Config) + expectedConfig := factory.CreateDefaultConfig().(*Config) + expectedConfig.RootPath = "testdata" + cpuScraperCfg := (&cpuscraper.Factory{}).CreateDefaultConfig() + cpuScraperCfg.SetRootPath("testdata") + expectedConfig.Scrapers = map[string]internal.Config{cpuscraper.TypeStr: cpuScraperCfg} + + assert.Equal(t, expectedConfig, r) +} + +func TestLoadInvalidConfig_RootPathNotExist(t *testing.T) { + factories, _ := componenttest.NopFactories() + factory := NewFactory() + factories.Receivers[typeStr] = factory + _, err := servicetest.LoadConfigAndValidate(filepath.Join("testdata", "config-bad-root-path.yaml"), factories) + assert.ErrorContains(t, err, "invalid root_path:") + globalRootPath = "" +} + +func testValidate(rootPath string, env environment) error { + err := validateRootPath(rootPath, env) + globalRootPath = "" + return err +} diff --git a/receiver/hostmetricsreceiver/hostmetrics_others.go b/receiver/hostmetricsreceiver/hostmetrics_others.go new file mode 100644 index 000000000000..f88a94b038fd --- /dev/null +++ b/receiver/hostmetricsreceiver/hostmetrics_others.go @@ -0,0 +1,30 @@ +// 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. + +//go:build !linux + +package hostmetricsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver" + +import "fmt" + +func validateRootPath(rootPath string, _ environment) error { + if rootPath == "" { + return nil + } + return fmt.Errorf("root_path is supported on linux only") +} + +func setGoPsutilEnvVars(_ string, _ environment) error { + return nil +} diff --git a/receiver/hostmetricsreceiver/hostmetrics_others_test.go b/receiver/hostmetricsreceiver/hostmetrics_others_test.go new file mode 100644 index 000000000000..b863e3316a71 --- /dev/null +++ b/receiver/hostmetricsreceiver/hostmetrics_others_test.go @@ -0,0 +1,31 @@ +// 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. + +//go:build !linux + +package hostmetricsreceiver + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRootPathNotAllowedOnOS(t *testing.T) { + assert.NotNil(t, validateRootPath("testdata", &testEnv{})) +} + +func TestRootPathUnset(t *testing.T) { + assert.Nil(t, validateRootPath("", &testEnv{})) +} diff --git a/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go b/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go index 3c047d828da8..f4a0aaec96df 100644 --- a/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go +++ b/receiver/hostmetricsreceiver/hostmetrics_receiver_test.go @@ -93,6 +93,22 @@ var factories = map[string]internal.ScraperFactory{ processscraper.TypeStr: &processscraper.Factory{}, } +type testEnv struct { + env map[string]string +} + +var _ environment = (*testEnv)(nil) + +func (e *testEnv) Lookup(k string) (string, bool) { + v, ok := e.env[k] + return v, ok +} + +func (e *testEnv) Set(k, v string) error { + e.env[k] = v + return nil +} + func TestGatherMetrics_EndToEnd(t *testing.T) { scraperFactories = factories @@ -208,6 +224,8 @@ const mockTypeStr = "mock" type mockConfig struct{} +func (m *mockConfig) SetRootPath(_ string) {} + type mockFactory struct{ mock.Mock } type mockScraper struct{ mock.Mock } diff --git a/receiver/hostmetricsreceiver/internal/scraper.go b/receiver/hostmetricsreceiver/internal/scraper.go index 5ef990f50545..c589cff38ae3 100644 --- a/receiver/hostmetricsreceiver/internal/scraper.go +++ b/receiver/hostmetricsreceiver/internal/scraper.go @@ -33,4 +33,13 @@ type ScraperFactory interface { // Config is the configuration of a scraper. type Config interface { + SetRootPath(rootPath string) +} + +type ScraperConfig struct { + RootPath string `mapstructure:"-"` +} + +func (p *ScraperConfig) SetRootPath(rootPath string) { + p.RootPath = rootPath } diff --git a/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go index 5c65395622f7..42c8caeb6df8 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/config.go @@ -15,10 +15,12 @@ package cpuscraper // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/internal/metadata" ) // Config relating to CPU Metric Scraper. type Config struct { Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig } diff --git a/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go index c3a425b6087d..fadf5c4ae029 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/diskscraper/config.go @@ -16,6 +16,7 @@ package diskscraper // import "github.com/open-telemetry/opentelemetry-collector import ( "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/processor/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/diskscraper/internal/metadata" ) @@ -23,6 +24,7 @@ import ( type Config struct { // Metrics allows to customize scraped metrics representation. Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig // Include specifies a filter on the devices that should be included from the generated metrics. // Exclude specifies a filter on the devices that should be excluded from the generated metrics. diff --git a/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go index 60537cfcf1d2..1e6123d83a24 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/config.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/processor/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/internal/metadata" ) @@ -25,6 +26,7 @@ import ( type Config struct { // Metrics allows to customize scraped metrics representation. Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig // IncludeVirtualFS will also capture filesystems such as tmpfs, ramfs // and other filesystem types that do no have an associated physical device. @@ -41,8 +43,10 @@ type Config struct { ExcludeFSTypes FSTypeMatchConfig `mapstructure:"exclude_fs_types"` // IncludeMountPoints specifies a filter on the mount points that should be included in the generated metrics. + // When `root_path` is set, the mount points must be from the host's perspective. IncludeMountPoints MountPointMatchConfig `mapstructure:"include_mount_points"` // ExcludeMountPoints specifies a filter on the mount points that should be excluded from the generated metrics. + // When `root_path` is set, the mount points must be from the host's perspective. ExcludeMountPoints MountPointMatchConfig `mapstructure:"exclude_mount_points"` } diff --git a/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go b/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go index 81720a16eba0..5756f3b04c09 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go +++ b/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper.go @@ -17,6 +17,7 @@ package filesystemscraper // import "github.com/open-telemetry/opentelemetry-col import ( "context" "fmt" + "path/filepath" "time" "github.com/shirou/gopsutil/v3/disk" @@ -90,9 +91,10 @@ func (s *scraper) scrape(_ context.Context) (pmetric.Metrics, error) { if !s.fsFilter.includePartition(partition) { continue } - usage, usageErr := s.usage(partition.Mountpoint) + translatedMountpoint := translateMountpoint(s.config.RootPath, partition.Mountpoint) + usage, usageErr := s.usage(translatedMountpoint) if usageErr != nil { - errors.AddPartial(0, fmt.Errorf("failed to read usage at %s: %w", partition.Mountpoint, usageErr)) + errors.AddPartial(0, fmt.Errorf("failed to read usage at %s: %w", translatedMountpoint, usageErr)) continue } @@ -154,3 +156,8 @@ func (f *fsFilter) includeMountPoint(mountPoint string) bool { return (f.includeMountPointFilter == nil || f.includeMountPointFilter.Matches(mountPoint)) && (f.excludeMountPointFilter == nil || !f.excludeMountPointFilter.Matches(mountPoint)) } + +// translateMountsRootPath translates a mountpoint from the host perspective to the chrooted perspective. +func translateMountpoint(rootPath, mountpoint string) string { + return filepath.Join(rootPath, mountpoint) +} diff --git a/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go b/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go index 8c17bba7b026..5952d54de29f 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go +++ b/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper/filesystem_scraper_test.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "path/filepath" "runtime" "testing" @@ -38,6 +39,7 @@ func TestScrape(t *testing.T) { type testCase struct { name string config Config + rootPath string bootTimeFunc func() (uint64, error) partitionsFunc func(bool) ([]disk.PartitionStat, error) usageFunc func(string) (*disk.UsageStat, error) @@ -169,6 +171,40 @@ func TestScrape(t *testing.T) { }, }, }, + { + name: "RootPath at /hostfs", + config: Config{ + Metrics: metadata.DefaultMetricsSettings(), + }, + rootPath: filepath.Join("/", "hostfs"), + usageFunc: func(s string) (*disk.UsageStat, error) { + if s != filepath.Join("/hostfs", "mount_point_a") { + return nil, errors.New("mountpoint not translated according to RootPath") + } + return &disk.UsageStat{ + Fstype: "fs_type_a", + }, nil + }, + partitionsFunc: func(b bool) ([]disk.PartitionStat, error) { + return []disk.PartitionStat{ + { + Device: "device_a", + Mountpoint: "mount_point_a", + Fstype: "fs_type_a", + }, + }, nil + }, + expectMetrics: true, + expectedDeviceDataPoints: 1, + expectedDeviceAttributes: []map[string]pcommon.Value{ + { + "device": pcommon.NewValueStr("device_a"), + "mountpoint": pcommon.NewValueStr("mount_point_a"), + "type": pcommon.NewValueStr("fs_type_a"), + "mode": pcommon.NewValueStr("unknown"), + }, + }, + }, { name: "Invalid Include Device Filter", config: Config{ @@ -289,7 +325,7 @@ func TestScrape(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() - + test.config.SetRootPath(test.rootPath) scraper, err := newFileSystemScraper(context.Background(), componenttest.NewNopReceiverCreateSettings(), &test.config) if test.newErrRegex != "" { require.Error(t, err) diff --git a/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go index 68b6e8175c7d..172e6c6227c1 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/loadscraper/config.go @@ -15,6 +15,7 @@ package loadscraper // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/loadscraper" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/loadscraper/internal/metadata" ) @@ -24,4 +25,5 @@ type Config struct { CPUAverage bool `mapstructure:"cpu_average"` // Metrics allows to customize scraped metrics representation. Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig } diff --git a/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go index 9749775a1aef..59a057517a8c 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/config.go @@ -15,10 +15,12 @@ package memoryscraper // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/memoryscraper" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/memoryscraper/internal/metadata" ) // Config relating to Memory Metric Scraper. type Config struct { Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig } diff --git a/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go index 4aeb7364b561..9456f2706113 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/networkscraper/config.go @@ -16,12 +16,14 @@ package networkscraper // import "github.com/open-telemetry/opentelemetry-collec import ( "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/processor/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/networkscraper/internal/metadata" ) // Config relating to Network Metric Scraper. type Config struct { Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig // Include specifies a filter on the network interfaces that should be included from the generated metrics. Include MatchConfig `mapstructure:"include"` // Exclude specifies a filter on the network interfaces that should be excluded from the generated metrics. diff --git a/receiver/hostmetricsreceiver/internal/scraper/pagingscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/pagingscraper/config.go index 8edd14535102..55f49aed456f 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/pagingscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/pagingscraper/config.go @@ -15,6 +15,7 @@ package pagingscraper // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/pagingscraper" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/pagingscraper/internal/metadata" ) @@ -22,4 +23,5 @@ import ( type Config struct { // Metrics allows customizing scraped metrics representation. Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig } diff --git a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go index 4c0e67ba2aa5..0711f699cf6c 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/processesscraper/config.go @@ -15,6 +15,7 @@ package processesscraper // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/processesscraper" import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/processesscraper/internal/metadata" ) @@ -22,4 +23,5 @@ import ( type Config struct { // Metrics allows customizing scraped metrics representation. Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig } diff --git a/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go b/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go index 71839841eb59..193cb99d66fa 100644 --- a/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go +++ b/receiver/hostmetricsreceiver/internal/scraper/processscraper/config.go @@ -18,6 +18,7 @@ import ( "time" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/processor/filterset" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/processscraper/internal/metadata" ) @@ -25,6 +26,7 @@ import ( type Config struct { // Metrics allows to customize scraped metrics representation. Metrics metadata.MetricsSettings `mapstructure:"metrics"` + internal.ScraperConfig // Include specifies a filter on the process names that should be included from the generated metrics. // Exclude specifies a filter on the process names that should be excluded from the generated metrics. // If neither `include` or `exclude` are set, process metrics will be generated for all processes. diff --git a/receiver/hostmetricsreceiver/testdata/config-bad-root-path.yaml b/receiver/hostmetricsreceiver/testdata/config-bad-root-path.yaml new file mode 100644 index 000000000000..cfe0107d5b4f --- /dev/null +++ b/receiver/hostmetricsreceiver/testdata/config-bad-root-path.yaml @@ -0,0 +1,18 @@ +receivers: + hostmetrics: + root_path: "does/not/exist" + scrapers: + cpu: + +processors: + nop: + +exporters: + nop: + +service: + pipelines: + metrics: + receivers: [hostmetrics] + processors: [nop] + exporters: [nop] diff --git a/receiver/hostmetricsreceiver/testdata/config-root-path.yaml b/receiver/hostmetricsreceiver/testdata/config-root-path.yaml new file mode 100644 index 000000000000..e2f1e58f53f0 --- /dev/null +++ b/receiver/hostmetricsreceiver/testdata/config-root-path.yaml @@ -0,0 +1,18 @@ +receivers: + hostmetrics: + root_path: "testdata" + scrapers: + cpu: + +processors: + nop: + +exporters: + nop: + +service: + pipelines: + metrics: + receivers: [hostmetrics] + processors: [nop] + exporters: [nop]