groonga-query-log 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2011-2012 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -38,6 +38,7 @@ module Groonga
38
38
  def parse(input, &block)
39
39
  current_statistics = {}
40
40
  input.each_line do |line|
41
+ next unless line.valid_encoding?
41
42
  case line
42
43
  when /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\.(\d+)\|(.+?)\|([>:<])/
43
44
  year, month, day, hour, minutes, seconds, micro_seconds =
@@ -72,7 +73,7 @@ module Groonga
72
73
  :elapsed => elapsed.to_i,
73
74
  :n_records => n_records)
74
75
  when "<"
75
- return unless /\A(\d+) rc=(\d+)/ =~ rest
76
+ return unless /\A(\d+) rc=(-?\d+)/ =~ rest
76
77
  elapsed = $1
77
78
  return_code = $2
78
79
  statistic = current_statistics.delete(context_id)
@@ -0,0 +1,196 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "time"
20
+ require "thread"
21
+
22
+ require "groonga/client"
23
+
24
+ require "groonga/query-log/parser"
25
+
26
+ module Groonga
27
+ module QueryLog
28
+ class Replayer
29
+ def initialize(options)
30
+ @options = options
31
+ @queue = SizedQueue.new(@options.request_queue_size)
32
+ @responses = Queue.new
33
+ end
34
+
35
+ def replay(input)
36
+ producer = run_producer(input)
37
+ consumers = run_consumers
38
+ response_logger = run_response_logger
39
+ producer.join
40
+ consumers.each(&:join)
41
+ response_logger.join
42
+ end
43
+
44
+ private
45
+ def run_producer(input)
46
+ Thread.new do
47
+ parser = Parser.new
48
+ id = 0
49
+ @options.create_request_output do |output|
50
+ parser.parse(input) do |statistic|
51
+ next unless target_command?(statistic.command)
52
+ # TODO: validate orignal_source is one line
53
+ output.puts(statistic.command.original_source)
54
+ @queue.push([id, statistic])
55
+ id += 1
56
+ end
57
+ end
58
+ @options.n_clients.times do
59
+ @queue.push(nil)
60
+ end
61
+ end
62
+ end
63
+
64
+ def run_consumers
65
+ @options.n_clients.times.collect do
66
+ Thread.new do
67
+ loop do
68
+ break if run_consumer
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def run_consumer
75
+ @options.create_client do |client|
76
+ loop do
77
+ id, statistic = @queue.pop
78
+ if id.nil?
79
+ @responses.push(nil)
80
+ return true
81
+ end
82
+ begin
83
+ replay_command(client, id, statistic.command)
84
+ rescue Groonga::Client::Connection::Error
85
+ # TODO: add error log mechanism
86
+ $stderr.puts(Time.now.iso8601)
87
+ $stderr.puts(statistic.command.original_source)
88
+ $stderr.puts($!.raw_error.message)
89
+ $stderr.puts($!.raw_error.backtrace)
90
+ return false
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def replay_command(client, id, command)
97
+ command["cache"] = "no" if @options.disable_cache?
98
+ response = client.execute(command)
99
+ @responses.push(response)
100
+ end
101
+
102
+ def run_response_logger
103
+ Thread.new do
104
+ @options.create_responses_output do |output|
105
+ loop do
106
+ response = @responses.pop
107
+ break if response.nil?
108
+ # TODO: ensure response is one line
109
+ # TODO: reorder by ID
110
+ output.puts(response.raw)
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def target_command?(command)
117
+ @options.target_command_name?(command.name)
118
+ end
119
+
120
+ class NullOutput
121
+ class << self
122
+ def open
123
+ output = new
124
+ if block_given?
125
+ yield(output)
126
+ else
127
+ output
128
+ end
129
+ end
130
+ end
131
+
132
+ def puts(string)
133
+ end
134
+ end
135
+
136
+ class Options
137
+ attr_accessor :host
138
+ attr_accessor :port
139
+ attr_accessor :protocol
140
+ attr_accessor :n_clients
141
+ attr_writer :request_queue_size
142
+ attr_accessor :target_command_names
143
+ def initialize
144
+ @host = "127.0.0.1"
145
+ @port = 10041
146
+ @protocol = :gqtp
147
+ @n_clients = 8
148
+ @request_queue_size = nil
149
+ @disable_cache = false
150
+ @requests_path = nil
151
+ @responses_path = nil
152
+ @target_command_names = ["*"]
153
+ end
154
+
155
+ def create_client(&block)
156
+ Groonga::Client.open(:host => @host,
157
+ :port => @port,
158
+ :protocol => @protocol,
159
+ &block)
160
+ end
161
+
162
+ def create_request_output(&block)
163
+ if @requests_path
164
+ File.open(@requests_path, "w", &block)
165
+ else
166
+ NullOutput.open(&block)
167
+ end
168
+ end
169
+
170
+ def create_responses_output(&block)
171
+ if @responses_path
172
+ File.open(@responses_path, "w", &block)
173
+ else
174
+ NullOutput.open(&block)
175
+ end
176
+ end
177
+
178
+ def request_queue_size
179
+ @request_queue_size || @n_clients * 3
180
+ end
181
+
182
+ def disable_cache?
183
+ @disable_cache
184
+ end
185
+
186
+ def target_command_name?(name)
187
+ @target_command_names.any? do |name_pattern|
188
+ flags = 0
189
+ flags |= File::FNM_EXTGLOB if File.const_defined?(:FNM_EXTGLOB)
190
+ File.fnmatch(name_pattern, name, flags)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -18,6 +18,6 @@
18
18
 
