initial modifications, needs testing
This commit is contained in:
14
extension/.gitignore
vendored
Normal file
14
extension/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
*.pbo
|
||||
*.bak
|
||||
*.dll
|
||||
|
||||
*.so
|
||||
|
||||
RangerMetrics.h
|
||||
|
||||
RangerMetrics_x64.h
|
||||
|
||||
*.log
|
||||
|
||||
settings.json
|
||||
!*.example
|
||||
15
extension/@RangerMetrics/settings.json.example
Normal file
15
extension/@RangerMetrics/settings.json.example
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"influxdb": {
|
||||
"host": "http://host:8086",
|
||||
"token": "",
|
||||
"org": "ranger-metrics"
|
||||
},
|
||||
"timescaledb": {
|
||||
"connectionUrl": "postgresql://postgres:password@host:5432",
|
||||
"databaseName": "ranger_metrics",
|
||||
"description": "TimescaleDB is an open-source time-series database built on Postgres. Please leave the final section of the connection URL as 'postgres' as this is the maintenance database name. The extension will connect here first and create the database with desired name, then shift connections to the newly created database to create the schema and conduct write operations."
|
||||
},
|
||||
"arma3": {
|
||||
"refreshRateMs": 1000
|
||||
}
|
||||
}
|
||||
25
extension/README.md
Normal file
25
extension/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Arma-Influx
|
||||
|
||||
A3 extension for sending metrics to InfluxDB using Golang
|
||||
|
||||
## Build
|
||||
|
||||
### Windows
|
||||
|
||||
_Requires Go 1.20.1+ & MinGW_
|
||||
|
||||
```powershell
|
||||
$ENV:GOARCH = "amd64"
|
||||
$ENV:CGO_ENABLED = 1
|
||||
go build -o RangerMetrics_x64.dll -buildmode=c-shared .
|
||||
```
|
||||
|
||||
To validate exported functions:
|
||||
|
||||
```powershell
|
||||
. "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\dumpbin.exe" /exports .\RangerMetrics_x64.dll
|
||||
```
|
||||
|
||||
## Notes
|
||||
>
|
||||
> See more: <https://github.com/code34/armago_x64>
|
||||
61
extension/RVExtension.c
Normal file
61
extension/RVExtension.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "extensionCallback.h"
|
||||
|
||||
extern void goRVExtension(char *output, size_t outputSize, char *input);
|
||||
extern void goRVExtensionVersion(char *output, size_t outputSize);
|
||||
extern void goRVExtensionArgs(char *output, size_t outputSize, char *input, char **argv, int argc);
|
||||
extern void goRVExtensionRegisterCallback(extensionCallback fnc);
|
||||
|
||||
#ifdef WIN64
|
||||
__declspec(dllexport) void RVExtension(char *output, size_t outputSize, char *input)
|
||||
{
|
||||
goRVExtension(output, outputSize, input);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void RVExtensionVersion(char *output, size_t outputSize)
|
||||
{
|
||||
goRVExtensionVersion(output, outputSize);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void RVExtensionArgs(char *output, size_t outputSize, char *input, char **argv, int argc)
|
||||
{
|
||||
goRVExtensionArgs(output, outputSize, input, argv, argc);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void RVExtensionRegisterCallback(extensionCallback fnc)
|
||||
{
|
||||
goRVExtensionRegisterCallback(fnc);
|
||||
}
|
||||
#else
|
||||
__declspec(dllexport) void __stdcall _RVExtension(char *output, size_t outputSize, char *input)
|
||||
{
|
||||
goRVExtension(output, outputSize, input);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall _RVExtensionVersion(char *output, size_t outputSize)
|
||||
{
|
||||
goRVExtensionVersion(output, outputSize);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall _RVExtensionArgs(char *output, size_t outputSize, char *input, char **argv, int argc)
|
||||
{
|
||||
goRVExtensionArgs(output, outputSize, input, argv, argc);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void __stdcall _RVExtensionRegisterCallback(extensionCallback fnc)
|
||||
{
|
||||
goRVExtensionRegisterCallback(fnc);
|
||||
}
|
||||
#endif
|
||||
// do this for all the other exported functions
|
||||
|
||||
// dll entrypoint
|
||||
// Path: RVExtension.c
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
568
extension/arma.go
Normal file
568
extension/arma.go
Normal file
@@ -0,0 +1,568 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "extensionCallback.h"
|
||||
*/
|
||||
import "C" // This is required to import the C code
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
// declare list of functions available for call
|
||||
var AVAILABLE_FUNCTIONS = map[string]interface{}{
|
||||
"initExtension": initExtension,
|
||||
"deinitExtension": deinitExtension,
|
||||
"loadSettings": loadSettings,
|
||||
"connectToInflux": connectToInflux,
|
||||
"writeToInflux": writeToInflux,
|
||||
"getDir": getDir,
|
||||
"sanitizeLineProtocol": sanitizeLineProtocol,
|
||||
"version": version,
|
||||
"getUnixTimeNano": getUnixTimeNano,
|
||||
}
|
||||
|
||||
var EXTENSION_VERSION string = "0.0.3"
|
||||
var extensionCallbackFnc C.extensionCallback
|
||||
|
||||
type ServerPollSetting struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ServerOnly bool `json:"serverOnly"`
|
||||
IntervalMs int `json:"intervalMs"`
|
||||
Bucket string `json:"bucket"`
|
||||
Measurement string `json:"measurement"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
var ServerPollSettingProperties []string = []string{
|
||||
"Name",
|
||||
"Enabled",
|
||||
"ServerOnly",
|
||||
"IntervalMs",
|
||||
"Bucket",
|
||||
"Measurement",
|
||||
"Description",
|
||||
}
|
||||
|
||||
type CBAEventHandler struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
var CBAEventHandlerProperties []string = []string{
|
||||
"Name",
|
||||
"Enabled",
|
||||
"Description",
|
||||
}
|
||||
|
||||
type settingsJson struct {
|
||||
Influx struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
Token string `json:"token"`
|
||||
Org string `json:"org"`
|
||||
} `json:"influxdb"`
|
||||
Arma3 struct {
|
||||
RefreshRateMs int `json:"refreshRateMs"`
|
||||
Debug bool `json:"debug"`
|
||||
} `json:"arma3"`
|
||||
RecordingSettings map[string]interface{} `json:"recordingSettings"`
|
||||
}
|
||||
|
||||
var activeSettings settingsJson
|
||||
|
||||
// InfluxDB variables
|
||||
var InfluxClient influxdb2.Client
|
||||
|
||||
// file paths
|
||||
var ADDON_FOLDER string = getDir() + "\\@RangerMetrics"
|
||||
var LOG_FILE string = ADDON_FOLDER + "\\rangermetrics.log"
|
||||
var SETTINGS_FILE string = ADDON_FOLDER + "\\settings.json"
|
||||
var SETTINGS_FILE_EXAMPLE string = ADDON_FOLDER + "\\settings.example.json"
|
||||
|
||||
var BACKUP_FILE_PATH string = ADDON_FOLDER + "\\local_backup.log.gzip"
|
||||
var BACKUP_WRITER *gzip.Writer
|
||||
|
||||
// configure log output
|
||||
func init() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
// log to file
|
||||
f, err := os.OpenFile(LOG_FILE, 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))
|
||||
// log only to file
|
||||
log.SetOutput(f)
|
||||
}
|
||||
|
||||
func deinitExtension() {
|
||||
functionName := "deinitExtension"
|
||||
writeLog(functionName, `Deinitializing RangerMetrics extension"`, "INFO")
|
||||
|
||||
if InfluxClient != nil {
|
||||
InfluxClient.Close()
|
||||
}
|
||||
|
||||
writeLog(functionName, `Influx connection closed.`, "INFO")
|
||||
}
|
||||
|
||||
// func RVExtensionContext(output *C.char, argc *C.int) {
|
||||
|
||||
// }
|
||||
|
||||
func initExtension() {
|
||||
deinitExtension()
|
||||
|
||||
// load settings
|
||||
loadSettings()
|
||||
|
||||
var err error
|
||||
|
||||
// connect to Influx
|
||||
InfluxClient, err = connectToInflux()
|
||||
if err != nil {
|
||||
writeLog("initExtension", fmt.Sprintf(`Error connecting to Influx: %s`, err), "ERROR")
|
||||
} else {
|
||||
writeLog("initExtension", `Influx connection established.`, "INFO")
|
||||
}
|
||||
|
||||
writeLog("extensionReady", `RangerMetrics extension ready.`, "INFO")
|
||||
|
||||
// show version
|
||||
version()
|
||||
}
|
||||
|
||||
func version() {
|
||||
functionName := "version"
|
||||
writeLog(functionName, fmt.Sprintf(`RangerMetrics Extension Version:%s`, EXTENSION_VERSION), "INFO")
|
||||
}
|
||||
|
||||
// return db client and error
|
||||
func connectToInflux() (influxdb2.Client, error) {
|
||||
|
||||
// create backup writer
|
||||
if BACKUP_WRITER == nil {
|
||||
writeLog("connectToInflux", `Creating backup file`, "INFO")
|
||||
file, err := os.Open(BACKUP_FILE_PATH)
|
||||
if err != nil {
|
||||
writeLog("connectToInflux", `Error opening backup file`, "ERROR")
|
||||
}
|
||||
BACKUP_WRITER = gzip.NewWriter(file)
|
||||
if err != nil {
|
||||
writeLog("connectToInflux", `Error creating gzip writer`, "ERROR")
|
||||
}
|
||||
}
|
||||
|
||||
if activeSettings.Influx.Host == "" ||
|
||||
activeSettings.Influx.Host == "http://host:8086" {
|
||||
|
||||
return nil, errors.New("influxConnectionSettings.Host is empty")
|
||||
// writeLog("connectToInflux", `["Creating backup file", "INFO"]`)
|
||||
// file, err := os.Open(BACKUP_FILE_PATH)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// writeLog("connectToInflux", `["Error opening backup file", "ERROR"]`)
|
||||
// }
|
||||
// BACKUP_WRITER = gzip.NewWriter(file)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// writeLog("connectToInflux", `["Error creating gzip writer", "ERROR"]`)
|
||||
// }
|
||||
// return "Error connecting to Influx. Using local backup"
|
||||
}
|
||||
|
||||
if activeSettings.Influx.Enabled == false {
|
||||
return nil, errors.New("influxConnectionSettings.Enabled is false")
|
||||
}
|
||||
|
||||
InfluxClient := influxdb2.NewClientWithOptions(activeSettings.Influx.Host, activeSettings.Influx.Token, influxdb2.DefaultOptions().SetBatchSize(2500).SetFlushInterval(1000))
|
||||
|
||||
if InfluxClient == nil {
|
||||
return nil, errors.New("InfluxClient is nil")
|
||||
}
|
||||
|
||||
return InfluxClient, nil
|
||||
}
|
||||
|
||||
func writeToInflux(a3DataRaw *[]string) string {
|
||||
|
||||
var err error
|
||||
|
||||
// convert to string array
|
||||
a3Data := *a3DataRaw
|
||||
|
||||
if !activeSettings.Influx.Enabled {
|
||||
return "InfluxDB is disabled"
|
||||
}
|
||||
|
||||
if InfluxClient == nil {
|
||||
InfluxClient, err = connectToInflux()
|
||||
if err != nil {
|
||||
InfluxClient = nil
|
||||
return fmt.Sprintf(`Error connecting to InfluxDB: %v`, err)
|
||||
}
|
||||
}
|
||||
|
||||
MIN_PARAMS_COUNT := 1
|
||||
|
||||
var logData string
|
||||
functionName := "writeToInflux"
|
||||
|
||||
if len(a3Data) < MIN_PARAMS_COUNT {
|
||||
logData = fmt.Sprintf(`Not all parameters present (got %d, expected at least %d)`, len(a3Data), MIN_PARAMS_COUNT)
|
||||
writeLog(functionName, logData, "ERROR")
|
||||
return logData
|
||||
}
|
||||
|
||||
// use custom bucket or default
|
||||
var bucket string = fixEscapeQuotes(trimQuotes(string(a3Data[0])))
|
||||
|
||||
// Get non-blocking write client
|
||||
WRITE_API := InfluxClient.WriteAPI(activeSettings.Influx.Org, bucket)
|
||||
|
||||
if WRITE_API == nil {
|
||||
// writeLog(functionName, "Error creating write API", "ERROR")
|
||||
// return logData
|
||||
} else {
|
||||
// Get errors channel
|
||||
errorsCh := WRITE_API.Errors()
|
||||
go func() {
|
||||
for writeErr := range errorsCh {
|
||||
writeLog(functionName, fmt.Sprintf(`Error parsing line protocol: %s`, writeErr.Error()), "ERROR")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// now we have our write client, we'll go through the rest of the receive array items in line protocol format and write them to influx (should only be one line per call)
|
||||
|
||||
var p string = fixEscapeQuotes(trimQuotes(string(a3Data[1])))
|
||||
|
||||
// write the line to influx or backup
|
||||
if WRITE_API != nil {
|
||||
WRITE_API.WriteRecord(p)
|
||||
writeLog(functionName, fmt.Sprintf(`Wrote %d lines to influx`, len(a3Data)-1), "DEBUG")
|
||||
} else {
|
||||
// append backup line to file if BACKUP_WRITER is set
|
||||
if BACKUP_WRITER != nil {
|
||||
_, err = BACKUP_WRITER.Write([]byte(p + "\n"))
|
||||
if err != nil {
|
||||
writeLog(functionName, fmt.Sprintf(`Error writing to backup file: %s`, err), "ERROR")
|
||||
} else {
|
||||
writeLog(functionName, fmt.Sprintf(`Wrote %d lines to backup file`, len(a3Data)-1), "DEBUG")
|
||||
}
|
||||
} else {
|
||||
writeLog(functionName, `BACKUP_WRITER is nil`, "ERROR")
|
||||
}
|
||||
}
|
||||
|
||||
return "Success"
|
||||
}
|
||||
|
||||
// sanitize line protocol for influx
|
||||
func sanitizeLineProtocol(line string) string {
|
||||
// replace all spaces with underscores
|
||||
// line = strings.ReplaceAll(line, ` `, `\ `)
|
||||
// replace all commas with underscores
|
||||
// line = strings.ReplaceAll(line, `,`, `\,`)
|
||||
// replace all equals with underscores
|
||||
// line = strings.ReplaceAll(line, "=", "_")
|
||||
// replace all quotes with underscores
|
||||
// line = strings.ReplaceAll(line, "\"", "_")
|
||||
|
||||
return line
|
||||
}
|
||||
|
||||
func getDir() string {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// return true if the program should continue
|
||||
func loadSettings() (settingsJson, error) {
|
||||
functionName := "loadSettings"
|
||||
writeLog(functionName, fmt.Sprintf(`ADDON_FOLDER: %s`, ADDON_FOLDER), "DEBUG")
|
||||
writeLog(functionName, fmt.Sprintf(`LOG_FILE: %s`, LOG_FILE), "DEBUG")
|
||||
writeLog(functionName, fmt.Sprintf(`SETTINGS_FILE: %s`, SETTINGS_FILE), "DEBUG")
|
||||
|
||||
settings := settingsJson{}
|
||||
|
||||
// print the current working directory
|
||||
var file *os.File
|
||||
var err error
|
||||
// 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_FILE); os.IsNotExist(err) {
|
||||
// file does not exist
|
||||
writeLog(
|
||||
functionName,
|
||||
fmt.Sprintf(`%s does not exist`, SETTINGS_FILE),
|
||||
"WARN",
|
||||
)
|
||||
// copy settings.json.example to settings.json
|
||||
// load contents
|
||||
fileContents, err := ioutil.ReadFile(SETTINGS_FILE_EXAMPLE)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
// write contents to settings.json
|
||||
err = ioutil.WriteFile(SETTINGS_FILE, fileContents, 0644)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
// Exit false to discontinue initialization since settings are defaults
|
||||
writeLog(functionName, `CREATED SETTINGS`, "INFO")
|
||||
// return a new error
|
||||
return settings, errors.New("settings.json does not exist")
|
||||
} else {
|
||||
// file exists
|
||||
writeLog(functionName, `settings.json found`, "DEBUG")
|
||||
// read the file
|
||||
file, err = os.Open(SETTINGS_FILE)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
defer file.Close()
|
||||
decoder := json.NewDecoder(file)
|
||||
err = decoder.Decode(&settings)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
// send contents of settings file
|
||||
// get the file contents
|
||||
fileContents, err := ioutil.ReadFile(SETTINGS_FILE)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
// compact the json
|
||||
var jsonStr bytes.Buffer
|
||||
err = json.Compact(&jsonStr, fileContents)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
writeLog(
|
||||
"loadSettingsJSON",
|
||||
jsonStr.String(),
|
||||
"DEBUG",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
|
||||
return C.runExtensionCallback(extensionCallbackFnc, name, function, data)
|
||||
}
|
||||
|
||||
//export goRVExtensionVersion
|
||||
func goRVExtensionVersion(output *C.char, outputsize C.size_t) {
|
||||
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) {
|
||||
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))
|
||||
}
|
||||
|
||||
var temp string
|
||||
temp = fmt.Sprintf("Function: %s nb params: %d params: %s!", C.GoString(input), argc, out)
|
||||
|
||||
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 writeToInflux(&out)
|
||||
// temp = fmt.Sprintf("Function: %s nb params: %d", C.GoString(input), argc)
|
||||
temp = "WRITE"
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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 getUnixTimeNano() int64 {
|
||||
// get the current unix timestamp in nanoseconds
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
|
||||
func trimQuotes(s string) string {
|
||||
// trim the start and end quotes from a string
|
||||
return strings.Trim(s, `"`)
|
||||
}
|
||||
|
||||
func fixEscapeQuotes(s string) string {
|
||||
// fix the escape quotes in a string
|
||||
return strings.Replace(s, `""`, `"`, -1)
|
||||
}
|
||||
|
||||
func writeLog(functionName string, data string, level string) {
|
||||
if activeSettings.Arma3.Debug && level == "DEBUG" {
|
||||
|
||||
} else if level != "DEBUG" {
|
||||
log.Println(data)
|
||||
}
|
||||
|
||||
if extensionCallbackFnc != nil {
|
||||
// replace double quotes with 2 double quotes
|
||||
escapedData := strings.Replace(data, `"`, `""`, -1)
|
||||
// do the same for single quotes
|
||||
escapedData = strings.Replace(escapedData, `'`, `''`, -1)
|
||||
a3Message := fmt.Sprintf(`["%s", "%s"]`, escapedData, level)
|
||||
|
||||
statusName := C.CString("RangerMetrics")
|
||||
defer C.free(unsafe.Pointer(statusName))
|
||||
statusFunction := C.CString(functionName)
|
||||
defer C.free(unsafe.Pointer(statusFunction))
|
||||
statusParam := C.CString(a3Message)
|
||||
defer C.free(unsafe.Pointer(statusParam))
|
||||
runExtensionCallback(statusName, statusFunction, statusParam)
|
||||
}
|
||||
}
|
||||
|
||||
//export goRVExtension
|
||||
func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {
|
||||
|
||||
var temp string
|
||||
|
||||
// writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "DEBUG"]`, C.GoString(input)), true)
|
||||
|
||||
switch C.GoString(input) {
|
||||
case "version":
|
||||
temp = EXTENSION_VERSION
|
||||
case "getDir":
|
||||
temp = getDir()
|
||||
case "getUnixTimeNano":
|
||||
time := getUnixTimeNano()
|
||||
temp = fmt.Sprintf(`["%s"]`, strconv.FormatInt(time, 10))
|
||||
|
||||
default:
|
||||
// check if input is in AVAILABLE_FUNCTIONS
|
||||
// if not, return error
|
||||
// if yes, continue
|
||||
if _, ok := AVAILABLE_FUNCTIONS[C.GoString(input)]; !ok {
|
||||
temp = fmt.Sprintf(`["Function: %s not found!", "ERROR"]`, C.GoString(input))
|
||||
} else {
|
||||
// call the function by name
|
||||
go reflect.ValueOf(AVAILABLE_FUNCTIONS[C.GoString(input)]).Call([]reflect.Value{})
|
||||
temp = fmt.Sprintf(`["Function: %s called successfully", "DEBUG"]`, C.GoString(input))
|
||||
}
|
||||
}
|
||||
|
||||
// switch C.GoString(input) {
|
||||
// case "version":
|
||||
// writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "INFO"]`, C.GoString(input)))
|
||||
// temp = EXTENSION_VERSION
|
||||
// case "getDir":
|
||||
// writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "INFO"]`, C.GoString(input)))
|
||||
// temp = getDir()
|
||||
// case "loadSettings":
|
||||
// writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "INFO"]`, C.GoString(input)))
|
||||
// cwd, result, influxHost, timescaleUrl := loadSettings()
|
||||
// log.Println("CWD:", cwd)
|
||||
// log.Println("RESULT:", result)
|
||||
// log.Println("INFLUX HOST:", influxHost)
|
||||
// log.Println("TIMESCALE URL:", timescaleUrl)
|
||||
// if result != "" {
|
||||
// writeLog("goRVExtension", result)
|
||||
// temp = fmt.Sprintf(
|
||||
// `["%s", "%s", "%s", "%d"]`,
|
||||
// EXTENSION_VERSION,
|
||||
// influxConnectionSettings.Host,
|
||||
// influxConnectionSettings.Org,
|
||||
// a3Settings.RefreshRateMs,
|
||||
// )
|
||||
// }
|
||||
// case "connectToInflux":
|
||||
// // writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "INFO"]`, C.GoString(input)))
|
||||
// go connectToInflux()
|
||||
// temp = `["Connecting to InfluxDB", "INFO"]`
|
||||
// case "connectToTimescale":
|
||||
// // writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "INFO"]`, C.GoString(input)))
|
||||
// go connectToTimescale()
|
||||
// temp = `["Connecting to TimescaleDB", "INFO"]`
|
||||
// case "getUnixTimeNano":
|
||||
// temp = fmt.Sprintf(`["%d", "INFO"]`, getUnixTimeNano())
|
||||
// case "deinitialize":
|
||||
// writeLog("goRVExtension", fmt.Sprintf(`["Input: %s", "INFO"]`, C.GoString(input)))
|
||||
// deinitialize()
|
||||
// temp = `["Deinitializing", "INFO"]`
|
||||
// default:
|
||||
// temp = fmt.Sprintf(`["Unknown command: %s", "ERROR"]`, 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
|
||||
}
|
||||
|
||||
//export goRVExtensionRegisterCallback
|
||||
func goRVExtensionRegisterCallback(fnc C.extensionCallback) {
|
||||
extensionCallbackFnc = fnc
|
||||
}
|
||||
|
||||
func main() {}
|
||||
5
extension/build.txt
Normal file
5
extension/build.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
$ENV:GOARCH = "amd64"
|
||||
$ENV:CGO_ENABLED = 1
|
||||
|
||||
go1.16.4 build -o RangerMetrics_x64.dll -buildmode=c-shared .
|
||||
go1.16.4 build -o ../@RangerMetrics/RangerMetrics_x64.dll -buildmode=c-shared .
|
||||
11
extension/extensionCallback.h
Normal file
11
extension/extensionCallback.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int (*extensionCallback)(char const *name, char const *function, char const *data);
|
||||
|
||||
/* https://golang.org/cmd/cgo/#hdr-C_references_to_Go */
|
||||
static inline int runExtensionCallback(extensionCallback fnc, char const *name, char const *function, char const *data)
|
||||
{
|
||||
return fnc(name, function, data);
|
||||
}
|
||||
5
extension/go.mod
Normal file
5
extension/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module main
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/influxdata/influxdb-client-go/v2 v2.6.0
|
||||
84
extension/go.sum
Normal file
84
extension/go.sum
Normal file
@@ -0,0 +1,84 @@
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
|
||||
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.6.0 h1:bIOaGTgvvv1Na2hG+nIvqyv7PK2UiU2WrJN1ck1ykyM=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.6.0/go.mod h1:Y/0W1+TZir7ypoQZYd2IrnVOKB3Tq6oegAQeSVN/+EU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
12
extension/makefile
Normal file
12
extension/makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
build:
|
||||
export GOARCH="amd64"
|
||||
export CGO_ENABLED=1
|
||||
go build -o RangerMetrics_x64.so -buildmode=c-shared .
|
||||
|
||||
export GOARCH = "386"
|
||||
export CGO_ENABLED = 1
|
||||
go build -o RangerMetrics.dll -buildmode=c-shared .
|
||||
|
||||
export GOARCH = "amd64"
|
||||
export CGO_ENABLED = 1
|
||||
go build -o RangerMetrics_x64.dll -buildmode=c-shared .
|
||||
26
extension/testsqf.sqf
Normal file
26
extension/testsqf.sqf
Normal file
@@ -0,0 +1,26 @@
|
||||
freeExtension "RangerMetrics";
|
||||
// sleep 0.5;
|
||||
// "RangerMetrics" callExtension "loadSettings";
|
||||
// "RangerMetrics" callExtension "version";
|
||||
// "RangerMetrics" callExtension "connectToInflux";
|
||||
// "RangerMetrics" callExtension "connectToTimescale";
|
||||
// sleep 5;
|
||||
// "RangerMetrics" callExtension "initTimescale";
|
||||
|
||||
// addMissionEventHandler ["ExtensionCallback", {
|
||||
// params ["_extension", "_function", "_data"];
|
||||
// if (
|
||||
// _extension == "RangerMetrics" && _function isEqualTo "connectToTimescale"
|
||||
// ) then {
|
||||
// diag_log format ["RangerMetrics: %1", _data];
|
||||
// };
|
||||
// }];
|
||||
"RangerMetrics" callExtension "deinitExtension";
|
||||
sleep 1;
|
||||
"RangerMetrics" callExtension "initExtension";
|
||||
|
||||
sleep 20;
|
||||
"RangerMetrics" callExtension "deinitExtension";
|
||||
// freeExtension "RangerMetrics";
|
||||
sleep 5;
|
||||
exit;
|
||||
Reference in New Issue
Block a user