atig 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +24 -0
- data/Gemfile +3 -0
- data/README.mkdn +52 -0
- data/Rakefile +15 -0
- data/atig.gemspec +25 -0
- data/bin/atig +74 -0
- data/docs/OMakefile +32 -0
- data/docs/OMakeroot +45 -0
- data/docs/_static/allow.png +0 -0
- data/docs/_static/emacs.png +0 -0
- data/docs/_static/irc_setting.png +0 -0
- data/docs/_static/irssi.png +0 -0
- data/docs/_static/limechat.png +0 -0
- data/docs/_static/limechat_s.png +0 -0
- data/docs/_static/oauth_channel.png +0 -0
- data/docs/_static/screenshot.png +0 -0
- data/docs/_static/structure.png +0 -0
- data/docs/_static/verify.png +0 -0
- data/docs/changelog.rst +96 -0
- data/docs/commandline_options.rst +21 -0
- data/docs/commands.rst +84 -0
- data/docs/conf.py +194 -0
- data/docs/config.rst +159 -0
- data/docs/feature.rst +41 -0
- data/docs/graphics.graffle +1995 -0
- data/docs/hacking_guide.rst +43 -0
- data/docs/index.rst +109 -0
- data/docs/irc.rst +31 -0
- data/docs/options.rst +75 -0
- data/docs/quickstart.rst +89 -0
- data/docs/resize.sh +7 -0
- data/docs/tiarra.rst +2 -0
- data/docs/tig.rst +21 -0
- data/lib/atig.rb +19 -0
- data/lib/atig/agent.rb +8 -0
- data/lib/atig/agent/agent.rb +38 -0
- data/lib/atig/agent/clenup.rb +23 -0
- data/lib/atig/agent/dm.rb +35 -0
- data/lib/atig/agent/following.rb +45 -0
- data/lib/atig/agent/full_list.rb +20 -0
- data/lib/atig/agent/list.rb +55 -0
- data/lib/atig/agent/list_status.rb +46 -0
- data/lib/atig/agent/mention.rb +13 -0
- data/lib/atig/agent/other_list.rb +18 -0
- data/lib/atig/agent/own_list.rb +18 -0
- data/lib/atig/agent/stream_follow.rb +38 -0
- data/lib/atig/agent/timeline.rb +13 -0
- data/lib/atig/agent/user_stream.rb +31 -0
- data/lib/atig/basic_twitter.rb +116 -0
- data/lib/atig/bitly.rb +52 -0
- data/lib/atig/channel.rb +5 -0
- data/lib/atig/channel/channel.rb +17 -0
- data/lib/atig/channel/dm.rb +14 -0
- data/lib/atig/channel/list.rb +76 -0
- data/lib/atig/channel/mention.rb +20 -0
- data/lib/atig/channel/retweet.rb +28 -0
- data/lib/atig/channel/timeline.rb +74 -0
- data/lib/atig/command.rb +21 -0
- data/lib/atig/command/autofix.rb +58 -0
- data/lib/atig/command/command.rb +24 -0
- data/lib/atig/command/command_helper.rb +95 -0
- data/lib/atig/command/destroy.rb +44 -0
- data/lib/atig/command/dm.rb +31 -0
- data/lib/atig/command/favorite.rb +27 -0
- data/lib/atig/command/info.rb +50 -0
- data/lib/atig/command/limit.rb +15 -0
- data/lib/atig/command/location.rb +23 -0
- data/lib/atig/command/name.rb +18 -0
- data/lib/atig/command/option.rb +37 -0
- data/lib/atig/command/refresh.rb +18 -0
- data/lib/atig/command/reply.rb +37 -0
- data/lib/atig/command/retweet.rb +63 -0
- data/lib/atig/command/search.rb +51 -0
- data/lib/atig/command/spam.rb +26 -0
- data/lib/atig/command/status.rb +41 -0
- data/lib/atig/command/thread.rb +44 -0
- data/lib/atig/command/time.rb +32 -0
- data/lib/atig/command/uptime.rb +32 -0
- data/lib/atig/command/user.rb +42 -0
- data/lib/atig/command/user_info.rb +27 -0
- data/lib/atig/command/version.rb +49 -0
- data/lib/atig/command/whois.rb +39 -0
- data/lib/atig/db/db.rb +60 -0
- data/lib/atig/db/followings.rb +131 -0
- data/lib/atig/db/listenable.rb +22 -0
- data/lib/atig/db/lists.rb +76 -0
- data/lib/atig/db/roman.rb +30 -0
- data/lib/atig/db/sized_uniq_array.rb +62 -0
- data/lib/atig/db/sql.rb +35 -0
- data/lib/atig/db/statuses.rb +147 -0
- data/lib/atig/db/transaction.rb +47 -0
- data/lib/atig/exception_util.rb +26 -0
- data/lib/atig/gateway.rb +62 -0
- data/lib/atig/gateway/channel.rb +99 -0
- data/lib/atig/gateway/session.rb +326 -0
- data/lib/atig/http.rb +95 -0
- data/lib/atig/ifilter.rb +7 -0
- data/lib/atig/ifilter/expand_url.rb +74 -0
- data/lib/atig/ifilter/retweet.rb +14 -0
- data/lib/atig/ifilter/retweet_time.rb +16 -0
- data/lib/atig/ifilter/sanitize.rb +18 -0
- data/lib/atig/ifilter/strip.rb +15 -0
- data/lib/atig/ifilter/utf7.rb +26 -0
- data/lib/atig/ifilter/xid.rb +36 -0
- data/lib/atig/levenshtein.rb +49 -0
- data/lib/atig/monkey.rb +4 -0
- data/lib/atig/oauth-patch.rb +40 -0
- data/lib/atig/oauth.rb +55 -0
- data/lib/atig/ofilter.rb +4 -0
- data/lib/atig/ofilter/escape_url.rb +102 -0
- data/lib/atig/ofilter/footer.rb +20 -0
- data/lib/atig/ofilter/geo.rb +17 -0
- data/lib/atig/ofilter/short_url.rb +47 -0
- data/lib/atig/option.rb +90 -0
- data/lib/atig/scheduler.rb +79 -0
- data/lib/atig/search.rb +22 -0
- data/lib/atig/search_twitter.rb +21 -0
- data/lib/atig/sized_hash.rb +33 -0
- data/lib/atig/stream.rb +66 -0
- data/lib/atig/twitter.rb +79 -0
- data/lib/atig/twitter_struct.rb +63 -0
- data/lib/atig/unu.rb +27 -0
- data/lib/atig/update_checker.rb +53 -0
- data/lib/atig/url_escape.rb +62 -0
- data/lib/atig/util.rb +16 -0
- data/lib/atig/version.rb +3 -0
- data/lib/memory_profiler.rb +77 -0
- data/spec/command/autofix_spec.rb +35 -0
- data/spec/command/destroy_spec.rb +98 -0
- data/spec/command/dm_spec.rb +28 -0
- data/spec/command/favorite_spec.rb +55 -0
- data/spec/command/limit_spec.rb +27 -0
- data/spec/command/location_spec.rb +25 -0
- data/spec/command/name_spec.rb +19 -0
- data/spec/command/option_spec.rb +133 -0
- data/spec/command/refresh_spec.rb +22 -0
- data/spec/command/reply_spec.rb +79 -0
- data/spec/command/retweet_spec.rb +66 -0
- data/spec/command/spam_spec.rb +27 -0
- data/spec/command/status_spec.rb +44 -0
- data/spec/command/thread_spec.rb +91 -0
- data/spec/command/time_spec.rb +52 -0
- data/spec/command/uptime_spec.rb +55 -0
- data/spec/command/user_info_spec.rb +42 -0
- data/spec/command/user_spec.rb +50 -0
- data/spec/command/version_spec.rb +67 -0
- data/spec/command/whois_spec.rb +78 -0
- data/spec/db/followings_spec.rb +100 -0
- data/spec/db/listenable_spec.rb +32 -0
- data/spec/db/lists_spec.rb +104 -0
- data/spec/db/roman_spec.rb +17 -0
- data/spec/db/sized_uniq_array_spec.rb +63 -0
- data/spec/db/statuses_spec.rb +180 -0
- data/spec/ifilter/expand_url_spec.rb +44 -0
- data/spec/ifilter/retweet_spec.rb +28 -0
- data/spec/ifilter/retweet_time_spec.rb +25 -0
- data/spec/ifilter/sanitize_spec.rb +25 -0
- data/spec/ifilter/sid_spec.rb +29 -0
- data/spec/ifilter/strip_spec.rb +23 -0
- data/spec/ifilter/tid_spec.rb +29 -0
- data/spec/ifilter/utf7_spec.rb +30 -0
- data/spec/levenshtein_spec.rb +24 -0
- data/spec/ofilter/escape_url_spec.rb +50 -0
- data/spec/ofilter/footer_spec.rb +32 -0
- data/spec/ofilter/geo_spec.rb +33 -0
- data/spec/ofilter/short_url_spec.rb +127 -0
- data/spec/option_spec.rb +91 -0
- data/spec/sized_hash_spec.rb +45 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/update_checker_spec.rb +55 -0
- metadata +326 -0
data/lib/atig/stream.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'uri'
|
5
|
+
require 'logger'
|
6
|
+
require 'atig/twitter_struct'
|
7
|
+
require 'atig/util'
|
8
|
+
require 'atig/url_escape'
|
9
|
+
|
10
|
+
module Atig
|
11
|
+
class Stream
|
12
|
+
include Util
|
13
|
+
|
14
|
+
class APIFailed < StandardError; end
|
15
|
+
def initialize(context, user, password)
|
16
|
+
@log = context.log
|
17
|
+
@opts = context.opts
|
18
|
+
@user = user
|
19
|
+
@password = password
|
20
|
+
end
|
21
|
+
|
22
|
+
def watch(path, query={}, &f)
|
23
|
+
path.sub!(%r{\A/+}, "")
|
24
|
+
|
25
|
+
uri = api_base
|
26
|
+
uri.path += path
|
27
|
+
uri.path += ".json"
|
28
|
+
uri.query = query.to_query_str unless query.empty?
|
29
|
+
|
30
|
+
@log.debug [uri.to_s]
|
31
|
+
|
32
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
33
|
+
request = Net::HTTP::Post.new uri.request_uri
|
34
|
+
request.basic_auth @user, @password
|
35
|
+
|
36
|
+
http.request(request) do |response|
|
37
|
+
unless response.code == '200' then
|
38
|
+
raise APIFailed,"#{response.code} #{response.message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
buffer = ''
|
43
|
+
response.read_body do |chunk|
|
44
|
+
next if chunk.chomp.empty?
|
45
|
+
buffer << chunk.to_s
|
46
|
+
|
47
|
+
if buffer =~ /\A(.*)\n/ then
|
48
|
+
text = $1
|
49
|
+
unless text.strip.empty?
|
50
|
+
f.call TwitterStruct.make(JSON.parse(text))
|
51
|
+
end
|
52
|
+
buffer = ''
|
53
|
+
end
|
54
|
+
end
|
55
|
+
rescue => e
|
56
|
+
raise APIFailed,e.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def api_base
|
63
|
+
URI(@opts.stream_api_base)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/atig/twitter.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
require 'atig/basic_twitter'
|
4
|
+
require 'atig/http'
|
5
|
+
|
6
|
+
module Atig
|
7
|
+
# from tig.rb
|
8
|
+
class Twitter < BasicTwitter
|
9
|
+
def initialize(context, oauth)
|
10
|
+
super context, :api_base
|
11
|
+
@oauth = oauth
|
12
|
+
@http = Atig::Http.new @log
|
13
|
+
end
|
14
|
+
|
15
|
+
# authenticate = trueでないとSSL verified errorがでることがある
|
16
|
+
def page(path, name, authenticate = true, &block)
|
17
|
+
limit = 0.98 * @remain # 98% of IP based rate limit
|
18
|
+
r = []
|
19
|
+
cursor = -1
|
20
|
+
1.upto(limit) do |num|
|
21
|
+
# next_cursor にアクセスするとNot found が返ってくることがあるので,その時はbreak
|
22
|
+
ret = api(path, { :cursor => cursor }, { :authenticate => authenticate }) rescue break
|
23
|
+
arr = ret[name.to_s]
|
24
|
+
r.concat arr
|
25
|
+
cursor = ret[:next_cursor]
|
26
|
+
break if cursor.zero?
|
27
|
+
end
|
28
|
+
r
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.http_methods(*methods)
|
32
|
+
methods.each do |m|
|
33
|
+
self.module_eval <<END
|
34
|
+
def #{m}(path, query = {}, opts = {})
|
35
|
+
opts.update( :method => :#{m})
|
36
|
+
api path, query, opts
|
37
|
+
end
|
38
|
+
END
|
39
|
+
end
|
40
|
+
end
|
41
|
+
http_methods :get, :post, :put, :delete
|
42
|
+
|
43
|
+
protected
|
44
|
+
def request(uri, opts)
|
45
|
+
authenticate = opts.fetch(:authenticate, true)
|
46
|
+
method = opts.fetch(:method, :get)
|
47
|
+
|
48
|
+
header = {}
|
49
|
+
req = @http.req method, uri, header
|
50
|
+
@log.debug [req.method, uri.to_s]
|
51
|
+
|
52
|
+
if authenticate
|
53
|
+
oauth 30, req
|
54
|
+
else
|
55
|
+
@http.http(uri, 30, 30).request req
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def oauth(time, req)
|
60
|
+
timeout(time) do
|
61
|
+
headers = {}
|
62
|
+
req.each{|k,v| headers[k] = v }
|
63
|
+
|
64
|
+
case req
|
65
|
+
when Net::HTTP::Get
|
66
|
+
@oauth.get req.path,headers
|
67
|
+
when Net::HTTP::Head
|
68
|
+
@oauth.head req.path,headers
|
69
|
+
when Net::HTTP::Post
|
70
|
+
@oauth.post req.path,req.body,headers
|
71
|
+
when Net::HTTP::Put
|
72
|
+
@oauth.put req.path,req.body,headers
|
73
|
+
when Net::HTTP::Delete
|
74
|
+
@oauth.delete req.path,headers
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
2
|
+
#
|
3
|
+
|
4
|
+
module Atig
|
5
|
+
# from tig.rb
|
6
|
+
class TwitterStruct
|
7
|
+
def self.make(obj)
|
8
|
+
case obj
|
9
|
+
when Hash
|
10
|
+
obj = obj.dup
|
11
|
+
obj.each do |k, v|
|
12
|
+
obj[k] = TwitterStruct.make(v)
|
13
|
+
end
|
14
|
+
TwitterStruct.new(obj)
|
15
|
+
when Array
|
16
|
+
obj.map {|i| TwitterStruct.make(i) }
|
17
|
+
else
|
18
|
+
obj
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(obj)
|
23
|
+
@obj = obj
|
24
|
+
end
|
25
|
+
|
26
|
+
def id
|
27
|
+
@obj["id"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](name)
|
31
|
+
@obj[name.to_s]
|
32
|
+
end
|
33
|
+
|
34
|
+
def []=(name,val)
|
35
|
+
@obj[name.to_s] = val
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge(hash)
|
39
|
+
obj = @obj.dup
|
40
|
+
hash.each do|key,value|
|
41
|
+
obj[key.to_s] = value
|
42
|
+
end
|
43
|
+
TwitterStruct.make obj
|
44
|
+
end
|
45
|
+
|
46
|
+
def hash
|
47
|
+
self.id ? self.id.hash : super
|
48
|
+
end
|
49
|
+
|
50
|
+
def eql?(other)
|
51
|
+
self.hash == other.hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
self.hash == other.hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(sym, *args)
|
59
|
+
# XXX
|
60
|
+
@obj[sym.to_s]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/atig/unu.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
2
|
+
module Atig
|
3
|
+
class Unu
|
4
|
+
def initialize(logger)
|
5
|
+
@log = logger
|
6
|
+
@http = Atig::Http.new logger
|
7
|
+
end
|
8
|
+
|
9
|
+
def shorten(url)
|
10
|
+
unu_url = "http://u.nu/"
|
11
|
+
unu = URI("#{unu_url}unu-api-simple")
|
12
|
+
url = URI.rstrip url
|
13
|
+
unu.query = { :url => url }.to_query_str
|
14
|
+
res = @http.http(unu, 5, 5).request(@http.req(:get, unu)).body
|
15
|
+
|
16
|
+
if res[0, 12] == unu_url
|
17
|
+
res
|
18
|
+
else
|
19
|
+
@log.error res
|
20
|
+
url
|
21
|
+
end
|
22
|
+
rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
|
23
|
+
@log.error e
|
24
|
+
url
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
2
|
+
|
3
|
+
module Atig
|
4
|
+
module UpdateChecker
|
5
|
+
def commits
|
6
|
+
uri = URI("http://github.com/api/v1/json/mzp/atig/commits/master")
|
7
|
+
http = Atig::Http.new
|
8
|
+
res = http.http(uri).request http.req(:get, uri)
|
9
|
+
JSON.parse(res.body)['commits']
|
10
|
+
end
|
11
|
+
|
12
|
+
def server_version
|
13
|
+
@server_version ||= instance_eval {
|
14
|
+
head = `git rev-parse HEAD 2>/dev/null`.chomp
|
15
|
+
head.empty?? "unknown" : head
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def local_repos?(rev)
|
20
|
+
system("git show #{rev} > /dev/null 2>&1")
|
21
|
+
end
|
22
|
+
|
23
|
+
def git?
|
24
|
+
system('which git > /dev/null 2>&1')
|
25
|
+
end
|
26
|
+
|
27
|
+
def latest
|
28
|
+
unless git? then
|
29
|
+
[]
|
30
|
+
else
|
31
|
+
cs = commits
|
32
|
+
latest = cs.first['id'][/^[0-9a-z]{40}$/]
|
33
|
+
raise "github API changed?" unless latest
|
34
|
+
|
35
|
+
if local_repos?(latest) then
|
36
|
+
[]
|
37
|
+
else
|
38
|
+
current = cs.map {|i| i['id'] }.index(server_version)
|
39
|
+
if current then
|
40
|
+
cs[0...current]
|
41
|
+
else
|
42
|
+
cs
|
43
|
+
end.map {|i| i['message'] }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue Errno::ECONNREFUSED, Timeout::Error => e
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
module_function :latest, :commits, :server_version, :local_repos?, :git?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# { :f => "v" } #=> "f=v"
|
5
|
+
# { "f" => [1, 2] } #=> "f=1&f=2"
|
6
|
+
# { "f" => "" } #=> "f="
|
7
|
+
# { "f" => nil } #=> "f"
|
8
|
+
def to_query_str separator = "&"
|
9
|
+
inject([]) do |r, (k, v)|
|
10
|
+
k = URI.encode_component k.to_s
|
11
|
+
(v.is_a?(Array) ? v : [v]).each do |i|
|
12
|
+
if i.nil?
|
13
|
+
r << k
|
14
|
+
else
|
15
|
+
r << "#{k}=#{URI.encode_component i.to_s}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
r
|
19
|
+
end.join separator
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class String
|
24
|
+
def ch?
|
25
|
+
/\A[&#+!][^ \007,]{1,50}\z/ === self
|
26
|
+
end
|
27
|
+
|
28
|
+
def screen_name?
|
29
|
+
/\A[A-Za-z0-9_]{1,15}\z/ === self
|
30
|
+
end
|
31
|
+
|
32
|
+
def encoding! enc
|
33
|
+
return self unless respond_to? :force_encoding
|
34
|
+
force_encoding enc
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module URI::Escape
|
39
|
+
alias :_orig_escape :escape
|
40
|
+
|
41
|
+
if defined? ::RUBY_REVISION and RUBY_REVISION < 24544
|
42
|
+
# URI.escape("あ1") #=> "%E3%81%82\xEF\xBC\x91"
|
43
|
+
# URI("file:///4") #=> #<URI::Generic:0x9d09db0 URL:file:/4>
|
44
|
+
# "\\d" -> "[0-9]" for Ruby 1.9
|
45
|
+
def escape str, unsafe = %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]]} #'
|
46
|
+
_orig_escape(str, unsafe)
|
47
|
+
end
|
48
|
+
alias :encode :escape
|
49
|
+
end
|
50
|
+
|
51
|
+
def encode_component str, unsafe = /[^-_.!~*'()a-zA-Z0-9 ]/
|
52
|
+
_orig_escape(str, unsafe).tr(" ", "+")
|
53
|
+
end
|
54
|
+
|
55
|
+
def rstrip str
|
56
|
+
str.sub(%r{
|
57
|
+
(?: ( / [^/?#()]* (?: \( [^/?#()]* \) [^/?#()]* )* ) \) [^/?#()]*
|
58
|
+
| \.
|
59
|
+
) \z
|
60
|
+
}x, "\\1")
|
61
|
+
end
|
62
|
+
end
|
data/lib/atig/util.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
2
|
+
|
3
|
+
require 'atig/exception_util'
|
4
|
+
module Atig
|
5
|
+
module Util
|
6
|
+
private
|
7
|
+
include ExceptionUtil
|
8
|
+
def log(type, s)
|
9
|
+
if @log then
|
10
|
+
@log.send type, "[#{self.class}] #{s}"
|
11
|
+
else
|
12
|
+
STDERR.puts s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/atig/version.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# This is a memory profiler for Ruby. Once started, it runs in a thread in the
|
2
|
+
# background, periodically inspecting Ruby's ObjectSpace to look for new
|
3
|
+
# objects and printing a count of objects added and removed since the previous
|
4
|
+
# cycle.
|
5
|
+
#
|
6
|
+
# To use the profiler, do something like this:
|
7
|
+
#
|
8
|
+
# require 'memory_profiler'
|
9
|
+
#
|
10
|
+
# MemoryProfiler.start
|
11
|
+
#
|
12
|
+
# The profiler will write logs to ./log/memory_profiler.log.
|
13
|
+
#
|
14
|
+
# If you start MemoryProfiler with the ':string_debug => true' option, then it
|
15
|
+
# will dump a list of all strings in the app into the log/ directory after
|
16
|
+
# each cycle. You can then use 'diff' to spot which strings were added
|
17
|
+
# between runs.
|
18
|
+
class MemoryProfiler
|
19
|
+
DEFAULTS = {:delay => 10, :string_debug => false}
|
20
|
+
|
21
|
+
def self.start(opt={})
|
22
|
+
opt = DEFAULTS.dup.merge(opt)
|
23
|
+
|
24
|
+
Thread.new do
|
25
|
+
prev = Hash.new(0)
|
26
|
+
curr = Hash.new(0)
|
27
|
+
curr_strings = []
|
28
|
+
delta = Hash.new(0)
|
29
|
+
|
30
|
+
file = File.open("log/memory_profiler.#{Time.now.to_i}.log",'w')
|
31
|
+
|
32
|
+
loop do
|
33
|
+
begin
|
34
|
+
GC.start
|
35
|
+
curr.clear
|
36
|
+
|
37
|
+
curr_strings = [] if opt[:string_debug]
|
38
|
+
|
39
|
+
ObjectSpace.each_object do |o|
|
40
|
+
curr[o.class] += 1 #Marshal.dump(o).size rescue 1
|
41
|
+
if opt[:string_debug] and o.class == String
|
42
|
+
curr_strings.push o
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if opt[:string_debug]
|
47
|
+
File.open("log/memory_profiler_strings.log.#{Time.now.to_i}",'w') do |f|
|
48
|
+
curr_strings.sort.each do |s|
|
49
|
+
f.puts s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
curr_strings.clear
|
53
|
+
end
|
54
|
+
|
55
|
+
delta.clear
|
56
|
+
(curr.keys + delta.keys).uniq.each do |k,v|
|
57
|
+
delta[k] = curr[k]-prev[k]
|
58
|
+
end
|
59
|
+
|
60
|
+
file.puts "Top 20: #{Time.now}"
|
61
|
+
delta.sort_by { |k,v| -v.abs }[0..19].sort_by { |k,v| -v}.each do |k,v|
|
62
|
+
file.printf "%+5d: %s (%d)\n", v, k.name, curr[k] unless v == 0
|
63
|
+
end
|
64
|
+
file.flush
|
65
|
+
|
66
|
+
delta.clear
|
67
|
+
prev.clear
|
68
|
+
prev.update curr
|
69
|
+
GC.start
|
70
|
+
rescue Exception => err
|
71
|
+
STDERR.puts "** memory_profiler error: #{err}"
|
72
|
+
end
|
73
|
+
sleep opt[:delay]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|