stackprof-remote 0.0.1

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: 9ba94890adf64d5a692a8de6970d625aa2a8ea98
4
+ data.tar.gz: 59002cb8994625f857fdef77c6bb2d935ca88e24
5
+ SHA512:
6
+ metadata.gz: 7e6149ad80253cfd9808610cffb054b9261a1a5406904ef0e6a66b9cd3161dd838c18f1f03d7268b3df1468dbdc7e3637f477dd085cd7ba4a66e1884b92a3f8f
7
+ data.tar.gz: 45b2de7669012da294af502b329428366fbc7545ac830866b4aafe232e9d49a167535dfa789636da46f94d4658d7e4efcad9e97a84a1446a48bf80d6a80c1ac5
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "rbtrace", "~>0.4.3"
6
+ gem "stackprof", "~>0.2.6"
7
+ gem "pry"
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "shoulda", ">= 0"
13
+ gem "rdoc", "~> 3.12"
14
+ gem "bundler", "~> 1.0"
15
+ gem "jeweler", "~> 2.0.1"
16
+ gem "simplecov", ">= 0"
17
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,98 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (4.1.1)
5
+ i18n (~> 0.6, >= 0.6.9)
6
+ json (~> 1.7, >= 1.7.7)
7
+ minitest (~> 5.1)
8
+ thread_safe (~> 0.1)
9
+ tzinfo (~> 1.1)
10
+ addressable (2.3.6)
11
+ builder (3.2.2)
12
+ coderay (1.1.0)
13
+ descendants_tracker (0.0.4)
14
+ thread_safe (~> 0.3, >= 0.3.1)
15
+ docile (1.1.4)
16
+ faraday (0.9.0)
17
+ multipart-post (>= 1.2, < 3)
18
+ ffi (1.9.3)
19
+ git (1.2.7)
20
+ github_api (0.11.3)
21
+ addressable (~> 2.3)
22
+ descendants_tracker (~> 0.0.1)
23
+ faraday (~> 0.8, < 0.10)
24
+ hashie (>= 1.2)
25
+ multi_json (>= 1.7.5, < 2.0)
26
+ nokogiri (~> 1.6.0)
27
+ oauth2
28
+ hashie (3.0.0)
29
+ highline (1.6.21)
30
+ i18n (0.6.9)
31
+ jeweler (2.0.1)
32
+ builder
33
+ bundler (>= 1.0)
34
+ git (>= 1.2.5)
35
+ github_api
36
+ highline (>= 1.6.15)
37
+ nokogiri (>= 1.5.10)
38
+ rake
39
+ rdoc
40
+ json (1.8.1)
41
+ jwt (1.0.0)
42
+ method_source (0.8.2)
43
+ mini_portile (0.6.0)
44
+ minitest (5.3.4)
45
+ msgpack (0.5.8)
46
+ multi_json (1.10.1)
47
+ multi_xml (0.5.5)
48
+ multipart-post (2.0.0)
49
+ nokogiri (1.6.2.1)
50
+ mini_portile (= 0.6.0)
51
+ oauth2 (0.9.4)
52
+ faraday (>= 0.8, < 0.10)
53
+ jwt (~> 1.0)
54
+ multi_json (~> 1.3)
55
+ multi_xml (~> 0.5)
56
+ rack (~> 1.2)
57
+ pry (0.10.0)
58
+ coderay (~> 1.1.0)
59
+ method_source (~> 0.8.1)
60
+ slop (~> 3.4)
61
+ rack (1.5.2)
62
+ rake (10.3.2)
63
+ rbtrace (0.4.3)
64
+ ffi (>= 1.0.6)
65
+ msgpack (>= 0.4.3)
66
+ trollop (>= 1.16.2)
67
+ rdoc (3.12.2)
68
+ json (~> 1.4)
69
+ shoulda (3.5.0)
70
+ shoulda-context (~> 1.0, >= 1.0.1)
71
+ shoulda-matchers (>= 1.4.1, < 3.0)
72
+ shoulda-context (1.2.1)
73
+ shoulda-matchers (2.6.1)
74
+ activesupport (>= 3.0.0)
75
+ simplecov (0.8.2)
76
+ docile (~> 1.1.0)
77
+ multi_json
78
+ simplecov-html (~> 0.8.0)
79
+ simplecov-html (0.8.0)
80
+ slop (3.5.0)
81
+ stackprof (0.2.6)
82
+ thread_safe (0.3.4)
83
+ trollop (2.0)
84
+ tzinfo (1.2.1)
85
+ thread_safe (~> 0.1)
86
+
87
+ PLATFORMS
88
+ ruby
89
+
90
+ DEPENDENCIES
91
+ bundler (~> 1.0)
92
+ jeweler (~> 2.0.1)
93
+ pry
94
+ rbtrace (~> 0.4.3)
95
+ rdoc (~> 3.12)
96
+ shoulda
97
+ simplecov
98
+ stackprof (~> 0.2.6)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Aaron Quint
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,92 @@
1
+ # stackprof-remote
2
+
3
+ A Middleware and CLI for fetching and interacting with [StackProf](https://github.com/tmm1/stackprof) dumps.
4
+
5
+ ## Description
6
+
7
+ stackprof-remote consists of a middleware for easy creation and retrieval of StackProf sampling profiler dumps from a remote machine, and a wrapper around pry (stackprof-cli) to create an interactive session for navigating dump files.
8
+
9
+ Currently, this is aimed at Rails apps running with unicorn, but there are options that should make it usable with any Rack app. In the future, I'd like to see it work with Resque and non-rack applications, too.
10
+
11
+ ## Why
12
+
13
+ StackProf is amazing (BIG UPS TO @TMM1) but is not very operator friendly when it comes to collecting data about a current process. I was inspired by the [`go tool pprof` process](http://golang.org/pkg/net/http/pprof/) to make something that could wrap StackProf in an interface that should be as easy as including a middleware and pointing a bin at it to fetch and navigate a dump.
14
+
15
+ ## Usage
16
+
17
+ 1 - Add the Middleware to your app.
18
+
19
+ ``` ruby
20
+ # rails 2.3 style
21
+ require 'stackprof/remote/middleware'
22
+
23
+ # Should we enable stackprof-remote for this request.
24
+ # enabled can be a boolean or a proc that takes the Rack env hash
25
+ enabled = proc do |env|
26
+ env['HOST_INFO'] =~ /private-hostname/ || Rails.env.development?
27
+ end
28
+ # Register the middleware
29
+ ActionController::Dispatcher.middleware.use StackProf::Remote::Middleware, enabled: enabled, logger: Rails.logger
30
+ ```
31
+
32
+ 2 - Run/restart your app.
33
+ 3 - Attach to your application.
34
+
35
+ ``` bash
36
+ $ stackprof-remote localhost
37
+ === StackProf on localhost ===
38
+ Starting
39
+ [localhost] StackProf Started
40
+ Waiting for 30 seconds
41
+ [localhost] Results: 3023kb
42
+ Saved results to /home/paperless/.sp/sp-localhost-1402684964.dump
43
+ >>> sp-localhost-1402684964.dump loaded
44
+ stackprof> top 5
45
+ ==================================
46
+ Mode: cpu(1000)
47
+ Samples: 5045 (3.28% miss rate)
48
+ GC: 355 (7.04%)
49
+ ==================================
50
+ TOTAL (pct) SAMPLES (pct) FRAME
51
+ 736 (14.6%) 707 (14.0%) ActiveSupport::LogSubscriber#start
52
+ 379 (7.5%) 379 (7.5%) block in ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#execute
53
+ 5248 (104.0%) 168 (3.3%) Benchmark#realtime
54
+ 282 (5.6%) 117 (2.3%) ActiveSupport::LogSubscriber#finish
55
+ 88 (1.7%) 88 (1.7%) block (2 levels) in Sass::Importers::Filesystem#find_real_file
56
+ ```
57
+
58
+ ## CLI
59
+
60
+ At the end of `stackprof-remote` it actually just enters a separate process `stackprof-cli`. This is a wrapper around [pry](https://github.com/pry/pry) that loads the dump file in an interactive session. It gives you a number of methods to interact with the dump:
61
+
62
+ * top N: show the top methods ordered by inner sample time.
63
+ * total N: show the top methods ordered by total time.
64
+ * all: Show all the methods ordered by sample time.
65
+ * method Name: show details about the callers and callees of Name
66
+
67
+ You can use `stackprof-cli` on its own by calling `stackprof-cli [dump-name]`
68
+
69
+ ## Notes/Caveats
70
+
71
+ - You should use `enabled` on the Middleware to lock this down in production environments.
72
+ - Collecting dumps uses [`rbtrace`](https://github.com/tmm1/rbtrace) to execute the stackprof methods against the pool of unicorns running. If you're running something other than `unicorn` or you mess with the procline, you'll need to set the `:pid_finder` option.
73
+ - In order to get line level code output when using the `method` view you need to execute `stackprof-cli` in the same directory structure that your unicorn runs in. This doesn't necessarily mean the same server - we use remote dumps and inspect them in our local Vagrant environments that have the same directory structure.
74
+
75
+ ## Requirements
76
+
77
+ Only works on MRI Ruby 2.1 (Upgrade already!). Its only been tested against Ruby 2.1.2 running on Linux (Centos 6.4).
78
+
79
+ ## Contributing to stackprof-remote
80
+
81
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
82
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
83
+ * Fork the project.
84
+ * Start a feature/bugfix branch.
85
+ * Commit and push until you are happy with your contribution.
86
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
87
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
88
+
89
+ ## Copyright
90
+
91
+ Copyright (c) 2014 Aaron Quint. See LICENSE.txt for further details.
92
+
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "stackprof-remote"
18
+ gem.homepage = "http://github.com/quirkey/stackprof-remote"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A Middleware and CLI for fetching and interacting with StackProf dumps}
21
+ gem.description = %Q{stackprof-remote consists of a middleware for easy creation and retreival of
22
+ stackprof sampling profiler dumps from a remote machine, and a wrapper around
23
+ pry (stackprof-cli) to create an interactive session for navigating stackprof
24
+ dumps.}
25
+ gem.email = "aaron@quirkey.com"
26
+ gem.authors = ["Aaron Quint"]
27
+ # dependencies defined in Gemfile
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ desc "Code coverage detail"
39
+ task :simplecov do
40
+ ENV['COVERAGE'] = "true"
41
+ Rake::Task['test'].execute
42
+ end
43
+
44
+ task :default => :test
45
+
46
+ require 'rdoc/task'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "stackprof-remote #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/stackprof-cli ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'stackprof/cli'
4
+
5
+ StackProf::CLI.start(ARGV.shift)
6
+
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # stackprof-remote
4
+ require 'stackprof/remote/client'
5
+
6
+ remote = StackProf::Remote::Client.new(ARGV.shift, ARGV.shift)
7
+ remote.run
8
+
9
+
@@ -0,0 +1,116 @@
1
+ require 'pry'
2
+ require 'stackprof/remote/process_report_collector'
3
+
4
+ module StackProf
5
+ # CLI is a simple wrapper around Pry that defines some helper
6
+ # methods for navigating stackprof dumps.
7
+ class CLI
8
+
9
+ class << self
10
+ # Set prompts and other defaults
11
+ def set_defaults
12
+ Pry.config.should_load_rc = false
13
+ Pry.config.prompt = proc {
14
+ "stackprof#{@current_report ? " (#{@current_report})" : ""}> "
15
+ }
16
+ end
17
+
18
+ # Add the helper methods to pry
19
+ def add_methods
20
+ session = Session.new
21
+ Pry::Commands.block_command "load-dump", "Load a stackprof dump at file" do |file|
22
+ session.with_context(self) {|s| s.load_dump(file) }
23
+ end
24
+ Pry::Commands.block_command "top", "print the top (n) results by sample time" do |limit|
25
+ session.with_context(self) {|s| s.top(limit) }
26
+ end
27
+ Pry::Commands.block_command "total", "print the top (n) results by total sample time" do |limit|
28
+ session.with_context(self) {|s| s.total(limit) }
29
+ end
30
+ Pry::Commands.block_command "all", "print all results by sample time" do
31
+ session.with_context(self) {|s| s.all }
32
+ end
33
+ Pry::Commands.block_command "method", "scope results to matching matching methods" do |method|
34
+ session.with_context(self) {|s| s.print_method(method) }
35
+ end
36
+ end
37
+
38
+ # Start a Pry session with an optional file
39
+ def start(file, options = {})
40
+ set_defaults
41
+ add_methods
42
+ initial = file ? StringIO.new("load-dump #{file}") : nil
43
+ Pry.start(nil, :input => initial)
44
+ end
45
+ end
46
+
47
+ class Session
48
+ attr_reader :ctx
49
+
50
+ # Load a dump into a StackProf::Report object.
51
+ def load_dump(file)
52
+ data = File.read(file)
53
+ @report = StackProf::Remote::ProcessReportCollector.report_from_marshaled_results(data)
54
+ @current_report = File.basename(file)
55
+ puts ">>> #{@current_report} loaded"
56
+ end
57
+
58
+ # Print the top `limit` methods by sample time
59
+ def top(limit = 10)
60
+ check_for_report
61
+ @report.print_text(false, limit.to_i, ctx.output)
62
+ end
63
+
64
+ # Print the top `limit` methods by total time
65
+ def total(limit = 10)
66
+ check_for_report
67
+ @report.print_text(true, limit.to_i, ctx.output)
68
+ end
69
+
70
+ # Print all the methods by sample time. Paged.
71
+ def all
72
+ check_for_report
73
+ page do |out|
74
+ @report.print_text(false, nil, out)
75
+ end
76
+ end
77
+
78
+ # Print callers/callees of methods matching method. Paged.
79
+ def print_method(method)
80
+ check_for_report
81
+ page do |out|
82
+ @report.print_method(method, out)
83
+ end
84
+ end
85
+
86
+ # Simple check to see if a report has been loaded.
87
+ def check_for_report
88
+ if !@report
89
+ puts "You have to load a dump first with load-dump"
90
+ return
91
+ end
92
+ end
93
+
94
+ # Wrap the execution of a method with a Pry context
95
+ def with_context(ctx, &block)
96
+ @ctx = ctx
97
+ res = yield self
98
+ @ctx = nil
99
+ res
100
+ end
101
+
102
+ # Helper to delegate puts to the current context
103
+ def puts(*args)
104
+ ctx.output.puts(*args)
105
+ end
106
+
107
+ # Wrap the output in pry's pager (less)
108
+ def page(&block)
109
+ out = StringIO.new
110
+ yield out
111
+ ctx._pry_.pager.page out.string
112
+ end
113
+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,72 @@
1
+ require 'net/http'
2
+ require 'fileutils'
3
+ require 'stackprof/cli'
4
+
5
+ module StackProf
6
+ module Remote
7
+ # Client wraps the script that uses net/http to make the start/stop
8
+ # requests to a host running the StackProf::Remote::Middleware
9
+ class Client
10
+ attr_reader :host
11
+
12
+ def initialize(host, wait)
13
+ @host = host
14
+ @wait = (wait || 30).to_i
15
+ check_host
16
+ end
17
+
18
+ def run
19
+ start
20
+ wait
21
+ fetch_results
22
+ save_results
23
+ enter_console
24
+ end
25
+
26
+ def start
27
+ puts "=== StackProf on #{host} ==="
28
+ puts "Starting"
29
+ result = Net::HTTP.get(host, "/__stackprof__/start")
30
+ puts "[#{host}] #{result}"
31
+ if result !~ /Started/
32
+ raise "Did not start successfully"
33
+ end
34
+ end
35
+
36
+ def wait
37
+ puts "Waiting for #{@wait} seconds"
38
+ sleep @wait
39
+ end
40
+
41
+ def fetch_results
42
+ @results = Net::HTTP.get(host, "/__stackprof__/stop")
43
+ puts "[#{host}] Results: #{@results.bytesize / 1024}kb"
44
+ if !@results
45
+ raise "Could not retreive results"
46
+ end
47
+ end
48
+
49
+ def result_path
50
+ result_dir = File.expand_path('~/.sp')
51
+ FileUtils.mkdir_p(result_dir)
52
+ @result_path ||= File.expand_path(File.join(result_dir, "sp-#{@host}-#{Time.now.to_i}.dump"))
53
+ end
54
+
55
+ def save_results
56
+ File.open(result_path, 'wb') {|f| f << @results }
57
+ puts "Saved results to #{result_path}"
58
+ end
59
+
60
+ def enter_console
61
+ StackProf::CLI.start(result_path)
62
+ end
63
+
64
+ private
65
+ def check_host
66
+ if !host || !URI.parse(host)
67
+ raise "Please supply a valid host to connect to (#{host})"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,88 @@
1
+ require 'fileutils'
2
+ require 'stackprof'
3
+
4
+ module StackProf
5
+ module Remote
6
+ # Middleware is a simple Rack middleware that handles requests to
7
+ # urls matching /__stackprof__ for starting/stopping a profile
8
+ # session and retreiving the dump files. It delegates to the
9
+ # ProcessReportCollector to do the actual work of collecting
10
+ # and combining the dumps.
11
+ class Middleware
12
+ class << self
13
+ attr_accessor :enabled, :logger, :options
14
+
15
+ def enabled?(env)
16
+ if enabled.respond_to?(:call)
17
+ enabled.call(env)
18
+ else
19
+ enabled
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(app, options = {})
25
+ @app = app
26
+ self.class.logger = options.delete(:logger) || Logger.new(STDOUT)
27
+ self.class.options = options
28
+ logger.info "[stackprof] Stackprof Middleware enabled"
29
+ end
30
+
31
+ def call(env)
32
+ path = env['PATH_INFO']
33
+ if in_stackprof?(path)
34
+ handle_stackprof(path)
35
+ else
36
+ @app.call(env)
37
+ end
38
+ end
39
+
40
+ private
41
+ def logger
42
+ self.class.logger
43
+ end
44
+
45
+ def in_stackprof?(path)
46
+ path =~ /^\/__stackprof__/
47
+ end
48
+
49
+ def handle_stackprof(path)
50
+ sp = StackProf::Remote::ProcessReportCollector.new(options)
51
+ if path =~ /start/
52
+ logger.debug "[stackprof] Starting StackProf"
53
+ sp.start
54
+ [200, {'Content-Type' => 'text/plain'}, ["StackProf Started"]]
55
+ elsif path =~ /stop/
56
+ logger.debug "[stackprof] Flushing StackProf"
57
+ sp.stop
58
+ sp.save
59
+ if results = sp.marshaled_results
60
+ [200, {'Content-Type' => 'binary/octet-stream'}, [results]]
61
+ else
62
+ [404, {'Content-Type' => 'text/plain'}, ["404 StackProf Results Not Found"]]
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ module ReportSaver
69
+ def self.marshaled_results
70
+ if results = StackProf.results
71
+ Marshal.dump(results)
72
+ end
73
+ end
74
+
75
+ def self.save(base_path)
76
+ if results = marshaled_results
77
+ FileUtils.mkdir_p(base_path)
78
+ filename = "stackprof-#{Process.pid}-#{Time.now.to_i}.dump"
79
+ path = File.expand_path(File.join(base_path, filename))
80
+ File.open(path, 'wb') do |f|
81
+ f.write results
82
+ end
83
+ path
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,86 @@
1
+ require 'stackprof/report'
2
+ require 'rbtrace/rbtracer'
3
+
4
+ module StackProf
5
+ module Remote
6
+ # ProcessReportCollector handles the work of actually starting,
7
+ # stopping, and collecting the dumps from the StackProf profiler.
8
+ #
9
+ # Internally it uses RBTrace to execute the start/stop methods
10
+ # against all runnign processes that match pids found by the :pid_finder
11
+ # option. By default this matches unicorn workers.
12
+ class ProcessReportCollector
13
+ DEFAULT_OPTIONS = {
14
+ :pid_finder => -> {
15
+ `pgrep -f 'unicorn worker'`.strip.split.collect {|p| p.to_i }
16
+ },
17
+ :mode => :cpu,
18
+ :interval => 1000,
19
+ :raw => true,
20
+ :path => 'tmp'
21
+ }.freeze
22
+
23
+ def initialize(options = {})
24
+ @options = DEFAULT_OPTIONS.merge(options)
25
+ collect_pids
26
+ end
27
+
28
+ def logger
29
+ StackProf::Remote::Middleware.logger
30
+ end
31
+
32
+ def start
33
+ command = "StackProf.start(mode: #{@options[:mode].inspect}, interval: #{@options[:interval].inspect}, raw: #{@options[:raw].inspect})"
34
+ execute(command)
35
+ end
36
+
37
+ def stop
38
+ command = "StackProf.stop"
39
+ execute(command)
40
+ end
41
+
42
+ def save
43
+ command = "StackProf::Remote::ReportSaver.save('#{@options[:path]}')"
44
+ @saved_files = execute(command)
45
+ end
46
+
47
+ def marshaled_results
48
+ if @saved_files
49
+ saved_data = @saved_files.collect {|f|
50
+ Marshal.load(File.read(f))
51
+ }
52
+ Marshal.dump(saved_data)
53
+ end
54
+ end
55
+
56
+ def self.report_from_marshaled_results(marshaled_data)
57
+ data = Marshal.load(marshaled_data)
58
+ report = data.inject(nil) {|sum, d| sum ? StackProf::Report.new(d) + sum : StackProf::Report.new(d) }
59
+ end
60
+
61
+ private
62
+ def collect_pids
63
+ logger.debug "[stackprof] Collecting PIDs"
64
+ @pids = @options[:pid_finder].call
65
+ @pids -= [Process.pid]
66
+ logger.debug "[stackprof] Found PIDs #{@pids.inspect} and current #{Process.pid}"
67
+ end
68
+
69
+ def execute(command)
70
+ logger.debug "[stackprof] execute: #{command}"
71
+ results = @pids.collect do |pid|
72
+ begin
73
+ tracer = RBTracer.new(pid)
74
+ output = tracer.eval(command)
75
+ ensure
76
+ tracer.detach
77
+ output
78
+ end
79
+ end
80
+ results << eval(command)
81
+ logger.debug "[stackprof] Results: #{results.inspect}"
82
+ results
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,2 @@
1
+ require 'stackprof/remote/middleware'
2
+ require 'stackprof/remote/client'
@@ -0,0 +1,79 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: stackprof-remote 0.0.1 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "stackprof-remote"
9
+ s.version = "0.0.1"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Aaron Quint"]
14
+ s.date = "2014-06-13"
15
+ s.description = "stackprof-remote consists of a middleware for easy creation and retreival of\n stackprof sampling profiler dumps from a remote machine, and a wrapper around\n pry (stackprof-cli) to create an interactive session for navigating stackprof\n dumps."
16
+ s.email = "aaron@quirkey.com"
17
+ s.executables = ["stackprof-cli", "stackprof-remote"]
18
+ s.extra_rdoc_files = [
19
+ "LICENSE.txt",
20
+ "README.md"
21
+ ]
22
+ s.files = [
23
+ ".document",
24
+ "Gemfile",
25
+ "Gemfile.lock",
26
+ "LICENSE.txt",
27
+ "README.md",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "bin/stackprof-cli",
31
+ "bin/stackprof-remote",
32
+ "lib/stackprof-remote.rb",
33
+ "lib/stackprof/cli.rb",
34
+ "lib/stackprof/remote/client.rb",
35
+ "lib/stackprof/remote/middleware.rb",
36
+ "lib/stackprof/remote/process_report_collector.rb",
37
+ "stackprof-remote.gemspec",
38
+ "test/helper.rb",
39
+ "test/test_stackprof-remote.rb"
40
+ ]
41
+ s.homepage = "http://github.com/quirkey/stackprof-remote"
42
+ s.licenses = ["MIT"]
43
+ s.rubygems_version = "2.2.1"
44
+ s.summary = "A Middleware and CLI for fetching and interacting with StackProf dumps"
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 4
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<rbtrace>, ["~> 0.4.3"])
51
+ s.add_runtime_dependency(%q<stackprof>, ["~> 0.2.6"])
52
+ s.add_runtime_dependency(%q<pry>, [">= 0"])
53
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
54
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
55
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
56
+ s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
57
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
58
+ else
59
+ s.add_dependency(%q<rbtrace>, ["~> 0.4.3"])
60
+ s.add_dependency(%q<stackprof>, ["~> 0.2.6"])
61
+ s.add_dependency(%q<pry>, [">= 0"])
62
+ s.add_dependency(%q<shoulda>, [">= 0"])
63
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
64
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
65
+ s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
66
+ s.add_dependency(%q<simplecov>, [">= 0"])
67
+ end
68
+ else
69
+ s.add_dependency(%q<rbtrace>, ["~> 0.4.3"])
70
+ s.add_dependency(%q<stackprof>, ["~> 0.2.6"])
71
+ s.add_dependency(%q<pry>, [">= 0"])
72
+ s.add_dependency(%q<shoulda>, [">= 0"])
73
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
74
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
75
+ s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
76
+ s.add_dependency(%q<simplecov>, [">= 0"])
77
+ end
78
+ end
79
+
data/test/helper.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'simplecov'
2
+
3
+ module SimpleCov::Configuration
4
+ def clean_filters
5
+ @filters = []
6
+ end
7
+ end
8
+
9
+ SimpleCov.configure do
10
+ clean_filters
11
+ load_adapter 'test_frameworks'
12
+ end
13
+
14
+ ENV["COVERAGE"] && SimpleCov.start do
15
+ add_filter "/.rvm/"
16
+ end
17
+ require 'rubygems'
18
+ require 'bundler'
19
+ begin
20
+ Bundler.setup(:default, :development)
21
+ rescue Bundler::BundlerError => e
22
+ $stderr.puts e.message
23
+ $stderr.puts "Run `bundle install` to install missing gems"
24
+ exit e.status_code
25
+ end
26
+ require 'test/unit'
27
+ require 'shoulda'
28
+
29
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
30
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
31
+ require 'stackprof-remote'
32
+
33
+ class Test::Unit::TestCase
34
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestStackprofRemote < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stackprof-remote
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Quint
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbtrace
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: stackprof
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.6
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.6
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: shoulda
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rdoc
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.12'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.12'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: jeweler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.0.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.0.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: |-
126
+ stackprof-remote consists of a middleware for easy creation and retreival of
127
+ stackprof sampling profiler dumps from a remote machine, and a wrapper around
128
+ pry (stackprof-cli) to create an interactive session for navigating stackprof
129
+ dumps.
130
+ email: aaron@quirkey.com
131
+ executables:
132
+ - stackprof-cli
133
+ - stackprof-remote
134
+ extensions: []
135
+ extra_rdoc_files:
136
+ - LICENSE.txt
137
+ - README.md
138
+ files:
139
+ - ".document"
140
+ - Gemfile
141
+ - Gemfile.lock
142
+ - LICENSE.txt
143
+ - README.md
144
+ - Rakefile
145
+ - VERSION
146
+ - bin/stackprof-cli
147
+ - bin/stackprof-remote
148
+ - lib/stackprof-remote.rb
149
+ - lib/stackprof/cli.rb
150
+ - lib/stackprof/remote/client.rb
151
+ - lib/stackprof/remote/middleware.rb
152
+ - lib/stackprof/remote/process_report_collector.rb
153
+ - stackprof-remote.gemspec
154
+ - test/helper.rb
155
+ - test/test_stackprof-remote.rb
156
+ homepage: http://github.com/quirkey/stackprof-remote
157
+ licenses:
158
+ - MIT
159
+ metadata: {}
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 2.2.1
177
+ signing_key:
178
+ specification_version: 4
179
+ summary: A Middleware and CLI for fetching and interacting with StackProf dumps
180
+ test_files: []