Added config file to configure Profiler Tool

This CL adds a configuration file that acts as the user interface to the
Node Profiler tool. It also adds a function that parses user input from
the configuration file to a Logger Option structure. Changes needed in the
cloudlogger package are reflected in this CL.

BUG=b/196059500

Change-Id: I4d96500f4985b9191b8f29f0cfb48837dd0cc8a6
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/21010
Reviewed-by: Dexter Rivera <riverade@google.com>
Tested-by: Dexter Rivera <riverade@google.com>
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
diff --git a/src/cmd/nodeprofiler/main.go b/src/cmd/nodeprofiler/main.go
index 89c4c97..9237c81 100644
--- a/src/cmd/nodeprofiler/main.go
+++ b/src/cmd/nodeprofiler/main.go
@@ -7,23 +7,44 @@
 
 import (
 	"context"
+	"encoding/json"
 	"flag"
+	"fmt"
+	"os"
 	"time"
 
 	"cloud.google.com/go/logging"
 	"cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/cloudlogger"
 	"cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/profiler"
+
 	log "github.com/sirupsen/logrus"
 )
 
 const cloudLoggerName = "cos_node_profiler"
 
-var projID, command *string
-var profilerCount, profilerInterval, cmdCount, cmdInterval, cmdTimeOut *int
+var (
+	configFile       = flag.String("config-file", "", "specifies the path of the configuration file. If path is not set, then it is assumed that command line flags will be passed to configure the Node Profiler.")
+	projID           = flag.String("project", "", "specifies the GCP project where logs will be added.")
+	command          = flag.String("cmd", "", "specifies raw commands for which to log output.")
+	cmdCount         = flag.Int("cmd-count", 0, "specifies the number of times to run an arbitrary shell command.")
+	cmdInterval      = flag.Int("cmd-interval", 0, "specifies the interval (in seconds) separating the number of times the user runs an arbitrary shell command.")
+	cmdTimeOut       = flag.Int("cmd-timeout", 300, "specifies the amount of time (in seconds) it will take for the a raw command to timeout and be killed.")
+	profilerCount    = flag.Int("profiler-count", 1, "specifies the number of times to collect USE Report.")
+	profilerInterval = flag.Int("profiler-interval", 0, "specifies the interval (in seconds) separating the number of times the user collects USE Report.")
+)
 
 func main() {
-	// Retrieving user input from command line flags.
-	opts := loadFlags()
+	var opts *cloudlogger.LoggerOpts
+	var err error
+	flag.Parse()
+	if *configFile != "" {
+		opts, err = loadConfig(*configFile)
+		if err != nil {
+			log.Fatalf("%v", err)
+		}
+	} else {
+		opts = loadFlags()
+	}
 	// [START client setup]
 	ctx := context.Background()
 	client, err := logging.NewClient(ctx, opts.ProjID)
@@ -45,33 +66,6 @@
 	log.Info("Successfully logged profiler report.")
 }
 
-// loadflags helps to load user command line flags to run the profiler tool.
-func loadFlags() *cloudlogger.LoggerOpts {
-	projID = flag.String("project", "", "specifies the GCP project where logs will be added.")
-	command = flag.String("cmd", "", "specifies raw commands for which to log output.")
-	profilerCount = flag.Int("profiler-count", 1, "specifies the number of times to run the profiler.")
-	profilerInterval = flag.Int("profiler-interval", 0, "specifies the interval (in seconds) separating the number of times the user runs the profiler.")
-	cmdCount = flag.Int("cmd-count", 0, "specifies the number of times to run an arbitrary shell command.")
-	cmdInterval = flag.Int("cmd-interval", 0, "specifies the interval (in seconds) separating the number of times the user runs an arbitrary shell command.")
-	cmdTimeOut = flag.Int("cmd-timeout", 300, "specifies the amount of time (in seconds) it will take for the a raw command to timeout and be killed.")
-	flag.Parse()
-	// Getting Profiler Options.
-	components, commands := generateProfilerOpts()
-	// populating LoggerOpts struct with configurations from user.
-	opts := &cloudlogger.LoggerOpts{
-		ProjID:           *projID,
-		Command:          *command,
-		CmdCount:         *cmdCount,
-		CmdInterval:      time.Duration(*cmdInterval) * time.Second,
-		CmdTimeOut:       time.Duration(*cmdTimeOut) * time.Second,
-		ProfilerCount:    *profilerCount,
-		ProfilerInterval: time.Duration(*profilerInterval) * time.Second,
-		Components:       components,
-		ProfilerCmds:     commands,
-	}
-	return opts
-}
-
 // generateProfilerOpts is a helper function used to generate the components
 // array as well as the profiler options used to call the
 // profiler.GenerateUSEReport function from the profiler package.
