log2sql.rb #1

  • //
  • guest/
  • perforce_software/
  • log-analyzer/
  • psla/
  • log2sql.rb
  • View
  • Commits
  • Open Download .zip Download (23 KB)
#!/usr/bin/ruby
##############################################################################
#
# Copyright (c) 2008,2011 Perforce Software, Inc.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#  
# = Date
#   
#   $Date: 2019/03/01 $
#
# = Description
#
#   Output SQL queries generated by parsing commands logged in a Perforce
#   server log file.
#   The output can be imported directly in any SQL database.
#
#   log2sql populates the following tables:
#
#   +----------------+----------+------+-----+
#   |                 process                |
#   +----------------+----------+------+-----+
#   | Field          | Type     | Null | Key |
#   +----------------+----------+------+-----+
#   | processKey     | char(32) | NO   | PRI |
#   | lineNumber     | int      | NO   | PRI |
#   | startTime      | date     | NO   |     |
#   | endTime        | date     | YES  |     |
#   | computedLapse  | float    | YES  |     |
#   | completedLapse | float    | YES  |     |
#   | pid            | int      | NO   |     |
#   | user           | text     | NO   |     |
#   | workspace      | text     | NO   |     |
#   | ip             | text     | NO   |     |
#   | app            | text     | NO   |     |
#   | cmd            | text     | NO   |     |
#   | args           | text     | YES  |     |
#   | uCpu           | int      | YES  |     |
#   | sCpu           | int      | YES  |     |
#   | diskIn         | int      | YES  |     |
#   | diskOut        | int      | YES  |     |
#   | ipcIn          | int      | YES  |     |
#   | ipcOut         | int      | YES  |     |
#   | maxRss         | int      | YES  |     |
#   | pageFaults     | int      | YES  |     |
#   | rpcMsgsIn      | int      | YES  |     |
#   | rpcMsgsOut     | int      | YES  |     |
#   | rpcSizeIn      | int      | YES  |     |
#   | rpcSizeOut     | int      | YES  |     |
#   | rpcHimarkFwd   | int      | YES  |     |
#   | rpcHimarkRev   | int      | YES  |     |
#   | running        | int      | NO   |     |
#   | error          | text     | YES  |     |
#   +----------------+----------+------+-----+
#
#   +----------------+--------------+------+-----+
#   |                  tableUse                  |
#   +----------------+--------------+------+-----+
#   | Field          | Type         | Null | Key |
#   +----------------+--------------+------+-----+
#   | processKey     | char(32)     | NO   | PRI |
#   | lineNumber     | int          | NO   | PRI |
#   | tableName      | varchar(255) | NO   | PRI |
#   | pagesIn        | int          | YES  |     |
#   | pagesOut       | int          | YES  |     |
#   | pagesCached    | int          | YES  |     |
#   | readLocks      | int          | YES  |     |
#   | writeLocks     | int          | YES  |     |
#   | getRows        | int          | YES  |     |
#   | posRows        | int          | YES  |     |
#   | scanRows       | int          | YES  |     |
#   | putRows        | int          | YES  |     |
#   | delRows        | int          | YES  |     |
#   | totalReadWait  | int          | YES  |     |
#   | totalReadHeld  | int          | YES  |     |
#   | totalWriteWait | int          | YES  |     |
#   | totalWriteHeld | int          | YES  |     |
#   | maxReadWait    | int          | YES  |     |
#   | maxReadHeld    | int          | YES  |     |
#   | maxWriteWait   | int          | YES  |     |
#   | maxWriteHeld   | int          | YES  |     |
#   +----------------+--------------+------+-----+
#
# = Usage
#
#   log2sql.rb -d <database_name> log_file
#
# = Requirements
#  
#   Ruby: http://www.ruby-lang.org
#
##############################################################################

require "getoptlong"
require "time"
require "digest/md5"

