stackprof-webnav 0.0.2 → 1.0.1

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.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.travis.yml +4 -0
  4. data/Gemfile +2 -0
  5. data/Procfile +1 -0
  6. data/README.md +27 -16
  7. data/Rakefile +24 -0
  8. data/bin/stackprof-webnav +8 -10
  9. data/lib/stackprof-webnav/dump.rb +41 -0
  10. data/lib/stackprof-webnav/presenter.rb +2 -1
  11. data/lib/stackprof-webnav/public/css/application.css +7 -0
  12. data/lib/stackprof-webnav/{css → public/css}/code.css +0 -0
  13. data/lib/stackprof-webnav/{css → public/css}/foundation.min.css +0 -0
  14. data/lib/stackprof-webnav/{css → public/css}/normalize.css +0 -0
  15. data/lib/stackprof-webnav/public/flamegraph.js +983 -0
  16. data/lib/stackprof-webnav/server.rb +109 -35
  17. data/lib/stackprof-webnav/version.rb +1 -1
  18. data/lib/stackprof-webnav/views/error.haml +8 -0
  19. data/lib/stackprof-webnav/views/file.haml +7 -4
  20. data/lib/stackprof-webnav/views/flamegraph.haml +77 -0
  21. data/lib/stackprof-webnav/views/graph.haml +4 -0
  22. data/lib/stackprof-webnav/views/index.haml +18 -0
  23. data/lib/stackprof-webnav/views/invalid_dump.haml +4 -0
  24. data/lib/stackprof-webnav/views/layout.haml +5 -4
  25. data/lib/stackprof-webnav/views/method.haml +11 -8
  26. data/lib/stackprof-webnav/views/overview.haml +17 -3
  27. data/screenshots/callgraph.png +0 -0
  28. data/screenshots/directory.png +0 -0
  29. data/screenshots/file.png +0 -0
  30. data/screenshots/flamegraph.png +0 -0
  31. data/screenshots/method.png +0 -0
  32. data/screenshots/overview.png +0 -0
  33. data/spec/fixtures/test-raw.dump +0 -0
  34. data/spec/fixtures/test.dump +0 -0
  35. data/spec/helpers.rb +17 -0
  36. data/spec/integration_spec.rb +79 -0
  37. data/spec/spec_helper.rb +15 -0
  38. data/stackprof-webnav.gemspec +8 -4
  39. metadata +104 -25
  40. data/lib/stackprof-webnav/css/application.css +0 -13
  41. data/screenshots/main.png +0 -0
@@ -1,73 +1,147 @@
1
- require 'nyny'
1
+ require 'sinatra'
2
2
  require 'haml'
3
3
  require "stackprof"
4
- require 'sprockets/nyny'
5
- require 'net/http'
6
4
  require_relative 'presenter'
5
+ require_relative 'dump'
6
+ require 'pry'
7
+ require "sinatra/reloader" if development?
8
+ require 'ruby-graphviz'
7
9
 
8
10
  module StackProf
9
11
  module Webnav
10
- class Server < NYNY::App
11
- register Sprockets::NYNY
12
- config.assets.paths << File.join(__dir__, 'css')
13
-
12
+ class Server < Sinatra::Application
14
13
  class << self
15
- attr_accessor :report_dump_path, :report_dump_url
14
+ attr_accessor :cmd_options
15
+ end
16
16
 
17
- def presenter
18
- return @presenter unless @presenter.nil?
19
- content = if report_dump_path.nil?
20
- Net::HTTP.get(URI.parse(report_dump_url))
21
- else
22
- File.open(report_dump_path).read
23
- end
24
-
25
- report = StackProf::Report.new(Marshal.load(content))
26
- @presenter ||= Presenter.new(report)
17
+ configure :development do
18
+ register Sinatra::Reloader
19
+ end
20
+
21
+ configure :production do
22
+ set :show_exceptions, false
23
+ end
24
+
25
+ error do
26
+ @error = env['sinatra.error']
27
+ haml :error
28
+ end
29
+
30
+ before do
31
+ unless request.path_info == '/'
32
+ if params[:dump] && params[:dump] != current_dump.path
33
+ current_dump.path = params[:dump]
34
+ end
27
35
  end
