feat: implement plugin structure

- Plugin Manager
- Plugin Opener
- Code refactoring
- Memory usage with gopsutil/mem
This commit is contained in:
Aliberk Sandıkçı 2023-08-24 17:44:01 +03:00
parent 73c6ceac7f
commit b3fcfc2f06
Signed by: asandikci
GPG key ID: 25C67A03B5666BC1
8 changed files with 131 additions and 103 deletions

View file

@ -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)
}

View file

@ -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() {

View file

@ -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
}

1
go.mod
View file

@ -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 (

2
go.sum
View file

@ -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=

View file

@ -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),
}
}

View file

@ -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 !

View file

@ -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