tweek 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b847609877ba323a4f9feb924b503cd80f9f553
4
- data.tar.gz: 28ca6b95b8f2481c00b85aadf4ef2b4aa690f78d
3
+ metadata.gz: b8c6ef673ab0665cb66d13f99dc0023475b897e7
4
+ data.tar.gz: 00de0099ebd776a88efd3179dc7ead5d5306d386
5
5
  SHA512:
6
- metadata.gz: dd5be7941a09e0351df76b35e99efe7e5a5d905691f656f7fa577b3e15c303fab0296b716cddbf79faf12f3f74185e063b3a367e7c94d7a33099053c69aed342
7
- data.tar.gz: 2ef05e618352ec963c405a9f05cc8b279e29f97c3fd2d847cdd1750352f790f559e44acc7475c638d2e24be98c01fe8a45cbfb616f7e5b352e8459193f79d13f
6
+ metadata.gz: 276719adb0d4db1ed7c326a06b477676ae9a078b561ebe5a45df8b76cb4247a31f252cb0a40234008c76f83b6ba008f9d1b91644824729f64ddbd6dddbeac6b5
7
+ data.tar.gz: 73b5560399d4374b0b13b21c39eeeffc94fc9023851cbb7e6dc344214a2ecf8f2c6c53defd6dadc6f4c07528ea1f25c14a0e54f7ca7580475b9857de2942bb1b
data/README.md CHANGED
@@ -55,6 +55,8 @@ Tweek can be used in the following modes:
55
55
  * To create a sample settings file that you can edit manually
56
56
  * To generate a settings file based on the current system configuration
57
57
  * To check the current system configuration against a file of settings.
58
+ * To set the current system configuration to a file of settings.<br>
59
+ __NOTE__ this is currently restricted to `sysctl` parameters only. TBD.
58
60
 
59
61
  In the first case Tweek uses a built-in set of defaults. To check using these simply run:
60
62
 
@@ -69,6 +71,55 @@ All the other uses involve the settings file. Documentation is contained within
69
71
  sample settings and you should read them for a full understanding of what is possible.
70
72
  Use `tweek -s` to show them.
71
73
 