28
36
  end
29
37
 
30
38
  helpers do
31
- def template_path name
32
- File.join(__dir__, name)
39
+ def current_dump
40
+ Thread.current[:cache] = {}
41
+ Thread.current[params[:dump]] ||= Dump.new(params[:dump])
33
42
  end
34
43
 
35
- def render_with_layout *args
36
- args[0] = template_path("views/#{args[0]}.haml")
37
- render(template_path('views/layout.haml')) { render(*args) }
44
+ def current_report
45
+ StackProf::Report.new(
46
+ Marshal.load(current_dump.content)
47
+ )
38
48
  end
39
49
 
40
50
  def presenter
41
- Server.presenter
51
+ Presenter.new(current_report)
42
52
  end
43
53
 
44
- def method_url name
45
- "/method?name=#{URI.escape(name)}"
54
+ def ensure_file_generated(path, &block)
55
+ return if File.exist?(path)
56
+ File.open(path, 'wb', &block)
46
57
  end
47
58
 
48
- def file_url path
49
- "/file?path=#{URI.escape(path)}"
59
+ def url_for(path, options={})
60
+ query = URI.encode_www_form({dump: params[:dump]}.merge(options))
61
+ path + "?" + query
50
62
  end
51
63
  end
52
64
 
53
65
  get '/' do
54
- @file = Server.report_dump_path || Server.report_dump_url
66
+ if Server.cmd_options[:filepath]
67
+ query = URI.encode_www_form(dump: Server.cmd_options[:filepath])
68
+ next redirect to("/overview?#{query}")
69
+ end
70
+
71
+ @directory = File.expand_path(Server.cmd_options[:directory] || '.')
72
+ @files = Dir.entries(@directory).sort.map do |file|
73
+ path = File.expand_path(file, @directory)
74
+
75
+ OpenStruct.new(
76
+ name: file,
77
+ path: path,
78
+ modified: File.mtime(path)
79
+ )
80
+ end.select do |file|
81
+ File.file?(file.path)
82
+ end
83
+
84
+ haml :index
85
+ end
86
+
87
+ get '/overview' do
55
88
  @action = "overview"
56
- @frames = presenter.overview_frames
57
- render_with_layout :overview
89
+
90
+ begin
91
+ @frames = presenter.overview_frames
92
+ haml :overview
93
+ rescue => error
94
+ haml :invalid_dump
95
+ end
96
+ end
97
+
98
+ get '/flames.json' do
99
+ ensure_file_generated(current_dump.flame_graph_path) do |file|
100
+ current_report.print_flamegraph(file, true, true)
101
+ end
102
+
103
+ send_file(current_dump.flame_graph_path, type: 'text/javascript')
104
+ end
105
+
106
+ get '/graph.png' do
107
+ ensure_file_generated(current_dump.graph_path) do |file|
108
+ current_report.print_graphviz({}, file)
109
+ end
110
+
111
+ ensure_file_generated(current_dump.graph_image_path) do |file|
112
+ GraphViz
113
+ .parse(current_dump.graph_path)
114
+ .output(png: current_dump.graph_image_path)
115
+ end
116
+
117
+ send_file(current_dump.graph_image_path, type: 'image/png')
118
+ end
119
+
120
+ get '/graph' do
121
+ haml :graph
122
+ end
123
+
124
+ get '/flamegraph' do
125
+ haml :flamegraph
58
126
  end
59
127
 
60
128
  get '/method' do
61
129
  @action = params[:name]
62
130
  @frames = presenter.method_info(params[:name])
63
- render_with_layout :method
131
+ haml :method
64
132
  end
65
133
 
66
134
  get '/file' do
67
135
  path = params[:path]
