groonga-query-log 1.3.3 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b787d986aa85a124beec78ff3b1da09915dacfb9012c1619120d6a1ff3526603
4
- data.tar.gz: 13f736d7fd43e6fa1f87ab5de39600ed71b17baf4a53bfa21e9f4ecca74753a6
3
+ metadata.gz: e09cac93999ccbd46a1521afa9ccae705d47795575d275d086a1cc15e16ed8f1
4
+ data.tar.gz: e39350cad4042ef656afc00e133db5913664e50ff9102ac7e11afb26a1fc7143
5
5
  SHA512:
6
- metadata.gz: 8e5fdcda766d04c1b57b391f0ebec4d86f986e441869aa884c6378494198dc60bdc731ef04d07199b5d0be9e41ec48b07b61b78c2657b2eac3d10ca8cecbfd5a
7
- data.tar.gz: cd6c5e20f7d73f8ef02ccc75b384178dbd3614179f797a19bad266ed16c8c7a6f0c792a46d899371d1bca7b382751d5b571e0af22d3244444d43be49b44ca986
6
+ metadata.gz: d06425f98da9e4d9402211298bf9b7e9baceb1735b37fb37fc0e634f8a71dae7c31bbd576682177650d72affaee9e0dbac953e0e0de26a000a121b4d15791684
7
+ data.tar.gz: 4a88d239858bd5e61ef0261c566039207cb95f36e2fad05ef0f847a967935765ee54128afe326c6a0e2989d071f2feb5f267f99e5136e4d15cee6b70dcab02f4
data/doc/text/news.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # News
2
2
 
3
+ ## 1.3.4: 2018-08-22
4
+
5
+ ### Improvements
6
+
7
+ * `groonga-query-log-check-crash`:
8
+
9
+ * Added support for logs generated by Mroonga.
10
+
11
+ * Added support for reporting unfinished processes.
12
+
13
+ * Added support for reporting successfully finished processes.
14
+
15
+ * Added support for reporting path that includes the last entry.
16
+
17
+ * `groonga-query-log-extract`:
18
+
19
+ * Added `--inspect-query` option.
20
+
21
+ * `groonga-query-log-analyze`:
22
+
23
+ * Added support conditions report for `range_filter`,
24
+ `logical_select` and `logical_range_filter`.
25
+
26
+ ### Fixes
27
+
28
+ * `groonga-query-log-check-crash`:
29
+
30
+ * Fixed memory leak detection logic.
31
+
3
32
  ## 1.3.3: 2018-07-05
4
33
 
5
34
  ### Improvements
@@ -143,7 +143,7 @@ module GroongaQueryLog
143
143
  return unless @collect_slow_statistics
144
144
  if statistic.slow?
145
145
  @n_slow_responses += 1
146
- if statistic.select_command?
146
+ if statistic.select_family_command?
147
147
  statistic.each_operation do |operation|
148
148
  next unless operation[:slow?]
149
149
  @n_slow_operations += 1
@@ -73,24 +73,39 @@ module GroongaQueryLog
73
73
  class GroongaProcess
74
74
  attr_reader :pid
75
75
  attr_reader :start_time
76
- attr_reader :log_path
76
+ attr_reader :start_log_path
77
77
  attr_accessor :last_time
78
+ attr_accessor :last_log_path
78
79
  attr_accessor :n_leaks
79
80
  attr_writer :crashed
81
+ attr_writer :finished
80
82
  attr_reader :important_entries
81
- def initialize(pid, start_time, log_path)
83
+ def initialize(pid, start_time, start_log_path)
82
84
  @pid = pid
83
85
  @start_time = start_time
84
86
  @last_time = @start_time
85
- @log_path = log_path
87
+ @start_log_path = start_log_path
88
+ @last_log_path = @start_log_path
86
89
  @n_leaks = 0
87
90
  @crashed = false
91
+ @finished = false
88
92
  @important_entries = []
89
93
  end
90
94
 
91
95
  def crashed?
92
96
  @crashed
93
97
  end
