tweek 0.0.1 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/tweek +194 -0
- data/lib/tweek/app.rb +146 -0
- data/lib/tweek/section.rb +252 -0
- data/lib/tweek/version.rb +1 -1
- data/lib/tweek.rb +1 -3
- data/tweek.gemspec +2 -4
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc8acd5816827fe451b4f223f31c70be9862dc25
|
4
|
+
data.tar.gz: 114a4da6ae11f12d5fa23edf7b43419fc0d1a78a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c69ab3d791fad607f25d4c6361387d3d59ac99467b9d79b2f6a94cf02c29541912c2f8af85fa430ad464b94cdf50a52e84d15f6012d35e91a2ea1753422f58d
|
7
|
+
data.tar.gz: 3b20cc0973d271c4d4a5c06aa4c6d247817cc9cd71d08a22fb8604e97d70dee21508d65b8467f8984c18ca3d643130cc126810fbfdd0215568f726e485845e10
|
data/bin/tweek
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# A script to check a variety of Linux system settings
|
4
|
+
# See the sample config after __END__ for a description of these.
|
5
|
+
# By Nick Townsend, June 2017
|
6
|
+
#
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
require 'optparse'
|
10
|
+
require 'ostruct'
|
11
|
+
require 'tweek'
|
12
|
+
require 'tweek/app'
|
13
|
+
require 'tweek/version'
|
14
|
+
|
15
|
+
trap('INT') do
|
16
|
+
print "\n"
|
17
|
+
exit(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
gflag = false
|
21
|
+
distro = nil
|
22
|
+
distro_ver = nil
|
23
|
+
kernel_ver = nil
|
24
|
+
OptionParser.new do |o|
|
25
|
+
o.banner = "Usage: #{$0} [options] [settings_file...]"
|
26
|
+
o.separator ""
|
27
|
+
o.separator "Check the system parameters specified in the settings files"
|
28
|
+
o.separator "(If not supplied then built-in defaults are used)"
|
29
|
+
o.separator "Options:"
|
30
|
+
o.on('--distro=NAME', "Set Distro (default uses lsb_release)" ) do |d|
|
31
|
+
distro = d
|
32
|
+
end
|
33
|
+
o.on('--distro-ver=X.Y.Z', "Set Distro version (default uses lsb_release)" ) do |dv|
|
34
|
+
distro_ver = dv
|
35
|
+
end
|
36
|
+
o.on('--kernel-ver=X.Y.Z', "Set Kernel version (default uses uname)" ) do |k|
|
37
|
+
kernel_ver = k
|
38
|
+
end
|
39
|
+
o.on( '-g', '--generate', "Generate a copy of the input file(s)", "containing current values on STDOUT" ) do
|
40
|
+
gflag = true
|
41
|
+
end
|
42
|
+
o.on( '-s', '--show', "Write the default parameters to STDOUT",
|
43
|
+
"Use as a fully documented starting point"
|
44
|
+
) do
|
45
|
+
DATA.each_line{|l| STDOUT.puts l} # no copy_stream in 1.8.7
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
o.on('-v', "--version", "Show version") do
|
49
|
+
puts Tweek::VERSION
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
o.on( '-?', '--help', 'Display this screen' ) do
|
53
|
+
puts o
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
o.separator <<MSG
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
0: If no mismatches, >0 number of mismatches
|
60
|
+
MSG
|
61
|
+
o.parse! rescue (puts "Error: #{$!}"; puts o; exit)
|
62
|
+
end
|
63
|
+
if distro.nil? or distro_ver.nil?
|
64
|
+
if RUBY_PLATFORM !~ /-linux-gnu$/
|
65
|
+
STDERR.puts "Tweek only runs on Linux!"
|
66
|
+
exit 2
|
67
|
+
end
|
68
|
+
begin
|
69
|
+
distro ||= `lsb_release -i`.partition(':').last.strip
|
70
|
+
distro_ver ||= `lsb_release -r`.partition(':').last.strip
|
71
|
+
rescue
|
72
|
+
puts "'lsb_release' not found! Install or specify --distro and --distro-ver on command line"
|
73
|
+
exit 2
|
74
|
+
end
|
75
|
+
end
|
76
|
+
if kernel_ver.nil?
|
77
|
+
begin
|
78
|
+
kernel_ver ||= `uname -r`.partition('-').first
|
79
|
+
rescue
|
80
|
+
puts "'uname' not found! Install or specify --kernel-ver on command line"
|
81
|
+
exit 2
|
82
|
+
end
|
83
|
+
end
|
84
|
+
cs = Tweek::App.new(gflag, true, distro, distro_ver, kernel_ver)
|
85
|
+
if ARGV.empty?
|
86
|
+
fh = DATA
|
87
|
+
fh.lineno -= 1
|
88
|
+
else
|
89
|
+
fh = ARGF
|
90
|
+
end
|
91
|
+
cs.read_sections(fh)
|
92
|
+
exit cs.results
|
93
|
+
|
94
|
+
__END__
|
95
|
+
# A documentation-rich sample for the parameter checking script
|
96
|
+
#
|
97
|
+
# Comments begin with # and extend to the end of the line
|
98
|
+
# Block comments are Ruby style =begin and =end
|
99
|
+
# Conditionals use 'k' for kernel version, 'v' for distro version and 'd' for distro
|
100
|
+
# Version conditionals use the http://guides.rubygems.org/patterns/#semantic-versioning
|
101
|
+
# Distro conditionals follow Ruby syntax and you may use string or regex comparisons
|
102
|
+
#
|
103
|
+
BLKDEV xvda # list one or more block devices here (filename globs OK)
|
104
|
+
read_ahead_kb = 1024 # to drive more merged IO
|
105
|
+
scheduler = noop # less CPU wasted stacking an elevator (for SSDs - still for EBS?)
|
106
|
+
rq_affinity = 2 # 1 to the CPU group, 2 to the actual CPU
|
107
|
+
nr_requests = 256
|
108
|
+
|
109
|
+
CLOCKSOURCE # Use cat /sys/devices/system/cl*/cl*/available_clocksource to see what's available
|
110
|
+
# xen is the default as supported on all instance types
|
111
|
+
# tsc is hardware supported and is significantly quicker, should be used on all post Sandybridge CPUs
|
112
|
+
clocksource0 = tsc
|
113
|
+
|
114
|
+
KERNEL # Kernel command line parameters
|
115
|
+
# true must be present, false must be absent, anything else is a value to be matched
|
116
|
+
xen_nopvspin = true
|
117
|
+
maxcpus = 63 # Set the number of physical cores, eliminates the 'B' hyperthreads
|
118
|
+
# This stops cores idling, eliminating turbo, but decrease latency of core spin-up
|
119
|
+
# intel_idle.max_cstate=1
|
120
|
+
numa = off if k>3.9 # won't move memory around between NUMA zones, useful with large working set
|
121
|
+
|
122
|
+
SYSCTL
|
123
|
+
vm.min_free_kbytes = 838608
|
124
|
+
vm.zone_reclaim = 1 if k~>4.9.0,d=='Ubuntu'
|
125
|
+
vm.zone_reclaim_mode = 1 if k~>3.13.0, k<4.9.0
|
126
|
+
|
127
|
+
# These parameters taken from Brendan Gregg's Re:Invent talk 2014
|
128
|
+
net.core.somaxconn = 1000
|
129
|
+
net.core.netdev_max_backlog = 5000
|
130
|
+
net.core.rmem_max = 16777216
|
131
|
+
net.core.wmem_max = 16777216
|
132
|
+
net.ipv4.tcp_wmem = 4096 12582912 16777216
|
133
|
+
net.ipv4.tcp_rmem = 4096 12582912 16777216
|
134
|
+
net.ipv4.tcp_max_syn_backlog = 8096
|
135
|
+
|
136
|
+
net.ipv4.tcp_slow_start_after_idle = 0
|
137
|
+
|
138
|
+
# When making lots of connections to a server enables reuse of a conn in time-wait status,
|
139
|
+
# if you run out of slots because of this then packets can get dropped!
|
140
|
+
#
|
141
|
+
net.ipv4.tcp_tw_reuse = 1
|
142
|
+
net.ipv4.ip_local_port_range = 1024 65535
|
143
|
+
net.ipv4.tcp_abort_on_overflow = 1 # allows a connection that is queued to be reset, not just dropped.
|
144
|
+
net.ipv4.tcp_fin_timeout = 50 if d=~/Amazon|RedHat/i
|
145
|
+
|
146
|
+
# Smoother page cache flushing: background flush earlier, aggressive later
|
147
|
+
# Brendan Gregg, Re:Invent 2014
|
148
|
+
#
|
149
|
+
vm.dirty_ratio = 80
|
150
|
+
vm.dirty_background_ratio = 5
|
151
|
+
vm.dirty_expire_centisecs = 12000
|
152
|
+
|
153
|
+
=begin
|
154
|
+
# These are all the recommended RedHat TCP settings
|
155
|
+
# not yet validated, so used as demo of multiline comments
|
156
|
+
net.ipv4.tcp_syn_retries = 0
|
157
|
+
net.ipv4.tcp_synack_retries = 0
|
158
|
+
net.ipv4.tcp_timestamps = 0
|
159
|
+
=end
|
160
|
+
|
161
|
+
NET eth0 # Define one or more network devices
|
162
|
+
# Check the IRQ strategy:
|
163
|
+
# aws: Use first numa node, split RSS from RPS
|
164
|
+
# RSS allocated across 0-7 (node 0), RPS on first numa node
|
165
|
+
# pin: Differs depending on number of cores
|
166
|
+
# 32 Apportion across the single node as follows:
|
167
|
+
# RPS allocated across cores 10-15 and 18-31
|
168
|
+
# RSS allocated across 0-7
|
169
|
+
# 64 Use both NUMA nodes, leave first core free on each node
|
170
|
+
# RPS allocated across remaining cores on each node
|
171
|
+
# RSS allocated across 1-4 (node 0) then 17-20 (node 1)
|
172
|
+
# null: Ignore this adapter
|
173
|
+
irqs = pin
|
174
|
+
driver = ena
|
175
|
+
# Check values in /sys/class/net/<device>/ tree
|
176
|
+
mtu = 9001
|
177
|
+
device/driver/module/version = 1.1.3
|
178
|
+
|
179
|
+
EXT4 /dev/xvda1 # Define one or more EXT4 devices or mountpoints (filename globs OK)
|
180
|
+
# Available parameters are those exposed in /proc/fs/ext4/<device>/options
|
181
|
+
|
182
|
+
barrier=true
|
183
|
+
stripe=0
|
184
|
+
|
185
|
+
XFS /mnt/xfstest # Define one or more XFS devices or mountpoints (filename globs OK)
|
186
|
+
# XFS parameters in the form "x.y" where x is log|data|naming|realtime
|
187
|
+
# and y is a valid name, eg. data.sunit or log.sunit
|
188
|
+
|
189
|
+
data.swidth = 2048
|
190
|
+
data.sunit = 128
|
191
|
+
data.bsize = 4096
|
192
|
+
log.sunit = 1
|
193
|
+
|
194
|
+
# vim: ft=ruby ts=8 sw=2 sts=2 et
|
data/lib/tweek/app.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'optparse'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'tweek'
|
5
|
+
require 'tweek/section'
|
6
|
+
|
7
|
+
class Tweek::App
|
8
|
+
|
9
|
+
attr_reader :distro, :distro_version, :kernel_version
|
10
|
+
|
11
|
+
def initialize( gflag, tty, distro, distro_version, kernel_version)
|
12
|
+
@distro = distro
|
13
|
+
@distro_version = Gem::Version.new(distro_version)
|
14
|
+
@kernel_version = Gem::Version.new(kernel_version)
|
15
|
+
@nparams = 0
|
16
|
+
@mismatches = []
|
17
|
+
@errors = []
|
18
|
+
@skips = []
|
19
|
+
@warns = []
|
20
|
+
@generated = []
|
21
|
+
@gflag = gflag
|
22
|
+
@tty = tty
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define data-collection methods so we can easily stub them for tests
|
26
|
+
#
|
27
|
+
def self.bootline
|
28
|
+
@bootline ||= File.open("/proc/cmdline", &:read).chomp rescue 'nowt'
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate type, e
|
32
|
+
return unless @gflag
|
33
|
+
case type
|
34
|
+
when :line
|
35
|
+
@generated.push "#{e.line}#{e.comment}"
|
36
|
+
when :section
|
37
|
+
@generated.push "#{e.type} #{e.list} #{e.comment}"
|
38
|
+
when :entry
|
39
|
+
cond = e.cond.nil? ? "": " if #{e.cond}"
|
40
|
+
comment = e.comment.nil? ? "#": e.comment
|
41
|
+
if e.actual
|
42
|
+
@generated.push "#{e.param} = #{e.actual}#{cond} #{comment} [expected #{e.value}]"
|
43
|
+
else
|
44
|
+
@generated.push "#{e.param} = #{e.value}#{cond} #{comment} [skipped]"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def error msg
|
50
|
+
@errors.push msg unless @errors.first == msg
|
51
|
+
end
|
52
|
+
|
53
|
+
def messages
|
54
|
+
strings = @errors + @mismatches
|
55
|
+
return strings.size == 0 ? "": strings.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
def warn msg
|
59
|
+
@warns.push msg unless @warns.first == msg
|
60
|
+
end
|
61
|
+
|
62
|
+
def skipcond entry
|
63
|
+
@skips.push entry
|
64
|
+
end
|
65
|
+
|
66
|
+
def condfail cond
|
67
|
+
return false unless cond
|
68
|
+
failures = 0
|
69
|
+
reqs = cond.split(/\s*,\s*/)
|
70
|
+
reqs.each do |req|
|
71
|
+
ok = case var = req.slice!(0)
|
72
|
+
when 'k'
|
73
|
+
Gem::Requirement.new(req).satisfied_by?(kernel_version)
|
74
|
+
when 'v'
|
75
|
+
Gem::Requirement.new(req).satisfied_by?(distro_version)
|
76
|
+
when 'd'
|
77
|
+
op = req.slice!(/^[<>~!=]+/)
|
78
|
+
begin
|
79
|
+
eval "distro #{op} #{req}"
|
80
|
+
rescue Exception => e
|
81
|
+
raise ArgumentError.new("entry has condition error: #{e.message}")
|
82
|
+
end
|
83
|
+
else
|
84
|
+
raise ArgumentError.new("entry has invalid condition variable: #{var}")
|
85
|
+
end
|
86
|
+
failures += 1 unless ok
|
87
|
+
end
|
88
|
+
return failures > 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def assert_equal expected, actual, msg = ''
|
92
|
+
@nparams += 1
|
93
|
+
if expected === actual
|
94
|
+
STDERR.print "." if @tty
|
95
|
+
return 0
|
96
|
+
else
|
97
|
+
STDERR.print "F" if @tty
|
98
|
+
@mismatches.push "#{msg}: Expected #{expected}\n#{" "*(msg.size+4)}Actual #{actual}"
|
99
|
+
end
|
100
|
+
return 1
|
101
|
+
end
|
102
|
+
|
103
|
+
# Read the entire file and split into sections
|
104
|
+
#
|
105
|
+
def read_sections handle
|
106
|
+
section = Tweek::Section.new(0,'<initial>','','')
|
107
|
+
while line = handle.gets
|
108
|
+
line.chomp!
|
109
|
+
if (line =~ /^=begin/)..(line =~ /^=end/)
|
110
|
+
section.push OpenStruct.new( :line => line, :lineno => handle.lineno )
|
111
|
+
next
|
112
|
+
end
|
113
|
+
|
114
|
+
comment = line.slice!(/#.*$/)
|
115
|
+
if line.empty?
|
116
|
+
section.push OpenStruct.new( :line => line, :lineno => handle.lineno, :comment => comment )
|
117
|
+
next
|
118
|
+
end
|
119
|
+
|
120
|
+
if /^\s*([A-Z0-9]+)\s*(.*?)\s*$/ =~ line # We've hit a new section
|
121
|
+
section.process(self)
|
122
|
+
section = Tweek::Section.new(handle.lineno, $1, $2, comment)
|
123
|
+
next
|
124
|
+
end
|
125
|
+
|
126
|
+
if /^\s*(.+?)\s*=\s*(.*?)\s*(if\s+(.*))?$/ =~ line
|
127
|
+
section.push OpenStruct.new( :lineno => handle.lineno, :param => $1, :value => $2,
|
128
|
+
:cond => $4, :comment => comment )
|
129
|
+
next
|
130
|
+
end
|
131
|
+
error "#{handle.lineno}: Unrecognized line: #{line}"
|
132
|
+
end
|
133
|
+
section.process(self)
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def results
|
138
|
+
STDERR.puts "\nDistro: #{@distro} Version: #{@distro_version} Kernel: #{@kernel_version}"
|
139
|
+
STDERR.puts "\n#{@nparams} parameters checked, #{@skips.size} skipped, #{@mismatches.size} mismatches, #{@warns.size} warnings, #{@errors.size} errors"
|
140
|
+
STDERR.puts "\n#{@errors.join("\n")}" unless @errors.empty?
|
141
|
+
STDERR.puts "\n#{@warns.join("\n")}" unless @warns.empty?
|
142
|
+
STDERR.puts "\n#{@mismatches.join("\n")}" unless @mismatches.empty?
|
143
|
+
@generated.each { |l| STDOUT.puts l}
|
144
|
+
return @mismatches.size
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
# A section is an array of the parsed lines, each element is an OpenStruct
|
2
|
+
# There are the following types of Entry
|
3
|
+
# With line attributes - single lines
|
4
|
+
# With section type and list attributes
|
5
|
+
# With parameter value and conditional attributes
|
6
|
+
# All entries may have an optional comment attribute
|
7
|
+
#
|
8
|
+
class Tweek::Section < Array
|
9
|
+
|
10
|
+
attr_reader :lineno, :type, :list, :comment
|
11
|
+
def initialize lineno, type, list, comment
|
12
|
+
@lineno = lineno
|
13
|
+
@type = type
|
14
|
+
@list = list
|
15
|
+
@comment = comment
|
16
|
+
end
|
17
|
+
|
18
|
+
def section_entry
|
19
|
+
OpenStruct.new(:lineno => lineno, :type => type, :list => list, :comment => comment )
|
20
|
+
end
|
21
|
+
|
22
|
+
# Iterate through each entry in the section.
|
23
|
+
# If it's a comment then pass to the generate method
|
24
|
+
# If it has a condition which is not met then pass to generate
|
25
|
+
# If it passes yield the entry
|
26
|
+
#
|
27
|
+
def each_entry cs, &block
|
28
|
+
self.each do |entry|
|
29
|
+
if entry.line
|
30
|
+
cs.generate :line, entry
|
31
|
+
next
|
32
|
+
end
|
33
|
+
begin
|
34
|
+
if cs.condfail entry.cond
|
35
|
+
cs.generate :entry, entry
|
36
|
+
cs.skipcond entry
|
37
|
+
next
|
38
|
+
end
|
39
|
+
yield entry
|
40
|
+
rescue Exception => e
|
41
|
+
cs.error "#{entry.lineno}: #{e.message}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def process cs
|
47
|
+
|
48
|
+
cs.generate :section, section_entry unless type == '<initial>'
|
49
|
+
|
50
|
+
return if self.empty?
|
51
|
+
|
52
|
+
case type
|
53
|
+
when '<initial>'
|
54
|
+
each_entry (cs) do |entry|
|
55
|
+
end
|
56
|
+
|
57
|
+
when 'BLKDEV' # BLOCK DEVICE PARAMETERS
|
58
|
+
|
59
|
+
Dir.chdir('/dev')
|
60
|
+
devices = Dir.glob(self.list.split(' ').map{|dev| dev.gsub(/^\/dev\//,'')})
|
61
|
+
|
62
|
+
cs.warn "Block device check skipped: no devices found" if devices.empty?
|
63
|
+
|
64
|
+
devices.each do |blk|
|
65
|
+
each_entry (cs) do |entry|
|
66
|
+
entry.actual = File.open("/sys/block/#{blk}/queue/#{entry.param}", &:read).chomp rescue $!.message
|
67
|
+
if entry.param == 'scheduler'
|
68
|
+
entry.actual = entry.actual.slice(/\[(.*?)\]/,1) unless entry.actual == 'none'
|
69
|
+
end
|
70
|
+
cs.assert_equal entry.value, entry.actual, "/dev/#{blk} #{entry.param}"
|
71
|
+
cs.generate :entry, entry
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
when 'KERNEL' # KERNEL PARAMETERS
|
76
|
+
each_entry (cs) do |entry|
|
77
|
+
actual = /\b(#{entry.param})(=\S+)?\b/.match(Tweek::App.bootline)
|
78
|
+
case
|
79
|
+
when (entry.value == 'true' and actual)
|
80
|
+
observed = actual.to_s
|
81
|
+
expected = entry.param
|
82
|
+
when (entry.value == 'true' and actual.nil?)
|
83
|
+
expected = 'to be found'
|
84
|
+
observed = 'was not found'
|
85
|
+
when (entry.value == 'false' and actual)
|
86
|
+
expected = 'to not be found'
|
87
|
+
observed = 'was found'
|
88
|
+
when (entry.value == 'false' and actual.nil?)
|
89
|
+
expected = nil
|
90
|
+
observed = nil
|
91
|
+
else
|
92
|
+
expected = entry.value
|
93
|
+
observed = actual.nil? ? 'was not found' : actual[2][1..-1]
|
94
|
+
end
|
95
|
+
cs.assert_equal expected, observed, "Kernel parameter #{entry.param}"
|
96
|
+
entry.actual = !actual.nil?
|
97
|
+
cs.generate :entry, entry
|
98
|
+
end
|
99
|
+
|
100
|
+
when 'CLOCKSOURCE'
|
101
|
+
each_entry (cs) do |entry|
|
102
|
+
entry.actual=File.open("/sys/devices/system/clocksource/#{entry.param}/current_clocksource",&:read).chomp rescue $!.message
|
103
|
+
cs.assert_equal entry.value, entry.actual, entry.param
|
104
|
+
cs.generate :entry, entry
|
105
|
+
end
|
106
|
+
|
107
|
+
when 'SYSCTL' # SYSCTL SETTINGS
|
108
|
+
each_entry (cs) do |entry|
|
109
|
+
file = entry.param.gsub(/\./,'/')
|
110
|
+
entry.actual = File.open("/proc/sys/#{file}", &:read).chomp.gsub(/\s+/,' ') rescue $!.message
|
111
|
+
cs.assert_equal entry.value, entry.actual, entry.param
|
112
|
+
cs.generate :entry, entry
|
113
|
+
end
|
114
|
+
|
115
|
+
when 'NET' # NETWORK DEVICES
|
116
|
+
devices = self.list.split(' ').map{|dev| dev.gsub(/^\/dev\//,'')}
|
117
|
+
cs.warn "Network device check skipped: no devices found" if devices.empty?
|
118
|
+
|
119
|
+
devices.each do |netdev|
|
120
|
+
each_entry (cs) do |entry|
|
121
|
+
|
122
|
+
case entry.param
|
123
|
+
when 'driver'
|
124
|
+
entry.actual = File.basename(File.readlink("/sys/class/net/#{netdev}/device/driver"))
|
125
|
+
cs.assert_equal entry.value, entry.actual, entry.param
|
126
|
+
|
127
|
+
when 'irqs'
|
128
|
+
|
129
|
+
irqs=File.open("/proc/interrupts", &:readlines).grep(/\b#{netdev}\b/).map{|m| m.partition(':')[0].strip}
|
130
|
+
|
131
|
+
if irqs.empty?
|
132
|
+
cs.warn "#{netdev} IRQ check skipped: none found"
|
133
|
+
entry.actual = 'null'
|
134
|
+
cs.generate :entry, entry
|
135
|
+
next
|
136
|
+
end
|
137
|
+
|
138
|
+
ncores = `getconf _NPROCESSORS_ONLN`.chomp.to_i rescue 0
|
139
|
+
mismatches = 0
|
140
|
+
irqs.each_with_index do |irq, ix|
|
141
|
+
case entry.value
|
142
|
+
when 'null','none','ignore'
|
143
|
+
next
|
144
|
+
|
145
|
+
when 'aws'
|
146
|
+
expected_rps = ("00000000," * 3) + "0000ff00"
|
147
|
+
expected_rss = ("00000000," * 3) + sprintf("%08x", 1<<ix)
|
148
|
+
|
149
|
+
when 'pin'
|
150
|
+
case ncores
|
151
|
+
when 32 # RPS on cores 10-15 and 18-31, RSS tied to 0-7
|
152
|
+
case ix
|
153
|
+
when 0..7
|
154
|
+
expected_rps = ("00000000," * 3) + "fffefe00"
|
155
|
+
expected_rss = ("00000000," * 3) + sprintf("%08x", 1<<(ix+1))
|
156
|
+
else
|
157
|
+
cs.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle IRQ#{ix}"
|
158
|
+
end
|
159
|
+
|
160
|
+
when 64 # Split the work across the two nodes
|
161
|
+
case ix
|
162
|
+
when 0..3 # alloc on node 0 (cpus 0-3)
|
163
|
+
expected_rps = ("00000000," * 2) + "0000ffff," + "0000ffe0"
|
164
|
+
expected_rss = ("00000000," * 3) + sprintf("%08x", 1<<(ix+1))
|
165
|
+
when 4..7 # alloc on node 1 (
|
166
|
+
expected_rps = ("00000000," * 2) + "ffff0000," + "ffe00000"
|
167
|
+
expected_rss = ("00000000," * 3) + sprintf("%08x", 1<<(ix + 9))
|
168
|
+
else
|
169
|
+
cs.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle IRQ#{ix}"
|
170
|
+
end
|
171
|
+
|
172
|
+
else
|
173
|
+
cs.error "#{entry.lineno}: Can't handle #{ncores} cores in IRQ strategy '#{entry.value}'"
|
174
|
+
end
|
175
|
+
|
176
|
+
else
|
177
|
+
cs.error "#{entry.lineno}: Unrecognized #{netdev} IRQ strategy '#{entry.value}'"
|
178
|
+
end
|
179
|
+
actual_rss = File.open("/proc/irq/#{irq}/smp_affinity",&:read).chomp rescue $!.message
|
180
|
+
actual_rps = File.open("/sys/class/net/#{netdev}/queues/rx-#{ix}/rps_cpus", &:read).chomp rescue $!.message
|
181
|
+
mismatches += cs.assert_equal expected_rss, actual_rss, "#{netdev}_irq_#{irq}_rss"
|
182
|
+
mismatches += cs.assert_equal expected_rps, actual_rps, "#{netdev}_irq_#{irq}_rps"
|
183
|
+
end
|
184
|
+
entry.actual = 'null' if mismatches > 0
|
185
|
+
|
186
|
+
else # try getting the name
|
187
|
+
begin
|
188
|
+
entry.actual = File.open("/sys/class/net/#{netdev}/#{entry.param}", &:read).chomp.gsub(/\s+/,' ')
|
189
|
+
cs.assert_equal entry.value, entry.actual, entry.param
|
190
|
+
|
191
|
+
rescue
|
192
|
+
cs.error "#{entry.lineno}: Network parameter #{entry.param} not handled: #{$!.message}"
|
193
|
+
next
|
194
|
+
end
|
195
|
+
end
|
196
|
+
cs.generate :entry, entry
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
when 'EXT4' # EXT4 filesystems info via `dumpe2fs -h /dev/#{device}`
|
201
|
+
mounted = File.open("/proc/mounts", &:readlines).map{|m| m.split(' ')}
|
202
|
+
mounts = Dir.glob(self.list.split(' '))
|
203
|
+
mounts.each do |mount|
|
204
|
+
unless this = mounted.select{|m| (mount == m[0] or mount == m[1]) and m[2] == 'ext4'}.first
|
205
|
+
cs.warn "EXT4 path #{mount} is not mounted or is not mounted as ext4"
|
206
|
+
next
|
207
|
+
end
|
208
|
+
device = this[0].gsub('/dev/','')
|
209
|
+
optstring = File.open("/proc/fs/ext4/#{device}/options",&:read)
|
210
|
+
options = Hash[optstring.scan(/([^=\s]+)=([^=\s,]+)/)] # options a=b
|
211
|
+
optstring.split(/\n/).reject{|o| o.index('=')}.each{|o| options[o] = 'true'}
|
212
|
+
each_entry (cs) do |entry|
|
213
|
+
entry.actual = options[entry.param] || "<not found>"
|
214
|
+
cs.assert_equal entry.value, entry.actual, "EXT4 #{mount}: #{entry.param}"
|
215
|
+
cs.generate :entry, entry
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
when 'XFS' # XFS filesystems info via `xfs_info`
|
220
|
+
# Dynamically via: /proc/fs/xfs/... ?
|
221
|
+
mounts = self.list.split(' ')
|
222
|
+
if mounts.empty?
|
223
|
+
cs.warn "#{self.lineno}: XFS device check skipped: no mountpoints found"
|
224
|
+
end
|
225
|
+
mounts.each do |mount|
|
226
|
+
xfsinfo = `xfs_info #{mount} 2>/dev/null`.chomp
|
227
|
+
if $?.exitstatus > 0
|
228
|
+
cs.warn "No XFS filesystem at #{mount}"
|
229
|
+
each_entry (cs) { |entry| cs.generate :entry, entry }
|
230
|
+
next
|
231
|
+
end
|
232
|
+
data = Hash[xfsinfo.slice!(/^meta-data.*(?=^naming)/m).scan(/([^=\s]+)=([^=\s,]+)/)]
|
233
|
+
naming = Hash[xfsinfo.slice!(/^naming.*(?=^log)/m).scan(/([^=\s]+)=([^=\s,]+)/)]
|
234
|
+
log = Hash[xfsinfo.slice!(/^log.*(?=^realtime)/m).scan(/([^=\s]+)=([^=\s,]+)/)]
|
235
|
+
realtime = Hash[xfsinfo.slice!(/^realtime.*$/m).scan(/([^=\s]+)=([^=\s,]+)/)]
|
236
|
+
xfsparms = { 'data' => data, 'naming' => naming, 'log' => log, 'realtime' => realtime }
|
237
|
+
|
238
|
+
each_entry (cs) do |entry|
|
239
|
+
parameter = entry.param.split('.',2)
|
240
|
+
entry.actual = xfsparms[parameter[0]][parameter[1]] rescue "<invalid name>"
|
241
|
+
cs.assert_equal entry.value, entry.actual, "XFS #{mount}: #{entry.param}"
|
242
|
+
cs.generate :entry, entry
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
else
|
247
|
+
cs.error "#{self.lineno}: Unknown type #{self.type}"
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
data/lib/tweek/version.rb
CHANGED
data/lib/tweek.rb
CHANGED
data/tweek.gemspec
CHANGED
@@ -12,8 +12,6 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.summary = %q{Read and compare Linux parameters for performance tweaking}
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
16
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
17
15
|
if spec.respond_to?(:metadata)
|
18
16
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
19
17
|
else
|
@@ -24,8 +22,8 @@ Gem::Specification.new do |spec|
|
|
24
22
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
25
23
|
f.match(%r{^(test|spec|features)/})
|
26
24
|
end
|
27
|
-
spec.bindir = "
|
28
|
-
spec.executables =
|
25
|
+
spec.bindir = "bin"
|
26
|
+
spec.executables = ['tweek']
|
29
27
|
spec.require_paths = ["lib"]
|
30
28
|
|
31
29
|
spec.add_development_dependency "bundler", "~> 1.15"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tweek
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Townsend
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-09-
|
11
|
+
date: 2017-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -55,7 +55,8 @@ dependencies:
|
|
55
55
|
description:
|
56
56
|
email:
|
57
57
|
- nick.townsend@mac.com
|
58
|
-
executables:
|
58
|
+
executables:
|
59
|
+
- tweek
|
59
60
|
extensions: []
|
60
61
|
extra_rdoc_files: []
|
61
62
|
files:
|
@@ -68,7 +69,10 @@ files:
|
|
68
69
|
- Rakefile
|
69
70
|
- bin/console
|
70
71
|
- bin/setup
|
72
|
+
- bin/tweek
|
71
73
|
- lib/tweek.rb
|
74
|
+
- lib/tweek/app.rb
|
75
|
+
- lib/tweek/section.rb
|
72
76
|
- lib/tweek/version.rb
|
73
77
|
- tweek.gemspec
|
74
78
|
homepage:
|