68
- @path = path
69
- @data = presenter.file_overview(path)
70
- render_with_layout :file
136
+
137
+ if File.exist?(path)
138
+ @path = path
139
+ @data = presenter.file_overview(path)
140
+ else
141
+ @data = nil
142
+ end
143
+
144
+ haml :file
71
145
  end
72
146
  end
73
147
  end
@@ -1,5 +1,5 @@
1
1
  module StackProf
2
2
  module Webnav
3
- VERSION = '0.0.2'
3
+ VERSION = '1.0.1'
4
4
  end
5
5
  end
@@ -0,0 +1,8 @@
1
+ %h1 Oops, something went wrong
2
+
3
+ %h2
4
+ = @error.class
5
+ \-
6
+ = @error.message
7
+
8
+ %h3 Are you looking at a valid stackprof dump?
@@ -5,7 +5,10 @@
5
5
  %br
6
6
  %br
7
7
 
8
- .code_linenums
9
- - @data[:lineinfo].each do |l|
10
- %span= l
11
- != @data[:code]
8
+ - if @data
9
+ .code_linenums
10
+ - @data[:lineinfo].each do |l|
11
+ %span= l
12
+ != @data[:code]
13
+ - else
14
+ %h3 Unable to load file, was the dump captured on another machine?
@@ -0,0 +1,77 @@
1
+ %a{:href => url_for("/overview")}
2
+ %button Back to overview
3
+
4
+ %br
5
+ %br
6
+
7
+ :css
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
12
+ font-size: 10pt;
13
+ }
14
+ .overview-container {
15
+ position: relative;
16
+ }
17
+ .overview {
18
+ cursor: col-resize;
19
+ }
20
+ .overview-viewport-overlay {
21
+ position: absolute;
22
+ top: 0;
23
+ left: 0;
24
+ width: 1;
25
+ height: 1;
26
+ background-color: rgba(0, 0, 0, 0.25);
27
+ transform-origin: top left;
28
+ cursor: -moz-grab;
29
+ cursor: -webkit-grab;
30
+ cursor: grab;
31
+ }
32
+ .moving {
33
+ cursor: -moz-grabbing;
34
+ cursor: -webkit-grabbing;
35
+ cursor: grabbing;
36
+ }
37
+ .info {
38
+ display: block;
39
+ height: 40px;
40
+ margin: 3px 6px;
41
+ margin-right: 206px;
42
+ padding: 3px 6px;
43
+ line-height: 18px;
44
+ }
45
+ .legend {
46
+ display: block;
47
+ float: right;
48
+ width: 195px;
49
+ max-height: 100%;
50
+ overflow-y: scroll;
51
+ }
52
+ .legend > div {
53
+ padding: 6px;
54
+ clear: right;
55
+ }
56
+ .legend > div span {
57
+ opacity: 0.75;
58
+ display: block;
59
+ text-align: right;
60
+ }
61
+ .legend > div .name {
62
+ max-width: 70%;
63
+ word-wrap: break-word;
64
+ }
65
+ %script{:src => "flamegraph.js"}
66
+ .legend
67
+ .overview-container
68
+ %canvas.overview
69
+ .overview-viewport-overlay
70
+ .info
71
+ %div{:style => "float: right; text-align: right"}
72
+ .samples
73
+ .exclusive
74
+ .frame
75
+ .file
76
+ %canvas.flamegraph
77
+ %script{:src => "/flames.json?dump=#{params[:dump]}"}
@@ -0,0 +1,4 @@
1
+ %a{:href => url_for("/overview")}
2
+ %button Back to overview
3
+
4
+ %img{src: url_for("/graph.png")}
@@ -0,0 +1,18 @@
1
+ %h3 StackProf Navigator
2
+ %hr
3
+
4
+ %p
5
+ Listing dumps in
6
+ %b= @directory
7
+
8
+ %table.centered
9
+ %thead
10
+ %th Filename
11
+ %th Modified
12
+
13
+ %tbody
14
+ - @files.each do |file|
15
+ %tr
16
+ %td
17
+ %a{href: url_for("/overview", dump: file.path)}= file.name
18
+ %td= file.modified
@@ -0,0 +1,4 @@
1
+ %h3 StackProf Navigator - #{@action}
2
+ %hr
3
+
4
+ %h4 Unable to open the specified file as a dump
@@ -1,9 +1,10 @@
1
1
  %html
