small reorg, some improvements, readme updates
- fix hemtt config to copy example
- add measurements table in Readme
- ENTITY COUNTS
- re add side tags
- use `side group _x` for units and players
- fix players_connected and add headless_clients
- get `_allUserInfos = allUsers apply {getUserInfo _x} select {count _x > 0};` from main loop and use in entity counts and player performance
This commit is contained in:
123
extension/IFXMetrics/internal/influx/influx.go
Normal file
123
extension/IFXMetrics/internal/influx/influx.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package influx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/indig0fox/IFXMetrics/internal/logger"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||
"github.com/influxdata/influxdb-client-go/v2/domain"
|
||||
"github.com/kataras/iris/v12/x/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var InfluxClient influxdb2.Client
|
||||
var Connected bool = false
|
||||
var activeSettings *viper.Viper
|
||||
|
||||
var writeAPIs = map[string]api.WriteAPI{}
|
||||
var writeAPIErrorChannels = map[string]<-chan error{}
|
||||
|
||||
func Setup(
|
||||
settings *viper.Viper,
|
||||
) error {
|
||||
activeSettings = settings
|
||||
// create influx client
|
||||
InfluxClient = influxdb2.NewClientWithOptions(
|
||||
activeSettings.GetString("influxdb.host"),
|
||||
activeSettings.GetString("influxdb.token"),
|
||||
influxdb2.DefaultOptions().SetFlushInterval(1000),
|
||||
)
|
||||
|
||||
_, err := InfluxClient.Ping(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Connected = true
|
||||
|
||||
// start error handler
|
||||
go func() {
|
||||
for {
|
||||
for bucket, err := range writeAPIErrorChannels {
|
||||
thisLog := logger.FileOnly.With().
|
||||
Str("component", "influx").
|
||||
Str("bucket", bucket).
|
||||
Logger()
|
||||
|
||||
select {
|
||||
case e := <-err:
|
||||
thisLog.Error().Msg(e.Error())
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteLine(
|
||||
bucket string,
|
||||
measurement string,
|
||||
tags map[string]string,
|
||||
fields map[string]interface{},
|
||||
) error {
|
||||
|
||||
if !Connected {
|
||||
return errors.New("influxdb not connected")
|
||||
}
|
||||
|
||||
if InfluxClient == nil {
|
||||
return errors.New("influxdb client not initialized")
|
||||
}
|
||||
|
||||
// check if bucket exists
|
||||
b := InfluxClient.BucketsAPI()
|
||||
_, err := b.FindBucketByName(context.Background(), bucket)
|
||||
if err != nil {
|
||||
// get organization
|
||||
org, err := InfluxClient.OrganizationsAPI().FindOrganizationByName(
|
||||
context.Background(),
|
||||
activeSettings.GetString("influxdb.org"),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
// create bucket
|
||||
bucket, err := b.CreateBucketWithName(
|
||||
context.Background(),
|
||||
org,
|
||||
bucket,
|
||||
domain.RetentionRule{EverySeconds: 0},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
logger.Log.Info().Msgf("created bucket %s", bucket.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
if writeAPIs[bucket] == nil {
|
||||
writeAPIs[bucket] = InfluxClient.WriteAPI(
|
||||
activeSettings.GetString("influxdb.org"),
|
||||
bucket,
|
||||
)
|
||||
writeAPIErrorChannels[bucket] = writeAPIs[bucket].Errors()
|
||||
}
|
||||
|
||||
p := influxdb2.NewPoint(
|
||||
measurement,
|
||||
tags,
|
||||
fields,
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
writeAPIs[bucket].WritePoint(p)
|
||||
|
||||
return nil
|
||||
}
|
||||
129
extension/IFXMetrics/internal/logger/logger.go
Normal file
129
extension/IFXMetrics/internal/logger/logger.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/indig0fox/a3go/a3interface"
|
||||
"github.com/rs/zerolog"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var ll *lumberjack.Logger
|
||||
var armaWriter *armaIoWriter
|
||||
|
||||
var Log, FileOnly, ArmaOnly zerolog.Logger
|
||||
var ActiveOptions *LoggerOptionsType = &LoggerOptionsType{}
|
||||
|
||||
type LoggerOptionsType struct {
|
||||
// LogPath is the path to the log file
|
||||
Path string
|
||||
// LogAddonName is the name of the addon that will be used to send log messages to arma
|
||||
AddonName string
|
||||
// LogExtensionName is the name of the extension that will be used to send log messages to arma
|
||||
ExtensionName string
|
||||
// ExtensionVersion is the version of this extension
|
||||
ExtensionVersion string
|
||||
// LogDebug determines if we should send Debug level messages to file & arma
|
||||
Debug bool
|
||||
// LogTrace is used to determine if file should receive trace level, regardless of debug
|
||||
Trace bool
|
||||
}
|
||||
|
||||
func RotateLogs() {
|
||||
ll.Rotate()
|
||||
}
|
||||
|
||||
// ArmaIoWriter is a custom type that implements the io.Writer interface and sends the output to Arma with the "log" callback
|
||||
type armaIoWriter struct{}
|
||||
|
||||
func (w *armaIoWriter) Write(p []byte) (n int, err error) {
|
||||
// write to arma log
|
||||
a3interface.WriteArmaCallback(ActiveOptions.ExtensionName, ":LOG:", string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// console writer
|
||||
func InitLoggers(o *LoggerOptionsType) {
|
||||
ActiveOptions = o
|
||||
|
||||
// create a new lumberjack file logger (adds log rotation and compression)
|
||||
ll = &lumberjack.Logger{
|
||||
Filename: ActiveOptions.Path,
|
||||
MaxSize: 5,
|
||||
MaxBackups: 10,
|
||||
MaxAge: 14,
|
||||
Compress: false,
|
||||
LocalTime: true,
|
||||
}
|
||||
|
||||
// create a new io writer using the a3go callback function
|
||||
// this will be used to write to the arma log
|
||||
armaWriter = new(armaIoWriter)
|
||||
|
||||
// create format functions for RPT log messages
|
||||
armaLogFormatLevel := func(i interface{}) string {
|
||||
return strings.ToUpper(
|
||||
fmt.Sprintf(
|
||||
"%s:",
|
||||
i,
|
||||
))
|
||||
}
|
||||
armaLogFormatTimestamp := func(i interface{}) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
FileOnly = zerolog.New(zerolog.ConsoleWriter{
|
||||
Out: ll,
|
||||
TimeFormat: time.RFC3339,
|
||||
NoColor: true,
|
||||
}).With().Timestamp().Caller().Logger()
|
||||
|
||||
if ActiveOptions.Trace {
|
||||
FileOnly = FileOnly.Level(zerolog.TraceLevel)
|
||||
} else if ActiveOptions.Debug {
|
||||
FileOnly = FileOnly.Level(zerolog.DebugLevel)
|
||||
} else {
|
||||
FileOnly = FileOnly.Level(zerolog.InfoLevel)
|
||||
}
|
||||
|
||||
ArmaOnly = zerolog.New(zerolog.ConsoleWriter{
|
||||
Out: armaWriter,
|
||||
TimeFormat: "",
|
||||
NoColor: true,
|
||||
FormatLevel: armaLogFormatLevel,
|
||||
FormatTimestamp: armaLogFormatTimestamp,
|
||||
}).With().Str("extension_version", ActiveOptions.ExtensionVersion).Logger()
|
||||
|
||||
if ActiveOptions.Debug {
|
||||
ArmaOnly = ArmaOnly.Level(zerolog.DebugLevel)
|
||||
} else {
|
||||
ArmaOnly = ArmaOnly.Level(zerolog.InfoLevel)
|
||||
}
|
||||
|
||||
// create something that can send the same message to both loggers
|
||||
// this is used to send messages to the arma log
|
||||
// and the file log
|
||||
Log = zerolog.New(zerolog.MultiLevelWriter(
|
||||
zerolog.ConsoleWriter{
|
||||
Out: ll,
|
||||
TimeFormat: time.RFC3339,
|
||||
NoColor: true,
|
||||
},
|
||||
zerolog.ConsoleWriter{
|
||||
Out: armaWriter,
|
||||
TimeFormat: "",
|
||||
NoColor: true,
|
||||
FormatTimestamp: armaLogFormatTimestamp,
|
||||
FormatLevel: armaLogFormatLevel,
|
||||
},
|
||||
)).With().Timestamp().Logger()
|
||||
|
||||
if ActiveOptions.Debug {
|
||||
Log = Log.Level(zerolog.DebugLevel)
|
||||
} else {
|
||||
Log = Log.Level(zerolog.InfoLevel)
|
||||
}
|
||||
|
||||
}
|
||||
80
extension/IFXMetrics/internal/settings/settings.go
Normal file
80
extension/IFXMetrics/internal/settings/settings.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/indig0fox/a3go/a3interface"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type CBAEventHandlerSetting struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Bucket string `json:"bucket"`
|
||||
Measurement string `json:"measurement"`
|
||||
}
|
||||
|
||||
var Active *viper.Viper
|
||||
|
||||
func init() {
|
||||
Active = viper.New()
|
||||
}
|
||||
|
||||
func Setup(
|
||||
addonFolder string,
|
||||
) error {
|
||||
Active.SetConfigName("ifxmetrics.config")
|
||||
Active.SetConfigType("json")
|
||||
Active.AddConfigPath(addonFolder)
|
||||
|
||||
armaDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Active.AddConfigPath(armaDir)
|
||||
|
||||
Active.SetDefault("influxdb", map[string]interface{}{
|
||||
"enabled": false,
|
||||
"host": "http://localhost:8086",
|
||||
"token": "",
|
||||
"org": "",
|
||||
})
|
||||
|
||||
Active.SetDefault("arma3", map[string]interface{}{
|
||||
"refreshRateMs": 2000,
|
||||
"debug": "true",
|
||||
})
|
||||
|
||||
Active.SetDefault("cbaEventHandlers", []map[string]interface{}{})
|
||||
|
||||
if err := Active.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
// Config file not found; ignore error if desired
|
||||
a3interface.WriteArmaCallback(
|
||||
"ifxmetrics",
|
||||
":LOG:",
|
||||
"WARN",
|
||||
"Config file not found; using default values.",
|
||||
)
|
||||
return nil
|
||||
} else {
|
||||
// Config file was found but another error was produced
|
||||
a3interface.WriteArmaCallback(
|
||||
"ifxmetrics",
|
||||
":LOG:",
|
||||
"ERROR",
|
||||
err.Error(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
a3interface.WriteArmaCallback(
|
||||
"ifxmetrics",
|
||||
":LOG:",
|
||||
"INFO",
|
||||
"Config file found; using values from config file.",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user