simple_apm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +42 -0
- data/Rakefile +36 -0
- data/app/assets/config/simple_apm_manifest.js +2 -0
- data/app/assets/javascripts/simple_apm/application.js +13 -0
- data/app/assets/stylesheets/simple_apm/application.css +46 -0
- data/app/controllers/simple_apm/apm_controller.rb +69 -0
- data/app/controllers/simple_apm/application_controller.rb +10 -0
- data/app/helpers/simple_apm/application_helper.rb +27 -0
- data/app/jobs/simple_apm/application_job.rb +4 -0
- data/app/mailers/simple_apm/application_mailer.rb +6 -0
- data/app/models/simple_apm/action.rb +72 -0
- data/app/models/simple_apm/application_record.rb +5 -0
- data/app/models/simple_apm/hit.rb +54 -0
- data/app/models/simple_apm/request.rb +35 -0
- data/app/models/simple_apm/slow_request.rb +76 -0
- data/app/models/simple_apm/sql.rb +43 -0
- data/app/views/layouts/simple_apm/application.html.erb +61 -0
- data/app/views/simple_apm/apm/action_info.html.erb +41 -0
- data/app/views/simple_apm/apm/actions.html.erb +30 -0
- data/app/views/simple_apm/apm/dashboard.html.erb +35 -0
- data/app/views/simple_apm/apm/index.html.erb +38 -0
- data/app/views/simple_apm/apm/show.html.erb +61 -0
- data/config/routes.rb +9 -0
- data/lib/simple_apm.rb +65 -0
- data/lib/simple_apm/engine.rb +18 -0
- data/lib/simple_apm/redis.rb +63 -0
- data/lib/simple_apm/setting.rb +12 -0
- data/lib/simple_apm/version.rb +3 -0
- data/lib/tasks/simple_apm_tasks.rake +4 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba1cd7b2606ced6aff23e56dae261c81de9ae06fb8dec56d0e91e4e7bb13aacb
|
4
|
+
data.tar.gz: 9205720ac911bd244e9ddb94f4c3e5ad1c40c3f6df3de13fb350a9cfcb7d1164
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 05340b8118889c1351bbd31aaddb2696fe46d6418dced5c532e453f1dd2a0a1aa93a5b4f5122b75384daa188edebeb3d23510122a91100bb72edc125350c44da
|
7
|
+
data.tar.gz: 46a70c6afe5e5464e6610260d7375bb740d66180169f2061eec633b187ba8b9ce3f6dee632303e7261bf1b3acc4f7fafd277975bf1a44426ba78e0a67d6f70c7
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 yuanyin.xia
|
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.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# 开发中……
|
2
|
+
# SimpleApm
|
3
|
+
基于Redis的简单的Web请求性能监控/慢事务追踪工具
|
4
|
+
|
5
|
+
以天为维度记录:
|
6
|
+
- 最慢的1000个(默认1000)请求
|
7
|
+
- 记录每个action最慢的100次请求
|
8
|
+
- 记录每个action的平均访问时间
|
9
|
+
- 记录每个小时请求量
|
10
|
+
- 记录慢请求的详情和对应SQL详情(多余的会删掉)
|
11
|
+
- 以10分钟为刻度记录平均/最慢访问时间、次数等性能指标
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
# routes.rb
|
17
|
+
mount SimpleApm::Engine => '/apm'
|
18
|
+
```
|
19
|
+
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'simple_apm'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
```bash
|
30
|
+
$ bundle
|
31
|
+
```
|
32
|
+
|
33
|
+
Or install it yourself as:
|
34
|
+
```bash
|
35
|
+
$ gem install simple_apm
|
36
|
+
```
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
Contribution directions go here.
|
40
|
+
|
41
|
+
## License
|
42
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'SimpleApm'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
require 'bundler/gem_tasks'
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'test'
|
31
|
+
t.pattern = 'test/**/*_test.rb'
|
32
|
+
t.verbose = false
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,46 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
16
|
+
footer{
|
17
|
+
text-align: center;
|
18
|
+
padding: 10px;
|
19
|
+
border-top: 1px solid #999999;
|
20
|
+
}
|
21
|
+
a.navbar-brand.active{
|
22
|
+
color: #00AEEF !important;
|
23
|
+
}
|
24
|
+
#page-container{
|
25
|
+
min-height: 80vh;
|
26
|
+
}
|
27
|
+
.select-apm-date{
|
28
|
+
width: auto;
|
29
|
+
float: right;
|
30
|
+
margin-top: 8px;
|
31
|
+
}
|
32
|
+
.select-apm-date>select{
|
33
|
+
display: inline-block;
|
34
|
+
width: auto;
|
35
|
+
}
|
36
|
+
.sql{
|
37
|
+
max-width: 300px;
|
38
|
+
white-space: nowrap;
|
39
|
+
text-overflow:ellipsis;
|
40
|
+
overflow: hidden;
|
41
|
+
color: #00AEEF;
|
42
|
+
cursor: pointer;
|
43
|
+
}
|
44
|
+
pre {
|
45
|
+
white-space: pre-wrap;
|
46
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_dependency "simple_apm/application_controller"
|
2
|
+
|
3
|
+
module SimpleApm
|
4
|
+
class ApmController < ApplicationController
|
5
|
+
include SimpleApm::ApplicationHelper
|
6
|
+
before_action :set_query_date
|
7
|
+
|
8
|
+
def dashboard
|
9
|
+
d = SimpleApm::RedisKey.query_date == Time.now.strftime('%Y-%m-%d') ? Time.now.strftime('%H:%M') : '23:50'
|
10
|
+
data = SimpleApm::Hit.chart_data(0, d)
|
11
|
+
@x_names = data.keys.sort
|
12
|
+
@time_arr = @x_names.map{|n| data[n][:hits].to_i.zero? ? 0 : (data[n][:time].to_f/data[n][:hits].to_i).round(3) }
|
13
|
+
@hits_arr = @x_names.map{|n| data[n][:hits] rescue 0}
|
14
|
+
end
|
15
|
+
|
16
|
+
def index
|
17
|
+
respond_to do |format|
|
18
|
+
format.json do
|
19
|
+
@slow_requests = SimpleApm::SlowRequest.list(params[:count]||200).map do |r|
|
20
|
+
request = r.request
|
21
|
+
[
|
22
|
+
link_to(time_label(request.started), show_path(id: request.request_id)),
|
23
|
+
link_to(request.action_name, action_info_path(action_name: request.action_name)),
|
24
|
+
sec_str(request.during),
|
25
|
+
sec_str(request.db_runtime),
|
26
|
+
sec_str(request.view_runtime),
|
27
|
+
request.host,
|
28
|
+
request.remote_addr
|
29
|
+
]
|
30
|
+
end
|
31
|
+
render json: {data: @slow_requests}
|
32
|
+
end
|
33
|
+
format.html
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def show
|
38
|
+
@request = SimpleApm::Request.find(params[:id])
|
39
|
+
end
|
40
|
+
|
41
|
+
def actions
|
42
|
+
@actions = SimpleApm::Action.all_names.map{|n| SimpleApm::Action.find(n)}
|
43
|
+
end
|
44
|
+
|
45
|
+
def action_info
|
46
|
+
@action = SimpleApm::Action.find(params[:action_name])
|
47
|
+
end
|
48
|
+
|
49
|
+
def change_date
|
50
|
+
session[:apm_date] = params[:date]
|
51
|
+
redirect_to request.referer
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_apm_date
|
55
|
+
# set_query_date
|
56
|
+
redirect_to action: :dashboard
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def set_query_date
|
61
|
+
session[:apm_date] = params[:apm_date] if params[:apm_date].present?
|
62
|
+
SimpleApm::RedisKey.query_date = session[:apm_date]
|
63
|
+
end
|
64
|
+
|
65
|
+
def link_to(name, url)
|
66
|
+
"<a href=#{url.to_json}>#{name}</a>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SimpleApm
|
2
|
+
module ApplicationHelper
|
3
|
+
def time_label(t, full = false)
|
4
|
+
Time.parse(t).strftime("#{'%Y-%m-%d ' if full}%H:%M:%S") rescue ''
|
5
|
+
end
|
6
|
+
|
7
|
+
def sec_str(sec, force = nil)
|
8
|
+
_sec = sec.to_f
|
9
|
+
|
10
|
+
if force == 'min'
|
11
|
+
return "#{(_sec / 60).to_f.round(1)}min"
|
12
|
+
elsif force == 's'
|
13
|
+
return "#{_sec.round(2)}s"
|
14
|
+
elsif force == 'ms'
|
15
|
+
return "#{(_sec * 1000).round}ms"
|
16
|
+
end
|
17
|
+
|
18
|
+
if (_sec / 60).to_i > 0
|
19
|
+
"#{(_sec / 60).to_f.round(1)}min"
|
20
|
+
elsif _sec.to_i > 0
|
21
|
+
"#{_sec.round(2)}s"
|
22
|
+
else
|
23
|
+
"#{(_sec * 1000).round}ms"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# 请求 Controller#Action
|
2
|
+
module SimpleApm
|
3
|
+
class Action
|
4
|
+
attr_accessor :name, :click_count, :time, :slow_time, :slow_id, :fast_time, :fast_id
|
5
|
+
|
6
|
+
def initialize(h)
|
7
|
+
h.each do |k, v|
|
8
|
+
send("#{k}=", v)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def fastest_request
|
13
|
+
@fastest_request ||= SimpleApm::Request.find(fast_id)
|
14
|
+
end
|
15
|
+
def slowest_request
|
16
|
+
@slowest_request ||= SimpleApm::Request.find(slow_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<SimpleApm::SlowRequest>]
|
20
|
+
def slow_requests(limit = 20, offset = 0)
|
21
|
+
@slow_requests ||= SimpleApm::SlowRequest.list_by_action(name, limit, offset)
|
22
|
+
end
|
23
|
+
|
24
|
+
def avg_time
|
25
|
+
time.to_f/click_count.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def find(action_name)
|
30
|
+
SimpleApm::Action.new SimpleApm::Redis.hgetall(info_key(action_name)).merge(name: action_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param h [Hash] 一次request请求的信息
|
34
|
+
def update_by_request(h)
|
35
|
+
SimpleApm::Redis.sadd(action_list_key, h['action_name'])
|
36
|
+
_key = info_key h['action_name']
|
37
|
+
_request_store = false
|
38
|
+
# 点击次数
|
39
|
+
SimpleApm::Redis.hincrby _key, 'click_count', 1
|
40
|
+
# 总时间
|
41
|
+
SimpleApm::Redis.hincrbyfloat _key, 'time', h['during']
|
42
|
+
_slow = SimpleApm::Redis.hget _key, 'slow_time'
|
43
|
+
if _slow.nil? || h['during'].to_f > _slow.to_f
|
44
|
+
# 记录最慢访问
|
45
|
+
SimpleApm::Redis.hmset _key, 'slow_time', h['during'], 'slow_id', h['request_id']
|
46
|
+
_request_store = true
|
47
|
+
end
|
48
|
+
_fast = SimpleApm::Redis.hget _key, 'fast_time'
|
49
|
+
if _fast.nil? || h['during'].to_f < _fast.to_f
|
50
|
+
# 记录最快访问
|
51
|
+
SimpleApm::Redis.hmset _key, 'fast_time', h['during'], 'fast_id', h['request_id']
|
52
|
+
_request_store = true
|
53
|
+
end
|
54
|
+
_request_store
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Array<String>]
|
58
|
+
def all_names
|
59
|
+
SimpleApm::Redis.smembers(action_list_key)
|
60
|
+
end
|
61
|
+
|
62
|
+
def action_list_key
|
63
|
+
SimpleApm::RedisKey['action-names']
|
64
|
+
end
|
65
|
+
|
66
|
+
def info_key(action_name)
|
67
|
+
SimpleApm::RedisKey["action-info:#{action_name}"]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# action的点击数,按小时来显示
|
2
|
+
module SimpleApm
|
3
|
+
class Hit
|
4
|
+
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def chart_data(start_time = '00:00', end_time = '23:50', per = 'minute')
|
8
|
+
start_hour = start_time.to_s.split(':').first.to_i
|
9
|
+
end_hour = [end_time.to_s.split(':').first.to_i, 23].min
|
10
|
+
end_min = end_time.to_s.split(':')[1]
|
11
|
+
minutes = %w[00 10 20 30 40 50]
|
12
|
+
redis_result = Hash[SimpleApm::Redis.hgetall(minute_key)]
|
13
|
+
result_hash = {}
|
14
|
+
start_hour.upto(end_hour).each do |_hour|
|
15
|
+
minutes.each do |_min|
|
16
|
+
break if end_hour.to_i==_hour && end_min && _min.to_i > end_min.to_i
|
17
|
+
k = "#{to_double_str _hour}:#{to_double_str _min}"
|
18
|
+
_time = redis_result["#{k}:time"].to_f
|
19
|
+
_hits = redis_result["#{k}:hits"].to_i
|
20
|
+
if per =='minute'
|
21
|
+
result_hash[k] = {time: _time, hits: _hits}
|
22
|
+
else
|
23
|
+
_key = to_double_str _hour
|
24
|
+
result_hash[_key] ||= { time: 0.0, hits: 0 }
|
25
|
+
result_hash[_key][:time] += _time
|
26
|
+
result_hash[_key][:hits] += _hits
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
result_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def update_by_request(h)
|
35
|
+
# SimpleApm::Redis.hincrby hour_hit_key, Time.now.hour, 1
|
36
|
+
minute_base = "#{to_double_str Time.now.hour}:#{to_double_str 10 * (Time.now.min / 10)}"
|
37
|
+
SimpleApm::Redis.hincrby minute_key, "#{minute_base}:hits", 1
|
38
|
+
SimpleApm::Redis.hincrbyfloat minute_key, "#{minute_base}:time", h['during']
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_double_str(i)
|
42
|
+
i.to_s.size==1 ? "0#{i}" : i.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def minute_key
|
46
|
+
SimpleApm::RedisKey['per-10-minute']
|
47
|
+
end
|
48
|
+
|
49
|
+
# def hour_hit_key
|
50
|
+
# SimpleApm::RedisKey['hour-hits']
|
51
|
+
# end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# 单个请求信息
|
2
|
+
module SimpleApm
|
3
|
+
class Request
|
4
|
+
attr_accessor :request_id, :action_name,
|
5
|
+
:during, :started, :db_runtime, :view_runtime,
|
6
|
+
:controller, :action, :format, :method,
|
7
|
+
:host, :remote_addr,
|
8
|
+
:exception, :status
|
9
|
+
def initialize(h)
|
10
|
+
h.each do |k, v|
|
11
|
+
send("#{k}=", v) rescue puts "attr #{k} not set!"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def sqls
|
16
|
+
@sqls ||= SimpleApm::Sql.find_by_request_id(request_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class << self
|
21
|
+
|
22
|
+
def find(id)
|
23
|
+
SimpleApm::Request.new JSON.parse(SimpleApm::Redis.hget(key, id))
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(h)
|
27
|
+
SimpleApm::Redis.hmset key, h['request_id'], h.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
def key
|
31
|
+
SimpleApm::RedisKey['requests']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# 慢请求列表,包括最慢的N个请求,指定Action的最慢的N个请求
|
2
|
+
module SimpleApm
|
3
|
+
class SlowRequest
|
4
|
+
attr_accessor :action_name, :request_id, :during
|
5
|
+
def initialize(request_id, during, action_name = nil)
|
6
|
+
self.action_name = action_name
|
7
|
+
self.request_id = request_id
|
8
|
+
self.during = during
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def request
|
13
|
+
@request_info ||= SimpleApm::Request.find(request_id)
|
14
|
+
end
|
15
|
+
alias :info :request
|
16
|
+
|
17
|
+
def sqls
|
18
|
+
@request_sqls ||= SimpleApm::Sql.find_by_request_id(request_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# 存储最慢的1000个请求和每个action的最慢100次请求
|
23
|
+
def update_by_request(info)
|
24
|
+
in_action = update_action(info[:action_name], info[:during], info[:request_id])
|
25
|
+
in_slow_request = update_request(info[:during], info[:request_id])
|
26
|
+
in_action || in_slow_request
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param action_name [String] 请求名ControllerName#ActionName
|
30
|
+
# @param during [Float] 耗时
|
31
|
+
# @param request_id [Hash] 请求id
|
32
|
+
# @return [Boolean] 是否插入成功
|
33
|
+
def update_action(action_name, during, request_id)
|
34
|
+
SimpleApm::Redis.zadd(action_key(action_name), during, request_id)
|
35
|
+
SimpleApm::Redis.zremrangebyrank(action_key(action_name), 0, -SimpleApm::Setting::ACTION_SLOW_REQUEST_LIMIT - 1)
|
36
|
+
SimpleApm::Redis.zrank(action_key(action_name), request_id).present?
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param request_id [Hash] 请求id
|
40
|
+
# @param during [Float] 耗时
|
41
|
+
# @return [Boolean] 是否插入成功
|
42
|
+
def update_request(during, request_id)
|
43
|
+
# 记录最慢请求列表1000个
|
44
|
+
SimpleApm::Redis.zadd(key, during, request_id)
|
45
|
+
SimpleApm::Redis.zremrangebyrank(key, 0, -SimpleApm::Setting::SLOW_ACTIONS_LIMIT - 1)
|
46
|
+
SimpleApm::Redis.zrank(key, request_id).present?
|
47
|
+
end
|
48
|
+
|
49
|
+
# 从慢到快的排序
|
50
|
+
# @return [Array<SimpleApm::SlowRequest>]
|
51
|
+
def list_by_action(action_name, limit = 100, offset = 0)
|
52
|
+
SimpleApm::Redis.zrevrange(
|
53
|
+
action_key(action_name), offset, limit, with_scores: true
|
54
|
+
).map{ |x| SimpleApm::SlowRequest.new(x[0], x[1], action_name)}
|
55
|
+
end
|
56
|
+
|
57
|
+
# 从慢到快的排序
|
58
|
+
# @return [Array<SimpleApm::SlowRequest>]
|
59
|
+
def list(limit = 100, offset = 0)
|
60
|
+
SimpleApm::Redis.zrevrange(
|
61
|
+
key, offset.to_i, limit.to_i - 1, with_scores: true
|
62
|
+
).map{ |x| SimpleApm::SlowRequest.new(x[0], x[1])}
|
63
|
+
end
|
64
|
+
|
65
|
+
def key
|
66
|
+
SimpleApm::RedisKey['slow-requests']
|
67
|
+
end
|
68
|
+
|
69
|
+
def action_key(action_name = nil)
|
70
|
+
SimpleApm::RedisKey["action-slow:#{action_name}"]
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# 请求对应的SQL查询列表
|
2
|
+
module SimpleApm
|
3
|
+
class Sql
|
4
|
+
attr_accessor :request_id, :sql, :line, :filename, :method, :name, :during, :started, :value
|
5
|
+
def initialize(h, request = nil)
|
6
|
+
h.each do |k, v|
|
7
|
+
send("#{k}=", v) rescue puts "attr #{k} not set!"
|
8
|
+
end
|
9
|
+
@request = request
|
10
|
+
end
|
11
|
+
|
12
|
+
def full_sql
|
13
|
+
_sql = sql.to_s.gsub(/^[\s]*/,'')
|
14
|
+
if value.present?
|
15
|
+
_sql << "\n\nParameters: #{value}"
|
16
|
+
end
|
17
|
+
_sql
|
18
|
+
end
|
19
|
+
|
20
|
+
def request
|
21
|
+
@request ||= SimpleApm::Request.find(request_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# @return [Array<SimpleApm::Sql>]
|
26
|
+
def find_by_request_id(request_id)
|
27
|
+
SimpleApm::Redis.lrange(key(request_id), 0, -1).map{|x|SimpleApm::Sql.new JSON.parse(x)}
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_by_request_id(request_id)
|
31
|
+
SimpleApm::Redis.del(key(request_id))
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(request_id, info)
|
35
|
+
SimpleApm::Redis.rpush(key(request_id), info.to_json)
|
36
|
+
end
|
37
|
+
|
38
|
+
def key(request_id)
|
39
|
+
SimpleApm::RedisKey["sql:#{request_id}"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Simple Apm</title>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta content="IE=Edge,chrome=1" http-equiv="X-UA-Compatible">
|
7
|
+
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
8
|
+
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
9
|
+
<link rel="stylesheet" href="//cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css">
|
10
|
+
<script src="//cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
|
11
|
+
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
12
|
+
<script src="//cdn.bootcss.com/echarts/4.1.0/echarts.js"></script>
|
13
|
+
<script src="//cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js"></script>
|
14
|
+
<%= stylesheet_link_tag "simple_apm/application", media: "all" %>
|
15
|
+
<%= javascript_include_tag "simple_apm/application" %>
|
16
|
+
<%= csrf_meta_tags %>
|
17
|
+
</head>
|
18
|
+
<body>
|
19
|
+
<div id="page-container">
|
20
|
+
<nav class="navbar navbar-default navbar-fixed-top">
|
21
|
+
<div class="container">
|
22
|
+
<div class="navbar-header">
|
23
|
+
<% [[dashboard_path, 'Dashboard'], [index_path, '慢事务列表'], [actions_path, 'ActionList']].each do |m| %>
|
24
|
+
<a class="navbar-brand <%= 'active' if request.url=~/#{m[0]}/ %>" href="<%= m[0] %>" ><%= m[1] %></a>
|
25
|
+
<% end %>
|
26
|
+
</div>
|
27
|
+
<div class="select-apm-date">
|
28
|
+
统计日期
|
29
|
+
<%= select_tag 'apm_date', options_for_select(SimpleApm::Redis.in_apm_days, apm_date), class: 'form-control'%>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
</div>
|
33
|
+
</nav>
|
34
|
+
<div class="container" style="margin-top: 55px">
|
35
|
+
<%= yield %>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
<footer style="text-align: center">
|
39
|
+
<% SimpleApm::Redis.simple_info.each do |k, v| %>
|
40
|
+
<label><%= k %>:</label><span><%= v %></span>
|
41
|
+
<% end %>
|
42
|
+
</footer>
|
43
|
+
<div id="sql-modal" class="modal" aria-hidden="true" role="dialog" style="display: none;" tabindex="-1">
|
44
|
+
<div class="modal-dialog modal-lg modal-dialog-popout">
|
45
|
+
<pre class="modal-content" data-lang="SQL">
|
46
|
+
</pre>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
</body>
|
51
|
+
<script>
|
52
|
+
var sql_modal_obj = $('#sql-modal')
|
53
|
+
$('.select-apm-date select').on('change',function(){
|
54
|
+
window.location.href = '<%= set_apm_date_path %>?apm_date='+this.value
|
55
|
+
})
|
56
|
+
$('.sql').on('click', function(){
|
57
|
+
sql_modal_obj.find('.modal-content').html($(this).html())
|
58
|
+
sql_modal_obj.modal('show')
|
59
|
+
})
|
60
|
+
</script>
|
61
|
+
</html>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<h4>请求概况</h4>
|
2
|
+
<h5><%= @action.name%></h5>
|
3
|
+
<p>
|
4
|
+
<label>当日点击次数:</label>
|
5
|
+
<span><%= @action.click_count %></span>
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
<label>平均响应时间:</label>
|
9
|
+
<span><%= sec_str @action.time.to_f/@action.click_count.to_i %></span>
|
10
|
+
</p>
|
11
|
+
<p>
|
12
|
+
<label>最慢响应时间:</label>
|
13
|
+
<span><%= link_to sec_str(@action.slow_time), show_path(id: @action.slow_id) %></span>
|
14
|
+
</p>
|
15
|
+
<p>
|
16
|
+
<label>最快响应时间:</label>
|
17
|
+
<span><%= link_to sec_str(@action.fast_time), show_path(id: @action.fast_id) %></span>
|
18
|
+
</p>
|
19
|
+
<table class="table table-bordered">
|
20
|
+
<tr>
|
21
|
+
<th>响应时间</th>
|
22
|
+
<th>访问时间</th>
|
23
|
+
<th>请求id</th>
|
24
|
+
<th>访问ip</th>
|
25
|
+
<th>server</th>
|
26
|
+
</tr>
|
27
|
+
<% @action.slow_requests.each do |slow_request| %>
|
28
|
+
<% r = slow_request.request %>
|
29
|
+
<tr>
|
30
|
+
<td>
|
31
|
+
<%= sec_str r.during %>
|
32
|
+
(DB: <%= sec_str r.db_runtime %> , View: <%= sec_str r.view_runtime %>)
|
33
|
+
</td>
|
34
|
+
<td><%= time_label r.started %></td>
|
35
|
+
<td><%= link_to r.request_id, show_path(id: r.request_id) %></td>
|
36
|
+
<td><%= r.remote_addr %></td>
|
37
|
+
<td><%= r.host %></td>
|
38
|
+
</tr>
|
39
|
+
<% end %>
|
40
|
+
</table>
|
41
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<table id="actions-table" class="table">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th>Name</th>
|
5
|
+
<th>点击次数</th>
|
6
|
+
<th>平均响应时间</th>
|
7
|
+
<th>最快响应时间</th>
|
8
|
+
<th>最慢响应时间</th>
|
9
|
+
</tr>
|
10
|
+
</thead>
|
11
|
+
<tbody>
|
12
|
+
<% @actions.each do |action| %>
|
13
|
+
<tr>
|
14
|
+
<td><%= link_to action.name, action_info_path(action_name: action.name) %></td>
|
15
|
+
<td><%= action.click_count %></td>
|
16
|
+
<td><%= sec_str action.avg_time, 's' %></td>
|
17
|
+
<td><%= link_to sec_str(action.fast_time, 's'), show_path(id: action.fast_id) %></td>
|
18
|
+
<td><%= link_to sec_str(action.slow_time, 's'), show_path(id: action.slow_id) %></td>
|
19
|
+
</tr>
|
20
|
+
<% end %>
|
21
|
+
</tbody>
|
22
|
+
</table>
|
23
|
+
<script type="text/javascript">
|
24
|
+
$(document).ready(function(){
|
25
|
+
$('#actions-table').DataTable({
|
26
|
+
iDisplayLength: 25
|
27
|
+
})
|
28
|
+
|
29
|
+
})
|
30
|
+
</script>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<div id="time_chart" style="height: 400px;width: auto"></div>
|
2
|
+
<div id="hits_chart" style="height: 400px;width: auto"></div>
|
3
|
+
<script>
|
4
|
+
var time_chart = echarts.init(document.getElementById('time_chart')),
|
5
|
+
hits_chart = echarts.init(document.getElementById('hits_chart'));
|
6
|
+
time_chart.setOption({
|
7
|
+
title: {
|
8
|
+
text: '响应时间'
|
9
|
+
},
|
10
|
+
tooltip: {},
|
11
|
+
xAxis: {
|
12
|
+
data: <%= @x_names.to_json.html_safe %>
|
13
|
+
},
|
14
|
+
yAxis: {type: 'value'},
|
15
|
+
series: [{
|
16
|
+
type: 'line',
|
17
|
+
data: <%= @time_arr.to_json.html_safe %>
|
18
|
+
}]
|
19
|
+
})
|
20
|
+
|
21
|
+
hits_chart.setOption({
|
22
|
+
title: {
|
23
|
+
text: '访问次数'
|
24
|
+
},
|
25
|
+
tooltip: {},
|
26
|
+
xAxis: {
|
27
|
+
data: <%= @x_names.to_json.html_safe %>
|
28
|
+
},
|
29
|
+
yAxis: {type: 'value'},
|
30
|
+
series: [{
|
31
|
+
type: 'line',
|
32
|
+
data: <%= @hits_arr.to_json.html_safe %>
|
33
|
+
}]
|
34
|
+
})
|
35
|
+
</script>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<table id="slow-requests" class="table">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th>请求时间</th>
|
5
|
+
<th>action</th>
|
6
|
+
<th>总耗时</th>
|
7
|
+
<th>db_time</th>
|
8
|
+
<th>view time</th>
|
9
|
+
<th>host</th>
|
10
|
+
<th>remote_addr</th>
|
11
|
+
</tr>
|
12
|
+
</thead>
|
13
|
+
<%# @slow_requests.each do |r| %>
|
14
|
+
<!-- <tr>-->
|
15
|
+
<%# request = r.request %>
|
16
|
+
<!-- <td>-->
|
17
|
+
<%#= link_to time_label(request.started), show_path(id: request.request_id)%>
|
18
|
+
<!-- </td>-->
|
19
|
+
<!-- <td><%#= link_to request.action_name, action_info_path(action_name: request.action_name)%></td>-->
|
20
|
+
<!-- <td><%#= sec_str request.during %></td>-->
|
21
|
+
<!-- <td><%#= sec_str request.db_runtime %></td>-->
|
22
|
+
<!-- <td><%#= sec_str request.view_runtime%></td>-->
|
23
|
+
<!-- <td><%#= request.host %></td>-->
|
24
|
+
<!-- <td><%#= request.remote_addr %></td>-->
|
25
|
+
<!-- </tr>-->
|
26
|
+
<%# end %>
|
27
|
+
</table>
|
28
|
+
|
29
|
+
<script type="text/javascript">
|
30
|
+
$(document).ready(function () {
|
31
|
+
$('#slow-requests').DataTable({
|
32
|
+
ajax: '<%= index_path %>.json',
|
33
|
+
iDisplayLength: 25,
|
34
|
+
ordering: false
|
35
|
+
})
|
36
|
+
|
37
|
+
})
|
38
|
+
</script>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
<h4><%= @request.action_name %>.<%= @request.format %></h4>
|
2
|
+
<!-- @host="xyy", @remote_addr="127.0.0.1", @method="GET", @format="html", @exception="null"-->
|
3
|
+
<p>
|
4
|
+
<label>开始时间:</label>
|
5
|
+
<span><%= time_label @request.started, true %></span>
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
<label>响应时间:</label>
|
9
|
+
<span>
|
10
|
+
<%= sec_str @request.during %>
|
11
|
+
(数据库:<%= sec_str @request.db_runtime %>, View: <%= sec_str @request.view_runtime %>)
|
12
|
+
</span>
|
13
|
+
</p>
|
14
|
+
<p>
|
15
|
+
<label>server:</label>
|
16
|
+
<span><%= @request.host %></span>
|
17
|
+
</p>
|
18
|
+
<p>
|
19
|
+
<label>访问ip:</label>
|
20
|
+
<span><%= @request.remote_addr %></span>
|
21
|
+
</p>
|
22
|
+
<% if @request.exception.present? && @request.exception != 'null' %>
|
23
|
+
<p>
|
24
|
+
<label>报错信息: </label>
|
25
|
+
<pre><%= @request.exception %></pre>
|
26
|
+
</p>
|
27
|
+
<% end %>
|
28
|
+
<table id="sql-table" class="table">
|
29
|
+
<thead>
|
30
|
+
<tr>
|
31
|
+
<td>time</td>
|
32
|
+
<td>name</td>
|
33
|
+
<td>sql</td>
|
34
|
+
<td>时间</td>
|
35
|
+
<td>位置</td>
|
36
|
+
</tr>
|
37
|
+
</thead>
|
38
|
+
<tbody>
|
39
|
+
<% @request.sqls.each do |sql| %>
|
40
|
+
<tr>
|
41
|
+
<td><%= time_label sql.started %></td>
|
42
|
+
<td>
|
43
|
+
<%= sql.name %>
|
44
|
+
</td>
|
45
|
+
<td>
|
46
|
+
<div class="sql"><%= sql.full_sql -%></div>
|
47
|
+
</td>
|
48
|
+
<td><%= sec_str sql.during %></td>
|
49
|
+
<td><%= sql.filename %>:<%= sql.line %></td>
|
50
|
+
</tr>
|
51
|
+
<% end %>
|
52
|
+
</tbody>
|
53
|
+
</table>
|
54
|
+
<script type="text/javascript">
|
55
|
+
$(document).ready(function(){
|
56
|
+
$('#sql-table').DataTable({
|
57
|
+
bPaginate: false
|
58
|
+
})
|
59
|
+
|
60
|
+
})
|
61
|
+
</script>
|
data/config/routes.rb
ADDED
data/lib/simple_apm.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require "simple_apm/setting"
|
2
|
+
require "simple_apm/redis"
|
3
|
+
require "simple_apm/engine"
|
4
|
+
|
5
|
+
module SimpleApm
|
6
|
+
ActiveSupport::Notifications.subscribe('process_action.action_controller') do |name, started, finished, unique_id, payload|
|
7
|
+
request_id = Thread.current['action_dispatch.request_id']
|
8
|
+
if request_id.present?
|
9
|
+
action_name = "#{payload[:controller]}##{payload[:action]}" #payload[:format]
|
10
|
+
info = {
|
11
|
+
request_id: request_id,
|
12
|
+
action_name: action_name,
|
13
|
+
during: finished - started,
|
14
|
+
started: started.to_s,
|
15
|
+
db_runtime: payload[:db_runtime].to_f/1000,
|
16
|
+
view_runtime: payload[:view_runtime].to_f/1000,
|
17
|
+
controller: payload[:controller],
|
18
|
+
action: payload[:action],
|
19
|
+
host: Socket.gethostname,
|
20
|
+
remote_addr: payload[:headers]['REMOTE_ADDR'],
|
21
|
+
method: payload[:method],
|
22
|
+
format: payload[:format],
|
23
|
+
exception: payload[:exception].presence.to_json
|
24
|
+
}.with_indifferent_access
|
25
|
+
info[:status] = '500' if payload[:exception]
|
26
|
+
# 存储
|
27
|
+
in_slow = SimpleApm::SlowRequest.update_by_request info
|
28
|
+
in_action_info = SimpleApm::Action.update_by_request info
|
29
|
+
SimpleApm::Hit.update_by_request info
|
30
|
+
if in_action_info || in_slow
|
31
|
+
SimpleApm::Request.create info
|
32
|
+
else
|
33
|
+
SimpleApm::Sql.delete_by_request_id(request_id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# rescue => e
|
37
|
+
# Logger.new("#{Rails.root}/log/simple_apm.log").info e.inspect
|
38
|
+
end
|
39
|
+
|
40
|
+
ActiveSupport::Notifications.subscribe 'sql.active_record' do |name, started, finished, unique_id, payload|
|
41
|
+
request_id = Thread.current['action_dispatch.request_id'].presence||Thread.main['action_dispatch.request_id']
|
42
|
+
if request_id.present?
|
43
|
+
dev_caller = caller.detect { |c| c.include? Rails.root.to_s }
|
44
|
+
if dev_caller
|
45
|
+
c = Callsite.parse(dev_caller)
|
46
|
+
payload.merge!(:line => c.line, :filename => c.filename.to_s.gsub(Rails.root.to_s,''), :method => c.method)
|
47
|
+
end
|
48
|
+
# ActiveRecord::Relation::QueryAttribute
|
49
|
+
sql_value = payload[:binds].map{|q|[q.name, q.value]}
|
50
|
+
info = {
|
51
|
+
request_id: request_id,
|
52
|
+
name: payload[:name],
|
53
|
+
during: finished - started,
|
54
|
+
started: started,
|
55
|
+
sql: payload[:sql],
|
56
|
+
value: sql_value,
|
57
|
+
filename: payload[:filename],
|
58
|
+
line: payload[:line]
|
59
|
+
}.with_indifferent_access
|
60
|
+
SimpleApm::Sql.create request_id, info
|
61
|
+
end
|
62
|
+
# rescue => e
|
63
|
+
# Logger.new("#{Rails.root}/log/simple_apm.log").info e.inspect
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SimpleApm
|
2
|
+
class Rack
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
Thread.current['action_dispatch.request_id'] = env['action_dispatch.request_id']
|
9
|
+
@app.call(env)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Engine < ::Rails::Engine
|
14
|
+
isolate_namespace SimpleApm
|
15
|
+
config.app_middleware.use SimpleApm::Rack
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# 删除 所有
|
2
|
+
# redis-cli keys "simple_apm:*" | xargs redis-cli del
|
3
|
+
module SimpleApm
|
4
|
+
class Redis
|
5
|
+
class << self
|
6
|
+
def instance
|
7
|
+
@current ||= ::Redis::Namespace.new(
|
8
|
+
:simple_apm,
|
9
|
+
:redis => ::Redis.new(
|
10
|
+
url: SimpleApm::Setting::REDIS_URL,
|
11
|
+
driver: SimpleApm::Setting::REDIS_DRIVER
|
12
|
+
)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
# http://redisdoc.com/server/info.html
|
17
|
+
def simple_info
|
18
|
+
h = {}
|
19
|
+
redis.info.each do |k, v|
|
20
|
+
if k == 'total_system_memory_human'
|
21
|
+
h['系统内存'] = v
|
22
|
+
elsif k == 'used_memory_rss_human'
|
23
|
+
h['当前内存占用(rss)'] = v
|
24
|
+
elsif k == 'used_memory_peak_human'
|
25
|
+
h['占用内存峰值'] = v
|
26
|
+
elsif k == 'redis_version'
|
27
|
+
h['redis版本'] = v
|
28
|
+
elsif k =~ /db[0-9]+/
|
29
|
+
h[k] = v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
h
|
33
|
+
end
|
34
|
+
|
35
|
+
# 所有统计的日期,通过hits来判断
|
36
|
+
def in_apm_days
|
37
|
+
SimpleApm::Redis.keys('*:action-names').map{|x|x.split(':').first}.sort
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(method, *args)
|
41
|
+
instance.send(method, *args)
|
42
|
+
rescue NoMethodError
|
43
|
+
super(method, *args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class RedisKey
|
49
|
+
class << self
|
50
|
+
def query_date=(d = nil)
|
51
|
+
Thread.current['apm_query_date'] = d
|
52
|
+
end
|
53
|
+
|
54
|
+
def query_date
|
55
|
+
Thread.current['apm_query_date'] || Time.now.strftime('%Y-%m-%d')
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](key, _date = nil)
|
59
|
+
"#{_date||query_date}:#{key}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module SimpleApm
|
2
|
+
class Setting
|
3
|
+
ApmSettings = YAML.load(IO.read(Dir.join(Rails.root, 'configs', 'simple_apm.yml'))) rescue {}
|
4
|
+
REDIS_URL = ApmSettings['redis_url'].presence || 'redis://localhost:6379/0'
|
5
|
+
# nil , hiredis ...
|
6
|
+
REDIS_DRIVER = ApmSettings['redis_driver']
|
7
|
+
# 最慢的请求数存储量
|
8
|
+
SLOW_ACTIONS_LIMIT = ApmSettings['slow_actions_limit'].presence || 1000
|
9
|
+
# 每个action存最慢的请求量
|
10
|
+
ACTION_SLOW_REQUEST_LIMIT = ApmSettings['action_slow_request_limit'].presence || 100
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_apm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yuanyin.xia
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis-namespace
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
description: 'xyy: Simple Apm View for rails using redis.'
|
56
|
+
email:
|
57
|
+
- 454536909@qq.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- app/assets/config/simple_apm_manifest.js
|
66
|
+
- app/assets/javascripts/simple_apm/application.js
|
67
|
+
- app/assets/stylesheets/simple_apm/application.css
|
68
|
+
- app/controllers/simple_apm/apm_controller.rb
|
69
|
+
- app/controllers/simple_apm/application_controller.rb
|
70
|
+
- app/helpers/simple_apm/application_helper.rb
|
71
|
+
- app/jobs/simple_apm/application_job.rb
|
72
|
+
- app/mailers/simple_apm/application_mailer.rb
|
73
|
+
- app/models/simple_apm/action.rb
|
74
|
+
- app/models/simple_apm/application_record.rb
|
75
|
+
- app/models/simple_apm/hit.rb
|
76
|
+
- app/models/simple_apm/request.rb
|
77
|
+
- app/models/simple_apm/slow_request.rb
|
78
|
+
- app/models/simple_apm/sql.rb
|
79
|
+
- app/views/layouts/simple_apm/application.html.erb
|
80
|
+
- app/views/simple_apm/apm/action_info.html.erb
|
81
|
+
- app/views/simple_apm/apm/actions.html.erb
|
82
|
+
- app/views/simple_apm/apm/dashboard.html.erb
|
83
|
+
- app/views/simple_apm/apm/index.html.erb
|
84
|
+
- app/views/simple_apm/apm/show.html.erb
|
85
|
+
- config/routes.rb
|
86
|
+
- lib/simple_apm.rb
|
87
|
+
- lib/simple_apm/engine.rb
|
88
|
+
- lib/simple_apm/redis.rb
|
89
|
+
- lib/simple_apm/setting.rb
|
90
|
+
- lib/simple_apm/version.rb
|
91
|
+
- lib/tasks/simple_apm_tasks.rake
|
92
|
+
homepage: https://github.com/xiayuanyin/simple_apm
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.7.3
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: 'xyy: Simple Rails Apm'
|
116
|
+
test_files: []
|