simple_apm 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd96eb80e66cb02f54e84663909f84a974ff4071b2f5d30a9c136fa3c23552b1
4
- data.tar.gz: 4b4018dc5da34ade9b345dc58622655d9263603557f1411cf17f8affe20e2725
3
+ metadata.gz: 23e7fd45a7f7fc7edc4590c681e1eabac3a34adfe7ac1a3d17e8b16a97303502
4
+ data.tar.gz: 3020c53720ad77a0414711531c590d7334eee6aab119bf3e1fb4d1b2c2b1c973
5
5
  SHA512:
6
- metadata.gz: bdcd065ce63b5143b915d8f288d23132da6caf4b383cdf938c5baf9347770eb90a252bc185ba70a9ddf66fdf46b3ae10103bb7aa7b9661e2674a978a7bd5813d
7
- data.tar.gz: c734c6adf33cfb68ff94c330a1b8dea7a2be74fdee069e8ee9406f86190b23be426dbb1994ac890e391c3b4976bca1b0e320390682ac2c8ac30089a1cbb5ae06
6
+ metadata.gz: 1701cce03295e27d0d6cef228f86358c6653fbecfb616c6613b84c90c5eb0dd73e3e5f30fec9b34a8f6a174c3b7ce754a8c0de693f35f8f58d896a6f8bb97c25
7
+ data.tar.gz: 21d7870003ba6dcf01383b65f657a3ee9a36d51943c5b76f5558191d609b17791fa6e688443394509aa86c8f99308993b01cb9200c7fb12fc00e85f0541c7c81
data/README.md CHANGED
@@ -3,18 +3,21 @@
3
3
  基于Redis的简单的Web请求性能监控/慢事务追踪工具
4
4
 
5
5
  以天为维度记录:
6
- - 最慢的1000个(默认1000)请求
7
- - 记录每个action最慢的100次请求
6
+ - 最慢的500个(默认500)请求
7
+ - 记录每个action最慢的20次请求
8
8
  - 记录每个action的平均访问时间
9
- - 记录每个小时请求量
10
9
  - 记录慢请求的详情和对应SQL详情(多余的会删掉)
11
- - 以10分钟为刻度记录平均/最慢访问时间、次数等性能指标
10
+ - 以10分钟为刻度记录平均/最慢访问时间、次数等性能指标,并生成图表
12
11
 
13
12
  ## Usage
14
13
 
15
14
  ```ruby
16
15
  # routes.rb
17
- mount SimpleApm::Engine => '/apm'
16
+ mount SimpleApm::Engine => "/apm"
17
+
18
+ # 或运行
19
+ rails generate simple_apm:install
20
+
18
21
  ```
19
22
 
20
23
 
@@ -11,3 +11,29 @@
11
11
  // about supported directives.
12
12
  //
13
13
  //= require_tree .
14
+
15
+ // JqueryDataTable自定义排序方法
16
+ jQuery.extend(jQuery.fn.dataTableExt.oSort, {
17
+ "sec-pre": function (a) {
18
+ var x = String(a).replace(/<[\s\S]*?>/g, ""); //去除html标记
19
+ x = x.replace(/&amp;nbsp;/ig, ""); //去除空格
20
+ x = x.replace(/%/, ""); //去除百分号
21
+ if(x.indexOf('ms')>0){
22
+ x = x.replace(/ms/, "");
23
+ return parseFloat(x)/1000;
24
+ }else if(x.indexOf('min')>0){
25
+ x = x.replace(/min/, "");
26
+ return parseFloat(x)*60;
27
+ }else{
28
+ x = x.replace(/min/, "");
29
+ return parseFloat(x)
30
+ }
31
+ },
32
+ "sec-asc": function (a, b) { //正序排序引用方法
33
+ return ((a < b) ? -1 : ((a > b) ? 1 : 0));
34
+ },
35
+ "sec-desc": function (a, b) { //倒序排序引用方法
36
+ return ((a < b) ? 1 : ((a > b) ? -1 : 0));
37
+ }
38
+ });
39
+
@@ -9,14 +9,14 @@ module SimpleApm
9
9
  d = SimpleApm::RedisKey.query_date == Time.now.strftime('%Y-%m-%d') ? Time.now.strftime('%H:%M') : '23:50'
10
10
  data = SimpleApm::Hit.chart_data(0, d)
11
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}
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
14
  end
15
15
 
16
16
  def index
17
17
  respond_to do |format|
