rackprof 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07ce5c7dfbfdce738b4236a0ebdd6677dda426be
4
+ data.tar.gz: 13c8d4ce865bba4409474811d7ac2d288ab04e12
5
+ SHA512:
6
+ metadata.gz: 14eccfc8b25ebca81314da07c6d63111834163d69c9b085e1a472b665a6eb9d6a897839701eac28a88087ad72ed6a9ed5be7a2f42e2f1b6620bcd524c9b57bb3
7
+ data.tar.gz: efd2681021433e03b521ada0128b241b46530070378ebf84482641b243c1dbdcf525b87f99827fb7d2d86794ec649cfdd7abb25833c14d1c30ba4b41bf89d2ec
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rackprof.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Norimasa Ando
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Rackprof
2
+
3
+ Middleware which displays GC, sampling call-stack on dashboard using GC.stat, [stackprof](https://github.com/tmm1/stackprof).
4
+
5
+ Only supports for ruby 2.1+ and [thin](https://github.com/macournoyer/thin) web server at the moment.
6
+
7
+ ![image](https://raw.githubusercontent.com/wiki/hbd225/rackprof/images/sample.png)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'rackprof'
15
+ ```
16
+
17
+ ### Rails
18
+
19
+ Nothing to do!
20
+
21
+ ### Sinatra
22
+
23
+ ```ruby
24
+ require 'rackprof'
25
+ class MyApp < Sinatra::Base
26
+ use Rackprof::Middleware
27
+ end
28
+ ```
29
+
30
+ ### Rack
31
+
32
+ In your `config.ru` add `use RackProf` line:
33
+
34
+ ```ruby
35
+ require 'rackprof'
36
+ use Rackprof::Middleware
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ Access to dashboard page which is automatically mounted at `/rackprof`:
42
+
43
+ ```
44
+ http://localhost:3000/rackprof
45
+ ```
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork it ( https://github.com/[my-github-username]/rackprof/fork )
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rackprof"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/rackprof.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'rackprof/version'
2
+ require 'observer'
3
+
4
+ module Rackprof
5
+ autoload :Web, 'rackprof/web'
6
+ autoload :Middleware, 'rackprof/middleware'
7
+ autoload :Observer, 'rackprof/observer'
8
+ autoload :GCNotify, 'rackprof/gc_notify'
9
+ autoload :GCObserver, 'rackprof/gc_observer'
10
+ autoload :RequestNotify, 'rackprof/request_notify'
11
+ autoload :RequestObserver, 'rackprof/request_observer'
12
+ end
13
+
14
+ require 'rackprof/rails' if defined? Rails::Railtie
@@ -0,0 +1,5 @@
1
+ module Rackprof
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Rackprof
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ require 'observer'
2
+
3
+ module Rackprof
4
+ class GCNotify
5
+ include Singleton
6
+ include Observable
7
+
8
+ def start
9
+ EM.run do
10
+ EM.add_periodic_timer(3) do
11
+ notify if count_observers > 0
12
+ end
13
+ end
14
+ end
15
+
16
+ def notify
17
+ changed
18
+ notify_observers
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Rackprof
2
+ class GCObserver < Observer
3
+ def update
4
+ connection << "data: #{GC.stat.to_json}\n\n"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module Rackprof
2
+ class Middleware
3
+ IGNORE_REQUESTS = /(assets|rackprof)/
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ if env['PATH_INFO'] !~ IGNORE_REQUESTS
11
+ RequestNotify.instance.notify { @app.call(env) }
12
+ else
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Rackprof
2
+ class Observer
3
+ attr_reader :connection
4
+
5
+ def initialize(connection)
6
+ @connection = connection
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Rackprof
2
+ class Railtie < Rails::Railtie
3
+ initializer 'rackprof.initialization' do |app|
4
+ app.middleware.use Rackprof::Middleware
5
+ app.routes.append { mount Rackprof::Web => '/rackprof' }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ require 'stackprof'
2
+
3
+ module Rackprof
4
+ class RequestNotify
5
+ include Singleton
6
+ include Observable
7
+
8
+ def notify(&block)
9
+ ret = []
10
+
11
+ profile = StackProf.run(mode: :cpu) do
12
+ ret = block.call
13
+ end
14
+
15
+ if count_observers > 0
16
+ io = StringIO.new
17
+ #StackProf::Report.new(profile).print_graphviz(nil, io)
18
+ StackProf::Report.new(profile).print_text(false, 20, io)
19
+
20
+ changed
21
+ notify_observers(io.string)
22
+ end
23
+
24
+ ret
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module Rackprof
2
+ class RequestObserver < Observer
3
+ def update(profile)
4
+ profile.split("\n").each_with_index do |line, index|
5
+ str = index == 0 ? "data: #{line}" : line
6
+ connection << "#{str}<br />"
7
+ end
8
+
9
+ connection << "\n\n"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Base structure
3
+ */
4
+
5
+ /* Move down content because we have a fixed navbar that is 50px tall */
6
+ body {
7
+ padding-top: 50px;
8
+ }
9
+
10
+
11
+ /*
12
+ * Global add-ons
13
+ */
14
+
15
+ .sub-header {
16
+ padding-bottom: 10px;
17
+ border-bottom: 1px solid #eee;
18
+ }
19
+
20
+ /*
21
+ * Top navigation
22
+ * Hide default border to remove 1px line.
23
+ */
24
+ .navbar-fixed-top {
25
+ border: 0;
26
+ }
27
+
28
+ /*
29
+ * Sidebar
30
+ */
31
+
32
+ /* Hide for mobile, show later */
33
+ .sidebar {
34
+ display: none;
35
+ }
36
+ @media (min-width: 768px) {
37
+ .sidebar {
38
+ position: fixed;
39
+ top: 51px;
40
+ bottom: 0;
41
+ left: 0;
42
+ z-index: 1000;
43
+ display: block;
44
+ padding: 20px;
45
+ overflow-x: hidden;
46
+ overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
47
+ background-color: #f5f5f5;
48
+ border-right: 1px solid #eee;
49
+ }
50
+ }
51
+
52
+ /* Sidebar navigation */
53
+ .nav-sidebar {
54
+ margin-right: -21px; /* 20px padding + 1px border */
55
+ margin-bottom: 20px;
56
+ margin-left: -20px;
57
+ }
58
+ .nav-sidebar > li > a {
59
+ padding-right: 20px;
60
+ padding-left: 20px;
61
+ }
62
+ .nav-sidebar > .active > a,
63
+ .nav-sidebar > .active > a:hover,
64
+ .nav-sidebar > .active > a:focus {
65
+ color: #fff;
66
+ background-color: #428bca;
67
+ }
68
+
69
+
70
+ /*
71
+ * Main content
72
+ */
73
+
74
+ .main {
75
+ padding: 20px;
76
+ }
77
+ @media (min-width: 768px) {
78
+ .main {
79
+ padding-right: 40px;
80
+ padding-left: 40px;
81
+ }
82
+ }
83
+ .main .page-header {
84
+ margin-top: 0;
85
+ }
86
+
87
+
88
+ /*
89
+ * Placeholder dashboard ideas
90
+ */
91
+
92
+ .placeholders {
93
+ margin-bottom: 30px;
94
+ text-align: center;
95
+ }
96
+ .placeholders h4 {
97
+ margin-bottom: 0;
98
+ }
99
+ .placeholder {
100
+ margin-bottom: 20px;
101
+ }
102
+ .placeholder img {
103
+ display: inline-block;
104
+ border-radius: 50%;
105
+ }
@@ -0,0 +1,152 @@
1
+ // Chart option
2
+
3
+ var options = {
4
+ legendTemplate : "<% for (var i=0; i<datasets.length; i++){%><li><span style=\"color:<%=datasets[i].strokeColor%>\">■</span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%>"
5
+ };
6
+
7
+ // GC count
8
+ var gc_chart_ctx = document.getElementById('gc_chart').getContext('2d');
9
+ var gc_chart = new Chart(gc_chart_ctx).Line({
10
+ labels: [],
11
+ datasets: [
12
+ {
13
+ label: 'minor GC count',
14
+ fillColor: 'rgba(220,220,220,0.2)',
15
+ strokeColor: 'rgba(220,220,220,1)',
16
+ pointColor: 'rgba(220,220,220,1)',
17
+ pointStrokeColor: '#fff',
18
+ pointHighlightFill: '#fff',
19
+ pointHighlightStroke: 'rgba(220,220,220,1)',
20
+ data: []
21
+ },
22
+ {
23
+ label: 'major GC Count',
24
+ fillColor: 'rgba(151,187,205,0.2)',
25
+ strokeColor: 'rgba(151,187,205,1)',
26
+ pointColor: 'rgba(151,187,205,1)',
27
+ pointStrokeColor: '#fff',
28
+ pointHighlightFill: '#fff',
29
+ pointHighlightStroke: 'rgba(151,187,205,1)',
30
+ data: []
31
+ }
32
+ ]
33
+ }, options);
34
+
35
+ document.getElementById('gc_legend').innerHTML = gc_chart.generateLegend();
36
+
37
+ // total object
38
+ var total_object_ctx = document.getElementById('total_object').getContext('2d');
39
+ var total_object_chart = new Chart(total_object_ctx).Line({
40
+ labels: [],
41
+ datasets: [
42
+ {
43
+ label: 'total allocated object',
44
+ fillColor: 'rgba(220,220,220,0.2)',
45
+ strokeColor: 'rgba(220,220,220,1)',
46
+ pointColor: 'rgba(220,220,220,1)',
47
+ pointStrokeColor: '#fff',
48
+ pointHighlightFill: '#fff',
49
+ pointHighlightStroke: 'rgba(220,220,220,1)',
50
+ data: []
51
+ },
52
+ {
53
+ label: 'total freed object',
54
+ fillColor: 'rgba(151,187,205,0.2)',
55
+ strokeColor: 'rgba(151,187,205,1)',
56
+ pointColor: 'rgba(151,187,205,1)',
57
+ pointStrokeColor: '#fff',
58
+ pointHighlightFill: '#fff',
59
+ pointHighlightStroke: 'rgba(151,187,205,1)',
60
+ data: []
61
+ }
62
+ ]
63
+ }, options);
64
+
65
+ document.getElementById('total_object_legend').innerHTML = total_object_chart.generateLegend();
66
+
67
+
68
+ // malloc increase
69
+ var malloc_increase_ctx = document.getElementById('malloc_increase').getContext('2d');
70
+ var malloc_increase_chart = new Chart(malloc_increase_ctx).Line({
71
+ labels: [],
72
+ datasets: [
73
+ {
74
+ label: 'malloc increase bytes limit',
75
+ fillColor: 'rgba(220,220,220,0.2)',
76
+ strokeColor: 'rgba(220,220,220,1)',
77
+ pointColor: 'rgba(220,220,220,1)',
78
+ pointStrokeColor: '#fff',
79
+ data: []
80
+ },
81
+ {
82
+ label: 'malloc increase bytes',
83
+ fillColor: 'rgba(151,187,205,0.2)',
84
+ strokeColor: 'rgba(151,187,205,1)',
85
+ pointColor: 'rgba(151,187,205,1)',
86
+ pointStrokeColor: '#fff',
87
+ data: []
88
+ }
89
+ ]
90
+ }, options);
91
+
92
+ document.getElementById('malloc_increase_legend').innerHTML = malloc_increase_chart.generateLegend();
93
+
94
+
95
+ // old objects
96
+ var old_objects_ctx = document.getElementById('old_objects').getContext('2d');
97
+ var old_objects_chart = new Chart(old_objects_ctx).Line({
98
+ labels: [],
99
+ datasets: [
100
+ {
101
+ label: 'old objects limit',
102
+ fillColor: 'rgba(220,220,220,0.2)',
103
+ strokeColor: 'rgba(220,220,220,1)',
104
+ pointColor: 'rgba(220,220,220,1)',
105
+ pointStrokeColor: '#fff',
106
+ data: []
107
+ },
108
+ {
109
+ label: 'old objects',
110
+ fillColor: 'rgba(151,187,205,0.2)',
111
+ strokeColor: 'rgba(151,187,205,1)',
112
+ pointColor: 'rgba(151,187,205,1)',
113
+ pointStrokeColor: '#fff',
114
+ data: []
115
+ }
116
+ ]
117
+ }, options);
118
+
119
+ document.getElementById('old_objects_legend').innerHTML = old_objects_chart.generateLegend();
120
+
121
+
122
+ // SSE
123
+ var source = new EventSource('/rackprof/gc_stream');
124
+
125
+ source.onmessage = function(event) {
126
+ data = JSON.parse(event.data);
127
+
128
+ var gc_data = [data.minor_gc_count, data.major_gc_count];
129
+ var total_object_data = [data.total_allocated_objects, data.total_freed_objects];
130
+ var malloc_increase_data = [data.malloc_increase_bytes_limit, data.malloc_increase_bytes];
131
+ var old_objects_data = [data.old_objects_limit, data.old_objects];
132
+
133
+ gc_chart.addData(gc_data, new Date().getSeconds());
134
+ if (gc_chart.datasets[0].points.length > 10) {
135
+ gc_chart.removeData();
136
+ }
137
+
138
+ total_object_chart.addData(total_object_data, new Date().getSeconds());
139
+ if (total_object_chart.datasets[0].points.length > 10) {
140
+ total_object_chart.removeData();
141
+ }
142
+
143
+ malloc_increase_chart.addData(malloc_increase_data, new Date().getSeconds());
144
+ if (malloc_increase_chart.datasets[0].points.length > 10) {
145
+ malloc_increase_chart.removeData();
146
+ }
147
+
148
+ old_objects_chart.addData(old_objects_data, new Date().getSeconds());
149
+ if (old_objects_chart.datasets[0].points.length > 10) {
150
+ old_objects_chart.removeData();
151
+ }
152
+ };