tweek 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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