74
+ ### Output
75
+
76
+ The output of Tweek is controlled by the `output` option, which takes the following
77
+ values (default `1`):
78
+
79
+ 0. No output, just a return code.
80
+
81
+ 1. Summary output only is written to STDERR. It consists of a string of dots (when a
82
+ parameter is OK) or the letter 'F' (when a parameter doesn't match) followed by a
83
+ summary line.
84
+
85
+ 2. Only the settings that didn't match are written to STDOUT in a form that can be reused
86
+ as input. The summary is written to STDERR
87
+
88
+ 3. All the settings are written to STDOUT in a form that can be reused as input.
89
+
90
+ The latter formats differ slightly depending on whether the utility is operating in query
91
+ mode, set mode or reset mode. See below.
92
+
93
+ Note that the output is colorized by default. Disable this with `--no-color`.
94
+
95
+ * red: a value that doesn't match or conditions that are not met
96
+ * yellow: expected values
97
+ * green: conditions that are met
98
+ * bold: emphasis
99
+
100
+ ### Operating Mode
101
+
102
+ The actions of Tweek are controlled by the `mode` option, which takes the following values
103
+ (default `query`):
104
+
105
+ <dl>
106
+
107
+ <dt>query</dt>
108
+
109
+ <dd>the settings are checked against the actual values and mismatches are highlighted and
110
+ noted in a comment with the syntax '[expected xxx]'</dd>
111
+
112
+ <dt>set</dt>
113
+
114
+ <dd>the settings are set to the given value, mismatches are highlighted and noted in a
115
+ trailing comment with the syntax '[was xxx]'</dd>
116
+
117
+ <dt>reset</dt>
118
+
119
+ <dd>action as above but the settings are taken from the '[was xxx]' comments</dd>
120
+
121
+ </dl>
122
+
72
123
  ## Settings File Format
73
124
 
74
125
  The settings file is a plaintext file encoded in UTF-8 (if you are running Ruby 2.0 or
data/bin/tweek CHANGED
@@ -6,19 +6,23 @@
6
6
  #
7
7
  require 'rubygems'
8
8
 
9
+ require 'fileutils'
9
10
  require 'optparse'
11
+ require 'open-uri'
10
12
  require 'tweek'
11
13
  require 'tweek/file'
12
14
  require 'tweek/version'
15
+ require 'colorize'
13
16
 
14
17
  trap('INT') do
15
18
  print "\n"
16
19
  exit(1)
17
20
  end
18
21
 
19
- gflag = false
20
- sflag = false
21
- qflag = false
22
+ modes = [:query, :set, :reset]
23
+ mflag = modes[0]
24
+ oflag = 1
25
+ cflag = true
22
26
  distro = nil
23
27
  distro_ver = nil
24
28
  kernel_ver = nil
@@ -37,11 +41,20 @@ OptionParser.new do |o|
37
41
  o.on('--kernel-ver=X.Y.Z', "Set Kernel version (default uses uname)" ) do |k|
38
42
  kernel_ver = k
39
43
  end
40
- o.on( '-g', '--generate', "Generate a copy of the input file(s)", "containing current values on STDOUT" ) do
41
- gflag = true
44
+ o.on( '-c', '--[no-]color', "Colorize output (default #{cflag})" ) do |c|
45
+ cflag = c
42
46
  end
43
- o.on( '-q', '--quiet', "No output (default false)" ) do
44
- qflag = true
47
+ o.on( '-m', '--mode MODE', modes, "Select mode from: #{modes.join(',')} (default #{mflag})" ) do |m|
48
+ mflag = m
49
+ end
50
+ o.on( '-o', '--output LEVEL', Integer, "Output level (default 1)",
51
+ "0: no output", "1: progress and stats",
52
+ "2: mismatches only", "3: all" ) do |o|
53
+ oflag = o.to_i
54
+ unless (0..3).include? oflag
55
+ puts o
56
+ exit 2
57
+ end
45
58
  end
46
59
  o.on( '-s', '--show', "Write the default parameters to STDOUT",
47
60
  "Use as a fully documented starting point"
@@ -49,10 +62,7 @@ OptionParser.new do |o|
49
62
  File.open(File.expand_path('../../settings/sample.twk',__FILE__)).each_line do |l|
50
63
  STDOUT.puts l # no copy_stream in 1.8.7
51
64
  end
52
- exit
53
- end
54
- o.on( '--set', "Set the tweak file(s)" ) do
55
- sflag = true
65
+ exit 99
56
66
  end
57
67
  o.on('-v', "--version", "Show version") do
58
68
  puts Tweek::VERSION
@@ -64,21 +74,26 @@ OptionParser.new do |o|
64
74
  end
65
75
  o.separator <<MSG
66
76
 
67
- Returns:
77
+ Exit code:
68
78
  0: If no mismatches, >0 number of mismatches
69
79
  MSG
70
80
  o.parse! rescue (puts "Error: #{$!}"; puts o; exit)
71
81
  end
82
+
83
+ String.disable_colorization = !cflag
84
+
72
85
  if distro.nil? or distro_ver.nil?
73
86
  if RUBY_PLATFORM !~ /\blinux\b/
74
- STDERR.puts "Tweek only runs on Linux!"
87
+ STDERR.puts "Tweek only runs on Linux!".colorize(:red)
75
88
  exit 2
76
89
  end
77
90
  begin
78
91
  distro ||= `lsb_release -i`.partition(':').last.strip
92
+ raise unless $? == 0
79
93
  distro_ver ||= `lsb_release -r`.partition(':').last.strip
94
+ raise unless $? == 0
80
95
  rescue
81
- puts "'lsb_release' not found! Install or specify --distro and --distro-ver on command line"
96
+ STDERR.puts "'lsb_release' not found! Install or specify --distro and --distro-ver on command line"
82
97
  exit 2
83
98
  end
84
99
  end
@@ -86,19 +101,24 @@ if kernel_ver.nil?
86
101
  begin
87
102
  kernel_ver ||= `uname -r`.partition('-').first
88
103
  rescue
89
- puts "'uname' not found! Install or specify --kernel-ver on command line"
104
+ STDERR.puts "'uname' not found! Install or specify --kernel-ver on command line"
90
105
  exit 2
91
106
  end
92
107
  end
93
- cs = Tweek::File.new(distro, distro_ver, kernel_ver, gflag, qflag, sflag)
108
+ cs = Tweek::File.new(distro, distro_ver, kernel_ver, mflag, oflag)
94
109
  if ARGV.empty?
95
- if sflag
96
- puts "You can't update the system with the sample settings, specify one or more Tweek files!"
110
+ if mflag != :query
111
+ STDERR.puts "You can't update the system with the sample settings, specify one or more Tweek files!"
97
112
  exit 2
98
113
  end
99
114
  fh = File.open(File.expand_path('../../settings/sample.twk',__FILE__))
100
115
  else
101
- fh = ARGF
116
+ dir = FileUtils.pwd
117
+ ARGV.each do |file|
118
+ FileUtils.chdir dir
119
+ open(file) do |fh|
120
+ cs.read_sections(fh)
121
+ end
122
+ end
102
123
  end
103
- cs.read_sections(fh)
104
124
  exit cs.results
data/lib/tweek/file.rb CHANGED
@@ -2,77 +2,77 @@ require 'rubygems'
2
2
  require 'tweek'
3
3
  require 'tweek/section'
4
4
  require 'tweek/entry'
5
+ require 'colorize'
5
6
 
6
7
  class Tweek::File
7
8
 
8
- attr_reader :distro, :distro_version, :kernel_version, :gflag, :qflag, :sflag
9
+ attr_reader :distro, :distro_version, :kernel_version, :mflag, :oflag, :mismatches, :skips
9
10
 
10
- def initialize( distro, distro_version, kernel_version, gflag = false, qflag = true, sflag = false)
11
- @distro = distro
12
- @distro_version = Gem::Version.new(distro_version)
13
- @kernel_version = Gem::Version.new(kernel_version)
11
+ def initialize( distro, distro_version, kernel_version, mflag = :query, oflag = 0)
12
+ @distrov = distro
13
+ @dv = Gem::Version.new(distro_version)
14
+ @kv = Gem::Version.new(kernel_version)
15
+ @distro_version = distro_version.bold
16
+ @kernel_version = kernel_version.bold
17
+ @distro = distro.bold
14
18
  @nparams = 0
15
- @mismatches = []
19
+ @mismatches = 0
16
20
  @errors = []
17
- @skips = []
21
+ @skips = 0
18
22
  @warns = []
19
23
  @generated = []
20
- if gflag
21
- @generated << "# Generated at #{Time.now.strftime("%c %Z")}"
22
- @generated << "# Distro: #{@distro} Version: #{@distro_version} Kernel: #{@kernel_version}"
23
- end
24
- @gflag = gflag
25
- @qflag = qflag
26
- @sflag = sflag
24
+ @mflag = mflag
25
+ @oflag = oflag
26
+
27
27
  end
28
28
 
29
- # Generate an output file recording what was found (check) or done (set)
29
+ # Called for every entry, generates output as required
30
30
  #
31
- def generate type, entry
32
- return unless @gflag
33
- case type
34
- when :line
35
- @generated.push "#{entry.line}#{entry.comment}"
31
+ def generate entry
32
+
33
+ line = entry.generate
34
+
35
+ case entry.type
36
+
37
+ when :literal
38
+ @generated.push line if @oflag > 1
39
+
36
40
  when :section
37
- return if entry.type.nil?
38
- @generated.push "#{entry.type} #{entry.list} #{entry.comment}"
39
- when :entry
40
- cond = entry.cond.nil? ? "": " if #{entry.cond}"
41
- if entry.actual.nil?
42
- line = "#{entry.param} = #{entry.value}#{cond}"
43
- note = "[condition not met]"
41
+ @generated.push line unless entry.section_type.nil? or @oflag < 2
42
+
43
+ when :parameter
44
+ @nparams += 1
45
+ if entry.condfail
46
+ @generated.push line if @oflag > 1
47
+ STDERR.print "s".colorize(:yellow) if @oflag == 1
44
48
  else
45
- entry.swap!
46
- line = "#{entry.param} = #{entry.actual}#{cond}"
47
- if entry.value === entry.actual
48
- note = nil
49
+ @generated.push line if @oflag == 3 or ((@oflag == 2) and entry.mismatch)
50
+ if entry.mismatch
51
+ @mismatches += 1
52
+ STDERR.print "F".colorize(:red) if @oflag == 1
49
53
  else
50
- note = "[#{entry.set ? 'was' : 'expected'} #{entry.value}]"
54
+ STDERR.print ".".colorize(:green) if @oflag == 1
51
55
  end
52
56
  end
53
- if entry.comment.nil?
54
- @generated.push (note ? line + " # " + note : line)
55
- else
56
- @generated.push (note ? line + " " + entry.comment + " " + note : line + " " + entry.comment)
57
- end
58
57
  end
59
58
  end
60
59
 
61
- def error msg
62
- @errors.push msg unless @errors.first == msg
60
+ def error msg, lineno = 0
61
+ line = (lineno == 0) ? msg : sprintf("%3d: #{msg}", lineno)
62
+ @errors.push line unless @errors.first == line
63
63
  end
64
64
 
65
- def messages
66
- strings = @errors + @mismatches
67
- return strings.size == 0 ? "": strings.join("\n")
65
+ def warn msg, lineno = 0
66
+ line = (lineno == 0) ? msg : sprintf("%3d: #{msg}", lineno)
67
+ @warns.push line unless @warns.first == line
68
68
  end
69
69
 
70
- def warn msg
71
- @warns.push msg unless @warns.first == msg
70
+ def messages
71
+ return @errors.size > 0 ? @errors.join("\n") : ""
72
72
  end
73
73
 
74
- def skipcond entry
75
- @skips.push entry
74
+ def generated
75
+ return @generated.size > 0 ? @generated.join("\n") : ""
76
76
  end
77
77
 
78
78
  def condfail cond
@@ -81,85 +81,79 @@ class Tweek::File
81
81
  failures = 0
82
82
  reqs = cond.split(/\s*,\s*/)
83
83
  reqs.each do |req|
84
- ok = case var = req.slice!(0)
84
+ raise ArgumentError.new("entry has invalid condition variable: #{req}") unless /^[kvd]/ =~ req
85
+ var = $&
86
+ ver = $'
87
+ ok = case var
85
88
  when 'k'
86
- Gem::Requirement.new(req).satisfied_by?(kernel_version)
89
+ Gem::Requirement.new(ver).satisfied_by?(@kv)
87
90
  when 'v'
88
- Gem::Requirement.new(req).satisfied_by?(distro_version)
91
+ Gem::Requirement.new(ver).satisfied_by?(@dv)
89
92
  when 'd'
90
- op = req.slice!(/^[<>~!=]+/)
93
+ op = ver.slice!(/^[<>~!=]+/)
91
94
  begin
92
- eval "distro #{op} #{req}"
95
+ eval "@distrov #{op} #{ver}"
93
96
  rescue Exception => e
94
97
  raise ArgumentError.new("entry has condition error: #{e.message}")
95
98
  end
96
- else
97
- raise ArgumentError.new("entry has invalid condition variable: #{var}")
98
99
  end
99
100
  failures += 1 unless ok
100
101
  end
101
- return failures > 0
102
- end
103
-
104
- def assert_equal expected, actual, msg = ''
105
- @nparams += 1
106
- if expected === actual
107
- STDERR.print "." if not @qflag and STDERR.isatty
108
- return 0
109
- else
110
- STDERR.print "F" if not @qflag and STDERR.isatty
111
- @mismatches.push "#{msg}: Expected #{expected}\n#{" "*(msg.size+4)}Actual #{actual}"
112
- end
113
- return 1
102
+ skipped = (failures > 0)
103
+ @skips += 1 if skipped
104
+ return skipped
114
105
  end
115
106
 
116
107
  # Read the entire file and split into sections
117
108
  #
118
109
  def read_sections handle
119
- section = Tweek::Section.new( 0, nil, '', '' )
110
+ section = Tweek::Section.initial self
120
111
  while line = handle.gets
121
112
  line.chomp!
122
113
  if (line =~ /^=begin/)..(line =~ /^=end/)
123
- section.push Tweek::Entry.new( :line => line, :lineno => handle.lineno )
114
+ entry = Tweek::Entry.new(section, :literal, :line => line, :lineno => handle.lineno )
115
+ section.push entry
124
116
  next
125
117
  end
126
118
 
127
- comment = line.slice!(/#.*$/)
128
- if line.empty?
129
- section.push Tweek::Entry.new( :line => line, :lineno => handle.lineno, :comment => comment )
130
- next
131
- end
119
+ begin
120
+ entry = Tweek::Entry.parse(section, line)
121
+ entry.lineno = handle.lineno
122
+ case entry.type
132
123
 
133
- if /^\s*([A-Z0-9]+)\s*(.*?)\s*$/ =~ line # We've hit a new section
134
- section.process(self)
135
- section = Tweek::Section.new( handle.lineno, $1, $2, comment )
136
- next
137
- end
124
+ when :literal
125
+ section.push entry
126
+
127
+ when :section
128
+ section.process
129
+ section = Tweek::Section.new(self, entry)
130
+
131
+ when :parameter
132
+ section.push entry
138
133
 
139
- if /^\s*(.+?)\s*=\s*(.*?)\s*(\bif\b(.*))?$/ =~ line
140
- if $4 and $4.strip.empty?
141
- error "#{handle.lineno}: Missing condition after 'if'"
142
134
  else
143
- section.push Tweek::Entry.new( :lineno => handle.lineno, :param => $1, :value => $2,
144
- :cond => $4, :comment => comment )
135
+ error "#{handle.lineno}: Unrecognized line: #{line}"
145
136
  end
146
- next
137
+ rescue
138
+ error "#{handle.lineno}: #{$!.message}"
147
139
  end
148
- error "#{handle.lineno}: Unrecognized line: #{line}"
149
140
  end
150
- section.process(self)
151
-
141
+ section.process
152
142
  end
153
143
 
154
144
  def results
155
- unless @qflag
156
- STDERR.puts "\nDistro: #{@distro} Version: #{@distro_version} Kernel: #{@kernel_version}"
157
- STDERR.puts "\n#{@nparams} parameters checked, #{@skips.size} conditions not met, #{@mismatches.size} mismatches, #{@warns.size} warnings, #{@errors.size} errors"
158
- STDERR.puts "\n#{@errors.join("\n")}" unless @errors.empty?
159
- STDERR.puts "\n#{@warns.join("\n")}" unless @warns.empty?
160
- STDERR.puts "\n#{@mismatches.join("\n")}" unless @mismatches.empty?
145
+ unless @oflag == 0
146
+ STDERR.puts "" if @oflag == 1
147
+ STDERR.puts "Mode: #{@mflag.to_s.bold} Distro: #{@distro} Version: #{@distro_version} Kernel: #{@kernel_version}"
148
+ STDERR.puts "#{@nparams} parameters checked, #{@skips} conditions not met, #{@mismatches} mismatches, #{@warns.size} warnings, #{@errors.size} errors"
149
+ STDERR.puts "#{@errors.join("\n")}" unless @errors.empty?
150
+ STDERR.puts "#{@warns.join("\n")}" unless @warns.empty?
151
+ if oflag > 1
152
+ @generated.unshift "# Generated at #{Time.now.strftime("%c %Z")}"
153
+ @generated.unshift "# Mode: #{@mflag} Distro: #{@distro} Version: #{@distro_version} Kernel: #{@kernel_version}"
154
+ end
161
155
  @generated.each { |l| STDOUT.puts l}
162
156
  end
163
- return @mismatches.size
157
+ return @mismatches
164
158
  end
165
159
  end
data/lib/tweek/section.rb CHANGED
@@ -1,224 +1,230 @@
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
1
+ # A section is an array of lines, each line is an Entry
2
+ # The section line itself is stored in a separate Entry object
7
3
  #
8
4
  class Tweek::Section < Array
9
5
 
10
- attr_reader :lineno, :type, :list, :comment
6
+ attr_reader :section_entry, :twf
11
7
 
12
- def initialize lineno, type, list, comment
13
- @lineno = lineno
14
- @type = type
15
- @list = list
16
- @comment = comment
8
+ def initialize twf, entry
9
+ @twf = twf
10
+ @section_entry = entry
17
11
  end
18
12
 
19
- # Iterate through each entry in the section.
20
- # If it's a comment then pass to the generate method
21
- # If it has a condition which is not met then pass to generate
22
- # If it passes yield the entry
13
+ # Return an initial section used to hold anything (such as comments) prior to
14
+ # the first real section
15
+ def self.initial twf
16
+ Tweek::Section.new(twf, Tweek::Entry.new(self, :section, :section_type => :initial) )
17
+ end
18
+
19
+ # Helper method to easily iterate through each actionable entry in the section.
23
20
  #
24
- def each_entry twf, &block
21
+ def each_entry &block
25
22
  self.each do |entry|
26
- if entry.line
27
- twf.generate :line, entry
28
- next
29
- end
30
- begin
31
- if twf.condfail entry.cond
32
- twf.generate :entry, entry
33
- twf.skipcond entry
34
- next
23
+ case entry.type
24
+ when :literal
25
+ @twf.generate entry
26
+ when :parameter
27
+ begin
28
+ if entry.condfail
29
+ @twf.generate entry
30
+ else
31
+ yield entry if block_given?
32
+ end
33
+ rescue Exception => e
34
+ @twf.error e.message, entry.lineno
35
+ STDERR.puts "#{e.message}\n#{e.backtrace.join("\n")}" if ENV['DEBUG_TWEEK']
35
36
  end
36
- yield entry
37
- rescue Exception => e
38
- twf.error "#{entry.lineno}: #{e.message}"
37
+ else
38
+ raise "Unknown entry type: #{entry.type}"
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
- def process twf
44
-
45
- twf.generate :section, Tweek::Entry.new( :lineno => lineno, :type => type, :list => list, :comment => comment )
43
+ def process
46
44
 
47
45
  return if self.empty?
48
46
 
49
- case self.type
50
- when nil
51
- each_entry (twf) do |entry|
52
- end
53
-
54
- when 'BLKDEV' # BLOCK DEVICE PARAMETERS
47
+ case section_entry.section_type
55
48
 
56
- twf.warn "Set is not currently implemented for #{type} section" if twf.sflag
49
+ when :initial
50
+ self.each do |entry|
51
+ case entry.type
52
+ when :literal
53
+ @twf.generate entry
54
+ else
55
+ @twf.warn "Parameter line ignored outside section", entry.lineno
56
+ end
57
+ end
57
58
 
59
+ when :BLKDEV # BLOCK DEVICE PARAMETERS
60
+ @twf.warn "Set/Reset is not currently implemented for #{section_entry.section_type} section" if [:set, :reset].include? @twf.mflag
58
61
  Dir.chdir('/dev')
59
- devices = Dir.glob(self.list.split(' ').map{|dev| dev.gsub(/^\/dev\//,'')})
60
-
61
- twf.warn "Block device check skipped: no devices found" if devices.empty?
62
-
62
+ devices = Dir.glob(section_entry.list.split(' ').map{|dev| dev.gsub(/^\/dev\//,'')})
63
+ @twf.warn("Block device check skipped: no devices found", section_entry.lineno) if devices.empty?
63
64
  devices.each do |blk|
64
- each_entry (twf) do |entry|
65
+ section_entry.list_element = blk
66
+ @twf.generate section_entry
67
+ each_entry do |entry|
65
68
  entry.actual = Tweek.blk_param(blk, entry.param)
66
69
  if entry.param == 'scheduler'
67
70
  entry.actual = entry.actual.slice(/\[(.*?)\]/,1) unless entry.actual == 'none'
68
71
  end
69
- twf.assert_equal entry.value, entry.actual, "/dev/#{blk} #{entry.param}"
70
- twf.generate :entry, entry
72
+ @twf.generate entry
71
73
  end
72
74
  end
73
75
 
74
- when 'KERNEL' # KERNEL PARAMETERS
75
- twf.warn "Set is not currently implemented for #{type} section" if twf.sflag
76
- each_entry (twf) do |entry|
76
+ when :KERNEL # KERNEL PARAMETERS
77
+ @twf.warn "Set/Reset is not currently implemented for #{section_entry.section_type} section" if [:set, :reset].include? @twf.mflag
78
+ @twf.generate section_entry
79
+ each_entry do |entry|
77
80
  actual = /\b(#{entry.param})(=\S+)?\b/.match(Tweek.bootline)
78
81
  case
79
82
  when (entry.value == 'true' and actual)
80
- observed = actual.to_s
81
- expected = entry.param
83
+ entry.actual = actual[2].nil? ? 'true' : actual[2][1..-1]
82
84
  when (entry.value == 'true' and actual.nil?)
83
- expected = 'to be found'
84
- observed = 'was not found'
85
+ entry.actual = 'false'
85
86
  when (entry.value == 'false' and actual)
86
- expected = 'to not be found'
87
- observed = 'was found'
87
+ entry.actual = 'true'
88
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]
89
+ entry.actual = 'false'
90
+ else
91
+ entry.actual = actual.nil? ? 'false' : actual[2][1..-1]
94
92
  end
95
- twf.assert_equal expected, observed, "Kernel parameter #{entry.param}"
96
- entry.actual = !actual.nil?
97
- twf.generate :entry, entry
93
+ @twf.generate entry
98
94
  end
99
95
 
100
- when 'CLOCKSOURCE'
101
- twf.warn "Set is not currently implemented for #{type} section" if twf.sflag
102
- each_entry (twf) do |entry|
96
+ when :CLOCKSOURCE
97
+ @twf.warn "Set/Reset is not currently implemented for #{section_entry.section_type} section" if [:set, :reset].include? @twf.mflag
98
+ @twf.generate section_entry
99
+ each_entry do |entry|
103
100
  entry.actual = Tweek.clocksource(entry.param)
104
- twf.assert_equal entry.value, entry.actual, entry.param
105
- twf.generate :entry, entry
101
+ @twf.generate entry
106
102
  end
107
103
 
108
- when 'SYSCTL' # SYSCTL SETTINGS
109
- each_entry (twf) do |entry|
104
+ when :SYSCTL # SYSCTL SETTINGS
105
+ @twf.generate section_entry
106
+ each_entry do |entry|
110
107
  file = entry.param.gsub(/\./,'/')
111
108
  entry.actual = Tweek.sysctl(file)
112
- twf.assert_equal entry.value, entry.actual, entry.param
113
- if twf.sflag
109
+ case @twf.mflag
110
+ when :set
114
111
  entry.value = Tweek.set_sysctl(file, entry.value)
115
- entry.set = true
112
+ entry.mode = :set
113
+ when :reset
114
+ if entry.was
115
+ if entry.actual == entry.value
116
+ entry.value = Tweek.set_sysctl(file, entry.was)
117
+ entry.mode = :reset
118
+ else
119
+ @twf.warn "Current value of #{entry.param.bold} does not match, cannot reset", entry.lineno
120
+ end
121
+ else
122
+ @twf.warn "Previous value of #{entry.param.bold} not found, cannot reset", entry.lineno
123
+ end
116
124
  end
117
- twf.generate :entry, entry
125
+ @twf.generate entry
118
126
  end
119
127
 
120
- when 'NET' # NETWORK DEVICES
121
- twf.warn "Set is not currently implemented for #{type} section" if twf.sflag
122
- devices = self.list.split(' ').map{|dev| dev.gsub(/^\/dev\//,'')}
123
- twf.warn "Network device check skipped: no devices found" if devices.empty?
128
+ when :NET # NETWORK DEVICES
129
+ @twf.warn "Set/Reset is not currently implemented for #{section_entry.section_type} section" if [:set, :reset].include? @twf.mflag
130
+ devices = section_entry.list.split(' ').map{|dev| dev.gsub(/^\/dev\//,'')}
131
+ @twf.warn("Network device check skipped: no devices found", section_entry.lineno) if devices.empty?
124
132
 
125
133
  devices.each do |netdev|
126
- each_entry (twf) do |entry|
134
+ unless Tweek.net_dev_exist? netdev
135
+ @twf.warn("NET device #{netdev} not found", section_entry.lineno)
136
+ next
137
+ end
138
+ section_entry.list_element = netdev
139
+ @twf.generate section_entry
140
+ each_entry do |entry|
127
141
 
128
142
  case entry.param
129
143
  when 'driver'
130
144
  entry.actual = Tweek.net_driver(netdev)
145
+ @twf.generate entry
131
146
 
132
147
  when 'irqs'
133
-
134
148
  balpid = Tweek.balance_pid
135
-
136
149
  if entry.value == 'balance'
137
150
  entry.actual = balpid.zero? ? 'IRQ balance daemon not running' : 'balance'
138
151
  else
139
152
  if balpid.zero?
140
- handle_irqs(netdev, twf, entry)
141
- next
153
+ handle_irqs(netdev, entry)
142
154
  else
143
155
  entry.actual = 'IRQ balance daemon running'
156
+ @twf.generate entry
144
157
  end
145
158
  end
146
159
 
147
160
  else # treat the parameter as a name
148
- begin
149
- entry.actual = Tweek.net_param(netdev, entry.param)
150
- rescue
151
- twf.error "#{entry.lineno}: Network parameter #{entry.param} not handled: #{$!.message}"
152
- next
153
- end
161
+ entry.actual = Tweek.net_param(netdev, entry.param)
162
+ @twf.generate entry
154
163
  end
155
164
 
156
- twf.assert_equal entry.value, entry.actual, entry.param
157
- twf.generate :entry, entry
158
165
  end
159
166
  end
160
167
 
161
- when 'EXT4' # EXT4 filesystems info via `dumpe2fs -h /dev/#{device}`
162
- twf.warn "Set is not currently implemented for #{type} section" if twf.sflag
168
+ when :EXT4 # EXT4 filesystems info via `dumpe2fs -h /dev/#{device}`
169
+ @twf.warn "Set/Reset is not currently implemented for #{section_entry.section_type} section" if [:set, :reset].include? @twf.mflag
163
170
  mounted = Tweek.mounted
164
- mounts = Dir.glob(self.list.split(' '))
171
+ mounts = Dir.glob(section_entry.list.split(' '))
172
+ @twf.warn("EXT4 check skipped: no mountpoints found", section_entry.lineno) if mounts.empty?
165
173
  mounts.each do |mount|
166
174
  unless this = mounted.select{|m| (mount == m[0] or mount == m[1]) and m[2] == 'ext4'}.first
167
- twf.warn "EXT4 path #{mount} is not mounted or is not mounted as ext4"
175
+ @twf.warn "EXT4 path #{mount} is not mounted or is not mounted as ext4", section_entry.lineno
168
176
  next
169
177
  end
178
+ section_entry.list_element = mount
179
+ @twf.generate section_entry
170
180
  device = this[0].gsub('/dev/','')
171
181
  optstring = File.open("/proc/fs/ext4/#{device}/options",&:read)
172
182
  options = Hash[optstring.scan(/([^=\s]+)=([^=\s,]+)/)] # options a=b
173
183
  optstring.split(/\n/).reject{|o| o.index('=')}.each{|o| options[o] = 'true'}
174
- each_entry (twf) do |entry|
184
+ each_entry do |entry|
175
185
  entry.actual = options[entry.param] || "<not found>"
176
- twf.assert_equal entry.value, entry.actual, "EXT4 #{mount}: #{entry.param}"
177
- twf.generate :entry, entry
186
+ @twf.generate entry
178
187
  end
179
188
  end
180
189
 
181
- when 'XFS' # XFS filesystems info via `xfs_info`
182
- twf.warn "Set is not currently implemented for #{type} section" if twf.sflag
190
+ when :XFS # XFS filesystems info via `xfs_info`
191
+ @twf.warn "Set/Reset is not currently implemented for #{section_entry.section_type} section" if [:set, :reset].include? @twf.mflag
183
192
  # Dynamically via: /proc/fs/xfs/... ?
184
- mounts = self.list.split(' ')
185
- if mounts.empty?
186
- twf.warn "#{self.lineno}: XFS device check skipped: no mountpoints found"
187
- end
193
+ mounts = section_entry.list.split(' ')
194
+ @twf.warn("XFS device check skipped: no mountpoints found", section_entry.lineno) if mounts.empty?
188
195
  mounts.each do |mount|
189
196
  xfsinfo = Tweek.xfsinfo(mount)
190
197
  unless xfsinfo
191
- twf.warn "No XFS filesystem at #{mount}"
192
- each_entry (twf) { |entry| twf.generate :entry, entry }
198
+ @twf.warn "No XFS filesystem at #{mount}", section_entry.lineno
193
199
  next
194
200
  end
201
+ section_entry.list_element = mount
202
+ @twf.generate section_entry
195
203
  data = Hash[xfsinfo.slice!(/^meta-data.*(?=^naming)/m).scan(/([^=\s]+)=([^=\s,]+)/)]
196
204
  naming = Hash[xfsinfo.slice!(/^naming.*(?=^log)/m).scan(/([^=\s]+)=([^=\s,]+)/)]
197
205
  log = Hash[xfsinfo.slice!(/^log.*(?=^realtime)/m).scan(/([^=\s]+)=([^=\s,]+)/)]
198
206
  realtime = Hash[xfsinfo.slice!(/^realtime.*$/m).scan(/([^=\s]+)=([^=\s,]+)/)]
199
207
  xfsparms = { 'data' => data, 'naming' => naming, 'log' => log, 'realtime' => realtime }
200
208
 
201
- each_entry (twf) do |entry|
209
+ each_entry do |entry|
202
210
  parameter = entry.param.split('.',2)
203
211
  entry.actual = xfsparms[parameter[0]][parameter[1]] rescue "<invalid name>"
204
- twf.assert_equal entry.value, entry.actual, "XFS #{mount}: #{entry.param}"
205
- twf.generate :entry, entry
212
+ @twf.generate entry
206
213
  end
207
214
  end
208
215
 
209
216
  else
210
- twf.error "#{self.lineno}: Unknown type #{self.type}"
217
+ @twf.error "Unknown type #{section_entry.section_type}", section_entry.lineno
211
218
  end
212
219
 
213
220
  end
214
221
 
215
- def handle_irqs netdev, twf, entry
222
+ def handle_irqs netdev, entry
216
223
  irqs = Tweek.irqs(netdev)
217
224
 
218
225
  if irqs.empty?
219
- twf.warn "#{netdev} IRQ check skipped: none found"
226
+ @twf.warn "#{netdev} IRQ check skipped: none found", entry.lineno
220
227
  entry.actual = 'null'
221
- twf.generate :entry, entry
222
228
  return
223
229
  end
224
230
 
@@ -240,7 +246,7 @@ class Tweek::Section < Array
240
246
  expected_rps << "0000"
241
247
  expected_rss << sprintf("%04x", 1<<ix)
242
248
  else
243
- twf.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle #{ncores} cores"
249
+ @twf.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle #{ncores} cores", entry.lineno
244
250
  return
245
251
  end
246
252
 
@@ -256,7 +262,7 @@ class Tweek::Section < Array
256
262
  expected_rps << ("00000000," * 3) + "fffefe00"
257
263
  expected_rss << ("00000000," * 3) + sprintf("%08x", 1<<(ix+1))
258
264
  else
259
- twf.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle IRQ#{ix}"
265
+ @twf.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle IRQ#{ix}", entry.lineno
260
266
  return
261
267
  end
262
268
 
@@ -269,29 +275,37 @@ class Tweek::Section < Array
269
275
  expected_rps << ("00000000," * 2) + "ffff0000," + "ffe00000"
270
276
  expected_rss << ("00000000," * 3) + sprintf("%08x", 1<<(ix + 9))
271
277
  else
272
- twf.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle IRQ#{ix}"
278
+ @twf.warn "#{netdev} IRQ strategy '#{entry.value}' can't handle IRQ#{ix}", entry.lineno
273
279
  return
274
280
  end
275
281
 
276
282
  else
277
- twf.error "#{entry.lineno}: Can't handle #{ncores} cores in IRQ strategy '#{entry.value}'"
283
+ @twf.error "Can't handle #{ncores} cores in IRQ strategy '#{entry.value}'", entry.lineno
278
284
  return
279
285
  end
280
286
 
281
287
  else
282
- twf.error "#{entry.lineno}: Unrecognized #{netdev} IRQ strategy '#{entry.value}'"
288
+ @twf.error "Unrecognized #{netdev} IRQ strategy '#{entry.value}'", entry.lineno
283
289
  return
284
290
  end
285
291
  end
292
+ rss = [] # sadly map.with_index doesn't work in 1.8.7, no other compatible way
286
293
  irqs.each_with_index do |irq, ix|
287
- actual_rss = Tweek.net_rss(irq)
288
- mismatches += twf.assert_equal expected_rss[ix], actual_rss, "#{netdev}_irq_#{irq}_rss"
294
+ rss << Tweek::Entry.new(self, :parameter, :param => "#{netdev}_irq_#{irq}_rss",
295
+ :actual => Tweek.net_rss(irq),
296
+ :value => expected_rss[ix])
289
297
  end
298
+ rps = []
290
299
  irqs.each_with_index do |irq, ix|
291
- actual_rps = Tweek.net_rps(netdev, ix)
292
- mismatches += twf.assert_equal expected_rps[ix], actual_rps, "#{netdev}_irq_#{irq}_rps"
300
+ rps << Tweek::Entry.new(self, :parameter, :param => "#{netdev}_irq_rx-#{ix}_rps",
301
+ :actual => Tweek.net_rps(netdev, ix),
302
+ :value => expected_rps[ix])
293
303
  end
294
- entry.actual = 'null' if mismatches > 0
304
+ mismatches = rss.reduce(0){|sum, r| sum += 1 if r.mismatch}
305
+ mismatches += rps.reduce(0){|sum, r| sum += 1 if r.mismatch}
306
+ entry.actual = 'explicit' if mismatches > 0
307
+ @twf.generate entry
308
+ (rss+rps).each {|e| @twf.generate e}
295
309
  return
296
310
  end
297
311
 
data/lib/tweek/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tweek
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/tweek.rb CHANGED
@@ -3,11 +3,13 @@
3
3
  module Tweek
4
4
 
5
5
  def self.bootline
6
- @bootline ||= ::File.open("/proc/cmdline", &:read).chomp
6
+ @bootline ||= ::File.open("/proc/cmdline", &:read).strip
7
7
  end
8
8
 
9
9
  def self.balance_pid
10
- @balance_pid ||= `pgrep irqbalance`.chomp.to_i
10
+ @balance_pid ||= `pgrep irqbalance`.strip.to_i
11
+ raise "pgrep appears to be missing!" if $? == 127
12
+ return @balance_pid
11
13
  end
12
14
 
13
15
  # Determine the queue processing interrupts for the network device
@@ -20,15 +22,15 @@ module Tweek
20
22
  end
21
23
 
22
24
  def self.ncores
23
- `getconf _NPROCESSORS_ONLN`.chomp.to_i rescue 0
25
+ `getconf _NPROCESSORS_ONLN`.strip.to_i rescue 0
24
26
  end
25
27
 
26
28
  def self.clocksource param
27
- ::File.open("/sys/devices/system/clocksource/#{param}/current_clocksource",&:read).chomp rescue $!.message
29
+ ::File.open("/sys/devices/system/clocksource/#{param}/current_clocksource",&:read).strip rescue $!.message
28
30
  end
29
31
 
30
32
  def self.sysctl name
31
- ::File.open("/proc/sys/#{name}", &:read).chomp.gsub(/\s+/,' ') rescue $!.message
33
+ ::File.open("/proc/sys/#{name}", &:read).strip.gsub(/\s+/,' ') rescue $!.message
32
34
  end
33
35
 
34
36
  def self.set_sysctl name, value
@@ -40,26 +42,30 @@ module Tweek
40
42
  end
41
43
  end
42
44
 
45
+ def self.net_dev_exist? name
46
+ ::File.readable?("/sys/class/net/#{name}")
47
+ end
48
+
43
49
  def self.net_driver name
44
50
  ::File.basename(::File.readlink("/sys/class/net/#{name}/device/driver"))
45
51
  end
46
52
 
47
53
  def self.blk_param blkdev, name
48
- ::File.open("/sys/block/#{blkdev}/queue/#{name}", &:read).chomp rescue $!.message
54
+ ::File.open("/sys/block/#{blkdev}/queue/#{name}", &:read).strip rescue $!.message
49
55
  end
50
56
 
51
57
  # Receive Side Scaling - hardware
52
58
  def self.net_rss irq
53
- ::File.open("/proc/irq/#{irq}/smp_affinity",&:read).chomp rescue $!.message
59
+ ::File.open("/proc/irq/#{irq}/smp_affinity",&:read).strip rescue $!.message
54
60
  end
55
61
 
56
62
  # Receive Packet Steering - software
57
63
  def self.net_rps netdev, ix
58
- ::File.open("/sys/class/net/#{netdev}/queues/rx-#{ix}/rps_cpus", &:read).chomp rescue $!.message
64
+ ::File.open("/sys/class/net/#{netdev}/queues/rx-#{ix}/rps_cpus", &:read).strip rescue $!.message
59
65
  end
60
66
 
61
67
  def self.net_param netdev, name
62
- ::File.open("/sys/class/net/#{netdev}/#{name}", &:read).chomp.gsub(/\s+/,' ')
68
+ ::File.open("/sys/class/net/#{netdev}/#{name}", &:read).strip.gsub(/\s+/,' ') rescue $!.message
63
69
  end
64
70
 
65
71
  def self.mounted
@@ -67,7 +73,7 @@ module Tweek
67
73
  end
68
74
 
69
75
  def self.xfsinfo mount
70
- info = `xfs_info #{mount} 2>/dev/null`.chomp
76
+ info = `xfs_info #{mount} 2>/dev/null`.strip
71
77
  return $?.exitstatus > 0 ? nil : info
72
78
  end
73
79
  end
data/settings/sample.twk CHANGED
@@ -81,6 +81,7 @@ NET eth0
81
81
  # RSS allocated across 1-4 (node 0) then 17-20 (node 1)
82
82
  #
83
83
  # balance: Use the irqbalance daemon
84
+ # explicit: Describe each parameter individually (try it to get documentation!)
84
85
  # null: Ignore this device
85
86
  #
86
87
  irqs = aws
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tweek
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Townsend
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-29 00:00:00.000000000 Z
11
+ date: 2017-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement