-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
374 lines (362 loc) · 13.7 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package main
import (
"fmt"
"github.com/HouzuoGuo/saptune/app"
"github.com/HouzuoGuo/saptune/sap/note"
"github.com/HouzuoGuo/saptune/sap/solution"
"github.com/HouzuoGuo/saptune/system"
"io"
"log"
"os"
"runtime"
"sort"
"syscall"
)
const (
SapconfService = "sapconf.service"
TunedService = "tuned.service"
TunedProfileName = "saptune"
ExitTunedStopped = 1
ExitTunedWrongProfile = 2
ExitNotTuned = 3
// ExtraTuningSheets is a directory located on file system for external parties to place their tuning option files.
ExtraTuningSheets = "/etc/saptune/extra/"
)
func PrintHelpAndExit(exitStatus int) {
fmt.Println(`saptune: Comprehensive system optimisation management for SAP solutions.
Daemon control:
saptune daemon [ start | status | stop ]
Tune system according to SAP and SUSE notes:
saptune note [ list | verify ]
saptune note [ apply | simulate | verify | customise | revert ] NoteID
Tune system for all notes applicable to your SAP solution:
saptune solution [ list | verify ]
saptune solution [ apply | simulate | verify | revert ] SolutionName
`)
os.Exit(exitStatus)
}
// Print the message to stderr and exit 1.
func errorExit(template string, stuff ...interface{}) {
fmt.Fprintf(os.Stderr, template+"\n", stuff...)
os.Exit(1)
}
// Return the i-th command line parameter, or empty string if it is not specified.
func cliArg(i int) string {
if len(os.Args) >= i+1 {
return os.Args[i]
}
return ""
}
var tuneApp *app.App // application configuration and tuning states
var tuningOptions note.TuningOptions // Collection of tuning options from SAP notes and 3rd party vendors.
var solutionSelector = runtime.GOARCH
func main() {
if arg1 := cliArg(1); arg1 == "" || arg1 == "help" || arg1 == "--help" {
PrintHelpAndExit(0)
}
// All other actions require super user privilege
if os.Geteuid() != 0 {
errorExit("Please run saptune with root privilege.")
return
}
var saptune_log io.Writer
saptune_log, err := os.OpenFile("/var/log/tuned/tuned.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
panic(err.Error())
}
saptune_writer := io.MultiWriter(os.Stderr, saptune_log)
log.SetOutput(saptune_writer)
if system.IsPagecacheAvailable() {
solutionSelector = solutionSelector + "_PC"
}
archSolutions, exist := solution.AllSolutions[solutionSelector]
if !exist {
errorExit("The system architecture (%s) is not supported.", runtime.GOARCH)
return
}
// Initialise application configuration and tuning procedures
tuningOptions = note.GetTuningOptions(ExtraTuningSheets)
tuneApp = app.InitialiseApp("", "", tuningOptions, archSolutions)
switch cliArg(1) {
case "daemon":
DaemonAction(cliArg(2))
case "note":
NoteAction(cliArg(2), cliArg(3))
case "solution":
SolutionAction(cliArg(2), cliArg(3))
default:
PrintHelpAndExit(1)
}
}
func DaemonAction(actionName string) {
switch actionName {
case "start":
fmt.Println("Starting daemon (tuned.service), this may take several seconds...")
system.SystemctlDisableStop(SapconfService) // do not error exit on failure
if err := system.WriteTunedAdmProfile("saptune"); err != nil {
errorExit("%v", err)
}
if err := system.SystemctlEnableStart(TunedService); err != nil {
errorExit("%v", err)
}
// tuned then calls `sapconf daemon apply`
fmt.Println("Daemon (tuned.service) has been enabled and started.")
if len(tuneApp.TuneForSolutions) == 0 && len(tuneApp.TuneForNotes) == 0 {
fmt.Println("Your system has not yet been tuned. Please visit `saptune note` and `saptune solution` to start tuning.")
}
case "apply":
// This action name is only used by tuned script, hence it is not advertised to end user.
if err := tuneApp.TuneAll(); err != nil {
panic(err)
}
case "status":
// Check daemon
if system.SystemctlIsRunning(TunedService) {
fmt.Println("Daemon (tuned.service) is running.")
} else {
fmt.Fprintln(os.Stderr, "Daemon (tuned.service) is stopped. If you wish to start the daemon, run `saptune daemon start`.")
os.Exit(ExitTunedStopped)
}
// Check tuned profile
if system.GetTunedProfile() != TunedProfileName {
fmt.Fprintln(os.Stderr, "tuned.service profile is incorrect. If you wish to correct it, run `saptune daemon start`.")
os.Exit(ExitTunedWrongProfile)
}
// Check for any enabled note/solution
if len(tuneApp.TuneForSolutions) > 0 || len(tuneApp.TuneForNotes) > 0 {
fmt.Println("The system has been tuned for the following solutions and notes:")
for _, sol := range tuneApp.TuneForSolutions {
fmt.Println("\t" + sol)
}
for _, noteID := range tuneApp.TuneForNotes {
fmt.Println("\t" + noteID)
}
} else {
fmt.Fprintln(os.Stderr, "Your system has not yet been tuned. Please visit `saptune note` and `saptune solution` to start tuning.")
os.Exit(ExitNotTuned)
}
case "stop":
fmt.Println("Stopping daemon (tuned.service), this may take several seconds...")
if err := system.SystemctlDisableStop(TunedService); err != nil {
errorExit("%v", err)
}
// tuned then calls `sapconf daemon revert`
fmt.Println("Daemon (tuned.service) has been disabled and stopped.")
fmt.Println("All tuned parameters have been reverted to default.")
case "revert":
// This action name is only used by tuned script, hence it is not advertised to end user.
if err := tuneApp.RevertAll(false); err != nil {
panic(err)
}
default:
PrintHelpAndExit(1)
}
}
// Print mismatching fields in the note comparison result.
func PrintNoteFields(noteID string, comparisons map[string]note.NoteFieldComparison, printComparison bool) {
fmt.Printf("%s - %s -\n", noteID, tuningOptions[noteID].Name())
hasDiff := false
for name, comparison := range comparisons {
if !comparison.MatchExpectation {
hasDiff = true
if printComparison {
fmt.Printf("\t%s Expected: %s\n", name, comparison.ExpectedValueJS)
fmt.Printf("\t%s Actual : %s\n", name, comparison.ActualValueJS)
} else {
fmt.Printf("\t%s : %s\n", name, comparison.ExpectedValueJS)
}
}
}
if !hasDiff {
fmt.Printf("\t(no change)\n")
}
}
// Verify that all system parameters do not deviate from any of the enabled solutions/notes.
func VerifyAllParameters() {
unsatisfiedNotes, comparisons, err := tuneApp.VerifyAll()
if err != nil {
errorExit("Failed to inspect the current system: %v", err)
}
if len(unsatisfiedNotes) == 0 {
fmt.Println("The running system is currently well-tuned according to all of the enabled notes.")
} else {
for _, unsatisfiedNoteID := range unsatisfiedNotes {
PrintNoteFields(unsatisfiedNoteID, comparisons[unsatisfiedNoteID], true)
}
errorExit("The parameters listed above have deviated from SAP/SUSE recommendations.")
}
}
func NoteAction(actionName, noteID string) {
switch actionName {
case "apply":
if noteID == "" {
PrintHelpAndExit(1)
}
if err := tuneApp.TuneNote(noteID); err != nil {
errorExit("Failed to tune for note %s: %v", noteID, err)
}
fmt.Println("The note has been applied successfully.")
if !system.SystemctlIsRunning(TunedService) || system.GetTunedProfile() != TunedProfileName {
fmt.Println("\nRemember: if you wish to automatically activate the solution's tuning options after a reboot," +
"you must instruct saptune to configure \"tuned\" daemon by running:" +
"\n saptune daemon start")
}
case "list":
fmt.Println("All notes (+ denotes manually enabled notes, * denotes notes enabled by solutions):")
solutionNoteIDs := tuneApp.GetSortedSolutionEnabledNotes()
for _, noteID := range tuningOptions.GetSortedIDs() {
noteObj := tuningOptions[noteID]
format := "\t%s\t%s\n"
if i := sort.SearchStrings(solutionNoteIDs, noteID); i < len(solutionNoteIDs) && solutionNoteIDs[i] == noteID {
format = "*" + format
} else if i := sort.SearchStrings(tuneApp.TuneForNotes, noteID); i < len(tuneApp.TuneForNotes) && tuneApp.TuneForNotes[i] == noteID {
format = "+" + format
}
if noteID == "Block" {
// workaround: internal used note for solution ASE. Do not display
continue
}
fmt.Printf(format, noteID, noteObj.Name())
}
if !system.SystemctlIsRunning(TunedService) || system.GetTunedProfile() != TunedProfileName {
fmt.Println("\nRemember: if you wish to automatically activate the solution's tuning options after a reboot," +
"you must instruct saptune to configure \"tuned\" daemon by running:" +
"\n saptune daemon start")
}
case "verify":
if noteID == "" {
VerifyAllParameters()
} else {
// Check system parameters against the specified note, no matter the note has been tuned for or not.
if conforming, comparisons, err := tuneApp.VerifyNote(noteID); err != nil {
errorExit("Failed to test the current system against the specified note: %v", err)
} else if !conforming {
PrintNoteFields(noteID, comparisons, true)
errorExit("The parameters listed above have deviated from the specified note.\n")
} else {
fmt.Println("The system fully conforms to the specified note.")
}
}
case "simulate":
if noteID == "" {
PrintHelpAndExit(1)
}
// Run verify and print out all fields of the note
if _, comparisons, err := tuneApp.VerifyNote(noteID); err != nil {
errorExit("Failed to test the current system against the specified note: %v", err)
} else {
fmt.Printf("If you run `saptune note apply %s`, the following changes will be applied to your system:\n", noteID)
PrintNoteFields(noteID, comparisons, false)
}
case "customise":
if noteID == "" {
PrintHelpAndExit(1)
}
if _, err := tuneApp.GetNoteByID(noteID); err != nil {
errorExit("%v", err)
}
fileName := fmt.Sprintf("/etc/sysconfig/saptune-note-%s", noteID)
if _, err := os.Stat(fileName); os.IsNotExist(err) {
errorExit("Note %s does not require additional customisation input.", noteID)
} else if err != nil {
errorExit("Failed to read file '%s' - %v", fileName, err)
}
editor := os.Getenv("EDITOR")
if editor == "" {
editor = "/usr/bin/vim" // launch vim by default
}
if err := syscall.Exec(editor, []string{editor, fileName}, os.Environ()); err != nil {
errorExit("Failed to start launch editor %s: %v", editor, err)
}
case "revert":
if noteID == "" {
PrintHelpAndExit(1)
}
if err := tuneApp.RevertNote(noteID, true); err != nil {
errorExit("Failed to revert note %s: %v", noteID, err)
}
fmt.Println("Parameters tuned by the note have been successfully reverted.")
fmt.Println("Please note: the reverted note may still show up in list of enabled notes, if an enabled solution refers to it.")
default:
PrintHelpAndExit(1)
}
}
func SolutionAction(actionName, solName string) {
switch actionName {
case "apply":
if solName == "" {
PrintHelpAndExit(1)
}
removedAdditionalNotes, err := tuneApp.TuneSolution(solName)
if err != nil {
errorExit("Failed to tune for solution %s: %v", solName, err)
}
fmt.Println("All tuning options for the SAP solution have been applied successfully.")
if len(removedAdditionalNotes) > 0 {
fmt.Println("The following previously-enabled notes are now tuned by the SAP solution:")
for _, noteNumber := range removedAdditionalNotes {
fmt.Printf("\t%s\t%s\n", noteNumber, tuningOptions[noteNumber].Name())
}
}
if !system.SystemctlIsRunning(TunedService) || system.GetTunedProfile() != TunedProfileName {
fmt.Println("\nRemember: if you wish to automatically activate the solution's tuning options after a reboot," +
"you must instruct saptune to configure \"tuned\" daemon by running:" +
"\n saptune daemon start")
}
case "list":
fmt.Println("All solutions (* denotes enabled solution):")
for _, solName := range solution.GetSortedSolutionNames(solutionSelector) {
format := "\t%s\n"
if i := sort.SearchStrings(tuneApp.TuneForSolutions, solName); i < len(tuneApp.TuneForSolutions) && tuneApp.TuneForSolutions[i] == solName {
format = "*" + format
}
fmt.Printf(format, solName)
}
if !system.SystemctlIsRunning(TunedService) || system.GetTunedProfile() != TunedProfileName {
fmt.Println("\nRemember: if you wish to automatically activate the solution's tuning options after a reboot," +
"you must instruct saptune to configure \"tuned\" daemon by running:" +
"\n saptune daemon start")
}
case "verify":
if solName == "" {
VerifyAllParameters()
} else {
// Check system parameters against the specified solution, no matter the solution has been tuned for or not.
unsatisfiedNotes, comparisons, err := tuneApp.VerifySolution(solName)
if err != nil {
errorExit("Failed to test the current system against the specified SAP solution: %v", err)
}
if len(unsatisfiedNotes) == 0 {
fmt.Println("The system fully conforms to the tuning guidelines of the specified SAP solution.")
} else {
for _, unsatisfiedNoteID := range unsatisfiedNotes {
PrintNoteFields(unsatisfiedNoteID, comparisons[unsatisfiedNoteID], true)
}
errorExit("The parameters listed above have deviated from the specified SAP solution recommendations.\n")
}
}
case "simulate":
if solName == "" {
PrintHelpAndExit(1)
}
// Run verify and print out all fields of the note
if _, comparisons, err := tuneApp.VerifySolution(solName); err != nil {
errorExit("Failed to test the current system against the specified note: %v", err)
} else {
fmt.Printf("If you run `saptune solution apply %s`, the following changes will be applied to your system:\n", solName)
for noteID, noteComparison := range comparisons {
PrintNoteFields(noteID, noteComparison, false)
}
}
case "revert":
if solName == "" {
PrintHelpAndExit(1)
}
if err := tuneApp.RevertSolution(solName); err != nil {
errorExit("Failed to revert tuning for solution %s: %v", solName, err)
}
fmt.Println("Parameters tuned by the notes referred by the SAP solution have been successfully reverted.")
default:
PrintHelpAndExit(1)
}
}