Class: Rack::MiniProfiler::GCProfiler

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_profiler/gc_profiler.rb

Instance Method Summary collapse

Constructor Details

#initializeGCProfiler

Returns a new instance of GCProfiler.



3
4
5
6
# File 'lib/mini_profiler/gc_profiler.rb', line 3

def initialize
  @ignore = []
  @ignore << @ignore.__id__
end

Instance Method Details

#analyze_growth(ids_before, ids_after) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/mini_profiler/gc_profiler.rb', line 64

def analyze_growth(ids_before, ids_after)
  new_objects = 0
  memory_allocated = 0

  ids_after.each do |id,_|
    if !ids_before.include?(id) && obj=ObjectSpace._id2ref(id)
      # this is going to be version specific (may change in 2.1)
      size = ObjectSpace.memsize_of(obj)
      memory_allocated += size
      new_objects += 1
    end
  end

  [new_objects, memory_allocated]
end

#analyze_initial_state(ids_before) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/mini_profiler/gc_profiler.rb', line 80

def analyze_initial_state(ids_before)
  memory_allocated = 0
  objects = 0

  ids_before.each do |id,_|
    if obj=ObjectSpace._id2ref(id)
      # this is going to be version specific (may change in 2.1)
      memory_allocated += ObjectSpace.memsize_of(obj)
      objects += 1
    end
  end

  [objects,memory_allocated]
end

#analyze_strings(ids_before, ids_after) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/mini_profiler/gc_profiler.rb', line 52

def analyze_strings(ids_before, ids_after)
  result = {}
  ids_after.each do |id,_|
    obj = ObjectSpace._id2ref(id)
    if String === obj && !ids_before.include?(obj.object_id)
      result[obj] ||= 0
      result[obj] += 1
    end
  end
  result
end

#diff_object_stats(before, after) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
# File 'lib/mini_profiler/gc_profiler.rb', line 40

def diff_object_stats(before, after)
  diff = {}
  after.each do |k,v|
    diff[k] = v - (before[k] || 0)
  end
  before.each do |k,v|
    diff[k] = 0 - v unless after[k]
  end

  diff
end

#object_space_statsObject



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
# File 'lib/mini_profiler/gc_profiler.rb', line 8

def object_space_stats
  stats = {}
  ids = {}

  @ignore << stats.__id__
  @ignore << ids.__id__

  i=0
  ObjectSpace.each_object { |o|
    begin
      i = stats[o.class] || 0
      i += 1
      stats[o.class] = i
      ids[o.__id__] = o if Integer === o.__id__
    rescue NoMethodError
      # protect against BasicObject
    end
  }

  @ignore.each do |id|
    if ids.delete(id)
      klass = ObjectSpace._id2ref(id).class
      stats[klass] -= 1
    end
  end

  result = {:stats => stats, :ids => ids}
  @ignore << result.__id__

  result
end

#profile_gc(app, env) ⇒ Object



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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/mini_profiler/gc_profiler.rb', line 115

def profile_gc(app, env)

  # for memsize_of
  require 'objspace'

  body = [];

  stat_before,stat_after,diff,string_analysis,
    new_objects, memory_allocated, stat, memory_before, objects_before = nil

  # clean up before
  GC.start
  stat          = GC.stat
  prev_gc_state = GC.disable
  stat_before   = object_space_stats
  b             = app.call(env)[2]
  b.close if b.respond_to? :close
  stat_after = object_space_stats
  # so we don't blow out on memory
  prev_gc_state ? GC.disable : GC.enable

  diff                          = diff_object_stats(stat_before[:stats],stat_after[:stats])
  string_analysis               = analyze_strings(stat_before[:ids], stat_after[:ids])
  new_objects, memory_allocated = analyze_growth(stat_before[:ids], stat_after[:ids])
  objects_before, memory_before = analyze_initial_state(stat_before[:ids])


  body << "
Overview
------------------------------------
Initial state: object count - #{objects_before} , memory allocated outside heap (bytes) #{memory_before}

GC Stats: #{stat.map{|k,v| "#{k} : #{v}" }.join(", ")}

New bytes allocated outside of Ruby heaps: #{memory_allocated}
New objects: #{new_objects}
"

  body << "
ObjectSpace delta caused by request:
--------------------------------------------\n"
  diff.to_a.reject{|k,v| v == 0}.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
    body << "#{k} : #{v}\n" if v != 0
  end

  body << "\n
ObjectSpace stats:
-----------------\n"

  stat_after[:stats].to_a.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
    body << "#{k} : #{v}\n"
  end


  body << "\n
String stats:
------------\n"

  string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
    body << "#{count} : #{string}\n"
  end

  return [200, {'Content-Type' => 'text/plain'}, body]
ensure
  prev_gc_state ? GC.disable : GC.enable
end

#profile_gc_time(app, env) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/mini_profiler/gc_profiler.rb', line 95

def profile_gc_time(app, env)
  body = []

  begin
    GC::Profiler.clear
    prev_profiler_state = GC::Profiler.enabled?
    prev_gc_state       = GC.enable
    GC::Profiler.enable
    b = app.call(env)[2]
    b.close if b.respond_to? :close
    body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
    body << GC::Profiler.result
  ensure
    prev_gc_state ? GC.disable : GC.enable
    GC::Profiler.disable unless prev_profiler_state
  end

  return [200, {'Content-Type' => 'text/plain'}, body]
end