19
19
  module Groonga
20
20
  module QueryLog
21
- VERSION = "1.0.1"
21
+ VERSION = "1.0.2"
22
22
  end
23
23
  end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -18,20 +18,35 @@
18
18
 
19
19
  $VERBOSE = true
20
20
 
21
- $KCODE = "u" if RUBY_VERSION < "1.9"
21
+ require "pathname"
22
22
 
23
- base_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
24
- lib_dir = File.join(base_dir, "lib")
25
- test_dir = File.join(base_dir, "test")
23
+ base_dir = Pathname.new(__FILE__).dirname.parent.expand_path
24
+ top_dir = base_dir.parent
25
+
26
+ gqtp_base_dir = top_dir + "gqtp"
27
+ gqtp_lib_dir = gqtp_base_dir + "lib"
28
+ $LOAD_PATH.unshift(gqtp_lib_dir.to_s)
29
+
30
+ groonga_client_base_dir = top_dir + "groonga-client"
31
+ groonga_client_lib_dir = groonga_client_base_dir + "lib"
32
+ $LOAD_PATH.unshift(groonga_client_lib_dir.to_s)
33
+
34
+ groonga_command_base_dir = top_dir + "groonga-command"
35
+ groonga_command_lib_dir = groonga_command_base_dir + "lib"
36
+ $LOAD_PATH.unshift(groonga_command_lib_dir.to_s)
37
+
38
+ lib_dir = base_dir + "lib"
39
+ test_dir = base_dir + "test"
26
40
 
27
41
  require "test-unit"
28
42
  require "test/unit/notify"
43
+ require "test/unit/rr"
29
44
 
30
45
  Test::Unit::Priority.enable
31
46
 
32
- $LOAD_PATH.unshift(lib_dir)
47
+ $LOAD_PATH.unshift(lib_dir.to_s)
33
48
 
34
- $LOAD_PATH.unshift(test_dir)
49
+ $LOAD_PATH.unshift(test_dir.to_s)
35
50
  require "groonga-query-log-test-utils"
36
51
 
37
52
  Dir.glob("#{base_dir}/test/**/test{_,-}*.rb") do |file|
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2011-2012 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -18,16 +18,27 @@
18
18
 
19
19
  class ParserTest < Test::Unit::TestCase
20
20
  def test_load
21
- @log = <<-EOL
21
+ statistics = parse(<<-LOG)
22
22
  2012-12-13 11:15:21.628105|0x7fff148c8a50|>load --table Video
23
23
  2012-12-13 11:15:21.645119|0x7fff148c8a50|<000000017041150 rc=0
24
- EOL
24
+ LOG
25
+ parsed_command = statistics.first.command
26
+ assert_instance_of(Groonga::Command::Load, parsed_command)
27
+ end
28
+
29
+ def test_ignore_invalid_line
30
+ garbage = "\x80"
31
+ statistics = parse(<<-LOG)
32
+ 2012-12-13 11:15:20.628105|0x7fff148c8a50|>#{garbage}
33
+ 2012-12-13 11:15:21.628105|0x7fff148c8a50|>load --table Video
34
+ 2012-12-13 11:15:21.645119|0x7fff148c8a50|<000000017041150 rc=0
35
+ LOG
25
36
  parsed_command = statistics.first.command
26
37
  assert_instance_of(Groonga::Command::Load, parsed_command)
27
38
  end
28
39
 
29
40
  private
30
- def statistics
41
+ def parse(log)
31
42
  statistics = []
32
43
  parser = Groonga::QueryLog::Parser.new
33
44
  parser.parse(StringIO.new(log)) do |statistic|
@@ -36,13 +47,9 @@ EOL
36
47
  statistics
37
48
  end
38
49
 
39
- def log
40
- @log
41
- end
42
-
43
50
  class StatisticOperationTest < self
44
51
  def setup
45
- @log = <<-EOL
52
+ @statistics = parse(<<-LOG)
46
53
  2011-06-02 16:27:04.731685|5091e5c0|>/d/select.join?table=Entries&filter=local_name+%40+%22gsub%22+%26%26+description+%40+%22string%22&sortby=_score&output_columns=_key&drilldown=name,class
47
54
  2011-06-02 16:27:04.733539|5091e5c0|:000000001849451 filter(15)
