package main /* #include #include #include #include "extensionCallback.h" */ import "C" // This is required to import the C code import ( "context" "encoding/json" "fmt" "io" "log" "os" "strconv" "strings" "time" "unsafe" influxdb2 "github.com/influxdata/influxdb-client-go/v2" ) var EXTENSION_VERSION string = "0.0.1" var extensionCallbackFnc C.extensionCallback var influxConnectionSettings influxSettings // configure log output func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) // log to file f, err := os.OpenFile("rangermetrics.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening file: %v", err) } // log to console as well log.SetOutput(io.MultiWriter(f, os.Stdout)) } // func RVExtensionContext(output *C.char, argc *C.int) { // } type influxSettings struct { Host string `json:"host"` Token string `json:"token"` Org string `json:"org"` Bucket string `json:"bucket"` } func loadSettings() (dir string, result string, host string) { // print the current working directory var file *os.File var err error dir, err = os.Getwd() if err != nil { log.Fatal(err) } log.Println("getSettings", dir) // read settings from file // settings.json should be in the same directory as the .dll // see if the file exists if _, err = os.Stat("settings.json"); os.IsNotExist(err) { // file does not exist log.Println("settings.json does not exist") // create the file file, err = os.Create("settings.json") if err != nil { log.Fatal(err) } defer file.Close() // write the default settings to the file defaultSettings := influxSettings{ Host: "http://localhost:8086", Token: "my-token", Org: "my-org", Bucket: "my-bucket", } encoder := json.NewEncoder(file) err = encoder.Encode(defaultSettings) if err != nil { log.Fatal(err) } result = "settings.json created - please modify!" host = defaultSettings.Host return dir, result, host } else { // file exists log.Println("settings.json exists") // read the file file, err = os.Open("settings.json") if err != nil { log.Fatal(err) } defer file.Close() decoder := json.NewDecoder(file) err = decoder.Decode(&influxConnectionSettings) if err != nil { log.Fatal(err) } result = "settings.json read" host = influxConnectionSettings.Host } return dir, result, host } func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int { return C.runExtensionCallback(extensionCallbackFnc, name, function, data) } //aexport goRVExtensionVersion // func goRVExtensionVersion(output *C.char, outputsize C.size_t) { // return // result := C.CString(EXTENSION_VERSION) // defer C.free(unsafe.Pointer(result)) // var size = C.strlen(result) + 1 // if size > outputsize { // size = outputsize // } // C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size) // } //export goRVExtensionArgs // func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv **C.char, argc C.int) { // return // var offset = unsafe.Sizeof(uintptr(0)) // var out []string // for index := C.int(0); index < argc; index++ { // out = append(out, C.GoString(*argv)) // argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + offset)) // } // temp := fmt.Sprintf("Function: %s nb params: %d params: %s!", C.GoString(input), argc, out) // // Return a result to Arma // result := C.CString(temp) // defer C.free(unsafe.Pointer(result)) // var size = C.strlen(result) + 1 // if size > outputsize { // size = outputsize // } // if C.GoString(input) == "sendToInflux" { // // start a goroutine to send the data to influx // // param string is argv[0] which is the data to send to influx // go sendToInflux(out) // } // C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size) // // return 1 // } func callBackExample() { name := C.CString("arma") defer C.free(unsafe.Pointer(name)) function := C.CString("funcToExecute") defer C.free(unsafe.Pointer(function)) // Make a callback to Arma for i := 0; i < 3; i++ { time.Sleep(2 * time.Second) param := C.CString(fmt.Sprintf("Loop: %d", i)) defer C.free(unsafe.Pointer(param)) runExtensionCallback(name, function, param) } } func sanitize(s string) string { t := strings.ReplaceAll(s, "\"", "") return t } func sendToInflux(a3Data []string) { EXPECTED_PARAMS := 8 var logData string functionName := "sendToInflux" if len(a3Data) < EXPECTED_PARAMS { logData = fmt.Sprintf(`["Not all parameters present (got %d, expected %d)", "ERROR"]`, len(a3Data), EXPECTED_PARAMS) logLine(functionName, logData, "ERROR") return } // sanitize all elements for i, v := range a3Data { a3Data[i] = sanitize(v) } host := influxConnectionSettings.Host token := influxConnectionSettings.Token org := influxConnectionSettings.Org bucket := influxConnectionSettings.Bucket profile, locality := a3Data[0], a3Data[1] missionName, worldName, serverName := a3Data[2], a3Data[3], a3Data[4] metric := a3Data[5] valueType := a3Data[6] tags := map[string]string{ "profile": profile, "locality": locality, "worldName": worldName, "serverName": serverName, } fields := map[string]interface{}{ "missionName": missionName, // "count": value, } // parse the value var err error // allow for float or int values, but remove any auto backslashes // check if includes certain strings if strings.Contains(valueType, "float") { fields["count"], err = strconv.ParseFloat(sanitize(a3Data[7]), 64) } else if strings.Contains(valueType, "int") { fields["count"], err = strconv.Atoi(sanitize(a3Data[7])) } else { logData = fmt.Sprintf("valueType must be 'float' or 'int': %s, %s, %s", metric, valueType, a3Data[7]) logLine(functionName, logData, "ERROR") return } if err != nil { logData = fmt.Sprintf("Error parsing value: %s", err.Error()) logLine(functionName, logData, "ERROR") return } // influxDB init client := influxdb2.NewClientWithOptions(host, token, influxdb2.DefaultOptions().SetBatchSize(120)) writeAPI := client.WriteAPIBlocking(org, bucket) p := influxdb2.NewPoint(metric, tags, fields, time.Now()) // write synchronously err = writeAPI.WritePoint(context.Background(), p) if err != nil { logData = fmt.Sprintf("Error writing to InfluxDB: %s", err.Error()) logLine(functionName, logData, "ERROR") } defer client.Close() logData = fmt.Sprintf("Sent to Influx: %s, %s", metric, a3Data[7]) logLine(functionName, logData, "INFO") } func logLine(functionName string, data string, resultType string) { statusName := C.CString("RangerMetrics") defer C.free(unsafe.Pointer(statusName)) statusFunction := C.CString(functionName) defer C.free(unsafe.Pointer(statusFunction)) statusParam := C.CString(fmt.Sprintf(`["%s", "%s"]`, data, resultType)) defer C.free(unsafe.Pointer(statusParam)) runExtensionCallback(statusName, statusFunction, statusParam) log.Println(data) } //export goRVExtension func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) { temp := fmt.Sprintf("Hello %s!", C.GoString(input)) // Return a result to Arma result := C.CString(temp) defer C.free(unsafe.Pointer(result)) var size = C.strlen(result) + 1 if size > outputsize { size = outputsize } C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size) // var temp string // logLine("goRVExtension", fmt.Sprintf("Input: %s", C.GoString(input)), "INFO") // switch C.GoString(input) { // case "version": // temp = fmt.Sprintf("%s", EXTENSION_VERSION) // case "loadSettings": // cwd, result, influxHost := loadSettings() // log.Println("CWD:", cwd) // log.Println("RESULT:", result) // log.Println("INFLUX HOST:", influxHost) // if result != "" { // temp = fmt.Sprintf(`["CWD: %s", "RESULT: %s", "%s"]`, cwd, result, influxHost) // } // default: // temp = fmt.Sprintf(`["ERR", "Unknown command: %s", "ERR"]`, C.GoString(input)) // } // result := C.CString(temp) // defer C.free(unsafe.Pointer(result)) // var size = C.strlen(result) + 1 // if size > outputsize { // size = outputsize // } // C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size) // return } //aexport goRVExtensionRegisterCallback // func goRVExtensionRegisterCallback(fnc C.extensionCallback) { // return // extensionCallbackFnc = fnc // } func main() {}