Sunday, September 26, 2010

Create WaterFall Chart for Performance (Duration)

usually, I wanna know how much load created at certain point of time on my async system, and see how much impact the concurrent load bring to it. So I made a simple Create WaterFall Chart to help me to understand these more clearly.
This is written in Ruby,sorry java guys, it is faster for me to write ruby code than java, but i like java gradually :-)

I am using Google Chart API, Googlecharts, TinyURL services, Thanks you for providing these!

A quick snapshot for waterfall chart(click to see big):




Test code:

 require 'gchart'  
 require 'time'  
 require 'open-uri'  
 require 'uri'  
 require 'accessDB'  
 #Get start and end time stamp from DB  
 startArray = ["2010-09-26 16:58:37.693","2010-09-26 16:58:38.223","2010-09-26 16:58:38.833",  
 "2010-09-26 16:58:39.600","2010-09-26 16:58:40.210","2010-09-26 16:58:40.833","2010-09-26 16:58:55.740"];  
 endArray = ["2010-09-26 16:59:56.543","2010-09-26 16:59:58.183","2010-09-26 17:00:04.217",  
 "2010-09-26 17:00:09.370","2010-09-26 17:00:10.217","2010-09-26 17:00:19.323","2010-09-26 17:00:25.027"];  
 Gchart_string = prepareData(startArray, endArray )  
 Gchart_url = waterFallUrl(Gchart_string)  
 puts minifyUrl(Gchart_url)  

Prepare Google chart Data and para:
 def prepareData(starttime, endtime )  
  if starttime.length > 400  
   puts "\nExceed the maxium number(400) of records."  
   puts "You can try to split them to display.Press Enter to exit..."; $stdout.flush  
   gets  
   break;  
  end  
  # convert Time Object string to Time object Int  
  starttime.collect! { |time1|  
  Time.parse("#{time1}").to_i;  
  }  
  starttime.sort!  
  endtime.collect! { |time2|  
   Time.parse("#{time2}").to_i;  
  }  
  endtime.sort!  
  # prepare data in array, convert to string used by Gchart  
  flag = starttime[0]  
  starttime.collect! { |time1|  
   time1-flag;  
  }  
  endtime.collect! { |time2|  
   time2-flag;  
  }  
  if endtime.last > 3600  
   puts "\nWarning: Duration is too long to display well for Chart(3600s is recommended).";  
   puts "You can split the data to display."  
  end  
  line_xy_string = "";  
  scale_num = 1;  
  cata = 10;  
  while endtime.last > cata  
   cata = cata + 10 ;  
  end  
  scale_num = cata/starttime.length  
  starttime.length.times { |i|   
   line_xy_string << starttime[i].to_s;  
   line_xy_string << ",";  
   line_xy_string << endtime[i].to_s;  
   line_xy_string << "|";  
   line_xy_string << ((i+1)*scale_num).to_s + "," + ((i+1)*scale_num).to_s;  
   # ignore the last "|"  
   if ((i+1) < starttime.length)  
    line_xy_string << "|";  
   end  
  }  
  line_xy_string << "&chds=";  
  line_xy_string << "0,#{cata}";  
  line_xy_string << "&chxt=x,x,y"  
  line_xy_string << "&chxr=0,0,#{cata}"  
  line_xy_string << "&chxl=1:|Duration(sec)"  
  return line_xy_string;  
 end  


Get Google Chart URL:
 def waterFallUrl(chart_string)  
  output_url = Gchart.line_xy(:size => '700x400',  
       :title => "WaterFall Charts",  
       :custom => "chd=t:#{chart_string}")  
  #"&chxr=0,0" is automatic added to the end of string,  
  #remove it due to already identify one in chart_string  
  output_url.chomp!("&chxr=0,0");  
  return output_url  
 end  

Minify URL using tinyURL:
 def minifyUrl(gchartUrl)  
  # convert URL to escaped one  
  escaped_url = URI.escape(gchartUrl, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))  
  short_url = open("http://tinyurl.com/api-create.php?url=#{escaped_url}").read  
  return short_url;  
 end  

Also if you have written a data query method to get start and end time from DB, then it is easy to get startArray and endArray directly by T-SQL. i am using this in practice: http://snippets.dzone.com/posts/show/3906

or you may take look at this, FYI:
 require 'win32ole'  
 class SQLServer  
   # This class manages database connection and queries  
   attr_accessor :connection, :data, :fields  
   attr_writer :username, :password  
   def initialize(host, username = 'sa', password='sa')  
     @connection = nil  
     @data = nil  
     @host = host  
     @username = username  
     @password = password  
   end  
   def open(database)  
     # Open ADO connection to the SQL Server database  
     connection_string = "Provider=SQLOLEDB.1;" #SQLOLEDB.1,SQLNCLI  
     connection_string << "Persist Security Info=False;"  
     connection_string << "User ID=#{@username};"  
     connection_string << "password=#{@password};"  
     connection_string << "Initial Catalog=#{database};"  
     connection_string << "Data Source=#{@host};"  
     connection_string << "Network Library=dbmssocn"  
     @connection = WIN32OLE.new('ADODB.Connection')  
     @connection.Open(connection_string)  
   end  
   def query(sql)  
     # Create an instance of an ADO Recordset  
     recordset = WIN32OLE.new('ADODB.Recordset')  
     # Open the recordset, using an SQL statement and the  
     # existing ADO connection  
     recordset.Open(sql, @connection)  
     # Create and populate an array of field names  
     @fields = []  
     recordset.Fields.each do |field|  
       @fields << field.Name  
     end  
     begin  
       # Move to the first record/row, if any exist  
       recordset.MoveFirst  
       # Grab all records  
       @data = recordset.GetRows  
     rescue  
       @data = []  
     end  
     recordset.Close  
     # An ADO Recordset's GetRows method returns an array  
     # of columns, so we'll use the transpose method to  
     # convert it to an array of rows  
     @data = @data.transpose  
   end  
   def close  
     @connection.Close  
   end  
 end  

Usage sample:
 db = SQLServer.new('YourDBIP', 'sa', 'sa')  
 sp = db.open('DBName')  
 startArray = db.query("select * from somewhere where ...")  
 endArray = db.query("sselect * from somewhere where ...")  
 db.close  

No comments:

Post a Comment