// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file 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. //go:build darwin // +build darwin package instancedetailedinformation import ( "fmt" "os/exec" "strconv" "strings" "github.com/aws/amazon-ssm-agent/agent/context" "github.com/aws/amazon-ssm-agent/agent/plugins/inventory/model" ) const ( sysctlCmd = "sysctl" cpuModelNameKey = "machdep.cpu.brand_string" cpuCoreKey = "hw.physicalcpu" cpusKey = "hw.logicalcpu" cpuFreqKey = "hw.cpufrequency" threadTypeKey = "hw.cputhreadtype" kernelVersionKey = "kern.osrelease" ) // cmdExecutor decouples exec.Command for easy testability var cmdExecutor = executeCommand func executeCommand(command string, args ...string) ([]byte, error) { return exec.Command(command, args...).CombinedOutput() } // collectPlatformDependentInstanceData collects data from the system. func collectPlatformDependentInstanceData(context context.T) (appData []model.InstanceDetailedInformation) { log := context.Log() var output []byte var err error cmd := sysctlCmd args := []string{"-i", cpuModelNameKey, cpuCoreKey, cpusKey, cpuFreqKey, threadTypeKey} log.Infof("Executing command: %v", cmd) if output, err = cmdExecutor(cmd, args...); err != nil { log.Errorf("Failed to execute command : %v %v; with error: %v", cmd, args, err.Error()) log.Debugf("Command Stderr: %v", string(output)) return } log.Infof("Parsing output %v", string(output)) r := parseSysctlOutput(string(output)) log.Infof("Parsed output %v", r) return r } // parseSysctlOutput collects relevant fields from sysctl output, which has the following format // machdep.cpu.brand_string: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz // hw.physicalcpu: 4 // hw.logicalcpu: 8 // hw.cpufrequency: 2800000000 // hw.cputhreadtype: 1 func parseSysctlOutput(output string) (data []model.InstanceDetailedInformation) { var cpuSpeed = parseInt(getFieldValue(output, cpuFreqKey), 0) // convert the frequency to MHz var cpuSpeedMHzStr = strconv.Itoa(cpuSpeed / 1000000) var fieldValue = getFieldValue(output, threadTypeKey) var hyperThreadEnabledStr string if fieldValue == "" { hyperThreadEnabledStr = "" } else { var threadTypeVal = parseInt(getFieldValue(output, threadTypeKey), 0) hyperThreadEnabledStr = boolToStr(threadTypeVal > 0) } itemContent := model.InstanceDetailedInformation{ CPUModel: getFieldValue(output, cpuModelNameKey), CPUCores: getFieldValue(output, cpuCoreKey), CPUs: getFieldValue(output, cpusKey), CPUSpeedMHz: cpuSpeedMHzStr, CPUSockets: "", CPUHyperThreadEnabled: hyperThreadEnabledStr, KernelVersion: getFieldValue(output, kernelVersionKey), } data = append(data, itemContent) return } func parseInt(value string, defaultValue int) int { res, err := strconv.Atoi(value) if err != nil { return defaultValue } return res } // getFieldValue looks for the first substring of the form "key: value \n" and returns the "value" // if no such field found, returns empty string func getFieldValue(input string, key string) string { keyStartPos := strings.Index(input, key+":") if keyStartPos < 0 { return "" } // add "\n" sentinel in case the key:value pair is on the last line and there is no newline at the end afterKey := input[keyStartPos+len(key)+1:] + "\n" valueEndPos := strings.Index(afterKey, "\n") return strings.TrimSpace(afterKey[:valueEndPos]) } func boolToStr(b bool) string { return fmt.Sprintf("%v", b) }