Class: MemoryMonitor

Inherits:
Object
  • Object
show all
Defined in:
lib/memory_monitor.rb,
lib/memory_monitor/version.rb

Constant Summary collapse

SIGNALS =
%w(HUP INT QUIT USR1 USR2 TERM)
VERSION =
"0.1.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command, limit:, timeout: 2, interval: 1) ⇒ MemoryMonitor

Returns a new instance of MemoryMonitor.


13
14
15
16
17
18
# File 'lib/memory_monitor.rb', line 13

def initialize(command, limit:, timeout: 2, interval: 1)
  @command  = command
  @limit    = limit
  @timeout  = timeout
  @interval = interval
end

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command


4
5
6
# File 'lib/memory_monitor.rb', line 4

def command
  @command
end

#intervalObject (readonly)

Returns the value of attribute interval


4
5
6
# File 'lib/memory_monitor.rb', line 4

def interval
  @interval
end

#limitObject (readonly)

Returns the value of attribute limit


4
5
6
# File 'lib/memory_monitor.rb', line 4

def limit
  @limit
end

#memory_in_kilobytesObject (readonly)

Returns the value of attribute memory_in_kilobytes


4
5
6
# File 'lib/memory_monitor.rb', line 4

def memory_in_kilobytes
  @memory_in_kilobytes
end

#memory_in_megabytesObject (readonly)

Returns the value of attribute memory_in_megabytes


4
5
6
# File 'lib/memory_monitor.rb', line 4

def memory_in_megabytes
  @memory_in_megabytes
end

#pgidObject (readonly)

Returns the value of attribute pgid


4
5
6
# File 'lib/memory_monitor.rb', line 4

def pgid
  @pgid
end

#pidObject (readonly)

Returns the value of attribute pid


4
5
6
# File 'lib/memory_monitor.rb', line 4

def pid
  @pid
end

#timeoutObject (readonly)

Returns the value of attribute timeout


4
5
6
# File 'lib/memory_monitor.rb', line 4

def timeout
  @timeout
end

Class Method Details

.run(*args) ⇒ Object


9
10
11
# File 'lib/memory_monitor.rb', line 9

def self.run(*args)
  new(*args).run
end

Instance Method Details

#forward_signalsObject


78
79
80
81
82
83
84
85
86
87
88
# File 'lib/memory_monitor.rb', line 78

def forward_signals
  SIGNALS.each do |signal|
    Signal.trap(signal) do
      begin
        Process.kill(signal, pid) if pid
      rescue Errno::ESRCH
        # pid doesn't exist
      end
    end
  end
end

#killObject


69
70
71
72
# File 'lib/memory_monitor.rb', line 69

def kill
  log "process failed to stop after #{timeout}s, sending SIGKILL"
  Process.kill("KILL", -pgid)
end

#limit_in_kilobytesObject


38
39
40
# File 'lib/memory_monitor.rb', line 38

def limit_in_kilobytes
  limit * 1024
end

#log(message) ⇒ Object


74
75
76
# File 'lib/memory_monitor.rb', line 74

def log(message)
  $stderr.puts "[memory_monitor] #{message}"
end

#monitorObject


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/memory_monitor.rb', line 42

def monitor
  loop do
    @memory_in_kilobytes =
      `ps -o pgrp= -o rss=`
        .split("\n")
        .map { |line| line.split(" ").map(&:to_i) }
        .select { |pgid, _| pgid == self.pgid }
        .map { |_, size| size }
        .max

    @memory_in_megabytes = (memory_in_kilobytes / 1024).round(1)

    if memory_in_kilobytes > limit_in_kilobytes
      term
      sleep timeout
      kill
    end

    sleep interval
  end
end

#runObject


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/memory_monitor.rb', line 20

def run
  forward_signals

  @pid  = Process.spawn(*command, pgroup: true)
  @pgid = Process.getpgid(pid)

  Thread.abort_on_exception = true
  Thread.new { monitor }

  Process.wait pid

  if $?.success?
    $?.exitstatus
  else
    false
  end
end

#termObject


64
65
66
67
# File 'lib/memory_monitor.rb', line 64

def term
  log "process memory #{memory_in_megabytes}MB exceeded limit #{limit}MB, sending SIGTERM"
  Process.kill("TERM", -pgid)
end