98
+
99
+ def finished?
100
+ @finished
101
+ end
102
+
103
+ def successfully_finished?
104
+ return false if crashed?
105
+ return false unless finished?
106
+
107
+ true
108
+ end
94
109
  end
95
110
 
96
111
  class Checker
@@ -101,12 +116,38 @@ module GroongaQueryLog
101
116
  def check
102
117
  processes = ProcessEnumerator.new(@general_log_paths)
103
118
  processes.each do |process|
104
- if process.crashed?
105
- p [:crashed,
119
+ need_query_log_parsing = true
120
+ if process.successfully_finished?
121
+ need_query_log_parsing = false
122
+ p [:process,
123
+ :success,
106
124
  process.start_time.iso8601,
107
125
  process.last_time.iso8601,
108
126
  process.pid,
109
- process.log_path]
127
+ process.start_log_path,
128
+ process.last_log_path]
129
+ elsif process.crashed?
130
+ p [:process,
131
+ :crashed,
132
+ process.start_time.iso8601,
133
+ process.last_time.iso8601,
134
+ process.pid,
135
+ process.start_log_path,
136
+ process.last_log_path]
137
+ else
138
+ p [:process,
139
+ :unfinished,
140
+ process.start_time.iso8601,
141
+ process.pid,
142
+ process.start_log_path]
143
+ end
144
+
145
+ unless process.n_leaks.zero?
146
+ p [:leak,
147
+ process.n_leaks,
148
+ process.last_time.iso8601,
149
+ process.pid,
150
+ process.last_log_path]
110
151
  end
111
152
 
112
153
  unless process.important_entries.empty?
@@ -118,14 +159,7 @@ module GroongaQueryLog
118
159
  end
119
160
  end
120
161
 
121
- unless process.n_leaks.zero?
122
- p [:leak,
123
- process.n_leaks,
124
- process.last_time.iso8601,
125
- process.log_path]
126
- end
127
-
128
- next unless process.crashed?
162
+ next unless need_query_log_parsing
129
163
 
130
164
  start = process.start_time
131
165
  last = process.last_time
@@ -139,9 +173,12 @@ module GroongaQueryLog
139
173
  statistic)
140
174
  end
141
175
  parsing_statistics = query_log_parser.parsing_statistics
142
- unless parsing_statistics.empty?
176
+ target_parsing_statistics = parsing_statistics.reject do |statistic|
177
+ statistic.start_time < start
178
+ end
179
+ unless target_parsing_statistics.empty?
143
180
  puts("Running queries:")
144
- parsing_statistics.each do |statistic|
181
+ target_parsing_statistics.each do |statistic|
145
182
  puts("#{statistic.start_time.iso8601}:")
146
183
  puts(statistic.command.to_command_format(pretty_print: true))
147
184
  end
@@ -226,19 +263,23 @@ module GroongaQueryLog
226
263
  end
227
264
 
228
265
  case entry.message
229
- when /\Agrn_init:/
266
+ when /\Agrn_init:/, /\Amroonga \d+\.\d+ started\.\z/
230
267
  process = @running_processes[entry.pid]
231
268
  if process
269
+ process.finished = true
232
270
  process.crashed = true
233
271
  yield(process)
234
272
  @running_processes.delete(entry.pid)
235
273
  end
236
274
  process = GroongaProcess.new(entry.pid, entry.timestamp, path)
237
275
  @running_processes[entry.pid] = process
238
- when /\Agrn_fin \(\d+\)\z/
276
+ when /\Agrn_fin \((\d+)\)\z/
239
277
  n_leaks = $1.to_i
240
278
  process = @running_processes[entry.pid]
241
279
  process.n_leaks = n_leaks
280
+ process.last_time = entry.timestamp
281
+ process.last_log_path = path
282
+ process.finished = true
242
283
  yield(process)
243
284
  @running_processes.delete(entry.pid)
244
285
  else
@@ -250,9 +291,11 @@ module GroongaQueryLog
250
291
  process.important_entries << entry
251
292
  end
252
293
  process.last_time = entry.timestamp
294
+ process.last_log_path = path
253
295
  case entry.message
254
296
  when "-- CRASHED!!! --"
