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