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.
- data/.gitignore +2 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/wonderdog.iml +41 -0
- data/Gemfile +1 -1
- data/bin/estool +22 -1
- data/bin/squirrel.rb +108 -0
- data/lib/wonderdog.rb +3 -0
- data/lib/wonderdog/hadoop_invocation_override.rb +4 -1
- data/lib/wonderdog/version.rb +1 -1
- data/pom.xml +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/wonderdog/hadoop_invocation_override_spec.rb +1 -1
- data/squirrel/all_facets.rb +95 -0
- data/squirrel/change_es_index_settings.rb +19 -0
- data/squirrel/clear_es_caches.rb +30 -0
- data/squirrel/esbackup.rb +184 -0
- data/squirrel/esbackup_stripped.rb +153 -0
- data/squirrel/fields.sh +5 -0
- data/squirrel/getFields.rb +19 -0
- data/squirrel/replay.rb +219 -0
- data/squirrel/squirrel.rb +95 -0
- data/squirrel/warmer_interface.rb +59 -0
- data/src/main/java/com/infochimps/elasticsearch/ElasticSearchInputFormat.java +2 -2
- data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingInputFormat.java +14 -2
- data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingOutputFormat.java +20 -5
- data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingRecordReader.java +55 -26
- data/src/main/java/com/infochimps/elasticsearch/ElasticSearchStreamingRecordWriter.java +59 -22
- data/test/cardinality.rb +43 -0
- data/test/change_es_index_settings.rb +19 -0
- data/test/clear_es_caches.rb +30 -0
- data/test/config/mapping.yml +327 -0
- data/test/config/mappings.yml +328 -0
- data/test/count_check.txt +0 -0
- data/test/esbackup_stripped.rb +153 -0
- data/test/mapping.yml +327 -0
- data/test/medium_slow_queries +41 -0
- data/test/queries.txt +0 -0
- data/test/quick_test_slow_queries +4 -0
- data/test/run_pry.rb +3 -0
- data/test/some_slow_queries +53 -0
- data/test/warmer_interface.rb +64 -0
- data/test/warmindices.rb +65 -0
- data/wonderdog.gemspec +1 -1
- 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
|
+
|
data/squirrel/fields.sh
ADDED
@@ -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)
|
data/squirrel/replay.rb
ADDED
@@ -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
|
+
|