255
297
  process.crashed = true
298
+ process.finished = true
256
299
  when "----------------"
257
300
  if process.crashed?
258
301
  yield(process)
@@ -14,168 +14,239 @@
14
14
  # License along with this library; if not, write to the Free Software
15
15
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
16
 
17
- require "ostruct"
17
+ require "json"
18
18
  require "optparse"
19
+ require "ostruct"
19
20
  require "pathname"
20
21
 
21
22
  require "groonga-query-log"
22
23
  require "groonga-query-log/command-line"
23
24
 
24
25
  module GroongaQueryLog
25
- module Command
26
- class Extract < CommandLine
27
- attr_accessor :options
28
- attr_reader :option_parser
29
-
30
- def initialize
31
- @options = nil
32
- @option_parser = nil
33
- setup_options
34
- end
35
-
36
- # Executes extractor for Groonga's query logs.
37
- # "groonga-query-log-extract" command runs this method.
38
- #
39
- # @example
40
- # extractor = GroongaQueryLog::Command::Extract.new
41
- # extractor.run("--output", "commands.output",
42
- # "--command", "select",
43
- # "query.log")
44
- #
45
- # If only paths of query log files are specified,
46
- # this method prints command(s) of them to console.
47
- #
48
- # @param [Array<String>] arguments arguments for
49
- # groonga-query-log-extract. Please execute
50
- # "groonga-query-log-extract --help" or see #setup_options.
51
- def run(arguments)
52
- begin
53
- log_paths = @option_parser.parse!(arguments)
54
- rescue OptionParser::ParseError
55
- $stderr.puts($!.message)
56
- return false
57
- end
26
+ module Command
27
+ class Extract < CommandLine
28
+ attr_accessor :options
29
+ attr_reader :option_parser
30
+
31
+ def initialize
32
+ @options = nil
33
+ @option_parser = nil
34
+ setup_options
35
+ end
36
+
37
+ # Executes extractor for Groonga's query logs.
38
+ # "groonga-query-log-extract" command runs this method.
39
+ #
40
+ # @example
41
+ # extractor = GroongaQueryLog::Command::Extract.new
42
+ # extractor.run("--output", "commands.output",
43
+ # "--command", "select",
44
+ # "query.log")
45
+ #
46
+ # If only paths of query log files are specified,
47
+ # this method prints command(s) of them to console.
48
+ #
49
+ # @param [Array<String>] arguments arguments for
50
+ # groonga-query-log-extract. Please execute
51
+ # "groonga-query-log-extract --help" or see #setup_options.
52
+ def run(arguments)
53
+ begin
54
+ log_paths = @option_parser.parse!(arguments)
55
+ rescue OptionParser::ParseError
56
+ $stderr.puts($!.message)
57
+ return false
58
+ end
58
59
 
59
- begin
60
- if @options.output_path
61
- File.open(@options.output_path, "w") do |output|
62
- extract(log_paths, output)
63
- end
64
- else
65
- extract(log_paths, $stdout)
60
+ begin
61
+ if @options.output
62
+ File.open(@options.output, "w") do |output|
63
+ extract(log_paths, output)
66
64
  end
67
- rescue Interrupt, Errno::EPIPE
68
- rescue Error
69
- $stderr.puts($!.message)
70
- return false
65
+ else
66
+ extract(log_paths, $stdout)
71
67
  end
68
+ rescue Interrupt, Errno::EPIPE
69
+ rescue Error
70
+ $stderr.puts($!.message)
71
+ return false
72
+ end
72
73
 
73
- true
74
- end
75
-
76
- private
77
- def setup_options
78
- @options = OpenStruct.new
79
- @options.unify_format = nil
80
- @options.commands = []
81
- @options.exclude_commands = []
82
- @options.include_arguments = true
83
- @options.output_path = nil
84
- @option_parser = OptionParser.new do |parser|
85
- parser.version = VERSION
86
- parser.banner += " QUERY_LOG1 ..."
87
-
88
- available_formats = ["uri", "command"]
89
- parser.on("--unify-format=FORMAT",
90
- available_formats,
91
- "Unify command format to FORMAT.",
92
- "(#{available_formats.join(', ')})",
93
- "[not unify]") do |format|
94
- @options.unify_format = format
95
- end
74
+ true
75
+ end
96
76
 
