diff --git a/cmd/agent/dist/conf.d/disk.d/conf.yaml.default b/cmd/agent/dist/conf.d/disk.d/conf.yaml.default index fea8fc347e21a3..384aced5e62e63 100644 --- a/cmd/agent/dist/conf.d/disk.d/conf.yaml.default +++ b/cmd/agent/dist/conf.d/disk.d/conf.yaml.default @@ -4,6 +4,33 @@ init_config: + ## @param file_system_global_exclude - list of strings - optional + ## Instruct the check to always add these patterns to `file_system_exclude`. + ## + ## WARNING: Overriding these defaults could negatively impact your system or + ## the performance of the check. + # + # file_system_global_exclude: + # - iso9660$ + # - tracefs$ + + ## @param device_global_exclude - list of strings - optional + ## Instruct the check to always add these patterns to `device_exclude`. + ## + ## WARNING: Overriding these defaults could negatively impact your system or + ## the performance of the check. + # + # device_global_exclude: [] + + ## @param mount_point_global_exclude - list of strings - optional + ## Instruct the check to always add these patterns to `mount_point_exclude`. + ## + ## WARNING: Overriding these defaults could negatively impact your system or + ## the performance of the check. + # + # mount_point_global_exclude: + # - (/host)?/proc/sys/fs/binfmt_misc$ + instances: ## @param use_mount - boolean - required @@ -54,6 +81,89 @@ instances: # - : # - : + ## @param file_system_include - list of strings - optional + ## Instruct the check to only collect from matching file systems. + ## + ## Character casing is ignored. For convenience, the regular expressions + ## start matching from the beginning and therefore to match anywhere you + ## must prepend `.*`. For exact matches append `$`. + # + # file_system_include: + # - ext[34]$ + # - ntfs$ + + ## @param file_system_exclude - list of strings - optional + ## Instruct the check to not collect from matching file systems. + ## + ## Character casing is ignored. For convenience, the regular expressions + ## start matching from the beginning and therefore to match anywhere you + ## must prepend `.*`. For exact matches append `$`. + ## + ## Devices from pseudo or memory-based file systems can be excluded by disabling the + ## `include_all_devices` option. + ## + ## When conflicts arise, this will override `file_system_include`. + # + # file_system_exclude: + # - tmpfs$ + # - rootfs$ + # - autofs$ + + ## @param device_include - list of strings - optional + ## Instruct the check to only collect from matching devices. + ## + ## Character casing is ignored on Windows. For convenience, the regular + ## expressions start matching from the beginning and therefore to match + ## anywhere you must prepend `.*`. For exact matches append `$`. + # + # device_include: + # - /dev/sda[1-3] + # - 'C:' + + ## @param device_exclude - list of strings - optional + ## Instruct the check to not collect from matching devices. + ## + ## Character casing is ignored on Windows. For convenience, the regular + ## expressions start matching from the beginning and therefore to match + ## anywhere you must prepend `.*`. For exact matches append `$`. + ## + ## When conflicts arise, this will override `device_include`. + # + # device_exclude: + # - /dev/sde + # - '[FJ]:' + + ## @param mount_point_include - list of strings - optional + ## Instruct the check to only collect from matching mount points. + ## + ## Character casing is ignored on Windows. For convenience, the regular + ## expressions start matching from the beginning and therefore to match + ## anywhere you must prepend `.*`. For exact matches append `$`. + # + # mount_point_include: + # - /dev/sda[1-3] + # - 'C:' + + ## @param mount_point_exclude - list of strings - optional + ## Instruct the check to not collect from matching mount points. + ## + ## Character casing is ignored on Windows. For convenience, the regular + ## expressions start matching from the beginning and therefore to match + ## anywhere you must prepend `.*`. For exact matches append `$`. + # + # mount_point_exclude: + # - /proc/sys/fs/binfmt_misc + # - /dev/sde + # - '[FJ]:' + + ## @param include_all_devices - boolean - optional - default: true + ## Instruct the check to collect from all devices, including non-physical devices. + ## Set this to false to exclude pseudo, memory, duplicate or inaccessible file systems. + ## + ## For more fine-grained control, use the inclusion and exclusion options. + # + # include_all_devices: true + ## @param service_check_rw - boolean - optional ## Instruct the check to notify based on partition state. ## @@ -68,7 +178,27 @@ instances: # # tag_by_filesystem: false - ## @param device_tag_re - list of regex:tags string - optional + ## @param tag_by_label - boolean - optional - default: true + ## Instruct the check to tag all the metrics with disk label if there is one. + ## Works on Linux only. + # + # tag_by_label: true + + ## @param blkid_cache_file - string - optional + ## Instruct the check to read the labels from the blkid cache file instead of `blkid` executable. + ## This parameter is used only if `tag_by_label` is true. It is incompatible with `use_lsblk`. + ## Works on Linux only. + # + # blkid_cache_file: /run/blkid/blkid.tab + + ## @param use_lsblk - boolean - optional - default: false + ## Instruct the check to read the labels from the `lsblk` executable instead of `blkid` executable. + ## This parameter is used only if `tag_by_label` is true. It is incompatible with `blkid_cache_file`. + ## Works on Linux only. + # + # use_lsblk: false + + ## @param device_tag_re - map of regex to tags - optional ## Instruct the check to apply additional tags to matching ## devices (or mount points if `use_mount` is true). ## @@ -80,3 +210,34 @@ instances: # /san/.*: device_type:san # /dev/sda3: role:db,disk_size:large # "c:": volume:boot + + ## @param min_disk_size - number - optional - default: 0 + ## Exclude devices with a total disk size less than a minimum value (in MiB) + # + # min_disk_size: 0 + + ## @param timeout - integer - optional - default: 5 + ## Timeout of the disk query in seconds + # + # timeout: 5 + + ## @param create_mounts - list of mappings - optional + ## On Windows, instruct the check to create one or more network + ## mounts, and have the check collect metrics for the mounted devices. + ## + ## Uses the provided username and password (if provided and necessary) + ## to create an SMB or NFS mount. If `type` is not specified, then + ## the operating system will choose the best available network filesystem + ## based on the other parameters. If `type` is specified, then any type + ## other than `nfs` will default to an SMB file share. + # + # create_mounts: + # - mountpoint: 's:' + # user: auser + # password: somepassword + # host: smbserver + # share: space + # - mountpoint: 'n:' + # host: nfsserver + # share: /mnt/nfs_share + # type: nfs diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index 10b15ba2f674ee..d9d6f6f0702375 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -121,6 +121,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/collector/corechecks/cluster/orchestrator" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/cpu/cpu" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/disk" + "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/diskv2" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/io" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/filehandles" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/memory" @@ -631,7 +632,11 @@ func registerChecks(wlm workloadmeta.Component, tagger tagger.Component, cfg con corecheckLoader.RegisterCheck(kubernetesapiserver.CheckName, kubernetesapiserver.Factory(tagger)) corecheckLoader.RegisterCheck(ksm.CheckName, ksm.Factory()) corecheckLoader.RegisterCheck(helm.CheckName, helm.Factory()) - corecheckLoader.RegisterCheck(disk.CheckName, disk.Factory()) + if cfg.GetBool("use_diskv2_check") { + corecheckLoader.RegisterCheck(disk.CheckName, diskv2.Factory()) + } else { + corecheckLoader.RegisterCheck(disk.CheckName, disk.Factory()) + } corecheckLoader.RegisterCheck(orchestrator.CheckName, orchestrator.Factory(wlm, cfg, tagger)) corecheckLoader.RegisterCheck(winproc.CheckName, winproc.Factory()) } diff --git a/pkg/collector/corechecks/system/disk/diskv2/disk.go b/pkg/collector/corechecks/system/disk/diskv2/disk.go new file mode 100644 index 00000000000000..7e1cc0d4ee343d --- /dev/null +++ b/pkg/collector/corechecks/system/disk/diskv2/disk.go @@ -0,0 +1,704 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +// Package diskv2 provides Disk Check. +package diskv2 + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/DataDog/datadog-agent/comp/core/autodiscovery/integration" + "github.com/DataDog/datadog-agent/pkg/aggregator/sender" + "github.com/DataDog/datadog-agent/pkg/collector/check" + "github.com/DataDog/datadog-agent/pkg/metrics/servicecheck" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/util/option" + yaml "gopkg.in/yaml.v2" + + "regexp" + + core "github.com/DataDog/datadog-agent/pkg/collector/corechecks" + "github.com/benbjohnson/clock" + gopsutil_disk "github.com/shirou/gopsutil/v4/disk" +) + +const ( + // CheckName is the name of the check + CheckName = "disk" + diskMetric = "system.disk.%s" + inodeMetric = "system.fs.inodes.%s" +) + +var ( + // DiskPartitions returns a list of mounted disk partitions using gopsutil. + DiskPartitions = gopsutil_disk.Partitions + // DiskUsage returns disk usage statistics for the given path using gopsutil. + DiskUsage = gopsutil_disk.Usage + // DiskIOCounters returns disk I/O statistics for the specified devices using gopsutil. + DiskIOCounters = gopsutil_disk.IOCounters +) + +// diskInstanceConfig represents an instance configuration. +type diskInitConfig struct { + DeviceGlobalExclude []string `yaml:"device_global_exclude"` + DeviceGlobalBlacklist []string `yaml:"device_global_blacklist"` + FileSystemGlobalExclude []string `yaml:"file_system_global_exclude"` + FileSystemGlobalBlacklist []string `yaml:"file_system_global_blacklist"` + MountPointGlobalExclude []string `yaml:"mount_point_global_exclude"` + MountPointGlobalBlacklist []string `yaml:"mount_point_global_blacklist"` +} + +// mount represents a network mount configuration. +type mount struct { + Host string `yaml:"host"` + Share string `yaml:"share"` + User string `yaml:"user"` + Password string `yaml:"password"` + Type string `yaml:"type"` + MountPoint string `yaml:"mountpoint"` +} + +// diskInstanceConfig represents an instance configuration. +type diskInstanceConfig struct { + UseMount bool `yaml:"use_mount"` + IncludeAllDevices bool `yaml:"include_all_devices"` + AllPartitions bool `yaml:"all_partitions"` + MinDiskSize uint64 `yaml:"min_disk_size"` + TagByFilesystem bool `yaml:"tag_by_filesystem"` + TagByLabel bool `yaml:"tag_by_label"` + UseLsblk bool `yaml:"use_lsblk"` + BlkidCacheFile string `yaml:"blkid_cache_file"` + ServiceCheckRw bool `yaml:"service_check_rw"` + CreateMounts []mount `yaml:"create_mounts"` + DeviceInclude []string `yaml:"device_include"` + DeviceWhitelist []string `yaml:"device_whitelist"` + DeviceExclude []string `yaml:"device_exclude"` + DeviceBlacklist []string `yaml:"device_blacklist"` + ExcludedDisks []string `yaml:"excluded_disks"` + ExcludedDiskRe string `yaml:"excluded_disk_re"` + FileSystemInclude []string `yaml:"file_system_include"` + FileSystemWhitelist []string `yaml:"file_system_whitelist"` + FileSystemExclude []string `yaml:"file_system_exclude"` + FileSystemBlacklist []string `yaml:"file_system_blacklist"` + ExcludedFileSystems []string `yaml:"excluded_filesystems"` + MountPointInclude []string `yaml:"mount_point_include"` + MountPointWhitelist []string `yaml:"mount_point_whitelist"` + MountPointExclude []string `yaml:"mount_point_exclude"` + MountPointBlacklist []string `yaml:"mount_point_blacklist"` + ExcludedMountPointRe string `yaml:"excluded_mountpoint_re"` + DeviceTagRe map[string]string `yaml:"device_tag_re"` + LowercaseDeviceTag bool `yaml:"lowercase_device_tag"` + Timeout uint16 `yaml:"timeout"` +} + +func sliceMatchesExpression(slice []regexp.Regexp, expression string) bool { + for _, regexp := range slice { + if regexp.MatchString(expression) { + return true + } + } + return false +} + +func compileRegExp(expr string, ignoreCase bool) (*regexp.Regexp, error) { + if ignoreCase { + expr = fmt.Sprintf("(?i)%s", expr) + } + re, err := regexp.Compile(expr) + if err != nil { + log.Warnf("`%s` is not a valid regular expression and will be ignored", expr) + } + return re, err +} + +// Check represents the Disk check that will be periodically executed via the Run() function +type Check struct { + core.CheckBase + clock clock.Clock + initConfig diskInitConfig + instanceConfig diskInstanceConfig + includedDevices []regexp.Regexp + excludedDevices []regexp.Regexp + includedFilesystems []regexp.Regexp + excludedFilesystems []regexp.Regexp + includedMountpoints []regexp.Regexp + excludedMountpoints []regexp.Regexp + deviceTagRe map[*regexp.Regexp][]string + deviceLabels map[string]string +} + +// Run executes the check +func (c *Check) Run() error { + sender, err := c.GetSender() + if err != nil { + return err + } + if c.instanceConfig.TagByLabel { + err = c.fetchAllDeviceLabels() + if err != nil { + log.Debugf("Unable to fetch device labels: %s", err) + } + } + err = c.collectPartitionMetrics(sender) + if err != nil { + return err + } + err = c.collectDiskMetrics(sender) + if err != nil { + return err + } + sender.Commit() + + return nil +} + +// Configure parses the check configuration and init the check +func (c *Check) Configure(senderManager sender.SenderManager, _ uint64, data integration.Data, initConfig integration.Data, source string) error { + err := c.CommonConfigure(senderManager, initConfig, data, source) + if err != nil { + return err + } + return c.configureDiskCheck(data, initConfig) +} + +func (c *Check) configureDiskCheck(data integration.Data, initConfig integration.Data) error { + err := c.checkDeprecatedConfig(data, initConfig) + if err != nil { + return err + } + err = yaml.Unmarshal([]byte(initConfig), &c.initConfig) + if err != nil { + return err + } + err = yaml.Unmarshal([]byte(data), &c.instanceConfig) + if err != nil { + return err + } + err = c.configureExcludeDevice() + if err != nil { + return err + } + err = c.configureIncludeDevice() + if err != nil { + return err + } + err = c.configureExcludeFileSystem() + if err != nil { + return err + } + err = c.configureIncludeFileSystem() + if err != nil { + return err + } + err = c.configureExcludeMountPoint() + if err != nil { + return err + } + err = c.configureIncludeMountPoint() + if err != nil { + return err + } + for reString, tags := range c.instanceConfig.DeviceTagRe { + if re, err := compileRegExp(reString, defaultIgnoreCase()); err == nil { + splitTags := strings.Split(tags, ",") + for i, tag := range splitTags { + splitTags[i] = strings.TrimSpace(tag) + } + c.deviceTagRe[re] = splitTags + } else { + return err + } + } + if c.instanceConfig.UseLsblk && c.instanceConfig.BlkidCacheFile != "" { + return errors.New("only one of 'use_lsblk' and 'blkid_cache_file' can be set at the same time") + } + c.configureCreateMounts() + return nil +} + +func (c *Check) checkDeprecatedConfig(data integration.Data, initConfig integration.Data) error { + unmarshalledInstanceConfig := make(map[interface{}]interface{}) + err := yaml.Unmarshal([]byte(data), &unmarshalledInstanceConfig) + if err != nil { + return err + } + unmarshalledInitConfig := make(map[interface{}]interface{}) + err = yaml.Unmarshal([]byte(initConfig), &unmarshalledInitConfig) + if err != nil { + return err + } + + deprecationsInitConf := map[string]string{ + "file_system_global_blacklist": "file_system_global_exclude", + "device_global_blacklist": "device_global_exclude", + "mount_point_global_blacklist": "mount_point_global_exclude", + } + for oldKey, newKey := range deprecationsInitConf { + if _, exists := unmarshalledInitConfig[oldKey]; exists { + log.Warnf("`%s` is deprecated and will be removed in a future release. Please use `%s` instead.", oldKey, newKey) + } + } + + deprecationsInstanceConf := map[string]string{ + "file_system_whitelist": "file_system_include", + "file_system_blacklist": "file_system_exclude", + "device_whitelist": "device_include", + "device_blacklist": "device_exclude", + "mount_point_whitelist": "mount_point_include", + "mount_point_blacklist": "mount_point_exclude", + "excluded_filesystems": "file_system_exclude", + "excluded_disks": "device_exclude", + "excluded_disk_re": "device_exclude", + "excluded_mountpoint_re": "mount_point_exclude", + } + for oldKey, newKey := range deprecationsInstanceConf { + if _, exists := unmarshalledInstanceConfig[oldKey]; exists { + log.Warnf("`%s` is deprecated and will be removed in a future release. Please use `%s` instead.", oldKey, newKey) + } + } + return nil +} + +func processRegExpSlices(slices [][]string, ignoreCase bool) ([]regexp.Regexp, error) { + regExpList := []regexp.Regexp{} + for _, slice := range slices { + for _, val := range slice { + if re, err := compileRegExp(val, ignoreCase); err == nil { + regExpList = append(regExpList, *re) + } else { + return regExpList, err + } + } + } + return regExpList, nil +} + +func (c *Check) configureExcludeDevice() error { + c.excludedDevices = []regexp.Regexp{} + if regExpList, err := processRegExpSlices([][]string{c.initConfig.DeviceGlobalExclude, c.initConfig.DeviceGlobalBlacklist, c.instanceConfig.DeviceExclude, c.instanceConfig.DeviceBlacklist, c.instanceConfig.ExcludedDisks}, defaultIgnoreCase()); err == nil { + c.excludedDevices = append(c.excludedDevices, regExpList...) + } else { + return err + } + if c.instanceConfig.ExcludedDiskRe != "" { + if re, err := compileRegExp(c.instanceConfig.ExcludedDiskRe, defaultIgnoreCase()); err == nil { + c.excludedDevices = append(c.excludedDevices, *re) + } else { + return err + } + } + return nil +} + +func (c *Check) configureIncludeDevice() error { + c.includedDevices = []regexp.Regexp{} + if regExpList, err := processRegExpSlices([][]string{c.instanceConfig.DeviceInclude, c.instanceConfig.DeviceWhitelist}, defaultIgnoreCase()); err == nil { + c.includedDevices = append(c.includedDevices, regExpList...) + } else { + return err + } + return nil +} + +func (c *Check) configureExcludeFileSystem() error { + c.excludedFilesystems = []regexp.Regexp{} + if regExpList, err := processRegExpSlices([][]string{c.initConfig.FileSystemGlobalExclude, c.initConfig.FileSystemGlobalBlacklist}, true); err == nil { + c.excludedFilesystems = append(c.excludedFilesystems, regExpList...) + } else { + return err + } + if len(c.excludedFilesystems) == 0 { + // Use default values if neither key was found + for _, val := range []string{"iso9660$", "tracefs$"} { + if re, err := compileRegExp(val, true); err == nil { + c.excludedFilesystems = append(c.excludedFilesystems, *re) + } else { + return err + } + } + } + if regExpList, err := processRegExpSlices([][]string{c.instanceConfig.FileSystemExclude, c.instanceConfig.FileSystemBlacklist, c.instanceConfig.ExcludedFileSystems}, true); err == nil { + c.excludedFilesystems = append(c.excludedFilesystems, regExpList...) + } else { + return err + } + return nil +} + +func (c *Check) configureIncludeFileSystem() error { + c.includedFilesystems = []regexp.Regexp{} + if regExpList, err := processRegExpSlices([][]string{c.instanceConfig.FileSystemInclude, c.instanceConfig.FileSystemWhitelist}, true); err == nil { + c.includedFilesystems = append(c.includedFilesystems, regExpList...) + } else { + return err + } + return nil +} + +func (c *Check) configureExcludeMountPoint() error { + c.excludedMountpoints = []regexp.Regexp{} + if regExpList, err := processRegExpSlices([][]string{c.initConfig.MountPointGlobalExclude, c.initConfig.MountPointGlobalBlacklist}, true); err == nil { + c.excludedMountpoints = append(c.excludedMountpoints, regExpList...) + } else { + return err + } + if len(c.excludedMountpoints) == 0 { + // https://github.com/DataDog/datadog-agent/issues/1961 + // https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1049 + for _, val := range []string{"(/host)?/proc/sys/fs/binfmt_misc$"} { + if re, err := compileRegExp(val, defaultIgnoreCase()); err == nil { + c.excludedMountpoints = append(c.excludedMountpoints, *re) + } else { + return err + } + } + } + if regExpList, err := processRegExpSlices([][]string{c.instanceConfig.MountPointExclude, c.instanceConfig.MountPointBlacklist}, true); err == nil { + c.excludedMountpoints = append(c.excludedMountpoints, regExpList...) + } else { + return err + } + if c.instanceConfig.ExcludedMountPointRe != "" { + if re, err := compileRegExp(c.instanceConfig.ExcludedMountPointRe, defaultIgnoreCase()); err == nil { + c.excludedMountpoints = append(c.excludedMountpoints, *re) + } else { + return err + } + } + return nil +} + +func (c *Check) configureIncludeMountPoint() error { + c.includedMountpoints = []regexp.Regexp{} + if regExpList, err := processRegExpSlices([][]string{c.instanceConfig.MountPointInclude, c.instanceConfig.MountPointWhitelist}, true); err == nil { + c.includedMountpoints = append(c.includedMountpoints, regExpList...) + } else { + return err + } + return nil +} + +func (c *Check) collectPartitionMetrics(sender sender.Sender) error { + partitions, err := DiskPartitions(c.instanceConfig.IncludeAllDevices) + if err != nil { + log.Warnf("Unable to get disk partitions: %s", err) + return err + } + log.Debugf("partitions %s", partitions) + for _, partition := range partitions { + log.Debugf("Checking partition: [device: %s] [mountpoint: %s] [fstype: %s]", partition.Device, partition.Mountpoint, partition.Fstype) + if c.excludePartition(partition) { + log.Debugf("Excluding partition: [device: %s] [mountpoint: %s] [fstype: %s]", partition.Device, partition.Mountpoint, partition.Fstype) + continue + } + if usage := c.getPartitionUsage(partition); usage != nil { + tags := c.getPartitionTags(partition) + c.sendPartitionMetrics(sender, usage, tags) + + if c.instanceConfig.ServiceCheckRw { + checkStatus := servicecheck.ServiceCheckUnknown + for _, opt := range partition.Opts { + if opt == "rw" { + checkStatus = servicecheck.ServiceCheckOK + break + } else if opt == "ro" { + checkStatus = servicecheck.ServiceCheckCritical + break + } + } + sender.ServiceCheck("disk.read_write", checkStatus, "", tags, "") + } + } + } + return nil +} + +func (c *Check) collectDiskMetrics(sender sender.Sender) error { + iomap, err := DiskIOCounters() + if err != nil { + log.Warnf("Unable to get disk iocounters: %s", err) + return err + } + for deviceName, ioCounters := range iomap { + log.Debugf("Checking iocounters: [device: %s] [ioCounters: %s]", deviceName, ioCounters) + tags := c.getDeviceNameTags(deviceName) + c.sendDiskMetrics(sender, ioCounters, tags) + } + + return nil +} + +func (c *Check) sendPartitionMetrics(sender sender.Sender, usage *gopsutil_disk.UsageStat, tags []string) { + // Disk metrics + // For legacy reasons, the standard unit it kB + sender.Gauge(fmt.Sprintf(diskMetric, "total"), float64(usage.Total)/1024, "", tags) + sender.Gauge(fmt.Sprintf(diskMetric, "used"), float64(usage.Used)/1024, "", tags) + sender.Gauge(fmt.Sprintf(diskMetric, "free"), float64(usage.Free)/1024, "", tags) + sender.Gauge(fmt.Sprintf(diskMetric, "utilized"), usage.UsedPercent, "", tags) + // FIXME(8.x): use percent, a lot more logical than in_use + sender.Gauge(fmt.Sprintf(diskMetric, "in_use"), usage.UsedPercent/100, "", tags) + + c.sendInodesMetrics(sender, usage, tags) +} + +func (c *Check) sendDiskMetrics(sender sender.Sender, ioCounter gopsutil_disk.IOCountersStat, tags []string) { + sender.MonotonicCount(fmt.Sprintf(diskMetric, "read_time"), float64(ioCounter.ReadTime), "", tags) + sender.MonotonicCount(fmt.Sprintf(diskMetric, "write_time"), float64(ioCounter.WriteTime), "", tags) + // FIXME(8.x): These older metrics are kept here for backwards compatibility, but they are wrong: the value is not a percentage + // See: https://github.com/DataDog/integrations-core/pull/7323#issuecomment-756427024 + sender.Rate(fmt.Sprintf(diskMetric, "read_time_pct"), float64(ioCounter.ReadTime)*100/1000, "", tags) + sender.Rate(fmt.Sprintf(diskMetric, "write_time_pct"), float64(ioCounter.WriteTime)*100/1000, "", tags) +} + +func (c *Check) getDiskUsageWithTimeout(mountpoint string) (*gopsutil_disk.UsageStat, error) { + type usageResult struct { + usage *gopsutil_disk.UsageStat + err error + } + resultCh := make(chan usageResult, 1) + // Start the disk usage call in a separate goroutine. + go func() { + // UsageWithContext in gopsutil ignores the context for now (PR opened: https://github.com/shirou/gopsutil/pull/1837) + usage, err := DiskUsage(mountpoint) + resultCh <- usageResult{usage, err} + }() + // Use select to wait for either the disk usage result or a timeout. + timeout := time.Duration(c.instanceConfig.Timeout) * time.Second + select { + case result := <-resultCh: + return result.usage, result.err + case <-c.clock.After(timeout): + return nil, fmt.Errorf("disk usage call timed out after %s", timeout) + } +} + +func (c *Check) getPartitionUsage(partition gopsutil_disk.PartitionStat) *gopsutil_disk.UsageStat { + usage, err := c.getDiskUsageWithTimeout(partition.Mountpoint) + if err != nil { + log.Warnf("Unable to get disk metrics for %s: %s. You can exclude this mountpoint in the settings if it is invalid.", partition.Mountpoint, err) + return nil + } + log.Debugf("usage %s", usage) + // Exclude disks with total disk size 0 + if usage.Total == 0 { + log.Debugf("Excluding partition: [device: %s] [mountpoint: %s] [fstype: %s] with total disk size %d bytes", partition.Device, partition.Mountpoint, partition.Fstype, usage.Total) + return nil + } + // Exclude disks with total disk size smaller than 'min_disk_size' (which is configured in MiB) + minDiskSizeInBytes := c.instanceConfig.MinDiskSize * 1024 * 1024 + if usage.Total < minDiskSizeInBytes { + log.Infof("Excluding partition: [device: %s] [mountpoint: %s] [fstype: %s] with total disk size %d bytes", partition.Device, partition.Mountpoint, partition.Fstype, usage.Total) + return nil + } + log.Debugf("Passed partition: [device: %s] [mountpoint: %s] [fstype: %s]", partition.Device, partition.Mountpoint, partition.Fstype) + return usage +} + +func (c *Check) getPartitionTags(partition gopsutil_disk.PartitionStat) []string { + tags := []string{} + if c.instanceConfig.TagByFilesystem { + tags = append(tags, partition.Fstype, fmt.Sprintf("filesystem:%s", partition.Fstype)) + } + var deviceName string + if c.instanceConfig.UseMount { + deviceName = partition.Mountpoint + } else { + deviceName = partition.Device + } + if c.instanceConfig.LowercaseDeviceTag { + tags = append(tags, fmt.Sprintf("device:%s", strings.ToLower(deviceName))) + } else { + tags = append(tags, fmt.Sprintf("device:%s", deviceName)) + } + tags = append(tags, fmt.Sprintf("device_name:%s", baseDeviceName(partition.Device))) + tags = append(tags, c.getDeviceTags(deviceName)...) + label, ok := c.deviceLabels[partition.Device] + if ok { + tags = append(tags, fmt.Sprintf("label:%s", label), fmt.Sprintf("device_label:%s", label)) + } + return tags +} + +func (c *Check) getDeviceNameTags(deviceName string) []string { + tags := []string{} + if c.instanceConfig.LowercaseDeviceTag { + tags = append(tags, fmt.Sprintf("device:%s", strings.ToLower(deviceName))) + } else { + tags = append(tags, fmt.Sprintf("device:%s", deviceName)) + } + tags = append(tags, fmt.Sprintf("device_name:%s", baseDeviceName(deviceName))) + tags = append(tags, c.getDeviceTags(deviceName)...) + label, ok := c.deviceLabels[deviceName] + if ok { + tags = append(tags, fmt.Sprintf("label:%s", label), fmt.Sprintf("device_label:%s", label)) + } + return tags +} + +func (c *Check) excludePartition(partition gopsutil_disk.PartitionStat) bool { + if c.excludePartitionInPlatform(partition) { + return true + } + device := partition.Device + if device == "" || device == "none" { + device = "" + if !c.instanceConfig.AllPartitions { + return true + } + } + // Hack for NFS secure mounts + // Secure mounts might look like this: '/mypath (deleted)', we should + // ignore all the bits not part of the mountpoint name. Take also into + // account a space might be in the mountpoint. + mountPoint := partition.Mountpoint + index := strings.LastIndex(mountPoint, " ") + // If a space is found, update mountPoint to be everything before the last space. + if index != -1 { + mountPoint = mountPoint[:index] + } + excludePartition := c.excludeDevice(device) || c.excludeFileSystem(partition.Fstype) || c.excludeMountPoint(mountPoint) + if excludePartition { + return true + } + includePartition := c.includeDevice(device) && c.includeFileSystem(partition.Fstype) && c.includeMountPoint(mountPoint) + return !includePartition +} + +func (c *Check) excludeDevice(device string) bool { + if device == "" || len(c.excludedDevices) == 0 { + return false + } + return sliceMatchesExpression(c.excludedDevices, device) +} + +func (c *Check) includeDevice(device string) bool { + if device == "" || len(c.includedDevices) == 0 { + return true + } + return sliceMatchesExpression(c.includedDevices, device) +} + +func (c *Check) excludeFileSystem(fileSystem string) bool { + if len(c.excludedFilesystems) == 0 { + return false + } + return sliceMatchesExpression(c.excludedFilesystems, fileSystem) +} + +func (c *Check) includeFileSystem(fileSystem string) bool { + if len(c.includedFilesystems) == 0 { + return true + } + return sliceMatchesExpression(c.includedFilesystems, fileSystem) +} + +func (c *Check) excludeMountPoint(mountPoint string) bool { + if len(c.excludedMountpoints) == 0 { + return false + } + return sliceMatchesExpression(c.excludedMountpoints, mountPoint) +} + +func (c *Check) includeMountPoint(mountPoint string) bool { + if len(c.includedMountpoints) == 0 { + return true + } + return sliceMatchesExpression(c.includedMountpoints, mountPoint) +} + +func (c *Check) getDeviceTags(device string) []string { + tags := []string{} + log.Debugf("Getting device tags for device '%s'", device) + for re, deviceTags := range c.deviceTagRe { + if re.MatchString(device) { + tags = append(tags, deviceTags...) + } + } + log.Debugf("getDeviceTags: %s", tags) + return tags +} + +func (c *Check) fetchAllDeviceLabels() error { + log.Debugf("Fetching all device labels") + if c.instanceConfig.UseLsblk { + return c.fetchAllDeviceLabelsFromLsblk() + } else if c.instanceConfig.BlkidCacheFile != "" { + return c.fetchAllDeviceLabelsFromBlkidCache() + } + return c.fetchAllDeviceLabelsFromBlkid() +} + +// Factory creates a new check factory +func Factory() option.Option[func() check.Check] { + return option.New(newCheck) +} + +// FactoryWithClock creates a new check factory with the clock dependency injection +func FactoryWithClock(clock clock.Clock) option.Option[func() check.Check] { + return option.New(func() check.Check { + return newCheckWithClock(clock) + }) +} + +func newCheck() check.Check { + return newCheckWithClock(clock.New()) +} + +func newCheckWithClock(clock clock.Clock) check.Check { + return &Check{ + CheckBase: core.NewCheckBase(CheckName), + clock: clock, + initConfig: diskInitConfig{ + DeviceGlobalExclude: []string{}, + DeviceGlobalBlacklist: []string{}, + FileSystemGlobalExclude: []string{}, + FileSystemGlobalBlacklist: []string{}, + MountPointGlobalExclude: []string{}, + MountPointGlobalBlacklist: []string{}, + }, + instanceConfig: diskInstanceConfig{ + UseMount: false, + IncludeAllDevices: true, + AllPartitions: false, + MinDiskSize: 0, + TagByFilesystem: false, + TagByLabel: true, + UseLsblk: false, + BlkidCacheFile: "", + ServiceCheckRw: false, + CreateMounts: []mount{}, + DeviceInclude: []string{}, + DeviceWhitelist: []string{}, + DeviceExclude: []string{}, + DeviceBlacklist: []string{}, + ExcludedDisks: []string{}, + ExcludedDiskRe: "", + FileSystemInclude: []string{}, + FileSystemWhitelist: []string{}, + FileSystemExclude: []string{}, + FileSystemBlacklist: []string{}, + ExcludedFileSystems: []string{}, + MountPointInclude: []string{}, + MountPointWhitelist: []string{}, + MountPointExclude: []string{}, + MountPointBlacklist: []string{}, + ExcludedMountPointRe: "", + DeviceTagRe: make(map[string]string), + LowercaseDeviceTag: false, + Timeout: 5, + }, + includedDevices: []regexp.Regexp{}, + excludedDevices: []regexp.Regexp{}, + includedFilesystems: []regexp.Regexp{}, + excludedFilesystems: []regexp.Regexp{}, + includedMountpoints: []regexp.Regexp{}, + excludedMountpoints: []regexp.Regexp{}, + deviceTagRe: make(map[*regexp.Regexp][]string), + deviceLabels: make(map[string]string), + } +} diff --git a/pkg/collector/corechecks/system/disk/diskv2/disk_nix.go b/pkg/collector/corechecks/system/disk/diskv2/disk_nix.go new file mode 100644 index 00000000000000..c93969181c1937 --- /dev/null +++ b/pkg/collector/corechecks/system/disk/diskv2/disk_nix.go @@ -0,0 +1,187 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build !windows + +package diskv2 + +import ( + "encoding/xml" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/DataDog/datadog-agent/pkg/aggregator/sender" + "github.com/DataDog/datadog-agent/pkg/util/log" + gopsutil_disk "github.com/shirou/gopsutil/v4/disk" +) + +func defaultIgnoreCase() bool { + return false +} + +func baseDeviceName(device string) string { + return filepath.Base(device) +} + +func (c *Check) configureCreateMounts() { +} + +func (c *Check) excludePartitionInPlatform(_ gopsutil_disk.PartitionStat) bool { + return false +} + +// LsblkCommand specifies the command used to retrieve block device information. +var LsblkCommand = func() (string, error) { + cmd := exec.Command("lsblk", "--noheadings", "--raw", "--output=NAME,LABEL") + output, err := cmd.Output() + if err != nil { + return "", err + } + return string(output), nil +} +var labelRegex = regexp.MustCompile(`(?i)LABEL="([^"]+)"`) + +func (c *Check) fetchAllDeviceLabelsFromLsblk() error { + log.Debugf("Fetching all device labels from lsblk") + rawOutput, err := LsblkCommand() + if err != nil { + return err + } + log.Debugf("lsblk output: %s", rawOutput) + lines := strings.Split(strings.TrimSpace(rawOutput), "\n") + c.deviceLabels = make(map[string]string) + for _, line := range lines { + line = strings.TrimSpace(line) + log.Debugf("processing line: '%s'", line) + if line == "" { + log.Debugf("skipping empty line") + continue + } + // Typically line looks like: + // sda1 MY_LABEL + device, label, ok := strings.Cut(line, " ") + if !ok { + log.Debugf("skipping malformed line: '%s'", line) + continue + } + if len(label) == 0 { + log.Debugf("skipping empty label: '%s'", line) + continue + } + device = "/dev/" + device + c.deviceLabels[device] = label + } + return nil +} + +// Device represents a device entry in an XML structure. +type device struct { + XMLName xml.Name `xml:"device"` + Label string `xml:"LABEL,attr"` + Text string `xml:",chardata"` +} + +// BlkidCacheCommand specifies the command used to query block device UUIDs and labels. +var BlkidCacheCommand = func(blkidCacheFile string) (string, error) { + file, err := os.Open(blkidCacheFile) + if err != nil { + return "", err + } + defer file.Close() + data, err := io.ReadAll(file) + if err != nil { + return "", err + } + return string(data), nil +} + +func (c *Check) fetchAllDeviceLabelsFromBlkidCache() error { + log.Debugf("Fetching all device labels from blkid cache") + rawOutput, err := BlkidCacheCommand(c.instanceConfig.BlkidCacheFile) + if err != nil { + return err + } + log.Debugf("blkid cache output: %s", rawOutput) + lines := strings.Split(strings.TrimSpace(rawOutput), "\n") + c.deviceLabels = make(map[string]string) + for _, line := range lines { + line = strings.TrimSpace(line) + log.Debugf("processing line: '%s'", line) + if line == "" { + log.Debugf("skipping empty line") + continue + } + var device device + err := xml.Unmarshal([]byte(line), &device) + if err != nil { + log.Debugf("Failed to parse line %s because of %v - skipping the line (some labels might be missing)\n", line, err) + continue + } + if device.Label != "" && device.Text != "" { + c.deviceLabels[device.Text] = device.Label + } + } + return nil +} + +// BlkidCommand specifies the command used to retrieve block device attributes. +var BlkidCommand = func() (string, error) { + cmd := exec.Command("blkid") + output, err := cmd.Output() + if err != nil { + return "", err + } + return string(output), nil +} + +func (c *Check) fetchAllDeviceLabelsFromBlkid() error { + log.Debugf("Fetching all device labels from blkid") + rawOutput, err := BlkidCommand() + if err != nil { + return err + } + lines := strings.Split(strings.TrimSpace(rawOutput), "\n") + c.deviceLabels = make(map[string]string) + for _, line := range lines { + line = strings.TrimSpace(line) + log.Debugf("processing line: '%s'", line) + if line == "" { + log.Debugf("skipping empty line") + continue + } + // Typically line looks like: + // /dev/sda1: UUID="..." TYPE="ext4" LABEL="root" + device, details, ok := strings.Cut(line, ":") + if !ok { + log.Debugf("skipping malformed line: '%s'", line) + continue + } + device = strings.TrimSpace(device) // e.g. "/dev/sda1" + details = strings.TrimSpace(details) // e.g. `UUID="..." TYPE="ext4" LABEL="root"` + match := labelRegex.FindStringSubmatch(details) + if len(match) == 2 { + // match[1] is everything captured by ([^"]+) + c.deviceLabels[device] = match[1] + } + } + return nil +} + +func (c *Check) sendInodesMetrics(sender sender.Sender, usage *gopsutil_disk.UsageStat, tags []string) { + if usage.InodesTotal != 0 { + // Inodes metrics + sender.Gauge(fmt.Sprintf(inodeMetric, "total"), float64(usage.InodesTotal), "", tags) + sender.Gauge(fmt.Sprintf(inodeMetric, "used"), float64(usage.InodesUsed), "", tags) + sender.Gauge(fmt.Sprintf(inodeMetric, "free"), float64(usage.InodesFree), "", tags) + sender.Gauge(fmt.Sprintf(inodeMetric, "utilized"), usage.InodesUsedPercent, "", tags) + // FIXME(8.x): use percent, a lot more logical than in_use + sender.Gauge(fmt.Sprintf(inodeMetric, "in_use"), usage.InodesUsedPercent/100, "", tags) + } +} diff --git a/pkg/collector/corechecks/system/disk/diskv2/disk_nix_test.go b/pkg/collector/corechecks/system/disk/diskv2/disk_nix_test.go new file mode 100644 index 00000000000000..462b25f0bc890f --- /dev/null +++ b/pkg/collector/corechecks/system/disk/diskv2/disk_nix_test.go @@ -0,0 +1,1064 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build !windows + +package diskv2_test + +import ( + "errors" + "testing" + + "github.com/DataDog/datadog-agent/comp/core/autodiscovery/integration" + "github.com/DataDog/datadog-agent/pkg/aggregator/mocksender" + "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/diskv2" + "github.com/DataDog/datadog-agent/pkg/metrics/servicecheck" + gopsutil_disk "github.com/shirou/gopsutil/v4/disk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const LsblkData = string(` +sda1 MYLABEL1 +sda2 MYLABEL2 + +sda3 +`) +const blkidCacheData = string(` +/dev/sda1 +/dev/sda2 +/dev/sdb1 +/dev/sdb2 +`) +const blkidData = string(` +/dev/sda1: UUID="abc-123" LABEL="MYLABEL1" +/dev/sda2: UUID=\"def-456\" LABEL="MYLABEL2" +/dev/sda3: UUID=\"def-789\" +/dev/sda6: UUID="abc-321" LABEL= + +/dev/sda4: +/dev/sda5 +`) + +func setupPlatformMocks() { + diskv2.LsblkCommand = func() (string, error) { + return LsblkData, nil + } + diskv2.BlkidCacheCommand = func(_blkidCacheFile string) (string, error) { + return blkidCacheData, nil + } + diskv2.BlkidCommand = func() (string, error) { + return blkidData, nil + } +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenAllUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithLowercaseDeviceTagConfigured_WhenCheckRuns_ThenLowercaseDevicesAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "/dev/SDA1", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskIOCounters = func(...string) (map[string]gopsutil_disk.IOCountersStat, error) { + return map[string]gopsutil_disk.IOCountersStat{ + "/dev/SDA1": { + Name: "sda1", + ReadCount: 100, + WriteCount: 200, + ReadBytes: 1048576, + WriteBytes: 2097152, + ReadTime: 300, + WriteTime: 450, + }, + "/dev/SDA2": { + Name: "sda2", + ReadCount: 50, + WriteCount: 75, + ReadBytes: 524288, + WriteBytes: 1048576, + ReadTime: 500, + WriteTime: 150, + }, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("lowercase_device_tag: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:SDA1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:SDA1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:SDA1"}) + m.AssertMetricTaggedWith(t, "MonotonicCount", "system.disk.read_time", []string{"device:/dev/sda1", "device_name:SDA1"}) + m.AssertMetricTaggedWith(t, "MonotonicCount", "system.disk.write_time", []string{"device:/dev/sda1", "device_name:SDA1"}) + m.AssertMetricTaggedWith(t, "Rate", "system.disk.read_time_pct", []string{"device:/dev/sda1", "device_name:SDA1"}) + m.AssertMetricTaggedWith(t, "Rate", "system.disk.write_time_pct", []string{"device:/dev/sda1", "device_name:SDA1"}) +} + +func TestGivenADiskCheckWithIncludeAllDevicesTrueConfigured_WhenCheckRuns_ThenAllUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("include_all_devices: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithIncludeAllDevicesFalseConfigured_WhenCheckRuns_ThenOnlyPhysicalDevicesUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("include_all_devices: false")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRunsAndPartitionsSystemReturnsEmptyDevice_ThenNoUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "", + Mountpoint: "/", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{"device:", "device_name:."}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{"device:", "device_name:."}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{"device:", "device_name:."}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) +} + +func TestGivenADiskCheckWithAllPartitionsFalseConfigured_WhenCheckRunsAndPartitionsSystemReturnsEmptyDevice_ThenNoUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "", + Mountpoint: "/", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("all_partitions: false")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:", "device_name:."}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:", "device_name:."}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:", "device_name:."}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) +} + +func TestGivenADiskCheckWithAllPartitionsTrueConfigured_WhenCheckRunsAndPartitionsSystemReturnsEmptyDevice_ThenUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "", + Mountpoint: "/", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("all_partitions: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:", "device_name:."}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:", "device_name:."}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:", "device_name:."}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) +} + +func TestGivenADiskCheckWithDeviceIncludeConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseDevicesAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_include: + - /dev/sda.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceWhiteListConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseDevicesAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_whitelist: + - /dev/sda.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeNotConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithIso9660FileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "cdrom", + Mountpoint: "/", + Fstype: "iso9660", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:cdrom", "device_name:cdrom"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:cdrom", "device_name:cdrom"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:cdrom", "device_name:cdrom"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeNotConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithTracefsFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "trace", + Mountpoint: "/", + Fstype: "tracefs", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:trace", "device_name:trace"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:trace", "device_name:trace"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:trace", "device_name:trace"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +file_system_global_exclude: + - tmp.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithFileSystemGlobalBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +file_system_global_blacklist: + - tmpfs +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithExcludedFileSystemsConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_filesystems: + - tmp.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithFileSystemExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_exclude: + - tmp.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithFileSystemBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_blacklist: + - tmp.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithFileSystemIncludeConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseFileSystemsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_include: + - ext.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithFileSystemWhiteListConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseFileSystemsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_whitelist: + - ext.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceTagReConfigured_WhenCheckRuns_ThenUsageMetricsAreReportedWithTheseTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_tag_re: + /dev/sda.*: role:primary + tmp.*: role:tmp +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda2", "device_name:sda2", "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda2", "device_name:sda2", "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda2", "device_name:sda2", "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:tmpfs", "device_name:tmpfs", "role:tmp"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:tmpfs", "device_name:tmpfs", "role:tmp"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:tmpfs", "device_name:tmpfs", "role:tmp"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:shm", "device_name:shm"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:shm", "device_name:shm"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenAllIOCountersMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "MonotonicCount", "system.disk.read_time", float64(300), "", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetric(t, "MonotonicCount", "system.disk.write_time", float64(450), "", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetric(t, "Rate", "system.disk.read_time_pct", float64(30), "", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetric(t, "Rate", "system.disk.write_time_pct", float64(45), "", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetric(t, "MonotonicCount", "system.disk.read_time", float64(500), "", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetric(t, "MonotonicCount", "system.disk.write_time", float64(150), "", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetric(t, "Rate", "system.disk.read_time_pct", float64(50), "", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetric(t, "Rate", "system.disk.write_time_pct", float64(15), "", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithTagByLabelConfiguredFalse_WhenCheckRuns_ThenBlkidLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +tag_by_label: false +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenBlkidLabelsAreReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenEmptyBlkidLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda6", "device_name:sda6", "label:", "device_label:"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda6", "device_name:sda6", "label:", "device_label:"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda6", "device_name:sda6", "label:", "device_label:"}) +} + +func TestGivenADiskCheckWithTagByLabelConfiguredTrue_WhenCheckRuns_ThenBlkidLabelsAreReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +tag_by_label: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithTagByLabelConfiguredTrue_WhenCheckRunsAndBlkidReturnsError_ThenBlkidLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskv2.BlkidCommand = func() (string, error) { + return "", errors.New("error calling blkid") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +tag_by_label: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithUseLsblkConfiguredTrue_WhenCheckRuns_ThenLsblkLabelsAreReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +use_lsblk: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithTagByLabelConfiguredTrueAndUseLsblk_WhenCheckRuns_ThenLsblkLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +tag_by_label: true +use_lsblk: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda2", "device_name:sda2", "label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithTagByLabelConfiguredFalseAndUseLsblk_WhenCheckRuns_ThenLsblkLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +tag_by_label: false +use_lsblk: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithUseLsblkConfiguredTrue_WhenLsblkReturnsError_ThenLsblkLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + diskv2.LsblkCommand = func() (string, error) { + return "", errors.New("error calling lsblk") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +use_lsblk: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL1", "device_label:MYLABEL1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL2", "device_label:MYLABEL2"}) +} + +func TestGivenADiskCheckWithBlkidCacheFileConfigured_WhenCheckRuns_ThenBlkidCacheFileLabelsAreReportedAsTags(t *testing.T) { + setupDefaultMocks() + var actualBlkidCacheFile string + diskv2.BlkidCacheCommand = func(blkidCacheFile string) (string, error) { + actualBlkidCacheFile = blkidCacheFile + return blkidCacheData, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +blkid_cache_file: /run/blkid/blkid.tab +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + assert.Equal(t, "/run/blkid/blkid.tab", actualBlkidCacheFile) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda2", "device_name:sda2", "label:BACKUP", "device_label:BACKUP"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda2", "device_name:sda2", "label:BACKUP", "device_label:BACKUP"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda2", "device_name:sda2", "label:BACKUP", "device_label:BACKUP"}) +} + +func TestGivenADiskCheckWithBlkidCacheFileConfigured_WhenBlkidCacheReturnsError_ThenBlkidCacheLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + var actualBlkidCacheFile string + diskv2.BlkidCacheCommand = func(blkidCacheFile string) (string, error) { + actualBlkidCacheFile = blkidCacheFile + return "", errors.New("error calling blkid cache") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +blkid_cache_file: /run/blkid/blkid.tab +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + assert.Equal(t, "/run/blkid/blkid.tab", actualBlkidCacheFile) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:BACKUP", "device_label:BACKUP"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:BACKUP", "device_label:BACKUP"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:BACKUP", "device_label:BACKUP"}) +} + +func TestGivenADiskCheckWithBlkidCacheFileConfigured_WhenBlkidCacheHasWrongLines_ThenBlkidCacheLabelsAreNotReportedAsTags(t *testing.T) { + setupDefaultMocks() + var actualBlkidCacheFile string + diskv2.BlkidCacheCommand = func(blkidCacheFile string) (string, error) { + actualBlkidCacheFile = blkidCacheFile + return string(` +/dev/sda1 + +/dev/sda2/dev/sdb1 +/dev/sdb2 +`), nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +blkid_cache_file: /run/blkid/blkid.tab +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + assert.Equal(t, "/run/blkid/blkid.tab", actualBlkidCacheFile) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/sda1", "device_name:sda1", "label:MYLABEL", "device_label:MYLABEL"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"label:BACKUP", "device_label:BACKUP"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"label:BACKUP", "device_label:BACKUP"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"label:BACKUP", "device_label:BACKUP"}) +} + +func TestGivenADiskCheckWithMountPointGlobalExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +mount_point_global_exclude: + - /dev/shm +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithMountPointGlobalBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +mount_point_global_blacklist: + - /dev/shm +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithMountPointExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_exclude: + - /dev/.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithMountPointBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_blacklist: + - /dev/.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithExcludedMountPointReConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("excluded_mountpoint_re: /dev/.*")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithUseMountConfigured_WhenCheckRuns_ThenUsageMetricsAreReportedWithMountPointTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("use_mount: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/", "device_name:sda1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/", "device_name:sda1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/", "device_name:sda1"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/home", "device_name:sda2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/home", "device_name:sda2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/home", "device_name:sda2"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/run", "device_name:tmpfs"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/run", "device_name:tmpfs"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/run", "device_name:tmpfs"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/shm", "device_name:shm"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/shm", "device_name:shm"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithServiceCheckRwTrueConfigured_WhenCheckRuns_ThenReadWriteServiceCheckReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +service_check_rw: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertServiceCheck(t, "disk.read_write", servicecheck.ServiceCheckOK, "", []string{"device:/dev/sda1", "device_name:sda1"}, "") + m.AssertServiceCheck(t, "disk.read_write", servicecheck.ServiceCheckOK, "", []string{"device:/dev/sda2", "device_name:sda2"}, "") + m.AssertServiceCheck(t, "disk.read_write", servicecheck.ServiceCheckUnknown, "", []string{"device:tmpfs", "device_name:tmpfs"}, "") + m.AssertServiceCheck(t, "disk.read_write", servicecheck.ServiceCheckCritical, "", []string{"device:shm", "device_name:shm"}, "") +} diff --git a/pkg/collector/corechecks/system/disk/diskv2/disk_test.go b/pkg/collector/corechecks/system/disk/diskv2/disk_test.go new file mode 100644 index 00000000000000..e2754f3960ae23 --- /dev/null +++ b/pkg/collector/corechecks/system/disk/diskv2/disk_test.go @@ -0,0 +1,1310 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package diskv2_test + +import ( + "bufio" + "bytes" + "errors" + "testing" + "time" + + "github.com/DataDog/datadog-agent/comp/core/autodiscovery/integration" + "github.com/DataDog/datadog-agent/pkg/aggregator" + "github.com/DataDog/datadog-agent/pkg/aggregator/mocksender" + "github.com/DataDog/datadog-agent/pkg/collector/check" + "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/diskv2" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/benbjohnson/clock" + gopsutil_disk "github.com/shirou/gopsutil/v4/disk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var partitionsTrue = []gopsutil_disk.PartitionStat{ + { + Device: "/dev/sda1", + Mountpoint: "/", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda6", + Mountpoint: "/home/backup", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "tmpfs", + Mountpoint: "/run", + Fstype: "tmpfs", + Opts: []string{"nosuid", "nodev", "relatime"}, + }, + { + Device: "shm", + Mountpoint: "/dev/shm", + Fstype: "tmpfs", + Opts: []string{"ro", "nosuid", "nodev"}, + }, +} +var partitionsFalse = []gopsutil_disk.PartitionStat{ + { + Device: "/dev/sda1", + Mountpoint: "/", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "/dev/sda2", + Mountpoint: "/home", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, +} +var usageData = map[string]*gopsutil_disk.UsageStat{ + "/": { + Path: "/", + Fstype: "ext4", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, + "/home": { + Path: "/home", + Fstype: "ext4", + Total: 50000000000, // 50 GB + Free: 20000000000, // 20 GB + Used: 30000000000, // 30 GB + UsedPercent: 60.0, + InodesTotal: 500000, + InodesUsed: 200000, + InodesFree: 300000, + InodesUsedPercent: 40.0, + }, + "/home/backup": { + Path: "/home/backup", + Fstype: "ext4", + Total: 20000000000, // 20 GB + Free: 10000000000, // 10 GB + Used: 10000000000, // 10 GB + UsedPercent: 60.0, + InodesTotal: 500000, + InodesUsed: 200000, + InodesFree: 300000, + InodesUsedPercent: 40.0, + }, + "/run": { + Path: "/run", + Fstype: "tmpfs", + Total: 2000000000, // 2 GB + Free: 1500000000, // 1.5 GB + Used: 500000000, // 0.5 GB + UsedPercent: 25.0, + InodesTotal: 10000, + InodesUsed: 5000, + InodesFree: 5000, + InodesUsedPercent: 50.0, + }, + "/dev/shm": { + Path: "/dev/shm", + Fstype: "tmpfs", + Total: 8000000000, // 8 GB + Free: 7000000000, // 7 GB + Used: 1000000000, // 1 GB + UsedPercent: 12.5, + InodesTotal: 20000, + InodesUsed: 1000, + InodesFree: 19000, + InodesUsedPercent: 5.0, + }, +} +var ioCountersData = map[string]gopsutil_disk.IOCountersStat{ + "/dev/sda1": { + Name: "/dev/sda1", + ReadCount: 100, + WriteCount: 200, + ReadBytes: 1048576, + WriteBytes: 2097152, + ReadTime: 300, + WriteTime: 450, + }, + "/dev/sda2": { + Name: "/dev/sda2", + ReadCount: 50, + WriteCount: 75, + ReadBytes: 524288, + WriteBytes: 1048576, + ReadTime: 500, + WriteTime: 150, + }, +} + +func setupDefaultMocks() { + diskv2.DiskPartitions = func(all bool) ([]gopsutil_disk.PartitionStat, error) { + if all { + return partitionsTrue, nil + } + return partitionsFalse, nil + } + diskv2.DiskUsage = func(mountpoint string) (*gopsutil_disk.UsageStat, error) { + return usageData[mountpoint], nil + } + diskv2.DiskIOCounters = func(_names ...string) (map[string]gopsutil_disk.IOCountersStat, error) { + return ioCountersData, nil + } + setupPlatformMocks() +} + +func createCheck() check.Check { + diskCheckOpt := diskv2.Factory() + diskCheckFunc, _ := diskCheckOpt.Get() + diskCheck := diskCheckFunc() + return diskCheck +} + +func createCheckWithClock(clock clock.Clock) check.Check { + diskCheckOpt := diskv2.FactoryWithClock(clock) + diskCheckFunc, _ := diskCheckOpt.Get() + diskCheck := diskCheckFunc() + return diskCheck +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, []byte(`min_collection_interval: "string_value"`), nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckAndStoppedSender(t *testing.T) { + stoppedSenderError := errors.New("demultiplexer is stopped") + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + m.GetSenderManager().(*aggregator.AgentDemultiplexer).Stop(false) + err := diskCheck.Run() + + assert.Equal(t, stoppedSenderError, err) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRunsAndPartitionsSystemCallReturnsError_ThenErrorIsReturnedAndNoUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return nil, errors.New("error calling disk.DiskPartitions") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.NotNil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRunsAndUsageSystemCallReturnsError_ThenNoUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return nil, errors.New("error calling diskUsage") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err = diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + + w.Flush() + assert.Contains(t, b.String(), "Unable to get disk metrics for /: error calling diskUsage. You can exclude this mountpoint in the settings if it is invalid.") + assert.Contains(t, b.String(), "Unable to get disk metrics for /home: error calling diskUsage. You can exclude this mountpoint in the settings if it is invalid.") + assert.Contains(t, b.String(), "Unable to get disk metrics for /run: error calling diskUsage. You can exclude this mountpoint in the settings if it is invalid.") + assert.Contains(t, b.String(), "Unable to get disk metrics for /dev/shm: error calling diskUsage. You can exclude this mountpoint in the settings if it is invalid.") + +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRunsAndIOCountersSystemCallReturnsError_ThenErrorIsReturnedAndNoUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskIOCounters = func(...string) (map[string]gopsutil_disk.IOCountersStat, error) { + return nil, errors.New("error calling diskIOCounters") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.NotNil(t, err) + m.AssertNotCalled(t, "MonotonicCount", "system.disk.read_time", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "MonotonicCount", "system.disk.write_time", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Rate", "system.disk.read_time_pct", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Rate", "system.disk.write_time_pct", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) +} + +func TestGivenADiskCheckWithFileSystemGlobalBlackListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +file_system_global_blacklist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + + w.Flush() + assert.Contains(t, b.String(), "`file_system_global_blacklist` is deprecated and will be removed in a future release. Please use `file_system_global_exclude` instead.") +} + +func TestGivenADiskCheckWithDeviceGlobalBlackListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +device_global_blacklist: + - shm +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + + w.Flush() + assert.Contains(t, b.String(), "`device_global_blacklist` is deprecated and will be removed in a future release. Please use `device_global_exclude` instead.") +} + +func TestGivenADiskCheckWithMountpointGlobalBlackListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +mount_point_global_blacklist: + - /dev/shm +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + + w.Flush() + assert.Contains(t, b.String(), "`mount_point_global_blacklist` is deprecated and will be removed in a future release. Please use `mount_point_global_exclude` instead.") +} + +func TestGivenADiskCheckWithFileSystemWhiteListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_whitelist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`file_system_whitelist` is deprecated and will be removed in a future release. Please use `file_system_include` instead.") +} + +func TestGivenADiskCheckWithFileSystemBlackListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_blacklist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`file_system_blacklist` is deprecated and will be removed in a future release. Please use `file_system_exclude` instead.") +} + +func TestGivenADiskCheckWithDeviceWhiteListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_whitelist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`device_whitelist` is deprecated and will be removed in a future release. Please use `device_include` instead.") +} + +func TestGivenADiskCheckWithDeviceBlackListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_blacklist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`device_blacklist` is deprecated and will be removed in a future release. Please use `device_exclude` instead.") +} + +func TestGivenADiskCheckWithMountPointWhiteListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_whitelist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`mount_point_whitelist` is deprecated and will be removed in a future release. Please use `mount_point_include` instead.") +} + +func TestGivenADiskCheckWithMountPointBlackListConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_blacklist: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`mount_point_blacklist` is deprecated and will be removed in a future release. Please use `mount_point_exclude` instead.") +} + +func TestGivenADiskCheckWithExcludedMountPointReConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_mountpoint_re: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`excluded_mountpoint_re` is deprecated and will be removed in a future release. Please use `mount_point_exclude` instead.") +} + +func TestGivenADiskCheckWithExcludedFileSystemsConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_filesystems: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`excluded_filesystems` is deprecated and will be removed in a future release. Please use `file_system_exclude` instead.") +} + +func TestGivenADiskCheckWithExcludedDisksConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_disks: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`excluded_disks` is deprecated and will be removed in a future release. Please use `device_exclude` instead.") +} + +func TestGivenADiskCheckWithExcludedDisksReConfigured_WhenCheckIsConfigured_ThenWarningMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_disk_re: + - ext4 +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + _ = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + w.Flush() + assert.Contains(t, b.String(), "`excluded_disk_re` is deprecated and will be removed in a future release. Please use `device_exclude` instead.") +} + +func TestGivenADiskCheckWithDeviceGlobalExcludeAndDeviceExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseDevices(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +device_global_exclude: + - /dev/sda1 +`)) + config := integration.Data([]byte(` +device_exclude: + - /dev/sda2 +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceGlobalBlackListAndDeviceExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseDevices(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +device_global_blacklist: + - /dev/sda1 +`)) + config := integration.Data([]byte(` +device_exclude: + - /dev/sda2 +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceGlobalExcludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +device_global_exclude: + - /dev/sda(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithDeviceExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseDevices(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_exclude: + - /dev/sda.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseDevices(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_blacklist: + - /dev/sda.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithExcludedDisksConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseDevices(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_disks: + - /dev/sda.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceExcludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_exclude: + - /dev/sda(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithExcludedDiskReConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseDevices(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("excluded_disk_re: /dev/sda.*")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithExcludedDiskReIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("excluded_disk_re: /dev/sda(.*")) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithDeviceIncludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_include: + - /dev/sda(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +file_system_global_exclude: + - tmp(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithFileSystemExcludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_exclude: + - tmp(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithFileSystemIncludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_include: + - ext(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithMountPointGlobalExcludeNotConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithBinfmt_miscMountPoints(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "first", + Mountpoint: "/host/proc/sys/fs/binfmt_misc", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "second", + Mountpoint: "/proc/sys/fs/binfmt_misc", + Fstype: "ext4", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "/host/proc/sys/fs/binfmt_misc", + Fstype: "ext4", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:first", "device_name:first"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:first", "device_name:first"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:first", "device_name:first"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:second", "device_name:second"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:second", "device_name:second"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:second", "device_name:second"}) +} + +func TestGivenADiskCheckWithMountPointGlobalExcludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +mount_point_global_exclude: + - /dev/shm(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithMountPointExcludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_exclude: + - /dev/(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithExcludedMountPointReIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("excluded_mountpoint_re: /dev/(.*")) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithMountPointIncludeConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseMountPointsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_include: + - /dev/.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithMountPointWhiteListConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseMountPointsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_whitelist: + - /dev/.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(97656250), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(68359375), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(29296875), "", []string{"device:/dev/sda1", "device_name:sda1"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(48828125), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(29296875), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(19531250), "", []string{"device:/dev/sda2", "device_name:sda2"}) + m.AssertNotCalled(t, "Gauge", "system.disk.total", float64(1953125), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", float64(488281.25), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", float64(1464843.75), "", []string{"device:tmpfs", "device_name:tmpfs"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(7812500), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(976562.5), "", []string{"device:shm", "device_name:shm"}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(6835937.5), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithMountPointIncludeIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_include: + - /dev/(.* +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRunsAndUsageSystemCallReturnsAPartitionWithZeroTotal_ThenNoUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "shm", + Mountpoint: "/dev/shm", + Fstype: "tmpfs", + Opts: []string{"rw", "nosuid", "nodev"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "/dev/shm", + Fstype: "tmpfs", + Total: 0, + Free: 0, + Used: 0, + UsedPercent: 0, + InodesTotal: 0, + InodesUsed: 0, + InodesFree: 0, + InodesUsedPercent: 0, + }, nil + } + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) +} + +func TestGivenADiskCheckWithMinDiskSizeConfiguredTo1MiBConfig_WhenCheckRunsAndUsageSystemCallReturnsAPartitionWith1024Total_ThenNoUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "shm", + Mountpoint: "/dev/shm", + Fstype: "tmpfs", + Opts: []string{"rw", "nosuid", "nodev"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "/dev/shm", + Fstype: "tmpfs", + Total: 1024, + Free: 1024, + Used: 0, + UsedPercent: 0, + InodesTotal: 0, + InodesUsed: 0, + InodesFree: 0, + InodesUsedPercent: 0, + }, nil + } + config := integration.Data([]byte(`min_disk_size: 1`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.InfoLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "info") + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err = diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + + w.Flush() + assert.Contains(t, b.String(), "Excluding partition: [device: shm] [mountpoint: /dev/shm] [fstype: tmpfs] with total disk size 1024 bytes") +} + +func TestGivenADiskCheckWithMinDiskSizeConfiguredTo1MiBConfig_WhenCheckRunsAndUsageSystemCallReturnsAPartitionWith1048576Total_ThenUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "shm", + Mountpoint: "/dev/shm", + Fstype: "tmpfs", + Opts: []string{"rw", "nosuid", "nodev"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "/dev/shm", + Fstype: "tmpfs", + Total: 1048576, + Free: 1048576, + Used: 0, + UsedPercent: 0, + InodesTotal: 0, + InodesUsed: 0, + InodesFree: 0, + InodesUsedPercent: 0, + }, nil + } + config := integration.Data([]byte(`min_disk_size: 1`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(1024), "", []string{"device:shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenUsageMetricsAreNotReportedWithFileSystemTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"tmpfs", "filesystem:tmpfs"}) +} + +func TestGivenADiskCheckWithTagByFileSystemFalseConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedWithFileSystemTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("tag_by_filesystem: false")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"tmpfs", "filesystem:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"tmpfs", "filesystem:tmpfs"}) +} + +func TestGivenADiskCheckWithTagByFileSystemTrueConfigured_WhenCheckRuns_ThenUsageMetricsAreReportedWithFileSystemTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("tag_by_filesystem: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{"ext4", "filesystem:ext4"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{"ext4", "filesystem:ext4"}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenUsageMetricsAreNotReportedWithMountPointTags(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/", "device_name:sda1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/", "device_name:sda1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/", "device_name:sda1"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/home", "device_name:sda2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/home", "device_name:sda2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/home", "device_name:sda2"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/run", "device_name:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/run", "device_name:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/run", "device_name:tmpfs"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.total", []string{"device:/dev/shm", "device_name:shm"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.used", []string{"device:/dev/shm", "device_name:shm"}) + m.AssertMetricNotTaggedWith(t, "Gauge", "system.disk.free", []string{"device:/dev/shm", "device_name:shm"}) +} + +func TestGivenADiskCheckWithDeviceTagReIncorrectlyConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_tag_re: + /dev/sda.*: role:primary + tmp.(*: role:tmp +`)) + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.NotNil(t, err) +} + +func TestGivenADiskCheckWithUseLsblkAndBlkidCacheFileConfigured_WhenCheckIsConfigured_ThenErrorIsReturned(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +use_lsblk: true +blkid_cache_file: /run/blkid/blkid.tab +`)) + expectedError := "only one of 'use_lsblk' and 'blkid_cache_file' can be set at the same time" + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.EqualError(t, err, expectedError) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenReadWriteServiceCheckNotReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "ServiceCheck", "disk.read_write", mock.AnythingOfType("servicecheck.ServiceCheckStatus"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string"), mock.AnythingOfType("string")) +} + +func TestGivenADiskCheckWithServiceCheckRwFalseConfigured_WhenCheckRuns_ThenReadWriteServiceCheckNotReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +service_check_rw: false +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "ServiceCheck", "disk.read_write", mock.AnythingOfType("servicecheck.ServiceCheckStatus"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string"), mock.AnythingOfType("string")) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenUsagePartitionTimeout_ThenUsageMetricsNotReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "shm", + Mountpoint: "/dev/shm", + Fstype: "tmpfs", + Opts: []string{"rw", "nosuid", "nodev"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + // Sleep 10s (longer than default timeout) + time.Sleep(10 * time.Second) + return &gopsutil_disk.UsageStat{ + Path: "/dev/shm", + Fstype: "tmpfs", + Total: 1024, + Free: 1024, + Used: 0, + UsedPercent: 0, + InodesTotal: 0, + InodesUsed: 0, + InodesFree: 0, + InodesUsedPercent: 0, + }, nil + } + mockClock := clock.NewMock() + diskCheck := createCheckWithClock(mockClock) + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + err := diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + done := make(chan error, 1) + go func() { + err = diskCheck.Run() + done <- err + }() + // Sleep momentarily so that other goroutines can process. + time.Sleep(10 * time.Millisecond) + // Move the clock forward longer than default timeout (5s) + mockClock.Add(10 * time.Second) + <-done + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.utilized", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) + m.AssertNotCalled(t, "Gauge", "system.disk.in_use", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")) +} diff --git a/pkg/collector/corechecks/system/disk/diskv2/disk_windows.go b/pkg/collector/corechecks/system/disk/diskv2/disk_windows.go new file mode 100644 index 00000000000000..264cc71deb35dc --- /dev/null +++ b/pkg/collector/corechecks/system/disk/diskv2/disk_windows.go @@ -0,0 +1,151 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build windows + +package diskv2 + +import ( + "fmt" + "github.com/DataDog/datadog-agent/pkg/util/log" + win "golang.org/x/sys/windows" + "slices" + "strings" + "unsafe" + + "github.com/DataDog/datadog-agent/pkg/aggregator/sender" + gopsutil_disk "github.com/shirou/gopsutil/v4/disk" +) + +func defaultIgnoreCase() bool { + return true +} + +func baseDeviceName(device string) string { + return strings.ToLower(strings.Trim(device, "\\")) +} + +func (c *Check) fetchAllDeviceLabelsFromLsblk() error { + return nil +} + +func (c *Check) fetchAllDeviceLabelsFromBlkidCache() error { + return nil +} + +func (c *Check) fetchAllDeviceLabelsFromBlkid() error { + return nil +} + +func (c *Check) excludePartitionInPlatform(partition gopsutil_disk.PartitionStat) bool { + /* skip cd-rom drives with no disk in it; they may raise + ENOENT, pop-up a Windows GUI error for a non-ready + partition or just hang; + and all the other excluded disks */ + return slices.Contains(partition.Opts, "cdrom") || partition.Fstype == "" +} + +var mpr = win.NewLazySystemDLL("mpr.dll") +var procWNetAddConnection2W = mpr.NewProc("WNetAddConnection2W") + +type netResource struct { + Scope uint32 + Type uint32 + DisplayType uint32 + Usage uint32 + localName *uint16 + remoteName *uint16 + comment *uint16 + provider *uint16 +} + +// RemotePath constructs the remote path based on the mount type. +// It converts the Type to uppercase to ensure case-insensitive evaluation. +func remotePath(m mount) string { + if strings.TrimSpace(m.Type) == "" { + m.Type = "smb" + } + // Convert Type to uppercase for case-insensitive comparison + normalizedType := strings.ToLower(strings.TrimSpace(m.Type)) + if normalizedType == "nfs" { + return fmt.Sprintf(`%s:%s`, m.Host, m.Share) + } + return fmt.Sprintf(`\\%s\%s`, m.Host, m.Share) +} + +func (c *Check) configureCreateMounts() { + for _, m := range c.instanceConfig.CreateMounts { + if len(m.Host) == 0 || len(m.Share) == 0 { + log.Errorf("Invalid configuration. Drive mount requires remote machine and share point") + continue + } + log.Debugf("Mounting: %s\n", m) + remoteName := remotePath(m) + err := NetAddConnection(m.MountPoint, remoteName, m.Password, m.User) + if err != nil { + log.Errorf("Failed to mount %s on %s: %s", m.MountPoint, remoteName, err) + continue + } + log.Debugf("Successfully mounted %s as %s\n", m.MountPoint, remoteName) + } +} + +// NetAddConnection specifies the command used to add a new network connection. +var NetAddConnection = func(localName, remoteName, password, username string) error { + return wNetAddConnection2(localName, remoteName, password, username) +} + +func createNetResource(localName, remoteName string) (netResource, error) { + lpLocalName, err := win.UTF16PtrFromString(localName) + if err != nil { + return netResource{}, fmt.Errorf("failed to convert local name to UTF16: %w", err) + } + lpRemoteName, err := win.UTF16PtrFromString(remoteName) + if err != nil { + return netResource{}, fmt.Errorf("failed to convert remote name to UTF16: %w", err) + } + return netResource{ + localName: lpLocalName, + remoteName: lpRemoteName, + }, nil +} + +func wNetAddConnection2(localName, remoteName, password, username string) error { + netResource, err := createNetResource(localName, remoteName) + if err != nil { + return fmt.Errorf("failed to create NetResource: %w", err) + } + var _password *uint16 + if password == "" { + _password = nil + } else { + _password, err = win.UTF16PtrFromString(password) + if err != nil { + return fmt.Errorf("failed to convert password to UTF16: %w", err) + } + } + var _username *uint16 + if username == "" { + _username = nil + } else { + _username, err = win.UTF16PtrFromString(username) + if err != nil { + return fmt.Errorf("failed to convert username to UTF16: %w", err) + } + } + rc, _, err := procWNetAddConnection2W.Call( + uintptr(unsafe.Pointer(&netResource)), + uintptr(unsafe.Pointer(_password)), + uintptr(unsafe.Pointer(_username)), + 0, + ) + if rc != 0 { + return err + } + return nil +} + +func (c *Check) sendInodesMetrics(_ sender.Sender, _ *gopsutil_disk.UsageStat, _ []string) { +} diff --git a/pkg/collector/corechecks/system/disk/diskv2/disk_windows_test.go b/pkg/collector/corechecks/system/disk/diskv2/disk_windows_test.go new file mode 100644 index 00000000000000..40b83ac8a0df00 --- /dev/null +++ b/pkg/collector/corechecks/system/disk/diskv2/disk_windows_test.go @@ -0,0 +1,1168 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build windows + +package diskv2_test + +import ( + "bufio" + "bytes" + "errors" + "testing" + + "github.com/DataDog/datadog-agent/comp/core/autodiscovery/integration" + "github.com/DataDog/datadog-agent/pkg/aggregator/mocksender" + "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/diskv2" + "github.com/DataDog/datadog-agent/pkg/metrics/servicecheck" + "github.com/DataDog/datadog-agent/pkg/util/log" + gopsutil_disk "github.com/shirou/gopsutil/v4/disk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func setupPlatformMocks() { + diskv2.NetAddConnection = func(_localName, _remoteName, _password, _username string) error { + return nil + } +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenAllUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithLowercaseDeviceTagConfigured_WhenCheckRuns_ThenLowercaseDevicesAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskv2.DiskIOCounters = func(...string) (map[string]gopsutil_disk.IOCountersStat, error) { + return map[string]gopsutil_disk.IOCountersStat{ + "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\": { + Name: "sda1", + ReadCount: 100, + WriteCount: 200, + ReadBytes: 1048576, + WriteBytes: 2097152, + ReadTime: 300, + WriteTime: 450, + }, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("lowercase_device_tag: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "MonotonicCount", "system.disk.read_time", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "MonotonicCount", "system.disk.write_time", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "Rate", "system.disk.read_time_pct", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "Rate", "system.disk.write_time_pct", []string{`device:\\?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithIncludeAllDevicesTrueConfigured_WhenCheckRuns_ThenAllUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("include_all_devices: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithIncludeAllDevicesFalseConfigured_WhenCheckRuns_ThenOnlyPhysicalDevicesUsageMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("include_all_devices: false")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRunsAndPartitionsSystemReturnsEmptyDevice_ThenNoUsageMetricsAreReportedForThatPartition(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "E:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{"device:", "device_name:."}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{"device:", "device_name:."}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{"device:", "device_name:."}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithDeviceIncludeConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseDevicesAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_include: + - \\?\Vol.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithDeviceWhiteListConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseDevicesAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_whitelist: + - \\?\Vol.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeNotConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithIso9660FileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "cdrom", + Mountpoint: "/", + Fstype: "iso9660", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{"device:cdrom", "device_name:cdrom"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{"device:cdrom", "device_name:cdrom"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{"device:cdrom", "device_name:cdrom"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeNotConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithTracefsFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "trace", + Mountpoint: "/", + Fstype: "tracefs", + Opts: []string{"rw", "relatime"}, + }, + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{"device:trace", "device_name:trace"}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{"device:trace", "device_name:trace"}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{"device:trace", "device_name:trace"}) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithDefaultConfig_WhenCheckRuns_ThenAllIOCountersMetricsAreReported(t *testing.T) { + setupDefaultMocks() + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "MonotonicCount", "system.disk.read_time", float64(300), "", []string{"device:/dev/sda1", "device_name:/dev/sda1"}) + m.AssertMetric(t, "MonotonicCount", "system.disk.write_time", float64(450), "", []string{"device:/dev/sda1", "device_name:/dev/sda1"}) + m.AssertMetric(t, "Rate", "system.disk.read_time_pct", float64(30), "", []string{"device:/dev/sda1", "device_name:/dev/sda1"}) + m.AssertMetric(t, "Rate", "system.disk.write_time_pct", float64(45), "", []string{"device:/dev/sda1", "device_name:/dev/sda1"}) + m.AssertMetric(t, "MonotonicCount", "system.disk.read_time", float64(500), "", []string{"device:/dev/sda2", "device_name:/dev/sda2"}) + m.AssertMetric(t, "MonotonicCount", "system.disk.write_time", float64(150), "", []string{"device:/dev/sda2", "device_name:/dev/sda2"}) + m.AssertMetric(t, "Rate", "system.disk.read_time_pct", float64(50), "", []string{"device:/dev/sda2", "device_name:/dev/sda2"}) + m.AssertMetric(t, "Rate", "system.disk.write_time_pct", float64(15), "", []string{"device:/dev/sda2", "device_name:/dev/sda2"}) +} + +func TestGivenADiskCheckWithCreateMountsConfigured_WhenCheckIsConfigured_ThenMountsAreCreated(t *testing.T) { + setupDefaultMocks() + var netAddConnectionCalls [][]string + diskv2.NetAddConnection = func(localName, remoteName, password, username string) error { + netAddConnectionCalls = append(netAddConnectionCalls, []string{localName, remoteName, password, username}) + return nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +create_mounts: +- mountpoint: "p:" + host: smbserver + share: space +- mountpoint: "s:" + user: auser + password: "somepassword" + host: smbserver + share: space + type: smb +- mountpoint: "n:" + host: nfsserver + share: /mnt/nfs_share + type: nfs +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + assert.Equal(t, 3, len(netAddConnectionCalls)) + expectedNetAddConnectionCalls := [][]string{ + {"p:", `\\smbserver\space`, "", ""}, + {"s:", `\\smbserver\space`, "somepassword", "auser"}, + {"n:", `nfsserver:/mnt/nfs_share`, "", ""}, + } + for i, mountCall := range netAddConnectionCalls { + assert.Equal(t, expectedNetAddConnectionCalls[i], mountCall) + } +} + +func TestGivenADiskCheckWithCreateMountsConfiguredWithoutHost_WhenCheckIsConfigured_ThenMountsAreNotCreated(t *testing.T) { + setupDefaultMocks() + var netAddConnectionCalls [][]string + diskv2.NetAddConnection = func(localName, remoteName, password, username string) error { + netAddConnectionCalls = append(netAddConnectionCalls, []string{localName, remoteName, password, username}) + return nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +create_mounts: +- mountpoint: "n:" + share: /mnt/nfs_share + type: nfs +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + err = diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + + assert.Nil(t, err) + assert.Equal(t, 0, len(netAddConnectionCalls)) + w.Flush() + assert.Contains(t, b.String(), "Invalid configuration. Drive mount requires remote machine and share point") +} + +func TestGivenADiskCheckWithCreateMountsConfigured_WhenCheckRunsAndIOCountersSystemCallReturnsError_ThenErrorMessagedIsLogged(t *testing.T) { + setupDefaultMocks() + diskv2.NetAddConnection = func(_localName, _remoteName, _password, _username string) error { + return errors.New("error calling NetAddConnection") + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +create_mounts: +- mountpoint: "p:" + host: smbserver + share: space +`)) + var b bytes.Buffer + w := bufio.NewWriter(&b) + logger, err := log.LoggerFromWriterWithMinLevelAndFormat(w, log.DebugLvl, "[%LEVEL] %Msg") + assert.Nil(t, err) + log.SetupLogger(logger, "debug") + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err = diskCheck.Run() + + assert.Nil(t, err) + w.Flush() + assert.Contains(t, b.String(), `Failed to mount p: on \\smbserver\space`) +} + +func TestGivenADiskCheckWithDeviceTagReConfigured_WhenCheckRuns_ThenUsageMetricsAreReportedWithTheseTags(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: `\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +device_tag_re: + \\\\\?\\Vol.*: role:primary +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`, "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`, "role:primary"}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`, "role:primary"}) +} + +func TestGivenADiskCheckWithFileSystemGlobalExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +file_system_global_exclude: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemGlobalBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +file_system_global_blacklist: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithExcludedFileSystemsConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +excluded_filesystems: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_exclude: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseFileSystems(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_blacklist: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemIncludeConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseFileSystemsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_include: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithFileSystemWhiteListConfigured_WhenCheckRuns_ThenOnlyUsageMetricsForPartitionsWithThoseFileSystemsAreReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +file_system_whitelist: + - ntfs.* +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetric(t, "Gauge", "system.disk.total", float64(97656250), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.used", float64(68359375), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetric(t, "Gauge", "system.disk.free", float64(29296875), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithMountPointGlobalExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +mount_point_global_exclude: + - D:\\ +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithMountPointGlobalBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + initConfig := integration.Data([]byte(` +mount_point_global_blacklist: + - D:\\ +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, nil, initConfig, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithMountPointExcludeConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_exclude: + - D:\\ +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithMountPointBlackListConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +mount_point_blacklist: + - D:\\ +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithExcludedMountPointReConfigured_WhenCheckRuns_ThenUsageMetricsAreNotReportedForPartitionsWithThoseMountPoints(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(`excluded_mountpoint_re: D:\\`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertNotCalled(t, "Gauge", "system.disk.total", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.used", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertNotCalled(t, "Gauge", "system.disk.free", mock.AnythingOfType("float64"), "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithUseMountConfigured_WhenCheckRuns_ThenUsageMetricsAreReportedWithMountPointTags(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte("use_mount: true")) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.total", []string{`device:D:\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.used", []string{`device:D:\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) + m.AssertMetricTaggedWith(t, "Gauge", "system.disk.free", []string{`device:D:\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}) +} + +func TestGivenADiskCheckWithServiceCheckRwTrueConfigured_WhenCheckRuns_ThenReadWriteServiceCheckReported(t *testing.T) { + setupDefaultMocks() + diskv2.DiskPartitions = func(_ bool) ([]gopsutil_disk.PartitionStat, error) { + return []gopsutil_disk.PartitionStat{ + { + Device: "\\\\?\\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\\", + Mountpoint: "D:\\", + Fstype: "NTFS", + Opts: []string{"rw", "relatime"}, + }}, nil + } + diskv2.DiskUsage = func(_ string) (*gopsutil_disk.UsageStat, error) { + return &gopsutil_disk.UsageStat{ + Path: "D:\\", + Fstype: "NTFS", + Total: 100000000000, // 100 GB + Free: 30000000000, // 30 GB + Used: 70000000000, // 70 GB + UsedPercent: 70.0, + InodesTotal: 1000000, + InodesUsed: 500000, + InodesFree: 500000, + InodesUsedPercent: 50.0, + }, nil + } + diskCheck := createCheck() + m := mocksender.NewMockSender(diskCheck.ID()) + m.SetupAcceptAll() + config := integration.Data([]byte(` +service_check_rw: true +`)) + + diskCheck.Configure(m.GetSenderManager(), integration.FakeConfigHash, config, nil, "test") + err := diskCheck.Run() + + assert.Nil(t, err) + m.AssertServiceCheck(t, "disk.read_write", servicecheck.ServiceCheckOK, "", []string{`device:\\?\Volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}\`, `device_name:?\volume{a1b2c3d4-e5f6-7890-abcd-ef1234567890}`}, "") +} diff --git a/pkg/commonchecks/corechecks.go b/pkg/commonchecks/corechecks.go index 1d6e92423fc255..5cbd6d173a7464 100644 --- a/pkg/commonchecks/corechecks.go +++ b/pkg/commonchecks/corechecks.go @@ -46,6 +46,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/cpu/cpu" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/cpu/load" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/disk" + "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/diskv2" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/disk/io" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/filehandles" "github.com/DataDog/datadog-agent/pkg/collector/corechecks/system/memory" @@ -92,7 +93,11 @@ func RegisterChecks(store workloadmeta.Component, tagger tagger.Component, cfg c corecheckLoader.RegisterCheck(nvidia.CheckName, nvidia.Factory()) corecheckLoader.RegisterCheck(oracle.CheckName, oracle.Factory()) corecheckLoader.RegisterCheck(oracle.OracleDbmCheckName, oracle.Factory()) - corecheckLoader.RegisterCheck(disk.CheckName, disk.Factory()) + if cfg.GetBool("use_diskv2_check") { + corecheckLoader.RegisterCheck(disk.CheckName, diskv2.Factory()) + } else { + corecheckLoader.RegisterCheck(disk.CheckName, disk.Factory()) + } corecheckLoader.RegisterCheck(wincrashdetect.CheckName, wincrashdetect.Factory()) corecheckLoader.RegisterCheck(winkmem.CheckName, winkmem.Factory()) corecheckLoader.RegisterCheck(winproc.CheckName, winproc.Factory()) diff --git a/pkg/config/setup/config.go b/pkg/config/setup/config.go index 227d4690162d28..2b67934399212d 100644 --- a/pkg/config/setup/config.go +++ b/pkg/config/setup/config.go @@ -313,6 +313,10 @@ func InitConfig(config pkgconfigmodel.Setup) { // Otherwise, Python is loaded when the collector is initialized. config.BindEnvAndSetDefault("python_lazy_loading", true) + // If true, then new version of disk v2 check will be used. + // Otherwise, the old version of disk check will be used (maintaining backward compatibility). + config.BindEnvAndSetDefault("use_diskv2_check", false) + // if/when the default is changed to true, make the default platform // dependent; default should remain false on Windows to maintain backward // compatibility with Agent5 behavior/win diff --git a/releasenotes/notes/disk_feature_parity-f26158df4ead7b63.yaml b/releasenotes/notes/disk_feature_parity-f26158df4ead7b63.yaml new file mode 100644 index 00000000000000..b22bb78353304c --- /dev/null +++ b/releasenotes/notes/disk_feature_parity-f26158df4ead7b63.yaml @@ -0,0 +1,13 @@ +# Each section from every release note are combined when the +# CHANGELOG.rst is rendered. So the text needs to be worded so that +# it does not depend on any information only available in another +# section. This may mean repeating some details, but each section +# must be readable independently of the other. +# +# Each section note must be formatted as reStructuredText. +--- +features: + - | + Feature parity between Python disk check and Go disk check. + The new version of the disk check is disabled by default for now, but it will be enabled later on. + It can be enabled by setting ``use_diskv2_check: true`` in your configuration. \ No newline at end of file diff --git a/test/new-e2e/go.mod b/test/new-e2e/go.mod index f216def88d5acd..dc8f1b10cb436c 100644 --- a/test/new-e2e/go.mod +++ b/test/new-e2e/go.mod @@ -125,7 +125,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/websocket v1.5.1 // indirect diff --git a/test/new-e2e/tests/agent-runtimes/check_disk/disk_common_test.go b/test/new-e2e/tests/agent-runtimes/check_disk/disk_common_test.go new file mode 100644 index 00000000000000..fc7ff1e6158015 --- /dev/null +++ b/test/new-e2e/tests/agent-runtimes/check_disk/disk_common_test.go @@ -0,0 +1,155 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +// Package checkdisk contains tests for the disk check +package checkdisk + +import ( + "cmp" + "fmt" + "math" + "slices" + + "github.com/DataDog/test-infra-definitions/components/datadog/agentparams" + e2eos "github.com/DataDog/test-infra-definitions/components/os" + "github.com/DataDog/test-infra-definitions/scenarios/aws/ec2" + gocmp "github.com/google/go-cmp/cmp" + gocmpopts "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/environments" + awshost "github.com/DataDog/datadog-agent/test/new-e2e/pkg/provisioners/aws/host" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/testcommon/check" + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/utils/e2e/client/agentclient" +) + +type baseCheckSuite struct { + e2e.BaseSuite[environments.Host] + descriptor e2eos.Descriptor + agentOptions []agentparams.Option +} + +func getAgentOptions() []agentparams.Option { + agentOptions := []agentparams.Option{} + return agentOptions +} + +func (v *baseCheckSuite) getSuiteOptions() []e2e.SuiteOption { + suiteOptions := []e2e.SuiteOption{} + suiteOptions = append(suiteOptions, e2e.WithProvisioner( + awshost.Provisioner( + awshost.WithAgentOptions(v.agentOptions...), + awshost.WithEC2InstanceOptions(ec2.WithOS(v.descriptor)), + ), + )) + + return suiteOptions +} + +// a relative diff considered acceptable when comparing metrics +const metricCompareFraction = 0.02 + +// number of decimals when comparing metrics +const metricCompareDecimals = 1 + +func (v *baseCheckSuite) TestCheckDisk() { + testCases := []struct { + name string + checkConfig string + agentConfig string + }{ + { + "default", + `init_config: +instances: + - use_mount: false +`, + ``, + }, + } + p := math.Pow(10, float64(metricCompareDecimals)) + for _, testCase := range testCases { + v.Run(testCase.name, func() { + v.T().Log("run the disk check using old version") + pythonMetrics := v.runDiskCheck(testCase.agentConfig, testCase.checkConfig, false) + v.T().Log("run the disk check using new version") + goMetrics := v.runDiskCheck(testCase.agentConfig, testCase.checkConfig, true) + + // assert the check output + diff := gocmp.Diff(pythonMetrics, goMetrics, + gocmp.Comparer(func(a, b float64) bool { + x := math.Round(a*p) / p + y := math.Round(b*p) / p + relMarg := metricCompareFraction * math.Min(math.Abs(x), math.Abs(y)) + return math.Abs(x-y) <= relMarg + }), + gocmpopts.SortSlices(cmp.Less[string]), // sort tags + gocmpopts.SortSlices(metricPayloadCompare), // sort metrics + ) + require.Empty(v.T(), diff) + }) + } +} + +func metricPayloadCompare(a, b check.Metric) int { + return cmp.Or( + cmp.Compare(a.Host, b.Host), + cmp.Compare(a.Metric, b.Metric), + cmp.Compare(a.Type, b.Type), + cmp.Compare(a.SourceTypeName, b.SourceTypeName), + cmp.Compare(a.Interval, b.Interval), + slices.Compare(a.Tags, b.Tags), + slices.CompareFunc(a.Points, b.Points, func(a, b []float64) int { + return slices.Compare(a, b) + }), + ) +} + +func (v *baseCheckSuite) runDiskCheck(agentConfig string, checkConfig string, useNewVersion bool) []check.Metric { + v.T().Helper() + + diskCheckVersion := "old" + if useNewVersion { + agentConfig += "\nuse_diskv2_check: true" + checkConfig += "\n loader: core" + diskCheckVersion = "new" + } + diskCheckVersionTag := fmt.Sprintf("disk_check_version:%s", diskCheckVersion) + checkConfig += fmt.Sprintf("\n tags:\n - %s", diskCheckVersionTag) + + agentOptions := v.agentOptions + agentOptions = append(agentOptions, + agentparams.WithAgentConfig(agentConfig), + ) + agentOptions = append(agentOptions, + agentparams.WithIntegration("disk.d", checkConfig), + ) + v.UpdateEnv(awshost.Provisioner( + awshost.WithEC2InstanceOptions(ec2.WithOS(v.descriptor)), + awshost.WithAgentOptions(agentOptions...)), + ) + + // run the check + output := v.Env().Agent.Client.Check(agentclient.WithArgs([]string{"disk", "--json"})) + data := check.ParseJSONOutput(v.T(), []byte(output)) + + require.Len(v.T(), data, 1) + metrics := data[0].Aggregator.Metrics + for i := range metrics { + // remove the disk_check_version tag + tagLen := len(metrics[i].Tags) + metrics[i].Tags = slices.DeleteFunc(metrics[i].Tags, func(tag string) bool { + return tag == diskCheckVersionTag + }) + removedElements := tagLen - len(metrics[i].Tags) + if !assert.Equalf(v.T(), 1, removedElements, "expected tag %s once in metric %s", diskCheckVersion, metrics[i].Metric) { + v.T().Logf("metric: %+v", metrics[i]) + } + } + + return metrics +} diff --git a/test/new-e2e/tests/agent-runtimes/check_disk/disk_nix_test.go b/test/new-e2e/tests/agent-runtimes/check_disk/disk_nix_test.go new file mode 100644 index 00000000000000..c04c29211fd7e0 --- /dev/null +++ b/test/new-e2e/tests/agent-runtimes/check_disk/disk_nix_test.go @@ -0,0 +1,24 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package checkdisk + +import ( + "testing" + + e2eos "github.com/DataDog/test-infra-definitions/components/os" + + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e" +) + +type linuxStatusSuite struct { + baseCheckSuite +} + +func TestLinuxDiskSuite(t *testing.T) { + t.Parallel() + suite := &linuxStatusSuite{baseCheckSuite{descriptor: e2eos.UbuntuDefault, agentOptions: getAgentOptions()}} + e2e.Run(t, suite, suite.getSuiteOptions()...) +} diff --git a/test/new-e2e/tests/agent-runtimes/check_disk/disk_win_test.go b/test/new-e2e/tests/agent-runtimes/check_disk/disk_win_test.go new file mode 100644 index 00000000000000..2f312a973f00ee --- /dev/null +++ b/test/new-e2e/tests/agent-runtimes/check_disk/disk_win_test.go @@ -0,0 +1,24 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package checkdisk + +import ( + "testing" + + e2eos "github.com/DataDog/test-infra-definitions/components/os" + + "github.com/DataDog/datadog-agent/test/new-e2e/pkg/e2e" +) + +type windowsStatusSuite struct { + baseCheckSuite +} + +func TestWindowsDiskSuite(t *testing.T) { + t.Parallel() + suite := &windowsStatusSuite{baseCheckSuite{descriptor: e2eos.WindowsDefault, agentOptions: getAgentOptions()}} + e2e.Run(t, suite, suite.getSuiteOptions()...) +}