18
18
  format.json do
19
- @slow_requests = SimpleApm::SlowRequest.list(params[:count]||200).map do |r|
19
+ @slow_requests = SimpleApm::SlowRequest.list(params[:count] || 200).map do |r|
20
20
  request = r.request
21
21
  [
22
22
  link_to(time_label(request.started), show_path(id: request.request_id)),
@@ -39,7 +39,7 @@ module SimpleApm
39
39
  end
40
40
 
41
41
  def actions
42
- @actions = SimpleApm::Action.all_names.map{|n| SimpleApm::Action.find(n)}
42
+ @actions = SimpleApm::Action.all_names.map {|n| SimpleApm::Action.find(n)}
43
43
  end
44
44
 
45
45
  def action_info
@@ -51,12 +51,37 @@ module SimpleApm
51
51
  redirect_to request.referer
52
52
  end
53
53
 
54
+ def data
55
+ @data = SimpleApm::Redis.in_apm_days.map {|x| SimpleApm::Hit.day_info(x)}
56
+ end
57
+
58
+ def data_delete
59
+ if params[:date].is_a?(String)
60
+ r = SimpleApm::Redis.clear_data(params[:date])
61
+ flash[:notice] = r[:success] ? '删除成功!' : r[:msg]
62
+ elsif params[:type]=='month'
63
+ del_count = SimpleApm::Redis.clear_data_before_time(Time.now.at_beginning_of_day - 1.month)
64
+ flash[:notice] = "成功删除#{del_count}条数据"
65
+ elsif params[:type]=='week'
66
+ del_count = SimpleApm::Redis.clear_data_before_time(Time.now.at_beginning_of_day - 1.week)
67
+ flash[:notice] = "成功删除#{del_count}条数据"
68
+ else
69
+ flash[:notice] = '未知操作!'
70
+ # r = params[:date].map{|d|SimpleApm::Redis.clear_data(d)}
71
+ # suc, fail = r.partition{|x|x[:success]}
72
+ # flash[:notice] = "成功删除#{suc.length}"
73
+ # flash[:notice] << ",失败#{fail.length}" if fail.length>0
74
+ end
75
+ redirect_to action: :data
76
+ end
77
+
54
78
  def set_apm_date
55
79
  # set_query_date
56
80
  redirect_to action: :dashboard
57
81
  end
58
82
 
59
83
  private
84
+
60
85
  def set_query_date
61
86
  session[:apm_date] = params[:apm_date] if params[:apm_date].present?
62
87
  SimpleApm::RedisKey.query_date = session[:apm_date]
@@ -8,19 +8,19 @@ module SimpleApm
8
8
  _sec = sec.to_f
9
9
 
10
10
  if force == 'min'
11
- return "#{(_sec / 60).to_f.round(1)}min"
11
+ return "#{(_sec / 60).to_f.round(1)} min"
12
12
  elsif force == 's'
13
- return "#{_sec.round(2)}s"
13
+ return "#{_sec.round(2)} s"
14
14
  elsif force == 'ms'
15
- return "#{(_sec * 1000).round}ms"
15
+ return "#{(_sec * 1000).round} ms"
16
16
  end
17
17
 
18
18
  if (_sec / 60).to_i > 0
19
- "#{(_sec / 60).to_f.round(1)}min"
19
+ "#{(_sec / 60).to_f.round(1)} min"
20
20
  elsif _sec.to_i > 0
21
- "#{_sec.round(2)}s"
21
+ "#{_sec.round(2)} s"
22
22
  else
23
- "#{(_sec * 1000).round}ms"
23
+ "#{(_sec * 1000).round} ms"
24
24
  end
25
25
  end
26
26
  end
@@ -4,6 +4,19 @@ module SimpleApm
4
4
 
5
5
 
6
6
  class << self
7
+ def day_info(date_str)
8
+ hits, time = 0, 0.0
9
+ SimpleApm::Redis.hgetall(minute_key(date_str)).each do |k, v|
10
+ if k=~/time/
11
+ time += v.to_f
12
+ elsif k=~/hits/
13
+ hits += v.to_i
14
+ end
15
+ end
16
+ avg_time = (hits.to_i==0 ? 0 : (time/hits).to_f.round(3))
17
+ {day: date_str, hits: hits, time: time, avg_time: avg_time}
18
+ end
19
+
7
20
  def chart_data(start_time = '00:00', end_time = '23:50', per = 'minute')
8
21
  start_hour = start_time.to_s.split(':').first.to_i
9
22
  end_hour = [end_time.to_s.split(':').first.to_i, 23].min
@@ -42,8 +55,8 @@ module SimpleApm
42
55
  i.to_s.size==1 ? "0#{i}" : i.to_s
43
56
  end
44
57
 
45
- def minute_key
46
- SimpleApm::RedisKey['per-10-minute']
58
+ def minute_key(date_str = nil)
59
+ SimpleApm::RedisKey['per-10-minute', date_str]
47
60
  end
48
61
 
49
62
  # def hour_hit_key
@@ -40,7 +40,7 @@ module SimpleApm
40
40
  # @param during [Float] 耗时
41
41
  # @return [Boolean] 是否插入成功
42
42
  def update_request(during, request_id)
43
- # 记录最慢请求列表1000
43
+ # 记录最慢请求列表5000
44
44
  SimpleApm::Redis.zadd(key, during, request_id)
45
45
  SimpleApm::Redis.zremrangebyrank(key, 0, -SimpleApm::Setting::SLOW_ACTIONS_LIMIT - 1)
46
46
  SimpleApm::Redis.zrank(key, request_id).present?
@@ -50,7 +50,7 @@ module SimpleApm
50
50
  # @return [Array<SimpleApm::SlowRequest>]
51
51
  def list_by_action(action_name, limit = 100, offset = 0)
52
52
  SimpleApm::Redis.zrevrange(
53
- action_key(action_name), offset, limit, with_scores: true
53
+ action_key(action_name), offset, limit.to_i - 1, with_scores: true
54
54
  ).map{ |x| SimpleApm::SlowRequest.new(x[0], x[1], action_name)}
55
55
  end
56
56
 
@@ -20,13 +20,23 @@
20
20
  <nav class="navbar navbar-default navbar-fixed-top">
21
21
  <div class="container">
22
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>
23
+ <%
24
+ [
25
+ [dashboard_path, 'Dashboard'],
26
+ [index_path, '慢事务列表'],
27
+ [actions_path, 'ActionList'],
28
+ [data_path, '数据管理']
29
+ ].each do |m|
30
+ %>
31
+ <a class="navbar-brand <%= 'active' if request.url =~ /#{m[0]}/ %>"
32
+ href="<%= m[0] %>" >
33
+ <%= m[1] %>
34
+ </a>
25
35
  <% end %>
26
36
  </div>
27
37
  <div class="select-apm-date">
28
38
  统计日期
29
- <%= select_tag 'apm_date', options_for_select(SimpleApm::Redis.in_apm_days, apm_date), class: 'form-control'%>
39
+ <%= select_tag 'apm_date', options_for_select(SimpleApm::Redis.in_apm_days, apm_date), class: 'form-control' %>
30
40
  </div>
31
41
 
32
42
  </div>
@@ -42,20 +52,35 @@
42
52
  </footer>
43
53
  <div id="sql-modal" class="modal" aria-hidden="true" role="dialog" style="display: none;" tabindex="-1">
44
54
  <div class="modal-dialog modal-lg modal-dialog-popout">
45
- <pre class="modal-content" data-lang="SQL">
46
- </pre>
55
+ <div class="panel panel-default">
56
+ <div class="panel-heading">
57
+ <div class="panel-title">SQL</div>
58
+ </div>
59
+ <div class="panel-body">
60
+ <pre class="modal-content" data-lang="SQL"></pre>
61
+ </div>
62
+ </div>
63
+
47
64
  </div>
48
65
  </div>
49
66
 
50
67
  </body>
51
68
  <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
- })
69
+ <% if flash[:notice].present? %>
70
+ alert(<%= flash[:notice].to_json.html_safe %>)
71
+ <% end %>
72
+ var sql_modal_obj = $('#sql-modal')
73
+ $('.select-apm-date select').on('change', function () {
74
+ window.location.href = '<%= set_apm_date_path %>?apm_date=' + this.value
75
+ })
76
+ $('.sql').on('click', function () {
77
+ sql_modal_obj.find('.modal-content').html($(this).html())
78
+ sql_modal_obj.modal('show')
79
+ })
80
+ // 手写部分ujs
81
+ $("a[data-confirm]").on('click', function () {
82
+ return confirm($(this).data('confirm'))
83
+ })
84
+
60
85
  </script>
