blob: 14a8cf11dd662ef06abdc4d50e05f3ec07a0d21d [file] [log] [blame]
package profiler
import (
"fmt"
"regexp"
"strconv"
"strings"
"cos.googlesource.com/cos/tools.git/src/pkg/nodeprofiler/utils"
)
// Command interface defines functions that can be implemented by
// structs to execute shell commands.
type Command interface {
Name() string
Run() (map[string][]string, error)
}
// vmstat represents a vmstat command.
type vmstat struct {
name string
// delay specifies times between updates in seconds.
delay int
// count specifies number of updates.
count int
// titles specifies the titles to get values for.
titles []string
}
// NewVMStat function helps to initialize a vmstat structure.
func NewVMStat(name string, delay int, count int, titles []string) *vmstat {
return &vmstat{
name: name,
delay: delay,
count: count,
titles: titles,
}
}
// Name returns the name for vmstat command.
func (v *vmstat) Name() string {
return v.name
}
func (v *vmstat) setDefaults() {
if v.delay == 0 {
v.delay = 1
}
if v.count == 0 {
v.count = 5
}
}
// Run executes the vmstat command, parses the output and returns it as
// a map of titles to their values.
func (v *vmstat) Run() (map[string][]string, error) {
// if delay and count not set
v.setDefaults()
interval := strconv.Itoa(v.delay)
count := strconv.Itoa(v.count)
args := []string{"-n", interval, count}
out, err := utils.RunCommand(v.Name(), args...)
if err != nil {
str := v.Name() + " " + strings.Join(args, " ")
return nil, fmt.Errorf("failed to run the command %q: %v",
str, err)
}
s := string(out)
lines := strings.Split(strings.Trim(s, "\n"), "\n")
// ignore the first row in vmstat's output
lines = lines[1:]
// split first row into columns based on titles
allTitles := strings.Fields(lines[0])
// parse output by columns
output, err := utils.ParseColumns(lines, allTitles, v.titles...)
return output, err
}
// lscpu represents an lscpu command.
type lscpu struct {
name string
// titles specifies the titles to get values for.
titles []string
}
// NewLscpu function helps to initialize a lscpu structure.
func NewLscpu(name string, titles []string) *lscpu {
return &lscpu{
name: name,
titles: titles,
}
}
// Name returns the name for the lscpu command.
func (l *lscpu) Name() string {
return l.name
}
// Run executes the lscpu command, parses the output and returns a
// a map of title(s) to their values.
func (l *lscpu) Run() (map[string][]string, error) {
out, err := utils.RunCommand(l.Name())
if err != nil {
return nil, fmt.Errorf("failed to run the command '%s': %v", l.Name(), err)
}
s := string(out)
lines := strings.Split(strings.Trim(s, "\n"), "\n")
// parse output by rows
output, err := utils.ParseRows(lines, ":", l.titles...)
return output, err
}
// free represents a free command.
type free struct {
name string
// titles specifies the titles to get values for.
titles []string
}
// NewFree function helps to initialize a free structure.
func NewFree(name string, titles []string) *free {
return &free{
name: name,
titles: titles,
}
}
// Name returns the name for the free command.
func (f *free) Name() string {
return f.name
}
// Run executes the free commands, parses the output and returns a
// a map of title(s) to their values.
func (f *free) Run() (map[string][]string, error) {
out, err := utils.RunCommand(f.Name(), "-m")
if err != nil {
cmd := f.Name() + " " + "-m"
return nil, fmt.Errorf("failed to run the command %q: %v",
cmd, err)
}
s := string(out)
lines := strings.Split(strings.Trim(s, "\n"), "\n")
// parse output by rows and columns
output, err := utils.ParseRowsAndColumns(lines, f.titles...)
return output, err
}
// iostat represents an iostat command
type iostat struct {
name string
// flags specify the flags to be passed into the command.
flags string
// delay specifies times between updates in seconds.
delay int
// count specifies number of updates.
count int
// titles specifies the titles to get values for.
titles []string
}
// NewIOStat function helps to initialize a iostat structure.
func NewIOStat(name string, flags string, delay int, count int, titles []string) *iostat {
return &iostat{
name: name,
flags: flags,
delay: delay,
count: count,
titles: titles,
}
}
// Name returns the name for the iostat command.
func (i *iostat) Name() string {
return i.name
}
func (i *iostat) setDefaults() {
if i.delay == 0 {
i.delay = 1
}
if i.count == 0 {
i.count = 5
}
}
// Run executes the iostat commands, parses the output and returns a
// a map of title(s) to their values.
func (i *iostat) Run() (map[string][]string, error) {
// if delay and count not set
i.setDefaults()
interval := strconv.Itoa(i.delay)
count := strconv.Itoa(i.count)
args := []string{i.flags, interval, count}
out, err := utils.RunCommand(i.Name(), args...)
if err != nil {
str := i.Name() + " " + strings.Join(args, " ")
return nil, fmt.Errorf("failed to run the command %q: %v",
str, err)
}
s := string(out)
lines := strings.Split(strings.Trim(s, "\n"), "\n")
// ignore the first 2 lines in iostat's output so that the first line
// is column titles.
lines = lines[2:]
// split first row into columns based on titles
allTitles := strings.Fields(lines[0])
// parse output by rows and columns
output, err := utils.ParseColumns(lines, allTitles, i.titles...)
return output, err
}
// df represents the command 'df'
type df struct {
name string
titles []string
}
// Name returns the name for the 'df' command
func (fs *df) Name() string {
return fs.name
}
// Run executes the 'df' command, parses its output and returns a
// 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")
if err != nil {
cmd := fs.Name() + " " + "-k"
return nil, fmt.Errorf("failed to run the command %q: %v",
cmd, err)
}
s := string(out)
lines := strings.Split(strings.Trim(s, "\n"), "\n")
// match all strings that start with an uppercase letter. This
// pattern makes it possible to split df's titles row since some
// titles are multi-worded (as seen with "Mounted on" below) so
// splitting by whitespaces will result in incorrect titles slice.
//
// "Filesystem Size Used Avail Use% Mounted on" ->
// ["Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on"]
re := regexp.MustCompile(`[A-Z][^A-Z]*`)
allTitles := re.FindAllString(lines[0], -1)
// trim trailing or leading white spaces in all titles.
allTitles = utils.TrimCharacter(allTitles, " ")
// parse output by columns
output, err := utils.ParseColumns(lines, allTitles, fs.titles...)
return output, err
}