tcpsnitch_analyzer 0.0.0
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.
- 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: []
|