├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── haproxy.gemspec ├── lib ├── haproxy.rb └── haproxy │ ├── csv_parser.rb │ ├── socket_reader.rb │ ├── stats_reader.rb │ └── version.rb └── spec ├── haproxy_spec.rb ├── socket_reader_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in haproxy.gemspec 4 | gemspec 5 | 6 | group :development, :test do 7 | gem 'rspec' 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Leandro López 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HAProxy RubyGem 2 | 3 | This gem aims to provide an interface to query HAProxy for statistics, 4 | and eventually management through the sockets API. 5 | 6 | ## What's HAProxy? 7 | 8 | [HAProxy](http://haproxy.1wt.eu/) is the _Reliable, High Performance 9 | TCP/HTTP Load Balancer_. 10 | 11 | ## Why this gem? 12 | 13 | * I use HAProxy at my work. 14 | * I wanted to know how to create gems. 15 | * I wanted to contribute with OSS. 16 | * Why not? 17 | * All of the above. 18 | 19 | ## Example of use 20 | 21 | #! /usr/bin/env ruby 22 | require 'haproxy' 23 | require 'pp' 24 | 25 | haproxy = HAProxy.read_stats '/path/to/haproxy.stats.socket' 26 | 27 | pp haproxy.info 28 | 29 | # {:name=>"HAProxy", 30 | # :version=>"1.3.22", 31 | # :release_date=>"2009/10/14", 32 | # :nbproc=>"1", 33 | # :process_num=>"1", 34 | # :pid=>"10222", 35 | # :uptime=>"0d 0h33m12s", 36 | # :uptime_sec=>"1992", 37 | # :memmax_mb=>"0", 38 | # :ulimit_n=>"4013", 39 | # :maxsock=>"4013", 40 | # :maxconn=>"2000", 41 | # :maxpipes=>"0", 42 | # :currconns=>"1", 43 | # :pipesused=>"0", 44 | # :pipesfree=>"0", 45 | # :tasks=>"1", 46 | # :run_queue=>"1", 47 | # :node=>"roke", 48 | # :"description:"=>nil} 49 | 50 | pp haproxy.stats 51 | 52 | # [{:pxname=>"app1", 53 | # :svname=>"thin1", 54 | # :qcur=>"0", 55 | # :qmax=>"0", 56 | # :scur=>"0", 57 | # :smax=>"0", 58 | # :slim=>"", 59 | # :stot=>"0", 60 | # :bin=>"0", 61 | # :bout=>"0", 62 | # :dreq=>"", 63 | # :dresp=>"0", 64 | # :ereq=>"", 65 | # :econ=>"0", 66 | # :eresp=>"0", 67 | # :wretr=>"0", 68 | # :wredis=>"0", 69 | # :status=>"no check", 70 | # :weight=>"1", 71 | # :act=>"1", 72 | # :bck=>"0", 73 | # :chkfail=>"", 74 | # :chkdown=>"", 75 | # :lastchg=>"", 76 | # :downtime=>"", 77 | # :qlimit=>"", 78 | # :pid=>"1", 79 | # :iid=>"1", 80 | # :sid=>"1", 81 | # :throttle=>"", 82 | # :lbtot=>"0", 83 | # :tracked=>"", 84 | # :type=>"2", 85 | # :rate=>"0", 86 | # :rate_lim=>"", 87 | # :rate_max=>"0"},...] 88 | 89 | #### HAProxy sample configuration 90 | 91 | global 92 | stats socket haproxy 93 | 94 | defaults 95 | mode http 96 | option httplog 97 | option httpclose 98 | retries 3 99 | option redispatch 100 | maxconn 2000 101 | contimeout 5000 102 | clitimeout 50000 103 | srvtimeout 50000 104 | stats uri /haproxy 105 | 106 | listen app1 0.0.0.0:10000 107 | balance roundrobin 108 | 109 | server thin1 127.0.0.1:10001 110 | server thin1 127.0.0.1:10002 111 | server thin1 127.0.0.1:10003 112 | server thin1 127.0.0.1:10004 113 | server thin1 127.0.0.1:10005 114 | 115 | frontend app2 116 | bind 0.0.0.0:10011 117 | default_backend app2 118 | 119 | backend app2 120 | balance roundrobin 121 | server thin1 127.0.0.1:10006 122 | server thin1 127.0.0.1:10007 123 | server thin1 127.0.0.1:10008 124 | server thin1 127.0.0.1:10009 125 | server thin1 127.0.0.1:10010 126 | 127 | ## Roadmap 128 | 129 | * Improve documentation 130 | * Improve CSV parsing 131 | * Add tests 132 | * Add examples 133 | * Read stats from HTTP and CSV files 134 | 135 | # License 136 | 137 | ``` 138 | Copyright (c) 2014 Leandro López 139 | 140 | Permission is hereby granted, free of charge, to any person obtaining a copy 141 | of this software and associated documentation files (the "Software"), to deal 142 | in the Software without restriction, including without limitation the rights 143 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 144 | copies of the Software, and to permit persons to whom the Software is 145 | furnished to do so, subject to the following conditions: 146 | 147 | The above copyright notice and this permission notice shall be included in 148 | all copies or substantial portions of the Software. 149 | 150 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 151 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 152 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 153 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 154 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 155 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 156 | THE SOFTWARE. 157 | ``` 158 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | -------------------------------------------------------------------------------- /haproxy.gemspec: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby; encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "haproxy/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "haproxy" 7 | s.version = HAProxy::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Leandro López (inkel)"] 10 | s.email = ["inkel.ar@gmail.com"] 11 | s.homepage = "https://github.com/inkel/haproxy-ruby" 12 | s.summary = 'HAProxy interface for reading statistics or managing servers (requires HAProxy 1.4+)' 13 | s.description = 'This gem is intended for use as a HAProxy interface when you need to read statistics or if you like to manage proxies thru Ruby' 14 | 15 | s.rubyforge_project = "haproxy" 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ["lib"] 21 | end 22 | -------------------------------------------------------------------------------- /lib/haproxy.rb: -------------------------------------------------------------------------------- 1 | libdir = File.dirname(__FILE__) 2 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 3 | 4 | require 'uri' 5 | Dir["#{libdir}/haproxy/*.rb"].each do |file| 6 | require file 7 | end 8 | 9 | module HAProxy 10 | 11 | def self.read_stats(from) 12 | uri = URI.parse(from) 13 | 14 | if uri.is_a?(URI::Generic) and File.socket?(uri.path) 15 | HAProxy::SocketReader.new(uri.path) 16 | else 17 | raise NotImplementedError, "Currently only sockets are implemented" 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/haproxy/csv_parser.rb: -------------------------------------------------------------------------------- 1 | module HAProxy 2 | class CSVParser 3 | 4 | # Uses CSV header from HAProxy 1.5, which is backwards compatible 5 | COLUMNS = "pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt".split(",").map(&:to_sym) unless const_defined?(:COLUMNS) 6 | LIMIT = COLUMNS.length unless const_defined?(:LIMIT) 7 | 8 | def self.parse(line) 9 | line.strip! 10 | unless line.start_with? "#" 11 | data = line.split(',') 12 | pxname = data.shift 13 | 14 | stats = { :pxname => pxname } 15 | 16 | data.each_with_index do |value, i| 17 | if i < LIMIT 18 | stats[COLUMNS[i + 1]] = value 19 | else 20 | stats[:extra] = Array.new if stats[:extra].nil? 21 | stats[:extra] << value 22 | end 23 | end 24 | 25 | stats 26 | else 27 | raise ArgumentError, "Line is a comment" 28 | end 29 | end 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/haproxy/socket_reader.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'haproxy/stats_reader' 3 | require 'haproxy/csv_parser' 4 | 5 | module HAProxy 6 | class SocketReader < HAProxy::StatsReader 7 | 8 | def initialize(path) 9 | raise ArgumentError, "Socket #{path} doesn't exists or is not a UNIX socket" unless File.exists?(path) and File.socket?(path) 10 | @path = path 11 | end 12 | 13 | def info 14 | returning({}) do |info| 15 | send_cmd "show info" do |line| 16 | key, value = line.split(': ') 17 | info[key.downcase.gsub('-', '_').to_sym] = value 18 | end 19 | end 20 | end 21 | 22 | def errors 23 | returning("") do |errors| 24 | send_cmd "show errors" do |line| 25 | errors << line 26 | end 27 | end 28 | end 29 | 30 | def sessions 31 | returning([]) do |sess| 32 | send_cmd "show sess" do |line| 33 | sess << line 34 | end 35 | end 36 | end 37 | 38 | TYPES = { 39 | :frontend => 1, 40 | :backend => 2, 41 | :server => 4 42 | } 43 | 44 | def stats(types=[:all], options={}) 45 | params = { 46 | :proxy => :all, 47 | :server => :all 48 | }.merge(options) 49 | 50 | params[:proxy] = "-1" if params[:proxy].eql?(:all) 51 | params[:server] = "-1" if params[:server].eql?(:all) 52 | 53 | types = [types] unless types.is_a?(Array) 54 | 55 | params[:type] = case 56 | when types.eql?([:all]) 57 | "-1" 58 | else 59 | types.map{ |type| TYPES[type] }.inject(:+) 60 | end 61 | 62 | cmd = "show stat #{params[:proxy]} #{params[:type]} #{params[:server]}" 63 | returning([]) do |stats| 64 | send_cmd(cmd) do |line| 65 | stats << CSVParser.parse(line) unless line.start_with?('#') 66 | end 67 | end 68 | end 69 | 70 | def disable(backend, server) 71 | cmd = "disable server #{backend}/#{server}" 72 | send_cmd(cmd) 73 | end 74 | 75 | def enable(backend, server) 76 | cmd = "enable server #{backend}/#{server}" 77 | send_cmd(cmd) 78 | end 79 | 80 | def frontends 81 | stats :frontend, :proxy => :all, :server => :all 82 | end 83 | 84 | def backends 85 | stats :backend, :proxy => :all, :server => :all 86 | end 87 | 88 | def servers 89 | stats :server, :proxy => :all, :server => :all 90 | end 91 | 92 | protected 93 | 94 | def send_cmd(cmd, &block) 95 | UNIXSocket.open(@path) do |socket| 96 | socket.write(cmd + "\n") 97 | socket.each do |line| 98 | next if line.chomp.empty? 99 | yield(line.strip) 100 | end 101 | end 102 | end 103 | 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/haproxy/stats_reader.rb: -------------------------------------------------------------------------------- 1 | libdir = File.dirname(__FILE__) 2 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 3 | 4 | module HAProxy 5 | 6 | class StatsReader 7 | 8 | def info 9 | raise NotImplementedError 10 | end 11 | 12 | def errors 13 | raise NotImplementedError 14 | end 15 | 16 | def sessions 17 | raise NotImplementedError 18 | end 19 | 20 | def stats 21 | raise NotImplementedError 22 | end 23 | 24 | def frontends 25 | raise NotImplementedError 26 | end 27 | 28 | def backends 29 | raise NotImplementedError 30 | end 31 | 32 | def servers 33 | raise NotImplementedError 34 | end 35 | 36 | protected 37 | 38 | # Borrowed from Rails 3 39 | def returning(value) 40 | yield(value) 41 | value 42 | end 43 | 44 | private 45 | 46 | def initialize 47 | end 48 | 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/haproxy/version.rb: -------------------------------------------------------------------------------- 1 | module HAProxy 2 | VERSION = "0.0.6" 3 | end 4 | -------------------------------------------------------------------------------- /spec/haproxy_spec.rb: -------------------------------------------------------------------------------- 1 | #require File.expand_path('../spec_helper.rb', __FILE__) 2 | require File.expand_path('../../lib/haproxy', __FILE__) 3 | 4 | describe HAProxy do 5 | 6 | end 7 | -------------------------------------------------------------------------------- /spec/socket_reader_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper.rb', __FILE__) 2 | require File.expand_path('../../lib/haproxy', __FILE__) 3 | 4 | describe HAProxy::SocketReader do 5 | 6 | it "should accept an existing socket file" do 7 | HAProxy::Test.with_socket do |path| 8 | HAProxy::SocketReader.new(path).should_not be_nil 9 | end 10 | end 11 | 12 | it "should fail without an existing socket file" do 13 | Tempfile.open('invalid-file') do |file| 14 | expect { HAProxy::SocketReader.new(file.path) }.to raise_error ArgumentError 15 | end 16 | end 17 | 18 | it "should return a Hash with proxy information" do 19 | HAProxy::Test.with_socket(:info) do |path| 20 | reader = HAProxy::SocketReader.new(path) 21 | reader.info.class.should == Hash 22 | end 23 | end 24 | 25 | it "should return a String when asking for errors" do 26 | HAProxy::Test.with_socket(:without_errors) do |path| 27 | reader = HAProxy::SocketReader.new(path) 28 | reader.errors.class.should == String 29 | end 30 | 31 | HAProxy::Test.with_socket(:with_errors) do |path| 32 | reader = HAProxy::SocketReader.new(path) 33 | reader.errors.class.should == String 34 | end 35 | end 36 | 37 | it "should return a String when asking for sessions" do 38 | HAProxy::Test.with_socket(:without_sessions) do |path| 39 | reader = HAProxy::SocketReader.new(path) 40 | reader.sessions.class.should == Array 41 | end 42 | 43 | HAProxy::Test.with_socket(:with_sessions) do |path| 44 | reader = HAProxy::SocketReader.new(path) 45 | reader.sessions.class.should == Array 46 | end 47 | end 48 | 49 | [ :frontends, :backends, :servers ].each do |type| 50 | it "should return an Array with #{type} information" do 51 | HAProxy::Test.with_socket do |path| 52 | reader = HAProxy::SocketReader.new(path) 53 | reader.send(type).class.should == Array 54 | end 55 | end 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | module HAProxy 4 | 5 | module Test 6 | 7 | class << self 8 | def temp_file_path 9 | tmp = Tempfile.new('haproxy-ruby-mocksock') 10 | path = tmp.path 11 | tmp.close! 12 | path 13 | end 14 | 15 | def with_socket action = nil, &block 16 | path = self.temp_file_path 17 | 18 | UNIXServer.open(path) do |socket| 19 | pid = Process.fork do 20 | client = socket.accept 21 | cmd, data = client.recvfrom(1024) 22 | cmd.chomp! 23 | client.send self.send(action || :unknown), 0 24 | client.close 25 | exit 26 | end 27 | 28 | block.call(path) 29 | 30 | Process.kill "TERM", pid rescue nil 31 | end 32 | end 33 | 34 | def unknown 35 | "Unknown command" 36 | end 37 | 38 | def info 39 | """Name: HAProxy 40 | Version: 1.3.25 41 | Release_date: 2010/06/16 42 | Nbproc: 1 43 | Process_num: 1 44 | Pid: 20518 45 | Uptime: 6d 7h07m46s 46 | Uptime_sec: 544066 47 | Memmax_MB: 0 48 | Ulimit-n: 80110 49 | Maxsock: 80110 50 | Maxconn: 40000 51 | Maxpipes: 0 52 | CurrConns: 28 53 | PipesUsed: 0 54 | PipesFree: 0 55 | Tasks: 125 56 | Run_queue: 1 57 | node: A1441 58 | description 59 | """ 60 | end 61 | 62 | def with_errors 63 | ''' 64 | [09/Mar/2011:16:05:48.038] frontend http-in (#1): invalid request 65 | src 10.0.111.185, session #25765333, backend (#-1), server (#-1) 66 | request length 352 bytes, error at position 23: 67 | 68 | 00000 POS LWqEQR/710 HTTP/1.1\x00\x00Content-Type: application/x-fcs\x00\x00 69 | 00058+ User- HTTP/1.1\r\n 70 | 00074 kwave Flash\r\n 71 | 00087 Host: 10.0.49.24\x00\x00Content-Length: 1\x00\x00P\r\n 72 | 00128 xy-Connection: Keep\r\n 73 | 00149 live\x00\x00Pragm\r\n 74 | 00162 no-cache\x00\x00X-NovINet: v1.2\x00\x00\x00\n 75 | 00192 \r\n 76 | 00194 \n 77 | 00195 k\xC3\xD9\xFB\x02\x9F\x02\r\x08\xBC\xDB\x1E\xD3Z[\xD4]\xDD%O\x0FX\x01 78 | 00218+ \xDD\x02\xB4\x98\xB4r\x87\xDB\x88\xD4\xBD\xA6\xBD\x80@\x15\x9D\x1A\xA7 79 | 00237+ \xE8,\x8B\x01\x1Ft\x97\x80\xDB\xBF\x87\xAD\x15\r\xDE\x9E\x0CL\x13 FB 80 | 00259+ \x822\xAA\xE0G\x10\t\xC3\xBC\e\xD0!\xC2dYh/\x01\x8A~\x85]z1\xC00\xBF 81 | 00286+ \xC5\x14\xFA\x7F\x03\xF2\xC1\x06\x10\r\n 82 | 00297 X\xA4Cqj0\xFC\x81\x14\xF2U\n 83 | 00309 (0\xBF\xC12<\xA0:\xB2\x84\xC5\xA1\xFAa01\xD0-\xF2\x85\xC8\xB0\r\n 84 | 00333 \x96$\x16\xB6\xB2\r,\x1D\x8B\x93\xAED\xBA\x9BE\r\n 85 | 00350 \r\n 86 | ''' 87 | end 88 | 89 | def without_errors 90 | "" 91 | end 92 | 93 | def with_sessions 94 | '''0xa14a288: proto=tcpv4 src=10.0.16.1:56772 fe=http-in be=gelderlander_textlink srv=mongrel-10306 as=0 ts=08 age=49s calls=5 rq[f=00f0a0h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=4s,wx=,ax=] s0=[7,0h,fd=13,ex=] s1=[7,0h,fd=16,ex=] exp=4s 95 | 0x9745898: proto=tcpv4 src=10.0.73.31:2778 fe=http-in be=gelderlander_textlink srv=mongrel-10315 as=0 ts=08 age=42s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=8s,wx=,ax=] s0=[7,0h,fd=19,ex=] s1=[7,8h,fd=20,ex=] exp=8s 96 | 0xa26cff0: proto=tcpv4 src=10.0.221.60:19546 fe=http-in be=limburger_textlink srv=mongrel-10708 as=0 ts=08 age=40s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=10s,wx=,ax=] s0=[7,0h,fd=25,ex=] s1=[7,8h,fd=26,ex=] exp=10s 97 | 0x9f2dff0: proto=tcpv4 src=10.0.45.76:59090 fe=http-in be=tctubantia_textlink srv=mongrel-10507 as=0 ts=08 age=38s calls=5 rq[f=00f0a0h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=11s,wx=,ax=] s0=[7,0h,fd=23,ex=] s1=[7,0h,fd=24,ex=] exp=11s 98 | 0xa2cb998: proto=tcpv4 src=10.0.108.32:1734 fe=http-in be=tctubantia_textlink srv=mongrel-10505 as=0 ts=08 age=37s calls=3 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=12s,wx=,ax=] s0=[7,0h,fd=28,ex=] s1=[7,8h,fd=29,ex=] exp=12s 99 | 0xa04b7e8: proto=tcpv4 src=10.0.148.59:1158 fe=http-in be=destentor_textlink srv=mongrel-10403 as=0 ts=08 age=34s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=16s,wx=,ax=] s0=[7,0h,fd=32,ex=] s1=[7,8h,fd=33,ex=] exp=16s 100 | 0x97d0b90: proto=tcpv4 src=10.0.133.111:50836 fe=http-in be=tctubantia_textlink srv=mongrel-10510 as=0 ts=08 age=21s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=29s,wx=,ax=] s0=[7,0h,fd=1,ex=] s1=[7,8h,fd=8,ex=] exp=29s 101 | 0x96b1220: proto=tcpv4 src=10.0.217.27:14445 fe=http-in be=gelderlander_textlink srv=mongrel-10310 as=0 ts=08 age=19s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=30s,wx=,ax=] s0=[7,0h,fd=21,ex=] s1=[7,8h,fd=22,ex=] exp=30s 102 | 0xa12e7a8: proto=tcpv4 src=10.0.34.84:49421 fe=http-in be=gelderlander_textlink srv=mongrel-10303 as=0 ts=08 age=16s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=33s,wx=,ax=] s0=[7,0h,fd=15,ex=] s1=[7,8h,fd=17,ex=] exp=33s 103 | 0x9b5f570: proto=tcpv4 src=10.0.242.177:50124 fe=http-in be=limburger_textlink srv=mongrel-10701 as=0 ts=08 age=14s calls=4 rq[f=009080h,l=0,an=00h,rx=,wx=,ax=] rp[f=001000h,l=0,an=10h,rx=35s,wx=,ax=] s0=[7,0h,fd=38,ex=] s1=[7,8h,fd=40,ex=] exp=35s 104 | 0x9fda668: proto=unix_stream as=2 ts=09 age=0s calls=2 rq[f=00e042h,l=10,an=20h,rx=10s,wx=,ax=] rp[f=048060h,l=2499,an=00h,rx=,wx=10s,ax=] s0=[7,0h,fd=2,ex=] s1=[0,0h,fd=-1,ex=] exp=10s 105 | ''' 106 | end 107 | 108 | def without_sessions 109 | "" 110 | end 111 | 112 | end 113 | end 114 | end 115 | --------------------------------------------------------------------------------