| // Copyright 2013 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 driver |
| |
| import ( |
| "bytes" |
| "log" |
| "os/exec" |
| "sync" |
| "syscall" |
| "time" |
| "unsafe" |
| ) |
| |
| // access to Windows APIs |
| var ( |
| modkernel32 = syscall.NewLazyDLL("kernel32.dll") |
| modpsapi = syscall.NewLazyDLL("psapi.dll") |
| procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo") |
| procSetProcessAffinityMask = modkernel32.NewProc("SetProcessAffinityMask") |
| procCreateJobObjectW = modkernel32.NewProc("CreateJobObjectW") |
| procOpenJobObjectW = modkernel32.NewProc("OpenJobObjectW") |
| procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject") |
| procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject") |
| |
| initJobOnce sync.Once |
| currentProcess syscall.Handle |
| childMu sync.Mutex |
| childProcesses []syscall.Handle |
| ) |
| |
| const ( |
| JOB_OBJECT_MSG_NEW_PROCESS = 6 |
| JobObjectAssociateCompletionPortInformation = 7 |
| ) |
| |
| type PROCESS_MEMORY_COUNTERS struct { |
| CB uint32 |
| PageFaultCount uint32 |
| PeakWorkingSetSize uintptr |
| WorkingSetSize uintptr |
| QuotaPeakPagedPoolUsage uintptr |
| QuotaPagedPoolUsage uintptr |
| QuotaPeakNonPagedPoolUsage uintptr |
| QuotaNonPagedPoolUsage uintptr |
| PagefileUsage uintptr |
| PeakPagefileUsage uintptr |
| } |
| |
| type JOBOBJECT_ASSOCIATE_COMPLETION_PORT struct { |
| CompletionKey uintptr |
| CompletionPort syscall.Handle |
| } |
| |
| func init() { |
| var err error |
| currentProcess, err = syscall.GetCurrentProcess() |
| if err != nil { |
| log.Fatalf("GetCurrentProcess failed: %v", err) |
| } |
| } |
| |
| func initJob() { |
| // Create Job object and assign current process to it. |
| jobObject, err := createJobObject(nil, nil) |
| if err != nil { |
| log.Printf("CreateJobObject failed: %v", err) |
| return |
| } |
| if err = assignProcessToJobObject(jobObject, currentProcess); err != nil { |
| log.Printf("AssignProcessToJobObject failed: %v", err) |
| syscall.Close(jobObject) |
| return |
| } |
| iocp, err := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 1) |
| if err != nil { |
| log.Printf("CreateIoCompletionPort failed: %v", err) |
| syscall.Close(jobObject) |
| return |
| } |
| port := JOBOBJECT_ASSOCIATE_COMPLETION_PORT{ |
| CompletionKey: uintptr(jobObject), |
| CompletionPort: iocp, |
| } |
| err = setInformationJobObject(jobObject, JobObjectAssociateCompletionPortInformation, uintptr(unsafe.Pointer(&port)), uint32(unsafe.Sizeof(port))) |
| if err != nil { |
| log.Printf("SetInformationJobObject failed: %v", err) |
| syscall.Close(jobObject) |
| syscall.Close(iocp) |
| return |
| } |
| // Read Job notifications about new "child" processes and collect them in childProcesses. |
| go func() { |
| var code, key uint32 |
| // o is declared as uintptr because GetQueuedCompletionStatus |
| // stores process id into it. If we declare it as *overlapped, |
| // runtime stack copier will crash due to bogus pointer value. |
| var o uintptr |
| for { |
| err := syscall.GetQueuedCompletionStatus(iocp, &code, &key, (**syscall.Overlapped)(unsafe.Pointer(&o)), syscall.INFINITE) |
| if err != nil { |
| log.Printf("GetQueuedCompletionStatus failed: %v", err) |
| return |
| } |
| if key != uint32(jobObject) { |
| panic("Invalid GetQueuedCompletionStatus key parameter") |
| } |
| if code == JOB_OBJECT_MSG_NEW_PROCESS { |
| pid := int(o) |
| if pid == syscall.Getpid() { |
| continue |
| } |
| c, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, false, uint32(pid)) |
| if err != nil { |
| log.Printf("OpenProcess failed: %v", err) |
| continue |
| } |
| childMu.Lock() |
| childProcesses = append(childProcesses, c) |
| childMu.Unlock() |
| } |
| } |
| }() |
| } |
| |
| func getProcessMemoryInfo(h syscall.Handle, mem *PROCESS_MEMORY_COUNTERS) (err error) { |
| r1, _, e1 := syscall.Syscall(procGetProcessMemoryInfo.Addr(), 3, uintptr(h), uintptr(unsafe.Pointer(mem)), uintptr(unsafe.Sizeof(*mem))) |
| if r1 == 0 { |
| if e1 != 0 { |
| err = error(e1) |
| } else { |
| err = syscall.EINVAL |
| } |
| } |
| return |
| } |
| |
| func setProcessAffinityMask(h syscall.Handle, mask uintptr) (err error) { |
| r1, _, e1 := syscall.Syscall(procSetProcessAffinityMask.Addr(), 2, uintptr(h), mask, 0) |
| if r1 == 0 { |
| if e1 != 0 { |
| err = error(e1) |
| } else { |
| err = syscall.EINVAL |
| } |
| } |
| return |
| } |
| |
| func createJobObject(jobAttrs *syscall.SecurityAttributes, name *uint16) (handle syscall.Handle, err error) { |
| r0, _, e1 := syscall.Syscall(procCreateJobObjectW.Addr(), 2, uintptr(unsafe.Pointer(jobAttrs)), uintptr(unsafe.Pointer(name)), 0) |
| handle = syscall.Handle(r0) |
| if handle == 0 { |
| if e1 != 0 { |
| err = error(e1) |
| } else { |
| err = syscall.EINVAL |
| } |
| } |
| return |
| } |
| |
| func assignProcessToJobObject(job syscall.Handle, process syscall.Handle) (err error) { |
| r1, _, e1 := syscall.Syscall(procAssignProcessToJobObject.Addr(), 2, uintptr(job), uintptr(process), 0) |
| if r1 == 0 { |
| if e1 != 0 { |
| err = error(e1) |
| } else { |
| err = syscall.EINVAL |
| } |
| } |
| return |
| } |
| |
| func setInformationJobObject(job syscall.Handle, infoclass uint32, info uintptr, infolien uint32) (err error) { |
| r1, _, e1 := syscall.Syscall6(procSetInformationJobObject.Addr(), 4, uintptr(job), uintptr(infoclass), uintptr(info), uintptr(infolien), 0, 0) |
| if r1 == 0 { |
| if e1 != 0 { |
| err = error(e1) |
| } else { |
| err = syscall.EINVAL |
| } |
| } |
| return |
| } |
| |
| func RunUnderProfiler(args ...string) (string, string) { |
| return "", "" |
| } |
| |
| // Size runs size command on the file. Returns filename with output. Any errors are ignored. |
| func Size(file string) string { |
| return "" |
| } |
| |
| type sysStats struct { |
| N uint64 |
| CPU syscall.Rusage |
| } |
| |
| func InitSysStats(N uint64) (ss sysStats) { |
| if err := syscall.GetProcessTimes(currentProcess, &ss.CPU.CreationTime, &ss.CPU.ExitTime, &ss.CPU.KernelTime, &ss.CPU.UserTime); err != nil { |
| log.Printf("GetProcessTimes failed: %v", err) |
| return |
| } |
| ss.N = N |
| return ss |
| } |
| |
| func (ss sysStats) Collect(res *Result) { |
| if ss.N == 0 { |
| return |
| } |
| var Mem PROCESS_MEMORY_COUNTERS |
| if err := getProcessMemoryInfo(currentProcess, &Mem); err != nil { |
| log.Printf("GetProcessMemoryInfo failed: %v", err) |
| return |
| } |
| var CPU syscall.Rusage |
| if err := syscall.GetProcessTimes(currentProcess, &CPU.CreationTime, &CPU.ExitTime, &CPU.KernelTime, &CPU.UserTime); err != nil { |
| log.Printf("GetProcessTimes failed: %v", err) |
| return |
| } |
| res.Metrics["user+sys-ns/op"] = (getCPUTime(CPU) - getCPUTime(ss.CPU)) / ss.N |
| res.Metrics["peak-RSS-bytes"] = uint64(Mem.PeakWorkingSetSize) |
| } |
| |
| func RunAndCollectSysStats(cmd *exec.Cmd, res *Result, N uint64, prefix string) (string, error) { |
| initJobOnce.Do(initJob) |
| |
| childMu.Lock() |
| children := childProcesses |
| childProcesses = []syscall.Handle{} |
| childMu.Unlock() |
| for _, proc := range children { |
| syscall.CloseHandle(proc) |
| } |
| |
| var out bytes.Buffer |
| cmd.Stdout = &out |
| cmd.Stderr = &out |
| t0 := time.Now() |
| if err := cmd.Run(); err != nil { |
| return out.String(), err |
| } |
| t1 := time.Now() |
| |
| res.RunTime = uint64(t1.Sub(t0)) / N |
| res.Metrics[prefix+"ns/op"] = res.RunTime |
| |
| childMu.Lock() |
| children = childProcesses |
| childProcesses = []syscall.Handle{} |
| childMu.Unlock() |
| if len(children) == 0 { |
| log.Printf("sysStats.Collect: no child processes?") |
| return out.String(), nil |
| } |
| defer func() { |
| for _, proc := range children { |
| syscall.CloseHandle(proc) |
| } |
| }() |
| cputime := uint64(0) |
| rss := uint64(0) |
| for _, proc := range children { |
| var Mem PROCESS_MEMORY_COUNTERS |
| if err := getProcessMemoryInfo(proc, &Mem); err != nil { |
| log.Printf("GetProcessMemoryInfo failed: %v", err) |
| return out.String(), nil |
| } |
| var CPU syscall.Rusage |
| if err := syscall.GetProcessTimes(proc, &CPU.CreationTime, &CPU.ExitTime, &CPU.KernelTime, &CPU.UserTime); err != nil { |
| log.Printf("GetProcessTimes failed: %v", err) |
| return out.String(), nil |
| } |
| cputime += getCPUTime(CPU) / N |
| rss += uint64(Mem.PeakWorkingSetSize) |
| } |
| |
| res.Metrics[prefix+"user+sys-ns/op"] = cputime |
| res.Metrics[prefix+"peak-RSS-bytes"] = rss |
| return out.String(), nil |
| } |
| |
| func getCPUTime(CPU syscall.Rusage) uint64 { |
| var CPU0 syscall.Rusage // time is offsetted, so we need to subtract "zero" |
| return uint64(CPU.KernelTime.Nanoseconds() + CPU.UserTime.Nanoseconds() - |
| CPU0.KernelTime.Nanoseconds() - CPU0.UserTime.Nanoseconds()) |
| } |
| |
| func setProcessAffinity(v int) { |
| h, err := syscall.GetCurrentProcess() |
| if err != nil { |
| log.Printf("GetCurrentProcess failed: %v", err) |
| return |
| } |
| if err := setProcessAffinityMask(h, uintptr(v)); err != nil { |
| log.Printf("SetProcessAffinityMask failed: %v", err) |
| return |
| } |
| } |