unicorn-worker-killer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +2 -0
- data/ChangeLog +3 -0
- data/Gemfile +3 -0
- data/LICENSE +64 -0
- data/README.md +23 -0
- data/Rakefile +5 -0
- data/VERSION +1 -0
- data/lib/unicorn/worker_killer.rb +111 -0
- data/unicorn-worker-killer.gemspec +20 -0
- metadata +88 -0
data/AUTHORS
ADDED
data/ChangeLog
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
Unicorn is copyrighted free software by all contributors, see logs in
|
2
|
+
revision control for names and email addresses of all of them.
|
3
|
+
|
4
|
+
You can redistribute it and/or modify it under either the terms of the
|
5
|
+
GNU General Public License (GPL) as published by the Free Software
|
6
|
+
Foundation (FSF), version {3.0}[http://www.gnu.org/licenses/gpl-3.0.txt]
|
7
|
+
or version {2.0}[http://www.gnu.org/licenses/gpl-2.0.txt]
|
8
|
+
or the Ruby-specific license terms (see below).
|
9
|
+
|
10
|
+
The unicorn project leader (Eric Wong) reserves the right to add future
|
11
|
+
versions of the GPL (and no other licenses) as published by the FSF to
|
12
|
+
the licensing terms.
|
13
|
+
|
14
|
+
=== Ruby-specific terms (if you're not using the GPLv2 or GPLv3)
|
15
|
+
|
16
|
+
1. You may make and give away verbatim copies of the source form of the
|
17
|
+
software without restriction, provided that you duplicate all of the
|
18
|
+
original copyright notices and associated disclaimers.
|
19
|
+
|
20
|
+
2. You may modify your copy of the software in any way, provided that
|
21
|
+
you do at least ONE of the following:
|
22
|
+
|
23
|
+
a) place your modifications in the Public Domain or otherwise make them
|
24
|
+
Freely Available, such as by posting said modifications to Usenet or an
|
25
|
+
equivalent medium, or by allowing the author to include your
|
26
|
+
modifications in the software.
|
27
|
+
|
28
|
+
b) use the modified software only within your corporation or
|
29
|
+
organization.
|
30
|
+
|
31
|
+
c) rename any non-standard executables so the names do not conflict with
|
32
|
+
standard executables, which must also be provided.
|
33
|
+
|
34
|
+
d) make other distribution arrangements with the author.
|
35
|
+
|
36
|
+
3. You may distribute the software in object code or executable
|
37
|
+
form, provided that you do at least ONE of the following:
|
38
|
+
|
39
|
+
a) distribute the executables and library files of the software,
|
40
|
+
together with instructions (in the manual page or equivalent) on where
|
41
|
+
to get the original distribution.
|
42
|
+
|
43
|
+
b) accompany the distribution with the machine-readable source of the
|
44
|
+
software.
|
45
|
+
|
46
|
+
c) give non-standard executables non-standard names, with
|
47
|
+
instructions on where to get the original software distribution.
|
48
|
+
|
49
|
+
d) make other distribution arrangements with the author.
|
50
|
+
|
51
|
+
4. You may modify and include the part of the software into any other
|
52
|
+
software (possibly commercial). But some files in the distribution
|
53
|
+
are not written by the author, so that they are not under this terms.
|
54
|
+
|
55
|
+
5. The scripts and library files supplied as input to or produced as
|
56
|
+
output from the software do not automatically fall under the
|
57
|
+
copyright of the software, but belong to whomever generated them,
|
58
|
+
and may be sold commercially, and may be aggregated with this
|
59
|
+
software.
|
60
|
+
|
61
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
62
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
63
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
64
|
+
PURPOSE.
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# unicorn-worker-killer
|
2
|
+
|
3
|
+
Killing Unicorn worker based on 1) Max number of requests and 2) Process memory size (RSS), without affecting the request.
|
4
|
+
|
5
|
+
# Install
|
6
|
+
|
7
|
+
gem 'unicorn-worker-killer'
|
8
|
+
|
9
|
+
# Usage
|
10
|
+
|
11
|
+
add these lines to your config.ru.
|
12
|
+
|
13
|
+
# Unicorn self-process killer
|
14
|
+
require 'unicorn/worker_killer'
|
15
|
+
|
16
|
+
# Max requests per worker
|
17
|
+
use UnicornWorkerKiller::MaxRequests, 10240 + Random.rand(10240)
|
18
|
+
|
19
|
+
# Max memory size (RSS) per worker
|
20
|
+
use UnicornWorkerKiller::Oom, (96 + Random.rand(32)) * 1024**2
|
21
|
+
|
22
|
+
# TODO
|
23
|
+
- Get RSS (Resident Set Size) without forking the child process at Mac OS and Windows
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module UnicornWorkerKiller
|
2
|
+
# Self-destruction by sending the signals to myself. The process sometimes
|
3
|
+
# doesn't terminate by SIGTERM, so this tries to send SIGQUIT and SIGKILL
|
4
|
+
# if it doesn't finish immediately.
|
5
|
+
def self.kill_self(logger, start_time)
|
6
|
+
alive_sec = (Time.now - start_time).to_i
|
7
|
+
|
8
|
+
i = 0
|
9
|
+
while true
|
10
|
+
i += 1
|
11
|
+
sig = :TERM
|
12
|
+
if i > 10 # TODO configurable TERM MAX
|
13
|
+
sig = :QUIT
|
14
|
+
elsif i > 15 # TODO configurable QUIT MAX
|
15
|
+
sig = :KILL
|
16
|
+
end
|
17
|
+
|
18
|
+
logger.warn "#{self} send SIGTERM (pid: #{Process.pid})\talive: #{alive_sec} sec (trial #{i})"
|
19
|
+
Process.kill sig, Process.pid
|
20
|
+
|
21
|
+
sleep 1 # TODO configurable sleep
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Oom
|
26
|
+
# Killing the process must be occurred at the outside of the request. We're
|
27
|
+
# using similar techniques used by OobGC, to ensure actual killing doesn't
|
28
|
+
# affect the request.
|
29
|
+
#
|
30
|
+
# @see https://github.com/defunkt/unicorn/blob/master/lib/unicorn/oob_gc.rb#L40
|
31
|
+
def self.new(app, memory_size = (1024**3), check_cycle = 16)
|
32
|
+
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
33
|
+
s.extend(self)
|
34
|
+
s.instance_variable_set(:@_worker_memory_size, memory_size)
|
35
|
+
s.instance_variable_set(:@_worker_check_cycle, check_cycle)
|
36
|
+
s.instance_variable_set(:@_worker_check_count, 0)
|
37
|
+
end
|
38
|
+
app # pretend to be Rack middleware since it was in the past
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_client(client)
|
42
|
+
@_worker_process_start ||= Time.now
|
43
|
+
super(client) # Unicorn::HttpServer#process_client
|
44
|
+
|
45
|
+
c = @_worker_check_count + 1
|
46
|
+
if c % @_worker_check_cycle == 0
|
47
|
+
@_worker_check_count = 0
|
48
|
+
if _worker_rss() > @_worker_memory_size
|
49
|
+
UnicornWorkerKiller.kill_self(logger, @_worker_process_start)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
@_worker_check_count = c
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def _worker_rss
|
58
|
+
proc_status = "/proc/#{Process.pid}/status"
|
59
|
+
if File.exists? proc_status
|
60
|
+
open(proc_status).each_line { |l|
|
61
|
+
if l.include? 'VmRSS'
|
62
|
+
ls = l.split
|
63
|
+
if ls.length == 3
|
64
|
+
value = ls[1].to_i
|
65
|
+
unit = ls[2]
|
66
|
+
case unit.downcase
|
67
|
+
when 'kb'
|
68
|
+
return value*(1024**1)
|
69
|
+
when 'mb'
|
70
|
+
return value*(1024**2)
|
71
|
+
when 'gb'
|
72
|
+
return value*(1024**3)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Forking the child process sometimes fails under low memory condition.
|
80
|
+
# It would be ideal for not forking the process to get RSS. For Linux,
|
81
|
+
# this module reads '/proc/<pid>/status' to get RSS, but not for other
|
82
|
+
# environments (e.g. MacOS and Windows).
|
83
|
+
kb = `ps -o rss= -p #{Process.pid}`.to_i
|
84
|
+
return kb * 1024
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module MaxRequests
|
89
|
+
# Killing the process must be occurred at the outside of the request. We're
|
90
|
+
# using similar techniques used by OobGC, to ensure actual killing doesn't
|
91
|
+
# affect the request.
|
92
|
+
#
|
93
|
+
# @see https://github.com/defunkt/unicorn/blob/master/lib/unicorn/oob_gc.rb#L40
|
94
|
+
def self.new(app, max_requests = 1024)
|
95
|
+
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
96
|
+
s.extend(self)
|
97
|
+
s.instance_variable_set(:@_worker_max_requests, max_requests)
|
98
|
+
end
|
99
|
+
app # pretend to be Rack middleware since it was in the past
|
100
|
+
end
|
101
|
+
|
102
|
+
def process_client(client)
|
103
|
+
@_worker_process_start ||= Time.now
|
104
|
+
super(client) # Unicorn::HttpServer#process_client
|
105
|
+
|
106
|
+
if (@_worker_max_requests -= 1) <= 0
|
107
|
+
UnicornWorkerKiller.kill_self(logger, @_worker_process_start)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "unicorn-worker-killer"
|
6
|
+
gem.description = "Kill unicorn workers by memory and request counts"
|
7
|
+
gem.homepage = "https://github.com/treasure-data/unicorn-worker-killer"
|
8
|
+
gem.summary = gem.description
|
9
|
+
gem.version = File.read("VERSION").strip
|
10
|
+
gem.authors = ["Kazuki Ohta", "Sadayuki Furuhashi"]
|
11
|
+
gem.email = ["kazuki.ohta@gmail.com", "frsyuki@gmail.com"]
|
12
|
+
gem.has_rdoc = false
|
13
|
+
#gem.platform = Gem::Platform::RUBY
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
gem.add_dependency "unicorn", "~> 4.2.0"
|
19
|
+
gem.add_development_dependency "rake", ">= 0.9.2"
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unicorn-worker-killer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kazuki Ohta
|
9
|
+
- Sadayuki Furuhashi
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-11-18 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: unicorn
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 4.2.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 4.2.0
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rake
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 0.9.2
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.9.2
|
47
|
+
description: Kill unicorn workers by memory and request counts
|
48
|
+
email:
|
49
|
+
- kazuki.ohta@gmail.com
|
50
|
+
- frsyuki@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- AUTHORS
|
56
|
+
- ChangeLog
|
57
|
+
- Gemfile
|
58
|
+
- LICENSE
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- VERSION
|
62
|
+
- lib/unicorn/worker_killer.rb
|
63
|
+
- unicorn-worker-killer.gemspec
|
64
|
+
homepage: https://github.com/treasure-data/unicorn-worker-killer
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.24
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Kill unicorn workers by memory and request counts
|
88
|
+
test_files: []
|