tcpsnitch_analyzer 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/tcpsnitch_analyzer +24 -0
- data/lib/tcpsnitch_analyzer.rb +101 -0
- data/lib/tcpsnitch_analyzer/descriptive_stat.rb +53 -0
- data/lib/tcpsnitch_analyzer/opt_parser.rb +65 -0
- data/lib/tcpsnitch_analyzer/proportion_stat.rb +22 -0
- data/lib/tcpsnitch_analyzer/time_serie_stat.rb +32 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 50b9dac312c23a4ec6e10f497b675078c0d6e7cf
|
4
|
+
data.tar.gz: 86e2119b21841973b78cadca2965d89377500ee4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b8446c031fe67e81e5cbd72ab6b0bd3dcd2bc827d023684bc2f3b59da73e6e7566d6d415938d417cd71fdf18319940ed0236a526988291134bb063aa811d5e8b
|
7
|
+
data.tar.gz: 58b7ef0d840080ad862399c4a55ec8b1b01d15ad3c33fe7ef94e0aeaf916618a299cc03d590f6234b2ed7244e7f9417fb5baa16b7311566d45b745e30bbac974
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tcpsnitch_analyzer'
|
4
|
+
|
5
|
+
# Parse options
|
6
|
+
options = TcpsnitchAnalyzer::OptParser.parse(ARGV)
|
7
|
+
|
8
|
+
# Check files
|
9
|
+
files = ARGV
|
10
|
+
|
11
|
+
if files.empty? then
|
12
|
+
TcpsnitchAnalyzer::error("missing argument, must provide at least 1 file")
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
if options.analysis_type == TcpsnitchAnalyzer::TimeSerieStat and
|
17
|
+
files.size > 1 then
|
18
|
+
TcpsnitchAnalyzer::error("invalid argument: time serie analysis has no "\
|
19
|
+
"meaning on multiple JSON traces")
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
# Start processing
|
24
|
+
TcpsnitchAnalyzer.process_files(options, files)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
require 'tcpsnitch_analyzer/opt_parser'
|
4
|
+
require 'tcpsnitch_analyzer/descriptive_stat'
|
5
|
+
require 'tcpsnitch_analyzer/proportion_stat'
|
6
|
+
require 'tcpsnitch_analyzer/time_serie_stat'
|
7
|
+
|
8
|
+
module TcpsnitchAnalyzer
|
9
|
+
EXECUTABLE = 'tcpsnitch_analyzer'
|
10
|
+
VERSION = '0.0.0'
|
11
|
+
|
12
|
+
# Configure Oj
|
13
|
+
Oj.default_options = { symbol_keys: true }
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def process_files(options, files)
|
17
|
+
# We DO NOT want to read the entire JSON files into memory.
|
18
|
+
# We DO NOT want to build a Ruby object for the entire JSON array.
|
19
|
+
#
|
20
|
+
# Instead, we want to the file line by line, where each line consists of
|
21
|
+
# a single event. We then instantiate each event individually and discard
|
22
|
+
# them as we consume the file, thus giving O(1) memory consumption.
|
23
|
+
matched_data = false
|
24
|
+
|
25
|
+
files.each do |file|
|
26
|
+
# IO.each should not read entire file in memory. To verify?
|
27
|
+
File.open(file).each_with_index do |line, index|
|
28
|
+
next if index == 0 # First line is opening '[' of JSON Array
|
29
|
+
next if line.eql? "]" # Last line is closing ']' of JSON Array
|
30
|
+
|
31
|
+
# Parse JSON object
|
32
|
+
begin
|
33
|
+
hash = Oj.load(line.chomp(",\n")) # Remove ',\n' after JSON object
|
34
|
+
rescue Exception => e
|
35
|
+
error(e)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Skip if filter does not match
|
39
|
+
next unless filter(hash, options.event_filter)
|
40
|
+
matched_data = true
|
41
|
+
|
42
|
+
# Extract value
|
43
|
+
begin
|
44
|
+
val = node_val(hash, options.node_path)
|
45
|
+
rescue Exception => e
|
46
|
+
error("invalid -n argument: '#{options.node_path}'")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Compute on value
|
50
|
+
if options.analysis_type == TimeSerieStat
|
51
|
+
options.analysis_type.add_point(node_val(hash, 'timestamp'), val)
|
52
|
+
else
|
53
|
+
options.analysis_type.add_val(val)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Output results
|
59
|
+
puts_options_header(options)
|
60
|
+
if matched_data
|
61
|
+
options.analysis_type.print(options)
|
62
|
+
else
|
63
|
+
puts "No data point matching criterias."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def error(msg)
|
68
|
+
puts "#{EXECUTABLE}: #{msg}."
|
69
|
+
puts "Try '#{EXECUTABLE} -h' for more information."
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def filter(hash, filter)
|
74
|
+
if filter then
|
75
|
+
hash[:type] == filter ? hash : nil
|
76
|
+
else
|
77
|
+
hash
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def val_for(hash, keys)
|
82
|
+
keys.reduce(hash) { |h, key| h[key] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def keys_from_path(path)
|
86
|
+
path.split('.').collect(&:to_sym)
|
87
|
+
end
|
88
|
+
|
89
|
+
def node_val(hash, path)
|
90
|
+
val_for(hash, keys_from_path(path))
|
91
|
+
end
|
92
|
+
|
93
|
+
def puts_options_header(options)
|
94
|
+
puts "JSON node:".ljust(15) + "#{options.node_path}"
|
95
|
+
event_filter = options.event_filter ? options.event_filter : "/"
|
96
|
+
puts "Type filter:".ljust(15) + event_filter
|
97
|
+
puts ""
|
98
|
+
end
|
99
|
+
|
100
|
+
end # class << self
|
101
|
+
end # module
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'descriptive_statistics'
|
2
|
+
require 'gnuplot'
|
3
|
+
|
4
|
+
module TcpsnitchAnalyzer
|
5
|
+
class DescriptiveStat
|
6
|
+
@@data = []
|
7
|
+
|
8
|
+
def self.add_val(val)
|
9
|
+
if val.is_a? Integer
|
10
|
+
@@data.push(val)
|
11
|
+
else
|
12
|
+
if val.is_a? Hash
|
13
|
+
TcpsnitchAnalyzer.error("invalid value for descriptive statistic: "\
|
14
|
+
"non-terminal node")
|
15
|
+
else
|
16
|
+
TcpsnitchAnalyzer.error("invalid value for descriptive statistic: '#{val}'")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.print(options)
|
22
|
+
puts "Descriptive statistics:"
|
23
|
+
@@data.descriptive_statistics.each do |key, value|
|
24
|
+
puts "#{key}".ljust(20) + "#{value}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Only plot CDF is we have a range
|
28
|
+
return unless @@data.range > 0
|
29
|
+
|
30
|
+
x = @@data.sort
|
31
|
+
n = x.size
|
32
|
+
y = x.map do |el|
|
33
|
+
((x.rindex { |v| v <= el } || -1.0) + 1.0) / n * 100.0
|
34
|
+
end
|
35
|
+
|
36
|
+
Gnuplot.open do |gp|
|
37
|
+
Gnuplot::Plot.new(gp) do |plot|
|
38
|
+
plot.xrange "[#{@@data.min}:#{@@data.max}]; set logscale x"
|
39
|
+
plot.title "CDF for #{options.node_path} (#{options.event_filter} events)"
|
40
|
+
plot.xlabel "Value"
|
41
|
+
plot.ylabel "Normal CDF"
|
42
|
+
plot.data << Gnuplot::DataSet.new([x,y]) do |ds|
|
43
|
+
ds.with = "lines"
|
44
|
+
ds.linewidth = 4
|
45
|
+
ds.title = options.node_path.split('.').last
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end # Gnuplot.open
|
49
|
+
end # def self.print
|
50
|
+
|
51
|
+
end # class
|
52
|
+
end # module
|
53
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module TcpsnitchAnalyzer
|
5
|
+
class OptParser
|
6
|
+
def self.parse(args)
|
7
|
+
options = OpenStruct.new
|
8
|
+
options.analysis_type = ProportionStat
|
9
|
+
options.event_filter = nil
|
10
|
+
options.node_path = "type"
|
11
|
+
|
12
|
+
begin
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: #{EXECUTABLE} [-h] [options] file...\n"
|
15
|
+
opts.separator ""
|
16
|
+
opts.separator "Analyze tcpsnitch JSON traces."
|
17
|
+
opts.separator ""
|
18
|
+
opts.separator "Options:"
|
19
|
+
|
20
|
+
opts.on("-a", "--analysis [TYPE]", "TYPE of statistic analysis:
|
21
|
+
descriptive (d), proportion (p) or timeserie (t).") do |type|
|
22
|
+
TcpsnitchAnalyzer.error("missing -a argument") unless type
|
23
|
+
|
24
|
+
case type.downcase
|
25
|
+
when /^d.*/
|
26
|
+
options.analysis_type = DescriptiveStat
|
27
|
+
when /^p.*/
|
28
|
+
options.analysis_type = ProportionStat
|
29
|
+
when /^t.*/
|
30
|
+
options.analysis_type = TimeSerieStat
|
31
|
+
else
|
32
|
+
TcpsnitchAnalyzer.error("invalid -a argument: '#{type}'")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-e", "--event [FILTER]", "filter on events of type FILTER") do |ev|
|
37
|
+
TcpsnitchAnalyzer.error("missing -e argument") unless ev
|
38
|
+
options.event_filter = ev
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on_tail("-h", "--help", "show this help text") do
|
42
|
+
puts opts
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-n", "--node [PATH]", "compute on node at PATH") do |node|
|
47
|
+
TcpsnitchAnalyzer.error("missing -n argument") unless node
|
48
|
+
options.node_path = node
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on_tail("--version", "show version") do
|
52
|
+
puts VERSION
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
end.parse!(args) # OptionParser
|
57
|
+
rescue OptionParser::ParseError => e
|
58
|
+
TcpsnitchAnalyzer.error(e)
|
59
|
+
end
|
60
|
+
|
61
|
+
options
|
62
|
+
end # def self.parse
|
63
|
+
|
64
|
+
end # class
|
65
|
+
end # module
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module TcpsnitchAnalyzer
|
2
|
+
class ProportionStat
|
3
|
+
@@count = 0
|
4
|
+
@@hash = Hash.new(0)
|
5
|
+
|
6
|
+
def self.add_val(val)
|
7
|
+
@@count += 1
|
8
|
+
@@hash[val] += 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.print(options)
|
12
|
+
puts "Proportion statistics:"
|
13
|
+
|
14
|
+
@@hash.sort_by { |val, count| -count }.each do |val, count|
|
15
|
+
pc = ((count.to_f/@@count) * 100).round(2)
|
16
|
+
puts "#{pc}%".ljust(7) + "(#{count})".ljust(10) + "#{val}"
|
17
|
+
end
|
18
|
+
puts "100%".ljust(7) + "(#{@@count})"
|
19
|
+
end
|
20
|
+
|
21
|
+
end # class
|
22
|
+
end # module
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module TcpsnitchAnalyzer
|
2
|
+
class TimeSerieStat
|
3
|
+
@@x = []
|
4
|
+
@@y = []
|
5
|
+
|
6
|
+
def self.add_point(timestamp, y)
|
7
|
+
usec = timestamp[:sec] * 1000000 + timestamp[:usec]
|
8
|
+
@@min ||= usec
|
9
|
+
@@x.push(usec-@@min)
|
10
|
+
@@y.push(y)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.print(options)
|
14
|
+
puts "Time serie plot"
|
15
|
+
|
16
|
+
Gnuplot.open do |gp|
|
17
|
+
Gnuplot::Plot.new(gp) do |plot|
|
18
|
+
plot.title "Time serie: #{options.node_path}(t)"
|
19
|
+
plot.xlabel "Micro seconds"
|
20
|
+
plot.ylabel options.node_path.split('.').last.capitalize
|
21
|
+
|
22
|
+
plot.data << Gnuplot::DataSet.new([@@x,@@y]) do |ds|
|
23
|
+
ds.with = "lines"
|
24
|
+
ds.linewidth = 4
|
25
|
+
ds.notitle
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end # Gnuplot.open
|
29
|
+
end # def self.print
|
30
|
+
|
31
|
+
end # class
|
32
|
+
end # module
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tcpsnitch_analyzer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gregory Vander Schueren
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: oj
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.18'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.18'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: descriptive_statistics
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: gnuplot
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.6'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.6'
|
55
|
+
description: Tool to analyze tcpsnitch traces. Longer description to come.
|
56
|
+
email: gregory.vanderschueren@gmail.com
|
57
|
+
executables:
|
58
|
+
- tcpsnitch_analyzer
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- bin/tcpsnitch_analyzer
|
63
|
+
- lib/tcpsnitch_analyzer.rb
|
64
|
+
- lib/tcpsnitch_analyzer/descriptive_stat.rb
|
65
|
+
- lib/tcpsnitch_analyzer/opt_parser.rb
|
66
|
+
- lib/tcpsnitch_analyzer/proportion_stat.rb
|
67
|
+
- lib/tcpsnitch_analyzer/time_serie_stat.rb
|
68
|
+
homepage: http://rubygems.org/gems/tcpsnitch_analyzer
|
69
|
+
licenses:
|
70
|
+
- GPL-3.0
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.6.8
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Tool to analyze tcpsnitch traces
|
92
|
+
test_files: []
|