From b3fcfc2f069f7a103fd3e711a00461e7e9585ae5 Mon Sep 17 00:00:00 2001 From: asandikci Date: Thu, 24 Aug 2023 17:44:01 +0300 Subject: [PATCH] feat: implement plugin structure - Plugin Manager - Plugin Opener - Code refactoring - Memory usage with gopsutil/mem --- cmd/ahenk-go/main.go | 55 +++++++++++++++++----------------- cmd/ahenk-go/plugin-manager.go | 47 +++++++++++++++-------------- cmd/ahenk-go/plugin-opener.go | 47 ++++++++++++++++------------- go.mod | 1 + go.sum | 2 ++ pkg/osinfo/main.go | 21 +++++++++++-- pkg/utils/main.go | 8 +++++ plugins/resources/main.go | 53 ++++++++++++++------------------ 8 files changed, 131 insertions(+), 103 deletions(-) diff --git a/cmd/ahenk-go/main.go b/cmd/ahenk-go/main.go index b226d11..72afee4 100644 --- a/cmd/ahenk-go/main.go +++ b/cmd/ahenk-go/main.go @@ -19,29 +19,9 @@ const LogFile = DataDir + "ahenk.log" const LibDir = "/usr/share/ahenk-go/" const PluginDir = LibDir + "/plugins/" -// FIXME there isn't any difference with Stop() function -// TODO There can be a Start() function in it but start function doesnt work properly right now -func Restart(pid, signal int) { - Stop(pid, signal) -} - -// Stop Ahenkd Daemon with a specific PID (running from second copy) -// do not forget to use also os.Exit() when using in ExecStop -func Stop(pid, signal int) { - log.Println("Stop Signal Caught") - - // FILLME what you want to do before stopping daemon? - - if err := syscall.Kill(pid, syscall.Signal(signal)); err == nil { - log.Printf("Ahenk Daemon with pid number %v Successfully stopped", pid) // TODO Also log to /etc/ahenk-go/ahenkd.log - } else { - log.Fatal(err) - } -} - // Main Function that starts daemon and controls arguments func main() { - if len(os.Args) == 2 && slices.Contains([]string{"start", "stop", "restart", "nodaemon"}, os.Args[1]) { + if len(os.Args) == 2 && slices.Contains([]string{"start", "stop", "restart", "nodaemon", "tmptest"}, os.Args[1]) { switch os.Args[1] { case "start": utils.CreatePath(DataDir) @@ -67,11 +47,9 @@ func main() { case "stop": i, _ := daemon.ReadPidFile(PidFile) Stop(i, 15) - os.Exit(0) case "restart": i, _ := daemon.ReadPidFile(PidFile) Restart(i, 15) - os.Exit(0) case "nodaemon": log.Print("STARTED AS NO-DAEMON") @@ -83,15 +61,36 @@ func main() { time.Sleep(10 * time.Second) log.Print("Killed") case "tmptest": - log.Print("TEMPORARY TEST") - - time.Sleep(3 * time.Second) - log.Print("Killed") + log.Print("TEMPORARY TEST STARTED, log files are NOT redirecting!") } } else { panic("Please enter a valid option !") } PluginManager() - log.Print("Plugin Manager Started Succesfully") // NEXT Make PluginManager async ! } + +// FIXME there isn't any difference with Stop() function +// TODO There can be a Start() function in it but start function doesnt work properly right now +// Restart ahenk daemon with a specific PID (running from second copy) +func Restart(pid, signal int) { + Stop(pid, signal) +} + +// Stop ahenk daemon with a specific PID (running from second copy) +func Stop(pid, signal int) { + log.Println("Stop Signal Caught") + + // FILLME what you want to do before stopping daemon? + + if err := syscall.Kill(pid, syscall.Signal(signal)); err == nil { + log.Printf("Ahenk Daemon with pid number %v Successfully stopped", pid) + f := utils.OpenLogFile(LogFile) + defer f.Close() + log.SetOutput(f) + log.Printf("Ahenk Daemon with pid number %v Successfully stopped", pid) + } else { + log.Fatal(err) + } + os.Exit(0) +} diff --git a/cmd/ahenk-go/plugin-manager.go b/cmd/ahenk-go/plugin-manager.go index 14309f4..7aa4bdf 100644 --- a/cmd/ahenk-go/plugin-manager.go +++ b/cmd/ahenk-go/plugin-manager.go @@ -1,31 +1,32 @@ package main -import "log" +import ( + "fmt" + "time" +) -// type Greeter interface { -// Greet() -// } - -func PluginManager() { - // LoadPlugin("resources") - - // // 4. use the module - // greeter.Greet() - - // greeter.Myvar() - // // for { - // // logPlugin(greeter.AgentInfo()) - // // time.Sleep(30 * time.Second) - // // } - log.Print("plugin manager started succesfully") +// plugins/resources +type Resources interface { + AgentInfo() map[string]interface{} } -// func logPlugin(mp map[string]interface{}) { -// fmt.Printf("\nOs Info:\n") -// for i, v := range mp { -// fmt.Printf("%v: %v\n", i, v) -// } -// } +// Loads Plugins and runs them. +// When you create a new plugin create a new interface and call this plugin in this function +func PluginManager() { + var resources Resources = LoadPlugin("resources").(Resources) + for { + logPlugin("AgentInfo", resources.AgentInfo()) + time.Sleep(30 * time.Second) + } +} + +// Logs plugin outputs. +func logPlugin(title string, mp map[string]interface{}) { + fmt.Printf("\n----- %v -----\n", title) + for i, v := range mp { + fmt.Printf("%v: %v\n", i, v) + } +} // // TODO response to Lider // // func createResponse() { diff --git a/cmd/ahenk-go/plugin-opener.go b/cmd/ahenk-go/plugin-opener.go index dd5c001..ba26b37 100644 --- a/cmd/ahenk-go/plugin-opener.go +++ b/cmd/ahenk-go/plugin-opener.go @@ -1,25 +1,32 @@ package main -// // Load Plugin with plugin name and function name -// func LoadPlugin(plugName, funcName string) { -// plug, err := plugin.Open("../../plugins/resources/main.so") -// if err != nil { -// fmt.Println(err) -// os.Exit(1) -// } +import ( + "fmt" + "os" + "plugin" -// symGreeter, err := plug.Lookup("Greeter") -// if err != nil { -// fmt.Println(err) -// os.Exit(1) -// } + "git.aliberksandikci.com.tr/Liderahenk/ahenk-go/pkg/utils" +) -// var greeter Greeter -// greeter, ok := symGreeter.(Greeter) -// if !ok { -// fmt.Println("unexpected type from module symbol") -// os.Exit(1) -// } -// } +// Load Plugin placed in PluginDir, returns empty interface. +// Do not forget to cast in plugin manager +// +// Give Plugin Name as argument and be sure you compiled plugins with `-buildmode=plugin` to PluginDir as `pluginname.so` +func LoadPlugin(plugName string) interface{} { -// // NEXT move plugin-manager.go main here ! + // TODO if error caugth try without relative path, this will be good for local testing + plug, err := plugin.Open(PluginDir + plugName + ".so") + utils.Check(err) + + // TODO also allow lookup another symbol other than PlugnameConnect + symGreeter, err := plug.Lookup(utils.FirstUpperEN(plugName) + "Connect") + utils.Check(err) + + var plugOut interface{} + plugOut, ok := symGreeter.(interface{}) + if !ok { + fmt.Println("unexpected type from module symbol") + os.Exit(1) + } + return plugOut +} diff --git a/go.mod b/go.mod index b004aba..ce2ce06 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/text v0.12.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 5959980..fa11463 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/osinfo/main.go b/pkg/osinfo/main.go index 3848ac3..292657d 100644 --- a/pkg/osinfo/main.go +++ b/pkg/osinfo/main.go @@ -5,6 +5,7 @@ import ( "git.aliberksandikci.com.tr/Liderahenk/ahenk-go/pkg/utils" "github.com/shirou/gopsutil/disk" + "github.com/shirou/gopsutil/mem" ) const KB = uint64(1024) @@ -35,8 +36,10 @@ func GetDisks() []disk.PartitionStat { return parts } -// return disk usage as MiB -// TODO different function for all disks / a specified disk? +// return disk usage as GiB +// +// TODO different functions for all disks / a specified disk? +// FIXME Wrong Disk values for docker !!! (probably because counting also virtual mountpoints?) func GetDiskUsage() map[string]float64 { var totalSize, freeSize, usedSize uint64 for _, part := range GetDisks() { @@ -52,3 +55,17 @@ func GetDiskUsage() map[string]float64 { "used": utils.Byte2GiB(usedSize), } } + +// return memory usage as GiB +// +// TODO also implement swap usage +func GetMemoryUsage() map[string]float64 { + v, _ := mem.VirtualMemory() + return map[string]float64{ + "total": utils.Byte2GiB(v.Total), + "free": utils.Byte2GiB(v.Free), + "used": utils.Byte2GiB(v.Used), + "avaliable": utils.Byte2GiB(v.Available), + "cached": utils.Byte2GiB(v.Cached), + } +} diff --git a/pkg/utils/main.go b/pkg/utils/main.go index b39561f..70cb632 100644 --- a/pkg/utils/main.go +++ b/pkg/utils/main.go @@ -3,6 +3,9 @@ package utils import ( "log" "os" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func Byte2String(arr []int8) string { @@ -55,3 +58,8 @@ func OpenLogFile(path string) *os.File { } return f } + +// Makes first character uppercase of given English string +func FirstUpperEN(str string) string { + return cases.Title(language.English).String(str) +} // TODO cases.NoLower vs cases.Compact ! diff --git a/plugins/resources/main.go b/plugins/resources/main.go index ec9aadd..84bc17d 100644 --- a/plugins/resources/main.go +++ b/plugins/resources/main.go @@ -1,13 +1,15 @@ package main import ( - "fmt" "runtime" "git.aliberksandikci.com.tr/Liderahenk/ahenk-go/pkg/osinfo" ) -type greeting string +type plug string + +// exported plugin Symbol +var ResourcesConnect plug // return instant resource usage information func ResourceUsage() map[string]string { @@ -31,28 +33,33 @@ func ResourceUsage() map[string]string { } // return general Agent information (that changes rarely) -func (g greeting) AgentInfo() map[string]interface{} { +func (p plug) AgentInfo() map[string]interface{} { data := map[string]interface{}{ - "System": runtime.GOOS, "Release": osinfo.GetKernelInfo()["Release"], + "System": runtime.GOOS, "Kernel": osinfo.GetKernelInfo()["Release"], + "hostname": osinfo.GetKernelInfo()["Hostname"], + "osMachine": osinfo.GetKernelInfo()["Machine"], + "diskTotal": osinfo.GetDiskUsage()["total"], + "diskUsed": osinfo.GetDiskUsage()["used"], + "diskFree": osinfo.GetDiskUsage()["free"], + "diskUsage": osinfo.GetDiskUsage()["used"] / osinfo.GetDiskUsage()["total"], + "memoryTotal": osinfo.GetMemoryUsage()["total"], + "memoryFree": osinfo.GetMemoryUsage()["free"], + "memoryAvaliable": osinfo.GetMemoryUsage()["avaliable"], + "memoryUsed": osinfo.GetMemoryUsage()["used"], + "memoryCached": osinfo.GetMemoryUsage()["cached"], + "memoryUsage": (osinfo.GetMemoryUsage()["used"] + osinfo.GetMemoryUsage()["cached"]) / osinfo.GetMemoryUsage()["total"], //REVIEW just used/total ? + + // TODO is calling all functions one by one slow downs code? + // TODO 'agentVersion': self.get_agent_version(), - "hostname": osinfo.GetKernelInfo()["Hostname"], // TODO 'ipAddresses': str(self.Hardware.Network.ip_addresses()).replace('[', '').replace(']', ''), - // "osName": osinfo.GetKernelInfo()["Sysname"], // TODO is it necessary? - // "osNodeName": osinfo.GetKernelInfo()["NodeName"],// TODO is it necessary? - // "osVersion": osinfo.GetKernelInfo()["Version"], // TODO is it necessary? - "osMachine": osinfo.GetKernelInfo()["Machine"], - // TODO get distrubition name also (pardus,arch,debian,windows10 for example ...) + // TODO get distrubition name also (pardus,arch,debian,windows10 for example ...) // TODO 'macAddresses': str(self.Hardware.Network.mac_addresses()).replace('[', '').replace(']', ''), // TODO 'hardware.systemDefinitions': self.Hardware.system_definitions(), // TODO 'hardware.monitors': self.Hardware.monitors(), // TODO 'hardware.screens': self.Hardware.screens(), // TODO 'hardware.usbDevices': self.Hardware.usb_devices(), // TODO 'hardware.printers': self.Hardware.printers(), - "diskTotal": osinfo.GetDiskUsage()["total"], - "diskUsed": osinfo.GetDiskUsage()["used"], - "diskFree": osinfo.GetDiskUsage()["free"], - // TODO "diskUsage": osinfo.GetDiskUsage()["Usage"], - // TODO 'memory': self.Hardware.Memory.total(), // TODO 'Device': device, } return data @@ -61,23 +68,9 @@ func (g greeting) AgentInfo() map[string]interface{} { func Info() map[string]string { inf := make(map[string]string) inf["name"] = "resources" - inf["version"] = "0.0.1" + inf["version"] = "0.0.2" inf["support"] = "debian" inf["description"] = "Resource Usage Information and Controls" inf["developer"] = "asandikci@aliberksandikci.com.tr" - - // inf["task"] = "true" - // inf["user_oriented"] = "false" - // inf["machine_oriented"] = "false" return inf } - -func (g greeting) Greet() { - fmt.Println("Hello Universe") -} -func (g greeting) Myvar() { - fmt.Println("I am here") -} - -// this is exported -var Greeter greeting