class Command

  attr_accessor :processKey,:pid,:startTime,:endTime,:computedLapse,
                :completedLapse,:user,:workspace,:ip,:app,:name,:args,:uCpu,
                :sCpu,:diskIn,:diskOut,:ipcIn,:ipcOut,:maxRss,
                :pageFaults,:rpcMsgsIn,:rpcMsgsOut,:rpcSizeIn,:rpcSizeOut,
                :rpcHimarkFwd,:rpcHimarkRev,:running,:lineNumber,:error

  def initialize(processKey,lineNumber,pid,startTime,
                 user,workspace,ip,app,name,args)
      @processKey = processKey
      @lineNumber = lineNumber
      @pid = pid
      @startTime = startTime
      @user = escapeChar(user)
      @workspace = escapeChar(workspace)
      @ip = ip
      @app = app
      @name = name
      if args
        @args = escapeChar(args)
      else 
         @args = "NULL"
      end
      @endTime = @computedLapse = @completedLapse = @uCpu = "NULL"
      @sCpu = @diskIn = @diskOut = @ipcIn = @ipcOut = @maxRss = "NULL"
      @pageFaults = @rpcMsgsIn = @rpcMsgsOut = @rpcSizeOut = "NULL"
      @rpcSizeIn = @rpcHimarkFwd = @rpcHimarkRev = @error = "NULL"
      @running = 0
  end

  def escapeChar(str)
      str = str.gsub("\\"){"\\\\"}
      str = str.gsub("\""){"\\\""}
      return str
  end

  def setUsage(uCpu,sCpu,diskIn,diskOut,ipcIn,ipcOut,maxRss,pageFaults)
      @uCpu = uCpu
      @sCpu = sCpu
      @diskIn = diskIn
      @diskOut = diskOut
      @ipcIn = ipcIn
      @ipcOut = ipcOut
      @maxRss = maxRss
      @pageFaults = pageFaults
  end

  def setRpc(rpcMsgsIn,rpcMsgsOut,rpcSizeIn,
             rpcSizeOut,rpcHimarkFwd,rpcHimarkRev)
      @rpcMsgsIn = rpcMsgsIn
      @rpcMsgsOut = rpcMsgsOut
      @rpcSizeIn = rpcSizeIn
      @rpcSizeOut = rpcSizeOut
      @rpcHimarkFwd = rpcHimarkFwd
      @rpcHimarkRev = rpcHimarkRev
  end
end

class Table
    attr_accessor :processKey,:lineNumber,:tableName,
                  :pagesIn,:pagesOut,:pagesCached,:readLocks,
                  :writeLocks,:getRows,:posRows,:scanRows,:putRows,:delRow,
                  :totalReadWait,:totalReadHeld,
                  :totalWriteWait,:totalWriteHeld,
                  :maxReadWait,:maxReadHeld,
                  :maxWriteWait,:maxWriteHeld

    def initialize(processKey,lineNumber,tableName)
        @processKey = processKey
        @lineNumber = lineNumber
        @tableName = tableName
        @pagesIn = @pagesOut = @pagesCached = "NULL"
        @readLocks = @writeLocks = @getRows = @posRows = @scanRows = "NULL"
        @putRows = @delRow = @totalReadWait = @totalReadHeld = "NULL"
        @totalWriteWait = @totalWriteHeld = @maxReadWait = "NULL"
        @maxReadHeld = @maxWriteWait = @maxWriteHeld = "NULL"
    end

    def setPages(pagesIn,pagesOut,pagesCached)
        @pagesIn = pagesIn
        @pagesOut = pagesOut
        @pagesCached = pagesCached
    end

    def setLocksRows(readLocks,writeLocks,getRows,posRows,
                     scanRows,putRows,delRow)
        @readLocks = readLocks
        @writeLocks = writeLocks
        @getRows = getRows
        @posRows = posRows
        @scanRows = scanRows
        @putRows = putRows
        @delRow = delRow
    end

    def setTotalLock(totalReadWait,totalReadHeld,
                     totalWriteWait,totalWriteHeld)
        @totalReadWait = totalReadWait
        @totalReadHeld = totalReadHeld
        @totalWriteWait = totalWriteWait
        @totalWriteHeld = totalWriteHeld
    end

    def setMaxLock(maxReadWait,maxReadHeld,maxWriteWait,maxWriteHeld)
        @maxReadWait = maxReadWait
        @maxReadHeld = maxReadHeld
        @maxWriteWait = maxWriteWait
        @maxWriteHeld = maxWriteHeld
    end
    
