internal/processmetrics/hanavolume/hanavolume.go (122 lines of code) (raw):
/*
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 hanavolume is responsible for collection of HANA Volume metrics.
package hanavolume
import (
"context"
"path"
"strings"
"github.com/cenkalti/backoff/v4"
"github.com/GoogleCloudPlatform/sapagent/internal/utils/protostruct"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/cloudmonitoring"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/commandlineexecutor"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log"
"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/timeseries"
mrpb "google.golang.org/genproto/googleapis/monitoring/v3"
tspb "google.golang.org/protobuf/types/known/timestamppb"
cnfpb "github.com/GoogleCloudPlatform/sapagent/protos/configuration"
)
// Properties struct contains the parameters necessary for hanavolume package common methods.
type Properties struct {
Executor commandlineexecutor.Execute
Config *cnfpb.Configuration
Client cloudmonitoring.TimeSeriesCreator
CommandParams commandlineexecutor.Params
PMBackoffPolicy backoff.BackOffContext
}
const (
metricURL = "workload.googleapis.com"
volumePath = "/sap/hana/volumes"
hanaLog = "/hana/log"
hanaShared = "/hana/shared"
hanaBackup = "/hanabackup"
hanaData = "/hana/data"
usrSap = "/usr/sap"
)
var mountPaths = map[string]string{
hanaLog: hanaLog,
hanaShared: hanaShared,
hanaBackup: hanaBackup,
hanaData: hanaData,
usrSap: usrSap,
}
/*
Collect is an implementation of Collector interface defined in processmetrics.go.
Collect method collects metrics to show the details about Hana logical volumes
including total size, available space, amount used and usage percentage, for each volumes.
*/
func (p *Properties) Collect(ctx context.Context) ([]*mrpb.TimeSeries, error) {
log.CtxLogger(ctx).Debug("Collecting hana volume metrics")
result := p.Executor(ctx, commandlineexecutor.Params{
Executable: p.CommandParams.Executable,
ArgsToSplit: p.CommandParams.ArgsToSplit,
})
if result.Error != nil {
log.CtxLogger(ctx).Debugw("could not execute df -h command", "error", result.Error)
return nil, result.Error
}
return p.createTSList(ctx, result.StdOut), nil
}
// CollectWithRetry decorates the Collect method with retry mechanism.
func (p *Properties) CollectWithRetry(ctx context.Context) ([]*mrpb.TimeSeries, error) {
attempt := 1
var res []*mrpb.TimeSeries
err := backoff.Retry(func() error {
select {
case <-ctx.Done():
log.CtxLogger(ctx).Debugw("Context cancelled, exiting CollectWithRetry")
return nil
default:
var err error
res, err = p.Collect(ctx)
if err != nil {
log.CtxLogger(ctx).Debugw("Error in Collection", "attempt", attempt, "error", err)
attempt++
}
return err
}
}, p.PMBackoffPolicy)
if err != nil {
log.CtxLogger(ctx).Infow("Retry limit exceeded", "error", err)
}
return res, err
}
// createTSList creates a slice of TimeSeries HANA logical volume metrics
// to send to cloud monitoring.
func (p *Properties) createTSList(ctx context.Context, cmdOutput string) []*mrpb.TimeSeries {
var metrics []*mrpb.TimeSeries
lines := strings.Split(cmdOutput, "\n")
for _, line := range lines {
items := strings.Fields(line)
if len(items) == 0 {
continue
}
if path, ok := mountPaths[items[len(items)-1]]; ok {
if len(items) < 6 {
log.CtxLogger(ctx).Debugw("too few items. need exactly 6", "length:", len(items), "line:", line)
continue
}
size := items[1]
used := items[2]
avail := items[3]
usage := items[4]
volMetrics := p.collectVolumeMetrics(ctx, path, size, used, avail, usage)
if volMetrics != nil {
metrics = append(metrics, volMetrics...)
}
}
}
log.CtxLogger(ctx).Debug("HANA Volume Metrics created", metrics)
return metrics
}
func (p *Properties) collectVolumeMetrics(ctx context.Context, path, size, used, avail, usage string) []*mrpb.TimeSeries {
labels := map[string]string{
"mountPath": path,
"size": size,
"used": used,
"avail": avail,
"usage": usage,
}
return []*mrpb.TimeSeries{p.createMetric(labels)}
}
func (p *Properties) createMetric(labels map[string]string) *mrpb.TimeSeries {
log.Logger.Debugw("Creating metric for instance", "metric", labels["mountPath"], "value", true, "labels", labels)
ts := timeseries.Params{
CloudProp: protostruct.ConvertCloudPropertiesToStruct(p.Config.CloudProperties),
MetricType: path.Join(metricURL, volumePath),
MetricLabels: labels,
Timestamp: tspb.Now(),
BareMetal: p.Config.BareMetal,
BoolValue: true,
}
log.Logger.Debug("Created metric path: ", ts.MetricType)
return timeseries.BuildBool(ts)
}