From 437b4ba1397c3bf10bb541e3d28da81a40f5536e Mon Sep 17 00:00:00 2001 From: Ping Xiang Date: Tue, 15 Jun 2021 06:24:04 +0000 Subject: [PATCH] add extractors for diskio/fs/net metrics --- .../cadvisor/extractors/diskio_extractor.go | 102 +++++++++++ .../extractors/diskio_extractor_test.go | 110 ++++++++++++ .../cadvisor/extractors/fs_extractor.go | 104 +++++++++++ .../cadvisor/extractors/fs_extractor_test.go | 150 ++++++++++++++++ .../cadvisor/extractors/net_extractor.go | 120 +++++++++++++ .../cadvisor/extractors/net_extractor_test.go | 169 ++++++++++++++++++ .../extractors/testdata/FileSystemStat.json | 163 +++++++++++++++++ 7 files changed, 918 insertions(+) create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor.go create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor_test.go create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor.go create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor_test.go create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor.go create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor_test.go create mode 100644 receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/testdata/FileSystemStat.json diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor.go b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor.go new file mode 100644 index 000000000000..5528a2dc53d1 --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor.go @@ -0,0 +1,102 @@ +// Copyright OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extractors + +import ( + "fmt" + "strings" + "time" + + cInfo "github.com/google/cadvisor/info/v1" + "go.uber.org/zap" + + ci "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/containerinsight" + awsmetrics "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/metrics" +) + +type DiskIOMetricExtractor struct { + logger *zap.Logger + rateCalculator awsmetrics.MetricCalculator +} + +func (d *DiskIOMetricExtractor) HasValue(info *cInfo.ContainerInfo) bool { + return info.Spec.HasDiskIo +} + +func (d *DiskIOMetricExtractor) GetValue(info *cInfo.ContainerInfo, _ CPUMemInfoProvider, containerType string) []*CAdvisorMetric { + var metrics []*CAdvisorMetric + if containerType != ci.TypeNode && containerType != ci.TypeInstance { + return metrics + } + + curStats := GetStats(info) + metrics = append(metrics, d.extractIoMetrics(curStats.DiskIo.IoServiceBytes, ci.DiskIOServiceBytesPrefix, containerType, info.Name, curStats.Timestamp)...) + metrics = append(metrics, d.extractIoMetrics(curStats.DiskIo.IoServiced, ci.DiskIOServicedPrefix, containerType, info.Name, curStats.Timestamp)...) + return metrics +} + +func (d *DiskIOMetricExtractor) extractIoMetrics(curStatsSet []cInfo.PerDiskStats, namePrefix string, containerType string, infoName string, curTime time.Time) []*CAdvisorMetric { + var metrics []*CAdvisorMetric + expectedKey := []string{ci.DiskIOAsync, ci.DiskIOSync, ci.DiskIORead, ci.DiskIOWrite, ci.DiskIOTotal} + for _, cur := range curStatsSet { + curDevName := devName(cur) + metric := newCadvisorMetric(getDiskIOMetricType(containerType, d.logger), d.logger) + metric.tags[ci.DiskDev] = curDevName + for _, key := range expectedKey { + if curVal, curOk := cur.Stats[key]; curOk { + mname := ci.MetricName(containerType, ioMetricName(namePrefix, key)) + assignRateValueToField(&d.rateCalculator, metric.fields, mname, infoName, float64(curVal), curTime, float64(time.Second)) + } + } + if len(metric.fields) > 0 { + metrics = append(metrics, metric) + } + } + return metrics +} + +func ioMetricName(prefix, key string) string { + return prefix + strings.ToLower(key) +} + +func devName(dStats cInfo.PerDiskStats) string { + devName := dStats.Device + if devName == "" { + devName = fmt.Sprintf("%d:%d", dStats.Major, dStats.Minor) + } + return devName +} + +func NewDiskIOMetricExtractor(logger *zap.Logger) *DiskIOMetricExtractor { + return &DiskIOMetricExtractor{ + logger: logger, + rateCalculator: newFloat64RateCalculator(), + } +} + +func getDiskIOMetricType(containerType string, logger *zap.Logger) string { + metricType := "" + switch containerType { + case ci.TypeNode: + metricType = ci.TypeNodeDiskIO + case ci.TypeInstance: + metricType = ci.TypeInstanceDiskIO + case ci.TypeContainer: + metricType = ci.TypeContainerDiskIO + default: + logger.Warn("diskio_extractor: diskIO metric extractor is parsing unexpected containerType", zap.String("containerType", containerType)) + } + return metricType +} diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor_test.go b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor_test.go new file mode 100644 index 000000000000..179bcb4e88b3 --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/diskio_extractor_test.go @@ -0,0 +1,110 @@ +// Copyright OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extractors + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + . "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/containerinsight" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awscontainerinsightreceiver/internal/cadvisor/testutils" +) + +func TestDiskIOStats(t *testing.T) { + + result := testutils.LoadContainerInfo(t, "./testdata/PreInfoContainer.json") + result2 := testutils.LoadContainerInfo(t, "./testdata/CurInfoContainer.json") + //for eks node-level metrics + containerType := TypeNode + extractor := NewDiskIOMetricExtractor(nil) + + var cMetrics []*CAdvisorMetric + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + + if extractor.HasValue(result2[0]) { + cMetrics = extractor.GetValue(result2[0], nil, containerType) + } + + expectedFieldsService := map[string]interface{}{ + "node_diskio_io_service_bytes_write": float64(10000), + "node_diskio_io_service_bytes_total": float64(10010), + "node_diskio_io_service_bytes_async": float64(10000), + "node_diskio_io_service_bytes_sync": float64(10000), + "node_diskio_io_service_bytes_read": float64(10), + } + expectedFieldsServiced := map[string]interface{}{ + "node_diskio_io_serviced_async": float64(10), + "node_diskio_io_serviced_sync": float64(10), + "node_diskio_io_serviced_read": float64(10), + "node_diskio_io_serviced_write": float64(10), + "node_diskio_io_serviced_total": float64(20), + } + expectedTags := map[string]string{ + "device": "/dev/xvda", + "Type": "NodeDiskIO", + } + AssertContainsTaggedField(t, cMetrics[0], expectedFieldsService, expectedTags) + AssertContainsTaggedField(t, cMetrics[1], expectedFieldsServiced, expectedTags) + + //for ecs node-level metrics + containerType = TypeInstance + extractor = NewDiskIOMetricExtractor(nil) + + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + + if extractor.HasValue(result2[0]) { + cMetrics = extractor.GetValue(result2[0], nil, containerType) + } + + expectedFieldsService = map[string]interface{}{ + "instance_diskio_io_service_bytes_write": float64(10000), + "instance_diskio_io_service_bytes_total": float64(10010), + "instance_diskio_io_service_bytes_async": float64(10000), + "instance_diskio_io_service_bytes_sync": float64(10000), + "instance_diskio_io_service_bytes_read": float64(10), + } + expectedFieldsServiced = map[string]interface{}{ + "instance_diskio_io_serviced_async": float64(10), + "instance_diskio_io_serviced_sync": float64(10), + "instance_diskio_io_serviced_read": float64(10), + "instance_diskio_io_serviced_write": float64(10), + "instance_diskio_io_serviced_total": float64(20), + } + expectedTags = map[string]string{ + "device": "/dev/xvda", + "Type": "InstanceDiskIO", + } + AssertContainsTaggedField(t, cMetrics[0], expectedFieldsService, expectedTags) + AssertContainsTaggedField(t, cMetrics[1], expectedFieldsServiced, expectedTags) + + //for non supported type + containerType = TypeContainerDiskIO + extractor = NewDiskIOMetricExtractor(nil) + + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + + if extractor.HasValue(result2[0]) { + cMetrics = extractor.GetValue(result2[0], nil, containerType) + } + + assert.Equal(t, len(cMetrics), 0) +} diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor.go b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor.go new file mode 100644 index 000000000000..108a45e3c9cd --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor.go @@ -0,0 +1,104 @@ +// Copyright OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extractors + +import ( + "regexp" + + cinfo "github.com/google/cadvisor/info/v1" + "go.uber.org/zap" + + ci "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/containerinsight" +) + +const ( + allowList = "^tmpfs$|^/dev/|^overlay$" +) + +type FileSystemMetricExtractor struct { + allowListRegexP *regexp.Regexp + logger *zap.Logger +} + +func (f *FileSystemMetricExtractor) HasValue(info *cinfo.ContainerInfo) bool { + return info.Spec.HasFilesystem +} + +func (f *FileSystemMetricExtractor) GetValue(info *cinfo.ContainerInfo, _ CPUMemInfoProvider, containerType string) []*CAdvisorMetric { + var metrics []*CAdvisorMetric + if containerType == ci.TypePod || info.Spec.Labels[containerNameLable] == infraContainerName { + return metrics + } + + containerType = getFSMetricType(containerType, f.logger) + stats := GetStats(info) + + for _, v := range stats.Filesystem { + metric := newCadvisorMetric(containerType, f.logger) + if v.Device == "" { + continue + } + if f.allowListRegexP != nil && !f.allowListRegexP.MatchString(v.Device) { + continue + } + + metric.tags[ci.DiskDev] = v.Device + metric.tags[ci.FSType] = v.Type + + metric.fields[ci.MetricName(containerType, ci.FSUsage)] = v.Usage + metric.fields[ci.MetricName(containerType, ci.FSCapacity)] = v.Limit + metric.fields[ci.MetricName(containerType, ci.FSAvailable)] = v.Available + + if v.Limit != 0 { + metric.fields[ci.MetricName(containerType, ci.FSUtilization)] = float64(v.Usage) / float64(v.Limit) * 100 + } + + if v.HasInodes { + metric.fields[ci.MetricName(containerType, ci.FSInodes)] = v.Inodes + metric.fields[ci.MetricName(containerType, ci.FSInodesfree)] = v.InodesFree + } + + metrics = append(metrics, metric) + } + return metrics +} + +func NewFileSystemMetricExtractor(logger *zap.Logger) *FileSystemMetricExtractor { + fse := &FileSystemMetricExtractor{ + logger: logger, + } + if p, err := regexp.Compile(allowList); err == nil { + fse.allowListRegexP = p + } else { + logger.Error("NewFileSystemMetricExtractor set regex failed", zap.Error(err)) + } + + return fse +} + +func getFSMetricType(containerType string, logger *zap.Logger) string { + metricType := "" + switch containerType { + case ci.TypeNode: + metricType = ci.TypeNodeFS + case ci.TypeInstance: + metricType = ci.TypeInstanceFS + case ci.TypeContainer: + metricType = ci.TypeContainerFS + default: + logger.Warn("fs_extractor: fs metric extractor is parsing unexpected containerType", zap.String("containerType", containerType)) + } + return metricType +} diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor_test.go b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor_test.go new file mode 100644 index 000000000000..5e823b3f15e0 --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/fs_extractor_test.go @@ -0,0 +1,150 @@ +// Copyright OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extractors + +import ( + "bytes" + "encoding/json" + "testing" + + cinfo "github.com/google/cadvisor/info/v1" + "github.com/stretchr/testify/assert" + + . "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/containerinsight" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awscontainerinsightreceiver/internal/cadvisor/testutils" +) + +func TestFSStats(t *testing.T) { + result := testutils.LoadContainerInfo(t, "./testdata/CurInfoContainer.json") + //container type + containerType := TypeContainer + extractor := NewFileSystemMetricExtractor(nil) + + var cMetrics []*CAdvisorMetric + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + + expectedFields := map[string]interface{}{ + "container_filesystem_usage": uint64(25661440), + "container_filesystem_capacity": uint64(21462233088), + "container_filesystem_available": uint64(0), + "container_filesystem_utilization": float64(0.11956556381986117), + } + expectedTags := map[string]string{ + "device": "/dev/xvda1", + "fstype": "vfs", + "Type": "ContainerFS", + } + AssertContainsTaggedField(t, cMetrics[0], expectedFields, expectedTags) + + //pod type + containerType = TypePod + extractor = NewFileSystemMetricExtractor(nil) + + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + + assert.Equal(t, len(cMetrics), 0) + + //node type for eks + + result2 := testutils.LoadContainerInfo(t, "./testdata/CurInfoNode.json") + containerType = TypeNode + extractor = NewFileSystemMetricExtractor(nil) + + if extractor.HasValue(result2[0]) { + cMetrics = extractor.GetValue(result2[0], nil, containerType) + } + + expectedFields = map[string]interface{}{ + "node_filesystem_available": uint64(67108864), + "node_filesystem_capacity": uint64(67108864), + "node_filesystem_inodes": uint64(2052980), + "node_filesystem_inodes_free": uint64(2052979), + "node_filesystem_usage": uint64(0), + "node_filesystem_utilization": float64(0), + } + expectedTags = map[string]string{ + "device": "/dev/shm", + "fstype": "vfs", + "Type": "NodeFS", + } + AssertContainsTaggedField(t, cMetrics[0], expectedFields, expectedTags) + + expectedFields = map[string]interface{}{ + "node_filesystem_available": uint64(6925574144), + "node_filesystem_capacity": uint64(21462233088), + "node_filesystem_inodes": uint64(10484672), + "node_filesystem_inodes_free": uint64(10387672), + "node_filesystem_usage": uint64(14536658944), + "node_filesystem_utilization": float64(67.73134409824186), + } + expectedTags = map[string]string{ + "device": "/dev/xvda1", + "fstype": "vfs", + "Type": "NodeFS", + } + AssertContainsTaggedField(t, cMetrics[1], expectedFields, expectedTags) + + expectedFields = map[string]interface{}{ + "node_filesystem_available": uint64(10682417152), + "node_filesystem_capacity": uint64(10726932480), + "node_filesystem_inodes": uint64(5242880), + "node_filesystem_inodes_free": uint64(5242877), + "node_filesystem_usage": uint64(44515328), + "node_filesystem_utilization": float64(0.4149865591397849), + } + expectedTags = map[string]string{ + "device": "/dev/xvdce", + "fstype": "vfs", + "Type": "NodeFS", + } + AssertContainsTaggedField(t, cMetrics[2], expectedFields, expectedTags) +} + +func TestAllowList(t *testing.T) { + extractor := NewFileSystemMetricExtractor(nil) + assert.Equal(t, true, extractor.allowListRegexP.MatchString("/dev/shm")) + assert.Equal(t, true, extractor.allowListRegexP.MatchString("tmpfs")) + assert.Equal(t, true, extractor.allowListRegexP.MatchString("overlay")) + assert.Equal(t, false, extractor.allowListRegexP.MatchString("overlaytest")) + assert.Equal(t, false, extractor.allowListRegexP.MatchString("/dev")) +} + +func TestFSStatsWithAllowList(t *testing.T) { + var result []*cinfo.ContainerInfo + containerInfos := testutils.LoadContainerInfo(t, "./testdata/FileSystemStat.json") + result = append(result, containerInfos...) + + var b bytes.Buffer + enc := json.NewEncoder(&b) + enc.Encode(result) + containerType := TypeContainer + extractor := NewFileSystemMetricExtractor(nil) + + var cMetrics []*CAdvisorMetric + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + + // There are 3 valid device names which pass the allowlist in testAllowList json. + assert.Equal(t, 3, len(cMetrics)) + assert.Equal(t, "tmpfs", cMetrics[0].tags["device"]) + assert.Equal(t, "/dev/xvda1", cMetrics[1].tags["device"]) + assert.Equal(t, "overlay", cMetrics[2].tags["device"]) + +} diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor.go b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor.go new file mode 100644 index 000000000000..3bcde959bc48 --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor.go @@ -0,0 +1,120 @@ +// Copyright OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extractors + +import ( + "time" + + cinfo "github.com/google/cadvisor/info/v1" + "go.uber.org/zap" + + ci "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/containerinsight" + awsmetrics "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/metrics" +) + +type NetMetricExtractor struct { + logger *zap.Logger + rateCalculator awsmetrics.MetricCalculator +} + +func getInterfacesStats(stats *cinfo.ContainerStats) []cinfo.InterfaceStats { + ifceStats := stats.Network.Interfaces + if len(ifceStats) == 0 { + ifceStats = []cinfo.InterfaceStats{stats.Network.InterfaceStats} + } + return ifceStats +} + +func (n *NetMetricExtractor) HasValue(info *cinfo.ContainerInfo) bool { + return info.Spec.HasNetwork +} + +func (n *NetMetricExtractor) GetValue(info *cinfo.ContainerInfo, _ CPUMemInfoProvider, containerType string) []*CAdvisorMetric { + var metrics []*CAdvisorMetric + + // Just a protection here, there is no Container level Net metrics + if (containerType == ci.TypePod && info.Spec.Labels[containerNameLable] != infraContainerName) || containerType == ci.TypeContainer { + return metrics + } + + curStats := GetStats(info) + curIfceStats := getInterfacesStats(curStats) + + // used for aggregation + var netIfceMetrics []map[string]interface{} + + for _, cur := range curIfceStats { + mType := getNetMetricType(containerType, n.logger) + netIfceMetric := make(map[string]interface{}) + + infoName := info.Name + containerType + cur.Name //used to identify the network interface + multiplier := float64(time.Second) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetRxBytes, infoName, float64(cur.RxBytes), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetRxPackets, infoName, float64(cur.RxPackets), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetRxDropped, infoName, float64(cur.RxDropped), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetRxErrors, infoName, float64(cur.RxErrors), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetTxBytes, infoName, float64(cur.TxBytes), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetTxPackets, infoName, float64(cur.TxPackets), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetTxDropped, infoName, float64(cur.TxDropped), curStats.Timestamp, multiplier) + assignRateValueToField(&n.rateCalculator, netIfceMetric, ci.NetTxErrors, infoName, float64(cur.TxErrors), curStats.Timestamp, multiplier) + + if netIfceMetric[ci.NetRxBytes] != nil && netIfceMetric[ci.NetTxBytes] != nil { + netIfceMetric[ci.NetTotalBytes] = netIfceMetric[ci.NetRxBytes].(float64) + netIfceMetric[ci.NetTxBytes].(float64) + } + + netIfceMetrics = append(netIfceMetrics, netIfceMetric) + + metric := newCadvisorMetric(mType, n.logger) + metric.tags[ci.NetIfce] = cur.Name + for k, v := range netIfceMetric { + metric.fields[ci.MetricName(mType, k)] = v + } + + metrics = append(metrics, metric) + } + + aggregatedFields := ci.SumFields(netIfceMetrics) + if len(aggregatedFields) > 0 { + metric := newCadvisorMetric(containerType, n.logger) + for k, v := range aggregatedFields { + metric.fields[ci.MetricName(containerType, k)] = v + } + metrics = append(metrics, metric) + } + + return metrics +} + +func NewNetMetricExtractor(logger *zap.Logger) *NetMetricExtractor { + return &NetMetricExtractor{ + logger: logger, + rateCalculator: newFloat64RateCalculator(), + } +} + +func getNetMetricType(containerType string, logger *zap.Logger) string { + metricType := "" + switch containerType { + case ci.TypeNode: + metricType = ci.TypeNodeNet + case ci.TypeInstance: + metricType = ci.TypeInstanceNet + case ci.TypePod: + metricType = ci.TypePodNet + default: + logger.Warn("net_extractor: net metric extractor is parsing unexpected containerType", zap.String("containerType", containerType)) + } + return metricType +} diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor_test.go b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor_test.go new file mode 100644 index 000000000000..40989927222c --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/net_extractor_test.go @@ -0,0 +1,169 @@ +// Copyright OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extractors + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + ci "github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/containerinsight" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awscontainerinsightreceiver/internal/cadvisor/testutils" +) + +func TestNetStats(t *testing.T) { + result := testutils.LoadContainerInfo(t, "./testdata/PreInfoNode.json") + result2 := testutils.LoadContainerInfo(t, "./testdata/CurInfoNode.json") + + containerType := ci.TypeNode + extractor := NewNetMetricExtractor(nil) + var cMetrics []*CAdvisorMetric + if extractor.HasValue(result[0]) { + cMetrics = extractor.GetValue(result[0], nil, containerType) + } + if extractor.HasValue(result2[0]) { + cMetrics = extractor.GetValue(result2[0], nil, containerType) + } + + expectedFields := []map[string]interface{}{ + { + "node_interface_network_rx_bytes": float64(382.28706877648807), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(2.2498322426788007), + "node_interface_network_total_bytes": float64(2644.3827413026775), + "node_interface_network_tx_bytes": float64(2262.0956725261894), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(2.2867147384604203), + }, + { + "node_interface_network_rx_bytes": float64(0), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(0), + "node_interface_network_total_bytes": float64(0), + "node_interface_network_tx_bytes": float64(0), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(0), + }, + { + "node_interface_network_rx_bytes": float64(265.24046841351793), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(2.397362225805279), + "node_interface_network_total_bytes": float64(4050.730746703727), + "node_interface_network_tx_bytes": float64(3785.490278290209), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(2.5264509610409482), + }, + { + "node_interface_network_rx_bytes": float64(7818.037954573597), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(19.197339054333046), + "node_interface_network_total_bytes": float64(10891.751388022218), + "node_interface_network_tx_bytes": float64(3073.713433448621), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(18.21995291612012), + }, + { + "node_interface_network_rx_bytes": float64(319.3286484772632), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(3.632925834489539), + "node_interface_network_total_bytes": float64(1910.8452239499343), + "node_interface_network_tx_bytes": float64(1591.516575472671), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(3.651367082380349), + }, + { + "node_interface_network_rx_bytes": float64(1616.061876415339), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(1.862566036971794), + "node_interface_network_total_bytes": float64(1748.9863912122962), + "node_interface_network_tx_bytes": float64(132.92451479695734), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(1.7150360538453153), + }, + { + "node_interface_network_rx_bytes": float64(268.76274676066265), + "node_interface_network_rx_dropped": float64(0), + "node_interface_network_rx_errors": float64(0), + "node_interface_network_rx_packets": float64(2.6370984483858075), + "node_interface_network_total_bytes": float64(1131.8131480505633), + "node_interface_network_tx_bytes": float64(863.0504012899006), + "node_interface_network_tx_dropped": float64(0), + "node_interface_network_tx_errors": float64(0), + "node_interface_network_tx_packets": float64(2.6370984483858075), + }, + { + "node_network_rx_bytes": float64(10669.718763416868), + "node_network_rx_dropped": float64(0), + "node_network_rx_errors": float64(0), + "node_network_rx_packets": float64(31.977123842664266), + "node_network_total_bytes": float64(22378.509639241416), + "node_network_tx_bytes": float64(11708.790875824548), + "node_network_tx_dropped": float64(0), + "node_network_tx_errors": float64(0), + "node_network_tx_packets": float64(31.03662020023296), + }, + } + + expectedTags := []map[string]string{ + { + "Type": "NodeNet", + "interface": "eni2bbf9bbc6ab", + }, + { + "Type": "NodeNet", + "interface": "eni5b727305f03", + }, + { + "Type": "NodeNet", + "interface": "enic36ed3f6bb5", + }, + { + "Type": "NodeNet", + "interface": "eth0", + }, + { + "Type": "NodeNet", + "interface": "eni40c8ef3f6c3", + }, + { + "Type": "NodeNet", + "interface": "eth1", + }, + { + "Type": "NodeNet", + "interface": "eni7cce1b61ea4", + }, + { + "Type": "Node", + }, + } + + assert.Equal(t, len(cMetrics), 8) + for i := range expectedFields { + AssertContainsTaggedField(t, cMetrics[i], expectedFields[i], expectedTags[i]) + } +} diff --git a/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/testdata/FileSystemStat.json b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/testdata/FileSystemStat.json new file mode 100644 index 000000000000..81bbb967af50 --- /dev/null +++ b/receiver/awscontainerinsightreceiver/internal/cadvisor/extractors/testdata/FileSystemStat.json @@ -0,0 +1,163 @@ +{ + "/kubepods/besteffort/podaf16b540-4ae2-11e9-977b-0672b6c6fc94/573ee6cd04a6208af809b2329652c74386f1992faca8662c733d7f250014e718": { + "id": "573ee6cd04a6208af809b2329652c74386f1992faca8662c733d7f250014e718", + "name": "/kubepods/besteffort/podaf16b540-4ae2-11e9-977b-0672b6c6fc94/573ee6cd04a6208af809b2329652c74386f1992faca8662c733d7f250014e718", + "aliases": [ + "k8s_ubuntu_stress-1-core-mh2pn_default_af16b540-4ae2-11e9-977b-0672b6c6fc94_0", + "573ee6cd04a6208af809b2329652c74386f1992faca8662c733d7f250014e718" + ], + "namespace": "docker", + "spec": { + "creation_time": "2019-03-20T07:35:09.746280405Z", + "labels": { + "annotation.io.kubernetes.container.hash": "70bfcd85", + "annotation.io.kubernetes.container.restartCount": "0", + "annotation.io.kubernetes.container.terminationMessagePath": "/dev/termination-log", + "annotation.io.kubernetes.container.terminationMessagePolicy": "File", + "annotation.io.kubernetes.pod.terminationGracePeriod": "30", + "io.kubernetes.container.logpath": "/var/log/pods/af16b540-4ae2-11e9-977b-0672b6c6fc94/ubuntu/0.log", + "io.kubernetes.container.name": "ubuntu", + "io.kubernetes.docker.type": "container", + "io.kubernetes.pod.name": "stress-1-core-mh2pn", + "io.kubernetes.pod.namespace": "default", + "io.kubernetes.pod.uid": "af16b540-4ae2-11e9-977b-0672b6c6fc94", + "io.kubernetes.sandbox.id": "a5bb552d7fb8e5014468756f165732e0c6bcd9dcbd229efc51afc014317d20d6" + }, + "has_cpu": true, + "cpu": { + "limit": 2, + "max_limit": 0, + "mask": "0-3", + "period": 100000 + }, + "has_memory": true, + "memory": { + "limit": 9223372036854771712, + "reservation": 9223372036854771712, + "swap_limit": 9223372036854771712 + }, + "has_network": false, + "has_filesystem": true, + "has_diskio": false, + "has_custom_metrics": false, + "image": "ubuntu@sha256:017eef0b616011647b269b5c65826e2e2ebddbe5d1f8c1e56b3599fb14fabec8" + }, + "stats": [ + { + "timestamp": "2019-04-09T22:26:42.984081498Z", + "filesystem": [ + { + "device": "tmpfs", + "type": "vfs", + "capacity": 21462233088, + "usage": 25661440, + "base_usage": 25640960, + "available": 0, + "has_inodes": false, + "inodes": 67, + "inodes_free": 0, + "reads_completed": 0, + "reads_merged": 0, + "sectors_read": 0, + "read_time": 0, + "writes_completed": 0, + "writes_merged": 0, + "sectors_written": 0, + "write_time": 0, + "io_in_progress": 0, + "io_time": 0, + "weighted_io_time": 0 + }, + { + "device": "/dev/xvda1", + "type": "vfs", + "capacity": 21462233088, + "usage": 25661440, + "base_usage": 25640960, + "available": 0, + "has_inodes": false, + "inodes": 67, + "inodes_free": 0, + "reads_completed": 0, + "reads_merged": 0, + "sectors_read": 0, + "read_time": 0, + "writes_completed": 0, + "writes_merged": 0, + "sectors_written": 0, + "write_time": 0, + "io_in_progress": 0, + "io_time": 0, + "weighted_io_time": 0 + }, + { + "device": "overlay", + "type": "vfs", + "capacity": 21462233088, + "usage": 25661440, + "base_usage": 25640960, + "available": 0, + "has_inodes": false, + "inodes": 67, + "inodes_free": 0, + "reads_completed": 0, + "reads_merged": 0, + "sectors_read": 0, + "read_time": 0, + "writes_completed": 0, + "writes_merged": 0, + "sectors_written": 0, + "write_time": 0, + "io_in_progress": 0, + "io_time": 0, + "weighted_io_time": 0 + }, + { + "device": "/dev", + "type": "vfs", + "capacity": 21462233088, + "usage": 25661440, + "base_usage": 25640960, + "available": 0, + "has_inodes": false, + "inodes": 67, + "inodes_free": 0, + "reads_completed": 0, + "reads_merged": 0, + "sectors_read": 0, + "read_time": 0, + "writes_completed": 0, + "writes_merged": 0, + "sectors_written": 0, + "write_time": 0, + "io_in_progress": 0, + "io_time": 0, + "weighted_io_time": 0 + }, + { + "device": "overlaytest", + "type": "vfs", + "capacity": 21462233088, + "usage": 25661440, + "base_usage": 25640960, + "available": 0, + "has_inodes": false, + "inodes": 67, + "inodes_free": 0, + "reads_completed": 0, + "reads_merged": 0, + "sectors_read": 0, + "read_time": 0, + "writes_completed": 0, + "writes_merged": 0, + "sectors_written": 0, + "write_time": 0, + "io_in_progress": 0, + "io_time": 0, + "weighted_io_time": 0 + } + ] + } + ] + } +} \ No newline at end of file