This repository has been archived by the owner on Dec 5, 2023. It is now read-only.
forked from bhb/rack-perftools_profiler
/
profiler.rb
163 lines (140 loc) · 4.58 KB
/
profiler.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
module Rack::PerftoolsProfiler
class ProfilingError < RuntimeError
attr_reader :stderr
def initialize(message, stderr)
super(message)
@stderr = stderr
end
end
class Profiler
def self.tmpdir
dir = nil
Dir.chdir Dir.tmpdir do dir = Dir.pwd end # HACK FOR OSX
dir
end
PROFILING_DATA_FILE = ::File.join(self.tmpdir, 'rack_perftools_profiler.prof')
PROFILING_SETTINGS_FILE = ::File.join(self.tmpdir, 'rack_perftools_profiler.config')
DEFAULT_PRINTER = :text
MODES = [:cputime, :objects, :walltime]
DEFAULT_MODE = :cputime
CHANGEABLE_MODES = [:objects]
UNSET_FREQUENCY = "-1"
DEFAULT_GEMFILE_DIR = '.'
def initialize(app, options)
@printer = (options.delete(:default_printer) { DEFAULT_PRINTER }).to_sym
@frequency = (options.delete(:frequency) { UNSET_FREQUENCY }).to_s
@mode = (options.delete(:mode) { DEFAULT_MODE }).to_sym
@bundler = (options.delete(:bundler) { false })
@gemfile_dir = (options.delete(:gemfile_dir) { DEFAULT_GEMFILE_DIR })
@mode_for_request = nil
ProfileDataAction.check_printer(@printer)
# We need to set the enviroment variables before loading perftools
set_env_vars
require 'perftools'
raise ProfilerArgumentError, "Invalid option(s): #{options.keys.join(' ')}" unless options.empty?
end
def profile(mode = nil)
validate_mode(mode) if mode
start(mode)
yield
ensure
stop
end
def self.clear_data
::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
end
def start(mode = nil)
PerfTools::CpuProfiler.stop
# if a mode is passed, change to that mode and set env variables accordingly.
if (mode)
@mode_for_request = mode
end
unset_env_vars
set_env_vars
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE)
self.profiling = true
ensure
@mode_for_request = nil
end
def stop
PerfTools::CpuProfiler.stop
self.profiling = false
unset_env_vars
end
def profiling?
pstore_transaction(true) do |store|
store[:profiling?]
end
end
def data(options = {})
printer = (options.fetch('printer') {@printer}).to_sym
ignore = options.fetch('ignore') { nil }
focus = options.fetch('focus') { nil }
if ::File.exists?(PROFILING_DATA_FILE)
args = "--#{printer}"
args += " --ignore=#{ignore}" if ignore
args += " --focus=#{focus}" if focus
cmd = "pprof.rb #{args} #{PROFILING_DATA_FILE}"
cmd = "bundle exec " + cmd if @bundler
stdout, stderr, status = Dir.chdir(@gemfile_dir) { run(cmd) }
if status!=0
raise ProfilingError.new("Running the command '#{cmd}' exited with status #{status}", stderr)
elsif stdout.length == 0 && stderr.length > 0
raise ProfilingError.new("Running the command '#{cmd}' failed to generate a file", stderr)
else
[printer, stdout]
end
else
[:none, nil]
end
end
private
def run(command)
out = err = ""
pid = nil
status = Open4.popen4(command) do |pid, stdin, stdout, stderr|
stdin.close
pid = pid
out = stdout.read
err = stderr.read
end
[out,err,status.exitstatus]
end
def set_env_vars
if @mode_for_request
mode_to_use = @mode_for_request
else
mode_to_use = @mode
end
ENV['CPUPROFILE_REALTIME'] = '1' if mode_to_use == :walltime
ENV['CPUPROFILE_OBJECTS'] = '1' if mode_to_use == :objects
ENV['CPUPROFILE_FREQUENCY'] = @frequency if @frequency != UNSET_FREQUENCY
end
# Useful for testing
def unset_env_vars
ENV.delete('CPUPROFILE_REALTIME')
ENV.delete('CPUPROFILE_FREQUENCY')
ENV.delete('CPUPROFILE_OBJECTS')
end
def profiling=(value)
pstore_transaction(false) do |store|
store[:profiling?] = value
end
end
def pstore_transaction(read_only)
pstore = PStore.new(PROFILING_SETTINGS_FILE)
pstore.transaction(read_only) do
yield pstore if block_given?
end
end
def validate_mode(mode)
if !CHANGEABLE_MODES.include?(mode)
message = "Cannot change mode to '#{mode}'.\n"
mode_string = CHANGEABLE_MODES.map{|m| "'#{m}'"}.join(", ")
message += "Per-request mode changes are only available for the following modes: #{mode_string}.\n"
message += "See the README for more details."
raise ProfilerArgumentError, message
end
end
end
end