end

class FifoList
      attr_accessor :max,:list

      def initialize(max)
          @list = Array.new
          @max = max
      end

      def add(cmd)
          if list.length + 1 > max
             list.delete_at(1)
          end
          list.push(cmd)
      end

      def remove(cmd)
          list.each_index do
          |i|
             if (list[i].processKey == cmd.processKey)
                list.delete_at(i)
                break
             end
          end
      end 

      def getLineNumber(cmd)
          lineNumber = 0
          list.each do
          |p|
             if (p.processKey == cmd.processKey)
                lineNumber = p.lineNumber
                break
             end
          end
          lineNumber
      end
end

class Log2sql

  attr_reader :logname,:dbname,:logfile,:ckpSize,
              :readBytes,:mod,:cmds,:lastCompletedCmds

  def initialize(logname, dbname)
      @logname = logname
      @dbname = dbname
      @ckpSize = File.size(logname)
      @logfile = File.open(logname,"r")
      @cmds = Hash.new
      @lastCompletedCmds = FifoList.new(1000)
      @readBytes = 0.0
      @mod = 10.0
      @running = 0
      db_create_database()
      db_create_process()
      db_create_tableUse()
      db_commit(FALSE)
      printf($stderr,"Processing %s: 0%%", logname)
      $stderr.flush
  end

  def terminate()
      flush_output()
      db_commit(TRUE)
  end

  def readLogfile()
      line = logfile.gets()
      if line 
         if line.respond_to?(:encode)
            line = line.encode("utf-8", 'binary', :invalid => :replace, :undef => :replace)
         end
         @readBytes += line.length
         if (((readBytes / ckpSize) * 100.0) / mod).floor == 1.0
            printf($stderr,"...%d%%", mod)
            $stderr.flush
            @mod += 10
         end
      end
      line
  end

  def getLineNumber()
      logfile.lineno
  end

  def db_commit(state)
      if (state)
         query = "COMMIT;"
      else
         query = "SET autocommit=0;"
      end
      puts(query)
  end

  def db_create_database()
      query = "CREATE DATABASE IF NOT EXISTS " + dbname + ";"
      puts(query)
      query = "USE " + dbname + ";"
      puts(query)
  end

  def db_create_process()
      query = "DROP TABLE IF EXISTS process;"
      puts(query)
      query = "CREATE TABLE process (processkey CHAR(32) NOT NULL, lineNumber INT NOT NULL, pid INT NOT NULL, startTime DATETIME NOT NULL,endTime DATETIME NULL, computedLapse FLOAT NULL,completedLapse FLOAT NULL, user TEXT NOT NULL, workspace TEXT NOT NULL, ip TEXT NOT NULL, app TEXT NOT NULL, cmd TEXT NOT NULL, args TEXT NULL, uCpu INT NULL, sCpu INT NULL, diskIn INT NULL, diskOut INT NULL, ipcIn INT NULL, ipcOut INT NULL, maxRss INT NULL, pageFaults INT NULL, rpcMsgsIn INT NULL, rpcMsgsOut INT NULL, rpcSizeIn INT NULL, rpcSizeOut INT NULL, rpcHimarkFwd INT NULL, rpcHimarkRev INT NULL, running INT NULL, error TEXT NULL, PRIMARY KEY (processkey, lineNumber));"
      puts(query)
  end

  def db_create_tableUse()
      query = "DROP TABLE IF EXISTS tableUse;"
      puts(query)
      query = "CREATE TABLE tableUse (processkey CHAR(32) NOT NULL, lineNumber INT NOT NULL, tableName VARCHAR(255) NOT NULL, pagesIn INT NULL, pagesOut INT NULL, pagesCached INT NULL, readLocks INT NULL, writeLocks INT NULL, getRows INT NULL, posRows INT NULL, scanRows INT NULL, putRows int NULL, delRows INT NULL, totalReadWait INT NULL, totalReadHeld INT NULL, totalWriteWait INT NULL, totalWriteHeld INT NULL, maxReadWait INT NULL, maxReadHeld INT NULL, maxWriteWait INT NULL, maxWriteHeld INT NULL, PRIMARY KEY (processkey, lineNumber, tableName));"
      puts(query)
  end

  def addCommand(cmd, vtrack)
      cmd.running = @running 
      cmds[cmd.pid] = cmd
      if !vtrack
         @running = @running + 1
      end
  end

  def deleteCommand(cmd, vtrack)
      if cmds.has_key?(cmd.pid)
         sql_process_insert(cmds[cmd.pid])
         cmds.delete(cmd.pid)
         if !vtrack
            @running = @running - 1
         end
      end
  end

  def computed(pid,computedLapse)
      if cmds.has_key?(pid)
         # sum all the compute values of a same command
         sum = cmds[pid].computedLapse.to_f
         sum += computedLapse.to_f
         cmds[pid].computedLapse = sum.to_s
      end
  end

  def completed(pid,endTime,completedLapse)
      if cmds.has_key?(pid)
         cmds[pid].endTime = endTime
         cmds[pid].completedLapse = completedLapse.to_f.to_s
         lastCompletedCmds.add(cmds[pid])
         deleteCommand(cmds[pid], FALSE)
      end 
  end

  def track(cmd,line)
      tables = Hash.new
      tableName = nil
      rpcMsgsIn = rpcMsgsOut = rpcSizeIn = rpcSizeOut = 0
      rpcHimarkFwd = rpcHimarRev =uCpu = 0
      sCpu = diskIn = diskOut = ipcIn = ipcOut = maxRss = pageFaults = 0
      readLocks = writeLocks = getRows = posRows = scanRows = 0
      putRows = delRow = totalReadWait = totalReadHeld = 0
      totalWriteWait = totalWriteHeld = maxReadWait = 0
      maxReadHeld = maxWriteWait = maxWriteHeld = 0
      lineNumber = lastCompletedCmds.getLineNumber(cmd)
      if lineNumber > 0
         cmd.lineNumber = lineNumber
         lastCompletedCmds.remove(cmd)
      else
         addCommand(cmd, TRUE)
      end
      while line && RE_TRACK.match(line)
            if match = RE_TRACK_LAPSE.match(line)
               cmd.completedLapse = match[1]
            elsif (match = RE_FAILED_AUTH.match(line)) ||
                  (match = RE_KILLED_BY.match(line)) ||
                  (match = RE_EXITED.match(line))
                  cmd.error = "\"" + match[1] + "\""
            elsif match = RE_TRACK_USAGE.match(line)
                  cmd.setUsage(match[1],match[2],match[3],match[4],
                              match[5],match[6],match[7],match[8])
            elsif match = RE_TRACK_RPC.match(line)
                  cmd.setRpc(match[1],match[2],match[3],
                             match[4],match[5],match[6])
            elsif match = RE_TRACK_TABLE.match(line)
                  tableName = match[1]
                  tables[tableName] = Table.new(cmd.processKey,
                                      cmd.lineNumber,tableName)
            elsif match = RE_TRACK_PAGES.match(line)
                  if tables.has_key?(tableName)     
                     tables[tableName].setPages(match[1],match[2],match[3])
                  end
            elsif match = RE_TRACK_LOCKS_ROWS.match(line)
                  if tables.has_key?(tableName)     
                     tables[tableName].setLocksRows(match[1],match[2],
                                                    match[3],match[4],
                                                    match[5],match[6],
                                                    match[7])
                  end
            elsif match = RE_TRACK_TOTAL_LOCK.match(line)
                  if tables.has_key?(tableName)     
                     tables[tableName].setTotalLock(match[1],match[2],
                                                    match[3],match[4])
                     # The max is only reported if there is more than one
                     # lock taken on the table
                     if (tables[tableName].readLocks.to_i < 2 &&
                         tables[tableName].writeLocks.to_i < 2)
                        tables[tableName].setMaxLock(match[1],match[2],
                                                     match[3],match[4])
                     end
                  end
            elsif match = RE_TRACK_MAX_LOCK.match(line)
                  if tables.has_key?(tableName)     
                     tables[tableName].setMaxLock(match[1],match[2],
                                                  match[3],match[4])
                  end
            end
            line = readLogfile()
      end
      deleteCommand(cmd, TRUE)
      sql_process_update(cmd)
      tables.values.each do
      |table|
         sql_tableUse_insert(table)
      end
  end

  def flush_output()
      cmds.values.each do
      |p|
         sql_process_insert(p)
      end
     $stderr.puts("")
  end

  def sql_process_insert(cmd)
      query = sprintf("INSERT IGNORE INTO process VALUES (\"%s\",%d,%d,%s,%s,%s,%s,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%s);",
                      cmd.processKey,cmd.lineNumber,cmd.pid,
                      cmd.startTime,cmd.endTime,cmd.computedLapse,
                      cmd.completedLapse,cmd.user,cmd.workspace,
                      cmd.ip,cmd.app,cmd.name,cmd.args,cmd.rpcMsgsIn,
                      cmd.rpcMsgsOut,cmd.rpcSizeIn,cmd.rpcSizeOut,
                      cmd.rpcHimarkFwd,cmd.rpcHimarkRev,cmd.uCpu,
                      cmd.sCpu,cmd.diskIn,cmd.diskOut,cmd.ipcIn,cmd.ipcOut,
                      cmd.maxRss,cmd.pageFaults,cmd.running,cmd.error)
      puts(query)
  end

  def sql_process_update(cmd)
      query = sprintf("UPDATE process SET uCpu=%s,sCpu=%s,diskIn=%s,diskOut=%s,ipcIn=%s,ipcOut=%s,maxRss=%s,pageFaults=%s,rpcMsgsIn=%s,rpcMsgsOut=%s,rpcSizeIn=%s,rpcSizeOut=%s,rpcHimarkFwd=%s,rpcHimarkRev=%s,error=%s WHERE processKey = \"%s\" and lineNumber = %d;",
                      cmd.uCpu,cmd.sCpu,cmd.diskIn,cmd.diskOut,cmd.ipcIn,
                      cmd.ipcOut,cmd.maxRss,cmd.pageFaults,cmd.rpcMsgsIn,
                      cmd.rpcMsgsOut,cmd.rpcSizeIn,cmd.rpcSizeOut,
                      cmd.rpcHimarkFwd,cmd.rpcHimarkRev,cmd.error,
                      cmd.processKey,cmd.lineNumber)
      puts(query)
      if (cmd.completedLapse != "NULL")
         query = sprintf("UPDATE process SET endTime=DATE_ADD(startTime,INTERVAL %d SECOND) WHERE processKey = \"%s\" and lineNumber = %d and endTime IS NULL;",
                         cmd.completedLapse,cmd.processKey,cmd.lineNumber) 
      else
         query = sprintf("UPDATE process SET endTime=startTime WHERE processKey = \"%s\" and lineNumber = %d and endTime IS NULL;",
                         cmd.processKey,cmd.lineNumber) 
      end
      puts(query)
  end

  def sql_tableUse_insert(tab)
      query = sprintf("INSERT IGNORE INTO tableUse VALUES (\"%s\",%d,\"%s\",%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);",
                      tab.processKey,tab.lineNumber,tab.tableName,tab.pagesIn,
                      tab.pagesOut,tab.pagesCached,tab.readLocks,
                      tab.writeLocks,tab.getRows,tab.posRows,tab.scanRows,
                      tab.putRows,tab.delRow,
                      tab.totalReadWait,tab.totalReadHeld,
                      tab.totalWriteWait,tab.totalWriteHeld,
                      tab.maxReadWait,tab.maxReadHeld,
                      tab.maxWriteWait,tab.maxWriteHeld)
      puts(query)
  end

