diff --git a/cmd/api.go b/cmd/api.go new file mode 100644 index 0000000..adb36e1 --- /dev/null +++ b/cmd/api.go @@ -0,0 +1,214 @@ +/* +Copyright © 2021 Bisohns + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/bisohns/saido/config" + "github.com/bisohns/saido/driver" + "github.com/bisohns/saido/inspector" + "github.com/gorilla/handlers" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + socketBufferSize = 1042 + messageBufferSize = 256 +) + +var ( + port string + server = http.NewServeMux() + upgrader = &websocket.Upgrader{ + ReadBufferSize: socketBufferSize, + WriteBufferSize: socketBufferSize, + CheckOrigin: func(r *http.Request) bool { + return true + }} +) + +type FullMessage struct { + Error bool + Message interface{} +} + +type Message struct { + Host string + Name string + Data interface{} +} + +type Client struct { + Socket *websocket.Conn + Send chan *FullMessage +} + +// Write to websocket +func (client *Client) Write() { + defer client.Socket.Close() + var err error + for msg := range client.Send { + err = client.Socket.WriteJSON(msg) + if err != nil { + log.Error("Error inside client write ", err) + } + } +} + +type Hosts struct { + Config *config.Config + // Connections : hostname mapped to connection instances to reuse + // across metrics + mu sync.Mutex + Drivers map[string]*driver.Driver + Client chan *Client + Start chan bool +} + +func (hosts *Hosts) getDriver(address string) *driver.Driver { + hosts.mu.Lock() + defer hosts.mu.Unlock() + return hosts.Drivers[address] +} + +func (hosts *Hosts) resetDriver(host config.Host) { + hosts.mu.Lock() + defer hosts.mu.Unlock() + hostDriver := host.Connection.ToDriver() + hosts.Drivers[host.Address] = &hostDriver +} + +func (hosts *Hosts) sendMetric(host config.Host, client *Client) { + if hosts.getDriver(host.Address) == nil { + hosts.resetDriver(host) + } + for _, metric := range config.GetDashboardInfoConfig(hosts.Config).Metrics { + initializedMetric, err := inspector.Init(metric, hosts.getDriver(host.Address)) + data, err := initializedMetric.Execute() + if err == nil { + var unmarsh interface{} + json.Unmarshal(data, &unmarsh) + message := &FullMessage{ + Message: Message{ + Host: host.Address, + Name: metric, + Data: unmarsh, + }, + Error: false, + } + client.Send <- message + } else { + // check for error 127 which means command was not found + var errorContent string + if !strings.Contains(fmt.Sprintf("%s", err), "127") { + errorContent = fmt.Sprintf("Could not retrieve metric %s from driver %s with error %s, resetting connection...", metric, host.Address, err) + } else { + errorContent = fmt.Sprintf("Command %s not found on driver %s", metric, host.Address) + } + log.Error(errorContent) + hosts.resetDriver(host) + message := &FullMessage{ + Message: errorContent, + Error: true, + } + client.Send <- message + } + } +} + +func (hosts *Hosts) Run() { + dashboardInfo := config.GetDashboardInfoConfig(hosts.Config) + log.Debug("In Running") + for { + select { + case client := <-hosts.Client: + for { + for _, host := range dashboardInfo.Hosts { + go hosts.sendMetric(host, client) + } + log.Infof("Delaying for %d seconds", dashboardInfo.PollInterval) + time.Sleep(time.Duration(dashboardInfo.PollInterval) * time.Second) + } + } + } + +} + +func (hosts *Hosts) ServeHTTP(w http.ResponseWriter, req *http.Request) { + socket, err := upgrader.Upgrade(w, req, nil) + if err != nil { + log.Fatal(err) + return + } + client := &Client{ + Socket: socket, + Send: make(chan *FullMessage, messageBufferSize), + } + hosts.Client <- client + client.Write() +} + +func newHosts(cfg *config.Config) *Hosts { + hosts := &Hosts{ + Config: cfg, + Drivers: make(map[string]*driver.Driver), + Client: make(chan *Client), + } + return hosts +} + +func setHostHandler(w http.ResponseWriter, r *http.Request) { + b, _ := json.Marshal("Hello World") + w.Write(b) +} + +var apiCmd = &cobra.Command{ + Use: "api", + Short: "host saido as an API on a PORT env variable, fallback to set argument", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + // server.HandleFunc("/set-hosts", SetHostHandler) + // FIXME: set up cfg using set-hosts endpoint + hosts := newHosts(cfg) + server.HandleFunc("/set-hosts", setHostHandler) + server.Handle("/metrics", hosts) + log.Info("listening on :", port) + _, err := strconv.Atoi(port) + if err != nil { + log.Fatal(err) + } + go hosts.Run() + loggedRouters := handlers.LoggingHandler(os.Stdout, server) + if err := http.ListenAndServe(":"+port, loggedRouters); err != nil { + log.Fatal(err) + } + }, +} + +func init() { + apiCmd.Flags().StringVarP(&port, "port", "p", "3000", "Port to run application server on") + rootCmd.AddCommand(apiCmd) +} diff --git a/config.example.yaml b/config.example.yaml index dd9fb7b..01a168d 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -29,4 +29,5 @@ hosts: metrics: - memory -- cpu +- tcp +poll-interval: 30 diff --git a/config/config.go b/config/config.go index fe56bac..b8b58cb 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" + "github.com/bisohns/saido/driver" "github.com/mitchellh/mapstructure" log "github.com/sirupsen/logrus" @@ -11,9 +12,10 @@ import ( ) type DashboardInfo struct { - Hosts []Host - Metrics []string - Title string + Hosts []Host + Metrics []string + Title string + PollInterval int } type Connection struct { @@ -22,6 +24,22 @@ type Connection struct { Password string `mapstructure:"password"` PrivateKeyPath string `mapstructure:"private_key_path"` Port int32 `mapstructure:"port"` + Host string +} + +func (conn *Connection) ToDriver() driver.Driver { + switch conn.Type { + case "ssh": + return &driver.SSH{ + User: conn.Username, + Host: conn.Host, + Port: int(conn.Port), + KeyFile: conn.PrivateKeyPath, + CheckKnownHosts: false, + } + default: + return &driver.Local{} + } } type Host struct { @@ -31,9 +49,10 @@ type Host struct { } type Config struct { - Hosts map[interface{}]interface{} `yaml:"hosts"` - Metrics []string `yaml:"metrics"` - Title string `yaml:"title"` + Hosts map[interface{}]interface{} `yaml:"hosts"` + Metrics []string `yaml:"metrics"` + Title string `yaml:"title"` + PollInterval int `yaml:"poll-interval"` } func LoadConfig(configPath string) *Config { @@ -62,6 +81,7 @@ func GetDashboardInfoConfig(config *Config) *DashboardInfo { for _, host := range dashboardInfo.Hosts { log.Debugf("%s: %v", host.Address, host.Connection) } + dashboardInfo.PollInterval = config.PollInterval return dashboardInfo } @@ -107,6 +127,7 @@ func parseConfig(name string, host string, group map[interface{}]interface{}, cu } if !isParent { + currentConn.Host = host newHost := Host{ Address: host, Connection: currentConn, diff --git a/go.mod b/go.mod index 625d530..d6a17c5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/bisohns/saido go 1.14 require ( + github.com/gorilla/handlers v1.5.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/melbahja/goph v1.2.1 github.com/mitchellh/mapstructure v1.4.3 diff --git a/go.sum b/go.sum index a2e7d9a..fd08290 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= @@ -140,6 +142,10 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= diff --git a/inspector/custom.go b/inspector/custom.go index 35966ba..c8805c6 100644 --- a/inspector/custom.go +++ b/inspector/custom.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "fmt" @@ -22,7 +23,7 @@ type Custom struct { // Parse : run custom parsing on output of the command func (i *Custom) Parse(output string) { - log.Debug("Parsing ouput string in Custom inspector") + log.Debug("Parsing output string in Custom inspector") i.Values = i.createMetric(output) } @@ -44,11 +45,13 @@ func (i Custom) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *Custom) Execute() { +func (i *Custom) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewCustom : Initialize a new Custom instance diff --git a/inspector/disk.go b/inspector/disk.go index cb8c09f..e431c4a 100644 --- a/inspector/disk.go +++ b/inspector/disk.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "fmt" "strconv" @@ -58,7 +59,7 @@ For Darwin it looks something like */ func (i *DF) Parse(output string) { var values []DFMetrics - log.Debug("Parsing ouput string in DF inspector") + log.Debug("Parsing output string in DF inspector") lines := strings.Split(output, "\n") for index, line := range lines { // skip title line @@ -114,11 +115,13 @@ func (i DF) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *DF) Execute() { +func (i *DF) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // DFWin: parse `wmic logicaldisk` to satisfy Inspector interface @@ -142,7 +145,7 @@ IMANI,C:,3,191980253184,,288303964160,OS */ func (i *DFWin) Parse(output string) { var values []DFMetrics - log.Debug("Parsing ouput string in DF inspector") + log.Debug("Parsing output string in DF inspector") lineChar := "\r" output = strings.TrimPrefix(output, lineChar) output = strings.TrimSuffix(output, lineChar) @@ -197,11 +200,13 @@ func (i DFWin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *DFWin) Execute() { +func (i *DFWin) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewDF : Initialize a new DF instance diff --git a/inspector/docker_stats.go b/inspector/docker_stats.go index 56c8b35..6097399 100644 --- a/inspector/docker_stats.go +++ b/inspector/docker_stats.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "strconv" "strings" @@ -45,7 +46,7 @@ func (i *DockerStats) Parse(output string) { } else { splitChars = "\n" } - log.Debug("Parsing ouput string in DockerStats inspector") + log.Debug("Parsing output string in DockerStats inspector") lines := strings.Split(output, splitChars) for index, line := range lines { // skip title line @@ -108,11 +109,13 @@ func (i DockerStats) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *DockerStats) Execute() { +func (i *DockerStats) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewDockerStats : Initialize a new DockerStats instance diff --git a/inspector/inspector.go b/inspector/inspector.go index 3a7ad0a..edf0b18 100644 --- a/inspector/inspector.go +++ b/inspector/inspector.go @@ -11,7 +11,7 @@ import ( type Inspector interface { Parse(output string) SetDriver(driver *driver.Driver) - Execute() + Execute() ([]byte, error) driverExec() driver.Command } diff --git a/inspector/loadavg.go b/inspector/loadavg.go index bacf6d0..ce11919 100644 --- a/inspector/loadavg.go +++ b/inspector/loadavg.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "fmt" "strconv" @@ -40,7 +41,7 @@ type LoadAvgWin struct { func loadavgParseOutput(output string) *LoadAvgMetrics { var err error - log.Debug("Parsing ouput string in LoadAvg inspector") + log.Debug("Parsing output string in LoadAvg inspector") columns := strings.Fields(output) Load1M, err := strconv.ParseFloat(columns[0], 64) Load5M, err := strconv.ParseFloat(columns[1], 64) @@ -77,10 +78,13 @@ func (i *LoadAvgDarwin) Parse(output string) { i.Values = loadavgParseOutput(output) } -func (i *LoadAvgDarwin) Execute() { +func (i *LoadAvgDarwin) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) + } else { + return []byte(""), err } } @@ -104,10 +108,13 @@ func (i LoadAvgLinux) driverExec() driver.Command { return (*i.Driver).ReadFile } -func (i *LoadAvgLinux) Execute() { +func (i *LoadAvgLinux) Execute() ([]byte, error) { output, err := i.driverExec()(i.FilePath) if err == nil { i.Parse(output) + return json.Marshal(i.Values) + } else { + return []byte(""), err } } @@ -134,10 +141,13 @@ func (i LoadAvgWin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *LoadAvgWin) Execute() { +func (i *LoadAvgWin) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) + } else { + return []byte(""), err } } diff --git a/inspector/meminfo.go b/inspector/meminfo.go index 3c4ab32..2ba26d7 100644 --- a/inspector/meminfo.go +++ b/inspector/meminfo.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "fmt" "strconv" @@ -60,7 +61,7 @@ type MemInfoWin struct { } func memInfoParseOutput(output, rawByteSize, displayByteSize string) *MemInfoMetrics { - log.Debug("Parsing ouput string in meminfo inspector") + log.Debug("Parsing output string in meminfo inspector") memTotal := getMatching("MemTotal", output) memFree := getMatching("MemFree", output) cached := getMatching("Cached", output) @@ -112,7 +113,7 @@ Cached: 1567652 kB */ func (i *MemInfoLinux) Parse(output string) { - log.Debug("Parsing ouput string in MemInfoLinux inspector") + log.Debug("Parsing output string in MemInfoLinux inspector") i.Values = memInfoParseOutput(output, i.RawByteSize, i.DisplayByteSize) } @@ -128,11 +129,13 @@ func (i MemInfoLinux) driverExec() driver.Command { return (*i.Driver).ReadFile } -func (i *MemInfoLinux) Execute() { +func (i *MemInfoLinux) Execute() ([]byte, error) { output, err := i.driverExec()(i.FilePath) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // Parse : parsing meminfo for Darwin command @@ -182,8 +185,11 @@ func (i MemInfoDarwin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *MemInfoDarwin) Execute() { +func (i *MemInfoDarwin) Execute() ([]byte, error) { physMemOutput, err := i.driverExec()(i.PhysMemCommand) + if err != nil { + return []byte(""), err + } swapOutput, err := i.driverExec()(i.SwapCommand) if err == nil { @@ -191,7 +197,9 @@ func (i *MemInfoDarwin) Execute() { swapOutput = strings.TrimSuffix(swapOutput, "\n") output := fmt.Sprintf("%s\n%s", physMemOutput, swapOutput) i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // Parse : run custom parsing on output of the command @@ -204,7 +212,7 @@ Virtual Memory: In Use: 14,061 MB 5120 12288 */ func (i *MemInfoWin) Parse(output string) { - log.Debug("Parsing ouput string in MemInfoWin inspector") + log.Debug("Parsing output string in MemInfoWin inspector") var cachesize, totalMem, freeMem, totalVirt, freeVirt int64 output = strings.ReplaceAll(output, ",", "") output = strings.ReplaceAll(output, "MB", "") @@ -257,8 +265,11 @@ func (i MemInfoWin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *MemInfoWin) Execute() { +func (i *MemInfoWin) Execute() ([]byte, error) { memOutput, err := i.driverExec()(i.MemCommand) + if err != nil { + return []byte(""), err + } cacheOutput, err := i.driverExec()(i.CacheCommand) if err == nil { cacheOutput = strings.ReplaceAll(cacheOutput, "\r", "") @@ -268,7 +279,9 @@ func (i *MemInfoWin) Execute() { cache := cacheOutputCols[1] output := fmt.Sprintf("%s\n%s", memOutput, cache) i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewMemInfoLinux : Initialize a new MemInfoLinux instance diff --git a/inspector/process.go b/inspector/process.go index 98e666f..9eb7503 100644 --- a/inspector/process.go +++ b/inspector/process.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "fmt" "strconv" @@ -61,11 +62,13 @@ func (i Process) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *Process) Execute() { +func (i *Process) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // Parse : run custom parsing on output of the command @@ -207,11 +210,13 @@ func (i ProcessWin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *ProcessWin) Execute() { +func (i *ProcessWin) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewProcess : Initialize a new Process instance diff --git a/inspector/rt.go b/inspector/rt.go index 9445bdb..b144708 100644 --- a/inspector/rt.go +++ b/inspector/rt.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "strconv" @@ -23,7 +24,7 @@ type ResponseTime struct { // Parse : run custom parsing on output of the command func (i *ResponseTime) Parse(output string) { - log.Debug("Parsing ouput string in ResponseTime inspector") + log.Debug("Parsing output string in ResponseTime inspector") strconv, err := strconv.ParseFloat(output, 64) if err != nil { log.Fatal(err) @@ -42,11 +43,13 @@ func (i ResponseTime) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *ResponseTime) Execute() { +func (i *ResponseTime) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewResponseTime : Initialize a new ResponseTime instance diff --git a/inspector/tcp.go b/inspector/tcp.go index 33400e7..cd968cf 100644 --- a/inspector/tcp.go +++ b/inspector/tcp.go @@ -2,8 +2,8 @@ package inspector import ( + "encoding/json" "errors" - "fmt" "strconv" "strings" @@ -25,9 +25,11 @@ type TcpDarwin struct { } type TcpLinux struct { - Command string - Driver *driver.Driver - Values TcpMetrics + Command string + BackupCommand string + UseBackup bool + Driver *driver.Driver + Values TcpMetrics } type TcpWin struct { @@ -80,15 +82,17 @@ func (i TcpDarwin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *TcpDarwin) Execute() { +func (i *TcpDarwin) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } /* -Parse for output +Parse for output (ss) State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 5 127.0.0.1:45481 0.0.0.0:* LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* @@ -96,6 +100,11 @@ LISTEN 0 5 127.0.0.1:631 0.0.0.0:* ESTAB 0 0 192.168.1.106:37986 198.252.206.25:443 CLOSE-WAIT 1 0 127.0.0.1:54638 127.0.0.1:45481 +Parse for output (netstat) +Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +tcp 0 0 172.17.0.2:2222 172.17.0.1:51874 ESTABLISHED 2104/sshd.pam: ci-d + + */ func (i *TcpLinux) Parse(output string) { ports := make(map[int]string) @@ -105,10 +114,17 @@ func (i *TcpLinux) Parse(output string) { if index == 0 { continue } + if i.UseBackup && (index == 1 || index == 2) { + continue + } columns := strings.Fields(line) if len(columns) >= 5 { - fmt.Print(columns) - status := columns[0] + var status string + if i.UseBackup { + status = columns[5] + } else { + status = columns[0] + } address := strings.Split(columns[3], ":") portString := address[len(address)-1] port, err := strconv.Atoi(portString) @@ -116,7 +132,6 @@ func (i *TcpLinux) Parse(output string) { log.Fatal("Could not parse port number in TcpLinux") } ports[port] = status - } } i.Values.Ports = ports @@ -134,11 +149,17 @@ func (i TcpLinux) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *TcpLinux) Execute() { +func (i *TcpLinux) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) + if err != nil { + output, err = i.driverExec()(i.BackupCommand) + i.UseBackup = true + } if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } /* Parse for output @@ -189,11 +210,13 @@ func (i TcpWin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *TcpWin) Execute() { +func (i *TcpWin) Execute() ([]byte, error) { output, err := i.driverExec()(i.Command) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewTcp: Initialize a new Tcp instance @@ -209,7 +232,10 @@ func NewTcp(driver *driver.Driver, _ ...string) (Inspector, error) { } } else if details.IsLinux { tcp = &TcpLinux{ - Command: `ss -tan`, + // Prioritize ss output over tan + Command: `ss -tpn`, + BackupCommand: `netstat -tpn`, + UseBackup: false, } } else if details.IsWindows { tcp = &TcpWin{ diff --git a/inspector/uptime.go b/inspector/uptime.go index 147ef97..c3050a5 100644 --- a/inspector/uptime.go +++ b/inspector/uptime.go @@ -1,6 +1,7 @@ package inspector import ( + "encoding/json" "errors" "fmt" "strconv" @@ -50,7 +51,7 @@ type UptimeWindows struct { func (i *UptimeLinux) Parse(output string) { fmt.Print(output) var err error - log.Debug("Parsing ouput string in Uptime inspector") + log.Debug("Parsing output string in Uptime inspector") columns := strings.Fields(output) Up, err := strconv.ParseFloat(columns[0], 64) Idle, err := strconv.ParseFloat(columns[1], 64) @@ -76,11 +77,13 @@ func (i UptimeLinux) driverExec() driver.Command { return (*i.Driver).ReadFile } -func (i *UptimeLinux) Execute() { +func (i *UptimeLinux) Execute() ([]byte, error) { output, err := i.driverExec()(i.FilePath) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // Parse : Parsing output of uptime commands on darwin @@ -91,7 +94,7 @@ func (i *UptimeLinux) Execute() { */ func (i *UptimeDarwin) Parse(output string) { fmt.Print(output) - log.Debug("Parsing ouput string in UptimeDarwin inspector") + log.Debug("Parsing output string in UptimeDarwin inspector") output = strings.TrimSuffix(output, ",") lines := strings.Split(output, "\n") unixTime, err := strconv.Atoi(lines[0]) @@ -119,7 +122,7 @@ func (i UptimeDarwin) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *UptimeDarwin) Execute() { +func (i *UptimeDarwin) Execute() ([]byte, error) { upOutput, err := i.driverExec()(i.UpCommand) idleOutput, err := i.driverExec()(i.IdleCommand) if err == nil { @@ -129,7 +132,9 @@ func (i *UptimeDarwin) Execute() { idleOutput = strings.TrimSuffix(idleOutput, "%") output := fmt.Sprintf("%s\n%s", upOutput, idleOutput) i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } /* Parse : SystemUpTime on windows @@ -139,7 +144,7 @@ SystemUpTime */ func (i *UptimeWindows) Parse(output string) { - log.Debug("Parsing ouput string in UptimeWindows inspector") + log.Debug("Parsing output string in UptimeWindows inspector") output = strings.ReplaceAll(output, "\r", "") output = strings.ReplaceAll(output, " ", "") upUnformatted := strings.Split(output, "\n")[1] @@ -164,11 +169,13 @@ func (i UptimeWindows) driverExec() driver.Command { return (*i.Driver).RunCommand } -func (i *UptimeWindows) Execute() { +func (i *UptimeWindows) Execute() ([]byte, error) { output, err := i.driverExec()(i.UpCommand) if err == nil { i.Parse(output) + return json.Marshal(i.Values) } + return []byte(""), err } // NewUptime : Initialize a new Uptime instance diff --git a/scripts/config.local.yaml b/scripts/config.local.yaml index 186c05f..ef39bc1 100644 --- a/scripts/config.local.yaml +++ b/scripts/config.local.yaml @@ -7,5 +7,6 @@ hosts: metrics: - memory -- cpu +- tcp +poll-interval: 30 diff --git a/scripts/prep-test-ssh.sh b/scripts/prep-test-ssh.sh index e464fd4..928e2a7 100755 --- a/scripts/prep-test-ssh.sh +++ b/scripts/prep-test-ssh.sh @@ -21,5 +21,7 @@ hosts: private_key_path: "$SSH_KEY_PATH/${SSH_KEY_NAME}" metrics: - memory -- cpu +- disk +- tcp +- docker EOF