2
2
  %head
3
3
  %title Stackprof navigator
4
- %link(rel="stylesheet" href="/assets/application.css")
4
+ %link(rel="stylesheet" href="/css/normalize.css")
5
+ %link(rel="stylesheet" href="/css/foundation.min.css")
6
+ %link(rel="stylesheet" href="/css/code.css")
7
+ %link(rel="stylesheet" href="/css/application.css")
5
8
 
6
9
  %body
7
- .row
8
- .large-12.columns
9
- = yield
10
+ = yield
@@ -1,12 +1,15 @@
1
1
  %h3 StackProf Navigator - #{@action} (#{@frames.count} frames)
2
2
  %hr
3
3
 
4
- %a{:href => '/'} &#8592; Back to overview
5
- %br
4
+ %p
5
+ %a{:href => url_for("/overview")} &#8592; Back to overview
6
6
 
7
7
  - @frames.each do |frame|
8
- %h4
9
- %a{:href => file_url(frame[:location])}= frame[:location]
8
+ %a{:href => url_for("/file", path: frame[:location])}
9
+ %button.btn
10
+ Browse
11
+ = frame[:location]
12
+
10
13
  - if frame[:callers].any?
11
14
  %table
12
15
  %thead
@@ -21,8 +24,8 @@
21
24
  %td= caller[:weight]
22
25
  %td= caller[:pct]
23
26
  %td
24
- %a{:href => method_url(caller[:method])}
25
- = caller[:method]
27
+ %a{:href => url_for("/method", name: caller[:method])}
28
+ &= caller[:method]
26
29
 
27
30
  - if frame[:callees].any?
28
31
  %table
@@ -38,8 +41,8 @@
38
41
  %td= caller[:weight]
39
42
  %td= caller[:pct]
40
43
  %td
41
- %a{:href => method_url(caller[:method])}
42
- = caller[:method]
44
+ %a{:href => url_for("/method", name: caller[:method])}
45
+ &= caller[:method]
43
46
 
44
47
  %h4 Code
45
48
  != frame[:source]
@@ -3,7 +3,21 @@
3
3
 
4
4
  %p
5
5
  Viewing dump
6
- %b= @file
6
+ %b= current_dump.path
7
+
8
+ %a{href: "/"}
9
+ %button.btn Browse directory
10
+
11
+
12
+ %a{href: url_for("/graph")}
13
+ %button.btn View call graph
14
+
15
+ - if current_report.data[:raw]
16
+ %a{href: url_for("/flamegraph")}
17
+ %button View flamegraph
18
+ - else
19
+ %a{href: "https://github.com/tmm1/stackprof#all-options"}
20
+ %button.secondary Flamegraph is not available
7
21
 
8
22
  %table.centered
9
23
  %thead
@@ -21,5 +35,5 @@
21
35
  %td= frame[:samples]
22
36
  %td= frame[:samples_pct]
23
37
  %td
24
- %a{:href => method_url(frame[:method])}
25
- = frame[:method]
38
+ %a{:href => url_for("/method", name: frame[:method])}
39
+ &= frame[:method]
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ require 'rack/test'
2
+
3
+ module Helpers
4
+ def build_app(options={})
5
+ StackProf::Webnav::Server.cmd_options = options
6
+ Rack::Test::Session.new(Rack::MockSession.new(StackProf::Webnav::Server))
7
+ end
8
+
9
+ def fixture_path(name)
10
+ File.join(File.dirname(__FILE__), "fixtures", name)
11
+ end
12
+
13
+ def build_presenter(path)
14
+ report = StackProf::Report.new(Marshal.load(File.read(path)))
15
+ StackProf::Webnav::Presenter.new(report)
16
+ end
17
+ end