solutious-stella 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +36 -0
- data/README.textile +162 -0
- data/Rakefile +88 -0
- data/bin/stella +12 -0
- data/bin/stella.bat +12 -0
- data/lib/daemonize.rb +56 -0
- data/lib/pcaplet.rb +180 -0
- data/lib/stella.rb +101 -0
- data/lib/stella/adapter/ab.rb +337 -0
- data/lib/stella/adapter/base.rb +106 -0
- data/lib/stella/adapter/httperf.rb +305 -0
- data/lib/stella/adapter/pcap_watcher.rb +221 -0
- data/lib/stella/adapter/proxy_watcher.rb +76 -0
- data/lib/stella/adapter/siege.rb +341 -0
- data/lib/stella/cli.rb +258 -0
- data/lib/stella/cli/agents.rb +73 -0
- data/lib/stella/cli/base.rb +55 -0
- data/lib/stella/cli/language.rb +18 -0
- data/lib/stella/cli/localtest.rb +78 -0
- data/lib/stella/cli/sysinfo.rb +16 -0
- data/lib/stella/cli/watch.rb +278 -0
- data/lib/stella/command/base.rb +40 -0
- data/lib/stella/command/localtest.rb +358 -0
- data/lib/stella/data/domain.rb +82 -0
- data/lib/stella/data/http.rb +131 -0
- data/lib/stella/logger.rb +84 -0
- data/lib/stella/response.rb +85 -0
- data/lib/stella/storable.rb +201 -0
- data/lib/stella/support.rb +276 -0
- data/lib/stella/sysinfo.rb +257 -0
- data/lib/stella/test/definition.rb +79 -0
- data/lib/stella/test/run/summary.rb +70 -0
- data/lib/stella/test/stats.rb +114 -0
- data/lib/stella/text.rb +64 -0
- data/lib/stella/text/resource.rb +38 -0
- data/lib/utils/crypto-key.rb +84 -0
- data/lib/utils/domainutil.rb +47 -0
- data/lib/utils/escape.rb +302 -0
- data/lib/utils/fileutil.rb +78 -0
- data/lib/utils/httputil.rb +266 -0
- data/lib/utils/mathutil.rb +15 -0
- data/lib/utils/stats.rb +88 -0
- data/lib/utils/textgraph.rb +267 -0
- data/lib/utils/timerutil.rb +58 -0
- data/lib/win32/Console.rb +970 -0
- data/lib/win32/Console/ANSI.rb +305 -0
- data/support/kvm.h +91 -0
- data/support/ruby-pcap-takuma-notes.txt +19 -0
- data/support/ruby-pcap-takuma-patch.txt +30 -0
- data/support/text/en.yaml +80 -0
- data/support/text/nl.yaml +7 -0
- data/support/useragents.txt +75 -0
- data/tests/01-util_test.rb +0 -0
- data/tests/02-stella-util_test.rb +42 -0
- data/tests/10-stella_test.rb +104 -0
- data/tests/11-stella-storable_test.rb +68 -0
- data/tests/60-stella-command_test.rb +248 -0
- data/tests/80-stella-cli_test.rb +45 -0
- data/tests/spec-helper.rb +31 -0
- metadata +165 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
|
2
|
+
require 'utils/httputil'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Stella::Data
|
6
|
+
|
7
|
+
# TODO: Implement HTTPHeaders. We should be printing untouched headers.
|
8
|
+
# HTTPUtil should split the HTTP event lines and that's it. Replace
|
9
|
+
# parse_header_body with split_header_body
|
10
|
+
class HTTPHeaders < Stella::Storable
|
11
|
+
attr_reader :raw_data
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@raw_data
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class HTTPRequest < Stella::Storable
|
20
|
+
attr_reader :raw_data
|
21
|
+
|
22
|
+
field :time => DateTime
|
23
|
+
field :client_ip => String
|
24
|
+
field :server_ip => String
|
25
|
+
field :header => String
|
26
|
+
field :uri => String
|
27
|
+
field :body => String
|
28
|
+
field :method => String
|
29
|
+
field :http_version => String
|
30
|
+
|
31
|
+
def has_body?
|
32
|
+
@body && !@body.nil & !@body.empty?
|
33
|
+
end
|
34
|
+
def has_request?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
def has_response?
|
38
|
+
(@response && @response.status && !@response.status.nil?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(raw_data=nil)
|
42
|
+
@raw_data = raw_data
|
43
|
+
if @raw_data
|
44
|
+
@method, @http_version, @uri, @header, @body = HTTPUtil::parse_http_request(raw_data)
|
45
|
+
end
|
46
|
+
@response = Stella::Data::HTTPResponse.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def uri
|
50
|
+
@uri.host = @server_ip.to_s if @uri && (@uri.host == 'unknown' || @uri.host.empty?)
|
51
|
+
@uri
|
52
|
+
end
|
53
|
+
|
54
|
+
def body
|
55
|
+
return nil unless @body
|
56
|
+
@body
|
57
|
+
#(!header || header[:Content_Type] || header[:Content_Type] !~ /text/) ? Base64.encode64(@body) : @body
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
headers = []
|
64
|
+
header.each_pair do |n,v|
|
65
|
+
headers << "#{n.to_s.gsub('_', '-')}: #{v[0]}"
|
66
|
+
end
|
67
|
+
str = "%s %s HTTP/%s" % [method, uri.to_s, http_version]
|
68
|
+
str << $/ + headers.join($/)
|
69
|
+
str << $/ + $/ + body if body
|
70
|
+
str
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
str = "%s: %s %s HTTP/%s" % [time.strftime(NICE_TIME_FORMAT), method, uri.to_s, http_version]
|
75
|
+
str
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
class HTTPResponse < Stella::Storable
|
81
|
+
attr_reader :raw_data
|
82
|
+
|
83
|
+
field :time => DateTime
|
84
|
+
field :client_ip => String
|
85
|
+
field :server_ip => String
|
86
|
+
field :header => String
|
87
|
+
field :body => String
|
88
|
+
field :status => String
|
89
|
+
field :message => String
|
90
|
+
field :http_version => String
|
91
|
+
|
92
|
+
def initialize(raw_data=nil)
|
93
|
+
@raw_data = raw_data
|
94
|
+
if @raw_data
|
95
|
+
@status, @http_version, @message, @header, @body = HTTPUtil::parse_http_response(@raw_data)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def has_body?
|
100
|
+
@body && !@body.nil & !@body.empty?
|
101
|
+
end
|
102
|
+
def has_request?
|
103
|
+
false
|
104
|
+
end
|
105
|
+
def has_response?
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
def body
|
110
|
+
return nil unless @body
|
111
|
+
#Base64.encode64(@body)
|
112
|
+
(!header || !header[:Content_Type] || header[:Content_Type] !~ /text/) ? '' : @body
|
113
|
+
end
|
114
|
+
|
115
|
+
def inspect
|
116
|
+
headers = []
|
117
|
+
header.each_pair do |n,v|
|
118
|
+
headers << "#{n.to_s.gsub('_', '-')}: #{v[0]}"
|
119
|
+
end
|
120
|
+
str = "HTTP/%s %s (%s)" % [@http_version, @status, @message]
|
121
|
+
str << $/ + headers.join($/)
|
122
|
+
str << $/ + $/ + body if body
|
123
|
+
str
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_s
|
127
|
+
str = "%s: HTTP/%s %s (%s)" % [time.strftime(NICE_TIME_FORMAT), @http_version, @status, @message]
|
128
|
+
str
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Stella
|
4
|
+
class Logger
|
5
|
+
attr_accessor :debug_level
|
6
|
+
|
7
|
+
# +args+ is a hash of initialization arguments
|
8
|
+
# * <tt>:info_logger</tt> The IO class for info level logging. Default: STDOUT
|
9
|
+
# * <tt>:error_logger</tt> The IO class for error level logging. Default: STDERR
|
10
|
+
# * <tt>:debug_logger</tt> The IO class for error level logging. Default: STDERR
|
11
|
+
# * <tt>:debug</tt> Log debugging output, true or false (default)
|
12
|
+
def initialize(args={})
|
13
|
+
@debug_level = args[:debug] || false
|
14
|
+
@info_logger = args[:info_logger]
|
15
|
+
@error_logger = args[:error_logger]
|
16
|
+
@debug_logger = args[:debug_logger]
|
17
|
+
end
|
18
|
+
|
19
|
+
# +msgs+ is an array which can contain a list of messages or a symbol and a list of values
|
20
|
+
# If the first element is a symbol, this will return the output of Stella::Text.msg(msgs[0],msgs[1..-1])
|
21
|
+
def info(*msgs)
|
22
|
+
return if !msgs || msgs.empty?
|
23
|
+
if msgs[0].is_a? Symbol
|
24
|
+
txtsym = msgs.shift
|
25
|
+
info_logger.puts Stella::TEXT.msg(txtsym, msgs)
|
26
|
+
else
|
27
|
+
msgs.each do |m|
|
28
|
+
info_logger.puts m
|
29
|
+
end
|
30
|
+
end
|
31
|
+
info_logger.flush
|
32
|
+
end
|
33
|
+
|
34
|
+
def info_logger
|
35
|
+
@info_logger || $stdout
|
36
|
+
end
|
37
|
+
def debug_logger
|
38
|
+
@debug_logger || $stderr
|
39
|
+
end
|
40
|
+
def error_logger
|
41
|
+
@error_logger || $stderr
|
42
|
+
end
|
43
|
+
|
44
|
+
def flush
|
45
|
+
info_logger.flush
|
46
|
+
error_logger.flush
|
47
|
+
debug_logger.flush
|
48
|
+
end
|
49
|
+
|
50
|
+
# Print all messages on a single line.
|
51
|
+
def info_print(*msgs)
|
52
|
+
msgs.each do |m|
|
53
|
+
info_logger.print m
|
54
|
+
end
|
55
|
+
info_logger.flush
|
56
|
+
end
|
57
|
+
|
58
|
+
# Print all messages on a single line.
|
59
|
+
def info_printf(pattern, *vals)
|
60
|
+
info_logger.printf(pattern, *vals)
|
61
|
+
info_logger.flush
|
62
|
+
end
|
63
|
+
|
64
|
+
def debug(*msgs)
|
65
|
+
return unless @debug_level
|
66
|
+
msgs.each do |m|
|
67
|
+
debug_logger.puts "DEBUG: #{m}"
|
68
|
+
end
|
69
|
+
debug_logger.flush
|
70
|
+
end
|
71
|
+
def warn(ex, prefix="WARN: ")
|
72
|
+
error(ex, prefix)
|
73
|
+
end
|
74
|
+
|
75
|
+
def error(ex, prefix="ERR: ")
|
76
|
+
msg = (ex.kind_of? String) ? ex : ex.message
|
77
|
+
error_logger.puts "#{prefix}#{msg}"
|
78
|
+
return unless @debug_level && ex.kind_of?(Exception)
|
79
|
+
error_logger.puts("#{prefix}------------------------------------------")
|
80
|
+
error_logger.puts("#{prefix}#{ex.backtrace.join("\n")}")
|
81
|
+
error_logger.puts("#{prefix}------------------------------------------")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Stella
|
5
|
+
|
6
|
+
# An object for HTTP response content
|
7
|
+
#
|
8
|
+
class Response < Storable
|
9
|
+
|
10
|
+
field :errors => Array
|
11
|
+
field :content => Hash
|
12
|
+
field :messages => Array
|
13
|
+
field :success => TrueClass
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@success = false
|
17
|
+
@errors = []
|
18
|
+
@messages = []
|
19
|
+
@content = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def success?
|
23
|
+
@success
|
24
|
+
end
|
25
|
+
|
26
|
+
def add(key, value)
|
27
|
+
@content[key] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(key)
|
31
|
+
@content[key] if @content.has_key? key
|
32
|
+
end
|
33
|
+
|
34
|
+
def message(msg)
|
35
|
+
@messages.push(msg)
|
36
|
+
end
|
37
|
+
|
38
|
+
def error(msg)
|
39
|
+
@errors.push(msg)
|
40
|
+
end
|
41
|
+
|
42
|
+
def output(format='yaml')
|
43
|
+
format = 'yaml' unless self.respond_to? "output_#{format}"
|
44
|
+
#STDERR.puts "OUTPUT: #{format}"
|
45
|
+
self.send("output_#{format}")
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_hash
|
49
|
+
h = {}
|
50
|
+
h[:version] = API_VERSION
|
51
|
+
h[:errors] = @errors unless @errors.empty?
|
52
|
+
h[:messages] = @messages unless @messages.empty?
|
53
|
+
h[:content] = @content || {}
|
54
|
+
h[:success] = @success || false
|
55
|
+
h
|
56
|
+
end
|
57
|
+
|
58
|
+
def output_zip
|
59
|
+
output = @content
|
60
|
+
end
|
61
|
+
|
62
|
+
def output_yaml
|
63
|
+
to_hash.to_yaml
|
64
|
+
end
|
65
|
+
|
66
|
+
# http://evang.eli.st/blog/2007/2/22/my-rails-gotcha-custom-to_xml-in-a-hash-or-array
|
67
|
+
# http://api.rubyonrails.org/classes/ActiveRecord/XmlSerialization.html#M000910
|
68
|
+
def output_xml
|
69
|
+
output = "<StellaResponse success=\":[\">\n"
|
70
|
+
output << "<todo>implement XML</todo>\n"
|
71
|
+
output << "</StellaResponse>\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def output_json
|
75
|
+
to_hash.to_json
|
76
|
+
end
|
77
|
+
|
78
|
+
def output_html
|
79
|
+
"hello!"
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,201 @@
|
|
1
|
+
|
2
|
+
# TODO: Handle nested hashes and arrays.
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'utils/fileutil'
|
6
|
+
|
7
|
+
module Stella
|
8
|
+
class Storable
|
9
|
+
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze unless defined? NICE_TIME_FORMAT
|
10
|
+
SUPPORTED_FORMATS = %w{tsv csv yaml json}.freeze unless defined? SUPPORTED_FORMATS
|
11
|
+
|
12
|
+
attr_reader :format
|
13
|
+
|
14
|
+
def format=(v)
|
15
|
+
raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
|
16
|
+
@format = v
|
17
|
+
end
|
18
|
+
|
19
|
+
def init
|
20
|
+
self.class.send(:class_variable_set, :@@field_names, []) unless class_variable_defined?(:@@field_names)
|
21
|
+
self.class.send(:class_variable_set, :@@field_types, []) unless class_variable_defined?(:@@field_types)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.field(args={})
|
25
|
+
args.each_pair do |m,t|
|
26
|
+
|
27
|
+
[[:@@field_names, m], [:@@field_types, t]].each do |tuple|
|
28
|
+
class_variable_set(tuple[0], []) unless class_variable_defined?(tuple[0])
|
29
|
+
class_variable_set(tuple[0], class_variable_get(tuple[0]) << tuple[1])
|
30
|
+
end
|
31
|
+
|
32
|
+
next if method_defined?(m)
|
33
|
+
|
34
|
+
# NOTE: I need a way to put these in the caller's namespace... Here's they're shared by all
|
35
|
+
# the subclasses which is not helpful. It will likely involve Kernel#caller and binding.
|
36
|
+
# Maybe class_eval, wraped around def field.
|
37
|
+
|
38
|
+
|
39
|
+
define_method(m) do instance_variable_get("@#{m}") end
|
40
|
+
|
41
|
+
define_method("#{m}=") do |val|
|
42
|
+
instance_variable_set("@#{m}",val)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.field_names
|
48
|
+
class_variable_get(:@@field_names)
|
49
|
+
end
|
50
|
+
def self.field_types
|
51
|
+
class_variable_get(:@@field_types)
|
52
|
+
end
|
53
|
+
|
54
|
+
def field_names
|
55
|
+
self.class.send(:class_variable_get, :@@field_names)
|
56
|
+
end
|
57
|
+
|
58
|
+
def field_types
|
59
|
+
self.class.send(:class_variable_get, :@@field_types)
|
60
|
+
end
|
61
|
+
|
62
|
+
def format=(v)
|
63
|
+
raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
|
64
|
+
@format = v
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def dump(format=nil, with_titles=true)
|
69
|
+
format ||= @format
|
70
|
+
raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
|
71
|
+
send("to_#{format}", with_titles)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.from_file(file_path=nil, format=nil)
|
75
|
+
raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
|
76
|
+
format = format || File.extname(file_path).tr('.', '')
|
77
|
+
me = send("from_#{format}", FileUtil.read_file_to_array(file_path))
|
78
|
+
me.format = format
|
79
|
+
me
|
80
|
+
end
|
81
|
+
def to_file(file_path=nil, with_titles=true)
|
82
|
+
raise "Cannot store to nil path" if file_path.nil?
|
83
|
+
format = File.extname(file_path).tr('.', '')
|
84
|
+
format ||= @format
|
85
|
+
FileUtil.write_file(file_path, dump(format, with_titles))
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def self.from_hash(from={})
|
90
|
+
me = self.new
|
91
|
+
|
92
|
+
return me if !from || from.empty?
|
93
|
+
|
94
|
+
fnames = field_names
|
95
|
+
fnames.each_with_index do |key,index|
|
96
|
+
|
97
|
+
value = from[key]
|
98
|
+
|
99
|
+
# TODO: Correct this horrible implementation (sorry, me. It's just one of those days.)
|
100
|
+
|
101
|
+
if field_types[index] == Time
|
102
|
+
value = Time.parse(from[key].to_s)
|
103
|
+
elsif field_types[index] == DateTime
|
104
|
+
value = DateTime.parse(from[key].to_s)
|
105
|
+
elsif field_types[index] == TrueClass
|
106
|
+
value = (from[key].to_s == "true")
|
107
|
+
elsif field_types[index] == Float
|
108
|
+
value = from[key].to_f
|
109
|
+
elsif field_types[index] == Integer
|
110
|
+
value = from[key].to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
me.send("#{key}=", value) if self.method_defined?("#{key}=")
|
114
|
+
end
|
115
|
+
me
|
116
|
+
end
|
117
|
+
def to_hash(with_titles=true)
|
118
|
+
tmp = {}
|
119
|
+
field_names.each do |fname|
|
120
|
+
tmp[fname] = self.send(fname)
|
121
|
+
end
|
122
|
+
tmp
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def self.from_yaml(from=[])
|
127
|
+
# from is an array of strings
|
128
|
+
from_str = from.join('')
|
129
|
+
hash = YAML::load(from_str)
|
130
|
+
hash = from_hash(hash) if hash.is_a? Hash
|
131
|
+
hash
|
132
|
+
end
|
133
|
+
def to_yaml(with_titles=true)
|
134
|
+
to_hash.to_yaml
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def self.from_json(from=[])
|
139
|
+
require 'json'
|
140
|
+
# from is an array of strings
|
141
|
+
from_str = from.join('')
|
142
|
+
tmp = JSON::load(from_str)
|
143
|
+
hash_sym = tmp.keys.inject({}) do |hash, key|
|
144
|
+
hash[key.to_sym] = tmp[key]
|
145
|
+
hash
|
146
|
+
end
|
147
|
+
hash_sym = from_hash(hash_sym) if hash_sym.is_a? Hash
|
148
|
+
hash_sym
|
149
|
+
end
|
150
|
+
def to_json(with_titles=true)
|
151
|
+
require 'json'
|
152
|
+
to_hash.to_json
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_delimited(with_titles=false, delim=',')
|
156
|
+
values = []
|
157
|
+
field_names.each do |fname|
|
158
|
+
values << self.send(fname.to_s) # TODO: escape values
|
159
|
+
end
|
160
|
+
output = values.join(delim)
|
161
|
+
output = field_names.join(delim) << $/ << output if with_titles
|
162
|
+
output
|
163
|
+
end
|
164
|
+
def to_tsv(with_titles=false)
|
165
|
+
to_delimited(with_titles, "\t")
|
166
|
+
end
|
167
|
+
def to_csv(with_titles=false)
|
168
|
+
to_delimited(with_titles, ',')
|
169
|
+
end
|
170
|
+
def self.from_tsv(from=[])
|
171
|
+
self.from_delimited(from, "\t")
|
172
|
+
end
|
173
|
+
def self.from_csv(from=[])
|
174
|
+
self.from_delimited(from, ',')
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.from_delimited(from=[],delim=',')
|
178
|
+
return if from.empty?
|
179
|
+
# We grab an instance of the class so we can
|
180
|
+
hash = {}
|
181
|
+
|
182
|
+
fnames = values = []
|
183
|
+
if (from.size > 1 && !from[1].empty?)
|
184
|
+
fnames = from[0].chomp.split(delim)
|
185
|
+
values = from[1].chomp.split(delim)
|
186
|
+
else
|
187
|
+
fnames = self.field_names
|
188
|
+
values = from[0].chomp.split(delim)
|
189
|
+
end
|
190
|
+
|
191
|
+
fnames.each_with_index do |key,index|
|
192
|
+
next unless values[index]
|
193
|
+
hash[key.to_sym] = values[index]
|
194
|
+
end
|
195
|
+
hash = from_hash(hash) if hash.is_a? Hash
|
196
|
+
hash
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|