internal/processmetrics/netweaver/netweaver.go (561 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 netweaver contains functions that gather SAP NetWeaver system metrics for the SAP agent. package netweaver import ( "bufio" "context" "fmt" "io" "net/http" "regexp" "strconv" "strings" "time" "github.com/cenkalti/backoff/v4" "github.com/GoogleCloudPlatform/sapagent/internal/processmetrics/sapcontrol" "github.com/GoogleCloudPlatform/sapagent/internal/sapcontrolclient" "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/metricevents" "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" sapb "github.com/GoogleCloudPlatform/sapagent/protos/sapapp" ) type ( // InstanceProperties struct has necessary context for Metrics collection. InstanceProperties struct { SAPInstance *sapb.SAPInstance Config *cnfpb.Configuration Client cloudmonitoring.TimeSeriesCreator SkippedMetrics map[string]bool PMBackoffPolicy backoff.BackOffContext } ) // Netweaver system availability. const ( systemAtLeastOneProcessNotGreen = 0 systemAllProcessesGreen = 1 ) const ( metricURL = "workload.googleapis.com" nwServicePath = "/sap/nw/service" nwICMRCodePath = "/sap/nw/icm/rcode" nwICMRTimePath = "/sap/nw/icm/rtime" nwMSResponseCodePath = "/sap/nw/ms/rcode" nwMSResponseTimePath = "/sap/nw/ms/rtime" nwMSWorkProcessesPath = "/sap/nw/ms/wp" nwABAPProcBusyPath = "/sap/nw/abap/proc/busy" nwABAPProcCountPath = "/sap/nw/abap/proc/count" nwABAPProcUtilPath = "/sap/nw/abap/proc/utilization" nwABAPProcQueueCurrentPath = "/sap/nw/abap/queue/current" nwABAPProcQueuePeakPath = "/sap/nw/abap/queue/peak" nwABAPSessionsPath = "/sap/nw/abap/sessions" nwABAPRFCPath = "/sap/nw/abap/rfc" nwEnqLocksPath = "/sap/nw/enq/locks/usercountowner" nwInstanceRolePath = "/sap/nw/instance/role" ) var ( msWorkProcess = regexp.MustCompile(`LB=([0-9]+)`) // regex for finding application processes // Matching groups: // 1. Process name // 2. SID // 3. Instance type // 4. Instance number // 5. Profile path root // 6. SID // 7. SID // 8. Instance type // 9. Instance number // 10. Instance name appProcessRegex = regexp.MustCompile(`(dw|enq|en|enqr|er|jc|ms)\.sap([A-Za-z][A-Za-z0-9]{2})_(D|ASCS|ERS|SCS|J)([0-9]{2}) pf=/(usr/sap|sapmnt)/([A-Za-z][A-Za-z0-9]{2})/SYS/profile/([A-Za-z][A-Za-z0-9]{2})_(D|ASCS|SCS|ERS|J)([0-9]{2})_(.*)`) ) // Collect is Netweaver implementation of Collector interface from processmetrics.go. // Returns a list of NetWeaver related metrics. // Collect method keeps on collecting all the metrics it can, logs errors if it encounters // any and returns the collected metrics with the last error encountered while collecting metrics. func (p *InstanceProperties) Collect(ctx context.Context) ([]*mrpb.TimeSeries, error) { scc := sapcontrolclient.New(p.SAPInstance.GetInstanceNumber()) var metricsCollectionError error metrics, err := collectNetWeaverMetrics(ctx, p, scc) if err != nil { metricsCollectionError = err } httpMetrics, err := collectHTTPMetrics(ctx, p) if err != nil { metricsCollectionError = err } if httpMetrics != nil { metrics = append(metrics, httpMetrics...) } abapProcessStatusMetrics, err := collectABAPProcessStatus(ctx, p, scc) if err != nil { metricsCollectionError = err } if abapProcessStatusMetrics != nil { metrics = append(metrics, abapProcessStatusMetrics...) } abapQueueStats, err := collectABAPQueueStats(ctx, p, scc) if err != nil { metricsCollectionError = err } if abapQueueStats != nil { metrics = append(metrics, abapQueueStats...) } dpmonPath := `/usr/sap/` + p.SAPInstance.GetSapsid() + `/SYS/exe/run/dpmon` command := `-c 'echo q | %s pf=%s v'` abapSessionParams := commandlineexecutor.Params{ User: p.SAPInstance.GetUser(), Executable: "bash", ArgsToSplit: fmt.Sprintf(command, dpmonPath, p.SAPInstance.GetProfilePath()), Env: []string{ "PATH=$PATH:" + p.SAPInstance.GetLdLibraryPath(), "LD_LIBRARY_PATH=" + p.SAPInstance.GetLdLibraryPath(), }, } abapSessionStats, err := collectABAPSessionStats(ctx, p, commandlineexecutor.ExecuteCommand, abapSessionParams) if err != nil { metricsCollectionError = err } if abapSessionStats != nil { metrics = append(metrics, abapSessionStats...) } command = `-c 'echo q | %s pf=%s c'` abapRFCParams := commandlineexecutor.Params{ User: p.SAPInstance.GetUser(), Executable: "bash", ArgsToSplit: fmt.Sprintf(command, dpmonPath, p.SAPInstance.GetProfilePath()), Env: []string{ "PATH=$PATH:" + p.SAPInstance.GetLdLibraryPath(), "LD_LIBRARY_PATH=" + p.SAPInstance.GetLdLibraryPath(), }, } rffcConnectionsMetric, err := collectRFCConnections(ctx, p, commandlineexecutor.ExecuteCommand, abapRFCParams) if err != nil { metricsCollectionError = err } if rffcConnectionsMetric != nil { metrics = append(metrics, rffcConnectionsMetric...) } enqLockParams := commandlineexecutor.Params{ User: p.SAPInstance.GetUser(), Executable: p.SAPInstance.GetSapcontrolPath(), ArgsToSplit: fmt.Sprintf("-nr %s -function EnqGetLockTable", p.SAPInstance.GetInstanceNumber()), Env: []string{"LD_LIBRARY_PATH=" + p.SAPInstance.GetLdLibraryPath()}, } enqLockMetrics, err := collectEnqLockMetrics(ctx, p, commandlineexecutor.ExecuteCommand, enqLockParams, scc) if err != nil { metricsCollectionError = err } if enqLockMetrics != nil { metrics = append(metrics, enqLockMetrics...) } roleMetrics, err := collectRoleMetrics(ctx, p, commandlineexecutor.ExecuteCommand) if err != nil { log.CtxLogger(ctx).Debugw("Error in collecting role metrics", "error", err) metricsCollectionError = err } if roleMetrics != nil { metrics = append(metrics, roleMetrics) } return metrics, metricsCollectionError } // CollectWithRetry decorates the Collect method with retry mechanism. func (p *InstanceProperties) CollectWithRetry(ctx context.Context) ([]*mrpb.TimeSeries, error) { var ( attempt = 1 res []*mrpb.TimeSeries ) err := backoff.Retry(func() error { select { case <-ctx.Done(): log.CtxLogger(ctx).Info("Process metrics context cancelled, exiting collectAndSend.") return nil default: log.CtxLogger(ctx).Debugw("Attempting collector retry", "attempt", attempt) 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).Debugw("Retry limit exceeded", "InstanceId", p.SAPInstance.GetInstanceId(), "error", err) } log.CtxLogger(ctx).Debugw("Collected netweaver metrics", "metrics", res) return res, err } // collectNetWeaverMetrics builds a slice of SAP metrics containing all relevant NetWeaver metrics func collectNetWeaverMetrics(ctx context.Context, p *InstanceProperties, scc sapcontrol.ClientInterface) ([]*mrpb.TimeSeries, error) { if _, ok := p.SkippedMetrics[nwServicePath]; ok { return nil, nil } now := tspb.Now() sc := &sapcontrol.Properties{Instance: p.SAPInstance} var ( err error procs map[int]*sapcontrol.ProcessStatus ) procs, err = sc.GetProcessList(ctx, scc) if err != nil { log.CtxLogger(ctx).Debugw("Error performing GetProcessList web method", log.Error(err)) labels := map[string]string{"instance_type": p.SAPInstance.GetKind().String()} metric := createMetrics(p, nwServicePath, labels, now, systemAtLeastOneProcessNotGreen) log.CtxLogger(ctx).Debugw("Sending default /nw/service metric", "metric", metric) return []*mrpb.TimeSeries{metric}, err } metrics := collectServiceMetrics(ctx, p, procs, now) return metrics, nil } // collectServiceMetrics collects NetWeaver "service" metrics describing Netweaver service // processes as managed by the sapcontrol program. func collectServiceMetrics(ctx context.Context, p *InstanceProperties, procs map[int]*sapcontrol.ProcessStatus, now *tspb.Timestamp) (metrics []*mrpb.TimeSeries) { start := tspb.Now() for _, proc := range procs { instanceType := p.SAPInstance.GetKind().String() switch proc.Name { case "gwrd", "sapwebdisp": // For gwrd or web dispatcher process running on another instance, categorize it as "APP" instanceType = sapb.InstanceKind_APP.String() } extraLabels := map[string]string{ "service_name": proc.Name, "instance_type": instanceType, } value := boolToInt64(proc.IsGreen) log.CtxLogger(ctx).Debugw("Creating metrics for process", "metric", nwServicePath, "process", proc.Name, "instanceid", p.SAPInstance.GetInstanceId(), "value", value) metricevents.AddEvent(ctx, metricevents.Parameters{ Path: metricURL + nwServicePath, Message: fmt.Sprintf("NetWeaver service process status for process %s", proc.Name), Value: strconv.FormatInt(value, 10), Labels: metricLabels(p, extraLabels), Identifier: proc.Name, }) metrics = append(metrics, createMetrics(p, nwServicePath, extraLabels, now, value)) } log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectServiceMetrics()", "time", time.Since(start.AsTime())) return metrics } // collectHTTPMetrics collects the HTTP health check metrics for different types of // Netweaver instances based on their types. func collectHTTPMetrics(ctx context.Context, p *InstanceProperties) ([]*mrpb.TimeSeries, error) { url := p.SAPInstance.GetNetweaverHealthCheckUrl() if url == "" { log.CtxLogger(ctx).Debugw("SAP Instance HTTP health check URL is empty", "instanceid", p.SAPInstance.GetInstanceId(), "url", url) return nil, fmt.Errorf("SAP Instance HTTP health check URL is empty %s", p.SAPInstance.GetInstanceId()) } log.CtxLogger(ctx).Debugw("SAP Instance HTTP health check", "instanceid", p.SAPInstance.GetInstanceId(), "url", url) switch strings.ToUpper(p.SAPInstance.GetServiceName()) { case "SAP-ICM-ABAP", "SAP-ICM-JAVA": return collectICMMetrics(ctx, p, url) case "SAP-CS": return collectMessageServerMetrics(ctx, p, url) default: log.CtxLogger(ctx).Debugw("unsupported service name", "serviceName", p.SAPInstance.GetServiceName()) return nil, fmt.Errorf("unsupported service name: %s", p.SAPInstance.GetServiceName()) } } // collectICMMetrics makes HTTP GET request on given URL. // Returns metrics built using: // - HTTP response code. // - Total time taken by the request. func collectICMMetrics(ctx context.Context, p *InstanceProperties, url string) ([]*mrpb.TimeSeries, error) { // Since these metrics are derived from the same operation, even if one of the metric is skipped the whole group will be skipped from collection. if _, ok := p.SkippedMetrics[nwICMRCodePath]; ok { return nil, nil } now := tspb.Now() response, err := http.Get(url) timeTaken := time.Since(now.AsTime()) if err != nil { log.CtxLogger(ctx).Debugw("HTTP GET failed", "instanceid", p.SAPInstance.GetInstanceId(), "url", url, "error", err) return nil, err } defer response.Body.Close() extraLabels := map[string]string{"service_name": p.SAPInstance.GetServiceName()} log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectICMMetrics", "time", time.Since(now.AsTime())) return []*mrpb.TimeSeries{ createMetrics(p, nwICMRCodePath, extraLabels, now, int64(response.StatusCode)), createMetrics(p, nwICMRTimePath, extraLabels, now, timeTaken.Milliseconds()), }, nil } // collectMessageServerMetrics uses HTTP GET on given URL to collect message server metrics. // Returns // - Two metrics - HTTP response code and response time for all HTTP status codes. // - Additional work process count as reported by the message server info page on StatusOK(200). // - A nil in case of errors in HTTP GET request failures. func collectMessageServerMetrics(ctx context.Context, p *InstanceProperties, url string) ([]*mrpb.TimeSeries, error) { // Since these metrics are derived from the same operation, even if one of the metric is skipped the whole group will be skipped from collection. if _, ok := p.SkippedMetrics[nwMSResponseCodePath]; ok { return nil, nil } now := tspb.Now() response, err := http.Get(url) timeTaken := time.Since(now.AsTime()) if err != nil { log.CtxLogger(ctx).Debugw("HTTP GET failed", "instanceid", p.SAPInstance.GetInstanceId(), "url", url, "error", err) return nil, err } defer response.Body.Close() extraLabels := map[string]string{"service_name": p.SAPInstance.GetServiceName()} metrics := []*mrpb.TimeSeries{ createMetrics(p, nwMSResponseCodePath, extraLabels, now, int64(response.StatusCode)), createMetrics(p, nwMSResponseTimePath, extraLabels, now, timeTaken.Milliseconds()), } if response.StatusCode != http.StatusOK { log.CtxLogger(ctx).Debugw("HTTP GET failed", "statuscode", response.StatusCode, "response", response) log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectMessageServerMetrics()", "time", time.Since(now.AsTime())) return nil, fmt.Errorf("HTTP GET failed code: %d", response.StatusCode) } workProcessCount, err := parseWorkProcessCount(response.Body) if err != nil { log.CtxLogger(ctx).Debugw("Reading work process count from message server info page failed", "error", err) return nil, err } log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectMessageServerMetrics()", "time", time.Since(now.AsTime())) return append(metrics, createMetrics(p, nwMSWorkProcessesPath, extraLabels, now, int64(workProcessCount))), nil } // parseWorkProcessCount processes the HTTP text/plain response body one line at a time // to find the message server work process count. // Returns the work process count on success, an error on failures. func parseWorkProcessCount(r io.ReadCloser) (count int, err error) { scanner := bufio.NewScanner(r) // NOMUTANTS--cannot test if text is or is not read one line at a time. scanner.Split(bufio.ScanLines) for scanner.Scan() { if match := msWorkProcess.FindStringSubmatch(scanner.Text()); len(match) == 2 { if count, err = strconv.Atoi(match[1]); err != nil { return 0, err } return count, nil } } return 0, fmt.Errorf("work process count not found") } // collectABAPProcessStatus collects the ABAP worker process status metrics. func collectABAPProcessStatus(ctx context.Context, p *InstanceProperties, scc sapcontrol.ClientInterface) ([]*mrpb.TimeSeries, error) { now := tspb.Now() // Since these metrics are derived from the same operation, even if one of the metric is skipped the whole group will be skipped from collection. skipABAPProcessStatus := p.SkippedMetrics[nwABAPProcCountPath] || p.SkippedMetrics[nwABAPProcBusyPath] || p.SkippedMetrics[nwABAPProcUtilPath] if skipABAPProcessStatus { return nil, nil } sc := &sapcontrol.Properties{Instance: p.SAPInstance} var ( err error processCount map[string]int busyProcessCount map[string]int busyPercentage map[string]int ) wpDetails, err := sc.ABAPGetWPTable(ctx, scc) if err != nil { log.CtxLogger(ctx).Debugw("Sapcontrol web method failed", "error", err) return nil, err } processCount = wpDetails.Processes busyProcessCount = wpDetails.BusyProcesses busyPercentage = wpDetails.BusyProcessPercentage var metrics []*mrpb.TimeSeries for k, v := range processCount { extraLabels := map[string]string{"abap_process": k} log.CtxLogger(ctx).Debugw("Creating metric for abap_process", "metric", nwABAPProcCountPath, "abapprocess", k, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPProcCountPath, extraLabels, now, int64(v))) } for k, v := range busyProcessCount { extraLabels := map[string]string{"abap_process": k} log.CtxLogger(ctx).Debugw("Creating metric for abap_process", "metric", nwABAPProcBusyPath, "abapprocess", k, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPProcBusyPath, extraLabels, now, int64(v))) } for k, v := range busyPercentage { extraLabels := map[string]string{"abap_process": k} log.CtxLogger(ctx).Debugw("Creating metric for abap_process", "metric", nwABAPProcUtilPath, "abapprocess", k, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPProcUtilPath, extraLabels, now, int64(v))) } log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectABAPProcessStatus()", "time", time.Since(now.AsTime())) return metrics, nil } // collectABAPQueueStats collects ABAP Queue utilization metrics using dpmon tool. func collectABAPQueueStats(ctx context.Context, p *InstanceProperties, scc sapcontrol.ClientInterface) ([]*mrpb.TimeSeries, error) { now := tspb.Now() // Since these metrics are derived from the same operation, even if one of the metric is skipped the whole group will be skipped from collection. skipABAPQueue := p.SkippedMetrics[nwABAPProcQueueCurrentPath] || p.SkippedMetrics[nwABAPProcQueuePeakPath] if skipABAPQueue { return nil, nil } sc := &sapcontrol.Properties{Instance: p.SAPInstance} var ( err error currentQueueUsage map[string]int64 peakQueueUsage map[string]int64 ) currentQueueUsage, peakQueueUsage, err = sc.GetQueueStatistic(ctx, scc) if err != nil { log.CtxLogger(ctx).Debugw("Sapcontrol web method failed", "error", err) return nil, err } var metrics []*mrpb.TimeSeries for k, v := range currentQueueUsage { extraLabels := map[string]string{"abap_queue": k} log.CtxLogger(ctx).Debugw("Creating metric with labels", "metric", nwABAPProcQueueCurrentPath, "labels", extraLabels, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPProcQueueCurrentPath, extraLabels, now, int64(v))) } for k, v := range peakQueueUsage { extraLabels := map[string]string{"abap_queue_peak": k} log.CtxLogger(ctx).Debugw("Creating metric with labels", "metric", nwABAPProcQueueCurrentPath, "labels", extraLabels, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPProcQueuePeakPath, extraLabels, now, int64(v))) } log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectABAPQueueStats()", "time", time.Since(now.AsTime())) return metrics, nil } // collectABAPSessionStats collects ABAP session related metrics using dpmon tool. func collectABAPSessionStats(ctx context.Context, p *InstanceProperties, exec commandlineexecutor.Execute, params commandlineexecutor.Params) ([]*mrpb.TimeSeries, error) { now := tspb.Now() if _, ok := p.SkippedMetrics[nwABAPSessionsPath]; ok { return nil, nil } results := exec(ctx, params) log.CtxLogger(ctx).Debugw("DPMON for sessionStat output", "stdout", results.StdOut, "stderr", results.StdErr, "exitcode", results.ExitCode, "error", results.Error) if results.Error != nil { log.CtxLogger(ctx).Debugw("DPMON failed", log.Error(results.Error)) return nil, results.Error } var metrics []*mrpb.TimeSeries sessionCounts, totalCount, err := parseABAPSessionStats(results.StdOut) if err != nil { log.CtxLogger(ctx).Debugw("DPMON ran successfully, but no ABAP session currently active", log.Error(err)) return nil, err } for k, v := range sessionCounts { extraLabels := map[string]string{"abap_session_type": k} log.CtxLogger(ctx).Debugw("Creating metric with labels", "metric", nwABAPSessionsPath, "labels", extraLabels, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPSessionsPath, extraLabels, now, int64(v))) } extraLabels := map[string]string{"abap_session_type": "total_count"} log.CtxLogger(ctx).Debugw("Creating metric with labels", "metric", nwABAPSessionsPath, "labels", extraLabels, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", totalCount) metrics = append(metrics, createMetrics(p, nwABAPSessionsPath, extraLabels, now, int64(totalCount))) log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectABAPSessionStats()", "time", time.Since(now.AsTime())) return metrics, nil } // collectRFCConnections collects the ABAP RFC connection metrics using dpmon tool. func collectRFCConnections(ctx context.Context, p *InstanceProperties, exec commandlineexecutor.Execute, params commandlineexecutor.Params) ([]*mrpb.TimeSeries, error) { now := tspb.Now() if _, ok := p.SkippedMetrics[nwABAPRFCPath]; ok { return nil, nil } result := exec(ctx, params) log.CtxLogger(ctx).Debugw("DPMON for RFC output", "stdout", result.StdOut, "stderr", result.StdErr, "exitcode", result.ExitCode, "error", result.Error) if result.Error != nil { log.CtxLogger(ctx).Debugw("DPMON for RFC failed", log.Error(result.Error)) return nil, result.Error } var metrics []*mrpb.TimeSeries rfcStateCount := parseRFCStats(result.StdOut) for k, v := range rfcStateCount { extraLabels := map[string]string{"abap_rfc_conn": k} log.CtxLogger(ctx).Debugw("Creating metric with labels", "metric", nwABAPRFCPath, "labels", extraLabels, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", v) metrics = append(metrics, createMetrics(p, nwABAPRFCPath, extraLabels, now, int64(v))) } log.CtxLogger(ctx).Debugw("Time taken to collect metrics in collectRFCConnections()", "time", time.Since(now.AsTime())) return metrics, nil } // collectEnqLockMetrics builds Enq Locks for SAP Netweaver ASCS instances. func collectEnqLockMetrics(ctx context.Context, p *InstanceProperties, exec commandlineexecutor.Execute, params commandlineexecutor.Params, scc sapcontrol.ClientInterface) ([]*mrpb.TimeSeries, error) { if _, ok := p.SkippedMetrics[nwEnqLocksPath]; ok { return nil, nil } instance := p.SAPInstance.GetInstanceId() if !strings.HasPrefix(instance, "ASCS") && !strings.HasPrefix(instance, "ERS") { log.CtxLogger(ctx).Debugw("The Enq Lock metric is only applicable for application type: ASCS.", "InstanceID", p.SAPInstance.InstanceId) return nil, nil } now := tspb.Now() sc := &sapcontrol.Properties{Instance: p.SAPInstance} var enqLocks []*sapcontrol.EnqLock var err error enqLocks, err = sc.EnqGetLockTable(ctx, scc) if err != nil { return nil, err } var metrics []*mrpb.TimeSeries for _, lock := range enqLocks { extraLabels := map[string]string{ "lock_name": lock.LockName, "lock_arg": lock.LockArg, "lock_mode": lock.LockMode, "owner": lock.Owner, "owner_vb": lock.OwnerVB, "user_count_owner": fmt.Sprintf("%d", lock.UserCountOwner), "user_count_owner_vb": fmt.Sprintf("%d", lock.UserCountOwnerVB), "client": lock.Client, "user": lock.User, "transaction": lock.Transaction, "object": lock.Object, "backup": lock.Backup, } log.CtxLogger(ctx).Debugw("Creating metric with labels", "metric", nwEnqLocksPath, "labels", extraLabels, "instancenumber", p.SAPInstance.GetInstanceNumber(), "value", lock.UserCountOwner) metrics = append(metrics, createMetrics(p, nwEnqLocksPath, extraLabels, now, lock.UserCountOwner)) } return metrics, nil } func collectRoleMetrics(ctx context.Context, p *InstanceProperties, exec commandlineexecutor.Execute) (*mrpb.TimeSeries, error) { params := commandlineexecutor.Params{ Executable: "ps", Args: []string{"-ef"}, } result := exec(ctx, params) if result.Error != nil { return nil, result.Error } roles := map[string]string{ "ascs": "false", "ers": "false", "app": "false", } hasRole := false hasEnq := false hasMS := false lines := strings.Split(result.StdOut, "\n") for _, line := range lines { matches := appProcessRegex.FindStringSubmatch(line) log.CtxLogger(ctx).Debugw("Line", "line", line, "matches", matches) if len(matches) < 3 { continue } bin := matches[1] sid := strings.ToUpper(matches[2]) if sid != p.SAPInstance.GetSapsid() { continue } switch bin { case "enqr", "er": log.CtxLogger(ctx).Debugw("Found enqr or er", "bin", bin) roles["ers"] = "true" hasRole = true case "ms": log.CtxLogger(ctx).Debugw("Found ms", "bin", bin) hasMS = true hasRole = true case "enq", "en": log.CtxLogger(ctx).Debugw("Found enq or en", "bin", bin) hasEnq = true hasRole = true case "dw", "jc": log.CtxLogger(ctx).Debugw("Found dw or jc", "bin", bin) roles["app"] = "true" hasRole = true } } if hasMS && hasEnq { log.CtxLogger(ctx).Debugw("Found both ms and enq.", "hasMS", hasMS, "hasEnq", hasEnq) roles["ascs"] = "true" hasRole = true } if !hasRole { return nil, nil } return createMetrics(p, nwInstanceRolePath, roles, tspb.Now(), 1), nil } // createMetrics - create mrpb.TimeSeries object for the given metric. func createMetrics(p *InstanceProperties, mPath string, extraLabels map[string]string, now *tspb.Timestamp, val int64) *mrpb.TimeSeries { params := timeseries.Params{ CloudProp: protostruct.ConvertCloudPropertiesToStruct(p.Config.CloudProperties), MetricType: metricURL + mPath, MetricLabels: metricLabels(p, extraLabels), Timestamp: now, Int64Value: val, BareMetal: p.Config.BareMetal, } return timeseries.BuildInt(params) } // metricLabels appends the default SAP Instance labels and extra labels // to return a consolidated map of metric labels. func metricLabels(p *InstanceProperties, extraLabels map[string]string) map[string]string { defaultLabels := map[string]string{ "sid": p.SAPInstance.GetSapsid(), "instance_nr": p.SAPInstance.GetInstanceNumber(), "instance_name": p.Config.CloudProperties.InstanceName, } for k, v := range extraLabels { defaultLabels[k] = v } return defaultLabels } // contains checks if a string slice contains a given string. func contains(list []string, s string) bool { for _, t := range list { if s == t { return true } } return false } // boolToInt64 converts bool to int64. func boolToInt64(b bool) int64 { if b { return 1 } return 0 }