end

def croakusage
    puts("Usage: log2sql.rb -d <database_name> log_filename" )
    exit(0)
end

######################
# START OF MAIN SCRIPT
######################

RE_CMD = Regexp.new( '^\t(\d+/\d+/\d+ \d+:\d+:\d+) pid (\d+) (.*)@(.*) (.*) \[(.*)\] \'(\w+-\w+) (.*)\'.*' )
RE_CMD_NOARG = Regexp.new( '^\t(\d+/\d+/\d+ \d+:\d+:\d+) pid (\d+) (.*)@(.*) (.*) \[(.*)\] \'(\w+-\w+)\'.*' )
RE_COMPUTE = Regexp.new( '^\t(\d+/\d+/\d+ \d+:\d+:\d+) pid (\d+) compute end ([0-9]+|[0-9]+\.[0-9]+|\.[0-9]+)s .*' )
RE_COMPLETED = Regexp.new( '^\t(\d+/\d+/\d+ \d+:\d+:\d+) pid (\d+) completed ([0-9]+|[0-9]+\.[0-9]+|\.[0-9]+)s .*')
RE_TRACK = Regexp.new( '^---|^locks acquired by blocking after 3 non-blocking attempts')
RE_TRACK_LAPSE = Regexp.new( '^--- lapse (\d+)')
RE_TRACK_RPC = Regexp.new( '^--- rpc msgs/size in\+out (\d+)\+(\d+)/(\d+)mb\+(\d+)mb himarks (\d+)/(\d+)')
RE_TRACK_USAGE = Regexp.new( '^--- usage (\d+)\+(\d+)us (\d+)\+(\d+)io (\d+)\+(\d+)net (\d+)k (\d+)pf')
RE_FAILED_AUTH = Regexp.new('^--- (failed authentication check)')
RE_KILLED_BY = Regexp.new('^--- (killed by .*)')
RE_EXITED = Regexp.new('^--- (exited on fatal server error)')
RE_TRACK_TABLE = Regexp.new('^--- db.([a-zA-Z]*)')
RE_TRACK_PAGES = Regexp.new( '^---   pages in\+out\+cached (\d+)\+(\d+)\+(\d+)')
RE_TRACK_LOCKS_ROWS = Regexp.new('^---   locks read/write (\d+)/(\d+) rows get\+pos\+scan put\+del (\d+)\+(\d+)\+(\d+) (\d+)\+(\d+)')
RE_TRACK_TOTAL_LOCK = Regexp.new('^---   total lock wait\+held read/write (\d+)ms\+(\d+)ms/(\d+)ms\+(\d+)ms')
RE_TRACK_MAX_LOCK = Regexp.new('^---   max lock wait\+held read/write (\d+)ms\+(\d+)ms/(\d+)ms\+(\d+)ms|---   locks wait+held read/write (\d+)ms\+(\d+)ms/(\d+)ms\+(\d+)ms')

