Skip to content

Commit a045d8f

Browse files
committed
Implement line profiler and rack profiler
1 parent 4c2de49 commit a045d8f

13 files changed

+236
-0
lines changed

.rubocop.yml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ AllCops:
66
- '**/*.gemspec'
77
Exclude:
88
- 'bin/**/*'
9+
- 'example/**/*'
910
- 'vendor/bundle/**/*'
1011
- 'node_modules/**/*'
1112
TargetRubyVersion: 2.3

lib/rack/speed_gun.rb

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
require 'rack'
2+
require 'speed_gun'
3+
require 'speed_gun/profiler/rack_profiler'
4+
require 'speed_gun/profiler/line_profiler'
5+
6+
class Rack::SpeedGun
7+
def initialize(app)
8+
@app = app
9+
end
10+
11+
def call(env)
12+
return @app.call(env) unless SpeedGun.config.enabled?
13+
14+
SpeedGun.current_report = SpeedGun::Report.new
15+
16+
SpeedGun::Profiler::RackProfiler.profile(env['PATH_INFO'], rack: rack_info(env), request: request_info(env)) do |event|
17+
res = SpeedGun::Profiler::LineProfiler.profile { @app.call(env) }
18+
event.payload[:response] = {
19+
status: res[0],
20+
headers: res[1]
21+
}
22+
res
23+
end
24+
ensure
25+
#p SpeedGun.current_report
26+
SpeedGun.current_report = nil
27+
end
28+
29+
private
30+
31+
def rack_info(env)
32+
{
33+
version: env['rack.version'],
34+
url_scheme: env['rack.url_scheme']
35+
}
36+
end
37+
38+
def request_info(env)
39+
{
40+
method: env['REQUEST_METHOD'],
41+
path: env['PATH_INFO'],
42+
headers: request_headers(env),
43+
query: Rack::Utils.parse_query(env['QUERY_STRING'])
44+
}
45+
end
46+
47+
def request_headers(env)
48+
headers = {}
49+
env.each_pair do |key, val|
50+
if key.start_with?('HTTP_')
51+
headers[key[5..-1].split('_').map(&:capitalize).join('-')] = val
52+
end
53+
end
54+
headers
55+
end
56+
end

lib/speed_gun.rb

+31
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
11
# frozen_string_literal: true
22
require 'speed_gun/version'
3+
require 'speed_gun/config'
34
require 'speed_gun/report'
5+
require 'rack/speed_gun'
6+
require 'speed_gun/railtie' if defined?(::Rails)
7+
8+
module SpeedGun
9+
class << self
10+
# @return [SpeedGun::Config]
11+
def config
12+
@config ||= SpeedGun::Config.new
13+
end
14+
15+
# @yield [config] Configure speed gun
16+
# @yieldparam config [SpeedGun::Config]
17+
def configure
18+
yield config
19+
end
20+
21+
# @return [SpeedGun::Report, nil]
22+
def current_report
23+
Thread.current[:speed_gun_report]
24+
end
25+
26+
def current_report=(report)
27+
Thread.current[:speed_gun_report] = report
28+
end
29+
30+
def record(event)
31+
current_report && current_report.record(event)
32+
end
33+
end
34+
end

lib/speed_gun/config.rb

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'speed_gun'
2+
3+
class SpeedGun::Config
4+
DEFAULT_PREFIX = '/speed_gun'
5+
6+
# @return [Boolean] Enabled SpeedGun
7+
attr_accessor :enabled
8+
9+
# @return [String] Console and API endpoint prefix
10+
attr_accessor :prefix
11+
12+
def initialize
13+
@enabled = true
14+
@prefix = DEFAULT_PREFIX
15+
end
16+
17+
# @return [Boolean] Enabled SpeedGun
18+
def enabled?
19+
enabled
20+
end
21+
end

lib/speed_gun/event.rb

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require 'securerandom'
2+
require 'speed_gun'
3+
4+
class SpeedGun::Event
5+
# @return [String] Event ID
6+
attr_reader :id
7+
# @return [String] Event name
8+
attr_reader :name
9+
# @return [Hash] Event payload
10+
attr_reader :payload
11+
# @return [Time] Started time of event
12+
attr_reader :started_at
13+
# @return [Time, nil] Finished time of event
14+
attr_reader :finished_at
15+
16+
def initialize(name, payload = {}, started_at = Time.now, finished_at = nil)
17+
@id = SecureRandom.uuid
18+
@name = name.to_s
19+
@payload = payload
20+
@started_at = started_at
21+
@finished_at = finished_at
22+
end
23+
24+
def finish!
25+
@finished_at = Time.now
26+
end
27+
28+
def duration
29+
finished_at ? finished_at.to_f - started_at.to_f : 0
30+
end
31+
end

