tweek 0.0.1 → 0.9.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.
- 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:
|