copperegg-apm 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +102 -0
  5. data/README.md +181 -0
  6. data/Rakefile +4 -0
  7. data/bin/copperegg-apm-init +45 -0
  8. data/bin/copperegg-apm-methods +27 -0
  9. data/copperegg-apm-1.0.0.pre1.gem +0 -0
  10. data/copperegg-apm.gemspec +34 -0
  11. data/copperegg_apm_test.db +0 -0
  12. data/ext/mkrf_conf.rb +19 -0
  13. data/lib/copperegg-apm.rb +1 -0
  14. data/lib/copperegg/apm.rb +28 -0
  15. data/lib/copperegg/apm/action_controller/base.rb +31 -0
  16. data/lib/copperegg/apm/active_record/connection_adapters/abstract_adapter.rb +35 -0
  17. data/lib/copperegg/apm/benchmark.rb +212 -0
  18. data/lib/copperegg/apm/benchmark_methods_table.rb +65 -0
  19. data/lib/copperegg/apm/configuration.rb +188 -0
  20. data/lib/copperegg/apm/engine.rb +15 -0
  21. data/lib/copperegg/apm/errors.rb +6 -0
  22. data/lib/copperegg/apm/ethon/easy/operations.rb +37 -0
  23. data/lib/copperegg/apm/kernel.rb +19 -0
  24. data/lib/copperegg/apm/middleware.rb +20 -0
  25. data/lib/copperegg/apm/mysql.rb +31 -0
  26. data/lib/copperegg/apm/mysql2/client.rb +35 -0
  27. data/lib/copperegg/apm/net/http.rb +39 -0
  28. data/lib/copperegg/apm/pg/connection.rb +35 -0
  29. data/lib/copperegg/apm/restclient/request.rb +33 -0
  30. data/lib/copperegg/apm/rum.rb +24 -0
  31. data/lib/copperegg/apm/sqlite3/database.rb +35 -0
  32. data/lib/copperegg/apm/tasks.rb +11 -0
  33. data/lib/copperegg/apm/typhoeus/hydra.rb +33 -0
  34. data/lib/copperegg/apm/unbound_method.rb +116 -0
  35. data/lib/copperegg/apm/version.rb +5 -0
  36. data/lib/generators/copperegg/apm/init_generator.rb +20 -0
  37. data/lib/generators/copperegg/apm/templates/config.rb +10 -0
  38. data/performance/mysql2.rb +44 -0
  39. data/screenshot01.png +0 -0
  40. data/spec/action_controller_spec.rb +139 -0
  41. data/spec/apm_spec.rb +330 -0
  42. data/spec/ethon_spec.rb +73 -0
  43. data/spec/helpers/mysql2_setup.rb +51 -0
  44. data/spec/helpers/mysql_setup.rb +45 -0
  45. data/spec/helpers/pg_setup.rb +43 -0
  46. data/spec/helpers/rails.rb +18 -0
  47. data/spec/helpers/sqlite3_setup.rb +30 -0
  48. data/spec/kernel_spec.rb +37 -0
  49. data/spec/mysql2_spec.rb +58 -0
  50. data/spec/mysql_spec.rb +66 -0
  51. data/spec/net_http_spec.rb +52 -0
  52. data/spec/pg_spec.rb +45 -0
  53. data/spec/restclient_spec.rb +67 -0
  54. data/spec/rum_spec.rb +23 -0
  55. data/spec/spec_helper.rb +12 -0
  56. data/spec/sqlite3_spec.rb +45 -0
  57. data/spec/typhoeus_spec.rb +66 -0
  58. metadata +316 -0
