norikra_query_debug.rb

NorikraQueryDebugとは
===================
 - お好きなテキストエディタでノリクラクエリを書くと
 - 書きなおすたびに自動的にアップロードしてくれつつ
 - クエリの結果がコンソールにながれるやつ。

コマンド
=======

```
ruby norikra_query_debug.rb start_debug query.txt --host NORIKRA_HOST
```

クエリファイル
============
"@@@"で始まる行で区切って複数のクエリを書きます。

```query.txt
@@@ name=query1 group=debug
SELECT ...
FROM ...
WHERE ...

@@@ name=query2 group=LOOPBACK(loopback_from_query2)
SELECT ...
FROM ...
WHERE ...

@@@ name=query3 group=debug
SELECT ...
FROM loopback_from_query2
WHERE ...

@@@ name=msg_per_second group=debug
SELECT
  COUNT(1) as mps
FROM
  target.win:time_batch(1sec)
```

コード
=====

```norikra_query_debug.rb
require "norikra-client"
require "thor"
require 'timeout'
require 'io/console'
require "ostruct"

class NorikraQueryDebug
  def initialize(host,port)
    @host = host
    @port = port
    @client = Norikra::Client.new(@host,@port)
    @event_count = 0
    @queries = []
  end

  def start_debug(query_file)
    puts "Hit 'q' to quit."
    next_fetch_time = Time.now
    loop do
      now = Time.now

      case input_cmd
      when :quit
        break
      end

      if file_updated?(query_file)
        @queries.each do |q|
          @client.deregister(q.name)
        end

        @queries = parse_query_file(query_file)
        @queries.each_with_index do |q,query_index|
          puts "################################################################"
          puts "## [#{'%03d' % [query_index+1]}] #{q.name},#{q.group} (#{query_file})"
          puts q.query
          if update_query(q.name, q.group, q.query)
            puts "## OK     name=#{q.name} group=#{q.group} host=#{@host} port=#{@port}"
            puts ""
          else
            puts "## FAILED name=#{q.name} group=#{q.group} host=#{@host} port=#{@port}"
            puts ""
          end
        end
        puts ""
      end

      if next_fetch_time < now
        @queries.each do |q|
          events = @client.event(q.name)
          events.each do |time,event|
            puts("[%d] %s | %s : %s" % [@event_count, Time.at(time).strftime("%Y-%m-%d %H:%M:%S"), q.name, event.inspect])
            @event_count += 1
          end
        end
        next_fetch_time = now+1
      end
    end

    @queries.each do |q|
      @client.deregister(q.name)
    end
  end

  def parse_query_file(filename)
    queries = []

    name  = nil
    group = nil
    query = ""
    query_index = 0

    s = File.read(filename)
    lines = ["@@@"] + s.lines
    commit_query = proc do
      if query!=""
        queries << OpenStruct.new({:name=>name, :group=>group, :query=>query})
        query_index += 1
      end
      name  = "query#{'%03d' % [query_index+1]}_#{`whoami`.strip}@#{`hostname`.strip}"
      group = "debug"
      query = ""
    end
    lines.each do |line|
      if line =~ /^@@@/
        commit_query.call
        if line =~ /name=(\S+)/
          name = $1
        end
        if line =~ /group=(\S+)/
          group = $1
          if group=="nil"
            group = nil
          end
        end
      else
        query << line
      end
    end
    commit_query.call
    return queries
  end

  def update_query(name, group, query)
    begin
      begin
        @client.register(name,group,query)
      rescue Norikra::RPC::ClientError => e
        if e.message =~ /query name.+already exists/
          @client.deregister(name)
          @client.register(name,group,query)
        else
          raise e
        end
      end
      return true
    rescue => e
      puts e.message
    end
    return false
  end

  def file_updated?(filename)
    unless File.exist?(filename)
      raise "not exist filename=#{filename}"
    end
    mtime = File.mtime(filename)
    size  = File.size(filename)

    @file_states ||= {}
    old_state = @file_states[filename]
    if old_state && old_state[:mtime]==mtime && old_state[:size]==size
      return false
    end
    @file_states[filename] = {:mtime=>mtime, :size=>size}
    return true
  end

  def input_cmd
    begin
      c = Timeout::timeout(0.1) do
        $stdin.getch
      end
      if c=="q"
        return :quit
      end
      if c=="\r"
        puts
      else
        putc(c)
      end
    rescue Timeout::Error => e
      return :timeout
    end
    return nil
  end

  class CLI < Thor
    desc "start_debug QUERY_FILE", "upload query for each update, and fetch result."
    option :host,  :type=>:string,  :default=>"localhost"
    option :port,  :type=>:numeric, :default=>26571
    def start_debug(query_file)
      nqd = NorikraQueryDebug.new(options[:host], options[:port])
      nqd.start_debug(query_file)
    end
    default_task :start_debug
  end
end

NorikraQueryDebug::CLI.start

```