wonderdog 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+