97
- parser.on("--command=COMMAND",
98
- "Extract only COMMAND.",
99
- "To extract one or more commands,",
100
- "specify this command a number of times.",
101
- "Use /.../ as COMMAND to match command with regular expression.",
102
- "[all commands]") do |command|
103
- case command
104
- when /\A\/(.*)\/(i)?\z/
105
- @options.commands << Regexp.new($1, $2 == "i")
106
- when
107
- @options.commands << command
108
- end
109
- end
77
+ private
78
+ def setup_options
79
+ @options = OpenStruct.new
80
+ @options.unify_format = nil
81
+ @options.commands = []
82
+ @options.exclude_commands = []
83
+ @options.include_arguments = true
84
+ @options.output = nil
85
+ @options.inspect_query = false
86
+ @option_parser = OptionParser.new do |parser|
87
+ parser.version = VERSION
88
+ parser.banner += " QUERY_LOG1 ..."
89
+
90
+ available_formats = ["uri", "command"]
91
+ parser.on("--unify-format=FORMAT",
92
+ available_formats,
93
+ "Unify command format to FORMAT.",
94
+ "(#{available_formats.join(', ')})",
95
+ "[not unify]") do |format|
96
+ @options.unify_format = format
97
+ end
110
98
 
111
- parser.on("--exclude-command=COMMAND",
112
- "Don't extract COMMAND.",
113
- "To ignore one or more commands,",
114
- "specify this command a number of times.",
115
- "Use /.../ as COMMAND to match command with regular expression.",
116
- "[no commands]") do |command|
117
- case command
118
- when /\A\/(.*)\/(i)?\z/
119
- @options.exclude_commands << Regexp.new($1, $2 == "i")
120
- when
121
- @options.exclude_commands << command
122
- end
99
+ parser.on("--command=COMMAND",
100
+ "Extract only COMMAND.",
101
+ "To extract one or more commands,",
102
+ "specify this command a number of times.",
103
+ "Use /.../ as COMMAND to match command with regular expression.",
104
+ "[all commands]") do |command|
105
+ case command
106
+ when /\A\/(.*)\/(i)?\z/
107
+ @options.commands << Regexp.new($1, $2 == "i")
108
+ when
109
+ @options.commands << command
123
110
  end
111
+ end
124
112
 
125
- parser.on("--[no-]include-arguments",
126
- "Whether include command arguments",
127
- "[#{@options.include_arguments}]") do |include_arguments|
128
- @options.include_arguments = include_arguments
113
+ parser.on("--exclude-command=COMMAND",
114
+ "Don't extract COMMAND.",
115
+ "To ignore one or more commands,",
116
+ "specify this command a number of times.",
117
+ "Use /.../ as COMMAND to match command with regular expression.",
118
+ "[no commands]") do |command|
119
+ case command
120
+ when /\A\/(.*)\/(i)?\z/
121
+ @options.exclude_commands << Regexp.new($1, $2 == "i")
122
+ when
123
+ @options.exclude_commands << command
129
124
  end
125
+ end
130
126
 
131
- parser.on("--output=PATH",
132
- "Output to PATH.",
133
- "[standard output]") do |path|
134
- @options.output_path = path
135
- end
127
+ parser.on("--[no-]include-arguments",
128
+ "Whether include command arguments",
129
+ "[#{@options.include_arguments}]") do |include_arguments|
130
+ @options.include_arguments = include_arguments
136
131
  end
137
- end
138
132
 
139
- def extract(log_paths, output)
140
- parser = Parser.new
141
- parse_log(parser, log_paths) do |statistic|
142
- extract_command(statistic, output)
133
+ parser.on("--output=OUTPUT",
134
+ "If you specify path as OUTPUT,",
135
+ "executed commands are printed to the path.",
136
+ "If you specify a URL like",
137
+ "http://localhost:10041/?table=QueryLogEntries,",
138
+ "each entry are stored to QueryLogEntries Groonga table",
139
+ "running at localhost on port 10041.",
140
+ "[standard output]") do |output|
141
+ @options.output = output
143
142
  end