61
86
  </html>
@@ -13,9 +13,9 @@
13
13
  <tr>
14
14
  <td><%= link_to action.name, action_info_path(action_name: action.name) %></td>
15
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>
16
+ <td><%= sec_str action.avg_time %></td>
17
+ <td><%= link_to sec_str(action.fast_time), show_path(id: action.fast_id) %></td>
18
+ <td><%= link_to sec_str(action.slow_time), show_path(id: action.slow_id) %></td>
19
19
  </tr>
20
20
  <% end %>
21
21
  </tbody>
@@ -23,7 +23,9 @@
23
23
  <script type="text/javascript">
24
24
  $(document).ready(function(){
25
25
  $('#actions-table').DataTable({
26
- iDisplayLength: 25
26
+ iDisplayLength: 25,
27
+ // 自定义的排序方法
28
+ aoColumnDefs: [ {sType: "sec", aTargets: [2,3,4]}]
27
29
  })
28
30
 
29
31
  })
@@ -0,0 +1,27 @@
1
+ <p class="pull-right">
2
+ <span class="text-danger">删除数据:</span>
3
+ <%= link_to '保留进一周数据', data_delete_path(type: 'week'), class: 'btn btn-danger', 'data-confirm': '确定删除所有一周以前的数据?'%>
4
+ <%= link_to '保留进一个月数据', data_delete_path(type: 'month'), class: 'btn btn-danger', 'data-confirm': '确定删除所有一个月以前的数据?' %>
5
+ </p>
6
+
7
+ <table class="table table-bordered">
8
+ <thead>
9
+ <tr>
10
+ <th>日期</th>
11
+ <th>请求数量</th>
12
+ <th>平均响应时间</th>
13
+ <th>操作</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <!-- {day: date_str, hits: hits, time: time, avg_time: avg_time}-->
18
+ <% @data.each do |d| %>
19
+ <tr>
20
+ <td><%= d[:day]%></td>
21
+ <td><%= d[:hits]%></td>
22
+ <td><%= sec_str d[:avg_time]%></td>
23
+ <td><%= link_to '删除', data_delete_path(date: d[:day]), 'data-confirm': '确定删除?' %></td>
24
+ </tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
@@ -10,20 +10,6 @@
10
10
  <th>remote_addr</th>
