stackprof-webnav 0.0.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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