144
- end
145
143
 
146
- def extract_command(statistic, output)
147
- command = statistic.command
148
- return unless target?(command)
149
- unless @options.include_arguments
150
- command.arguments.clear
144
+ parser.on("--[no-]inspect-query",
145
+ "Inspect query.",
146
+ "[#{@options.inspect_query}]") do |boolean|
147
+ @options.inspect_query = boolean
151
148
  end
152
- command_text = nil
153
- case @options.unify_format
154
- when "uri"
155
- command_text = command.to_uri_format
156
- when "command"
157
- command_text = command.to_command_format
149
+ end
150
+ end
151
+
152
+ def extract(log_paths, output)
153
+ if @options.inspect_query
154
+ formatter = InspectFormatter.new(output)
155
+ else
156
+ formatter = DumpFormatter.new(output)
157
+ end
158
+ formatter.start
159
+ parser = Parser.new
160
+ parse_log(parser, log_paths) do |statistic|
161
+ extract_command(statistic, formatter)
162
+ end
163
+ formatter.finish
164
+ end
165
+
166
+ def extract_command(statistic, formatter)
167
+ command = statistic.command
168
+ return unless target?(command)
169
+ unless @options.include_arguments
170
+ command.arguments.clear
171
+ end
172
+ command_text = nil
173
+ case @options.unify_format
174
+ when "uri"
175
+ command_text = command.to_uri_format
176
+ when "command"
177
+ command_text = command.to_command_format
178
+ else
179
+ command_text = command.to_s
180
+ end
181
+ formatter.command(statistic, command_text)
182
+ end
183
+
184
+ def target?(command)
185
+ name = command.command_name
186
+ target_commands = @options.commands
187
+ exclude_commands = @options.exclude_commands
188
+
189
+ unless target_commands.empty?
190
+ return target_commands.any? {|target_command| target_command === name}
191
+ end
192
+
193
+ unless exclude_commands.empty?
194
+ return (not exclude_commands.any? {|exclude_command| exclude_command === name})
195
+ end
196
+
197
+ true
198
+ end
199
+
200
+ class InspectFormatter
201
+ def initialize(output)
202
+ @output = output
203
+ @first_comand = false
204
+ end
205
+
206
+ def start
207
+ @output.puts("[")
208
+ end
209
+
210
+ def command(statistic, command_text)
211
+ if @first_command
212
+ @first_command = false
158
213
  else
159
- command_text = command.to_s
214
+ @output.puts(",")
160
215
  end
161
- output.puts(command_text)
216
+ record = {
217
+ "start_time" => statistic.start_time,
218
+ "elapsed_time" => statistic.elapsed_in_seconds,
219
+ "last_time" => statistic.last_time,
220
+ "return_code" => statistic.return_code,
221
+ "command" => command_text,
222
+ }
223
+ statistic.command.arguments.each do |name, value|
224
+ record["argument_#{name}"] = value
225
+ end
226
+ @output.print(record.to_json)
227
+ end
228
+
229
+ def finish
230
+ @output.puts("") unless @first_comand
231
+ @output.puts("]")
162
232
  end
233
+ end
163
234
 
164
- def target?(command)
165
- name = command.command_name
166
- target_commands = @options.commands
167
- exclude_commands = @options.exclude_commands
235
+ class DumpFormatter
236
+ def initialize(output)
237
+ @output = output
238
+ end
168
239
 
169
- unless target_commands.empty?
170
- return target_commands.any? {|target_command| target_command === name}
171
- end
240
+ def start
241
+ end
172
242
 
173
- unless exclude_commands.empty?
174
- return (not exclude_commands.any? {|exclude_command| exclude_command === name})
175
- end
243
+ def command(statistic, command_text)
244
+ @output.puts(command_text)
245
+ end
176
246
 
177
- true
247
+ def finish
178
248
  end
179
249
  end
180
250
  end