@@ -0,0 +1,28 @@
1
+ at_exit do
2
+ begin
3
+ CopperEgg::APM.send_payload_cache
4
+ rescue
5
+ end
6
+ end
7
+
8
+ %w(
9
+ benchmark
10
+ action_controller/base
11
+ active_record/connection_adapters/abstract_adapter
12
+ configuration
13
+ errors
14
+ ethon/easy/operations
15
+ kernel
16
+ mysql
17
+ mysql2/client
18
+ net/http
19
+ pg/connection
20
+ restclient/request
21
+ rum
22
+ sqlite3/database
23
+ typhoeus/hydra
24
+ unbound_method
25
+ version
26
+ ).each { |file| require "copperegg/apm/#{file}" }
27
+
28
+ require 'copperegg/apm/engine' if defined?(::Rails::Engine)
@@ -0,0 +1,31 @@
1
+ module CopperEgg
2
+ module APM
3
+ module ActionController
4
+ module Base
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ def self.add_benchmarks(options={})
8
+ raise ArgumentError.new("hash expected") if !options.is_a?(Hash)
9
+ around_filter :ce_benchmark_controller_actions, options
10
+ end
11
+
12
+ def ce_benchmark_controller_actions
13
+ starttime = (Time.now.to_f * 1000.0).to_i
14
+ yield
15
+ time = (Time.now.to_f * 1000.0).to_i - starttime
16
+ CopperEgg::APM.send_payload({:method => "#{self.to_s.sub!(/:[a-z0-9]x[^>]+>/, "").sub!("#<","")}##{action_name}", :time => time})
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ if defined?(::ActionController::Base)
26
+
27
+ class ActionController::Base
28
+ include CopperEgg::APM::ActionController::Base
29
+ end
30
+
31
+ end
@@ -0,0 +1,35 @@
1
+ module CopperEgg
2
+ module APM
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module AbstractAdapter
6
+ def log_with_ce_instrumentation(*args)
7
+ if CopperEgg::APM::Configuration.benchmark_active_record?
8
+ sql, name = args
9
+ starttime = (Time.now.to_f * 1000.0).to_i
10
+ result = block_given? ? log_without_ce_instrumentation(*args, &Proc.new) : log_without_ce_instrumentation(*args)
11
+ time = (Time.now.to_f * 1000.0).to_i - starttime
12
+
13
+ CopperEgg::APM.send_payload({:sql => CopperEgg::APM.obfuscate_sql(sql), :time => time})
14
+
15
+ result
16
+ else
17
+ block_given? ? log_without_ce_instrumentation(*args, &Proc.new) : log_without_ce_instrumentation(*args)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 3
27
+
28
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
29
+ include CopperEgg::APM::ActiveRecord::ConnectionAdapters::AbstractAdapter
30
+ alias_method :log_without_ce_instrumentation, :log
31
+ alias_method :log, :log_with_ce_instrumentation
32
+ protected :log
33
+ end
34
+
35
+ end
@@ -0,0 +1,212 @@
1
+ require 'socket'
2
+ require 'json'
3
+
4
+ module CopperEgg
5
+ module APM
6
+ DO_NOT_INCLUDE_PATHS = %w(. test spec vendor config)
7
+ PAYLOAD_FREQUENCY = 15
8
+ PAYLOAD_BYTESIZE = 1024*8
9
+ @@benchmarkable_methods = []
10
+ @@payload_sent_at = 0
11
+ @@payload_cache = ""
12
+ @@gem_version_sent = false
13
+
14
+ module_function
15
+
16
+ def configure(&block)
17
+ @@payload_cache = {:version => CopperEgg::APM::GEM_VERSION}.to_json
18
+ send_payload_cache
19
+ Configuration.configure(&block)
20
+ end
21
+
22
+ def benchmark(arg, &block)
23
+ if arg.class == Hash
24
+ parameters = arg
25
+ elsif arg.class == Class || arg.class == Module
26
+ parameters = {:method => "#{arg}.#{calling_method}"}
27
+ else
28
+ parameters = {:method => "#{arg.class}##{calling_method}"}
29
+ end
30
+
31
+ starttime = (Time.now.to_f * 1000.0).to_i
32
+ result = yield
33
+ parameters[:time] = (Time.now.to_f * 1000.0).to_i - starttime
34
+
35
+ send_payload(parameters)
36
+
37
+ result
38
+ end
39
+
40
+ # Inject benchmarking code into user-defined public instance and class accessor methods whose names are made up of only alphanumeric characters.
41
+ # By user-defined, we mean those whose source is defined in neither the load path nor the gem path.
42
+ def add_method_benchmarking
43
+ benchmarkable_methods.each {|method| method.add_benchmarking unless method.excluded?}
44
+ end
45
+
46
+ # Returns an array of unbound methods to which benchmarking can be added.
47
+ def benchmarkable_methods
48
+ return @@benchmarkable_methods if @@benchmarkable_methods.size > 0
49
+
50
+ if defined?(::Rails) && Rails.respond_to?(:configuration)
51
+ $LOAD_PATH.each do |path|
52
+ v, $VERBOSE = $VERBOSE, nil
53
+ if path.include?(Rails.configuration.root.to_s) && DO_NOT_INCLUDE_PATHS.detect {|part| path.include?("/#{part}")}.nil?
54
+ Dir.glob("#{path}/**/*.rb").each {|f| ActiveSupport::Dependencies::Loadable.require_dependency f, ""}
55
+ end
56
+ $VERBOSE = v
57
+ end
58
+ end
59
+
60
+ ObjectSpace.each_object(Class) do |class_or_module|
61
+ begin
62
+ class_or_module.instance_methods(false).each do |name|
63
+ method = class_or_module.instance_method(name)
64
+ method.parent_class = class_or_module
65
+ @@benchmarkable_methods.push(method) if method.benchmarkable?
66
+ end
67
+ rescue
68
+ end
69
+
70
+ begin
71
+ class_or_module.singleton_class.instance_methods(false).each do |name|
72
+ method = class_or_module.singleton_class.instance_method(name)
73
+ method.parent_class = class_or_module
74
+ method.class_method = true
75
+ @@benchmarkable_methods.push(method) if method.benchmarkable?
76
+ end
77
+ rescue
78
+ end
79
+ end
80
+
81
+ @@benchmarkable_methods
82
+ end
83
+
84
+ def send_payload(parameters)
85
+ return if CopperEgg::APM::Configuration.instrument_key == nil
86
+
87
+ param_type = if parameters.has_key?(:sql)
88
+ :sql
89
+ elsif parameters.has_key?(:method)
90
+ :method
91
+ elsif parameters.has_key?(:url)
92
+ :url
93
+ elsif parameters.has_key?(:error)
94
+ :error
95
+ end
96
+
97
+ parameters[param_type] += " {Ruby}"
98
+
99
+ key = parameters[:error] ? :excp : :inst
100
+ hash = {key => parameters, :id => CopperEgg::APM::Configuration.instrument_key}
101
+
102
+ json = hash.to_json
103
+ payload = "#{[0x6375].pack("N")}#{[json.length].pack("N")}#{json}"
104
+ if @@payload_cache.bytesize > PAYLOAD_BYTESIZE
105
+ @@payload_cache = ""
106
+ elsif @@payload_cache.bytesize > 0 && ((@@payload_cache.bytesize + payload.bytesize) >= PAYLOAD_BYTESIZE || Time.now.to_i - @@payload_sent_at >= PAYLOAD_FREQUENCY)
107
+ send_payload_cache
108
+ end
109
+ @@payload_cache = "#{@@payload_cache}#{payload}"
110
+ end
111
+
112
+ def send_payload_cache
113
+ begin
114
+ if RUBY_VERSION < "1.9"
115
+ socket = UDPSocket.new
116
+ socket.send(@@payload_cache, 0, CopperEgg::APM::Configuration.udp_host, CopperEgg::APM::Configuration.udp_port)
117
+ else
118
+ socket = Socket.new(:INET, :DGRAM)
119
+ addr = Socket.sockaddr_in(CopperEgg::APM::Configuration.udp_port, CopperEgg::APM::Configuration.udp_host)
120
+ socket.connect_nonblock(addr)
121
+ socket.send(@@payload_cache, 0)
122
+ end
123
+ rescue Errno::EMSGSIZE
124
+ end
125
+ socket.close
126
+ CopperEgg::APM::Configuration.log @@payload_cache
127
+ @@payload_sent_at = Time.now.to_i
128
+ @@payload_cache = ""
129
+ end
130
+
131
+ # Returns a copy of self with escaping, quotes, whitespacing and actual values removed
132
+ # from SQL statements
133
+ def obfuscate_sql(sql)
134
+ return sql if sql.bytesize > 1024 # don't bother with sql statements larger than 1k
135
+ sql = sql.dup
136
+
137
+ # Remove escaping quotes
138
+ sql.gsub!(/\\["']/, '')
139
+
140
+ # Remove surrounding backticks
141
+ sql.gsub!(/[`]([^`]+)[`]/i,'\1')
142
+
143
+ # Remove surrounding quotes from strings immediately following "FROM"
144
+ sql.gsub!(/(from|into|update|set)\s*['"`]([^'"`]+)['"`]/i,'\1 \2')
145
+
146
+ # Remove surrounding quotes from strings immediately neighboring a period
147
+ sql.gsub!(/([\.])['"]([^'"]+)['"]/,'\1\2')
148
+ sql.gsub!(/['"]([^'"]+)['"]([\.])/,'\1\2')
149
+
150
+ # Replace other quoted strings with a question mark
151
+ sql.gsub!(/['"]([^'"]+)['"]/,'?')
152
+
153
+ # Remove integers
154
+ sql.gsub!(/\b\d+\b/, '?')
155
+
156
+ # Removing padded spaces
157
+ sql.gsub!(/(\s){2,}/,'\1')
158
+
159
+ # Remove leading and trailing whitespace
160
+ sql.strip!
161
+
162
+ sql
163
+ end
164
+
165
+ def trim_stacktrace(array)
166
+ previous_components = nil
167
+
168
+ last = array.last.strip
169
+
170
+ array.reject! {|path| path.include? CopperEgg::APM::Configuration.gem_root}
171
+
172
+ if !CopperEgg::APM::Configuration.app_root.empty?
173
+ array.reject! {|path| !path.include?(CopperEgg::APM::Configuration.app_root) || !CopperEgg::APM::DO_NOT_INCLUDE_PATHS.detect {|part| path.include?("/#{part}")}.nil?}
174
+ end
175
+
176
+ array.map! do |path|
177
+ if previous_components
178
+ current_components = path.split("/")
179
+
180
+ if (current_components - previous_components).size == 1
181
+ path = ".../" + (current_components - previous_components).join("/")
182
+ end
183
+
184
+ previous_components = current_components
185
+ else
186
+ previous_components = path.split("/")
187
+ end
188
+ path.strip
189
+ end
190
+
191
+ array.push(last) if last && array.empty?
192
+ array
193
+ end
194
+
195
+ def calling_method
196
+ caller[1] =~ /`([^']*)'/ and $1
197
+ end
198
+
199
+ def capture_exception(*args)
200
+ exception = if args.size == 0
201
+ $!.nil? ? RuntimeError.new : $!
202
+ elsif args.size == 1
203
+ args.first.is_a?(String) ? RuntimeError.exception(args.first) : args.first.exception
204
+ elsif args.size <= 3
205
+ args.first.exception(args[1])
206
+ end
207
+ stacktrace = trim_stacktrace(caller)
208
+ parameters = {:error => "#{exception.class}|#{stacktrace.first}", :stacktrace => "#{exception.message}\n#{stacktrace.join("\n")}", :ts => Time.now.to_i}
209
+ send_payload(parameters)
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,65 @@
1
+ module CopperEgg
2
+ module APM
3
+ class BenchmarkMethodsTable
4
+ def benchmarkable_methods
5
+ @benchmarkable_methods ||= CopperEgg::APM.benchmarkable_methods.sort_by(&:display_name)
6
+ end
7
+
8
+ def excluded_methods
9
+ benchmarkable_methods.select {|method| method.excluded?}
10
+ end
11
+
12
+ def method_column_width
13
+ benchmarkable_methods.reduce(0) do |memo, method|
14
+ method.display_name.size > memo ? method.display_name.size : memo
15
+ end + column_padding
16
+ end
17
+
18
+ def source_filename_column_width
19
+ benchmarkable_methods.reduce(0) do |memo, method|
20
+ method.display_filename.size > memo ? method.display_filename.size : memo
21
+ end + column_padding
22
+ end
23
+
24
+ def column_padding
25
+ 2
26
+ end
27
+
28
+ def separator
29
+ "+" + "-"*(method_column_width+1) + "+" + "-"*(source_filename_column_width+1) + "+" + "--------------+"
30
+ end
31
+
32
+ def header_columns
33
+ "| Method" + " "*(method_column_width - 6).abs + "| Source Location" + " "*(source_filename_column_width - 15).abs + "| Benchmarked? |"
34
+ end
35
+
36
+ def method_columns(method)
37
+ "| #{method.display_name}" + " "*(method_column_width - method.display_name.size) + "| #{method.display_filename}" + " "*(source_filename_column_width - method.display_filename.size) + "| #{method.excluded? ? "NO " : "YES"} |"
38
+ end
39
+
40
+ def classes_count
41
+ benchmarkable_methods.map(&:owner).uniq!.size
42
+ end
43
+
44
+ def file_count
45
+ benchmarkable_methods.map(&:source_filename).uniq!.size
46
+ end
47
+
48
+ def print_table
49
+ if RUBY_VERSION < "1.9"
50
+ puts "Method benchmarking is not available in Ruby #{RUBY_VERSION}. It is only available in Ruby 1.9 or later."
51
+ elsif CopperEgg::APM::Configuration.benchmark_methods_level == :disabled
52
+ puts "Method benchmarking is disabled. You can enable method benchmarking by setting the benchmark_methods configuration value."
53
+ elsif benchmarkable_methods.size == 0
54
+ puts "No methods benchmarked"
55
+ else
56
+ puts
57
+ puts "#{separator}\n#{header_columns}\n#{separator}\n#{benchmarkable_methods.map {|method| method_columns(method)}.join("\n")}\n#{separator}"
58
+ puts "#{benchmarkable_methods.size} methods defined in #{classes_count} classes across #{file_count} files. #{benchmarkable_methods.size - excluded_methods.size == 1 ? "1 method" : "#{benchmarkable_methods.size - excluded_methods.size} methods"} benchmarked."
59
+ puts
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,188 @@
1
+ require 'logger'
2
+ require 'date'
3
+ require 'set'
4
+
5
+ module CopperEgg
6
+ module APM
7
+ class Configuration
8
+ BENCHMARK_METHOD_LEVELS = [:disabled, :basic, :moderate, :full, :custom]
9
+
10
+ @@udp_port = 28344
11
+ @@udp_host = "127.0.0.1"
12
+ @@app_root = ""
13
+ @@instrument_key = nil
14
+ @@rum_short_url = false
15
+ @@rum_beacon_url = "http://bacon.copperegg.com/bacon.gif"
16
+ @@gem_root = File.dirname(File.dirname(__FILE__))
17
+ @@log_to = nil
18
+ @@benchmark_sql = true
19
+ @@benchmark_active_record = false
20
+ @@benchmark_http = true
21
+ @@benchmark_exceptions = true
22
+ @@benchmark_methods_level = :disabled
23
+ @@only_methods = []
24
+ @@exclude_methods = []
25
+ @@include_methods = []
26
+ @@logger = nil
27
+
28
+ def self.udp_port
29
+ @@udp_port
30
+ end
31
+
32
+ def self.udp_host
33
+ @@udp_host
34
+ end
35
+
36
+ def self.rum_beacon_url
37
+ @@rum_beacon_url
38
+ end
39
+
40
+ def self.gem_root
41
+ @@gem_root
42
+ end
43
+
44
+ def self.instrument_key=(key)
45
+ raise ConfigurationError.new("invalid instrument key") if !key =~ /\A[a-z0-9]+\z/i
46
+ @@instrument_key = key
47
+ create_logfile if @@log_to
48
+ end
49
+
50
+ def self.instrument_key
51
+ @@instrument_key
52
+ end
53
+
54
+ def self.rum_short_url=(boolean)
55
+ raise ConfigurationError.new("RUM short url must be a boolean") if boolean != true && boolean != false
56
+ @@rum_short_url = boolean
57
+ end
58
+
59
+ def self.rum_short_url
60
+ @@rum_short_url
61
+ end
62
+
63
+ def self.app_root=(path)
64
+ @@app_root = path.to_s
65
+ end
66
+
67
+ def self.app_root
68
+ @@app_root
69
+ end
70
+
71
+ def self.benchmark_sql=(boolean)
72
+ raise ConfigurationError.new("Boolean expected for benchmark_sql") if boolean != true && boolean != false
73
+ @@benchmark_sql = boolean
74
+ end
75
+
76
+ def self.benchmark_sql?
77
+ @@benchmark_sql
78
+ end
79
+
80
+ def self.benchmark_active_record=(boolean)
81
+ raise ConfigurationError.new("Boolean expected for benchmark_active_record") if boolean != true && boolean != false
82
+ @@benchmark_sql = boolean
83
+ end
84
+
85
+ def self.benchmark_active_record?
86
+ @@benchmark_active_record
87
+ end
88
+
89
+ def self.benchmark_http=(boolean)
90
+ raise ConfigurationError.new("Boolean expected for benchmark_http") if boolean != true && boolean != false
91
+ @@benchmark_http = boolean
92
+ end
93
+
94
+ def self.benchmark_http?
95
+ @@benchmark_http
96
+ end
97
+
98
+ def self.benchmark_exceptions=(boolean)
99
+ raise ConfigurationError.new("Boolean expected for benchmark_exceptions") if boolean != true && boolean != false
100
+ @@benchmark_exceptions = boolean
101
+ end
102
+
103
+ def self.benchmark_exceptions?
104
+ @@benchmark_exceptions
105
+ end
106
+
107
+ def self.benchmark_methods(level, options={})
108
+ raise ConfigurationError.new("Method benchmark level can only be :disabled, :basic, :moderate, :full, or :custom") if !BENCHMARK_METHOD_LEVELS.include?(level)
109
+
110
+ @@benchmark_methods_level = level
111
+ return if level == :disabled
112
+
113
+ if level == :custom
114
+ benchmark_methods_option(options, :@@only_methods)
115
+ else
116
+ benchmark_methods_option(options[:include], :@@include_methods) if options[:include]
117
+ benchmark_methods_option(options[:exclude], :@@exclude_methods) if options[:exclude]
118
+ end
119
+ end
120
+
121
+ def self.benchmark_methods_level
122
+ @@benchmark_methods_level
123
+ end
124
+
125
+ def self.only_methods
126
+ @@only_methods || []
127
+ end
128
+
129
+ def self.include_methods
130
+ @@include_methods || []
131
+ end
132
+
133
+ def self.exclude_methods
134
+ @@exclude_methods || []
135
+ end
136
+
137
+ def self.log(payload)
138
+ return if @@logger == nil
139
+ @@logger.debug "Payload sent at #{DateTime.strptime(Time.now.to_i.to_s, '%s').strftime('%Y-%m-%d %H:%M:%S')} #{payload.bytesize} bytes\n"
140
+ @@logger.debug payload.split("\x00").select {|i| i.size > 2}.map {|i| i.sub(/^[^\{]+/,'')}.join("\n")
141
+ @@logger.debug ""
142
+ end
143
+
144
+ def self.enable_logging=(boolean)
145
+ raise ConfigurationError.new("Boolean expected for logging_enabled") if boolean != true && boolean != false
146
+ dir = '/tmp'
147
+ raise ConfigurationError.new("Directory #{dir} must be readable and writable.") if !File.readable?(dir) || !File.writable?(dir)
148
+ @@log_to = dir
149
+ if @@instrument_key
150
+ create_logfile
151
+ end
152
+ end
153
+
154
+ def self.configure(&block)
155
+ yield(self)
156
+
157
+ if @@app_root.empty?
158
+ if defined?(::Rails) && ::Rails.respond_to?(:configuration)
159
+ @@app_root = ::Rails.configuration.root.to_s
160
+ else
161
+ @@app_root = File.dirname(caller[1])
162
+ end
163
+ end
164
+
165
+ CopperEgg::APM.add_method_benchmarking if @@benchmark_methods_level != :disabled
166
+ end
167
+
168
+ class <<self
169
+ private
170
+
171
+ def benchmark_methods_option(array, class_variable_name)
172
+ raise ConfigurationError.new("Array expected for benchmark method option") if !array.is_a?(Array)
173
+ array.each do |value|
174
+ raise ConfigurationError.new("Invalid item #{value} in benchmark method option. String expected.") if !value.is_a?(String)
175
+ end
176
+ class_variable_set(class_variable_name, array)
177
+ end
178
+
179
+ def create_logfile
180
+ logdir = File.join(@@log_to, 'copperegg', 'apm')
181
+ FileUtils.mkdir_p(logdir) unless File.directory?(logdir)
182
+
183
+ @@logger = Logger.new(File.join(logdir, "#{@@instrument_key}.log"), 0)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end