11
11
  </tr>
12
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
13
  </table>
28
14
 
29
15
  <script type="text/javascript">
@@ -1,5 +1,4 @@
1
- <h4><%= @request.action_name %>.<%= @request.format %></h4>
2
- <!-- @host="xyy", @remote_addr="127.0.0.1", @method="GET", @format="html", @exception="null"-->
1
+ <h4><%= @request.action_name %>.<%= @request.format %>(<%= @request.method %>)</h4>
3
2
  <p>
4
3
  <label>开始时间:</label>
5
4
  <span><%= time_label @request.started, true %></span>
@@ -31,7 +30,7 @@
31
30
  <td>time</td>
32
31
  <td>name</td>
33
32
  <td>sql</td>
34
- <td>时间</td>
33
+ <td>时间(ms)</td>
35
34
  <td>位置</td>
36
35
  </tr>
37
36
  </thead>
@@ -54,7 +53,9 @@
54
53
  <script type="text/javascript">
55
54
  $(document).ready(function(){
56
55
  $('#sql-table').DataTable({
57
- bPaginate: false
56
+ bPaginate: false,
57
+ // 自定义的排序方法
58
+ aoColumnDefs: [{sType: "sec", aTargets: [3]}]
58
59
  })
59
60
 
60
61
  })
@@ -4,6 +4,8 @@ SimpleApm::Engine.routes.draw do
4
4
  get 'show', to: 'apm#show'
5
5
  get 'action_info', to: 'apm#action_info'
6
6
  get 'actions', to: 'apm#actions'
7
+ get 'data', to: 'apm#data'
8
+ get 'data_delete', to: 'apm#data_delete'
7
9
  get 'set_apm_date', to: 'apm#set_apm_date'
8
10
 
9
11
  end
