| // Copyright 2024 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 manager | |
| import ( | |
| "context" | |
| "fmt" | |
| "time" | |
| "github.com/GoogleCloudPlatform/galog" | |
| acpb "github.com/GoogleCloudPlatform/google-guest-agent/internal/acp/proto/google_guest_agent/acp" | |
| "github.com/GoogleCloudPlatform/google-guest-agent/internal/ps" | |
| tpb "google.golang.org/protobuf/types/known/timestamppb" | |
| ) | |
| // Metric is a struct to store the plugin's current memory and CPU usage at a | |
| // specific timestamp. | |
| type Metric struct { | |
| // timestamp is the time when the metric is recorded. | |
| timestamp *tpb.Timestamp | |
| // memoryUsage is the memory usage of the plugin at the timestamp in kB. | |
| memoryUsage int64 | |
| // cpuUsage is the percent CPU usage of the plugin at the timestamp. | |
| cpuUsage float32 | |
| } | |
| // PluginMetrics is a struct to monitor and store a plugin's metrics. | |
| type PluginMetrics struct { | |
| // plugin is the plugin to be monitored. | |
| plugin *Plugin | |
| // interval is the interval of getting the plugin's metrics. | |
| interval time.Duration | |
| } | |
| // NewPluginMetrics creates a new PluginMetrics. | |
| func NewPluginMetrics(plugin *Plugin, interval time.Duration) *PluginMetrics { | |
| return &PluginMetrics{plugin: plugin, interval: interval} | |
| } | |
| // ID returns the ID of the plugin metric. | |
| func (p *PluginMetrics) ID() string { | |
| return fmt.Sprintf("%s-metrics", p.plugin.FullName()) | |
| } | |
| // MetricName returns the metric name for the job. | |
| func (p *PluginMetrics) MetricName() acpb.GuestAgentModuleMetric_Metric { | |
| return acpb.GuestAgentModuleMetric_MODULE_UNSPECIFIED | |
| } | |
| // Interval returns the interval of the getting plugin metrics. | |
| func (p *PluginMetrics) Interval() (time.Duration, bool) { | |
| return p.interval, true | |
| } | |
| // ShouldEnable returns true if this job should be scheduled or not by the | |
| // scheduler. | |
| func (*PluginMetrics) ShouldEnable(ctx context.Context) bool { | |
| return true | |
| } | |
| // Run gets and caches the plugin's metrics. | |
| func (p *PluginMetrics) Run(ctx context.Context) (bool, error) { | |
| currentState := p.plugin.State() | |
| if currentState != acpb.CurrentPluginStates_RUNNING { | |
| // Skip metric collection if process is not in running state. Reading | |
| // [/proc] for example on Linux would fail anyways. | |
| return true, fmt.Errorf("plugin %q found in state %v, skipping metric collection", p.plugin.FullName(), currentState) | |
| } | |
| pid := p.plugin.pid() | |
| if pid == 0 { | |
| // Stop metric collection if pid found as 0. This can happen if the plugin | |
| // is being stopped/removed and previous scheduled metric collection job | |
| // happened to attempt metric collection at the same time. | |
| return false, fmt.Errorf("plugin %q is being stopped/removed, got pid 0, skipping metric collection", p.plugin.FullName()) | |
| } | |
| galog.V(2).Debugf("Getting CPU and memory usage for plugin %s", p.plugin.FullName()) | |
| currentCPUUsage, err := ps.CPUUsage(ctx, pid) | |
| if err != nil { | |
| galog.Warnf("Failed to get CPU usage for plugin %s: %v", p.plugin.FullName(), err) | |
| } | |
| currentMemoryUsage, err := ps.Memory(pid) | |
| if err != nil { | |
| galog.Warnf("Failed to get memory usage for plugin %s: %v", p.plugin.FullName(), err) | |
| } | |
| galog.V(3).Debugf("CPU usage for plugin %s: %f%%", p.plugin.FullName(), currentCPUUsage) | |
| galog.V(3).Debugf("Memory usage for plugin %s: %d kB", p.plugin.FullName(), currentMemoryUsage) | |
| p.plugin.RuntimeInfo.metricsMu.Lock() | |
| defer p.plugin.RuntimeInfo.metricsMu.Unlock() | |
| // Cache the metrics. | |
| p.plugin.RuntimeInfo.metrics.Add(Metric{ | |
| timestamp: tpb.Now(), | |
| memoryUsage: int64(currentMemoryUsage), | |
| cpuUsage: float32(currentCPUUsage), | |
| }) | |
| return true, nil | |
| } |