opts = GetoptLong.new(["--dbname", "-d", GetoptLong::REQUIRED_ARGUMENT],
                      ["--help", "-h", GetoptLong::NO_ARGUMENT])
dbname = nil
opts.each do
    |opt,arg|
    if opt == "--dbname" || opt == "-d" 
       dbname = arg
    elsif opt == "--help" || opt == "-h" 
          croakusage
    end
end
logname = $<.filename
if !logname || !dbname
  croakusage
end
log2sql = Log2sql.new(logname,dbname)
line = log2sql.readLogfile()
while line
  nextLine = nil
  #
  # Pattern matching a command
  #
  if (match = RE_CMD.match(line)) || (match = RE_CMD_NOARG.match(line))
     pid = match[2]
     date = Date._parse(match[1])
     startTime = Time.local(date[:year],date[:mon],date[:mday],
                            date[:hour],date[:min],
                            date[:sec]).strftime("\"%Y-%m-%d %H:%M:%S\"")
     user = match[3]
     workspace = match[4]
     ip = match[5]
     # following gsub required due to a 2009.2 P4V bug
     app = match[6].gsub("\x00"){"/"}
     name = match[7]
     args = match[8]
     processKey = Digest::MD5.new << line
     cmd = Command.new(processKey,log2sql.getLineNumber(),pid,startTime,
                       user,workspace,ip,app,name,args)

     nextLine = log2sql.readLogfile()

     if nextLine && RE_TRACK.match(nextLine)
        log2sql.track(cmd,nextLine)
     else 
        log2sql.addCommand(cmd, FALSE)
     end
  #
  # Pattern matching a compute line
  #
  elsif match = RE_COMPUTE.match(line)
        pid = match[2]
        computedLapse = match[3]
        log2sql.computed(pid,computedLapse)
  #
  # Pattern matching a completed line
  #
  elsif match = RE_COMPLETED.match(line)
        pid = match[2]
        date = Date._parse(match[1])
        endTime = Time.local(date[:year],date[:mon],date[:mday],
                             date[:hour],date[:min],
                             date[:sec]).strftime("\"%Y-%m-%d %H:%M:%S\"")
        completedLapse = match[3]
        log2sql.completed(pid,endTime,completedLapse)
  end
  if nextLine
     line = nextLine
  else
     line = log2sql.readLogfile()
  end
