Add performance regression test for CPU component

This CL adds an end to end test for the profiler's GenerateUSEReport
to test that the CPU's CollectUSEMetrics works correctly when a stress
test is applied on the component over time

Change-Id: I4513302e36d5171f190cfc12fe36adb708b4f58b
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/20130
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
Reviewed-by: Dexter Rivera <riverade@google.com>
Tested-by: Dexter Rivera <riverade@google.com>
diff --git a/src/pkg/nodeprofiler/profilertest/README.md b/src/pkg/nodeprofiler/profilertest/README.md
new file mode 100644
index 0000000..27ceb12
--- /dev/null
+++ b/src/pkg/nodeprofiler/profilertest/README.md
@@ -0,0 +1,41 @@
+# Profilertest package
+Profilertest package runs end to end tests for the APIs written in the profiler package. It does this by simulating perfomance regression for different components, generating their USE reports and checking that the stress on each component was reflected in the USE metrics collected.
+
+# Tools
+To accomplish this, the tests makes use of the Linux package "stress-ng" which can stress test a Linux computer system in various selectable ways. The tool can be run as a shell command and can be configured by passing in a number of options. This configuration can be general options to modify the behavior of stress-ng or it can be specific to the type of stressor being used. Here's a general format of how to use the command:
+```
+stress-ng <general stress-ng options> <type of stressor> <number of workers> <stressor-specific options>
+```
+
+Examples: 
+```
+stress-ng --cpu 2 --cpu-load 95 -t 10s
+```
+load CPU, and specifically 2 processors, with 95 percent loading for the 2 CPU stress workers (each for 1 processor) for 10 seconds
+
+```
+stress-ng --vm-bytes 256M -m 1 --vm-keep
+```
+start 1 stress worker that will continually write to the allocated memory thus overloading memory capacity
+
+```
+stress-ng --help
+```
+shows more information about package usage
+
+
+Further documentation on the stress-ng package can be found by typing "man stress-ng" or on ubuntu's man pages: https://manpages.ubuntu.com/manpages/artful/man1/stress-ng.1.html
+
+# Installing stress-ng 
+The version of "stress-ng" used to run tests in package profilertest is 0.09.50.
+To download the "stress-ng" package, the following command can be used:
+```
+ $ sudo apt install stress-ng (on Ubuntu/Debian)
+ $ yum install stress-ng (on Fedora/CentOS)
+```
+
+# Run tests
+To run the stress tests, the following command can be run from package profilertest
+```
+ go test . -v
+ ```
\ No newline at end of file
diff --git a/src/pkg/nodeprofiler/profilertest/profiler_e2e_test.go b/src/pkg/nodeprofiler/profilertest/profiler_e2e_test.go
new file mode 100644
index 0000000..05568ce
--- /dev/null
+++ b/src/pkg/nodeprofiler/profilertest/profiler_e2e_test.go
@@ -0,0 +1,94 @@
+package profilertest
+
+import (
+	"bufio"
+	"os/exec"
+	"runtime"
+	"strconv"
+	"strings"
+	"testing"
+
+	"cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/profiler"
+)
+
+// TestCPUOverload tests whether the CPU's CollectUSEMetrics in package profiler
+// is working or not. It does this by overloading the CPU component using the
+// "stress-ng" package upto a certain threshold and checking whether this was
+// reflected in the component's metrics. The flags specificed to the shell command
+// "stress-ng" include:
+//        --cpu N: specifies the computer system the stress test is will be applied
+//                 on - the CPU and specifcally, N number of cores
+//        --cpu-load P: load CPU with P percent loading for the stress workers to
+//                 set an approximate threshold on expetected utilization
+//		  --fork N: continually fork child processes that exit to increase wait time
+//                 for processes and thus make saturation true
+//        -v: (verbose) show all debug, warnings and normal information output
+//        -t N: stop stress after N units of time (also specified in N)
+func TestCPUOverload(t *testing.T) {
+	// initialize all commands needed and the cpu component
+	titles := []string{"r", "us", "sy", "st"}
+	vmstat := profiler.NewVMStat("vmstat", 1, 10, titles)
+
+	titles = []string{"CPU(s)"}
+	lscpu := profiler.NewLscpu("lscpu", titles)
+
+	commands := []profiler.Command{vmstat, lscpu}
+
+	cpu := profiler.NewCPU("CPU")
+	components := []profiler.Component{cpu}
+	// get number of cores in CPU
+	n := runtime.NumCPU()
+	// number of processes should be more than number of cores to make CPU saturated
+	processes := n + 4
+
+	// The stress test will be run for 1 minute, overloading the CPU cores upto 92% and creating
+	// a number of dummy processes that will make the component busy.
+	args := []string{"--cpu", strconv.Itoa(n), "--cpu-load",
+		"92", "--fork", strconv.Itoa(processes), "-v", "-t", "60s"}
+	cmd := exec.Command("stress-ng", args...)
+
+	// get pipe that will be connected to command's standard output when comamnd starts.
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		t.Errorf("failed to connect to command's standard output: %v", err)
+	}
+	// Use the same pipe for standard error
+	cmd.Stderr = cmd.Stdout
+
+	str := "stress-ng" + " " + strings.Join(args, " ")
+	t.Logf("running %q", str)
+	// starts the command but does not wait for it to complete.
+	if err := cmd.Start(); err != nil {
+		t.Errorf("failed to start the command %q: %v", str, err)
+	}
+	scanner := bufio.NewScanner(stdout)
+
+	// print to stdout in real time
+	go func() {
+		for scanner.Scan() {
+			m := scanner.Text()
+			t.Log(m)
+		}
+	}()
+	// generates USE report while stress test is running
+	report, err := profiler.GenerateUSEReport(components, commands)
+	t.Logf("USE Report generated for CPU :\n %+v", report.Components[0].USEMetrics())
+	if err != nil {
+		t.Errorf("failed to generate USE report for CPU component, %v", err)
+	}
+
+	if utilization := cpu.USEMetrics().Utilization; utilization < 90 {
+		err := "overloaded the CPU upto 90 percent but utilization was less that 90: %v"
+		t.Errorf(err, utilization)
+	}
+	if saturated := cpu.USEMetrics().Saturation; !saturated {
+		err := "overloaded cpu cores with stress test processes but saturation was false"
+		t.Errorf(err)
+	}
+
+	// wait for command to exit and release any resources associated with it.
+	if err = cmd.Wait(); err != nil {
+		t.Errorf("failed to finish running the command %q: %v", str, err)
+	}
+	t.Logf("finished running %q command successfully", str)
+}