stackprof-webnav 0.0.3 → 1.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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +2 -0
  6. data/Procfile +1 -0
  7. data/README.md +27 -18
  8. data/Rakefile +24 -0
  9. data/bin/stackprof-webnav +8 -6
  10. data/lib/stackprof-webnav.rb +1 -1
  11. data/lib/stackprof-webnav/dump.rb +41 -0
  12. data/lib/stackprof-webnav/presenter.rb +1 -18
  13. data/lib/stackprof-webnav/public/css/application.css +18 -0
  14. data/lib/stackprof-webnav/{css → public/css}/code.css +0 -0
  15. data/lib/stackprof-webnav/{css → public/css}/foundation.min.css +0 -0
  16. data/lib/stackprof-webnav/{css → public/css}/normalize.css +0 -0
  17. data/lib/stackprof-webnav/public/flamegraph.js +983 -0
  18. data/lib/stackprof-webnav/server.rb +105 -69
  19. data/lib/stackprof-webnav/version.rb +1 -1
  20. data/lib/stackprof-webnav/views/error.haml +8 -0
  21. data/lib/stackprof-webnav/views/file.haml +7 -4
  22. data/lib/stackprof-webnav/views/flamegraph.haml +77 -0
  23. data/lib/stackprof-webnav/views/graph.haml +5 -0
  24. data/lib/stackprof-webnav/views/index.haml +18 -0
  25. data/lib/stackprof-webnav/views/invalid_dump.haml +4 -0
  26. data/lib/stackprof-webnav/views/layout.haml +5 -4
  27. data/lib/stackprof-webnav/views/method.haml +11 -8
  28. data/lib/stackprof-webnav/views/overview.haml +17 -3
  29. data/screenshots/callgraph.png +0 -0
  30. data/screenshots/directory.png +0 -0
  31. data/screenshots/file.png +0 -0
  32. data/screenshots/flamegraph.png +0 -0
  33. data/screenshots/method.png +0 -0
  34. data/screenshots/overview.png +0 -0
  35. data/spec/fixtures/test-raw.dump +0 -0
  36. data/spec/fixtures/test.dump +0 -0
  37. data/spec/helpers.rb +17 -0
  38. data/spec/integration_spec.rb +79 -0
  39. data/spec/spec_helper.rb +15 -0
  40. data/stackprof-webnav.gemspec +9 -5
  41. metadata +106 -28
  42. data/lib/stackprof-webnav/css/application.css +0 -13
  43. data/lib/stackprof-webnav/views/listing.haml +0 -21
  44. data/screenshots/main.png +0 -0
@@ -1,111 +1,147 @@
1
- require 'nyny'
1
+ require 'sinatra'
2
2
  require 'haml'
3
- require "stackprof"
4
- require 'sprockets/nyny'
5
- require 'net/http'
3
+ require 'stackprof'
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 :cmd_options, :report_dump_path, :report_dump_uri, :report_dump_listing
16
-
17
- def presenter regenerate=false
18
- return @presenter unless regenerate || @presenter.nil?
19
- process_options
20
- if self.report_dump_path || self.report_dump_uri
21
- report_contents = if report_dump_path.nil?
22
- Net::HTTP.get(URI.parse(report_dump_uri))
23
- else
24
- File.open(report_dump_path).read
25
- end
26
- report = StackProf::Report.new(Marshal.load(report_contents))
27
- end
28
- @presenter = Presenter.new(report)
29
- end
14
+ attr_accessor :cmd_options
15
+ end
16
+
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
30
29
 
31
- private
32
- def process_options
33
- if cmd_options[:filepath]
34
- self.report_dump_path = cmd_options[:filepath]
35
- elsif cmd_options[:uri]
36
- self.report_dump_uri = cmd_options[:uri]
37
- elsif cmd_options[:bucket]
38
- self.report_dump_listing = cmd_options[:bucket]
39
- end
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
40
35
  end
41
-
42
36
  end
43
37
 
44
38
  helpers do
45
- def template_path name
46
- File.join(__dir__, name)
39
+ def current_dump
40
+ Thread.current[:cache] = {}
41
+ Thread.current[params[:dump]] ||= Dump.new(params[:dump])
47
42
  end
48
43
 
49
- def render_with_layout *args
50
- args[0] = template_path("views/#{args[0]}.haml")
51
- render(template_path('views/layout.haml')) { render(*args) }
44
+ def current_report
45
+ StackProf::Report.new(
46
+ Marshal.load(current_dump.content)
47
+ )
52
48
  end
53
49
 
54
50
  def presenter
55
- Server.presenter
51
+ Presenter.new(current_report)
56
52
  end
57
53
 
58
- def method_url name
59
- "/method?name=#{URI.escape(name)}"
54
+ def ensure_file_generated(path, &block)
55
+ return if File.exist?(path)
56
+ File.open(path, 'wb', &block)
60
57
  end
61
58
 
62
- def file_url path
63
- "/file?path=#{URI.escape(path)}"
64
- end
65
-
66
- def overview_url path
67
- "/overview?path=#{URI.escape(path)}"
59
+ def url_for(path, options={})
60
+ query = URI.encode_www_form({dump: params[:dump]}.merge(options))
61
+ path + "?" + query
68
62
  end
69
63
  end
70
64
 
71
65
  get '/' do
72
- presenter
73
- if Server.report_dump_listing
74
- redirect_to '/listing'
75
- else
76
- redirect_to '/overview'
66
+ if Server.cmd_options[:filepath]
67
+ query = URI.encode_www_form(dump: Server.cmd_options[:filepath])
68
+ next redirect to("/overview?#{query}")
77
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
78
85
  end
79
86
 
80
87
  get '/overview' do
81
- if params[:path]
82
- Server.report_dump_uri = params[:path]
83
- Server.presenter(true)
84
- end
85
- @file = Server.report_dump_path || Server.report_dump_uri
86
88
  @action = "overview"
87
- @frames = presenter.overview_frames
88
- 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.svg' 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(svg: current_dump.graph_image_path)
115
+ end
116
+
117
+ send_file(current_dump.graph_image_path, type: 'image/svg+xml')
89
118
  end
90
119
 
91
- get '/listing' do
92
- @file = Server.report_dump_listing
93
- @action = "listing"
94
- @dumps = presenter.listing_dumps
95
- render_with_layout :listing
96
- end
120
+ get '/graph' do
121
+ haml :graph
122
+ end
123
+
124
+ get '/flamegraph' do
125
+ haml :flamegraph
126
+ end
97
127
 
98
128
  get '/method' do
99
129
  @action = params[:name]
100
130
  @frames = presenter.method_info(params[:name])
101
- render_with_layout :method
131
+ haml :method
102
132
  end
103
133
 
104
134
  get '/file' do
105
135
  path = params[:path]
106
- @path = path
107
- @data = presenter.file_overview(path)
108
- 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
109
145
  end
110
146
  end
111
147
  end
@@ -1,5 +1,5 @@
1
1
  module StackProf
2
2
  module Webnav
3
- VERSION = '0.0.3'
3
+ VERSION = '1.0.2'
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,5 @@
1
+ #graph
2
+ %a{:href => url_for("/overview")}
3
+ %button Back to overview
4
+ %div
5
+ %img{src: url_for("/graph.svg")}
@@ -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]