From 030ff58c950616dcbd3200199b9b1998a0e57a18 Mon Sep 17 00:00:00 2001 From: andrewheberle Date: Fri, 28 Aug 2020 09:06:29 +0800 Subject: [PATCH 1/2] Use go netstat, cleanup, make paths cross platform --- .gitignore | 3 +- Makefile | 17 +++++ config.go | 141 ----------------------------------- go.mod | 12 +++ go.sum | 23 ++++++ run_command.go | 71 ------------------ server.go | 25 ------- src/config.go | 131 ++++++++++++++++++++++++++++++++ src/globals_linux.go | 7 ++ src/globals_windows.go | 10 +++ handler.go => src/handler.go | 106 ++++++++++++++++---------- main.go => src/main.go | 0 src/server.go | 25 +++++++ 13 files changed, 292 insertions(+), 279 deletions(-) create mode 100644 Makefile delete mode 100644 config.go create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 run_command.go delete mode 100644 server.go create mode 100644 src/config.go create mode 100644 src/globals_linux.go create mode 100644 src/globals_windows.go rename handler.go => src/handler.go (59%) rename main.go => src/main.go (100%) create mode 100644 src/server.go diff --git a/.gitignore b/.gitignore index db993dd..ba077a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ - -go-feedback-agent.exe +bin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..42a75e7 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONEY: clean get + +VERSION=`git describe --tags` +BUILD=`git rev-parse HEAD` +LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD}" + +default: build + +build: windows +windows: + env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -v -o ./bin/windows64/LBCPUMon ./src +linux: + env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -v -o ./bin/linux64/lbcpumon ./src +get: + go mod download +clean: + go clean -modcache \ No newline at end of file diff --git a/config.go b/config.go deleted file mode 100644 index 11b9a0b..0000000 --- a/config.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "encoding/xml" - "io/ioutil" - "log" - "os" - "strconv" - "time" - "strings" -) - -var currentAgentStatus string = "" -var GlobalConfig *XMLConfig - -type ValueAttr struct { - Value string `xml:"value,attr"` -} - -func (va ValueAttr) ToInt() int { - val, err := strconv.Atoi(va.Value) - if err != nil { - panic(err) - } - return val -} - -func (va ValueAttr) ToFloat() float64 { - val, err := strconv.ParseFloat(va.Value, 64) - if err != nil { - panic(err) - } - return val -} -func (va ValueAttr) ToString() string { - return va.Value -} - -type TCPService struct { - Name ValueAttr - IPAddress ValueAttr - Port ValueAttr - MaxConnections ValueAttr - ImportanceFactor ValueAttr -} - -type CPU struct { - ImportanceFactor ValueAttr - ThresholdValue ValueAttr -} - -type RAM struct { - ImportanceFactor ValueAttr - ThresholdValue ValueAttr -} - -type XMLConfig struct { - XMLName xml.Name `xml:"xml"` - Cpu CPU - Ram RAM - TCPService []TCPService - ReadAgentStatusFromConfig ValueAttr - ReadAgentStatusFromConfigInterval ValueAttr - AgentStatus ValueAttr - Interval ValueAttr - Port ValueAttr -} - -type ticker struct { - period time.Duration - ticker time.Ticker -} -func createTicker(period time.Duration) *ticker { - return &ticker{period, *time.NewTicker(period)} -} -func (t *ticker) resetTicker() { - t.ticker = *time.NewTicker(t.period) -} - -func readConfig() { - xmlFile, err := os.Open("C:/ProgramData/LoadBalancer.org/LoadBalancer/config.xml") - if err != nil { - panic(err) - } - defer xmlFile.Close() - content, err := ioutil.ReadAll(xmlFile) - if err != nil { - panic(err) - } - - err = xml.Unmarshal(content, &GlobalConfig) - if err != nil { - panic(err) - } -} - -func InitConfig() { - f, err := os.OpenFile("C:/ProgramData/LoadBalancer.org/LoadBalancer/lbfbalogfile", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) - if err != nil { - log.Fatalf("error opening file: %v", err) - } - log.SetOutput(f) - readConfig() - - intervalTicker := time.NewTicker(time.Second * time.Duration(GlobalConfig.Interval.ToInt())) - go func() { - for { - select { - case <-intervalTicker.C: - initialRun = false - } - } - }() - - if strings.ToLower(GlobalConfig.ReadAgentStatusFromConfig.Value) == "true" { - statusTicker := time.NewTicker(time.Second * time.Duration(GlobalConfig.ReadAgentStatusFromConfigInterval.ToInt())) - go func() { - for { - select { - case <-statusTicker.C: - readConfig() - // If status changed, send 'up ready' for a full interval - if currentAgentStatus != GlobalConfig.AgentStatus.Value { - initialRun = true - intervalTicker.Stop() - intervalTicker = time.NewTicker(time.Second * time.Duration(GlobalConfig.Interval.ToInt())) - go func() { - for { - select { - case <-intervalTicker.C: - initialRun = false - } - } - }() - } - currentAgentStatus = GlobalConfig.AgentStatus.Value - } - } - }() - } -} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9caf6d9 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/loadbalancer-org/go-feedback-agent + +go 1.13 + +require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/kardianos/service v1.0.0 + github.com/shirou/gopsutil v2.20.2+incompatible + github.com/stretchr/testify v1.6.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d58a102 --- /dev/null +++ b/go.sum @@ -0,0 +1,23 @@ +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 h1:BjkPE3785EwPhhyuFkbINB+2a1xATwk8SNDWnJiD41g= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5/go.mod h1:jtAfVaU/2cu1+wdSRPWE2c1N2qeAA3K4RH9pYgqwets= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYef0= +github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shirou/gopsutil v2.20.2+incompatible h1:ucK79BhBpgqQxPASyS2cu9HX8cfDVljBN1WWFvbNvgY= +github.com/shirou/gopsutil v2.20.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/run_command.go b/run_command.go deleted file mode 100644 index 2116f8c..0000000 --- a/run_command.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "log" - "os/exec" - "runtime" - "time" -) - -func runcmd(command string) (res string) { - var shell, flag string - if runtime.GOOS == "windows" { - shell = "cmd" - flag = "/c" - } else { - shell = "/bin/sh" - flag = "-c" - } - res, err := run(10, shell, flag, command) - if err != nil { - log.Println(err) - return - } - return -} - -func run(timeout int, command string, args ...string) (res string, err error) { - // instantiate new command - cmd := exec.Command(command, args...) - - // get pipe to standard output - stdout, err := cmd.StdoutPipe() - if err != nil { - return res, errors.New("cmd.StdoutPipe() error: " + err.Error()) - } - - // start process via command - if err = cmd.Start(); err != nil { - return res, errors.New("cmd.Start() error: " + err.Error()) - } - - // setup a buffer to capture standard output - var buf bytes.Buffer - - // create a channel to capture any errors from wait - done := make(chan error) - go func() { - if _, err := buf.ReadFrom(stdout); err != nil { - panic("buf.Read(stdout) error: " + err.Error()) - } - done <- cmd.Wait() - }() - - // block on select, and switch based on actions received - select { - case <-time.After(time.Duration(timeout) * time.Second): - if err := cmd.Process.Kill(); err != nil { - return res, errors.New("failed to kill: " + err.Error()) - } - return "", errors.New("command timed out") - case err = <-done: - if err != nil { - close(done) - return res, errors.New("process done, with error: " + err.Error()) - } - return buf.String(), nil - } - return "", nil -} diff --git a/server.go b/server.go deleted file mode 100644 index 769710f..0000000 --- a/server.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "log" - "net" - "strconv" -) - -type Server struct { - server net.Listener -} - -func InitServer() *Server { - srv := &Server{} - // If Port is not specified, use 3333 by default - if _, err := strconv.Atoi(GlobalConfig.Port.ToString()); err != nil { - GlobalConfig.Port.Value = "3333" - } - listner, err := net.Listen("tcp", ":" + GlobalConfig.Port.ToString()) - if err != nil { - log.Fatal(err) - } - srv.server = listner - return srv -} diff --git a/src/config.go b/src/config.go new file mode 100644 index 0000000..ac70309 --- /dev/null +++ b/src/config.go @@ -0,0 +1,131 @@ +package main + +import ( + "encoding/xml" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +var currentAgentStatus string = "" +var GlobalConfig *XMLConfig + +type ValueAttr struct { + Value string `xml:"value,attr"` +} + +func (va ValueAttr) ToInt() int { + val, err := strconv.Atoi(va.Value) + if err != nil { + panic(err) + } + return val +} + +func (va ValueAttr) ToFloat() float64 { + val, err := strconv.ParseFloat(va.Value, 64) + if err != nil { + panic(err) + } + return val +} +func (va ValueAttr) ToString() string { + return va.Value +} + +type TCPService struct { + Name ValueAttr + IPAddress ValueAttr + Port ValueAttr + MaxConnections ValueAttr + ImportanceFactor ValueAttr +} + +type CPU struct { + ImportanceFactor ValueAttr + ThresholdValue ValueAttr +} + +type RAM struct { + ImportanceFactor ValueAttr + ThresholdValue ValueAttr +} + +type XMLConfig struct { + XMLName xml.Name `xml:"xml"` + Cpu CPU + Ram RAM + TCPService []TCPService + ReadAgentStatusFromConfig ValueAttr + ReadAgentStatusFromConfigInterval ValueAttr + AgentStatus ValueAttr + Interval ValueAttr + Port ValueAttr +} + +func readConfig() { + xmlFile, err := os.Open(filepath.Join(configDir, "config.xml")) + if err != nil { + panic(err) + } + defer xmlFile.Close() + content, err := ioutil.ReadAll(xmlFile) + if err != nil { + panic(err) + } + + err = xml.Unmarshal(content, &GlobalConfig) + if err != nil { + panic(err) + } +} + +func InitConfig() { + f, err := os.OpenFile(filepath.Join(configDir, "lbfbalogfile"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("error opening file: %v", err) + } + log.SetOutput(f) + readConfig() + + intervalTicker := time.NewTicker(time.Second * time.Duration(GlobalConfig.Interval.ToInt())) + go func() { + for { + select { + case <-intervalTicker.C: + initialRun = false + } + } + }() + + if strings.ToLower(GlobalConfig.ReadAgentStatusFromConfig.Value) == "true" { + statusTicker := time.NewTicker(time.Second * time.Duration(GlobalConfig.ReadAgentStatusFromConfigInterval.ToInt())) + go func() { + for { + select { + case <-statusTicker.C: + readConfig() + // If status changed, send 'up ready' for a full interval + if currentAgentStatus != GlobalConfig.AgentStatus.Value { + initialRun = true + intervalTicker.Stop() + intervalTicker = time.NewTicker(time.Second * time.Duration(GlobalConfig.Interval.ToInt())) + go func() { + for { + select { + case <-intervalTicker.C: + initialRun = false + } + } + }() + } + currentAgentStatus = GlobalConfig.AgentStatus.Value + } + } + }() + } +} diff --git a/src/globals_linux.go b/src/globals_linux.go new file mode 100644 index 0000000..3b8abd5 --- /dev/null +++ b/src/globals_linux.go @@ -0,0 +1,7 @@ +package main + +import "path/filepath" + +var ( + configDir string = filepath.Join("/etc", "LoadBalancer.org", "LoadBalancer") +) diff --git a/src/globals_windows.go b/src/globals_windows.go new file mode 100644 index 0000000..c20234f --- /dev/null +++ b/src/globals_windows.go @@ -0,0 +1,10 @@ +package main + +import ( + "os" + "path/filepath" +) + +var ( + configDir string = filepath.Join(os.Getenv("ProgramData"), "LoadBalancer.org", "LoadBalancer") +) diff --git a/handler.go b/src/handler.go similarity index 59% rename from handler.go rename to src/handler.go index a0f3790..0f8ad00 100644 --- a/handler.go +++ b/src/handler.go @@ -2,11 +2,13 @@ package main import ( "fmt" - "github.com/shirou/gopsutil/cpu" - "github.com/shirou/gopsutil/mem" "math" "net" - "strings" + "strconv" + + "github.com/cakturk/go-netstat/netstat" + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/mem" ) const ( @@ -27,7 +29,6 @@ const ( func handleClient(conn net.Conn) { defer conn.Close() conn.Write(GetResponseForMode()) - conn.Close() } func GetResponseForMode() (response []byte) { @@ -38,28 +39,34 @@ func GetResponseForMode() (response []byte) { switch GlobalConfig.AgentStatus.Value { case Normal: - averageCpuLoad := 0.0 - if cpuImportance > 0 { - cpuLoad, err := cpu.Percent(0, false) - if err != nil { - return []byte("0%\n") - } - averageCpuLoad = cpuLoad[0] - } - usedRam := 0.0 - if ramImportance > 0 { - v, err := mem.VirtualMemory() - if err != nil { - return []byte("0%\n") - } - usedRam = v.UsedPercent - } + usedRam := 0.0 + averageCpuLoad := 0.0 + utilization := 0.0 + divider := 0.0 + + // Calculate CPU + if cpuImportance > 0 { + cpuLoad, err := cpu.Percent(0, false) + if err != nil { + return []byte("0%\n") + } + averageCpuLoad = cpuLoad[0] + } + + // Calculate RAM + if ramImportance > 0 { + v, err := mem.VirtualMemory() + if err != nil { + return []byte("0%\n") + } + usedRam = v.UsedPercent + } + // If any resource is important and utilized 100% then everything else is not important if averageCpuLoad > cpuThresholdValue && cpuThresholdValue > 0 || (usedRam > ramThresholdValue && ramThresholdValue > 0) { - response = []byte("0%\n") + return []byte("0%\n") } - utilization := 0.0 - divider := 0.0 + utilization = utilization + averageCpuLoad*cpuImportance if cpuImportance > 0 { divider++ @@ -71,32 +78,41 @@ func GetResponseForMode() (response []byte) { } for _, tcpService := range GlobalConfig.TCPService { - if tcpService.ImportanceFactor.ToFloat() > 0 { - sessionOccupied := GetSessionUtilized(tcpService.IPAddress.Value, tcpService.Port.Value, tcpService.MaxConnections.ToInt()) + // Make sure our importance factor is greater than 0 otherwise ignore + if tcpService.ImportanceFactor.ToFloat() > 0 { + // Get session occupied + sessionOccupied := GetSessionUtilized(tcpService.IPAddress.Value, tcpService.Port.Value, tcpService.MaxConnections.ToInt()) - utilization = utilization + sessionOccupied*tcpService.ImportanceFactor.ToFloat() - divider++ + // Calculate utilization + utilization = utilization + sessionOccupied*tcpService.ImportanceFactor.ToFloat() - if sessionOccupied > 99 && tcpService.ImportanceFactor.ToFloat() == 1 { - response = []byte("0%\n") - break - } - } + // increase our divider + divider++ + + if sessionOccupied > 99 && tcpService.ImportanceFactor.ToFloat() == 1 { + return []byte("0%\n") + } + } } utilization = utilization / divider + // Account for utilization less than 0 if utilization < 0 { utilization = 0 } + + // Account for utilization more than 0 if utilization > 100 { utilization = 100 } + if returnIdle { response = []byte(fmt.Sprintf("%v%%\n", math.Ceil(100-utilization))) } else { response = []byte(fmt.Sprintf("%v%%\n", math.Ceil(utilization))) } + if initialRun { response = append([]byte("up ready "), response...) } @@ -106,9 +122,10 @@ func GetResponseForMode() (response []byte) { response = []byte("down\n") case Halt: response = []byte("down\n") - default: + default: response = []byte("error\n") } + return } @@ -121,13 +138,22 @@ func GetSessionUtilized(IPAddress, servicePort string, maxNumberOfSessionsPerSer } func getNumberOfLocalEstablishedConnections(ipAddress string, port string) int { - if ipAddress == "*" { - ipAddress = "" + p, err := strconv.Atoi(port) + if err != nil { + return 0 } - result := runcmd("netstat -nt | findstr " + ipAddress + ":" + port + " | findstr ESTABLISHED ") - count := len(strings.Split(result, "\n")) - if count == 0 { - return count + + // get slice of sockets based on match function + tabs, err := netstat.TCPSocks(func(s *netstat.SockTabEntry) bool { + if ipAddress == "*" { + return s.State == netstat.Established && s.LocalAddr.Port == uint16(p) + } + + return s.State == netstat.Established && s.LocalAddr.IP.String() == ipAddress && s.LocalAddr.Port == uint16(p) + }) + if err != nil { + return 0 } - return count - 1 + + return len(tabs) } diff --git a/main.go b/src/main.go similarity index 100% rename from main.go rename to src/main.go diff --git a/src/server.go b/src/server.go new file mode 100644 index 0000000..84deb60 --- /dev/null +++ b/src/server.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + "net" + "strconv" +) + +type Server struct { + server net.Listener +} + +func InitServer() *Server { + srv := &Server{} + // If Port is not specified, use 3333 by default + if _, err := strconv.Atoi(GlobalConfig.Port.ToString()); err != nil { + GlobalConfig.Port.Value = "3333" + } + listner, err := net.Listen("tcp", ":"+GlobalConfig.Port.ToString()) + if err != nil { + log.Fatal(err) + } + srv.server = listner + return srv +} From 57373df8463c4f6405d464bfe06ebb16e62409a5 Mon Sep 17 00:00:00 2001 From: Andrew Heberle Date: Fri, 28 Aug 2020 10:06:19 +0800 Subject: [PATCH 2/2] Remove dead code --- src/run_command.go | 71 ---------------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 src/run_command.go diff --git a/src/run_command.go b/src/run_command.go deleted file mode 100644 index 2116f8c..0000000 --- a/src/run_command.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "log" - "os/exec" - "runtime" - "time" -) - -func runcmd(command string) (res string) { - var shell, flag string - if runtime.GOOS == "windows" { - shell = "cmd" - flag = "/c" - } else { - shell = "/bin/sh" - flag = "-c" - } - res, err := run(10, shell, flag, command) - if err != nil { - log.Println(err) - return - } - return -} - -func run(timeout int, command string, args ...string) (res string, err error) { - // instantiate new command - cmd := exec.Command(command, args...) - - // get pipe to standard output - stdout, err := cmd.StdoutPipe() - if err != nil { - return res, errors.New("cmd.StdoutPipe() error: " + err.Error()) - } - - // start process via command - if err = cmd.Start(); err != nil { - return res, errors.New("cmd.Start() error: " + err.Error()) - } - - // setup a buffer to capture standard output - var buf bytes.Buffer - - // create a channel to capture any errors from wait - done := make(chan error) - go func() { - if _, err := buf.ReadFrom(stdout); err != nil { - panic("buf.Read(stdout) error: " + err.Error()) - } - done <- cmd.Wait() - }() - - // block on select, and switch based on actions received - select { - case <-time.After(time.Duration(timeout) * time.Second): - if err := cmd.Process.Kill(); err != nil { - return res, errors.New("failed to kill: " + err.Error()) - } - return "", errors.New("command timed out") - case err = <-done: - if err != nil { - close(done) - return res, errors.New("process done, with error: " + err.Error()) - } - return buf.String(), nil - } - return "", nil -}