@@ -81,15 +75,66 @@
 	cpu := profiler.NewCPU("CPU")
 	memcap := profiler.NewMemCap("MemCap")
 	sDevIO := profiler.NewStorageDevIO("StorageDevIO")
-	components := []profiler.Component{cpu, memcap, sDevIO}
+	sCap := profiler.NewStorageCap("StorageCap")
+	components := []profiler.Component{cpu, memcap, sDevIO, sCap}
 	// End Getting Components
 	// Getting Commands
 	vmstat := profiler.NewVMStat("vmstat", 1, 5, []string{"us", "sy", "st", "si", "so", "r"})
 	lscpu := profiler.NewLscpu("lscpu", []string{"CPU(s)"})
 	free := profiler.NewFree("free", []string{"Mem:used", "Mem:total", "Swap:used", "Swap:total"})
 	iostat := profiler.NewIOStat("iostat", "-xdz", 1, 5, []string{"aqu-sz", "%util"})
-	commands := []profiler.Command{vmstat, lscpu, free, iostat}
+	df := profiler.NewDF("df", "-k", []string{})
+	commands := []profiler.Command{vmstat, lscpu, free, iostat, df}
 	// End Getting Commands
 	// [End generating ProfilerOpts from Profiler Package]
 	return components, commands
 }
+
+// loadflags helps to use command line flags as configuration to the Node
+// Profiler tool.
+func loadFlags() *cloudlogger.LoggerOpts {
+	// Getting Profiler Options.
+	components, commands := generateProfilerOpts()
+	shCmds := []cloudlogger.ShellCmdOpts{
+		cloudlogger.ShellCmdOpts{
+			Command:     *command,
+			CmdCount:    *cmdCount,
+			CmdInterval: time.Duration(*cmdInterval) * time.Second,
+			CmdTimeOut:  time.Duration(*cmdTimeOut) * time.Second,
+		},
+	}
+	// populating LoggerOpts struct with configurations from user.
+	opts := &cloudlogger.LoggerOpts{
+		ProjID:           *projID,
+		ShCmds:           shCmds,
+		ProfilerCount:    *profilerCount,
+		ProfilerInterval: time.Duration(*profilerInterval) * time.Second,
+		Components:       components,
+		ProfilerCmds:     commands,
+	}
+	return opts
+}
+
+// loadConfig helps to use a json configuration file in the current directory
+// as configuration to the Node Profiler Tool.
+func loadConfig(filename string) (*cloudlogger.LoggerOpts, error) {
+	var logger cloudlogger.LoggerOpts
+	configFile, err := os.Open(filename)
+	defer configFile.Close()
+	if err != nil {
+		return &logger, fmt.Errorf("failed to open config file %v: %v", filename, err)
+	}
+	jsonParser := json.NewDecoder(configFile)
+	if err = jsonParser.Decode(&logger); err != nil {
+		return &logger, fmt.Errorf("failed to parse config file %v: %v", filename, err)
+	}
+	for i := 0; i < len(logger.ShCmds); i++ {
+		logger.ShCmds[i].CmdInterval = logger.ShCmds[i].CmdInterval * time.Second
+		logger.ShCmds[i].CmdTimeOut = logger.ShCmds[i].CmdTimeOut * time.Second
+	}
+	logger.ProfilerInterval = logger.ProfilerInterval * time.Second
+	components, commands := generateProfilerOpts()
+	logger.Components = components
+	logger.ProfilerCmds = commands
+	return &logger, err
+}
diff --git a/src/cmd/nodeprofiler/sample_config.json b/src/cmd/nodeprofiler/sample_config.json
new file mode 100644
index 0000000..53f0e07
--- /dev/null
+++ b/src/cmd/nodeprofiler/sample_config.json
@@ -0,0 +1,19 @@
+{
+    "ProjID":"cos-interns-playground",
+    "ShCmds":[
+       {
+          "Command":"lscpu",
+          "CmdCount":2,
+          "CmdInterval":1,
+          "CmdTimeOut":5
+       },
+       {
+          "Command":"vmstat",
+          "CmdCount":3,
+          "CmdInterval":1,
+          "CmdTimeOut": 5
+       }
+    ],
+    "ProfilerCount":2,
+    "ProfilerInterval":2
+}
diff --git a/src/pkg/nodeprofiler/cloudlogger/cloudlogger.go b/src/pkg/nodeprofiler/cloudlogger/cloudlogger.go
index 351d6ab..6ee0530 100644
--- a/src/pkg/nodeprofiler/cloudlogger/cloudlogger.go
+++ b/src/pkg/nodeprofiler/cloudlogger/cloudlogger.go
@@ -26,25 +26,32 @@
 	Additional string
 }
 
