| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "os" | |
| "os/exec" | |
| "regexp" | |
| "runtime" | |
| "strconv" | |
| "strings" | |
| "syscall" | |
| ) | |
| var ( | |
| cpuSetRE = regexp.MustCompile(`(\d,?)+`) | |
| ) | |
| func init() { | |
| register("FreeBSDNumCPU", FreeBSDNumCPU) | |
| register("FreeBSDNumCPUHelper", FreeBSDNumCPUHelper) | |
| } | |
| func FreeBSDNumCPUHelper() { | |
| fmt.Printf("%d\n", runtime.NumCPU()) | |
| } | |
| func FreeBSDNumCPU() { | |
| _, err := exec.LookPath("cpuset") | |
| if err != nil { | |
| // Can not test without cpuset command. | |
| fmt.Println("OK") | |
| return | |
| } | |
| _, err = exec.LookPath("sysctl") | |
| if err != nil { | |
| // Can not test without sysctl command. | |
| fmt.Println("OK") | |
| return | |
| } | |
| cmd := exec.Command("sysctl", "-n", "kern.smp.active") | |
| output, err := cmd.CombinedOutput() | |
| if err != nil { | |
| fmt.Printf("fail to launch %#q, error: %s, output: %s\n", cmd, err, output) | |
| return | |
| } | |
| if !bytes.Equal(output, []byte("1\n")) { | |
| // SMP mode deactivated in kernel. | |
| fmt.Println("OK") | |
| return | |
| } | |
| list, err := getList() | |
| if err != nil { | |
| fmt.Printf("%s\n", err) | |
| return | |
| } | |
| err = checkNCPU(list) | |
| if err != nil { | |
| fmt.Printf("%s\n", err) | |
| return | |
| } | |
| if len(list) >= 2 { | |
| err = checkNCPU(list[:len(list)-1]) | |
| if err != nil { | |
| fmt.Printf("%s\n", err) | |
| return | |
| } | |
| } | |
| fmt.Println("OK") | |
| return | |
| } | |
| func getList() ([]string, error) { | |
| pid := syscall.Getpid() | |
| // Launch cpuset to print a list of available CPUs: pid <PID> mask: 0, 1, 2, 3. | |
| cmd := exec.Command("cpuset", "-g", "-p", strconv.Itoa(pid)) | |
| output, err := cmd.CombinedOutput() | |
| if err != nil { | |
| return nil, fmt.Errorf("fail to execute %#q: %s", cmd, err) | |
| } | |
| output, _, ok := bytes.Cut(output, []byte("\n")) | |
| if !ok { | |
| return nil, fmt.Errorf("invalid output from %#q, '\\n' not found: %s", cmd, output) | |
| } | |
| _, cpus, ok := bytes.Cut(output, []byte(":")) | |
| if !ok { | |
| return nil, fmt.Errorf("invalid output from %#q, ':' not found: %s", cmd, output) | |
| } | |
| var list []string | |
| for _, val := range bytes.Split(cpus, []byte(",")) { | |
| index := string(bytes.TrimSpace(val)) | |
| if len(index) == 0 { | |
| continue | |
| } | |
| list = append(list, index) | |
| } | |
| if len(list) == 0 { | |
| return nil, fmt.Errorf("empty CPU list from %#q: %s", cmd, output) | |
| } | |
| return list, nil | |
| } | |
| func checkNCPU(list []string) error { | |
| listString := strings.Join(list, ",") | |
| if len(listString) == 0 { | |
| return fmt.Errorf("could not check against an empty CPU list") | |
| } | |
| cListString := cpuSetRE.FindString(listString) | |
| if len(cListString) == 0 { | |
| return fmt.Errorf("invalid cpuset output '%s'", listString) | |
| } | |
| // Launch FreeBSDNumCPUHelper() with specified CPUs list. | |
| cmd := exec.Command("cpuset", "-l", cListString, os.Args[0], "FreeBSDNumCPUHelper") | |
| output, err := cmd.CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("fail to launch child %#q, error: %s, output: %s", cmd, err, output) | |
| } | |
| // NumCPU from FreeBSDNumCPUHelper come with '\n'. | |
| output = bytes.TrimSpace(output) | |
| n, err := strconv.Atoi(string(output)) | |
| if err != nil { | |
| return fmt.Errorf("fail to parse output from child %#q, error: %s, output: %s", cmd, err, output) | |
| } | |
| if n != len(list) { | |
| return fmt.Errorf("runtime.NumCPU() expected to %d, got %d when run with CPU list %s", len(list), n, cListString) | |
| } | |
| return nil | |
| } |