simple_apm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|