48
55
  2011-06-02 16:27:04.734978|5091e5c0|:000000003293459 filter(13)
@@ -52,11 +59,12 @@ EOL
52
59
  2011-06-02 16:27:04.735606|5091e5c0|:000000003921419 drilldown(3)
53
60
  2011-06-02 16:27:04.735762|5091e5c0|:000000004077552 drilldown(2)
54
61
  2011-06-02 16:27:04.735808|5091e5c0|<000000004123726 rc=0
55
- EOL
62
+ LOG
63
+ @statistic = @statistics.first
56
64
  end
57
65
 
58
66
  def test_context
59
- operations = statistics.first.operations.collect do |operation|
67
+ operations = @statistic.operations.collect do |operation|
60
68
  [operation[:name], operation[:context]]
61
69
  end
62
70
  expected = [
@@ -72,7 +80,7 @@ EOL
72
80
  end
73
81
 
74
82
  def test_n_records
75
- operations = statistics.first.operations.collect do |operation|
83
+ operations = @statistic.operations.collect do |operation|
76
84
  [operation[:name], operation[:n_records]]
77
85
  end
78
86
  expected = [
@@ -87,4 +95,22 @@ EOL
87
95
  assert_equal(expected, operations)
88
96
  end
89
97
  end
98
+
99
+ class TestRC < self
100
+ def test_success
101
+ statistics = parse(<<-LOG)
102
+ 2012-12-13 11:15:21.628105|0x7fff148c8a50|>table_create --name Videos
103
+ 2012-12-13 11:15:21.645119|0x7fff148c8a50|<000000017041150 rc=0
104
+ LOG
105
+ assert_equal([0], statistics.collect(&:return_code))
106
+ end
107
+
108
+ def test_fialure
109
+ statistics = parse(<<-LOG)
110
+ 2012-12-13 11:15:21.628105|0x7fff148c8a50|>table_create --name Videos
111
+ 2012-12-13 11:15:21.645119|0x7fff148c8a50|<000000017041150 rc=-22
112
+ LOG
113
+ assert_equal([-22], statistics.collect(&:return_code))
114
+ end
115
+ end
90
116
  end
@@ -0,0 +1,119 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ class ReplayerTest < Test::Unit::TestCase
20
+ class OptionTest < self
21
+ class ClientTest < self
22
+ def setup
23
+ @options = Groonga::QueryLog::Replayer::Options.new
24
+ @options.n_clients = 1
25
+ end
26
+
27
+ def test_host
28
+ host = "example.com"
29
+ @options.host = host
30
+ mock_client_open(:host => host)
31
+ replay
32
+ end
33
+
34
+ def test_port
35
+ port = 2929
36
+ @options.port = 2929
37
+ mock_client_open(:port => 2929)
38
+ replay
39
+ end
40
+
41
+ private
42
+ def replay
43
+ replayer = Groonga::QueryLog::Replayer.new(@options)
44
+ replayer.replay(StringIO.new(""))
45
+ end
46
+
47
+ def mock_client_open(expected_options)
48
+ client = Object.new
49
+ default_options = {
50
+ :host => "127.0.0.1",
51
+ :port => 10041,
52
+ :protocol => :gqtp,
53
+ }
54
+ expected_open_options = default_options.merge(expected_options)
55
+ mock(Groonga::Client).open(expected_open_options).yields(client) do
56
+ client
57
+ end
58
+ end
59
+ end
60
+
61
+ class TargetCommandNameTest < self
62
+ def setup
63
+ @options = Groonga::QueryLog::Replayer::Options.new
64
+ end
65
+
66
+ def test_default
67
+ assert_true(@options.target_command_name?("shutdown"))
68
+ end
69
+
70
+ class GlobTest < self
71
+ def setup
72
+ super
73
+ @options.target_command_names = ["se*"]
74
+ end
75
+
76
+ def test_match
77
+ assert_true(@options.target_command_name?("select"))
78
+ end
79
+
80
+ def test_not_match
81
+ assert_false(@options.target_command_name?("status"))
82
+ end
83
+ end
84
+
85
+ class ExtGlobTest < self
86
+ def setup
87
+ super
88
+ @options.target_command_names = ["s{elect,tatus}"]
89
+ unless File.const_defined?(:FNM_EXTGLOB)
90
+ omit("File:::FNM_EXTGLOB (Ruby 2.0.0 or later) is required.")
91
+ end
92
+ end
93
+
94
+ def test_match
95
+ assert_true(@options.target_command_name?("select"))
96
+ end
97
+
98
+ def test_not_match
99
+ assert_false(@options.target_command_name?("selectX"))
100
+ end
101
+ end
102
+
103
+ class ExactMatchTest < self
104
+ def setup
105
+ super
106
+ @options.target_command_names = ["select"]
107
+ end
108
+
109
+ def test_match
110
+ assert_true(@options.target_command_name?("select"))
111
+ end
112
+
113
+ def test_not_match
114
+ assert_false(@options.target_command_name?("selectX"))
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end