simple_apm 0.1.8 → 0.1.9

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 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