lib/speed_gun/profiler.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'speed_gun'
2+
require 'speed_gun/event'
3+
4+
class SpeedGun::Profiler
5+
def self.profile(*args, &block)
6+
new.profile(*args, &block)
7+
end
8+
9+
def profile(name = self.class.name, payload = {}, &block)
10+
started_at = Time.now
11+
12+
event = SpeedGun::Event.new(name, payload, started_at)
13+
result = yield(event)
14+
event.finish!
15+
16+
SpeedGun.record(event)
17+
18+
result
19+
end
20+
end
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'rblineprof'
2+
require 'speed_gun/source'
3+
require 'speed_gun/profiler'
4+
5+
class SpeedGun::Profiler::LineProfiler < SpeedGun::Profiler
6+
def profile(*args, &block)
7+
result = nil
8+
lineprofiled = lineprof(/./) do
9+
result = yield
10+
end
11+
store(lineprofiled) if SpeedGun.current_report
12+
result
13+
end
14+
15+
def store(lineprofiled)
16+
lineprofiled.each_pair do |file, linesamples|
17+
SpeedGun.current_report.sources.push(SpeedGun::Source.new(file, linesamples))
18+
end
19+
20+
puts SpeedGun.current_report.sources.select { |source|
21+
source.file.end_with?('posts_controller.rb')
22+
}.first.to_s
23+
end
24+
end
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require 'speed_gun/profiler'
2+
3+
class SpeedGun::Profiler::RackProfiler < SpeedGun::Profiler
4+
end

lib/speed_gun/railtie.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'speed_gun'
2+
require 'rack/speed_gun'
3+
require 'rails/railtie'
4+
5+
class SpeedGun::Railtie < ::Rails::Railtie
6+
initializer 'speed_gun' do |app|
7+
app.middleware.insert(0, Rack::SpeedGun)
8+
end
9+
end

lib/speed_gun/report.rb

+4
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ def initialize
1717
@sources = []
1818
@events = []
1919
end
20+
21+
def record(event)
22+
@events.push(event)
23+
end
2024
end

lib/speed_gun/source.rb

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require 'speed_gun'
2+
3+
class SpeedGun::Source
4+
Line = Struct.new(:line, :code, :wall, :cpu, :calls, :allocations) do
5+
def to_s
6+
format("% 8.1fms + % 8.1fms (% 5d) % 5d allocs | %04d %s", cpu / 1000.0, (wall-cpu) / 1000.0, calls, allocations, line, code)
7+
end
8+
end
9+
10+
attr_reader :file, :lines
11+
12+
def initialize(file, linesamples)
13+
@file = file
14+
@lines = []
15+
analyze(file, linesamples)
16+
end
17+
18+
def analyze(file, linesamples)
19+
code_lines = File.exists?(file) ? File.readlines(file) : []
20+
21+
code_lines.each_with_index do |line, idx|
22+
sample = linesamples[idx + 1] || [0, 0, 0, 0]
23+
wall, cpu, calls, allocations = *sample
24+
25+
@lines.push(Line.new(idx + 1, line, wall, cpu, calls, allocations))
26+
end
27+
end
28+
29+
def to_s
30+
"#{file}====\n#{lines.map(&:to_s).join("")}"
31+
end
32+
end

lib/speed_gun/version.rb

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module SpeedGun
55
VERSION = '2.0.0-alpha.1'
66

7+
# @return [Semantic::Version] Version
78
def self.version
89
@version ||= Semantic::Version.new(VERSION)
910
end

speed_gun.gemspec

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
2121
spec.require_paths = ['lib']
2222

2323
spec.add_dependency 'semantic', '~> 1.4'
24+
spec.add_dependency 'rblineprof', '~> 0.3'
25+
spec.add_dependency 'rack', '~> 1.6'
2426

2527
spec.add_development_dependency 'bundler', '~> 1.12'
2628
spec.add_development_dependency 'rake', '~> 11.0'

0 commit comments

Comments
 (0)