internal/hostmetrics/osmetricreader/osmetricreader.go (307 lines of code) (raw):

/* Copyright 2022 Google LLC 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 https://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 osmetricreader contains utlity functions that build collections of protocol buffers that store various VM metrics */ package osmetricreader import ( "fmt" "strconv" "strings" "github.com/GoogleCloudPlatform/sapagent/internal/hostmetrics/agenttime" "github.com/GoogleCloudPlatform/sapagent/internal/hostmetrics/metricsformatter" iipb "github.com/GoogleCloudPlatform/sapagent/protos/instanceinfo" mpb "github.com/GoogleCloudPlatform/sapagent/protos/metrics" statspb "github.com/GoogleCloudPlatform/sapagent/protos/stats" "github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log" ) /* min compares two 64 bit integers and returns whichever one is smaller. */ func min(x, y int64) int64 { if x < y { return x } return y } /* Read returns a collection of metrics read for cpu, disk, network, and memory metrics. */ func Read(cpuStats *statspb.CpuStats, instanceProps *iipb.InstanceProperties, memStats *statspb.MemoryStats, diskStats *statspb.DiskStatsCollection, at agenttime.AgentTime) *mpb.MetricsCollection { log.Logger.Debug("Getting OS metrics...") var metrics []*mpb.Metric metrics = append(metrics, memoryMetrics(memStats, at.Startup().UnixMilli())...) metrics = append(metrics, diskMetrics(diskStats, instanceProps, at.Startup().UnixMilli(), at.LocalRefresh().UnixMilli())...) metrics = append(metrics, networkMetrics(cpuStats, instanceProps, at.Startup().UnixMilli())...) metrics = append(metrics, cpuMetrics(cpuStats, instanceProps, at.Startup().UnixMilli())...) return &mpb.MetricsCollection{Metrics: metrics} } /* memoryMetrics returns a slice of memory metrics from the OS - total memory available and the percentage that is consumed. */ func memoryMetrics(memoryStats *statspb.MemoryStats, startTime int64) []*mpb.Metric { // Memory Consumption defaults to "0.0" instead of unavailable. consumption := float64(0) if memoryStats.GetUsed() > 0 && memoryStats.GetTotal() > 0 { consumption = float64(memoryStats.GetUsed()) / float64(memoryStats.GetTotal()) consumption = metricsformatter.ToPercentage(consumption, 3) } return []*mpb.Metric{ &mpb.Metric{ Name: "Guaranteed Memory assigned", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_MEMORY, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MB, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(memoryStats.GetTotal(), 10), }, &mpb.Metric{ Name: "Memory Consumption", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_MEMORY, Type: mpb.Type_TYPE_DOUBLE, Unit: mpb.Unit_UNIT_PERCENT, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_PER_MINUTE, LastRefresh: startTime, Value: strconv.FormatFloat(consumption, 'f', 1, 64), }, } } func formattedDeviceType(diskType string) string { t := strings.Replace(diskType, "-", "_", -1) return strings.ToUpper(t) } /* diskMetrics returns a slice of disk metrics from the OS. */ func diskMetrics(diskStats *statspb.DiskStatsCollection, instanceProperties *iipb.InstanceProperties, startTime int64, refreshTime int64) []*mpb.Metric { var diskMetrics []*mpb.Metric for _, disk := range instanceProperties.GetDisks() { deviceID := disk.GetDeviceName() if disk.GetDiskName() != "" { deviceID = disk.GetDiskName() } localDiskMapping := localDiskForMapping(diskStats, disk.GetMapping()) volumeQueueLength := fmt.Sprintf("%g", metricsformatter.Unavailable) volumeReadResponseTime := fmt.Sprintf("%g", metricsformatter.Unavailable) volumeWriteResponseTime := fmt.Sprintf("%g", metricsformatter.Unavailable) if localDiskMapping != nil { volumeQueueLength = strconv.FormatInt(localDiskMapping.GetQueueLength(), 10) volumeReadResponseTime = strconv.FormatInt(localDiskMapping.GetAverageReadResponseTimeMillis(), 10) volumeWriteResponseTime = strconv.FormatInt(localDiskMapping.GetAverageWriteResponseTimeMillis(), 10) } diskMetrics = append(diskMetrics, &mpb.Metric{ Name: "Volume ID", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_DISK, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, DeviceId: deviceID, Value: deviceID, }, &mpb.Metric{ Name: "Volume Type", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_DISK, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, DeviceId: deviceID, Value: formattedDeviceType(disk.GetDeviceType()), }, &mpb.Metric{ Name: "Mapping", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_DISK, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, DeviceId: deviceID, Value: disk.GetMapping(), }, &mpb.Metric{ Name: "Volume Queue Length", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_DISK, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_PER_MINUTE, LastRefresh: refreshTime, DeviceId: deviceID, Value: volumeQueueLength, }, &mpb.Metric{ Name: "Volume Read Response Time", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_DISK, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MSEC, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_PER_MINUTE, LastRefresh: refreshTime, DeviceId: deviceID, Value: volumeReadResponseTime, }, &mpb.Metric{ Name: "Volume Write Response Time", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_DISK, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MSEC, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_PER_MINUTE, LastRefresh: refreshTime, DeviceId: deviceID, Value: volumeWriteResponseTime, }) } return diskMetrics } /* networkMetrics returns a slice of network adapter metrics from the OS - adapter ID, adapter device mapping, minimum bandwidth, maximum bandwidth, and the current bandwidth. This sequence of metrics is generated for each network adapter in use by the OS (i.e. {adapterID_1, mapping_1, minBandwidth_1, ..., adapterID_2, mapping_2, ...}). */ func networkMetrics(cpuStats *statspb.CpuStats, instanceProperties *iipb.InstanceProperties, startTime int64) []*mpb.Metric { var networkMetrics []*mpb.Metric for _, networkAdapter := range instanceProperties.GetNetworkAdapters() { adapterID := networkAdapter.GetName() networkMetrics = append(networkMetrics, &mpb.Metric{ Name: "Adapter ID", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_NETWORK, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: adapterID, DeviceId: adapterID, }, ) networkMetrics = append(networkMetrics, &mpb.Metric{ Name: "Mapping", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_NETWORK, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: networkAdapter.GetMapping(), DeviceId: adapterID, }, ) bandwidth := min(16, cpuStats.GetCpuCount()*2) * 1000 networkMetrics = append(networkMetrics, &mpb.Metric{ Name: "Minimum Network Bandwidth", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_NETWORK, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MBPS, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(bandwidth, 10), DeviceId: adapterID, }, &mpb.Metric{ Name: "Maximum Network Bandwidth", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_NETWORK, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MBPS, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(bandwidth, 10), DeviceId: adapterID, }, &mpb.Metric{ Name: "Current Network Bandwidth", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_NETWORK, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MBPS, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(bandwidth, 10), DeviceId: adapterID, }, ) } return networkMetrics } /* cpuMetrics returns a slice of cpu metrics from the OS. */ func cpuMetrics(cpuStats *statspb.CpuStats, instanceProperties *iipb.InstanceProperties, startTime int64) []*mpb.Metric { // CPU metrics. var cpuMetrics []*mpb.Metric if instanceProperties.GetCpuPlatform() != "" { cpuMetrics = []*mpb.Metric{ &mpb.Metric{ Name: "Processor Type", Context: mpb.Context_CONTEXT_HOST, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: instanceProperties.GetCpuPlatform(), }, } } // usually 2 threads per core. var threadsPerCore int64 = 2 if cpuStats.GetCpuCount() > 0 && cpuStats.GetCpuCores() > 9 { threadsPerCore = cpuStats.GetCpuCount() / cpuStats.GetCpuCores() } cpuMetrics = append(cpuMetrics, &mpb.Metric{ Name: "Number of Threads per Core", Context: mpb.Context_CONTEXT_HOST, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(threadsPerCore, 10), }, &mpb.Metric{ Name: "Current HW frequency", Context: mpb.Context_CONTEXT_HOST, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MHZ, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(cpuStats.GetMaxMhz(), 10), }, &mpb.Metric{ Name: "Max. HW frequency", Context: mpb.Context_CONTEXT_HOST, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_INT64, Unit: mpb.Unit_UNIT_MHZ, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(cpuStats.GetMaxMhz(), 10), }, &mpb.Metric{ Name: "Reference Compute Unit [CU]", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_STRING, Unit: mpb.Unit_UNIT_NONE, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: cpuStats.GetProcessorType(), }, &mpb.Metric{ Name: "Phys. Processing Power per vCPU", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_DOUBLE, Unit: mpb.Unit_UNIT_CU, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: "1.00", }, &mpb.Metric{ Name: "Guaranteed VM Processing Power", Context: mpb.Context_CONTEXT_VM, Category: mpb.Category_CATEGORY_CPU, Type: mpb.Type_TYPE_DOUBLE, Unit: mpb.Unit_UNIT_CU, RefreshInterval: mpb.RefreshInterval_REFRESHINTERVAL_RESTART, LastRefresh: startTime, Value: strconv.FormatInt(cpuStats.GetCpuCount(), 10) + ".00", }, ) return cpuMetrics } /* localDiskForMapping returns the disk stats associated with a disk mapping "mapping" from the collection of disk stats "collection". */ func localDiskForMapping(collection *statspb.DiskStatsCollection, mapping string) *statspb.DiskStats { for _, diskStats := range collection.GetDiskStats() { if diskStats.GetDeviceName() == mapping { return diskStats } } return nil }