mongo_profiler 0.0.1 → 0.0.2

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.
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoProfiler
4
+ describe Util do
5
+
6
+ describe '.deep_keys' do
7
+ it 'iterates over arrays' do
8
+ hash = {
9
+ key1: [{ 'key1_sub1' => 'key1_sub1_value', key1_sub2: [ 'key1_sub2_sub1' => {}] }, 'test'],
10
+ key2: nil
11
+ }
12
+
13
+ expect(described_class.deep_keys(hash)).to eq([:key1, 'key1_sub1', :key1_sub2, 'key1_sub2_sub1', :key2])
14
+ end
15
+
16
+ it 'returns all keys' do
17
+ hash = {
18
+ key1: {
19
+ key1_sub1: {
20
+ key1_sub1_sub1: 'key1_sub1_sub1_value'
21
+ },
22
+ key1_sub2: 'key1_sub2_value',
23
+ key1_sub3: {}
24
+ },
25
+ key2: {
26
+ key2_sub1: {
27
+ key2_sub1_sub1: 'key1_sub1_sub1_value',
28
+ key2_sub1_sub2: {
29
+ key2_sub1_sub2_sub1: {
30
+ key2_sub1_sub2_sub1_sub1: {}
31
+ }
32
+ }
33
+ },
34
+ key2_sub2: 'key2_sub2_value',
35
+ key2_sub3: 'key2_sub3_value',
36
+ key2_sub4: 'key2_sub4_value',
37
+ key2_sub5: 'key2_sub5_value'
38
+ }
39
+ }
40
+
41
+ expect(described_class.deep_keys(hash)).to eq(%i[key1 key1_sub1 key1_sub1_sub1
42
+ key1_sub2 key1_sub3 key2 key2_sub1
43
+ key2_sub1_sub1 key2_sub1_sub2
44
+ key2_sub1_sub2_sub1 key2_sub1_sub2_sub1_sub1
45
+ key2_sub2 key2_sub3 key2_sub4 key2_sub5])
46
+ end
47
+ end
48
+ end
49
+ end
@@ -4,49 +4,5 @@ require 'mongo_profiler/web_helpers'
4
4
  module MongoProfiler
5
5
  describe WebHelpers do
6
6
  subject { Class.new { include MongoProfiler::WebHelpers }.new }
