wonderdog 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/.gitignore +2 -0
  2. data/.idea/encodings.xml +5 -0
  3. data/.idea/misc.xml +5 -0
  4. data/.idea/modules.xml +9 -0
  5. data/.idea/scopes/scope_settings.xml +5 -0
  6. data/.idea/vcs.xml +7 -0
  7. data/.idea/wonderdog.iml +41 -0
  8. data/Gemfile +1 -1
  9. data/bin/estool +22 -1
  10. data/bin/squirrel.rb +108 -0
  11. data/lib/wonderdog.rb +3 -0
  12. data/lib/wonderdog/hadoop_invocation_override.rb +4 -1
  13. data/lib/wonderdog/version.rb +1 -1
  14. data/pom.xml +1 -1
  15. data/spec/spec_helper.rb +1 -1
  16. data/spec/wonderdog/hadoop_invocation_override_spec.rb +1 -1
  17. data/squirrel/all_facets.rb +95 -0
  18. data/squirrel/change_es_index_settings.rb +19 -0
  19. data/squirrel/clear_es_caches.rb +30 -0
  20. data/squirrel/esbackup.rb +184 -0
  21. data/squirrel/esbackup_stripped.rb +153 -0
  22. data/squirrel/fields.sh +5 -0
  23. data/squirrel/getFields.rb +19 -0
  24. data/squirrel/replay.rb +219 -0
  25. data/squirrel/squirrel.rb +95 -0
  26. data/squirrel/warmer_interface.rb +59 -0
  27. data/src/main/java/com/infochimps/elasticsearch/ElasticSearchInputFormat.java +2 -2
  28. data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingInputFormat.java +14 -2
  29. data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingOutputFormat.java +20 -5
  30. data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingRecordReader.java +55 -26
  31. data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingRecordWriter.java +59 -22
  32. data/test/cardinality.rb +43 -0
  33. data/test/change_es_index_settings.rb +19 -0
  34. data/test/clear_es_caches.rb +30 -0
  35. data/test/config/mapping.yml +327 -0
  36. data/test/config/mappings.yml +328 -0
  37. data/test/count_check.txt +0 -0
  38. data/test/esbackup_stripped.rb +153 -0
  39. data/test/mapping.yml +327 -0
  40. data/test/medium_slow_queries +41 -0
  41. data/test/queries.txt +0 -0
  42. data/test/quick_test_slow_queries +4 -0
  43. data/test/run_pry.rb +3 -0
  44. data/test/some_slow_queries +53 -0
  45. data/test/warmer_interface.rb +64 -0
  46. data/test/warmindices.rb +65 -0
  47. data/wonderdog.gemspec +1 -1
  48. metadata +40 -7
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Simple script to dump elasticsearch indexes as raw JSON
4
+
5
+ require 'tire'
6
+ require 'zlib'
7
+ require 'socket'
8
+ require 'pathname'
9
+ require 'configliere'
10
+ require 'multi_json'
11
+
12
+ Settings.use :commandline
13
+ def Settings.available_commands() %w[ backup restore ] ; end
14
+ def Settings.usage() 'usage: esbackup command [ path ] [..--param=value..]' ; end
15
+ Settings.description = <<-DESC.gsub(/^ {2}/, '').chomp
16
+ Simple backup and restore tool for Elasticsearch.
17
+
18
+ # Example backup command
19
+ $ esbackup backup -c localhost -p 9200 -b 100 -i my_index -q '{"query":{"term":{"field":"search_term"}}}'
20
+
21
+ # Example restore command
22
+ $ esbackup restore -c localhost -p 9200 -i my_index -m mapping_file.json
23
+
24
+ CAVEAT: Due to stupid fucking restrictions in the gem we use (tire), if you would like to use a dynamic query
25
+ when backing up you have to specify it like so: -q '{"query_method":["*args_to_method"]}'
26
+
27
+ Available commands:
28
+ #{Settings.available_commands.map{ |cmd| ' ' << cmd }.join("\n")}
29
+ DESC
30
+ Settings.define :host, default: Socket.gethostname, flag: 'c', description: 'Host to connect to Elasticsearch'
31
+ Settings.define :port, default: 9200, flag: 'p', description: 'Port to connect to Elasticsearch'
32
+ Settings.define :batch_size, default: 10000, flag: 'b', description: 'Batch size'
33
+ Settings.define :index, flag: 'i', description: 'Index to backup/restore', required: true
34
+ Settings.define :mappings, flag: 'm', description: 'Dump mappings to or load mappings from provided file'
35
+ Settings.define :query, flag: 'q', description: 'A JSON hash containing a query to apply to the command (backup only)'
36
+ Settings.define :dump_file, default: nil flag: 'd', description: 'The name of the JSON dump file, if not passed the index name will be the dump files name'
37
+ Settings.resolve!
38
+
39
+ Tire::Configuration.url "http://#{Settings[:host]}:#{Settings[:port]}"
40
+
41
+ class ESBackup
42
+
43
+ def initialize(output_dir, options = {})
44
+ options = options.merge()
45
+ @output_dir = output_dir || ''
46
+ @index = options[:index]
47
+ @batch_size = options[:batch_size].to_i
48
+ @mapping_file = options[:mappings]
49
+ @query = MultiJson.load(options[:query]) rescue nil
50
+ if options[:dump_file].nil?
51
+ @dump_file = @index
52
+ else
53
+ @dump_file = options[:dump_file]
54
+ end
55
+ end
56
+
57
+ def dump_mapping
58
+ index = Tire::Index.new @index
59
+ File.open(@mapping_file, 'w'){ |f| f.puts index.mapping.to_json }
60
+ end
61
+
62
+ def fullpath dir
63
+ basedir = dir.start_with?('/') ? dir : File.join(Dir.pwd, dir)
64
+ FileUtils.mkdir_p(basedir)
65
+ basedir
66
+ end
67
+
68
+ def gz_output
69
+ File.join(fullpath(@output_dir), @index + '.gz')
70
+ end
71
+
72
+ def create_scanner
73
+ scan_opts = { size: @batch_size }
74
+ additional_query = @query
75
+ Tire::Search::Scan.new(@index, scan_opts) do
76
+ # This is fucking stupid; why people have to be cute and make everything DSL only
77
+ # I'll never understand, but the person who wrote this gem has forced us to ONLY be able to
78
+ # ask queries in this manner.
79
+ query do
80
+ additional_query.each_pair do |key, vals|
81
+ case vals
82
+ # Assuming here that you are only asking for one field at a time...this is getting hacky fast
83
+ when Hash then self.send(key.to_sym, *vals.to_a.flatten)
84
+ when Array then self.send(key.to_sym, *vals)
85
+ end
86
+ end
87
+ end if additional_query
88
+ end
89
+ end
90
+
91
+ def run
92
+ dump_mapping if @mapping_file
93
+ gz = Zlib::GzipWriter.open gz_output
94
+ count = 0
95
+ create_scanner.each do |document|
96
+ document.each do |record|
97
+ json_doc = record.to_hash.except(:type, :_index, :_explanation, :_score, :_version, :highlight, :sort).to_json
98
+ gz.puts json_doc
99
+ count += 1
100
+ end
101
+ end
102
+ gz.close
103
+ puts "#{@index} backup complete. #{count} records written"
104
+ end
105
+ end
106
+
107
+ class ESRestore
108
+
109
+ def initialize(input, options = {})
110
+ @index = options[:index]
111
+ @batch_size = options[:batch_size].to_i
112
+ @gz_input = Zlib::GzipReader.open(input)
113
+ @mapping_file = options[:mappings]
114
+ end
115
+
116
+ def create_index
117
+ index = Tire::Index.new @index
118
+ options = @mapping_file ? { mappings: MultiJson.load(File.read(@mapping_file)) } : {}
119
+ index.create(options) unless index.exists?
120
+ index
121
+ end
122
+
123
+ def run
124
+ reindex = create_index
125
+ count, documents = 0, []
126
+ @gz_input.each_line do |json|
127
+ documents << MultiJson.load(json)
128
+ count += 1
129
+ if count % @batch_size == 0
130
+ reindex.bulk_create documents
131
+ puts "#{count} records loaded"
132
+ documents.clear
133
+ end
134
+ end
135
+ @gz_input.close()
136
+ reindex.bulk_create documents if not documents.empty?
137
+ puts "#{@index} restore complete with #{count} records loaded"
138
+ end
139
+ end
140
+
141
+ class ESDup
142
+
143
+ def initialize(input, options = {})
144
+ @index = options[:index]
145
+ @batch_size = options[:batch_size].to_i
146
+ @gz_input = Zlib::GzipReader.open(input)
147
+ @mapping_file = options[:mappings]
148
+ end
149
+
150
+ def create_index
151
+ index = Tire::Index.new @index
152
+ options = @mapping_file ? { mappings: MultiJson.load(File.read(@mapping_file)) } : {}
153
+ index.create(options) unless index.exists?
154
+ index
155
+ end
156
+
157
+ def run
158
+ reindex = create_index
159
+ count, documents = 0, []
160
+ @gz_input.each_line do |json|
161
+ line = MultiJson.load(json)
162
+ line.delete("_id")
163
+ line.delete("id")
164
+ documents << line
165
+ count += 1
166
+ if count % @batch_size == 0
167
+ reindex.bulk_create documents
168
+ puts "#{count} records loaded"
169
+ documents.clear
170
+ end
171
+ end
172
+ @gz_input.close()
173
+ reindex.bulk_create documents if not documents.empty?
174
+ puts "#{@index} restore complete with #{count} records loaded"
175
+ end
176
+ end
177
+
178
+ case command = Settings.rest.shift.to_s.to_sym
179
+ when :restore then ESRestore.new(Settings.rest.shift, Settings.to_hash).run
180
+ when :backup then ESBackup.new(Settings.rest.shift, Settings.to_hash).run
181
+ when :duplicate then ESDup.new(Settings.rest.shift, Settings.to_hash).run
182
+ else abort Settings.help("Must specify either backup, restore or duplicate. Got <#{command}>")
183
+ end
184
+
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Simple script to dump elasticsearch indexes as raw JSON
4
+
5
+ require 'tire'
6
+ require 'zlib'
7
+ require 'socket'
8
+ require 'pathname'
9
+ require 'multi_json'
10
+
11
+ class ESBackup
12
+
13
+ def initialize(output_dir, options = {})
14
+ Tire::Configuration.url "http://#{options[:host]}:#{options[:port]}"
15
+ @output_dir = output_dir || ''
16
+ @index = options[:index]
17
+ @batch_size = options[:batch_size].to_i
18
+ @mapping_file = options[:mappings]
19
+ if options[:query].nil?
20
+ @query = nil
21
+ else
22
+ @query = MultiJson.load(options[:query]) rescue nil
23
+ end
24
+ if options[:dump_file].nil?
25
+ @dump_file = @index
26
+ else
27
+ @dump_file = options[:dump_file]
28
+ end
29
+ end
30
+
31
+ def dump_mapping
32
+ index = Tire::Index.new @index
33
+ File.open(@mapping_file, 'w'){ |f| f.puts index.mapping.to_json }
34
+ end
35
+
36
+ def fullpath dir
37
+ basedir = dir.start_with?('/') ? dir : File.join(Dir.pwd, dir)
38
+ FileUtils.mkdir_p(basedir)
39
+ basedir
40
+ end
41
+
42
+ def gz_output
43
+ File.join(fullpath(@output_dir), @index + '.gz')
44
+ end
45
+
46
+ def create_scanner
47
+ scan_opts = { size: @batch_size }
48
+ additional_query = @query
49
+ Tire::Search::Scan.new(@index, scan_opts) do
50
+ # This is fucking stupid; why people have to be cute and make everything DSL only
51
+ # I'll never understand, but the person who wrote this gem has forced us to ONLY be able to
52
+ # ask queries in this manner.
53
+ query do
54
+ additional_query.each_pair do |key, vals|
55
+ case vals
56
+ # Assuming here that you are only asking for one field at a time...this is getting hacky fast
57
+ when Hash then self.send(key.to_sym, *vals.to_a.flatten)
58
+ when Array then self.send(key.to_sym, *vals)
59
+ end
60
+ end
61
+ end if additional_query
62
+ end
63
+ end
64
+
65
+ def run
66
+ dump_mapping if @mapping_file
67
+ gz = Zlib::GzipWriter.open gz_output
68
+ count = 0
69
+ create_scanner.each do |document|
70
+ document.each do |record|
71
+ json_doc = record.to_hash.except(:type, :_index, :_explanation, :_score, :_version, :highlight, :sort).to_json
72
+ gz.puts json_doc
73
+ count += 1
74
+ end
75
+ end
76
+ gz.close
77
+ puts "#{@index} backup complete. #{count} records written"
78
+ end
79
+ end
80
+
81
+ class ESRestore
82
+
83
+ def initialize(input, options = {})
84
+ Tire::Configuration.url "http://#{options[:host]}:#{options[:port]}"
85
+ @index = options[:index]
86
+ @batch_size = options[:batch_size].to_i
87
+ @gz_input = Zlib::GzipReader.open(input)
88
+ @mapping_file = options[:mappings]
89
+ end
90
+
91
+ def create_index
92
+ index = Tire::Index.new @index
93
+ options = @mapping_file ? { mappings: MultiJson.load(File.read(@mapping_file)) } : {}
94
+ index.create(options) unless index.exists?
95
+ index
96
+ end
97
+
98
+ def run
99
+ reindex = create_index
100
+ count, documents = 0, []
101
+ @gz_input.each_line do |json|
102
+ documents << MultiJson.load(json)
103
+ count += 1
104
+ if count % @batch_size == 0
105
+ reindex.bulk_create documents
106
+ puts "#{count} records loaded"
107
+ documents.clear
108
+ end
109
+ end
110
+ @gz_input.close()
111
+ reindex.bulk_create documents if not documents.empty?
112
+ puts "#{@index} restore complete with #{count} records loaded"
113
+ end
114
+ end
115
+
116
+ class ESDup
117
+
118
+ def initialize(input, options = {})
119
+ Tire::Configuration.url "http://#{options[:host]}:#{options[:port]}"
120
+ @index = options[:index]
121
+ @batch_size = options[:batch_size].to_i
122
+ @gz_input = Zlib::GzipReader.open(input)
123
+ @mapping_file = options[:mappings]
124
+ end
125
+
126
+ def create_index
127
+ index = Tire::Index.new @index
128
+ options = @mapping_file ? { mappings: MultiJson.load(File.read(@mapping_file)) } : {}
129
+ index.create(options) unless index.exists?
130
+ index
131
+ end
132
+
133
+ def run
134
+ reindex = create_index
135
+ count, documents = 0, []
136
+ @gz_input.each_line do |json|
137
+ line = MultiJson.load(json)
138
+ line.delete("_id")
139
+ line.delete("id")
140
+ documents << line
141
+ count += 1
142
+ if count % @batch_size == 0
143
+ reindex.bulk_create documents
144
+ puts "#{count} records loaded"
145
+ documents.clear
146
+ end
147
+ end
148
+ @gz_input.close()
149
+ reindex.bulk_create documents if not documents.empty?
150
+ puts "#{@index} restore complete with #{count} records loaded"
151
+ end
152
+ end
153
+
@@ -0,0 +1,5 @@
1
+ for foo in flight_id metric tb_h feature base_feature metric_feature cnt; do
2
+ echo $foo;
3
+ /home/maphysics/.rbenv/shims/ruby /home/maphysics/GitProjects/wonderdog/test/getFields.rb --dump=/home/maphysics/GitProjects/wonderdog/test/flight_count_20130405 --field=$foo >> $foo.txt ;
4
+ cat $foo.txt |sort | uniq -c |sort -n | wc -l;
5
+ done
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'configliere'
3
+ require 'json'
4
+ require 'multi_json'
5
+
6
+ Settings.use :commandline
7
+ Settings.use :config_block
8
+ Settings.define :dump
9
+ Settings.define :field
10
+ Settings.resolve!
11
+
12
+ def get_value_counts(dump, field)
13
+ File.open(dump).each do |line|
14
+ record = MultiJson.load(line)
15
+ puts record[field]
16
+ end
17
+ end
18
+
19
+ get_value_counts(Settings.dump, Settings.field)
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'json'
5
+ require 'time'
6
+
7
+
8
+ #require 'random'
9
+
10
+ ########################################################################################################################
11
+ # This program is designed to read an elasticsearch log file and return #
12
+ # information about how long a slow process took, run the query, and #
13
+ # return information about how long it took to run the query again. #
14
+ # Example command: #
15
+ # ruby ./replay.rb --logfile=/var/log/elasticsearch/patrick.log --port=9200 --host=localhost #
16
+ ########################################################################################################################
17
+
18
+ ########################################################################################################################
19
+ # Global variables for storing metadata #
20
+ ########################################################################################################################
21
+ @slowlog_lines = []
22
+ @metadata_hash = {}
23
+
24
+ ########################################################################################################################
25
+ # Parse logfile, grab: #
26
+ # *the timestamp #
27
+ # *the index #
28
+ # *the node #
29
+ # *the type of search #
30
+ # *the time in millisecond #
31
+ # *At least first 50 char of query #
32
+ ########################################################################################################################
33
+
34
+ class ParseMetaData
35
+ attr_accessor :metaData
36
+
37
+ def initialize(metaString, metaArray = [])
38
+ @metaString = metaString
39
+ @metaArray = metaArray
40
+ @metaData = {}
41
+ @bracket_pairs = get_bracket_pair_indexes
42
+ end
43
+
44
+ def get_left_bracket_indexes
45
+ @metaString.enum_for(:scan, Regexp.new('\[')).map {Regexp.last_match.begin(0)}
46
+ end
47
+
48
+ def get_right_bracket_indexes
49
+ @metaString.enum_for(:scan, Regexp.new('\]')).map {Regexp.last_match.begin(0)}
50
+ end
51
+
52
+ def get_bracket_pair_indexes
53
+ get_left_bracket_indexes.zip(get_right_bracket_indexes)
54
+ end
55
+
56
+ def get_query
57
+ startInd = @metaString.enum_for(:scan, Regexp.new(' source\[')).map {Regexp.last_match.begin(0)+8}
58
+ endInd = @metaString.enum_for(:scan, Regexp.new('_source\[')).map {Regexp.last_match.begin(0)-9}
59
+ @metaData["query"] = @metaString[startInd[0]..endInd[0]]
60
+ end
61
+
62
+ def find_meta_data(meta)
63
+ start = @metaString.enum_for(:scan, Regexp.new(meta)).map {Regexp.last_match.begin(0) + meta.size}
64
+ index = get_left_bracket_indexes.index(start[0])
65
+ unless index.nil?
66
+ bracket_pair = @bracket_pairs[index]
67
+ #puts @metaString[bracket_pair[0]+1..bracket_pair[1]-1].inspect
68
+ @metaData[meta] = @metaString[bracket_pair[0]+1..bracket_pair[1]-1]
69
+ end
70
+ end
71
+
72
+ def get_extra_meta_data
73
+ @metaArray.each do |meta|
74
+ find_meta_data(meta)
75
+ end
76
+ end
77
+
78
+ def get_basic_meta_data
79
+ #FIXME! Make this dynamic and depended on the first four [] to contain the same things everytime
80
+ @metaData["timestamp"] = @metaString[@bracket_pairs[0][0]+1..@bracket_pairs[0][1]-1]
81
+ @metaData["node"] = @metaString[@bracket_pairs[3][0]+1..@bracket_pairs[3][1]-1]
82
+ @metaData["index"] = @metaString[@bracket_pairs[4][0]+1..@bracket_pairs[4][1]-1]
83
+ end
84
+
85
+ def get_meta_data
86
+ get_basic_meta_data
87
+ get_query
88
+ unless @metaArray == []
89
+ get_extra_meta_data
90
+ end
91
+ end
92
+ end
93
+
94
+ def parse_logline(line, metaArray)
95
+
96
+ if (line =~ %r{, source\[(.*)\], extra_source})
97
+ query = $1
98
+ else
99
+ warn("couldn't parse line")
100
+ return
101
+ end
102
+
103
+ #puts line
104
+ parser = ParseMetaData.new(line, metaArray)
105
+ parser.get_meta_data
106
+
107
+ return parser.metaData["query"], parser.metaData
108
+ end
109
+
110
+ ########################################################################################################################
111
+ # Return the following info to stdout as tab delimited: #
112
+ # Current time #
113
+ # Original timestamp #
114
+ # Duration of query in log #
115
+ # Duration of re-ran query according to elastic search #
116
+ # Duration of re-ran query according to the wall clock #
117
+ # The meta captured from the logfile #
118
+ # A snippet of query #
119
+ # Extra source data from logfile #
120
+ ########################################################################################################################
121
+ class Replay
122
+
123
+ def initialize(logfile, host, port, preference, routing)
124
+ @logfile = logfile
125
+ @host = host
126
+ @port = port
127
+ @preference = preference
128
+ @routing = routing
129
+ end
130
+
131
+ def header()
132
+ puts "\n"
133
+ puts %w[current_timestamp original_timestamp es_duration(ms) new_duration(ms) clock_time_duration(ms) node index query_fragment].join("\t")
134
+ end
135
+
136
+ def output(query, data, malformed=false)
137
+ query_fragment = query[0..49]
138
+ if malformed
139
+ puts "malformed"
140
+ puts query_fragment
141
+ else
142
+ took = data['took'].to_s
143
+ current_time = data['new_timestamp'].to_s
144
+ original_timestamp = data['timestamp'].to_s
145
+ es_duration = data['original_dur'].to_s
146
+ new_duration = data['new_duration'].to_i.to_s
147
+ node = data['node'].to_s
148
+ index = data['index'].to_s
149
+ if Random.rand() < 0.1
150
+ header
151
+ end
152
+ puts [current_time, original_timestamp, es_duration, took, new_duration, node, index, query_fragment].join("\t")
153
+ end
154
+ end
155
+
156
+ def build_curl_command_string(query, data)
157
+ base_uri = "'#{@host}:#{@port}/#{data['index']}/_search"
158
+ if @preference[0] && @routing[0]
159
+ base_uri.concat("?preference=#{@preference[1]},routing=#{@routing[1]}")
160
+ elsif @preference[0] && !@routing[0]
161
+ base_uri.concat("?reference=#{@preference[1]}")
162
+ elsif @routing[0] && !@preference[0]
163
+ base_uri.concat("routing=#{@routing[1]}")
164
+ end
165
+ curl_command = "curl -s -XGET ".concat(base_uri)
166
+ curl_command.concat("/' -d '#{query}'")
167
+ end
168
+
169
+ ########################################################################################################################
170
+ # Execute slow query from log #
171
+ ########################################################################################################################
172
+
173
+ def execute_query(total_took, query, data)
174
+ if query.include? " " or query.index('(\\\'.*?\\\')').nil?
175
+ if data['search_type'] == "QUERY_THEN_FETCH"
176
+ data['new_timestamp'] = Time.now
177
+ data['new_start_time'] = Time.now.to_f * 1000
178
+ cmd = build_curl_command_string(query, data)
179
+ #puts cmd
180
+ curl_result = `#{cmd}`
181
+ #puts curl_result
182
+ #puts "\n"
183
+ data['new_end_time'] = Time.now.to_f * 1000
184
+ data['new_duration'] = data['new_end_time'] - data['new_start_time']
185
+ data['original_dur'] = data['took']
186
+ data = data.merge(JSON.parse(curl_result))
187
+ output(query, data)
188
+ else
189
+ puts "error don't know search type, please throw an exception here"
190
+ end
191
+ else
192
+ puts "malformed query string"
193
+ puts query
194
+ output(query, data, malformed=true)
195
+ end
196
+ total_took + data['new_duration'].to_i
197
+ end
198
+
199
+ ########################################################################################################################
200
+ # MAIN #
201
+ ########################################################################################################################
202
+
203
+ def run
204
+ sl_regex = Regexp.new(('(slowlog\\.query)'), Regexp::IGNORECASE)
205
+ metaArray = %w[took took_millis types search_type total_shards]
206
+ header
207
+ total_took = 0
208
+ File.readlines(@logfile).each do |line|
209
+ if sl_regex.match(line)
210
+ query, query_hash = parse_logline(line, metaArray)
211
+ total_took = execute_query(total_took, query, query_hash)
212
+ end
213
+ end
214
+ total_took /= 60000.0
215
+ puts "All together the slow logs took: #{total_took}min"
216
+ end
217
+ end
218
+
219
+