Class: ActiveRecordProfiler::Collector

Inherits:
Object
  • Object
show all
Defined in:
lib/active-record-profiler/collector.rb

Constant Summary collapse

DURATION =
0
COUNT =
1
LONGEST =
2
LONGEST_SQL =
3
LOCATION =
-1
AVG_DURATION =
-2
DATE_FORMAT =
'%Y-%m-%d'
HOUR_FORMAT =
'-%H'
DATETIME_FORMAT =
DATE_FORMAT + HOUR_FORMAT + '-%M'
AGGREGATE_QUIET_PERIOD =
1.minutes
CSV_DURATION =
0
CSV_COUNT =
1
CSV_AVG =
2
CSV_LONGEST =
3
CSV_LOCATION =
4
CSV_LONGEST_SQL =
5
NON_APP_CODE_DESCRIPTION =
'Non-application code'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCollector

Returns a new instance of Collector.



72
73
74
75
76
# File 'lib/active-record-profiler/collector.rb', line 72

def initialize
  @query_sites = {}
  @last_stats_flush = nil
  @profile_data_directory = self.class.profile_dir
end

Instance Attribute Details

#last_stats_flushObject

Returns the value of attribute last_stats_flush.



54
55
56
# File 'lib/active-record-profiler/collector.rb', line 54

def last_stats_flush
  @last_stats_flush
end

#profile_data_directoryObject

Returns the value of attribute profile_data_directory.



56
57
58
# File 'lib/active-record-profiler/collector.rb', line 56

def profile_data_directory
  @profile_data_directory
end

#query_sitesObject

Returns the value of attribute query_sites.



55
56
57
# File 'lib/active-record-profiler/collector.rb', line 55

def query_sites
  @query_sites
end

Class Method Details

.clear_dataObject



66
67
68
69
70
# File 'lib/active-record-profiler/collector.rb', line 66

def self.clear_data
  dir = Dir.new(profile_dir)
  prof_files = dir.entries.select{ |filename| /.prof$/.match(filename) }.map{ |filename| File.join(dir.path, filename) }
  FileUtils.rm(prof_files) if prof_files.size > 0
end

.instanceObject



58
59
60
# File 'lib/active-record-profiler/collector.rb', line 58

def self.instance
  Thread.current[:active_record_profiler_collector] ||= Collector.new
end

.profile_self?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/active-record-profiler/collector.rb', line 62

def self.profile_self?
  self.profile_self
end

Instance Method Details

#aggregate(options = {}) ⇒ Object



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
# File 'lib/active-record-profiler/collector.rb', line 117

def aggregate(options = {})
  prefix = options[:prefix]
  compact = options[:compact]
  raise "Cannot compact without a prefix!" if compact && prefix.nil?
  return self.query_sites unless File.exists?(self.profile_data_directory)
  
  dir = Dir.new(self.profile_data_directory)
  now = Time.now
  raw_files_processed = []
  date_regexp = Regexp.new(prefix) if prefix

  dir.each do |filename|
    next unless /.prof$/.match(filename)
    next if date_regexp && ! date_regexp.match(filename)
    # Parse the datetime out of the filename and convert it to localtime
    begin
      file_time = DateTime.strptime(filename, DATETIME_FORMAT)
      file_time = Time.local(file_time.year, file_time.month, file_time.day, file_time.hour, file_time.min)
    rescue Exception => e
      if e.to_s != 'invalid date'
        raise e
      end
    end

    if (file_time.nil? || ((file_time + AGGREGATE_QUIET_PERIOD) < now))
      begin
        update_from_file(File.join(dir.path, filename))
      
        raw_files_processed << filename if file_time    # any files that are already aggregated don't count
      rescue Exception => e
        RAILS_DEFAULT_LOGGER.warn "Unable to read file #{filename}: #{e.message}"
      end
    else
      Rails.logger.info "Skipping file #{filename} because it is too new and may still be open for writing."
    end
  end

  if compact && raw_files_processed.size > 0
    write_file(File.join(dir.path, "#{prefix}.prof"))

    raw_files_processed.each do |filename|
      FileUtils.rm(File.join(dir.path, filename))
    end
  end

  return self.query_sites
end

#call_location_name(caller_array = nil) ⇒ Object



78
79
80
# File 'lib/active-record-profiler/collector.rb', line 78

def call_location_name(caller_array = nil)
  find_app_call_location(caller_array) || NON_APP_CODE_DESCRIPTION
end

#flush_query_sites_statisticsObject



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/active-record-profiler/collector.rb', line 97

def flush_query_sites_statistics
  pid = $$
  thread_id = Thread.current.object_id
  flush_time = Time.now
  site_count = self.query_sites.keys.size
  Rails.logger.info("Flushing ActiveRecordProfiler statistics for PID #{pid} at #{flush_time} (#{site_count} sites).")
  
  if (site_count > 0)
    FileUtils.makedirs(self.profile_data_directory)
  
    filename = File.join(self.profile_data_directory, "#{flush_time.strftime(DATETIME_FORMAT)}.#{pid}-#{thread_id}.prof")
    write_file(filename)
    
    # Nuke each value to make sure it can be reclaimed by Ruby
    self.query_sites.keys.each{ |k| self.query_sites[k] = nil }
  end
  self.query_sites = {}
  self.last_stats_flush = flush_time
end

#record_caller_info(location, seconds, sql) ⇒ Object



82
83
84
85
86
# File 'lib/active-record-profiler/collector.rb', line 82

def record_caller_info(location, seconds, sql)
  return if sql_ignore_pattern.match(sql)
  
  update_counts(location, seconds, 1, sql)
end

#record_self_info(seconds, name) ⇒ Object



88
89
90
# File 'lib/active-record-profiler/collector.rb', line 88

def record_self_info(seconds, name)
  record_caller_info(trim_location(caller.first), seconds, name)
end

#save_aggregated(date = nil) ⇒ Object



165
166
167
# File 'lib/active-record-profiler/collector.rb', line 165

def save_aggregated(date = nil)
  aggregate(:date => date, :compact => true)
end

#should_flush_stats?Boolean

Returns:

  • (Boolean)


92
93
94
95
# File 'lib/active-record-profiler/collector.rb', line 92

def should_flush_stats?
  self.last_stats_flush ||= Time.now
  return(Time.now > self.last_stats_flush + stats_flush_period)
end

#sorted_locations(sort_field = nil, max_locations = nil) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/active-record-profiler/collector.rb', line 169

def sorted_locations(sort_field = nil, max_locations = nil)
  sort_field ||= DURATION
  case sort_field
    when LOCATION
      sorted = self.query_sites.keys.sort
    when AVG_DURATION
      sorted = self.query_sites.keys.sort_by{ |k| (self.query_sites[k][DURATION] / self.query_sites[k][COUNT]) }.reverse
    when DURATION, COUNT, LONGEST
      sorted = self.query_sites.keys.sort{ |a,b| self.query_sites[b][sort_field] <=> self.query_sites[a][sort_field] }
    else
      raise "Invalid sort field: #{sort_field}"
  end
  if max_locations && max_locations > 0
    sorted.first(max_locations)
  else
    sorted
  end
end