x_runtime 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://ruby.taobao.org"
2
+ gem 'rack'
3
+ gem 'rake'
4
+ gem 'jeweler'
5
+ gem 'active_support'
6
+ gem 'redis', '~>3.0.1'
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://ruby.taobao.org/
3
+ specs:
4
+ active_support (3.0.0)
5
+ activesupport (= 3.0.0)
6
+ activesupport (3.0.0)
7
+ git (1.2.5)
8
+ jeweler (1.8.4)
9
+ bundler (~> 1.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rdoc
13
+ json (1.7.3)
14
+ rack (1.4.1)
15
+ rake (0.9.2.2)
16
+ rdoc (3.12)
17
+ json (~> 1.4)
18
+ redis (3.0.1)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ active_support
25
+ jeweler
26
+ rack
27
+ rake
28
+ redis (~> 3.0.1)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 崔峥
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,76 @@
1
+ # x_runtime
2
+
3
+ XRuntime是一个Rack的middleware,配合Redis用来分析Http Server每个URI请求时长.
4
+ 由于使用到Redis的**lua script**,所以需要你的Redis服务支持.
5
+
6
+ 版本要求:
7
+ * redis-server版本>2.6.x
8
+ * redis(ruby gem)>3.0.1
9
+
10
+ 在Redis中,对每一个URI记录了以下信息:
11
+ * 请求总数
12
+ * 总共请求时间
13
+ * 最近一次请求时间
14
+ * 平均请求时间
15
+
16
+ ## Portal
17
+
18
+ 可以访问Http Server的这个URL来实时查看当前记录的请求:[/xruntime](/xruntime)
19
+ 这个页面是按照每个URI数次执行后的平均时间来排序的。
20
+
21
+ ## Usage
22
+
23
+ 引入这个middleware需要两个参数:
24
+
25
+ 1. threshold,表示处理时间超过多少毫秒的请求才会被记录
26
+ 2. redis对象
27
+
28
+ 可以指定XRuntime使用的Redis的key前缀或者叫命名空间:
29
+ `XRuntime::NameSpace = "RuntimeEx::Threshold"`
30
+
31
+ ### Sinatra
32
+
33
+ use Rack::XRuntime, 100, Redis.connect(:url => "redis://localhost:6380/")
34
+
35
+ ### Rails3
36
+
37
+ # config/environment.rb
38
+ config.middleware.insert_after Rack::Runtime, Rack::XRuntime, 100, Redis.connect(:url => "redis://localhost:6380/")
39
+
40
+ ### Test
41
+
42
+ 请先修改test/server.rb和test/client.rb中的Redis参数,我的地址是localhost:6380,这个请改为你的地址。
43
+
44
+ * 先启动服务 `ruby test/server.rb`
45
+ * 再产生测试数据 `ruby test/client.rb`
46
+
47
+ 执行完毕后可以打开浏览器访问[/xruntime](http://localhost:4567/xruntime),看是否已经准确的记录了一些数据
48
+
49
+ ## Redis Lua Script
50
+
51
+ redis-server的版本要大于2.6.0才会支持lua script,
52
+ 可以使用__script__系列命令来测试服务端是否支持
53
+
54
+ 启动__redis-cli__
55
+
56
+ redis 127.0.0.1:6380> SCRIPT LOAD "local key = KEYS[1];local path = tonumber(ARGV[1]);redis.call('set',key, path)"
57
+ "dab89791b5a512390f69e1f59eb1753f671b6649"
58
+ redis 127.0.0.1:6380> evalsha dab89791b5a512390f69e1f59eb1753f671b6649 1 hahaha 123456789
59
+ redis 127.0.0.1:6380> get hahaha
60
+ "123456789"
61
+
62
+ ## Contributing to x_runtime
63
+
64
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
65
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
66
+ * Fork the project.
67
+ * Start a feature/bugfix branch.
68
+ * Commit and push until you are happy with your contribution.
69
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
70
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
71
+
72
+ ## Copyright
73
+
74
+ Copyright (c) 2012 崔峥. See LICENSE.txt for
75
+ further details.
76
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "x_runtime"
18
+ gem.homepage = "http://github.com/charlescui/x_runtime"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{XRuntime是一个Rack的middleware,配合Redis用来分析Http Server每个URI请求时长}
21
+ gem.description = %Q{由于使用到Redis的lua script,所以需要你的Redis服务支持,redis-server版本>2.6.x,redis(ruby gem)>3.0.1.}
22
+ gem.email = "zheng.cuizh@gmail.com"
23
+ gem.authors = ["崔峥"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ task :default => :test
36
+
37
+ require 'rdoc/task'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "x_runtime #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,15 @@
1
+ class Array
2
+ def to_hash
3
+ _hashify.call(self)
4
+ end
5
+
6
+ def _hashify
7
+ lambda { |array|
8
+ hash = Hash.new
9
+ array.each_slice(2) do |field, value|
10
+ hash[field] = value
11
+ end
12
+ hash
13
+ }
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module XRuntime
2
+ class DataSet
3
+
4
+ def initialize(key, script)
5
+ raise ArgumentError, "Script must not nil and be valid!" unless script
6
+ @key = key
7
+ @key_counter = "#{@key}::Counter"
8
+ @key_amount = "#{@key}::Amount"
9
+ @key_average = "#{@key}::Average"
10
+ @script = script
11
+ end
12
+
13
+ # {key => {:score => score, :count => count, :average => average}}
14
+ def latest(opts = {})
15
+ opts.delete_if{|k,v| v == nil}
16
+ opts = {:limit => 100, :offset => 0}.update(opts)
17
+ data = {}
18
+ key_average = @script.redis
19
+ .zrevrangebyscore(@key_average, '+inf', '-inf', :limit => [opts[:offset], opts[:limit]], :withscores => true)
20
+ .inject({}){|hash, array|hash[array[0]] = array[1]; hash}
21
+
22
+ if key_average.size > 0
23
+ key_count = {}
24
+ keys = key_average.keys
25
+ @script.redis.hmget(@key_counter, *key_average.keys).each_with_index{|count, idx| key_count[keys[idx]] = count}
26
+
27
+ key_average.keys.each do |key|
28
+ data[key] = {
29
+ :latest => @script.redis.zscore(@key, key),
30
+ :count => key_count[key],
31
+ :average => key_average[key]
32
+ }
33
+ end
34
+ end
35
+ data
36
+ end
37
+
38
+ def size
39
+ @script.redis.zcard(@key)
40
+ end
41
+
42
+ def add(member, score)
43
+ @script.evalsha([@key], [member, score])
44
+ end
45
+ end#end of DataSet
46
+ end
@@ -0,0 +1,56 @@
1
+ module XRuntime
2
+ class Middleware
3
+ # threshold => ms
4
+ def initialize(app, threshold, redis)
5
+ @app = app
6
+ @threshold = threshold.to_f
7
+ @redis = redis
8
+ end
9
+
10
+ def ds
11
+ @@ds ||= DataSet.new(redis_key, script)
12
+ end
13
+
14
+ def script
15
+ @@script ||= Script.new(@redis)
16
+ end
17
+
18
+ def call(env)
19
+ status, headers, body = nil
20
+
21
+ if env['REQUEST_PATH'] == "/xruntime"
22
+ status, headers, body = call_portal(env)
23
+ else
24
+ status, headers, body = call_app(env)
25
+ end
26
+
27
+ [status, headers, body]
28
+ end
29
+
30
+ def call_app(env)
31
+ start_time = Time.now
32
+ status, headers, body = @app.call(env)
33
+ request_time = (Time.now - start_time).to_f*1000
34
+
35
+ if request_time >= @threshold
36
+ logredis(request_time, env['REQUEST_URI']) rescue nil
37
+ end
38
+
39
+ [status, headers, body]
40
+ end
41
+
42
+ def call_portal(env)
43
+ @req = Rack::Request.new(env)
44
+ [200, {}, [Template.new(ds, :limit => (@req.params["limit"] ? @req.params["limit"].to_i : 20), :offset => @req.params["offset"].to_i).render]]
45
+ end
46
+
47
+ def logredis(cost,uri)
48
+ ds.add(uri, cost)
49
+ end
50
+
51
+ def redis_key
52
+ @key ||= "#{XRuntime::NameSpace}::#{@threshold}"
53
+ end
54
+
55
+ end#end of Middleware
56
+ end
@@ -0,0 +1,17 @@
1
+ local path = ARGV[1];
2
+ local score = tonumber(ARGV[2])
3
+ local key = KEYS[1]
4
+ local key_counter = string.format("%s::Counter",key)
5
+ local key_amount = string.format("%s::Amount",key)
6
+ local key_average = string.format("%s::Average",key)
7
+
8
+ -- 记录最新一次请求的耗时
9
+ redis.call('zadd',key,score,path)
10
+ -- 记录某个path的请求次数
11
+ redis.call('hincrby',key_counter,path,1)
12
+ -- 记录某个path的请求总时长
13
+ redis.call('hincrbyfloat',key_amount,path,score)
14
+ -- 记录某个path的平均时长
15
+ local average = tonumber(redis.call('hget',key_amount,path))/tonumber(redis.call('hget',key_counter,path))
16
+ redis.call('zadd',key_average,average,path)
17
+ return average
@@ -0,0 +1,23 @@
1
+ module XRuntime
2
+ class Script
3
+ attr_accessor :redis
4
+
5
+ def initialize(redis)
6
+ @redis = redis
7
+ end
8
+
9
+ def content
10
+ @script ||= IO.read(::File.join(::File.dirname(__FILE__),'redis.lua'))
11
+ end
12
+
13
+ def sha
14
+ # 加载脚本 redis-server会将该脚本缓存起来
15
+ @sha ||= @redis.script(:load, content)
16
+ end
17
+
18
+ def evalsha(keys, argv)
19
+ @redis.evalsha(sha, :keys => keys, :argv => argv)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ <html>
2
+ <head>
3
+ <title>XRuntime</title>
4
+ </head>
5
+ <body>
6
+ <table>
7
+ <tr>
8
+ <td>#</td>
9
+ <td>Last</td>
10
+ <td>URI</td>
11
+ <td>Count</td>
12
+ <td>Average</td>
13
+ </tr>
14
+ <% num = 0 %>
15
+ <% @data.each do |member, hash| %>
16
+ <% num +=1 %>
17
+ <tr>
18
+ <td><%= num %></td>
19
+ <td><%= hash[:latest] %>ms</td>
20
+ <td><a href=<%= member %>><%= member %></a></td>
21
+ <td><%= hash[:count]%></td>
22
+ <td><%= hash[:average]%></td>
23
+ </tr>
24
+ <% end %>
25
+ <% num = nil %>
26
+ </table>
27
+ <br/>
28
+ <br/>
29
+ <div>
30
+ <% if @offset - @limit < 0 %>
31
+ Prev
32
+ <% else %>
33
+ <a href="/xruntime?offset=<%=[@offset - @limit, 0].max%>&limit=<%=@limit%>">Prev</a>
34
+ <% end %>
35
+ &nbsp-&nbsp-&nbsp
36
+ <% if @offset + @limit >= @ds.size %>
37
+ Next
38
+ <% else %>
39
+ <a href="/xruntime?offset=<%=[@offset + @limit, @ds.size].min%>&limit=<%=@limit%>">Next</a>
40
+ <% end %>
41
+ </div>
42
+ </body>
43
+ </html>
@@ -0,0 +1,24 @@
1
+ module XRuntime
2
+ class Template
3
+ def initialize(ds, opts={})
4
+ opts.delete_if{|k,v| v == nil}
5
+ opts = {:limit => 100, :offset => 0}.update(opts)
6
+ @ds = ds
7
+ @offset = opts[:offset]
8
+ @limit = opts[:limit]
9
+ @data = @ds.latest(:limit => opts[:limit], :offset => opts[:offset])
10
+ end
11
+
12
+ def render
13
+ Template.erb.result(binding)
14
+ end
15
+
16
+ def self.erb
17
+ @erb ||= ERB.new(self.html)
18
+ end
19
+
20
+ def self.html
21
+ @html ||= IO.read(File.join(File.dirname(__FILE__),'template.erb'))
22
+ end
23
+ end#end of Template
24
+ end
@@ -0,0 +1,4 @@
1
+ module XRuntime
2
+ module Utils
3
+ end
4
+ end
data/lib/x_runtime.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "pp"
2
+ require "rack"
3
+ require "redis"
4
+ require "digest"
5
+ require "fileutils"
6
+
7
+ require_relative "x_runtime/array"
8
+
9
+ $:.unshift(File.dirname(__FILE__))
10
+
11
+ module XRuntime
12
+ NameSpace = "RuntimeEx::Threshold"
13
+ end
14
+
15
+ XRuntime.autoload :Middleware, "x_runtime/middleware"
16
+ XRuntime.autoload :DataSet, "x_runtime/data_set"
17
+ XRuntime.autoload :Script, "x_runtime/script"
18
+ XRuntime.autoload :Template, "x_runtime/template"
19
+ XRuntime.autoload :Utils, "x_runtime/utils"
20
+
21
+ module Rack
22
+ XRuntime = XRuntime::Middleware
23
+ end
data/test/client.rb ADDED
@@ -0,0 +1,12 @@
1
+ def alpha
2
+ # @@alpha ||= ('a'..'z').to_a | ('A'..'Z').to_a | ('0'..'9').to_a
3
+ @@alpha ||= ('a'..'z').to_a
4
+ end
5
+
6
+ def rand_url(size=1)
7
+ "http://localhost:4567/#{(1..size).to_a.inject(''){|s,t|s+=alpha[rand(alpha.size-1)].to_s}}"
8
+ end
9
+
10
+ 100.times do
11
+ `curl "#{rand_url}" >/dev/null 2>&1`
12
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'x_runtime'
16
+
17
+ class Test::Unit::TestCase
18
+ end
data/test/server.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "sinatra"
2
+ require_relative "../lib/x_runtime"
3
+
4
+ # redis-server version must > 2.6.0 for lua script.
5
+ # use XRuntime::Middleware, 10, Redis.connect(:url => "redis://localhost:6380/")
6
+ use Rack::XRuntime, 10, Redis.connect(:url => "redis://localhost:6380/")
7
+
8
+ get /.*/ do
9
+ # sleep(1)
10
+ sleep(0.01*rand(10))
11
+ "Hello, I'am x_runtime"
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestXRuntime < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: x_runtime
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - 崔峥
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: jeweler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: active_support
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: redis
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 3.0.1
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 3.0.1
94
+ description: 由于使用到Redis的lua script,所以需要你的Redis服务支持,redis-server版本>2.6.x,redis(ruby
95
+ gem)>3.0.1.
96
+ email: zheng.cuizh@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files:
100
+ - LICENSE.txt
101
+ - README.markdown
102
+ files:
103
+ - Gemfile
104
+ - Gemfile.lock
105
+ - LICENSE.txt
106
+ - README.markdown
107
+ - Rakefile
108
+ - VERSION
109
+ - lib/x_runtime.rb
110
+ - lib/x_runtime/array.rb
111
+ - lib/x_runtime/data_set.rb
112
+ - lib/x_runtime/middleware.rb
113
+ - lib/x_runtime/redis.lua
114
+ - lib/x_runtime/script.rb
115
+ - lib/x_runtime/template.erb
116
+ - lib/x_runtime/template.rb
117
+ - lib/x_runtime/utils.rb
118
+ - test/client.rb
119
+ - test/helper.rb
120
+ - test/server.rb
121
+ - test/test_x_runtime.rb
122
+ homepage: http://github.com/charlescui/x_runtime
123
+ licenses:
124
+ - MIT
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ segments:
136
+ - 0
137
+ hash: 1659196055007791068
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ! '>='
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 1.8.24
147
+ signing_key:
148
+ specification_version: 3
149
+ summary: XRuntime是一个Rack的middleware,配合Redis用来分析Http Server每个URI请求时长
150
+ test_files: []