[Freeswitch-trunk] [commit] r12545 - freeswitch/trunk/scripts/contrib/seven/sip
FreeSWITCH SVN
seven at freeswitch.org
Mon Mar 9 20:53:20 PDT 2009
Author: seven
Date: Mon Mar 9 22:53:19 2009
New Revision: 12545
Log:
script to parse sip messages and insert to Mysql
Added:
freeswitch/trunk/scripts/contrib/seven/sip/
freeswitch/trunk/scripts/contrib/seven/sip/sip2db.rb
Added: freeswitch/trunk/scripts/contrib/seven/sip/sip2db.rb
==============================================================================
--- (empty file)
+++ freeswitch/trunk/scripts/contrib/seven/sip/sip2db.rb Mon Mar 9 22:53:19 2009
@@ -0,0 +1,207 @@
+#!/usr/bin/env ruby
+
+# read SIP messages from a plain text file(FreeSWITCH with TPORT_DUMP=/tmp/xxxx.log),
+# and write to a Mysql database
+#
+# There are lots of ways to monitor sip messages. However, not all of them are convinient
+# as we want. Let's say a simple example:
+#
+# FreeSWITCH :> originate sofia/gateways/carrier1/5550000|sofia/gateways/carrier2/5550000|sofia/carrier3...
+#
+# It's hard to tell what happend if the call fails. Because it's different sip sessions.
+# The idea is to group them in one super session and see what happend. I do this by
+# adding an arbitary sip header to do cross reference. And by parse the sip messages to
+# a DB we can easily show it as html. I even can build a simple graph based on the DB data:
+#
+# http://skitch.com/seven1240/b8xj2/voip-master-idapted
+#
+# You can easily add a sip header to INVITE by(I use x_interaction):
+# FreeSWITCH :> originate {sip_h_x_interaction=TEST0001}sofia/gateways/.....
+#
+# And I can get all the messages from DB:
+# SELECT * FROM `sip_messages` WHERE (call_id IN
+# (SELECT distinct call_id FROM sip_messages WHERE x_interaction = 'TEST0001')) ORDER BY created_at;
+#
+# There are two aproches to get sip packets:
+# 1) tcpdump/tshark
+# 2) FreeSWITCH
+#
+# I use the second. Note, there is no way to actually get sip messages from FS currently, but sofia-sip
+# has the ability to log all sip messages to a disk file by using TPORT_DUMP
+#
+# And you can use this script to parse them to a DB. I know it hurt performance,
+# but we don't have tons of traffic and you know there are only 5-10 messages for each
+# sip call. While we get about 1G bytes each day in the sip log, most of them are OPTIONS/NOTIFY etc.
+# I filtered them before inserting to DB, but it will be better if sofia-sip can filter that :)
+#
+# The script will monitor the log file and parse and insert to DB in real time. It's written in
+# the Ruby on Rails framework, however, I think it can run out of Rails with or without
+# modification. But you still need ruby and rubygems if you want to use it.
+#
+# on Ubuntu/Debian
+# # apt-get install ruby rubygems
+# # gem install mysql file-tail yaml
+#
+# It's just an idea, you may like to write your own tools to parse the sip log file. Also
+# the log file need to be rotated regularly. And I think it maybe possible to store the log
+# file on a memory disk, whatever... :)
+#
+# By default, it will go to the end of a log file and follow(like tail),
+# if you want to parse the existing part, comment line: f.backward(0)
+#
+# Sample mysql DB DDL:
+# CREATE TABLE `sip_messages` (
+# `id` int(11) NOT NULL auto_increment,
+# `message_type` varchar(255) default NULL,
+# `sip_to` varchar(255) default NULL,
+# `call_id` varchar(255) default NULL,
+# `message_bytes` int(11) default NULL,
+# `content_length` int(11) default NULL,
+# `message_body` text,
+# `created_at` datetime default NULL,
+# `x_interaction` varchar(255) default NULL,
+# PRIMARY KEY (`id`),
+# KEY `index_sip_messages_on_x_interaction` (`x_interaction`)
+# ) ENGINE=InnoDB;
+#
+# Author: seven_at_idapted.com
+# Lisense: Free of use
+
+require 'rubygems'
+require "file/tail"
+require "mysql"
+require "yaml"
+
+
+RAILS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+# Uncomment the following line and comment the above to make it work withouth rails framework
+# RAILS_ROOT = "."
+DB_CONFIG_FILENAME = File.join(RAILS_ROOT, "config/database.yml")
+LOG_FILE = File.join(RAILS_ROOT, 'log/sip2db.log')
+# FILENAME="/tmp/tport_sip.log"
+FILENAME="/usr/local/freeswitch/log/tport_sip.log"
+ at debug = true
+
+if File.writable?(LOG_FILE)
+ @log_file = File.open(LOG_FILE, "a+")
+ puts "Writing logs to #{LOG_FILE}"
+else
+ @log_file = STDOUT
+end
+
+def log(msg)
+ @log_file.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
+end
+
+def db_config
+ if File.readable?(DB_CONFIG_FILENAME)
+ conf = YAML.load_file(DB_CONFIG_FILENAME)
+ return conf["production"]
+ else
+ conf["host"] = "localhost"
+ conf["username"] = "root"
+ conf["password"] = ""
+ conf["database"] = "freeswitch_sip_db"
+ end
+end
+
+#connect to Mysql
+ at dbh = nil
+ at debug = true
+ at call_id_array = [] #use as a queue to filter related messages
+
+def mysql_connect
+ Mysql.real_connect(db_config["host"] || db_config["server"], db_config["username"], db_config["password"], db_config["database"])
+end
+
+def ignore_message?(call_id, message_type)
+ #we do not care about certain types of messages and it's related ones
+
+ # log @call_id_array.size.to_s
+ @call_id_array.shift if @call_id_array.size > 10000
+ return true if @call_id_array.include?(call_id)
+ if message_type =~ /(REGISTER|OPTIONS|NOTIFY)/
+ @call_id_array << call_id
+ return true
+ end
+ return false
+end
+
+def insert_to_db(message_body)
+
+ # puts message_body
+ message_body =~ /(recv|sent) (\d+)[^\n]*\n(\S+).*To: ([^\n]+).*Call-ID: (\S+).*Content-Length: (\d+)[^(x_int)]*(x_interaction: ([A-Z0-9]+))*/m
+ message_bytes, message_type, sip_to, call_id, content_length, x_interaction = $2, $3, $4, $5, $6, $8
+ sip_to.sub!(/^.*<([^;>]+).*/, '\1') unless sip_to.nil?
+
+ return if call_id.nil?
+
+ call_id = call_id[0,100] + "_TRUNCATED" if call_id.length > 100
+ return if ignore_message?(call_id, message_type)
+
+ message_body = @dbh.escape_string(message_body)
+ sql = "INSERT INTO sip_messages (message_type, sip_to, call_id, message_bytes, x_interaction, content_length, message_body, created_at)
+ VALUES('#{message_type}', '#{sip_to}', '#{call_id}', '#{message_bytes}', '#{x_interaction}', #{content_length || 0 }, '#{message_body}', now())"
+ log sql if @debug
+
+ begin
+ @dbh.query(sql)
+ rescue Mysql::Error => e
+ log "Error code #{e.errno}"
+ log "Error message: #{e.error}"
+ log "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
+ end
+end
+
+
+#start here
+begin
+ # connect to the MySQL server
+ # get server version string and display it
+ @dbh = mysql_connect
+ log "Server version: " + @dbh.get_server_info
+rescue Mysql::Error => e
+ log "Error code: #{e.errno}"
+ log "Error message: #{e.error}"
+ log "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
+ensure
+ # disconnect from server
+ # @dbh.close if @dbh
+end
+
+
+
+File.open(FILENAME) do |f|
+ message_type_expecting = true
+ message_body = ''
+ message_started = false
+ call_id = nil
+ line_count = 0
+
+ f.extend(File::Tail)
+ f.interval = 10
+ f.backward(0)
+ f.tail do |line|
+
+ if line_count > 1000 # crazy?
+ line_count = 0
+ message_started = false
+ end
+
+ if !message_started && line =~ /^(recv|sent) (\d+)/
+ message_started = true
+ end
+
+ if line =~ /\013/ # \v - seperator in dump file
+ insert_to_db message_body
+ message_body = ''
+ message_started = false
+ else
+ message_body << line
+ end
+
+ end
+end
+
+
+
More information about the Freeswitch-trunk
mailing list