github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/executor/executor.go (about) 1 package executor 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "net" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strconv" 14 "strings" 15 "sync" 16 "syscall" 17 "time" 18 19 "github.com/armon/circbuf" 20 "github.com/hashicorp/go-multierror" 21 "github.com/mitchellh/go-ps" 22 "github.com/shirou/gopsutil/process" 23 24 "github.com/hashicorp/nomad/client/allocdir" 25 "github.com/hashicorp/nomad/client/driver/env" 26 "github.com/hashicorp/nomad/client/driver/logging" 27 "github.com/hashicorp/nomad/client/stats" 28 shelpers "github.com/hashicorp/nomad/helper/stats" 29 "github.com/hashicorp/nomad/nomad/structs" 30 31 dstructs "github.com/hashicorp/nomad/client/driver/structs" 32 cstructs "github.com/hashicorp/nomad/client/structs" 33 ) 34 35 const ( 36 // pidScanInterval is the interval at which the executor scans the process 37 // tree for finding out the pids that the executor and it's child processes 38 // have forked 39 pidScanInterval = 5 * time.Second 40 ) 41 42 var ( 43 // The statistics the basic executor exposes 44 ExecutorBasicMeasuredMemStats = []string{"RSS", "Swap"} 45 ExecutorBasicMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"} 46 ) 47 48 // Executor is the interface which allows a driver to launch and supervise 49 // a process 50 type Executor interface { 51 SetContext(ctx *ExecutorContext) error 52 LaunchCmd(command *ExecCommand) (*ProcessState, error) 53 LaunchSyslogServer() (*SyslogServerState, error) 54 Wait() (*ProcessState, error) 55 ShutDown() error 56 Exit() error 57 UpdateLogConfig(logConfig *structs.LogConfig) error 58 UpdateTask(task *structs.Task) error 59 Version() (*ExecutorVersion, error) 60 Stats() (*cstructs.TaskResourceUsage, error) 61 Signal(s os.Signal) error 62 Exec(deadline time.Time, cmd string, args []string) ([]byte, int, error) 63 } 64 65 // ExecutorContext holds context to configure the command user 66 // wants to run and isolate it 67 type ExecutorContext struct { 68 // TaskEnv holds information about the environment of a Task 69 TaskEnv *env.TaskEnv 70 71 // Task is the task whose executor is being launched 72 Task *structs.Task 73 74 // TaskDir is the host path to the task's root 75 TaskDir string 76 77 // LogDir is the host path where logs should be written 78 LogDir string 79 80 // Driver is the name of the driver that invoked the executor 81 Driver string 82 83 // PortUpperBound is the upper bound of the ports that we can use to start 84 // the syslog server 85 PortUpperBound uint 86 87 // PortLowerBound is the lower bound of the ports that we can use to start 88 // the syslog server 89 PortLowerBound uint 90 } 91 92 // ExecCommand holds the user command, args, and other isolation related 93 // settings. 94 type ExecCommand struct { 95 // Cmd is the command that the user wants to run. 96 Cmd string 97 98 // Args is the args of the command that the user wants to run. 99 Args []string 100 101 // TaskKillSignal is an optional field which signal to kill the process 102 TaskKillSignal os.Signal 103 104 // FSIsolation determines whether the command would be run in a chroot. 105 FSIsolation bool 106 107 // User is the user which the executor uses to run the command. 108 User string 109 110 // ResourceLimits determines whether resource limits are enforced by the 111 // executor. 112 ResourceLimits bool 113 } 114 115 // ProcessState holds information about the state of a user process. 116 type ProcessState struct { 117 Pid int 118 ExitCode int 119 Signal int 120 IsolationConfig *dstructs.IsolationConfig 121 Time time.Time 122 } 123 124 // nomadPid holds a pid and it's cpu percentage calculator 125 type nomadPid struct { 126 pid int 127 cpuStatsTotal *stats.CpuStats 128 cpuStatsUser *stats.CpuStats 129 cpuStatsSys *stats.CpuStats 130 } 131 132 // SyslogServerState holds the address and islation information of a launched 133 // syslog server 134 type SyslogServerState struct { 135 IsolationConfig *dstructs.IsolationConfig 136 Addr string 137 } 138 139 // ExecutorVersion is the version of the executor 140 type ExecutorVersion struct { 141 Version string 142 } 143 144 func (v *ExecutorVersion) GoString() string { 145 return v.Version 146 } 147 148 // UniversalExecutor is an implementation of the Executor which launches and 149 // supervises processes. In addition to process supervision it provides resource 150 // and file system isolation 151 type UniversalExecutor struct { 152 cmd exec.Cmd 153 ctx *ExecutorContext 154 command *ExecCommand 155 156 pids map[int]*nomadPid 157 pidLock sync.RWMutex 158 exitState *ProcessState 159 processExited chan interface{} 160 fsIsolationEnforced bool 161 162 lre *logging.FileRotator 163 lro *logging.FileRotator 164 rotatorLock sync.Mutex 165 166 syslogServer *logging.SyslogServer 167 syslogChan chan *logging.SyslogMessage 168 169 resConCtx resourceContainerContext 170 171 totalCpuStats *stats.CpuStats 172 userCpuStats *stats.CpuStats 173 systemCpuStats *stats.CpuStats 174 logger *log.Logger 175 } 176 177 // NewExecutor returns an Executor 178 func NewExecutor(logger *log.Logger) Executor { 179 if err := shelpers.Init(); err != nil { 180 logger.Printf("[ERR] executor: unable to initialize stats: %v", err) 181 } 182 183 exec := &UniversalExecutor{ 184 logger: logger, 185 processExited: make(chan interface{}), 186 totalCpuStats: stats.NewCpuStats(), 187 userCpuStats: stats.NewCpuStats(), 188 systemCpuStats: stats.NewCpuStats(), 189 pids: make(map[int]*nomadPid), 190 } 191 192 return exec 193 } 194 195 // Version returns the api version of the executor 196 func (e *UniversalExecutor) Version() (*ExecutorVersion, error) { 197 return &ExecutorVersion{Version: "1.1.0"}, nil 198 } 199 200 // SetContext is used to set the executors context and should be the first call 201 // after launching the executor. 202 func (e *UniversalExecutor) SetContext(ctx *ExecutorContext) error { 203 e.ctx = ctx 204 return nil 205 } 206 207 // LaunchCmd launches the main process and returns its state. It also 208 // configures an applies isolation on certain platforms. 209 func (e *UniversalExecutor) LaunchCmd(command *ExecCommand) (*ProcessState, error) { 210 e.logger.Printf("[DEBUG] executor: launching command %v %v", command.Cmd, strings.Join(command.Args, " ")) 211 212 // Ensure the context has been set first 213 if e.ctx == nil { 214 return nil, fmt.Errorf("SetContext must be called before launching a command") 215 } 216 217 e.command = command 218 219 // setting the user of the process 220 if command.User != "" { 221 e.logger.Printf("[DEBUG] executor: running command as %s", command.User) 222 if err := e.runAs(command.User); err != nil { 223 return nil, err 224 } 225 } 226 227 // set the task dir as the working directory for the command 228 e.cmd.Dir = e.ctx.TaskDir 229 230 // configuring the chroot, resource container, and start the plugin 231 // process in the chroot. 232 if err := e.configureIsolation(); err != nil { 233 return nil, err 234 } 235 // Apply ourselves into the resource container. The executor MUST be in 236 // the resource container before the user task is started, otherwise we 237 // are subject to a fork attack in which a process escapes isolation by 238 // immediately forking. 239 if err := e.applyLimits(os.Getpid()); err != nil { 240 return nil, err 241 } 242 243 // Setup the loggers 244 if err := e.configureLoggers(); err != nil { 245 return nil, err 246 } 247 e.cmd.Stdout = e.lro 248 e.cmd.Stderr = e.lre 249 250 // Look up the binary path and make it executable 251 absPath, err := e.lookupBin(e.ctx.TaskEnv.ReplaceEnv(command.Cmd)) 252 if err != nil { 253 return nil, err 254 } 255 256 if err := e.makeExecutable(absPath); err != nil { 257 return nil, err 258 } 259 260 path := absPath 261 262 // Determine the path to run as it may have to be relative to the chroot. 263 if e.fsIsolationEnforced { 264 rel, err := filepath.Rel(e.ctx.TaskDir, path) 265 if err != nil { 266 return nil, fmt.Errorf("failed to determine relative path base=%q target=%q: %v", e.ctx.TaskDir, path, err) 267 } 268 path = rel 269 } 270 271 // Set the commands arguments 272 e.cmd.Path = path 273 e.cmd.Args = append([]string{e.cmd.Path}, e.ctx.TaskEnv.ParseAndReplace(command.Args)...) 274 e.cmd.Env = e.ctx.TaskEnv.List() 275 276 // Start the process 277 if err := e.cmd.Start(); err != nil { 278 return nil, fmt.Errorf("failed to start command path=%q --- args=%q: %v", path, e.cmd.Args, err) 279 } 280 go e.collectPids() 281 go e.wait() 282 ic := e.resConCtx.getIsolationConfig() 283 return &ProcessState{Pid: e.cmd.Process.Pid, ExitCode: -1, IsolationConfig: ic, Time: time.Now()}, nil 284 } 285 286 // Exec a command inside a container for exec and java drivers. 287 func (e *UniversalExecutor) Exec(deadline time.Time, name string, args []string) ([]byte, int, error) { 288 ctx, cancel := context.WithDeadline(context.Background(), deadline) 289 defer cancel() 290 return ExecScript(ctx, e.cmd.Dir, e.ctx.TaskEnv, e.cmd.SysProcAttr, name, args) 291 } 292 293 // ExecScript executes cmd with args and returns the output, exit code, and 294 // error. Output is truncated to client/driver/structs.CheckBufSize 295 func ExecScript(ctx context.Context, dir string, env *env.TaskEnv, attrs *syscall.SysProcAttr, 296 name string, args []string) ([]byte, int, error) { 297 name = env.ReplaceEnv(name) 298 cmd := exec.CommandContext(ctx, name, env.ParseAndReplace(args)...) 299 300 // Copy runtime environment from the main command 301 cmd.SysProcAttr = attrs 302 cmd.Dir = dir 303 cmd.Env = env.List() 304 305 // Capture output 306 buf, _ := circbuf.NewBuffer(int64(dstructs.CheckBufSize)) 307 cmd.Stdout = buf 308 cmd.Stderr = buf 309 310 if err := cmd.Run(); err != nil { 311 exitErr, ok := err.(*exec.ExitError) 312 if !ok { 313 // Non-exit error, return it and let the caller treat 314 // it as a critical failure 315 return nil, 0, err 316 } 317 318 // Some kind of error happened; default to critical 319 exitCode := 2 320 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 321 exitCode = status.ExitStatus() 322 } 323 324 // Don't return the exitError as the caller only needs the 325 // output and code. 326 return buf.Bytes(), exitCode, nil 327 } 328 return buf.Bytes(), 0, nil 329 } 330 331 // configureLoggers sets up the standard out/error file rotators 332 func (e *UniversalExecutor) configureLoggers() error { 333 e.rotatorLock.Lock() 334 defer e.rotatorLock.Unlock() 335 336 logFileSize := int64(e.ctx.Task.LogConfig.MaxFileSizeMB * 1024 * 1024) 337 if e.lro == nil { 338 lro, err := logging.NewFileRotator(e.ctx.LogDir, fmt.Sprintf("%v.stdout", e.ctx.Task.Name), 339 e.ctx.Task.LogConfig.MaxFiles, logFileSize, e.logger) 340 if err != nil { 341 return fmt.Errorf("error creating new stdout log file for %q: %v", e.ctx.Task.Name, err) 342 } 343 e.lro = lro 344 } 345 346 if e.lre == nil { 347 lre, err := logging.NewFileRotator(e.ctx.LogDir, fmt.Sprintf("%v.stderr", e.ctx.Task.Name), 348 e.ctx.Task.LogConfig.MaxFiles, logFileSize, e.logger) 349 if err != nil { 350 return fmt.Errorf("error creating new stderr log file for %q: %v", e.ctx.Task.Name, err) 351 } 352 e.lre = lre 353 } 354 return nil 355 } 356 357 // Wait waits until a process has exited and returns it's exitcode and errors 358 func (e *UniversalExecutor) Wait() (*ProcessState, error) { 359 <-e.processExited 360 return e.exitState, nil 361 } 362 363 // COMPAT: prior to Nomad 0.3.2, UpdateTask didn't exist. 364 // UpdateLogConfig updates the log configuration 365 func (e *UniversalExecutor) UpdateLogConfig(logConfig *structs.LogConfig) error { 366 e.ctx.Task.LogConfig = logConfig 367 if e.lro == nil { 368 return fmt.Errorf("log rotator for stdout doesn't exist") 369 } 370 e.lro.MaxFiles = logConfig.MaxFiles 371 e.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024) 372 373 if e.lre == nil { 374 return fmt.Errorf("log rotator for stderr doesn't exist") 375 } 376 e.lre.MaxFiles = logConfig.MaxFiles 377 e.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024) 378 return nil 379 } 380 381 func (e *UniversalExecutor) UpdateTask(task *structs.Task) error { 382 e.ctx.Task = task 383 384 // Updating Log Config 385 e.rotatorLock.Lock() 386 if e.lro != nil && e.lre != nil { 387 fileSize := int64(task.LogConfig.MaxFileSizeMB * 1024 * 1024) 388 e.lro.MaxFiles = task.LogConfig.MaxFiles 389 e.lro.FileSize = fileSize 390 e.lre.MaxFiles = task.LogConfig.MaxFiles 391 e.lre.FileSize = fileSize 392 } 393 e.rotatorLock.Unlock() 394 return nil 395 } 396 397 func (e *UniversalExecutor) wait() { 398 defer close(e.processExited) 399 err := e.cmd.Wait() 400 ic := e.resConCtx.getIsolationConfig() 401 if err == nil { 402 e.exitState = &ProcessState{Pid: 0, ExitCode: 0, IsolationConfig: ic, Time: time.Now()} 403 return 404 } 405 406 e.lre.Close() 407 e.lro.Close() 408 409 exitCode := 1 410 var signal int 411 if exitErr, ok := err.(*exec.ExitError); ok { 412 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 413 exitCode = status.ExitStatus() 414 if status.Signaled() { 415 // bash(1) uses the lower 7 bits of a uint8 416 // to indicate normal program failure (see 417 // <sysexits.h>). If a process terminates due 418 // to a signal, encode the signal number to 419 // indicate which signal caused the process 420 // to terminate. Mirror this exit code 421 // encoding scheme. 422 const exitSignalBase = 128 423 signal = int(status.Signal()) 424 exitCode = exitSignalBase + signal 425 } 426 } 427 } else { 428 e.logger.Printf("[DEBUG] executor: unexpected Wait() error type: %v", err) 429 } 430 431 e.exitState = &ProcessState{Pid: 0, ExitCode: exitCode, Signal: signal, IsolationConfig: ic, Time: time.Now()} 432 } 433 434 var ( 435 // finishedErr is the error message received when trying to kill and already 436 // exited process. 437 finishedErr = "os: process already finished" 438 ) 439 440 // ClientCleanup is the cleanup routine that a Nomad Client uses to remove the 441 // reminants of a child UniversalExecutor. 442 func ClientCleanup(ic *dstructs.IsolationConfig, pid int) error { 443 return clientCleanup(ic, pid) 444 } 445 446 // Exit cleans up the alloc directory, destroys resource container and kills the 447 // user process 448 func (e *UniversalExecutor) Exit() error { 449 var merr multierror.Error 450 if e.syslogServer != nil { 451 e.syslogServer.Shutdown() 452 } 453 454 if e.lre != nil { 455 e.lre.Close() 456 } 457 458 if e.lro != nil { 459 e.lro.Close() 460 } 461 462 // If the executor did not launch a process, return. 463 if e.command == nil { 464 return nil 465 } 466 467 // Prefer killing the process via the resource container. 468 if e.cmd.Process != nil && !e.command.ResourceLimits { 469 proc, err := os.FindProcess(e.cmd.Process.Pid) 470 if err != nil { 471 e.logger.Printf("[ERR] executor: can't find process with pid: %v, err: %v", 472 e.cmd.Process.Pid, err) 473 } else if err := proc.Kill(); err != nil && err.Error() != finishedErr { 474 merr.Errors = append(merr.Errors, 475 fmt.Errorf("can't kill process with pid: %v, err: %v", e.cmd.Process.Pid, err)) 476 } 477 } 478 479 if e.command.ResourceLimits { 480 if err := e.resConCtx.executorCleanup(); err != nil { 481 merr.Errors = append(merr.Errors, err) 482 } 483 } 484 return merr.ErrorOrNil() 485 } 486 487 // Shutdown sends an interrupt signal to the user process 488 func (e *UniversalExecutor) ShutDown() error { 489 if e.cmd.Process == nil { 490 return fmt.Errorf("executor.shutdown error: no process found") 491 } 492 proc, err := os.FindProcess(e.cmd.Process.Pid) 493 if err != nil { 494 return fmt.Errorf("executor.shutdown failed to find process: %v", err) 495 } 496 if runtime.GOOS == "windows" { 497 if err := proc.Kill(); err != nil && err.Error() != finishedErr { 498 return err 499 } 500 return nil 501 } 502 503 // Set default kill signal, as some drivers don't support configurable 504 // signals (such as rkt) 505 var osSignal os.Signal 506 if e.command.TaskKillSignal != nil { 507 osSignal = e.command.TaskKillSignal 508 } else { 509 osSignal = os.Interrupt 510 } 511 512 if err = proc.Signal(osSignal); err != nil && err.Error() != finishedErr { 513 return fmt.Errorf("executor.shutdown error: %v", err) 514 } 515 516 return nil 517 } 518 519 // pidStats returns the resource usage stats per pid 520 func (e *UniversalExecutor) pidStats() (map[string]*cstructs.ResourceUsage, error) { 521 stats := make(map[string]*cstructs.ResourceUsage) 522 e.pidLock.RLock() 523 pids := make(map[int]*nomadPid, len(e.pids)) 524 for k, v := range e.pids { 525 pids[k] = v 526 } 527 e.pidLock.RUnlock() 528 for pid, np := range pids { 529 p, err := process.NewProcess(int32(pid)) 530 if err != nil { 531 e.logger.Printf("[TRACE] executor: unable to create new process with pid: %v", pid) 532 continue 533 } 534 ms := &cstructs.MemoryStats{} 535 if memInfo, err := p.MemoryInfo(); err == nil { 536 ms.RSS = memInfo.RSS 537 ms.Swap = memInfo.Swap 538 ms.Measured = ExecutorBasicMeasuredMemStats 539 } 540 541 cs := &cstructs.CpuStats{} 542 if cpuStats, err := p.Times(); err == nil { 543 cs.SystemMode = np.cpuStatsSys.Percent(cpuStats.System * float64(time.Second)) 544 cs.UserMode = np.cpuStatsUser.Percent(cpuStats.User * float64(time.Second)) 545 cs.Measured = ExecutorBasicMeasuredCpuStats 546 547 // calculate cpu usage percent 548 cs.Percent = np.cpuStatsTotal.Percent(cpuStats.Total() * float64(time.Second)) 549 } 550 stats[strconv.Itoa(pid)] = &cstructs.ResourceUsage{MemoryStats: ms, CpuStats: cs} 551 } 552 553 return stats, nil 554 } 555 556 // lookupBin looks for path to the binary to run by looking for the binary in 557 // the following locations, in-order: task/local/, task/, based on host $PATH. 558 // The return path is absolute. 559 func (e *UniversalExecutor) lookupBin(bin string) (string, error) { 560 // Check in the local directory 561 local := filepath.Join(e.ctx.TaskDir, allocdir.TaskLocal, bin) 562 if _, err := os.Stat(local); err == nil { 563 return local, nil 564 } 565 566 // Check at the root of the task's directory 567 root := filepath.Join(e.ctx.TaskDir, bin) 568 if _, err := os.Stat(root); err == nil { 569 return root, nil 570 } 571 572 // Check the $PATH 573 if host, err := exec.LookPath(bin); err == nil { 574 return host, nil 575 } 576 577 return "", fmt.Errorf("binary %q could not be found", bin) 578 } 579 580 // makeExecutable makes the given file executable for root,group,others. 581 func (e *UniversalExecutor) makeExecutable(binPath string) error { 582 if runtime.GOOS == "windows" { 583 return nil 584 } 585 586 fi, err := os.Stat(binPath) 587 if err != nil { 588 if os.IsNotExist(err) { 589 return fmt.Errorf("binary %q does not exist", binPath) 590 } 591 return fmt.Errorf("specified binary is invalid: %v", err) 592 } 593 594 // If it is not executable, make it so. 595 perm := fi.Mode().Perm() 596 req := os.FileMode(0555) 597 if perm&req != req { 598 if err := os.Chmod(binPath, perm|req); err != nil { 599 return fmt.Errorf("error making %q executable: %s", binPath, err) 600 } 601 } 602 return nil 603 } 604 605 // getFreePort returns a free port ready to be listened on between upper and 606 // lower bounds 607 func (e *UniversalExecutor) getListener(lowerBound uint, upperBound uint) (net.Listener, error) { 608 if runtime.GOOS == "windows" { 609 return e.listenerTCP(lowerBound, upperBound) 610 } 611 612 return e.listenerUnix() 613 } 614 615 // listenerTCP creates a TCP listener using an unused port between an upper and 616 // lower bound 617 func (e *UniversalExecutor) listenerTCP(lowerBound uint, upperBound uint) (net.Listener, error) { 618 for i := lowerBound; i <= upperBound; i++ { 619 addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%v", i)) 620 if err != nil { 621 return nil, err 622 } 623 l, err := net.ListenTCP("tcp", addr) 624 if err != nil { 625 continue 626 } 627 return l, nil 628 } 629 return nil, fmt.Errorf("No free port found") 630 } 631 632 // listenerUnix creates a Unix domain socket 633 func (e *UniversalExecutor) listenerUnix() (net.Listener, error) { 634 f, err := ioutil.TempFile("", "plugin") 635 if err != nil { 636 return nil, err 637 } 638 path := f.Name() 639 640 if err := f.Close(); err != nil { 641 return nil, err 642 } 643 if err := os.Remove(path); err != nil { 644 return nil, err 645 } 646 647 return net.Listen("unix", path) 648 } 649 650 // collectPids collects the pids of the child processes that the executor is 651 // running every 5 seconds 652 func (e *UniversalExecutor) collectPids() { 653 // Fire the timer right away when the executor starts from there on the pids 654 // are collected every scan interval 655 timer := time.NewTimer(0) 656 defer timer.Stop() 657 for { 658 select { 659 case <-timer.C: 660 pids, err := e.getAllPids() 661 if err != nil { 662 e.logger.Printf("[DEBUG] executor: error collecting pids: %v", err) 663 } 664 e.pidLock.Lock() 665 666 // Adding pids which are not being tracked 667 for pid, np := range pids { 668 if _, ok := e.pids[pid]; !ok { 669 e.pids[pid] = np 670 } 671 } 672 // Removing pids which are no longer present 673 for pid := range e.pids { 674 if _, ok := pids[pid]; !ok { 675 delete(e.pids, pid) 676 } 677 } 678 e.pidLock.Unlock() 679 timer.Reset(pidScanInterval) 680 case <-e.processExited: 681 return 682 } 683 } 684 } 685 686 // scanPids scans all the pids on the machine running the current executor and 687 // returns the child processes of the executor. 688 func (e *UniversalExecutor) scanPids(parentPid int, allPids []ps.Process) (map[int]*nomadPid, error) { 689 processFamily := make(map[int]struct{}) 690 processFamily[parentPid] = struct{}{} 691 692 // A mapping of pids to their parent pids. It is used to build the process 693 // tree of the executing task 694 pidsRemaining := make(map[int]int, len(allPids)) 695 for _, pid := range allPids { 696 pidsRemaining[pid.Pid()] = pid.PPid() 697 } 698 699 for { 700 // flag to indicate if we have found a match 701 foundNewPid := false 702 703 for pid, ppid := range pidsRemaining { 704 _, childPid := processFamily[ppid] 705 706 // checking if the pid is a child of any of the parents 707 if childPid { 708 processFamily[pid] = struct{}{} 709 delete(pidsRemaining, pid) 710 foundNewPid = true 711 } 712 } 713 714 // not scanning anymore if we couldn't find a single match 715 if !foundNewPid { 716 break 717 } 718 } 719 720 res := make(map[int]*nomadPid) 721 for pid := range processFamily { 722 np := nomadPid{ 723 pid: pid, 724 cpuStatsTotal: stats.NewCpuStats(), 725 cpuStatsUser: stats.NewCpuStats(), 726 cpuStatsSys: stats.NewCpuStats(), 727 } 728 res[pid] = &np 729 } 730 return res, nil 731 } 732 733 // aggregatedResourceUsage aggregates the resource usage of all the pids and 734 // returns a TaskResourceUsage data point 735 func (e *UniversalExecutor) aggregatedResourceUsage(pidStats map[string]*cstructs.ResourceUsage) *cstructs.TaskResourceUsage { 736 ts := time.Now().UTC().UnixNano() 737 var ( 738 systemModeCPU, userModeCPU, percent float64 739 totalRSS, totalSwap uint64 740 ) 741 742 for _, pidStat := range pidStats { 743 systemModeCPU += pidStat.CpuStats.SystemMode 744 userModeCPU += pidStat.CpuStats.UserMode 745 percent += pidStat.CpuStats.Percent 746 747 totalRSS += pidStat.MemoryStats.RSS 748 totalSwap += pidStat.MemoryStats.Swap 749 } 750 751 totalCPU := &cstructs.CpuStats{ 752 SystemMode: systemModeCPU, 753 UserMode: userModeCPU, 754 Percent: percent, 755 Measured: ExecutorBasicMeasuredCpuStats, 756 TotalTicks: e.systemCpuStats.TicksConsumed(percent), 757 } 758 759 totalMemory := &cstructs.MemoryStats{ 760 RSS: totalRSS, 761 Swap: totalSwap, 762 Measured: ExecutorBasicMeasuredMemStats, 763 } 764 765 resourceUsage := cstructs.ResourceUsage{ 766 MemoryStats: totalMemory, 767 CpuStats: totalCPU, 768 } 769 return &cstructs.TaskResourceUsage{ 770 ResourceUsage: &resourceUsage, 771 Timestamp: ts, 772 Pids: pidStats, 773 } 774 } 775 776 // Signal sends the passed signal to the task 777 func (e *UniversalExecutor) Signal(s os.Signal) error { 778 if e.cmd.Process == nil { 779 return fmt.Errorf("Task not yet run") 780 } 781 782 e.logger.Printf("[DEBUG] executor: sending signal %s to PID %d", s, e.cmd.Process.Pid) 783 err := e.cmd.Process.Signal(s) 784 if err != nil { 785 e.logger.Printf("[ERR] executor: sending signal %v failed: %v", s, err) 786 return err 787 } 788 789 return nil 790 }