# ./ppp-time.rb | less ./ppp-time.rb:335: [BUG] gc_sweep(): unknown data type 121 #-------------------------- # ppp-time.rb #-------------------------- Report generated at May 26 15:54 Abort (core dumped) # uname -a Linux shoo.to.you 2.2.5-15 #13 Thu Jan 6 10:59:30 CST 2000 i586 unknown # ruby -v ruby 1.4.3 (1999-12-08) [i586-linux-gnu] # The code is longish, but here it is.... #!/usr/bin/ruby # ppp-time.rb # # PURPOSE: Scan through ppp.auth and # produce statistics and billing # reports # # DATE: Sat Apr 15 09:17:23 CDT 2000 # PROGRAMMER: David Douthitt # ppp.auth format: # # 948840929:peer1:tty1:in # 948840933:peer1:tty1:out # 948852042:ddouthitt:/dev/ttyG0_19:in # 948852203:ddouthitt:/dev/ttyG0_19:out # 948853385:ddouthitt:/dev/ttyG0_20:in # 948859439:jforsmo:/dev/ttyG0_22:in # 948860584:jforsmo:/dev/ttyG0_22:out #--------------------------------------------- # GENERIC CLASSES #--------------------------------------------- class Array def each_line self.each { |line| yield line.chomp } end end class TextFile < File def each_line super { |line| yield line.chomp } end alias each each_line end class String def fields split(":") end end class Numeric def to_mins self / 60 end def to_mins_rounded # (self + 30) / 60 # round to nearest minute (self / 60) + 1 # round to next minute up end def to_hrs self / 60 / 60 end def format_time raise ("bad time format!") if self <= 0; if (self > 3600) sprintf("%d:%02d:%02d", self / 3600, (self / 60) % 60, self % 60); else sprintf("%d:%02d", self / 60, self % 60); end end end #--------------------------------------------- # SPECIFIC CLASSES #--------------------------------------------- class PPPAuth < TextFile def PPPAuth.open super("/var/log/ppp.auth") end end class Report def Report.header print "#--------------------------\n"; print "# ppp-time.rb\n"; print "#--------------------------\n"; print "\nReport generated at ", Time.now.strftime("%b %d %H:%M"), "\n\n"; end end class System attr_accessor :online, :uptime; def initialize @online = 0; @uptime = 0; end def upsince (time) @uptime = (Time.now - Time.at(time)).to_i; end def summary printf(" Total online time: %10s\n", @online.to_mins.format_time); printf(" Total uptime: %10s * since first recorded login\n\n", @uptime.to_mins.format_time); printf(" Total online usage: %10.2f%%\n\n", @online * 100.0 / @uptime); end end class Passwd # /etc/passwd file format: # # Go look for yourself :-) Userdata = Hash.new def initialize TextFile.open("/etc/passwd") { |f| f.readlines.each_line { |line| # user, pwd, uid, gid, info, home, shell = line.fields user, *data = line.fields Userdata[user] = data; } } end def Passwd.uid (str) raise("user #{str} not in passwd file!") if Userdata[str] == nil; (Userdata[str])[1] end def Passwd.gid (str) raise("user #{str} not in passwd file!") if Userdata[str] == nil; (Userdata[str])[2] end def Passwd.info (str) raise("user #{str} not in passwd file!") if Userdata[str] == nil; (Userdata[str])[3] end def Passwd.home (str) raise("user #{str} not in passwd file!") if Userdata[str] == nil; (Userdata[str])[4] end def Passwd.shell (str) raise("user #{str} not in passwd file!") if Userdata[str] == nil; (Userdata[str])[5] end def Passwd.user? (str) (Userdata[user] != nil) end def Passwd.login? (str) ! (Passwd.shell(str) === "/bin/false") end end class Login attr_accessor :name, :time, :tty, :login, :logout, :action; def initialize (str, time, tty, action) @time = 0; @login = 0; @name = str; @time = time; @tty = tty; @action = action; @login = time; @logout = 0; end def online? @logout == 0 end def login_s Time.at(@login).strftime("%b %d %H:%M") end def logout_s if (self.logout == 0) "--" else Time.at(@logout).strftime("%b %d %H:%M") end end def online_summary printf(" %s %s %s\n", @name, login_s, @tty.tty_short); end def summary if (@logout == 0) printf(" %-15s %8s %6s %s\n", login_s, "----", "----", @tty.tty_short); else printf(" %-15s %8s %6.2f%% %s\n", login_s, @time.to_mins_rounded.format_time, @time * 100.0 / $sys.online, @tty.tty_short); end end end class Tty attr_accessor :online, :name def initialize (tty) @online = 0 @name = tty end def online (time_secs) @online += time_secs end def print_summary printf(" %-8s %8s %8.2f%%\n", @name.tty_short, @online.to_mins.format_time, @online * 100 / $sys.online) end end class User attr_accessor :name, :actual_secs, :total_mins, :lastlogin, :charge; def initialize (str) @name = str @total_mins = 0 @actual_secs = 0 @lastlogin = 0 @charge = 0.02; end def summary printf(" %-15s %8s %8.2f%%\n", @name, @total_mins.format_time, @actual_secs * 100.0 / $sys.online); end def charges printf("\n %-15s %8s @ $%.2f/min. = $%.2f\n\n", "Total", @total_mins.format_time, @charge, @total_mins * @charge); end def reset_lastlogin @lastlogin = 0; end def charge (secs) @actual_secs += secs; @total_mins += secs.to_mins_rounded; end def user_info print "User: ", Passwd.info(@name), "\n"; if Passwd.login? (@name) print "Login Shell: ", Passwd.shell (@name), "\n" end print "\n"; end end class String def tty_short self =~ /.*(tty.*)/ $1 end end class Array def usersort sort { |a,b| a.name <=> b.name } end end #--------------------------------------------- # MAIN PROGRAM #--------------------------------------------- $sys = System.new; $charge = .02; ttys = Hash.new Passwd.new; Report.header users = Hash.new ; logins = Hash.new PPPAuth.open { |auth| auth.readlines.each_line { |line| time, user, tty, action = line.fields time_secs = time.to_i raise("invalid online time!") if (time_secs <= 0); $sys.upsince(time_secs) if $sys.uptime == 0; users[user] = User.new(user) unless users.include?(user); ttys[tty] = Tty.new(tty) unless ttys.include?(tty); case action when "in" logins[time_secs] = Login.new(user, time_secs, tty, action); users[user].lastlogin = time_secs; # - - - this line appears to be suspect.... when "out" lastlogin_secs = users[user].lastlogin; # temp_var for speed concessions raise("logout time without login time!") if (lastlogin_secs == 0); raise("login data lost!") if logins[lastlogin_secs] == nil; logins[lastlogin_secs].logout = time_secs; online_secs = time_secs - lastlogin_secs; users[user].reset_lastlogin; users[user].charge(online_secs); logins[lastlogin_secs].time = online_secs; ttys[tty].online(online_secs); $sys.online += online_secs; else raise("bad data: action = #{action}"); end } } #--------------------------------------------- # REPORTS #--------------------------------------------- #---------------------------- # User Summary Report print "User Summary:\n\n"; users.values.usersort.each { |userdata| userdata.summary } #---------------------------- # System Summary Report print "\nSystem Summary:\n\n"; $sys.summary #---------------------------- # Online Summary Report if (logins.values.find{ |login| login.online? } != nil) print "Currently Online:\n\n"; logins.values.select{ |login| login.online? }.each { |login| login.online_summary } print "\n" end #---------------------------- # TTY Usage Summary Report print "TTY Summary:\n\n"; ttys.values.sort { |a,b| a.name <=> b.name }.each { |tty| tty.print_summary } print "\n"; #---------------------------- # User Login Detail Report users.values.usersort.each { |userdata| userdata.user_info logins.values.select { |login| userdata.name == login.name }.each { |login| login.summary } userdata.charges; }