end
log2sql.terminate()
# Change User Description Committed
#1 25216 Robert Cowham Branch files to Workshop mandated path for project
//guest/perforce_software/utils/log_analyzer/log2sql.rb
#1 23636 Robert Cowham Merge in latests changes to log2sql.py
- much faster
- better at detecting completed commands and out of order vtrack
- full test harness
//guest/robert_cowham/perforce/utils/log_analysis/log2sql.rb
#2 10402 Robert Cowham Pascal's change:
The encode() method is only supported with Ruby 1.9 or greater.
With Ruby 1.8, I cannot (easily) deal with non ASCII characters.
If that matters to users, they need to switch to Ruby 1.9.
#1 10401 Robert Cowham Populate //guest/robert_cowham/perforce/utils/log_analysis/log2sql.rb from //guest/pascal_soccard/Scripts/log2sql.rb.
//guest/pascal_soccard/Scripts/log2sql.rb
#12 8226 Pascal Soccard Increased size of circular list because on busy server, vtrack output
can be logged much later than the completed message. May increase
memory usage.
Database performance improvement by committing insert/update only after
all the rows have been inserted/updated.
This fix was necessary because of the dramatical performance
degradation after upgrading to Ubuntu 12.04 which probably uses
a newer Mysql
#11 8042 Pascal Soccard Fixed endTime for vtrack command
Added support for Broker log parsing
#10 7983 Pascal Soccard Forgot to submit as ktext
#9 7982 Pascal Soccard New improved version, includes now vtrack output parsing (same as
track2sql.php). See script header for more details.
#8 7705 Pascal Soccard Striped 0x0 characters in client value introduced by a buggy P4V version
#7 7647 Pascal Soccard Wrong type in sprintf call of the to_text method
#6 7562 Pascal Soccard "Perforce Server starting" block was incorrectly handled
#5 7483 Pascal Soccard Command parsing was not considering "dm-*" commands
#4 7470 Pascal Soccard Fixed an issue with " in command arguments
#3 7267 Pascal Soccard Fixed few bugs
#2 6471 Pascal Soccard Added new "running" field which gives the number of commands
not completed at the time the command was started
#1 6384 Pascal Soccard Added a Ruby Perforce log analyser