@@ -0,0 +1,11 @@
1
+ # 默认为 redis://localhost:6379/0
2
+ # 确保是完整的redis地址,可以设置远程地址
3
+ redis_url:
4
+ # 默认为空, 建议使用hiredis(并确保有这个gem)
5
+ redis_driver:
6
+ # 记录每天的最慢的请求数存储量,默认500
7
+ slow_actions_limit:
8
+ # 记录每天的每个Action最慢的N个请求,默认20
9
+ action_slow_request_limit:
10
+ # 项目名,默认为 'app',区分项目名可以多个项目数据存于一台redis server上
11
+ app_name:
@@ -0,0 +1,23 @@
1
+ require 'rails/generators'
2
+ module SimpleApm
3
+ module Generators
4
+ class InstallGenerator < Rails::Generators::Base
5
+ desc "Create Notifications's base files"
6
+ source_root File.expand_path('../../../../', __FILE__)
7
+
8
+ def add_default_config
9
+ path = "#{Rails.root}/config/simple_apm.yml"
10
+ if File.exist?(path)
11
+ puts 'Skipping config/simple_apm.yml creation, as file already exists!'
12
+ else
13
+ puts 'Adding simple_apm default config file (config/simple_apm.yml)...'
14
+ template 'config/simple_apm.yml', path
15
+ end
16
+ end
17
+
18
+ def add_routes
19
+ route 'mount SimpleApm::Engine => "/apm"'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -37,7 +37,22 @@ module SimpleApm
37
37
 
38
38
  # 所有统计的日期,通过hits来判断
39
39
  def in_apm_days
40
- SimpleApm::Redis.keys('*:action-names').map{|x|x.split(':').first}.sort
40
+ SimpleApm::Redis.keys('*:action-names').map{|x|x.split(':').first}.sort.reverse
41
+ end
42
+
43
+ # 清理指定日期之前的数据
44
+ def clear_data_before_time(date)
45
+ i = 0
46
+ SimpleApm::Redis.in_apm_days.each do |d|
47
+ SimpleApm::Redis.clear_data(d) and i+=1 if Time.parse(d) <= date
48
+ end
49
+ i
50
+ end
51
+
52
+ def clear_data(date_str)
53
+ return {success: false, msg: '当日没有数据'} unless in_apm_days.include?(date_str)
54
+ keys = SimpleApm::Redis.keys("#{date_str}:*")
55
+ {success: true, msg: SimpleApm::Redis.del(keys)}
41
56
  end
42
57
 
43
58
  def method_missing(method, *args)
@@ -1,13 +1,13 @@
1
1
  module SimpleApm
2
2
  class Setting
3
- ApmSettings = YAML.load(IO.read(Dir.join(Rails.root, 'configs', 'simple_apm.yml'))) rescue {}
3
+ ApmSettings = YAML.load(IO.read("config/simple_apm.yml")) rescue {}
4
4
  REDIS_URL = ApmSettings['redis_url'].presence || 'redis://localhost:6379/0'
5
5
  # nil , hiredis ...
6
6
  REDIS_DRIVER = ApmSettings['redis_driver']
7
7
  # 最慢的请求数存储量
8
- SLOW_ACTIONS_LIMIT = ApmSettings['slow_actions_limit'].presence || 1000
8
+ SLOW_ACTIONS_LIMIT = ApmSettings['slow_actions_limit'].presence || 500
9
9
  # 每个action存最慢的请求量
10
- ACTION_SLOW_REQUEST_LIMIT = ApmSettings['action_slow_request_limit'].presence || 100
10
+ ACTION_SLOW_REQUEST_LIMIT = ApmSettings['action_slow_request_limit'].presence || 20
11
11
  # 区分项目显示
12
12
  APP_NAME = ApmSettings['app_name'].presence || 'app'
13
13
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleApm
2
- VERSION = '0.1.8'
2
+ VERSION = '0.1.9'
3
3
  end
@@ -1,4 +1,4 @@
1
1
  # desc "Explaining what the task does"
2
- # task :simple_apm do
3
- # # Task goes here
4
- # end
2
+ task :simple_apm do
3
+ # Task goes here
4
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - yuanyin.xia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-04 00:00:00.000000000 Z
11
+ date: 2018-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -80,9 +80,12 @@ files:
80
80
  - app/views/simple_apm/apm/action_info.html.erb
81
81
  - app/views/simple_apm/apm/actions.html.erb
82
82
  - app/views/simple_apm/apm/dashboard.html.erb
83
+ - app/views/simple_apm/apm/data.html.erb
83
84
  - app/views/simple_apm/apm/index.html.erb
84
85
  - app/views/simple_apm/apm/show.html.erb
85
86
  - config/routes.rb
87
+ - config/simple_apm.yml
88
+ - lib/generators/simple_apm/install_generator.rb
86
89
  - lib/simple_apm.rb
87
90
  - lib/simple_apm/engine.rb
88
91
  - lib/simple_apm/redis.rb