+// ShellCmdOpts contains the options that each arbitrary shell command should
+// be mapped to.
+type ShellCmdOpts struct {
+	// Specifies raw commands for which to log output.
+	Command string `json:"Command"`
+	// Specifies the number of times to run an arbitrary shell command.
+	CmdCount int `json:"CmdCount"`
+	// Specifies the interval separating the number of times the user runs
+	// an arbitrary shell command.
+	CmdInterval time.Duration `json:"CmdInterval"`
+	// Specifies the amount of time it will take for the a raw shell command to
+	// timeout.
+	CmdTimeOut time.Duration `json: "CmdTimeOut"`
+}
+
 // LoggerOpts contains the options supported when logging the Profiler Report
 // to Google Cloud Logging backend.
 type LoggerOpts struct {
 	// Specifies the project ID to write logs to.
-	ProjID string
-	// Specifies raw commands for which to log output.
-	Command string
-	// Specifies the number of times to run an arbitrary shell command.
-	CmdCount int
-	// Specifies the interval separating the number of times the user runs
-	// an arbitrary shell command.
-	CmdInterval time.Duration
-	// Specifies the amount of time it will take for the a raw shell command to
-	// timeout.
-	CmdTimeOut time.Duration
+	ProjID string `json:"ProjID"`
+	// Specifies the commands to run mapped with their options.
+	ShCmds []ShellCmdOpts `json:"ShCmds"`
 	// Specifies the number of times to run the profiler.
-	ProfilerCount int
+	ProfilerCount int `json: "ProfilerCount"`
 	// Specifies the interval the profiler will run.
-	ProfilerInterval time.Duration
+	ProfilerInterval time.Duration `json: "ProfilerInterval"`
 	// Components on which to run profiler. It may contain CPU(s), Memory, etc.
 	Components []profiler.Component
 	// ProfilerCmds field specifies additional options needed to run the profiler
@@ -84,29 +91,34 @@
 	if l.ProfilerCount == 0 && l.ProfilerInterval != 0 {
 		return fmt.Errorf("invalid Logger options: cannot set ProfilerInterval if ProfilerCount is not set")
 	}
-	// if the command configuration is nil, ensure CmdCount, CmdInterval, and
-	// CmdTimeOut configurations are all 0.
-	if l.Command == "" {
-		if l.CmdCount != 0 || l.CmdInterval != 0 || l.CmdTimeOut != 0 {
-			return fmt.Errorf("invalid Logger options: CmdCount, CmdInterval and CmdTimeout should not be set if Command is not set")
+	for _, shCmd := range l.ShCmds {
+		// if the command configuration is nil, ensure CmdCount, CmdInterval, and
+		// CmdTimeOut configurations are all 0.
+		if shCmd.Command == "" {
+			if shCmd.CmdCount != 0 || shCmd.CmdInterval != 0 || shCmd.CmdTimeOut != 0 {
+				return fmt.Errorf("invalid Logger options: CmdCount, CmdInterval and CmdTimeout should not be set if Command is not set")
+			}
+		} else {
+			// if the the command configuration was set, but the user did not specify
+			// when the command will timeout, then the CmdTimeOut configuration will be
+			// set to defaultCommandTimeout, which is 300 seconds.
+			if shCmd.CmdTimeOut == 0 {
+				shCmd.CmdTimeOut = defaultCommandTimeout
+			}
+
+			// if CmdCount config is not set, then CmdInterval must not be set.
+			if shCmd.CmdCount == 0 && shCmd.CmdInterval != 0 {
+				return fmt.Errorf("invalid Logger options: cannot set CmdInterval if CmdCount is not set")
+			}
+			// if the command configuration is set but no other options is set, run the
+			// command once.
+			if shCmd.CmdCount == 0 && shCmd.CmdInterval == 0 {
+				shCmd.CmdCount = 1
+			}
 		}
-	} else {
-		// if the the command configuration was set, but the user did not specify
-		// when the command will timeout, then the CmdTimeOut configuration will be
-		// set to defaultCommandTimeout, which is 300 seconds.
-		if l.CmdTimeOut == 0 {
-			l.CmdTimeOut = defaultCommandTimeout
-		}
-		// if CmdCount config is not set, then CmdInterval must not be set.
-		if l.CmdCount == 0 && l.CmdInterval != 0 {
-			return fmt.Errorf("invalid Logger options: cannot set CmdInterval if CmdCount is not set")
-		}
-		// if the command configuration is set but no other options is set, run the
-		// command once.
-		if l.CmdCount == 0 && l.CmdInterval == 0 {
-			l.CmdCount = 1
-		}
+
 	}
+
 	return nil
 }
 
@@ -219,30 +231,34 @@
 		return err
 	}
 	log.Info("Done validating logger options.")
-	log.Info("Running Profiler . . .")
 	// Ensure logging entries are written to the cloud logging backend.
 	defer g.Flush()
-	// Only log shell command if the user specified a command.
-	if len(opts.Command) == 0 {
-		emptyCmd = true
-	} else {
-		emptyCmd = false
-		log.Info("Running shell command . . .")
-		// Fetching command from user input and populating the cmdArray
-		// with the main command and its flags.
-		cmdArray := strings.Split(opts.Command, " ")
-		usrMainCmd := cmdArray[0]
-		usrMainCmdFlags := cmdArray[1:]
-		for i := 0; i < opts.CmdCount; i++ {
-			if err := logShellCommand(g, opts.CmdTimeOut, usrMainCmd, usrMainCmdFlags...); err != nil {
-				errArr = append(errArr, err)
-				continue
+	for _, shCmd := range opts.ShCmds {
+		// Only log shell command if the user specified a command.
+		if len(shCmd.Command) == 0 {
+			emptyCmd = true
+		} else {
+			emptyCmd = false
+			log.Infof("Running %v . . .", shCmd.Command)
+			// Fetching command from user input and populating the cmdArray
+			// with the main command and its flags.
+			cmdArray := strings.Split(shCmd.Command, " ")
+			usrMainCmd := cmdArray[0]
+			usrMainCmdFlags := cmdArray[1:]
+			for i := 0; i < shCmd.CmdCount; i++ {
+				if err := logShellCommand(g, shCmd.CmdTimeOut, usrMainCmd, usrMainCmdFlags...); err != nil {
+					errArr = append(errArr, err)
+					continue
+				}
+				// Delaying execution by cmdInterval seconds.
+				time.Sleep(shCmd.CmdInterval)
+
 			}
-			// Delaying execution by cmdInterval seconds.
-			time.Sleep(opts.CmdInterval)
+			log.Infof("Done running %v\n", shCmd.Command)
+
 		}
-		log.Infof("Done running shell command.")
 	}
+	log.Info("Running Profiler . . .")
 	// Run the profiler profCount times. The default value is 1 time unless user
 	// set the counter to a different number.
 	for i := 0; i < opts.ProfilerCount; i++ {
diff --git a/src/pkg/nodeprofiler/cloudlogger/cloudlogger_test.go b/src/pkg/nodeprofiler/cloudlogger/cloudlogger_test.go
index f36214d..c8eacdf 100644
--- a/src/pkg/nodeprofiler/cloudlogger/cloudlogger_test.go
+++ b/src/pkg/nodeprofiler/cloudlogger/cloudlogger_test.go
@@ -208,18 +208,22 @@
 
 			t.Errorf("LogText(%v, %v) = %v, wantErr %t", f, test.input, err, test.wantErr)
 		}
-		if diff := cmp.Diff(test.wantOutput, f.logged); diff != "" {
-			t.Errorf("ran logText(fakeTextLogger, %+v), but got mismatch between got and want (-got, +want): \n diff %s", test.input, diff)
+		if diff := cmp.Diff(f.logged, test.wantOutput); diff != "" {
+			t.Errorf("ran LogText(fakeTextLogger, %+v), but got mismatch between got and want (-got, +want): \n diff %s", test.input, diff)
 		}
 	}
 }
 
 func TestTableLogProfilerReport(t *testing.T) {
 	// Retrieving testing data.
-	inputFile := "testdata/testdata.txt"
-	inputFileData, err := ioutil.ReadFile(inputFile)
+	inputFile1, inputFile2 := "testdata/testdata.txt", "testdata/testdata2.txt"
+	inputFileData1, err := ioutil.ReadFile(inputFile1)
 	if err != nil {
-		t.Errorf("failed to open testing input file: %v\n", err)
+		t.Errorf("failed to open testing input file %v: %v\n", inputFile1, err)
+	}
+	inputFileData2, err := ioutil.ReadFile(inputFile2)
+	if err != nil {
+		t.Errorf("failed to open testing input file %v: %v\n", inputFile2, err)
 	}
 	// Retrieving profiler components and commands.
 	components, cmds := generateFakeProfilerOpts()
@@ -241,6 +245,7 @@
 	for _, c := range useReport.Components {
 		cInfos = append(cInfos, componentInfo{Name: c.Name(), Metrics: expected.Metrics, Additional: c.AdditionalInformation()})
 	}
+
 	var tests = []struct {
 		name       string
 		input      *LoggerOpts
@@ -250,11 +255,15 @@
 		{
 			name: "valid logger options and non-empty json payload log.",
 			input: &LoggerOpts{
-				ProjID:           "cos-interns-playground",
-				Command:          "bash testdata/testcmd.sh",
-				CmdCount:         1,
-				CmdInterval:      0 * time.Second,
-				CmdTimeOut:       3 * time.Second,
+				ProjID: "cos-interns-playground",
+				ShCmds: []ShellCmdOpts{
+					ShellCmdOpts{
+						Command:     "bash testdata/testcmd.sh",
+						CmdCount:    1,
+						CmdInterval: 0 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					},
+				},
 				ProfilerCount:    1,
 				ProfilerInterval: 0 * time.Second,
 				Components:       components,
@@ -267,7 +276,7 @@
 						CommandOutput string
 					}{
 						CommandName:   "bash testdata/testcmd.sh",
-						CommandOutput: string(inputFileData),
+						CommandOutput: string(inputFileData1),
 					},
 					Severity: logging.Debug,
 				},
@@ -287,11 +296,20 @@
 		{
 			name: "multiple commands executions and multiple profiler runs non-empty json payload log.",
 			input: &LoggerOpts{
-				ProjID:           "cos-interns-playground",
-				Command:          "bash testdata/testcmd.sh",
-				CmdCount:         3,
-				CmdInterval:      0 * time.Second,
-				CmdTimeOut:       3 * time.Second,
+				ProjID: "cos-interns-playground",
+				ShCmds: []ShellCmdOpts{
+					ShellCmdOpts{
+						Command:     "bash testdata/testcmd.sh",
+						CmdCount:    1,
+						CmdInterval: 0 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					}, ShellCmdOpts{
+						Command:     "bash testdata/testhello.sh",
+						CmdCount:    1,
+						CmdInterval: 0 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					},
+				},
 				ProfilerCount:    2,
 				ProfilerInterval: 0 * time.Second,
 				Components:       components,
@@ -304,25 +322,18 @@
 						CommandOutput string
 					}{
 						CommandName:   "bash testdata/testcmd.sh",
-						CommandOutput: string(inputFileData),
+						CommandOutput: string(inputFileData1),
 					},
 					Severity: logging.Debug,
-				}, {
+				},
+
+				{
 					Payload: struct {
 						CommandName   string
 						CommandOutput string
 					}{
-						CommandName:   "bash testdata/testcmd.sh",
-						CommandOutput: string(inputFileData),
-					},
-					Severity: logging.Debug,
-				}, {
-					Payload: struct {
-						CommandName   string
-						CommandOutput string
-					}{
-						CommandName:   "bash testdata/testcmd.sh",
-						CommandOutput: string(inputFileData),
+						CommandName:   "bash testdata/testhello.sh",
+						CommandOutput: string(inputFileData2),
 					},
 					Severity: logging.Debug,
 				},
@@ -348,15 +359,18 @@
 				}},
 			wantErr: false,
 		},
-
 		{
 			name: "invalid logger options payload log: empty command with CmdCount and/or CmdInterval.",
 			input: &LoggerOpts{
-				ProjID:           "cos-interns-playground",
-				Command:          "",
-				CmdCount:         1,
-				CmdInterval:      0 * time.Second,
-				CmdTimeOut:       3 * time.Second,
+				ProjID: "cos-interns-playground",
+				ShCmds: []ShellCmdOpts{
+					ShellCmdOpts{
+						Command:     "",
+						CmdCount:    1,
+						CmdInterval: 0 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					},
+				},
 				ProfilerCount:    1,
 				ProfilerInterval: 0 * time.Second,
 				Components:       components,
@@ -368,11 +382,15 @@
 		{
 			name: "invalid logger options payload log: inconsistent CmdCount and CmdInterval.",
 			input: &LoggerOpts{
-				ProjID:           "cos-interns-playground",
-				Command:          "bash testdata/testcmd.sh",
-				CmdCount:         0,
-				CmdInterval:      3 * time.Second,
-				CmdTimeOut:       3 * time.Second,
+				ProjID: "cos-interns-playground",
+				ShCmds: []ShellCmdOpts{
+					ShellCmdOpts{
+						Command:     "bash testdata/testcmd.sh",
+						CmdCount:    0,
+						CmdInterval: 3 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					},
+				},
 				ProfilerCount:    1,
 				ProfilerInterval: 0 * time.Second,
 				Components:       components,
@@ -384,11 +402,15 @@
 		{
 			name: "invalid logger options payload log: inconsistent ProfilerCount and ProfilerInterval.",
 			input: &LoggerOpts{
-				ProjID:           "",
-				Command:          "bash testdata/testcmd.sh",
-				CmdCount:         1,
-				CmdInterval:      1 * time.Second,
-				CmdTimeOut:       3 * time.Second,
+				ProjID: "cos-interns-playground",
+				ShCmds: []ShellCmdOpts{
+					ShellCmdOpts{
+						Command:     "bash testdata/testcmd.sh",
+						CmdCount:    0,
+						CmdInterval: 3 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					},
+				},
 				ProfilerCount:    0,
 				ProfilerInterval: 4 * time.Second,
 				Components:       components,
@@ -398,13 +420,17 @@
 			wantErr:    true,
 		},
 		{
-			name: "invalid logger options payload log: no project ID.",
+			name: "invalid logger options payload log: no project ID/invalid project ID.",
 			input: &LoggerOpts{
-				ProjID:           "",
-				Command:          "bash testdata/testcmd.sh",
-				CmdCount:         0,
-				CmdInterval:      3 * time.Second,
-				CmdTimeOut:       3 * time.Second,
+				ProjID: "",
+				ShCmds: []ShellCmdOpts{
+					ShellCmdOpts{
+						Command:     "bash testdata/testcmd.sh",
+						CmdCount:    0,
+						CmdInterval: 3 * time.Second,
+						CmdTimeOut:  3 * time.Second,
+					},
+				},
 				ProfilerCount:    1,
 				ProfilerInterval: 0 * time.Second,
 				Components:       components,
diff --git a/src/pkg/nodeprofiler/cloudlogger/testdata/testdata2.txt b/src/pkg/nodeprofiler/cloudlogger/testdata/testdata2.txt
new file mode 100644
index 0000000..a58b168
--- /dev/null
+++ b/src/pkg/nodeprofiler/cloudlogger/testdata/testdata2.txt
@@ -0,0 +1 @@
+        Hello world !
diff --git a/src/pkg/nodeprofiler/cloudlogger/testdata/testhello.sh b/src/pkg/nodeprofiler/cloudlogger/testdata/testhello.sh
new file mode 100755
index 0000000..6e2bdf5
--- /dev/null
+++ b/src/pkg/nodeprofiler/cloudlogger/testdata/testhello.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+main() {
+    cat <<EOF
+        Hello world !
+EOF
+}
+main "$#"
diff --git a/src/pkg/nodeprofiler/profiler/commands.go b/src/pkg/nodeprofiler/profiler/commands.go
index b310d79..5d21fac 100644
--- a/src/pkg/nodeprofiler/profiler/commands.go
+++ b/src/pkg/nodeprofiler/profiler/commands.go
@@ -236,6 +236,15 @@
 	titles []string
 }
 
+// NewDF function helps to initialize a df structure.
+func NewDF(name string, flags string, titles []string) *df {
+	return &df{
+		name:   name,
+		flags:  flags,
+		titles: titles,
+	}
+}
+
 // Name returns the name for the 'df' command
 func (fs *df) Name() string {
 	return fs.name
@@ -245,9 +254,9 @@
 // map of title(s) to their values.
 func (fs *df) Run() (map[string][]string, error) {
 	// get output in 1K size to make summing values direct
-	out, err := utils.RunCommand(fs.Name(), "-k", fs.flags)
+	out, err := utils.RunCommand(fs.Name(), fs.flags)
 	if err != nil {
-		cmd := fs.Name() + " " + "-k"
+		cmd := fs.Name() + " " + fs.flags
 		return nil, fmt.Errorf("failed to run the command %q: %v",
 			cmd, err)
 	}
diff --git a/src/pkg/nodeprofiler/profiler/commands_test.go b/src/pkg/nodeprofiler/profiler/commands_test.go
index 5c80495..a396c3a 100644
--- a/src/pkg/nodeprofiler/profiler/commands_test.go
+++ b/src/pkg/nodeprofiler/profiler/commands_test.go
@@ -76,7 +76,7 @@
 			name: "df",
 			fakeCmd: &df{
 				name:   "testdata/df.sh",
-				flags:  "1",
+				flags:  "-k",
 				titles: []string{"Use%"},
 			},
 			want: map[string][]string{
@@ -87,7 +87,7 @@
 			name: "df's output with column titles mixed up",
 			fakeCmd: &df{
 				name:   "testdata/df.sh",
-				flags:  "2",
+				flags:  "-h",
 				titles: []string{"Mounted on"},
 			},
 			want: map[string][]string{
diff --git a/src/pkg/nodeprofiler/profiler/components.go b/src/pkg/nodeprofiler/profiler/components.go
index 2fdb23d..bfa8df1 100644
--- a/src/pkg/nodeprofiler/profiler/components.go
+++ b/src/pkg/nodeprofiler/profiler/components.go
@@ -8,7 +8,6 @@
 	"time"
 
 	"cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/utils"
-
 	log "github.com/sirupsen/logrus"
 )
 
@@ -47,7 +46,6 @@
 // this can be used to initialize CPU outside of the
 // profiler package.
 func NewCPU(name string) *CPU {
-
 	return &CPU{
 		name:    name,
 		metrics: &USEMetrics{},
@@ -94,7 +92,6 @@
 	if !stPresent {
 		return fmt.Errorf("missing vmstat column 'st'")
 	}
-
 	if len(us) == 0 {
 		return fmt.Errorf("no vmstat report collected")
 	} else if len(us) == 1 {
@@ -102,13 +99,11 @@
 			" reflecting current conditions of component, additional reports are needed"
 		return fmt.Errorf(err)
 	}
-
 	// ignore the first values of 'us', 'sy' and 'st' since they reflect averages
 	// since last reboot and can bring averages down
 	us = us[1:]
 	sy = sy[1:]
 	st = st[1:]
-
 	columns := [][]string{us, sy, st}
 	var total int
 	// loop over us, sy, st columns and sum their values
@@ -120,7 +115,6 @@
 		total += sum
 	}
 	count := len(us)
-
 	c.metrics.Utilization = math.Round((float64(total)/float64(count))*100) / 100
 	return nil
 }
@@ -160,7 +154,6 @@
 	if !present {
 		return fmt.Errorf("missing vmstat column 'r'")
 	}
-
 	if len(running) == 0 {
 		return fmt.Errorf("no vmstat report collected")
 	} else if len(running) == 1 {
@@ -168,7 +161,6 @@
 			" reflecting current conditions of component, additional reports are needed"
 		return fmt.Errorf(err)
 	}
-
 	// ignore the first values of 'r' since they reflect averages since last
 	// reboot and can bring the average down
 	running = running[1:]
@@ -177,7 +169,6 @@
 	if err != nil {
 		return err
 	}
-
 	num := len(running)
 	runningProcs := sum / num
 	count, err := c.calculateCPUCount(outputs)
@@ -205,7 +196,6 @@
 // this can be used to initialize MemCap outside of the
 // profiler package.
 func NewMemCap(name string) *MemCap {
-
 	return &MemCap{
 		name:    name,
 		metrics: &USEMetrics{},
@@ -366,7 +356,6 @@
 // this can be used to initialize Storage device I/O outside of the
 // profiler package.
 func NewStorageDevIO(name string) *StorageDevIO {
-
 	return &StorageDevIO{
 		name:    name,
 		metrics: &USEMetrics{},
@@ -377,7 +366,6 @@
 // the StorageDevIO component.
 func (d *StorageDevIO) AdditionalInformation() string {
 	return ""
-
 }
 
 // Name returns the name of the Storage device I/O component.
@@ -403,7 +391,6 @@
 	if !ok {
 		return fmt.Errorf("mising iostat column util")
 	}
-
 	total, err := utils.SumParseFloat(util)
 	if err != nil {
 		return err
@@ -434,7 +421,6 @@
 	}
 	average := total / float64(len(queue))
 	d.metrics.Saturation = average > 1
-
 	return nil
 }
 
@@ -456,7 +442,6 @@
 // this can be used to initialize StorageCap outside of the
 // profiler package.
 func NewStorageCap(name string) *StorageCap {
-
 	return &StorageCap{
 		name:    name,
 		metrics: &USEMetrics{},
@@ -490,7 +475,6 @@
 func (s *StorageCap) CollectUtilization(outputs map[string]utils.ParsedOutput) error {
 	// if devices are not set
 	s.setDefaults()
-
 	dfCmd := "df"
 	parsedOutput, ok := outputs[dfCmd]
 	if !ok {
@@ -526,7 +510,6 @@
 					return fmt.Errorf("failed to convert %q to int: %v", val, err)
 				}
 				fUsed += val
-
 				s = totalBlocks[index]
 				val, err = strconv.Atoi(s)
 				if err != nil {
@@ -544,7 +527,6 @@
 	}
 	util := (float64(fUsed) / float64(fSize)) * 100
 	fsUtilization := math.Round((util)*100) / 100
-
 	s.metrics.Utilization = fsUtilization
 	return nil
 }
@@ -560,11 +542,9 @@
 	// Not yet implemented
 	return nil
 }
-
 func (s *StorageCap) USEMetrics() *USEMetrics {
 	return s.metrics
 }
-
 func (s *StorageCap) Name() string {
 	return s.name
 }
@@ -572,11 +552,9 @@
 // CollectUSEMetrics collects USE Metrics for the component specified. It does this by calling
 // the necessary methods to collect utilization, saturation and errors.
 func CollectUSEMetrics(component Component, outputs map[string]utils.ParsedOutput) error {
-
 	metrics := component.USEMetrics()
 	metrics.Timestamp = time.Now()
 	start := metrics.Timestamp
-
 	var gotErr bool
 	if err := component.CollectUtilization(outputs); err != nil {
 		gotErr = true
@@ -588,7 +566,6 @@
 	}
 	end := time.Now()
 	metrics.Interval = end.Sub(start)
-
 	if gotErr {
 		err := "failed to collect all USE metrics for %q. " +
 			"Please check the logs for more information"
diff --git a/src/pkg/nodeprofiler/profiler/testdata/df.sh b/src/pkg/nodeprofiler/profiler/testdata/df.sh
index 5d82228..7621b4d 100755
--- a/src/pkg/nodeprofiler/profiler/testdata/df.sh
+++ b/src/pkg/nodeprofiler/profiler/testdata/df.sh
@@ -1,8 +1,8 @@
 #!/bin/bash
 
 main() {
-    case "$2" in
-    "1")
+    case "$1" in
+    "-k")
     cat <<EOF
     Filesystem     1K-blocks   Used Available Use% Mounted on
     /dev/vdb         7864320 5401876   1738492  76% /
@@ -11,7 +11,7 @@
     /dev/root         176176  173936         0 100% /dev/.ssh/sshd_config
 EOF
     ;;
-    "2")
+    "-h")
     cat <<EOF
     1K-blocks     Filesystem   Used Available Use% Mounted on
     /dev/vdb         7864320 5401876   1738492  76% /
@@ -24,4 +24,4 @@
     esac
 }
 
-main "$@"
\ No newline at end of file
+main "$@"