Muninのプラグインを作るときなど、大きなサイズのログファイルを解析する必要がたまにありますよね。そんなとき、ファイルの先頭から処理をしていくとファイルサイズが増加するにしたがって処理時間も増えていってしまいます。Muninのプラグインの場合は最近5分間のデータだけあれば十分なので、ファイルの先頭からではなく、最後から処理する方が効率的です。最後から処理すると、ファイルサイズが大きくなっても処理時間にはほとんど影響がありません。
ということで、ファイルの最後から1行ずつ読み込む小さなRubyのクラスを作りました。groongaのリポジトリに入っているので、groongaと同じライセンスで利用できます。
class ReverseLineReader
def initialize(io)
@io = io
@io.seek(0, IO::SEEK_END)
@buffer = ""
@data = ""
end
def each
separator = $/
separator_length = separator.length
while read_to_buffer
loop do
index = @buffer.rindex(separator, @buffer.length - 1 - separator_length)
break if index.nil? or index.zero?
last_line = @buffer.slice!((index + separator_length)..-1)
yield(last_line)
end
end
yield(@buffer) unless @buffer.empty?
end
private
BYTES_PER_READ = 4096
def read
position = @io.pos
if position < BYTES_PER_READ
bytes_per_read = position
else
bytes_per_read = BYTES_PER_READ
end
if bytes_per_read.zero?
@data.replace("")
else
@io.seek(-bytes_per_read, IO::SEEK_CUR)
@io.read(bytes_per_read, @data)
@io.seek(-bytes_per_read, IO::SEEK_CUR)
end
@data
end
def read_to_buffer
data = read
if data.empty?
false
else
@buffer.insert(0, data)
true
end
end
end
以下のように使います。
File.open("/var/log/groonga/query.log", "r") do |file|
ReverseLineReader.new(file).each do |line|
break if no_more_need?(line)
# ...
end
end
ログファイルから直近のログだけを取り出して処理したいときなどに利用してみてはいかがでしょうか。