solutious-stella 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|