7
-
8
-
9
- describe '#graphite_graph_url' do
10
- before { MongoProfiler.graphite_url = 'http://graphite' }
11
-
12
- let(:profile) { { 'method' => 'load_schedulers',
13
- 'file' => '/workspace/project/config/schedule.rb',
14
- 'line' => 11,
15
- 'method' => 'load_schedulers' } }
16
-
17
- context 'when -1h and 1min' do
18
- xit 'generates graphite url' do
19
- expect(subject.graphite_graph_url(profile, '-1h', '1min', 'hello')).to eq("http://graphite/render?from=-1h&until=now&width=400&height=250&target=alias(summarize(stats_counts.mongo_profiler..schedule_rb.load_schedulers,%20'1min',%20'sum'),%20'schedule.rb%23load_schedulers')&title=hello")
20
- end
21
- end
22
- end
23
-
24
- describe '#print_backtrace_entry' do
25
- it 'prints project entry' do
26
- c = %{/Users/pablo/workspace/project/app/controllers/queues_controller.rb:34:in `show'}
27
- expect(subject.print_backtrace_entry(c)).to eq %{<span class="btn-info">#{c}</span>}
28
- end
29
-
30
- context 'when gem/ruby' do
31
- it 'prints dependency entry' do
32
- c = %{/Users/pablo/.gem/ruby/2.0.0/gems/rack-1.4.5/lib/rack/session/abstract/id.rb:210:in `context'}
33
- expect(subject.print_backtrace_entry(c)).to eq c
34
- end
35
- end
36
-
37
- context 'when rubies/ruby' do
38
- it 'prints dependency entry' do
39
- c = %{/Users/pablo/.rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/httpserver.rb:94:in `run'}
40
- expect(subject.print_backtrace_entry(c)).to eq c
41
- end
42
- end
43
-
44
- context 'when bundle/ruby' do
45
- it 'prints dependency entry' do
46
- c = %{/Users/pablo/bundle/ruby/2.0.0/gems/rspec-core-2.14.4/lib/rspec/core/memoized_helpers.rb:199:in `block (2 levels) in let'}
47
- expect(subject.print_backtrace_entry(c)).to eq c
48
- end
49
- end
50
- end
51
7
  end
52
8
  end
@@ -9,35 +9,5 @@ module MongoProfiler
9
9
  def app
10
10
  described_class
11
11
  end
12
-
13
- describe 'POST /profiler/enable' do
14
- it 'enables profiler' do
15
- MongoProfiler.disable!
16
-
17
- post '/profiler/enable'
18
-
19
- expect(last_response.status).to eq 302
20
- expect(MongoProfiler.enabled?).to be_true
21
- end
22
- end
23
-
24
- describe 'POST /profiler/disable' do
25
- it 'disables profiler' do
26
- MongoProfiler.enable!
27
-
28
- post '/profiler/disable'
29
-
30
- expect(last_response.status).to eq 302
31
- expect(MongoProfiler.disabled?).to be_true
32
- end
33
- end
34
-
35
- describe 'GET /profiler/groups/:group_id' do
36
- pending
37
- end
38
-
39
- describe 'GET /profiler/:_id/explain' do
40
- pending
41
- end
42
12
  end
43
13
  end
@@ -1,113 +1,4 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MongoProfiler do
4
- describe '.log' do
5
- it 'merges required keys' do
6
- expect(MongoProfiler.collection).to receive(:insert).with({ application_name: MongoProfiler.application_name, group_id: MongoProfiler.group_id})
7
-
8
- described_class.log({})
9
- end
10
- end
11
-
12
- describe '.connect' do
13
- context 'without any argument' do
14
- it 'connects to a mongo database' do
15
- expect {
16
- described_class.connect
17
-
18
- expect(described_class.connected?).to be_true
19
- }.to_not raise_error
20
- end
21
- end
22
-
23
- context 'with arguments' do
24
- it 'connects with host, port and database' do
25
- expect {
26
- described_class.connect('localhost', 27017, 'project_development')
27
-
28
- expect(described_class.connected?).to be_true
29
- }.to_not raise_error
30
- end
31
- end
32
-
33
- context 'when invalid config' do
34
- it 'connects with host, port and database' do
35
- expect {
36
- described_class.connect('xxx')
37
- }.to raise_error
38
-
39
- expect(described_class.connected?).to be_false
40
- end
41
- end
42
- end
43
-
44
- describe '.should_skip?' do
45
- let(:_caller) {
46
- [
47
- "/Users/pablo/workspace/project/file.rb:7:in `new'",
48
- "/Users/pablo/.gem/ruby/2.0.0/gems/rspec-core-2.14.4/lib/rspec/core/memoized_helpers.rb:199:in `block (2 levels) in let'",
49
- "/Users/pablo/.gem/ruby/2.0.0/gems/rspec-core-2.14.4/lib/rspec/core/memoized_helpers.rb:199:in `fetch'",
50
- "/Users/pablo/.gem/ruby/2.0.0/gems/rspec-core-2.14.4/lib/rspec/core/memoized_helpers.rb:199:in `block in let'"
51
- ]
52
- }
53
-
54
- it 'accepts valid payload' do
55
- payload = { :database => 'project',
56
- :collection => 'stores',
57
- :selector => { '_id' => BSON::ObjectId('5282d125755b1c7f2c000005') },
58
- :limit => -1 }
59
-
60
- expect(described_class.should_skip?(payload, MongoProfiler::Caller.new(_caller))).to be_false
61
- end
62
-
63
- it 'skips $cmd for unknown collections' do
64
- payload = { :database => 'project',
65
- :collection => '$cmd',
66
- :selector => { :getnonce => 1 },
67
- :limit => -1 }
68
-
69
- expect(described_class.should_skip?(payload, MongoProfiler::Caller.new(_caller))).to be_true
70
- end
71
- end
72
-
73
- describe '.extra_attrs' do
74
- it 'sets an extra attr' do
75
- described_class.extra_attrs['test'] = 'test_value'
76
-
77
- expect(described_class.extra_attrs['test']).to eq 'test_value'
78
- end
79
- end
80
-
81
- describe '.group_id' do
82
- it 'uses default keys by default' do
83
- expect(described_class.group_id).to match /process_pid/
84
- expect(described_class.group_id).to match /thread_object_id/
85
- end
86
- end
87
-
88
- describe 'enabled and disable' do
89
- context 'when disabled' do
90
- before { described_class.disable! }
91
-
92
- it(:disabled?) { be_true }
93
- it(:enabled?) { be_false }
94
- end
95
-
96
- context 'when enabled' do
97
- before { described_class.enable! }
98
-
99
- it(:enabled?) { be_true }
100
- it(:disabled?) { be_true }
101
- end
102
- end
103
-
104
- describe '#collection' do
105
- it { expect(described_class.collection).to be_a(Mongo::Collection) }
106
- it { expect(described_class.collection.name).to eq 'mongo_profiler' }
107
- end
108
-
109
- describe '#collection_config' do
110
- it { expect(described_class.collection_config).to be_a(Mongo::Collection) }
111
- it { expect(described_class.collection_config.name).to eq 'mongo_profiler_config' }
112
- end
113
4
  end
@@ -0,0 +1,20 @@
1
+ development:
2
+ sessions:
3
+ default:
4
+ database: mongo_profiler_development
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ pool_size: 50
9
+ options:
10
+ include_root_in_json: false
11
+ raise_not_found_error: true
12
+ test:
13
+ sessions:
14
+ default:
15
+ database: mongo_profiler_test
16
+ hosts:
17
+ - localhost:27017
18
+ options:
19
+ include_root_in_json: false
20
+ raise_not_found_error: false
@@ -1,28 +1,29 @@
1
1
  require 'bundler/setup'
2
2
  require 'pry-byebug'
3
+ require 'database_cleaner'
4
+ require 'mongo_profiler'
3
5
 
4
6
  Bundler.require(:default, :test)
5
7
 
6
- require_relative '../lib/mongo_profiler'
7
- require_relative '../lib/mongo_profiler/extensions/mongo/cursor'
8
+ ENV['MONGOID_ENV'] = 'test'
8
9
 
9
- CONNECTION = Mongo::MongoClient.new
10
- DB = CONNECTION.db('mongo_profiler-database-test')
11
- COLL = DB['example-collection']
10
+ Mongoid.load!(File.join(File.dirname(__FILE__), 'mongoid.yml'))
11
+
12
+ class TestModel
13
+ include Mongoid::Document
14
+ include Mongoid::Timestamps
15
+ include Mongoid::Attributes::Dynamic
16
+ end
12
17
 
13
18
  Dir['./spec/support/**/*.rb'].each &method(:require)
14
19
 
15
20
  RSpec.configure do |config|
16
21
  config.before do
17
- MongoProfiler.connect('localhost', 27017, 'mongo_profiler-database-test')
18
-
19
- # creates capped collections
20
- MongoProfiler.create_collections
22
+ DatabaseCleaner.strategy = :truncation
23
+ DatabaseCleaner.start
21
24
  end
22
25
 
23
26
  config.after do
24
- DB.collections.each do |collection|
25
- collection.drop unless collection.name.match(/^system\./)
26
- end
27
+ DatabaseCleaner.clean
27
28
  end
28
29
  end
@@ -1,32 +1,36 @@
1
1
  <h2 class="page-header">Profile Groups</h2>
2
2
 
3
3
  <div class="row">
4
- <table class="table table-bordered">
4
+ <table class="table table-bordered table-striped">
5
5
  <thead>
6
- <th>Application</th>
7
- <th>File Name</th>
8
- <th>Method</th>
9
- <th>Total Queries</th>
10
- <th>Total Time</th>
11
- <th>Avg Total Time</th>
12
- <th>Group Id</th>
6
+ <th>Group name</th>
7
+ <th>Total time ms</th>
8
+ <th>AVG time ms</th>
9
+ <th>Min time ms</th>
10
+ <th>Max time ms</th>
11
+ <th>Queries</th>
12
+ <th>perfect</th>
13
+ <th>had_to_order</th>
14
+ <th>scanned_more_than_returned</th>
15
+ <th>no_index</th>
16
+ <th>no_docs_found</th>
13
17
  </thead>
14
- <% @grouped_profiles.each_with_index do |(group_id, profiles), grouped_index| %>
15
- <tbody class="<%= (grouped_index % 2 == 0) ? 'tbody-gray' : '' %>">
16
- <% profiles.each_with_index do |profile, profiles_index| %>
17
- <tr>
18
- <td><%= profile['application_name'] %></td>
19
- <td><%= profile['file'].split('/').last %></td>
20
- <td><%= profile['method'] %></td>
21
- <td><%= profile['total'] %></td>
22
- <td><%= profile['total_time'] %></td>
23
- <td><%= profile['total_time'] / profile['total'] %></td>
24
- <% if profiles_index == 0 %>
25
- <td rowspan="<%= profiles.size %>"><a href="<%= root_path %>profiler/groups/<%= group_id %>"><%= group_id %></a></td>
26
- <% end %>
27
- </tr>
28
- <% end %>
29
- </tbody>
30
- <% end %>
18
+ <tbody class="header">
19
+ <% @groups.each do |group| %>
20
+ <tr>
21
+ <td><a href="<%= root_path %>groups/<%= group.id %>"><%= group.name %></a></td>
22
+ <td><%= group.total_time %></td>
23
+ <td><%= group.avg_time %></td>
24
+ <td><%= group.min_time %></td>
25
+ <td><%= group.max_time %></td>
26
+ <td><%= group.profiles.count %></td>
27
+ <td><%= group.count_by_score :perfect %></td>
28
+ <td><%= group.count_by_score :had_to_order %></td>
29
+ <td><%= group.count_by_score :scanned_more_than_returned %></td>
30
+ <td><%= group.count_by_score :no_index %></td>
31
+ <td><%= group.count_by_score :no_docs_found %></td>
32
+ </tr>
33
+ <% end %>
34
+ </tbody>
31
35
  </table>
32
36
  </div>
@@ -36,20 +36,16 @@
36
36
  <li><a href="<%= root_path %>">Dashboard</a></li>
37
37
  </ul>
38
38
  -->
39
- <% if MongoProfiler.enabled? %>
40
- <form action="<%= root_path %>profiler/disable" method="post" class="navbar-form navbar-right">
41
- <button class="btn btn-danger">Disable Profiler</button>
42
- </form>
43
- <% else %>
44
- <form action="<%= root_path %>profiler/enable" method="post" class="navbar-form navbar-right">
45
- <button class="btn btn-primary">Enable Profiler</button>
46
- </form>
47
- <% end %>
39
+
40
+ <form action="<%= root_path %>clear" method="post" class="navbar-form navbar-left" role="search">
41
+ <button class="btn btn-danger">Clear History</button>
42
+ </form>
43
+
48
44
  </div><!--/.nav-collapse -->
49
45
  </div>
50
46
  </div>
51
47
 
52
- <div class="container">
48
+ <div class="container-fluid">
53
49
  <div class="starter-template">
54
50
  <% if @error_alert %>
55
51
  <div class="alert alert-danger alert-dismissable">
@@ -1,83 +1,83 @@
1
- <h2 class="page-header">Profile Id: <%= @profile_id %></h2>
1
+ <% group = @group %>
2
+
3
+ <h2 class="page-header">Profile Groups <%= group.name %></h2>
4
+
5
+ <style>
6
+ tr, td, pre
7
+ {
8
+ height:60px;
9
+ max-height:60px;
10
+ }
11
+ </style>
12
+
13
+ <script>
14
+ $(function(){
15
+ $('tr pre').click(function(){
16
+ var height = '400px';
17
+
18
+ if($(this).height() > 300){
19
+ height = '60px';
20
+ }
21
+
22
+ $(this).animate({ 'height': height, 'max-height': height });
23
+ })
24
+ });
25
+ </script>
2
26
 
3
27
  <div class="row">
4
- <table class="table table-bordered">
28
+ <table class="table table-bordered table-striped">
5
29
  <thead>
6
- <th>File Name</th>
7
- <th>Method</th>
8
- <th>Total Time</th>
9
- <th>Instrument Payload</th>
10
- <th>Group Id</th>
30
+ <th>Group name</th>
31
+ <th>Total time ms</th>
32
+ <th>AVG time ms</th>
33
+ <th>Min time ms</th>
34
+ <th>Max time ms</th>
35
+ <th>Queries</th>
36
+ <th>perfect</th>
37
+ <th>had_to_order</th>
38
+ <th>scanned_more_than_returned</th>
39
+ <th>no_index</th>
40
+ <th>no_docs_found</th>
11
41
  </thead>
12
- <tbody>
42
+ <tbody>
13
43
  <tr>
14
- <td><%= @profile['file'].split('/').last %>:<%= @profile['line'] %></td>
15
- <td><%= @profile['method'] %></td>
16
- <td><%= @profile['total_time'] %></td>
17
- <td><pre><code><%= @profile['instrument_payload'] %></code></pre></td>
18
- <td><a href="<%= root_path %>profiler/groups/<%= @profile['group_id'] %>"><%= @profile['group_id'] %></a></td>
44
+ <td><%= group.name %></td>
45
+ <td><%= group.total_time %></td>
46
+ <td><%= group.avg_time %></td>
47
+ <td><%= group.min_time %></td>
48
+ <td><%= group.max_time %></td>
49
+ <td><%= group.profiles.count %></td>
50
+ <td><%= group.count_by_score :perfect %></td>
51
+ <td><%= group.count_by_score :had_to_order %></td>
52
+ <td><%= group.count_by_score :scanned_more_than_returned %></td>
53
+ <td><%= group.count_by_score :no_index %></td>
54
+ <td><%= group.count_by_score :no_docs_found %></td>
19
55
  </tr>
20
- </tbody>
56
+ </tbody>
21
57
  </table>
22
- </div>
23
-
24
- <% if MongoProfiler.graphite_url %>
25
- <div class="row">
26
- <h4>Graphite / Timers</h4>
27
- <div class="col-sm-6 col-md-4">
28
- <div class="thumbnail">
29
- <img data-src="holder.js/300x200" alt="..." src="<%= graphite_graph_timers_url(@profile, '-1h', '1min', 'Last hour/min') %>">
30
- </div>
31
- </div>
32
- <div class="col-sm-6 col-md-4">
33
- <div class="thumbnail">
34
- <img data-src="holder.js/300x200" alt="..." src="<%= graphite_graph_timers_url(@profile, '-1day', '1h', 'Last day/hour') %>">
35
- </div>
36
- </div>
37
- <div class="col-sm-6 col-md-4">
38
- <div class="thumbnail">
39
- <img data-src="holder.js/300x200" alt="..." src="<%= graphite_graph_timers_url(@profile, '-1month', '1d', 'Last month/day') %>">
40
- </div>
41
- </div>
42
- </div>
43
-
44
- <div class="row">
45
- <h4>Graphite / Count</h4>
46
- <div class="col-sm-6 col-md-4">
47
- <div class="thumbnail">
48
- <img data-src="holder.js/300x200" alt="..." src="<%= graphite_graph_count_url(@profile, '-1h', '1min', 'Last hour/min') %>">
49
- </div>
50
- </div>
51
- <div class="col-sm-6 col-md-4">
52
- <div class="thumbnail">
53
- <img data-src="holder.js/300x200" alt="..." src="<%= graphite_graph_count_url(@profile, '-1day', '1h', 'Last day/hour') %>">
54
- </div>
55
- </div>
56
- <div class="col-sm-6 col-md-4">
57
- <div class="thumbnail">
58
- <img data-src="holder.js/300x200" alt="..." src="<%= graphite_graph_count_url(@profile, '-1month', '1d', 'Last month/day') %>">
59
- </div>
60
- </div>
61
- </div>
62
- <% end %>
63
58
 
64
- <div class="row">
65
- <div class="col-md-5">
66
- <h4>Selector</h4>
67
- <pre><code><%= @selector.to_json %></code></pre>
68
- </div>
69
-
70
- <div class="col-md-5">
71
- <h4>Explain</h4>
72
- <pre><code><%= @explain.to_json %></code></pre>
73
- </div>
74
- </div>
75
-
76
- <div class="row">
77
- <h4>Backtrace</h4>
78
- <ol>
79
- <% @profile['backtrace'].to_a.each do |c| %>
80
- <li><%= print_backtrace_entry(c) %></li>
81
- <% end %>
82
- </ol>
59
+ <table class="table table-bordered table-striped">
60
+ <thead class="header">
61
+ <th>Total Time</th>
62
+ <th>Query score</th>
63
+ <th>File Name</th>
64
+ <th>Database</th>
65
+ <th>Collection</th>
66
+ <th>Command</th>
67
+ <th>Explain</th>
68
+ </thead>
69
+ <tbody>
70
+ <% group.profiles.sort_by(&:total_time).reverse.each do |profile| %>
71
+ <tr>
72
+ <td><%= profile['total_time'] %></td>
73
+ <td><%= profile.score %></td>
74
+ <td><%= "#{profile['file'].split('/').last}:#{profile['line']}" %></td>
75
+ <td><%= profile['command_database'] %></td>
76
+ <td><%= profile['command_collection'] %></td>
77
+ <td><pre style="width: 300px" title="Click to toggle"><code><%= profile['command'] %></code></pre></td>
78
+ <td><pre style="width: 300px" title="Click to toggle"><code><%= profile['explain'] %></code></pre></td>
79
+ </tr>
80
+ <% end %>
81
+ </tbody>
82
+ </table>
83
83
  </div>