251
+ end
181
252
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2011-2017 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2011-2018 Kouhei Sutou <kou@clear-code.com>
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -27,8 +27,6 @@ module GroongaQueryLog
27
27
  def initialize(context_id)
28
28
  @context_id = context_id
29
29
  @start_time = nil
30
- @command = nil
31
- @select_command = nil
32
30
  @raw_command = nil
33
31
  @operations = []
34
32
  @elapsed = nil
@@ -48,16 +46,7 @@ module GroongaQueryLog
48
46
  end
49
47
 
50
48
  def command
51
- Groonga::Command::Parser.parse(@raw_command) do |status, command|
52
- case status
53
- when :on_load_start
54
- @loading = false
55
- @command ||= command
56
- when :on_command
57
- @command ||= command
58
- end
59
- end
60
- @command
49
+ @command ||= parse_command
61
50
  end
62
51
 
63
52
  def elapsed_in_seconds
@@ -76,7 +65,6 @@ module GroongaQueryLog
76
65
  return to_enum(__method__) unless block_given?
77
66
 
78
67
  previous_elapsed = 0
79
- ensure_parse_command
80
68
  operation_context_context = {
81
69
  :filter_index => 0,
82
70
  :drilldown_index => 0,
@@ -114,8 +102,15 @@ module GroongaQueryLog
114
102
  _operations
115
103
  end
116
104
 
117
- def select_command?
118
- command.name == "select"
105
+ def select_family_command?
106
+ case command.command_name
107
+ when "select", "range_filter"
108
+ true
109
+ when "logical_select", "logical_range_filter"
110
+ true
111
+ else
112
+ false
113
+ end
119
114
  end
120
115
 
121
116
  def to_hash
@@ -148,12 +143,20 @@ module GroongaQueryLog
148
143
  end
149
144
 
150
145
  private
146
+ def parse_command
147
+ command = nil
148
+ Groonga::Command::Parser.parse(@raw_command) do |_status, _command|
149
+ command = _command
150
+ end
151
+ command
152
+ end
153
+
151
154
  def nano_seconds_to_seconds(nano_seconds)
152
155
  nano_seconds / 1000.0 / 1000.0 / 1000.0
153
156
  end
154
157
 
155
158
  def operation_context(operation, context)
156
- return nil if @select_command.nil?
159
+ return nil unless select_family_command?
157
160
 
158
161
  extra = operation[:extra]
159
162
  return extra if extra
@@ -161,34 +164,29 @@ module GroongaQueryLog
161
164
  label = operation[:name]
162
165
  case label
163
166
  when "filter"
164
- if @select_command.query and context[:query_used].nil?
167
+ if command.query and context[:query_used].nil?
165
168
  context[:query_used] = true
166
- "query: #{@select_command.query}"
169
+ "query: #{command.query}"
167
170
  else
168
171
  index = context[:filter_index]
169
172
  context[:filter_index] += 1
170
- @select_command.conditions[index]
173
+ command.conditions[index]
171
174
  end
172
175
  when "sort"
173
- @select_command.sortby
176
+ command.sortby
174
177
  when "score"
175
- @select_command.scorer
178
+ command.scorer
176
179
  when "output"
177
- @select_command.output_columns
180
+ command.output_columns
178
181
  when "drilldown"
179
182
  index = context[:drilldown_index]
180
183
  context[:drilldown_index] += 1
181
- @select_command.drilldowns[index]
184
+ command.drilldowns[index]
182
185
  else
183
186
  nil
184
187
  end
185
188
  end
186
189
 
187
- def ensure_parse_command
188
- return unless select_command?
189
- @select_command = Groonga::Command::Parser.parse(@raw_command)
190
- end
191
-
192
190
  def slow_operation?(elapsed)
193
191
  elapsed >= @slow_operation_threshold
194
192
  end
@@ -15,5 +15,5 @@
15
15
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
  module GroongaQueryLog
18
- VERSION = "1.3.3"
18
+ VERSION = "1.3.4"
19
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groonga-query-log
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kouhei Sutou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-06 00:00:00.000000000 Z
11
+ date: 2018-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: groonga-command-parser