15 Commits

63 changed files with 6364 additions and 488 deletions

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ extension/RangerMetrics.h
extension/RangerMetrics_x64.h extension/RangerMetrics_x64.h
\@RangerMetrics/settings.json \@RangerMetrics/settings.json
*.log

View File

@@ -1,68 +0,0 @@
Jarvis — Today at 8:23 PM
@EagleTrooper let's use this instead
to save blowing up the recruiters notifications
on recruitment
EagleTrooper — Today at 8:23 PM
Yeah keep the recruitment clean. I understand Im a recruiter / NCO / admin for the community in involved in.
Alright,
I am working on developing a dashboard that gives historical stats with our server as well as our missions that run Performance / FPS / Memory Usage / etc. During my research i came across your groups Repo and it appears that you are doing something very similar to what I wanted to do. I did read through just to get an idea unfortunately there was some gaps in the implementation and i was hoping to speak to someone about how it was done with your group as well as if they would be so kind as to provide pointers and insight so that I may implement something very similar.
Jarvis — Today at 8:26 PM
Do you know how to setup a Grafana/InfluxDB stack?
EagleTrooper — Today at 8:27 PM
Already have it setup (Background in IT and Cloud Network Engineering).
Sweetwater.I — Today at 8:27 PM
u can see our dashboard here if u wanna see what cavmetrics extension is outputting.
https://metrics.7cav.us/d/NED-gV3Mz/arma3?orgId=1&refresh=10s
Jarvis — Today at 8:28 PM
It's basically just arma -> influx -> grafana
Sweetwater.I — Today at 8:28 PM
but yeah, influxdb+grafana is what we do
Jarvis — Today at 8:28 PM
The cavmetrics addon you see is the arma extension
EagleTrooper — Today at 8:28 PM
Yep understood the workflow and that was very clear. I believe the piece im missing or dont understand is the dll creation / implementation
Jarvis — Today at 8:28 PM
If you know influx/grafana, you're 2/3rds of the way there
Just git clone the cavmetrics repo
EagleTrooper — Today at 8:29 PM
I believe it was the Arma-Influx?
That was the piece that i was not clear on. Unless i was misunderstanding it still required a compiled DLL
Jarvis — Today at 8:29 PM
the a3_influx dll is inside of the git repo
so == a DLL but linux
we run our servers on linux
EagleTrooper — Today at 8:30 PM
Unfortuantly it looks like its a .so
Sweetwater.I — Today at 8:30 PM
use the arma-influx repo to build the extension
EagleTrooper — Today at 8:30 PM
Yeah we have windows :/
Sweetwater.I — Today at 8:30 PM
cavmetrics is what we called the packaged extension.
on the repo
Jarvis — Today at 8:30 PM
https://github.com/7Cav/Arma-Influx
GitHub
GitHub - 7Cav/Arma-Influx: Arma Extension for sending metrics to In...
Arma Extension for sending metrics to InfluxDB. Contribute to 7Cav/Arma-Influx development by creating an account on GitHub.
Just compile that for dll
it's an arma ext written in go
Liber.N — Today at 8:31 PM
Cavmetrics is an arma addon thats calls to arma influx extension
Jarvis — Today at 8:31 PM
you'll need to change the makefile on that repo tho
EagleTrooper — Today at 8:32 PM
does it need to be labeld armago? or a3influx?
If that is what your referring to then it appears i do have an understanding of it and something else is missing outside of my imediate understanding and may not have anything you can provide input on. I will review the implementation again and see if I can get any further.
I do appreciate the input and help.
Jarvis — Today at 8:35 PM
no probs, it's a long road but found it worth it in the end
https://metrics.7cav.us/
Specifically - https://metrics.7cav.us/d/NED-gV3Mz/arma3?orgId=1&refresh=10s
EagleTrooper — Today at 8:36 PM
I will take a look! Keep on Keeping on and again I appreciate the friendly response!
Jarvis — Today at 8:36 PM
You'll need to update these for your influx db host - https://github.com/7Cav/cav_metrics/blob/dev/%40CavMetrics/addons/CavMetrics/functions/fn_send.sqf#L9-L12

View File

@@ -2,21 +2,84 @@ class CfgPatches {
class RangerMetrics { class RangerMetrics {
units[] = {}; units[] = {};
weapons[] = {}; weapons[] = {};
requiredVersion = 0.1; requiredVersion = 2.10;
requiredAddons[] = {}; requiredAddons[] = {};
author[] = {"EagleTrooper and Gary"}; author[] = {"EagleTrooper","Gary","IndigoFox"};
authorUrl = "http://example.com"; authorUrl = "http://example.com";
}; };
}; };
class CfgFunctions { class CfgFunctions {
class RangerMetrics_callback {
class functions {
file = "\RangerMetrics\functions\callbackHandlers";
class callbackHandler {};
class loadSettings {};
};
};
class RangerMetrics_event {
class functions {
file = "\RangerMetrics\functions\capture\EHOnly";
class ace_unconscious {};
class EntityKilled {};
class Explosion {};
class FiredMan {};
class HandleChatMessage {};
class MarkerCreated {};
class MarkerDeleted {};
class MarkerUpdated {};
class milsim_serverEfficiency {};
};
};
class RangerMetrics_cDefinitions {
class functions {
file = "\RangerMetrics\functions\captureDefinitions";
class server_poll {};
class server_missionEH {};
class client_poll {};
// class clientEvent {};
class server_CBA {};
class unit_handlers {};
};
};
class RangerMetrics_capture {
// these names represent measurement names send to InfluxDB - snake case
class functions {
file = "\RangerMetrics\functions\capture";
class entity_count {};
class mission_config_file {};
class player_identity {};
class player_performance {};
class player_status {};
class running_mission {};
class running_scripts {};
class server_performance {};
class server_time {};
class unit_inventory {};
class unit_state {};
class view_distance {};
class weather {};
};
};
class RangerMetrics { class RangerMetrics {
class Common { class core {
file = "\RangerMetrics\functions"; file = "\RangerMetrics\functions\core";
class postInit { postInit = 1;}; class postInit { postInit = 1; };
class captureLoop {};
class log {}; class log {};
class queue {};
class send {}; class send {};
class run {}; class sendClientPoll {};
class startServerPoll {};
class classHandlers {};
class initCapture {};
};
class helpers {
file = "\RangerMetrics\functions\helpers";
class toLineProtocol {};
class encodeJSON {};
class stringReplace {};
class unixTimestamp {};
}; };
}; };
}; };

View File

@@ -0,0 +1,84 @@
params ["_name", "_function", "_data"];
if !(_name == "RangerMetrics") exitWith {};
// Validate data param
if (isNil "_data") then {_data = ""};
if (_data isEqualTo "") exitWith {
[
format ["Callback empty data: %1", _function],
"WARN"
] call RangerMetrics_fnc_log;
false;
};
// Parse response from string array
private "_response";
try {
// diag_log format ["Raw callback: %1: %2", _function, _data];
if (_function find "JSON" > -1) then {
_response = [_data, 2] call CBA_fnc_parseJSON;
} else {
_response = parseSimpleArray _data;
};
} catch {
[
format ["Callback invalid data: %1: %2", _function, _data],
"WARN"
] call RangerMetrics_fnc_log;
};
switch (_function) do {
case "deinitExtension": {
// Our first call is deinitExtension. When we received a single "true" value, we can then run init processes for the extension connections.
if ((_response select 0) isEqualTo true) then {
"RangerMetrics" callExtension "initExtension";
} else {
_response call RangerMetrics_fnc_log;
};
};
case "loadSettingsJSON": {
[_function, _response] call RangerMetrics_callback_fnc_loadSettings;
};
case "loadSettings": {
// Load settings
[_function, _response] call RangerMetrics_callback_fnc_loadSettings;
};
case "extensionReady": {
// deinitialize existing captures
if (!isNil "RangerMetrics_allMEH") then {
{
private _handle = missionNamespace getVariable _x;
if (isNil "_handle") then {continue};
private _EHName = (_x splitString "_") select 2;
removeMissionEventHandler [_EHName, _handle];
missionNamespace setVariable [_x, nil];
} forEach RangerMetrics_allMEH;
};
if (!isNil "RangerMetrics_allCBA") then {
{
private _handle = missionNamespace getVariable _x;
if (isNil "_handle") then {continue};
private _EHName = (_x splitString "_") select 2;
[_EHName, _handle] call CBA_fnc_removeEventHandler;
missionNamespace setVariable [_x, nil];
} forEach RangerMetrics_allCBA;
};
if (!isNil "RangerMetrics_allServerPoll") then {
{
private _handle = missionNamespace getVariable _x;
if (isNil "_handle") then {continue};
terminate _handle;
missionNamespace setVariable [_x, nil];
} forEach RangerMetrics_allServerPoll;
};
call RangerMetrics_fnc_initCapture;
};
default {
_response call RangerMetrics_fnc_log;
};
};

View File

@@ -0,0 +1,52 @@
params ["_function", "_data"];
if (_function isEqualTo "loadSettingsJSON") exitWith {
RangerMetrics_settings = _data;
RangerMetrics_recordingSettings = _data get "recordingSettings";
RangerMetrics_debug = RangerMetrics_settings get "arma3" get "debug";
[
format [
"Settings loaded: %1",
_data
],
"INFO"
] call RangerMetrics_fnc_log;
if (isServer) then {
missionNamespace setVariable [
"RangerMetrics_serverProfileName",
profileName,
true
];
};
};
switch (_data select 0) do {
case "CREATED SETTINGS": {
[
"settings.json did not exist and has been created - you will need to update it with your own settings before the addon will initialize further.",
"ERROR"
] call RangerMetrics_fnc_log;
};
case "loadSettings": {
[
format [
"Setting loaded: %1",
_data
],
"INFO"
] call RangerMetrics_fnc_log;
};
default {
[
_data select 0,
"INFO"
] call RangerMetrics_fnc_log;
};
};

View File

@@ -0,0 +1,43 @@
if (!RangerMetrics_run) exitWith {};
params ["_killed", "_killer", "_instigator"];
if (!isPlayer _killed) exitWith {}; // only track player deaths
// check in case ACE is active and lastDamageSource has been broadcast via addLocalSoldierEH
_instigator = _unit getVariable [
"ace_medical_lastDamageSource",
_instigator
];
if (isNull _instigator) then { _instigator = UAVControl vehicle _killer select 0 }; // UAV/UGV player operated road kill
if (isNull _instigator) then { _instigator = _killer }; // player driven vehicle road kill
if (isNull _instigator) then { _instigator = _killed };
// hint format ["Killed By %1", name _instigator];
if (!isPlayer _killed && !isPlayer _instigator) exitWith {}; // only track player kills
private _tags = [];
private _fields = [];
if (getPlayerUID _instigator != "") then {
_tags pushBack ["string", "killerPlayerUID", getPlayerUID _instigator];
};
if (name _instigator != "") then {
_fields pushBack ["string", "killerName", name _instigator];
};
if (getPlayerUID _killed != "") then {
_tags pushBack ["string", "killedPlayerUID", getPlayerUID _killed];
};
if (name _killed != "") then {
_fields pushBack ["string", "killedName", name _killed];
};
[
"server_events",
"EntityKilled",
_tags,
_fields,
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,33 @@
params ["_vehicle", "_damage", "_source"];
if (isNull _vehicle) exitWith {};
private _sourceClass = "";
private _sourceDisplayName = "";
private _sourcePlayerUID = "";
if !(isNull _source) then {
private _sourceClass = typeOf _source;
private _sourceDisplayName = [configOf _source] call BIS_fnc_displayName;
if (isPlayer _source) then {
private _sourcePlayerId = getPlayerId _source;
private _sourceUserInfo = getUserInfo _sourcePlayerId;
private _sourcePlayerUID = _sourceUserInfo select 2;
} else {
private _sourcePlayerUID = "";
};
};
private _unitPlayerId = getPlayerId _vehicle;
private _userInfo = getUserInfo _unitPlayerId;
private _unitPlayerUID = _userInfo select 2;
[
"player_events",
"Explosion",
[["string", "playerUID", _unitPlayerUID]],
[
["string", "sourceClass", _sourceClass],
["string", "sourceDisplayName", _sourceDisplayName],
["string", "sourcePlayerUID", _sourcePlayerUID],
["float", "damage", _damage]
],
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,27 @@
params [
["_unit", objNull],
"_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"
];
if (isNull _unit) exitWith {};
private _unitPlayerId = getPlayerId _unit;
private _userInfo = getUserInfo _unitPlayerId;
[
"player_events",
"FiredMan",
[
["string", "playerUID", _userInfo select 2]
],
[
["string", "weapon", _weapon],
["string", "muzzle", _muzzle],
["string", "mode", _mode],
["string", "ammo", _ammo],
["string", "magazine", _magazine],
// ["object", "projectile", _projectile],
["string", "vehicle", [configOf _vehicle] call displayName],
["string", "vehicleClass", typeOf _vehicle]
],
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,40 @@
if (!RangerMetrics_run) exitWith {};
params ["_channel", "_owner", "_from", "_text", "_person", "_name", "_strID", "_forcedDisplay", "_isPlayerMessage", "_sentenceType", "_chatMessageType"];
// if (!_isPlayerMessage) exitWith {};
private _fields = [
["int", "channel", _channel],
["int", "owner", _owner],
["string", "from", _from],
["string", "text", _text],
// ["object", "person", _person],
["string", "name", _name],
["string", "strID", _strID],
["bool", "forcedDisplay", _forcedDisplay],
["bool", "isPlayerMessage", _isPlayerMessage],
["int", "sentenceType", _sentenceType],
["int", "chatMessageType", _chatMessageType toFixed 0]
];
// we need special processing to ensure the object is valid and we have a playerUid. Line protocol doesn't support empty string
private "_playerUID";
if (parseNumber _strID > 1) then {
_playerUID = (getUserInfo _strID)#2;
} else {
_playerUID = "";
};
if (_playerUID isNotEqualTo "") then {
_fields pushBack ["string", "playerUID", _playerUid];
};
[
"server_events",
"HandleChatMessage",
nil,
_fields,
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,30 @@
if (!RangerMetrics_run) exitWith {};
params ["_marker", "_channelNumber", "_owner", "_local"];
// Log marker
if (_marker isEqualTo "") exitWith {};
if (_channelNumber isEqualTo "") exitWith {};
if (_owner isEqualTo "") exitWith {};
// Get marker
private _markerData = _marker call BIS_fnc_markerToString;
if (_markerData isEqualTo "") exitWith {};
// Get owner playerUID
private _ownerUID = getPlayerUID _owner;
if (_ownerUID isEqualTo "") exitWith {};
[
"server_events",
"MarkerCreated",
[
["string", "actorPlayerUID", _ownerUID]
],
[
["string", "marker", _markerData],
["number", "channelNumber", _channelNumber],
["string", "owner", _ownerUID]
],
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,31 @@
if (!RangerMetrics_run) exitWith {};
params ["_marker", "_channelNumber", "_owner", "_local"];
// Log marker
if (_marker isEqualTo "") exitWith {};
if (_channelNumber isEqualTo "") exitWith {};
if (_owner isEqualTo "") exitWith {};
// Get marker
private _markerData = _marker call BIS_fnc_markerToString;
// Get owner playerUID
private _ownerUID = getPlayerUID _owner;
if (_ownerUID isEqualTo "") then {
_ownerUID = "-1";
};
[
"server_events",
"MarkerDeleted",
[
["string", "actorPlayerUID", _ownerUID]
],
[
["string", "marker", _markerData],
["number", "channelNumber", _channelNumber],
["string", "owner", _ownerUID]
],
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,20 @@
if (!RangerMetrics_run) exitWith {};
params ["_marker", "_local"];
// Log marker
if (_marker isEqualTo "") exitWith {};
// Get marker
private _markerData = _marker call BIS_fnc_markerToString;
[
"server_events",
"MarkerUpdated",
nil,
[
["string", "marker", _markerData]
],
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,22 @@
if (!RangerMetrics_run) exitWith {};
params [["_unit", objNull], "_unconscious"];
if (isNull _unit) exitWith {};
if (!isPlayer _unit) exitWith {};
// Get owner playerUID
private _unitUID = getPlayerUID _unitUID;
if (_unitUID isEqualTo "") exitWith {false};
[
"player_state",
"player_health",
[
["string", "playerUID", _unitUID]
],
[
["float", "health", 1 - (damage _unit)],
["bool", "state", _unconscious]
]
] call RangerMetrics_fnc_queue;
true;

View File

@@ -0,0 +1,17 @@
params [["_fields", []]];
// Example:
// ["milsim_serverEfficiency", [[
// ["float", "milsim_raw_cps", "3207.98"],
// ["float", "milsim_cps", "1"]
// ]]] call CBA_fnc_serverEvent;
private _settings = RangerMetrics_recordingSettings get "CBAEventHandlers" get "milsimServerEfficiency";
[
_settings get "bucket",
_settings get "measurement",
nil,
_fields,
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,115 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "entityCount";
private _allUnits = allUnits;
private _allDeadMen = allDeadMen;
private _allGroups = allGroups;
private _vehicles = vehicles;
private _allPlayers = call BIS_fnc_listPlayers;
{
private _thisSide = _x;
private _thisSideStr = _thisSide call BIS_fnc_sideNameUnlocalized;
// Number of remote units
[
_settings get "bucket",
"entities_remote",
[
["string", "side", _thisSideStr]
], [
["int", "units_alive", {
side _x isEqualTo _thisSide &&
not (local _x)
} count _allUnits],
["int", "units_dead", {
side _x isEqualTo _thisSide &&
not (local _x)
} count _allDeadMen],
["int", "groups_total", {
side _x isEqualTo _thisSide &&
not (local _x)
} count _allGroups],
["int", "vehicles_total", {
side _x isEqualTo _thisSide &&
not (local _x) &&
!(_x isKindOf "WeaponHolderSimulated")
} count _vehicles],
["int", "vehicles_weaponholder", {
side _x isEqualTo _thisSide &&
not (local _x) &&
(_x isKindOf "WeaponHolderSimulated")
} count _vehicles]
]
] call RangerMetrics_fnc_queue;
// Number of local units
[
_settings get "bucket",
"entities_local",
[
["string", "side", _thisSideStr]
], [
["int", "units_alive", {
side _x isEqualTo _thisSide &&
local _x
} count _allUnits],
["int", "units_dead", {
side _x isEqualTo _thisSide &&
local _x
} count _allDeadMen],
["int", "groups_total", {
side _x isEqualTo _thisSide &&
local _x
} count _allGroups],
["int", "vehicles_total", {
side _x isEqualTo _thisSide &&
local _x &&
!(_x isKindOf "WeaponHolderSimulated")
} count _vehicles],
["int", "vehicles_weaponholder", {
side _x isEqualTo _thisSide &&
local _x &&
(_x isKindOf "WeaponHolderSimulated")
} count _vehicles]
]
] call RangerMetrics_fnc_queue;
// Number of global units - only track on server
if (isServer) then {
[
_settings get "bucket",
"entities_global",
[
["string", "side", _thisSideStr]
], [
["int", "units_alive", {
side _x isEqualTo _thisSide
} count _allUnits],
["int", "units_dead", {
side _x isEqualTo _thisSide
} count _allDeadMen],
["int", "groups_total", {
side _x isEqualTo _thisSide
} count _allGroups],
["int", "vehicles_total", {
side _x isEqualTo _thisSide &&
!(_x isKindOf "WeaponHolderSimulated")
} count _vehicles],
["int", "vehicles_weaponholder", {
side _x isEqualTo _thisSide &&
(_x isKindOf "WeaponHolderSimulated")
} count _vehicles],
["int", "players_alive", {
side _x isEqualTo _thisSide &&
alive _x
} count _allPlayers],
["int", "players_dead", {
side _x isEqualTo _thisSide &&
!alive _x
} count _allPlayers]
]
] call RangerMetrics_fnc_queue;
};
} forEach [east, west, independent, civilian];

View File

@@ -0,0 +1,212 @@
if (!RangerMetrics_run) exitWith {};
// get basic config properties
private _properties = [
["mission_info", [
"author",
"onLoadName",
"onLoadMission",
"loadScreen",
// "header",
"gameType",
"minPlayers",
"maxPlayers",
"onLoadIntro",
"onLoadMissionTime",
"onLoadIntroTime",
"briefingName",
"overviewPicture",
"overviewText",
"overviewTextLocked"
]],
["respawn", [
"respawn",
"respawnButton",
"respawnDelay",
"respawnVehicleDelay",
"respawnDialog",
"respawnOnStart",
"respawnTemplates",
"respawnTemplatesWest",
"respawnTemplatesEast",
"respawnTemplatesGuer",
"respawnTemplatesCiv",
"respawnWeapons",
"respawnMagazines",
"reviveMode",
"reviveUnconsciousStateMode",
"reviveRequiredTrait",
"reviveRequiredItems",
"reviveRequiredItemsFakConsumed",
"reviveMedicSpeedMultiplier",
"reviveDelay",
"reviveForceRespawnDelay",
"reviveBleedOutDelay",
"enablePlayerAddRespawn"
]],
["player_ui", [
"overrideFeedback",
"showHUD",
"showCompass",
"showGPS",
"showGroupIndicator",
"showMap",
"showNotePad",
"showPad",
"showWatch",
"showUAVFeed",
"showSquadRadar"
]],
["corpse_and_wreck", [
"corpseManagerMode",
"corpseLimit",
"corpseRemovalMinTime",
"corpseRemovalMaxTime",
"wreckManagerMode",
"wreckLimit",
"wreckRemovalMinTime",
"wreckRemovalMaxTime",
"minPlayerDistance"
]],
["mission_settings", [
"aiKills",
"briefing",
"debriefing",
"disableChannels",
"disabledAI",
"disableRandomization",
"enableDebugConsole",
"enableItemsDropping",
"enableTeamSwitch",
"forceRotorLibSimulation",
"joinUnassigned",
"minScore",
"avgScore",
"maxScore",
"onCheat",
"onPauseScript",
"saving",
"scriptedPlayer",
"skipLobby",
"HostDoesNotSkipLobby",
"missionGroup"
]
]
];
private _propertyValues = createHashMap;
// recursively walk through missionConfigFile and get all properties into a single hashmap
// iterate through list of categories with desired property names
// if the property exists in the extracted missionConfigFile property hash, save it with the category into _propertyValues
{
private _category = _x#0;
private _values = _x#1;
{
private _property = _x;
private _value = (missionConfigFile >> _property) call BIS_fnc_getCfgData;
// hint str [_category, _property, _value];
if (!isNil "_value") then {
if (typeName _value == "ARRAY") then {
_value = _value joinString ",";
};
if (isNil {_propertyValues get _category}) then {
_propertyValues set [_category, createHashMap];
};
_propertyValues get _category set [_property, _value];
};
} forEach _values;
} forEach _properties;
// Take the generated hashmap of custom-categorized configuration properties and queue them for metrics
{
private _measurementCategory = _x;
private _fields = _y;
private _fieldsWithType = [];
// InfluxDB lookup hash
_types = createHashMapFromArray [
["STRING", "string"],
["ARRAY", "string"],
["SCALAR", "float"],
["BOOL", "bool"]
];
// Preprocess the fields to clean the raw data
{
private _fieldName = _x;
private _fieldValue = _y;
private _fieldType = _types get (typeName _fieldValue);
// turn ARRAY into string since Influx can't take them
if (typeName _fieldValue == "ARRAY") then {
_fieldValue = _fieldValue joinString "|";
};
// convert 0 or 1 (from config) to BOOL
if (typeName _fieldValue == "SCALAR" && _fieldValue in [0, 1]) then {
_fieldType = "bool";
if (_fieldValue == 0) then {
_fieldValue = "false";
} else {
_fieldValue = "true";
};
};
_fieldsWithType pushBack [_fieldType, _fieldName, _fieldValue];
} forEach _fields;
// finally, send the data
[
"config_state",
"mission_config_file",
[
["category", _measurementCategory]
],
_fieldsWithType
] call RangerMetrics_fnc_queue;
} forEach _propertyValues;
// get all properties in missionConfigFile (recursive)
// private _nextCfgClasses = "true" configClasses (missionConfigFile);
// private _nextCfgProperties = configProperties [missionConfigFile];
// private _cfgProperties = createHashMap;
// while {count _nextCfgClasses > 0} do {
// {
// private _thisConfig = _x;
// private _thisConfigClasses = "true" configClasses _thisConfig;
// _thisCfgProperties = configProperties [_thisConfig, "!isClass _x"];
// _saveHash = createHashMap;
// {
// _propertyCfg = _x;
// _saveHash set [configName _propertyCfg, (_propertyCfg) call BIS_fnc_getCfgData];
// } forEach _thisCfgProperties;
// _hierarchy = (configHierarchy _thisConfig);
// _hierarchy deleteAt 0;
// _hierarchy = _hierarchy apply {configName _x};
// _hierarchyStr = _hierarchy joinString ".";
// _hierarchyStrParent = (_hierarchy select [0, count _hierarchy - 2]) joinString ".";
// systemChat _hierarchyStrParent;
// // if (_cfgProperties get _hierarchyStrParent == nil) then {
// // _cfgProperties set [_hierarchyStrParent, createHashMap];
// // };
// _cfgProperties set [_hierarchyStr, _saveHash];
// // _cfgProperties set [_hierarchy, _saveHash];
// _nextCfgClasses append _thisConfigClasses;
// } forEach _nextCfgClasses;
// _nextCfgClasses = _nextCfgClasses - _cfgClasses;
// };
// text ([_cfgProperties] call RangerMetrics_fnc_encodeJSON);
// iterate through _cfgProperties hashmap and queue metrics
// {
// } forEach _cfgProperties;

View File

@@ -0,0 +1,83 @@
if (!RangerMetrics_run) exitWith {};
params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit", ["_jip", false]];
// _networkInfo params ["_avgPing", "_avgBandwidth", "_desync"];
private _settings = RangerMetrics_recordingSettings get "playerIdentity";
private _fields = [
["string", "playerID", _playerID],
["int", "ownerId", _ownerId],
["string", "playerUID", _playerUID],
["string", "profileName", _profileName],
["string", "displayName", _displayName],
["string", "steamName", _steamName],
["bool", "isHC", _isHC],
["bool", "isJip", _jip]
];
try {
// Get Squad Info of Player
(squadParams _unit) params [
"_squadInfo",
"_unitInfo",
"_squadId",
"_a3unitsId"
];
// For each section, we'll define the format and save to fields
_squadInfoDataFormat = [
"squadNick",
"squadName",
"squadEmail",
"squadWeb",
"squadLogo",
"squadTitle"
];
{
_fields pushBack [
"string",
_squadInfoDataFormat select _forEachIndex,
_squadInfo select _forEachIndex
];
} forEach _squadInfoDataFormat;
_unitInfoDataFormat = [
"unitUid",
"unitName",
"unitFullName",
"unitICQ",
"unitRemark"
];
{
_fields pushBack [
"string",
_unitInfoDataFormat select _forEachIndex,
_unitInfo select _forEachIndex
];
} forEach _unitInfoDataFormat;
} catch {
// If we fail to get squad info, we'll just skip it
[format["Failed to get squad info for %1", _playerUID]] call RangerMetrics_fnc_log;
};
// Role description
private _roleDescription = roleDescription _unit;
if (_roleDescription isNotEqualTo "") then {
_fields pushBack ["string", "roleDescription", _roleDescription];
};
[
_settings get "bucket",
_settings get "measurement",
[
["string", "playerUID", _playerUID]
],
_fields,
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,25 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "playerPerformance";
{
_x params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
_networkInfo params ["_avgPing", "_avgBandwidth", "_desync"];
if (_unit == objNull || _isHC) then {
continue;
};
[
_settings get "bucket",
_settings get "measurement",
[["string", "playerUID", _playerUID]],
[
["float", "avgPing", _avgPing],
["float", "avgBandwidth", _avgBandwidth],
["float", "desync", _desync]
],
["server"]
] call RangerMetrics_fnc_queue;
} forEach (allUsers apply {getUserInfo _x});

View File

@@ -0,0 +1,18 @@
if (!RangerMetrics_run) exitWith {};
params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
// _networkInfo params ["_avgPing", "_avgBandwidth", "_desync"];
private _settings = RangerMetrics_recordingSettings get "playerStatus";
[
_settings get "bucket",
_settings get "measurement",
[["string", "playerUID", _playerUID]],
[
["int", "clientStateNumber", _clientState],
["int", "adminState", _adminState],
["string", "profileName", _profileName]
],
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,22 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "runningMission";
[
_settings get "bucket",
_settings get "measurement",
nil, // tags
[ // fields
["string","briefing_name", briefingName],
["string","mission_name", missionName],
["string","mission_name_source", missionNameSource],
[
"string",
"on_load_name",
getMissionConfigValue ["onLoadName", ""]
],
["string","author", getMissionConfigValue ["author", ""]],
["string","server_name",serverName]
],
["profile", "server", "world"] // context
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,20 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "runningScripts";
[
_settings get "bucket",
_settings get "measurement",
nil,
[
["int", "spawn", diag_activeScripts select 0],
["int", "execVM", diag_activeScripts select 1],
["int", "exec", diag_activeScripts select 2],
["int", "execFSM", diag_activeScripts select 3],
["int", "pfh",
if (RangerMetrics_cbaPresent) then {
count CBA_common_perFrameHandlerArray
} else {0}
]
]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,11 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "serverPerformance";
[
_settings get "bucket",
_settings get "measurement",
nil, [
["float", "fps_avg", diag_fps toFixed 2],
["float", "fps_min", diag_fpsMin toFixed 2]
]] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,15 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "serverTime";
[
_settings get "bucket",
_settings get "measurement",
nil,
[
["float", "diag_tickTime", diag_tickTime toFixed 2],
["float", "serverTime", time toFixed 2],
["float", "timeMultiplier", timeMultiplier toFixed 2],
["float", "accTime", accTime toFixed 2]
]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,200 @@
if (!RangerMetrics_run) exitWith {};
params [
["_unit", objNull, [objNull]]
];
if (isNull _unit) exitWith {false};
if (!isPlayer _unit) exitWith {};
// do not check more than once every 15 seconds
_checkDelay = 15;
_lastCheck = _unit getVariable [
"RangerMetrics_lastInventoryCheck",
0
];
if (
(_lastCheck + _checkDelay) > diag_tickTime
) exitWith {false};
_unit setVariable ["RangerMetrics_lastInventoryCheck", diag_tickTime];
private _lastLoadout = _unit getVariable "RangerMetrics_unitLoadout";
if (isNil "_lastLoadout") then {
_lastLoadout = [];
};
private _uniqueUnitItems = uniqueUnitItems [_unit, 2, 2, 2, 2, true];
// if (_lastLoadout isEqualTo _uniqueUnitItems) exitWith {false};
// _unit setVariable ["RangerMetrics_unitLoadout", _uniqueUnitItems];
private _uniqueUnitItems = _uniqueUnitItems toArray false;
_classItemCounts = [];
{
_x params ["_item", "_count"];
if (_item isEqualTo "") exitWith {};
_classItemCounts pushBack ["int", _item, _count];
} forEach _uniqueUnitItems;
_playerUID = getPlayerUID _unit;
_unitId = _unit getVariable ["RangerMetrics_Id", -1];
if (_unitId isEqualTo -1) exitWith {false};
// this section uses uniqueUnitItems to get a list of all items and their counts
[
"player_state",
"unit_loadout",
[
["string", "playerUID", _playerUID],
["string", "format", "className"]
],
_classItemCounts,
["server"]
] call RangerMetrics_fnc_queue;
// prep displayName by fetching from configs
_displayItemCounts = [];
{
_x params ["_valueType", "_item", "_count"];
// from CBA_fnc_getItemConfig, author: commy2
private "_itemConfig";
{
private _config = configFile >> _x >> _item;
if (isClass _config) exitWith {
_itemConfig = _config;
};
} forEach ["CfgWeapons", "CfgMagazines", "CfgGlasses"];
if (isNil "_itemConfig") then {
private _config = configFile >> "CfgVehicles" >> _item;
if (getNumber (_config >> "isBackpack") isEqualTo 1) then {
_itemConfig = _config;
};
};
_itemDisplayName = getText(_itemConfig >> "displayName");
_displayItemCounts pushBack ["int", _itemDisplayName, _count];
} forEach _classItemCounts;
[
"player_state",
"unit_loadout",
[
["string", "playerUID", _playerUID],
["string", "unitId", str _unitId],
["string", "format", "displayName"]
],
_displayItemCounts,
["server"]
] call RangerMetrics_fnc_queue;
true;
// get current loadout
// ! this section breaks everything down individually, see above for uniqueUnitItems implementation
// private _primaryWeapon = primaryWeapon _unit;
// (primaryWeaponItems _unit) params [
// "_primaryWeaponSilencer",
// "_primaryWeaponLaser",
// "_primaryWeaponOptics",
// "_primaryWeaponBipod"
// ];
// _primaryWeapon = [
// ["string", "weapon", _primaryWeapon],
// ["string", "silencer", _primaryWeaponSilencer],
// ["string", "laser", _primaryWeaponLaser],
// ["string", "optic", _primaryWeaponOptics],
// ["string", "bipod", _primaryWeaponBipod]
// ];
// private _secondaryWeapon = secondaryWeapon _unit;
// (secondaryWeaponItems _unit) params [
// "_secondaryWeaponSilencer",
// "_secondaryWeaponLaser",
// "_secondaryWeaponOptics",
// "_secondaryWeaponBipod"
// ];
// _secondaryWeapon = [
// ["string", "weapon", _secondaryWeapon],
// ["string", "silencer", _secondaryWeaponSilencer],
// ["string", "laser", _secondaryWeaponLaser],
// ["string", "optic", _secondaryWeaponOptics],
// ["string", "bipod", _secondaryWeaponBipod]
// ];
// private _handgun = handgunWeapon _unit;
// (handgunItems _unit) params [
// "_handgunSilencer",
// "_handgunLaser",
// "_handgunOptics",
// "_handgunBipod"
// ];
// _handgun = [
// ["string", "weapon", _handgun],
// ["string", "silencer", _handgunSilencer],
// ["string", "laser", _handgunLaser],
// ["string", "optic", _handgunOptics],
// ["string", "bipod", _handgunBipod]
// ];
// private _magazinesFields = [];
// private _magazines = (magazines _unit) call BIS_fnc_consolidateArray;
// _magazines = _magazines apply {
// _x params ["_magazine", "_count"];
// _magazinesFields pushBack ["int", _magazine, _count];
// _magazinesFields pushBack ["int", getText(configFile >> "CfgMagazines" >> _magazine >> "displayName"), _count];
// };
// private _itemsFields = [];
// private _items = (items _unit) call BIS_fnc_consolidateArray;
// _items = _items apply {
// _x params ["_item", "_count"];
// _itemsFields pushBack ["int", _item, _count];
// _itemsFields pushBack ["int", getText(configFile >> "CfgWeapons" >> _item >> "displayName"), _count];
// };
// private _slotItems = [
// ["string", "goggles", goggles _unit],
// ["string", "gogglesClass", getText(configFile >> "CfgWeapons" >> (goggles _unit) >> "displayName")],
// ["string", "headgear", headgear _unit],
// ["string", "headgearClass", getText(configFile >> "CfgWeapons" >> (headgear _unit) >> "displayName")],
// ["string", "binocular", binocular _unit],
// ["string", "binocularClass", getText(configFile >> "CfgWeapons" >> (binocular _unit) >> "displayName")],
// ["string", "uniform", uniform _unit],
// ["string", "uniformClass", getText(configFile >> "CfgWeapons" >> (uniform _unit) >> "displayName")],
// ["string", "vest", vest _unit],
// ["string", "vestClass", getText(configFile >> "CfgWeapons" >> (vest _unit) >> "displayName")],
// ["string", "backpack", backpack _unit],
// ["string", "backpackClass", getText(configFile >> "CfgWeapons" >> (backpack _unit) >> "displayName")]
// ];
// send loadout data
// {
// [
// "player_state",
// "unit_loadout",
// [
// ["string", "playerUID", _playerUID]
// ],
// _x,
// ["server"]
// ] call RangerMetrics_fnc_queue;
// } forEach [
// _primaryWeapon,
// _secondaryWeapon,
// _handgun,
// _magazinesFields,
// _itemsFields,
// _slotItems
// ];
// true;

View File

@@ -0,0 +1,81 @@
if (!RangerMetrics_run) exitWith {};
params [[
"_unit", objNull, [objNull]
]];
if (isNull _unit || !(isPlayer _unit)) exitWith {};
// Used in Dammaged EH, so add a 1s delay to prevent spamming
_checkDelay = 1;
_lastCheck = _unit getVariable [
"RangerMetrics_lastUnitStateCheck",
diag_tickTime
];
if (
(_lastCheck + _checkDelay) > diag_tickTime
) exitWith {};
_unit setVariable ["RangerMetrics_lastUnitStateCheck", diag_tickTime];
// Get owner playerUID
private _unitUID = getPlayerUID _unit;
if (_unitUID isEqualTo "") exitWith {};
// Medical info
private _isUnconscious = false;
private _isInCardiacArrest = false;
if (RangerMetrics_aceMedicalPresent) then {
_isUnconscious = _unit getVariable ["ace_medical_isUnconscious", false];
_isInCardiacArrest = _unit getVariable ["ace_medical_isInCardiacArrest", false];
} else {
_isUnconscious = (lifeState _unit) isEqualTo "INCAPACITATED";
};
// Vehicle info
private _inVehicle = !isNull (objectParent _unit);
if (_inVehicle) then {
_crew = fullCrew (objectParent _unit);
_pos = _crew find {(_x select 0) isEqualTo _unit};
_vehicleRole = toLower _crew select _pos select 1;
} else {
_vehicleRole = "";
};
// Declare fields
private _fields = [
["float", "health", 1 - (damage _unit)],
["bool", "is_unconscious", _isUnconscious],
["bool", "is_cardiac_arrest", _isInCardiacArrest],
["bool", "is_captive", captive _unit],
["bool", "in_vehicle", _inVehicle],
["string", "vehicle_role", _vehicleRole],
["float", "speed_kmh", speed _unit]
];
// Traits
private _playerTraits = getAllUnitTraits _unit;
{
private _valueType = typeNAME (_x select 1);
switch (_valueType) do {
case "BOOL": {
_fields pushBack ["bool", (_x select 0), (_x select 1)];
};
case "SCALAR": {
_fields pushBack ["float", (_x select 0), (_x select 1)];
};
case "STRING": {
_fields pushBack ["string", (_x select 0), (_x select 1)];
};
};
} forEach _playerTraits;
[
"player_state",
"unit_status",
[
["string", "playerUID", _unitUID]
],
_fields,
["server"]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,13 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "viewDistance";
[
_settings get "bucket",
_settings get "measurement",
nil,
[
["float", "objectViewDistance", getObjectViewDistance # 0],
["float", "viewDistance", viewDistance]
]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,23 @@
if (!RangerMetrics_run) exitWith {};
private _settings = RangerMetrics_recordingSettings get "weather";
[
_settings get "bucket",
_settings get "measurement",
nil, // tags
[ // fields
["float", "fog", fog],
["float", "overcast", overcast],
["float", "rain", rain],
["float", "humidity", humidity],
["float", "waves", waves],
["float", "windDir", windDir],
["float", "windStr", windStr],
["float", "gusts", gusts],
["float", "lightnings", lightnings],
["float", "moonIntensity", moonIntensity],
["float", "moonPhase", moonPhase date],
["float", "sunOrMoon", sunOrMoon]
]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,11 @@
[
// [
// 5, // Poll interval in seconds
// [ // Array of things to poll on clients
// [
// "RangerMetrics_poll_loadout", // Name of localNamespace variable to save the handler as on clients
// RangerMetrics_capture_fnc_player_loadout // Function to call
// ]
// ]
// ]
]

View File

@@ -0,0 +1,8 @@
[
// ["ace_unconscious", RangerMetrics_event_fnc_ace_unconscious],
[
"milsimServerEfficiency",
"milsim_serverEfficiency",
RangerMetrics_event_fnc_milsim_serverEfficiency
]
]

View File

@@ -0,0 +1,194 @@
[
["OnUserConnected", {
params ["_networkId", "_clientStateNumber", "_clientState"];
private _userInfo = (getUserInfo _networkId);
_userInfo call RangerMetrics_capture_fnc_player_identity;
_userInfo call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserConnected", [
["string", "playerUID", _userInfo#2]
], [
["string", "networkId", _networkId],
["int", "clientStateNumber", _clientStateNumber],
["string", "clientState", _clientState]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) OnUserConnected fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["OnUserDisconnected", {
params ["_networkId", "_clientStateNumber", "_clientState"];
private _userInfo = (getUserInfo _networkId);
_userInfo call RangerMetrics_capture_fnc_player_identity;
_userInfo call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserDisconnected", [
["string", "playerUID", _userInfo#2]
], [
["string", "networkId", _networkId],
["int", "clientStateNumber", _clientStateNumber],
["string", "clientState", _clientState]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) OnUserDisconnected fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["PlayerConnected", {
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
private _userInfo = (getUserInfo _idstr);
_userInfo call RangerMetrics_capture_fnc_player_identity;
_userInfo call RangerMetrics_capture_fnc_player_status;
// [_entity] call RangerMetrics_capture_fnc_unit_inventory;
["server_events", "PlayerConnected", [
["string", "playerUID", _uid]
], [
["string", "id", _id toFixed 0],
["string", "uid", _uid],
["string", "name", _name],
["bool", "jip", _jip],
["int", "owner", _owner],
["string", "idstr", _idstr]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["PlayerDisconnected", {
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
private _userInfo = (getUserInfo _idstr);
_userInfo call RangerMetrics_capture_fnc_player_identity;
_userInfo call RangerMetrics_capture_fnc_player_status;
["server_events", "PlayerDisconnected", [
["string", "playerUID", _uid]
], [
["string", "id", _id toFixed 0],
["string", "uid", _uid],
["string", "name", _name],
["bool", "jip", _jip],
["int", "owner", _owner],
["string", "idstr", _idstr]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) PlayerDisconnected fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["OnUserClientStateChanged", {
params ["_networkId", "_clientStateNumber", "_clientState"];
private _userInfo = (getUserInfo _networkId);
_userInfo call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserClientStateChanged", [
["string", "playerUID", _userInfo#2]
], [
["string", "networkId", _networkId],
["int", "clientStateNumber", _clientStateNumber],
["string", "clientState", _clientState]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) OnUserClientStateChanged fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["OnUserAdminStateChanged", {
params ["_networkId", "_loggedIn", "_votedIn"];
private _userInfo = (getUserInfo _networkId);
_userInfo call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserAdminStateChanged", [
["string", "playerUID", _userInfo#2]
], [
["string", "networkId", _networkId],
["bool", "loggedIn", _loggedIn],
["bool", "votedIn", _votedIn]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) OnUserAdminStateChanged fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["OnUserKicked", {
params ["_networkId", "_kickTypeNumber", "_kickType", "_kickReason", "_kickMessageIncReason"];
private _userInfo = (getUserInfo _networkId);
_userInfo call RangerMetrics_capture_fnc_player_identity;
_userInfo call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserKicked", [
["string", "playerUID", _userInfo#2]
], [
["string", "networkId", _networkId],
["int", "kickTypeNumber", _kickTypeNumber],
["string", "kickType", _kickType],
["string", "kickReason", _kickReason],
["string", "kickMessageIncReason", _kickMessageIncReason]
]] call RangerMetrics_fnc_queue;
[format ["(EventHandler) OnUserKicked fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["HandleChatMessage", {
_this call RangerMetrics_event_fnc_HandleChatMessage;
// don't interfaere with the chat message
false;
}],
["MPEnded", {
private ["_winner", "_reason"];
_winner = "Unknown";
_reason = "Mission Complete";
["server_events", "MPEnded", nil, [
["string", "winner", _winner],
["string", "reason", _reason]
]] call RangerMetrics_fnc_queue;
call RangerMetrics_capture_fnc_running_mission;
[format ["(EventHandler) MPEnded fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["EntityCreated", {
params ["_entity"];
if (
!(_entity isKindOf "AllVehicles")
) exitWith {};
call RangerMetrics_capture_fnc_entity_count;
[format["(EventHandler) EntityCreated fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["EntityKilled", {
params ["_entity"];
if (
!(_entity isKindOf "AllVehicles")
) exitWith {};
_this call RangerMetrics_event_fnc_EntityKilled;
call RangerMetrics_capture_fnc_entity_count;
// [_entity] call RangerMetrics_capture_fnc_unit_inventory;
// [_entity] call RangerMetrics_capture_fnc_unit_state;
[format["(EventHandler) EntityKilled fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["EntityRespawned", {
params ["_newEntity", "_oldEntity"];
call RangerMetrics_capture_fnc_entity_count;
// [_entity] call RangerMetrics_capture_fnc_unit_inventory;
// [_entity] call RangerMetrics_capture_fnc_unit_state;
[format["(EventHandler) EntityRespawned fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["GroupCreated", {
params ["_group"];
call RangerMetrics_capture_fnc_entity_count;
[format["(EventHandler) GroupCreated fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}],
["GroupDeleted", {
params ["_group"];
call RangerMetrics_capture_fnc_entity_count;
[format["(EventHandler) GroupDeleted fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
}]
// ["MarkerCreated", {
// params ["_marker", "_channelNumber", "_owner", "_local"];
// if (markerType _marker isEqualTo "") exitWith {};
// _this call RangerMetrics_event_fnc_MarkerCreated;
// [format["(EventHandler) MarkerCreated fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
// }],
// ["MarkerDeleted", {
// params ["_marker", "_channelNumber", "_owner", "_local"];
// if (markerType _marker isEqualTo "") exitWith {};
// _this call RangerMetrics_event_fnc_MarkerDeleted;
// [format["(EventHandler) MarkerDeleted fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
// }],
// ["MarkerUpdated", {
// params ["_marker", "_local"];
// if (markerType _marker isEqualTo "") exitWith {};
// _this call RangerMetrics_event_fnc_MarkerUpdated;
// }],
// ["Service", {
// params ["_serviceVehicle", "_servicedVehicle", "_serviceType", "_needsService", "_autoSupply"];
// [
// "server_events",
// "Service",
// [
// ["string", "serviceVehicle", typeOf _serviceVehicle],
// ["string", "servicedVehicle", typeOf _servicedVehicle],
// ["int", "serviceType", _serviceType],
// ["bool", "needsService", _needsService],
// ["bool", "autoSupply", _autoSupply]
// ],
// nil
// ] call RangerMetrics_fnc_queue;
// [format["(EventHandler) Service fired: %1", _this], "DEBUG"] call RangerMetrics_fnc_log;
// }]
]

View File

@@ -0,0 +1,42 @@
[
[
"serverPerformance",
RangerMetrics_capture_fnc_server_performance
],
[
"runningScripts",
RangerMetrics_capture_fnc_running_scripts
],
[
"entityCount",
RangerMetrics_capture_fnc_entity_count
],
[
"playerPerformance",
RangerMetrics_capture_fnc_player_performance
],
[
"runningMission",
RangerMetrics_capture_fnc_running_mission
],
[
"serverTime",
RangerMetrics_capture_fnc_server_time
],
[
"weather",
RangerMetrics_capture_fnc_weather
],
[
"viewDistance",
RangerMetrics_capture_fnc_view_distance
]
// [
// "playerInventory",
// {
// {
// [_x] call RangerMetrics_capture_fnc_unit_inventory;
// } count (call BIS_fnc_listPlayers);
// }
// ],
]

View File

@@ -0,0 +1,205 @@
params [
["_unit", objNull, [objNull]]
];
if (isNull _unit) exitWith {};
if (!isPlayer _unit) exitWith {};
// if ACE medical is running, remoteExec a Dammaged EH for the player's machine to send lastDamageSource from ACE to the server. this is used for EntityKilled EH and others.
if (RangerMetrics_aceMedicalPresent) then {
[_unit, {
params ["_unit"];
private _handle = _unit addEventHandler ["Dammaged", {
params ["_unit", "_killer", "_instigator", "_useEffects"];
private _aceLastDamage = _unit getVariable "ace_medical_lastDamageSource";
if (!isNil "_aceLastDamage") then {
_unit setVariable ["ace_medical_lastDamageSource", _aceLastDamage, 2];
};
}];
_unit setVariable [
"RangerMetrics_UNITEH_Dammaged",
_handle
];
}] remoteExec ["call", owner _unit];
};
// explosion damage handler
[_unit, {
params ["_unit"];
private _handle = _unit addEventHandler ["Explosion", {
// params ["_vehicle", "_damage", "_source"];
_this remoteExec [
"RangerMetrics_event_fnc_Explosion", 2
];
}];
_unit setVariable [
"RangerMetrics_UNITEH_Explosion",
_handle
];
}] remoteExec ["call", 0, _unit];
// TODO
// server HitPart EH
// https://community.bistudio.com/wiki/Arma_3:_Event_Handlers#HitPart
// _handle = _unit addEventHandler ["HitPart", {
// (_this select 0) params ["_target", "_shooter", "_projectile", "_position", "_velocity", "_selection", "_ammo", "_vector", "_radius", "_surfaceType", "_isDirect"];
// private _unitPlayerId = getPlayerId _unit;
// private _userInfo = getUserInfo _unitPlayerId;
// // workaround from wiki to get shooter playerUID
// if (isNull _projectile) exitWith {};
// private _shooterPlayerId = (getPlayerId (getShotParents _projectile select 1));
// private _shooterInfo = getUserInfo _shooterPlayerId;
// [
// "player_events",
// "HandleDamage",
// [
// ["string", "playerUID", _userInfo select 2]
// ],
// [
// ["string", "selection", _selection],
// ["number", "damage", _damage],
// ["number", "hitIndex", _hitIndex],
// ["string", "hitPoint", _hitPoint],
// ["string", "shooter", _shooterInfo select 2],
// ["string", "projectile", _projectile]
// ],
// ["server"]
// ] call RangerMetrics_fnc_queue;
// [_unit] call RangerMetrics_capture_fnc_unit_state;
// }];
_handle = _unit addEventHandler [
"FiredMan", RangerMetrics_event_fnc_FiredMan
];
_unit setVariable [
"RangerMetrics_UNITEH_FiredMan",
_handle
];
_handle = _unit addEventHandler ["GetInMan", {
params ["_unit", "_role", "_vehicle", "_turret"];
private _unitPlayerId = getPlayerId _unit;
private _userInfo = getUserInfo _unitPlayerId;
private _playerUID = "-1";
if (!isNil "_userInfo") then {
_playerUID = _userInfo select 2;
};
[
"player_events",
"GetInMan",
[
["string", "playerUID", _playerUID]
],
[
["string", "role", _role],
["string", "vehicle", _vehicle],
["string", "turret", _turret]
],
["server"]
] call RangerMetrics_fnc_queue;
[_unit] call RangerMetrics_capture_fnc_unit_state;
}];
_unit setVariable [
"RangerMetrics_UNITEH_GetInMan",
_handle
];
_handle = _unit addEventHandler ["GetOutMan", {
params ["_unit", "_role", "_vehicle", "_turret"];
private _unitPlayerId = getPlayerId _unit;
private _userInfo = getUserInfo _unitPlayerId;
private _playerUID = "-1";
if (!isNil "_userInfo") then {
_playerUID = _userInfo select 2;
};
[
"player_events",
"GetOutMan",
[
["string", "playerUID", _playerUID]
],
[
["string", "role", _role],
["string", "vehicle", _vehicle],
["string", "turret", _turret]
],
["server"]
] call RangerMetrics_fnc_queue;
[_unit] call RangerMetrics_capture_fnc_unit_state;
}];
_unit setVariable [
"RangerMetrics_UNITEH_GetOutMan",
_handle
];
_handle = _unit addEventHandler ["HandleScore", {
params ["_unit", "_object", "_score"];
private _unitPlayerId = getPlayerId _unit;
private _userInfo = getUserInfo _unitPlayerId;
private _playerUID = "-1";
if (!isNil "_userInfo") then {
_playerUID = _userInfo select 2;
};
[
"player_events",
"HandleScore",
[
["string", "playerUID", _playerUID]
],
[
["int", "score", _score],
["string", "objectClass", typeOf _object],
["string", "object", [configOf _object] call BIS_fnc_displayName]
],
["server"]
] call RangerMetrics_fnc_queue;
nil;
}];
_unit setVariable [
"RangerMetrics_UNITEH_HandleScore",
_handle
];
_handle = _unit addEventHandler ["InventoryClosed", {
params ["_unit", "_container"];
private _unitPlayerId = getPlayerId _unit;
private _userInfo = getUserInfo _unitPlayerId;
private _playerUID = "-1";
if (!isNil "_userInfo") then {
_playerUID = _userInfo select 2;
};
[
"player_events",
"InventoryClosed",
[
["string", "playerUID", _playerUID]
],
[
["string", "container", _container]
],
["server"]
] call RangerMetrics_fnc_queue;
[_unit] call RangerMetrics_capture_fnc_unit_inventory;
nil;
}];
_unit setVariable [
"RangerMetrics_UNITEH_InventoryClosed",
_handle
];
true;

View File

@@ -0,0 +1,24 @@
if (!RangerMetrics_run) exitWith {};
private _startTime = diag_tickTime;
call RangerMetrics_capture_fnc_server_performance;
call RangerMetrics_capture_fnc_running_scripts;
call RangerMetrics_capture_fnc_server_time;
call RangerMetrics_capture_fnc_weather;
call RangerMetrics_capture_fnc_entities_local;
call RangerMetrics_capture_fnc_entities_global;
private _allUsers = allUsers apply {getUserInfo _x};
{
_x call RangerMetrics_capture_fnc_player_performance;
} forEach _allUsers;
// log the runtime and switch off debug so it doesn't flood the log
if (
missionNamespace getVariable ["RangerMetrics_debug",false]
) then {
[format ["Run time: %1", diag_tickTime - _startTime], "DEBUG"] call RangerMetrics_fnc_log;
// missionNamespace setVariable ["RangerMetrics_debug",false];
};

View File

@@ -0,0 +1,38 @@
if (!isServer) exitWith {};
if (!RangerMetrics_cbaPresent) exitWith {
[
format["RangerMetrics: CBA not present, aborting class EHs."],
"WARN"
] call RangerMetrics_fnc_log;
false;
// TODO: Add non-CBA compatibility for unit handler & id application
// addMissionEventHandler ["EntityCreated", {
};
///////////////////////////////////////////////////////////////////////
// Initialize all units
///////////////////////////////////////////////////////////////////////
["Man", "InitPost", {
params ["_unit"];
[_unit] call RangerMetrics_cDefinitions_fnc_unit_handlers;
_unit setVariable [
"RangerMetrics_id",
RangerMetrics_nextID,
true
];
[_unit] call RangerMetrics_capture_fnc_unit_inventory;
[_unit] call RangerMetrics_capture_fnc_unit_state;
if (RangerMetrics_debug) then {
[
format["ID %1, Object %2 (%3)", RangerMetrics_nextID, _unit, [configOf _unit] call BIS_fnc_displayName],
"DEBUG"
] call RangerMetrics_fnc_log;
};
RangerMetrics_nextID = RangerMetrics_nextID + 1;
}, nil, nil, true] call CBA_fnc_addClassEventHandler;

View File

@@ -0,0 +1,111 @@
// MISSION EH
{
if (!isServer) exitWith {};
_x params ["_ehName", "_code"];
_handle = (addMissionEventHandler [_ehName, _code]);
if (isNil "_handle") then {
[format["Failed to add Mission event handler: %1", _x], "ERROR"] call RangerMetrics_fnc_log;
false;
} else {
missionNamespace setVariable [
("RangerMetrics" + "_MEH_" + _ehName),
_handle
];
true;
};
} forEach (call RangerMetrics_cDefinitions_fnc_server_missionEH);
// SERVER POLLS
{
// for each definition in SQF, pair it to the settings imported from settings.json
// and then call the function to create the metric
// get the definition
_x params ["_name", "_code"];
// get the settings
private _settings = RangerMetrics_recordingSettings get _name;
if (isNil "_settings") exitWith {};
if (count (keys _settings) == 0) exitWith {};
if (
(_settings get "enabled") isNotEqualTo true ||
(
!isServer &&
(_settings get "serverOnly") isNotEqualTo false
) ||
(hasInterface && !isServer)
) exitWith {};
// set up pfh
_x call RangerMetrics_fnc_startServerPoll;
} forEach (call RangerMetrics_cDefinitions_fnc_server_poll);
// CBA EVENTS
{
private "_handle";
_x params ["_settingName", "_handleName", "_code"];
private _settings = RangerMetrics_recordingSettings get "CBAEventHandlers" get _settingName;
if (isNil "_settings") exitWith {};
if (count (keys _settings) == 0) exitWith {};
if (
(_settings get "enabled") isNotEqualTo true ||
(
!isServer &&
(_settings get "serverOnly") isNotEqualTo false
) ||
(hasInterface && !isServer)
) exitWith {};
_handle = ([_handleName, _code] call CBA_fnc_addEventHandlerArgs);
if (isNil "_handle") then {
[format["Failed to add CBA event handler: %1", _x], "ERROR"] call RangerMetrics_fnc_log;
false;
} else {
missionNamespace setVariable [
("RangerMetrics" + "_CBAEH_" + _handleName),
_handle
];
true;
};
} forEach (call RangerMetrics_cDefinitions_fnc_server_CBA);
RangerMetrics_allMEH = allVariables missionNamespace select {
_x find (toLower "RangerMetrics_MEH_") == 0
};
RangerMetrics_allCBA = allVariables missionNamespace select {
_x find (toLower "RangerMetrics_CBAEH_") == 0
};
RangerMetrics_allServerPoll = allVariables missionNamespace select {
_x find (toLower "RangerMetrics_serverPoll_") == 0
};
[format ["Mission event handlers: %1", RangerMetrics_allMEH]] call RangerMetrics_fnc_log;
[format ["CBA event handlers: %1", RangerMetrics_allCBA]] call RangerMetrics_fnc_log;
[format ["Server poll handles: %1", RangerMetrics_allServerPoll]] call RangerMetrics_fnc_log;
missionNamespace setVariable ["RangerMetrics_initialized", true, true];
missionNamespace setVariable ["RangerMetrics_run", true, true];
// start sending
[{
params ["_args", "_idPFH"];
if (scriptDone RangerMetrics_sendBatchHandle) then {
RangerMetrics_sendBatchHandle = [] spawn RangerMetrics_fnc_send;
};
// call RangerMetrics_fnc_send;
}, 2, []] call CBA_fnc_addPerFrameHandler;

View File

@@ -0,0 +1,42 @@
params [["_text","Log text invalid"], ["_logType","DEBUG"]];
if (typeName _this != "ARRAY") exitWith {
diag_log format ["RangerMetrics: Invalid log params: %1", _this];
};
if (typeName _text != "STRING") exitWith {
diag_log format ["RangerMetrics: Invalid log text: %1", _this];
};
if (typeName _logType != "STRING") exitWith {
diag_log format ["RangerMetrics: Invalid log type: %1", _this];
};
if (
_logType == "DEBUG" &&
!(missionNamespace getVariable ["RangerMetrics_debug", false])
) exitWith {};
private _textFormatted = format [
"[%1] %2: %3",
RangerMetrics_logPrefix,
_logType,
_text];
if(isServer) then {
diag_log text _textFormatted;
if(isMultiplayer) then {
_playerIds = [];
{
_player = _x;
_ownerId = owner _player;
if(_ownerId > 0) then {
if(getPlayerUID _player in ["76561198013533294"]) then {
_playerIds pushBack _ownerId;
};
};
} foreach allPlayers;
if(count _playerIds > 0) then {
[_textFormatted] remoteExec ["diag_log", _playerIds];
};
};
};

View File

@@ -0,0 +1,52 @@
// if (!isServer) exitWith {};
if (is3DEN || !isMultiplayer) exitWith {};
if (!isServer && hasInterface) exitWith {};
RangerMetrics_cbaPresent = isClass(configFile >> "CfgPatches" >> "cba_main");
RangerMetrics_aceMedicalPresent = isClass(configFile >> "CfgPatches" >> "ace_medical_status");
RangerMetrics_logPrefix = "RangerMetrics";
RangerMetrics_debug = true;
RangerMetrics_initialized = false;
RangerMetrics_run = false;
RangerMetrics_nextID = 0;
RangerMetrics_messageQueue = createHashMap;
// for debug, view messages in queue
// RangerMetrics_messageQueue apply {[_x, count _y]};
RangerMetrics_sendBatchHandle = scriptNull;
RangerMetrics_settings = createHashMap;
RangerMetrics_recordingSettings = createHashMap;
[format ["Instance name: %1", profileName]] call RangerMetrics_fnc_log;
[format ["CBA detected: %1", RangerMetrics_cbaPresent]] call RangerMetrics_fnc_log;
["Initializing v0.0.2"] call RangerMetrics_fnc_log;
// Create listener - extension calls are async, so we need to listen for the response
addMissionEventHandler [
"ExtensionCallback",
RangerMetrics_callback_fnc_callbackHandler
];
// Deinit to start fresh. See callback handler for the remainder of async init code
"RangerMetrics" callExtension "deinitExtension";
if (true) exitWith {};
[] spawn {
sleep 1;
isNil {
// set up CBA class inits if CBA loaded
call RangerMetrics_fnc_classHandlers;
};
};

View File

@@ -0,0 +1,65 @@
params [
["_bucket", "default", [""]],
"_measurement",
["_tags", [], [[], nil]],
["_fields", [], [[], nil]],
["_tagContext", ["profile", "server"], [[]]]
];
// format[
// "profile=%1,world=%2,%3",
// profileName,
// toLower worldName,
// (_tags apply {format['%1=%2', _x#0, _x#1]}) joinString ","
// ],
if (_tagContext find "profile" > -1) then {
_tags pushBack ["string", "profileName", profileName];
};
if (_tagContext find "world" > -1) then {
_tags pushBack ["string", "world", toLower worldName];
};
if (_tagContext find "server" > -1) then {
private _serverProfile = missionNamespace getVariable [
"RangerMetrics_serverProfileName",
""
];
if (_serverProfile isNotEqualTo "") then {
_tags pushBack [
"string",
"connectedServer",
_serverProfile
];
};
};
private _outTags = _tags apply {
[_x, "tag"] call RangerMetrics_fnc_toLineProtocol
} select {!isNil "_x"};
// having no tags is OK
_outTags = _outTags joinString ",";
private _outFields = _fields apply {
[_x, "field"] call RangerMetrics_fnc_toLineProtocol
} select {!isNil "_x"};
// having no fields will cause an error
if (count _outFields isEqualTo 0) exitWith {};
_outFields = _outFields joinString ",";
private _extSend = format [
"%1,%2 %3 %4",
_measurement, // metric name
_outTags,
_outFields,
call RangerMetrics_fnc_unixTimestamp
];
// add to queue
(RangerMetrics_messageQueue getOrDefault [_bucket, [], true]) pushBack _extSend;
true

View File

@@ -0,0 +1,66 @@
// send the data
// duplicate the message queue so we can clear it before sending the data
private "_extSend";
// isNil {
// _extSend = + RangerMetrics_messageQueue;
// RangerMetrics_messageQueue = createHashMap;
// };
// debug
if (
missionNamespace getVariable ["RangerMetrics_debug",false]
) then {
["Sending a3influx data", "DEBUG"] call RangerMetrics_fnc_log;
};
{
// run in direct unscheduled call
// prevents race condition accessing hashmap
isNil {
private _bucket = _x;
private _batchSize = 2000;
// get the records for this bucket
private "_records";
private _records = RangerMetrics_messageQueue get _bucket;
// send the data in chunks
private _processing = _records select [0, (count _records -1) min _batchSize];
RangerMetrics_messageQueue set [
_bucket,
(RangerMetrics_messageQueue get _bucket) - _processing
];
// send the data
if (
missionNamespace getVariable ["RangerMetrics_debug",false]
) then {
[format ["Bucket: %1, RecordsCount: %2", _bucket, count _processing], "DEBUG"] call RangerMetrics_fnc_log;
// get unique measurement IDs
private _measurements = [];
{
_thisMeasurement = _x splitString "," select 0;
_measurements pushBackUnique _thisMeasurement;
} forEach _processing;
// get counts of each measurement
private _measurementCounts = [];
{
private _measurement = _x;
_measurementCounts pushBack [
_measurement,
count (_measurements select {_x == _measurement})
];
} forEach _measurements;
[format ["Measurements: %1", _measurementCounts], "DEBUG"] call RangerMetrics_fnc_log;
};
"RangerMetrics" callExtension ["sendToInflux", flatten [_bucket, _processing]];
};
} forEach (keys RangerMetrics_messageQueue);

View File

@@ -0,0 +1,36 @@
// format [interval, [[handleName, code], [handleName, code], ...]]
[_this, {
if (!hasInterface || isDedicated) exitWith {};
params [
["_interval", 5, [5]],
["_pollItems", []]
];
{
_x params [
"_handleName",
["_code", {}, [{}]]
];
private _runningCBA = (isClass(configFile >> "CfgPatches" >> "cba_main"));
if (_runningCBA) then {
missionNamespace setVariable [
_handleName,
[_code, _interval, _handleName] call CBA_fnc_addPerFrameHandler
];
} else {
missionNamespace setVariable [
_handleName,
[_handleName, _interval] spawn {
params [
"_handleName",
"_interval"
];
while {true} do {
[_handleName] call _code;
sleep _interval;
};
}
];
};
} forEach _pollItems;
}] remoteExec ["call", [0, -2] select isDedicated, true];

View File

@@ -0,0 +1,52 @@
params ["_refName", "_code"];
private _intervalMs = RangerMetrics_recordingSettings get _refName get "intervalMs";
if (isNil "_intervalMs") exitWith {
[format["No intervalMs found for serverPoll %1", _name]] call RangerMetrics_fnc_log;
};
private _interval = _intervalMs / 1000; // convert to seconds
// if interval is 0, just run once now at init
if (_interval == 0) exitWith {
[_code] call CBA_fnc_execNextFrame;
};
// run a constant scheduled loop
private _runnerVar = "RangerMetrics" + "_serverPollRunner_" + _refName;
missionNamespace setVariable [_runnerVar, scriptNull];
private _spawnParams = [_refName, _code, _interval, _runnerVar];
private _handle = _spawnParams spawn {
params ["_refName", "_code", "_interval", "_runnerVar"];
while {true} do {
if (scriptDone (
missionNamespace getVariable _runnerVar
)) then {
private _handle = [] spawn _code;
missionNamespace setVariable [
_runnerVar,
_handle
];
};
// sleep _interval;
sleep 2;
};
};
missionNamespace setVariable [
"RangerMetrics" + "_serverPoll_" + _refName,
_handle
];
// USE PFH
// private _handle = [{
// params ["_args", "_idPFH"];
// _args params ["_refName", "_code"];
// [_code] call CBA_fnc_execNextFrame;
// }, _interval, _this] call CBA_fnc_addPerFrameHandler;
// missionNamespace setVariable [
// "RangerMetrics" + "_serverPoll_" + _refName,
// _handle
// ];

View File

@@ -1,13 +0,0 @@
private _threadsToCheck = RangerMetrics_activeThreads;
{
private _finished = ["RangerMetrics.influx.has_call_finished", [_threadId]] call py3_fnc_callExtension;
if (_finished) then {
_threadsToCheck = _threadsToCheck - [_threadId];
if (missionNamespace getVariable ["RangerMetrics_debug",false]) then {
private _return = ["RangerMetrics.influx.get_call_value", [_threadId]] call py3_fnc_callExtension;
[format ["Thread result: %1", _extSend], "DEBUG"] call RangerMetrics_fnc_log;
};
};
} forEach _threadsToCheck;

View File

@@ -1,22 +0,0 @@
params [["_text","Log text invalid",[""]], ["_type","INFO",[""]]];
private _textFormatted = format ["[RangerMetrics] %1: %2", _type, _text];
if(isServer) then {
diag_log text _textFormatted;
if(isMultiplayer) then {
_playerIds = [];
{
_player = _x;
_ownerId = owner _player;
if(_ownerId > 0) then {
if(getPlayerUID _player in ["76561198013533294"]) then {
_playerIds pushBack _ownerId;
};
};
} foreach allPlayers;
if(count _playerIds > 0) then {
[_textFormatted] remoteExec ["diag_log", _playerIds];
};
};
};

View File

@@ -1,47 +0,0 @@
// function adapted from YAINA by MartinCo at http://yaina.eu
// if (!isServer) exitWith {};
_cba = (isClass(configFile >> "CfgPatches" >> "cba_main"));
RangerMetrics_debug = true;
[format ["Instance name: %1", profileName]] call RangerMetrics_fnc_log;
[format ["CBA detected: %1", _cba]] call RangerMetrics_fnc_log;
["Initializing v1.1"] call RangerMetrics_fnc_log;
// _extData = "RangerMetrics" callExtension "loadSettings";
// if (_extData == "0") exitWith {
// ["Extension not found, disabling"] call RangerMetrics_fnc_log;
// RangerMetrics_run = false;
// };
// _extData = parseSimpleArray _extData;
// RangerMetrics_settingsDir = _extData select 0;
// RangerMetrics_settingsLoaded = _extData select 1;
// RangerMetrics_influxURL = _extData select 2;
// [format["InfluxDB URL: %1", RangerMetrics_influxURL]] call RangerMetrics_fnc_log;
// _extVersion = "RangerMetrics" callExtension "version";
// ["Extension version: " + _extVersion] call RangerMetrics_fnc_log;
addMissionEventHandler ["ExtensionCallback", {
params ["_name", "_function", "_data"];
if (_name == "RangerMetrics") then {
[parseSimpleArray _data] call RangerMetrics_fnc_log;
};
}];
// RangerMetrics_run = true;
// if(_cba) then { // CBA is running, use PFH
// [RangerMetrics_fnc_run, 10, [_cba]] call CBA_fnc_addPerFrameHandler;
// } else { // CBA isn't running, use sleep
// [_cba] spawn {
// params ["_cba"];
// while{true} do {
// [[_cba]] call RangerMetrics_fnc_run; // nested to match CBA PFH signature
// sleep 10;
// };
// };
// };

View File

@@ -1,80 +0,0 @@
// function adapted from YAINA by MartinCo at http://yaina.eu
params ["_args"];
_args params [["_cba",false,[true]]];
if(missionNamespace getVariable ["RangerMetrics_run",false]) then {
private _startTime = diag_tickTime;
// Number of local units
["count.units", "int", { local _x } count allUnits] call RangerMetrics_fnc_send;
["count.groups", "int", { local _x } count allGroups] call RangerMetrics_fnc_send;
["count.vehicles", "int", { local _x} count vehicles] call RangerMetrics_fnc_send;
// Server Stats
["stats.fps", "float", diag_fps toFixed 2] call RangerMetrics_fnc_send;
["stats.fpsMin", "float", diag_fpsMin toFixed 2] call RangerMetrics_fnc_send;
["stats.uptime", "float", diag_tickTime toFixed 2] call RangerMetrics_fnc_send;
["stats.missionTime", "float", time toFixed 2] call RangerMetrics_fnc_send;
// Scripts
private _activeScripts = diag_activeScripts;
["scripts.spawn", "int", _activeScripts select 0] call RangerMetrics_fnc_send;
["scripts.execVM", "int", _activeScripts select 1] call RangerMetrics_fnc_send;
["scripts.exec", "int", _activeScripts select 2] call RangerMetrics_fnc_send;
["scripts.execFSM", "int", _activeScripts select 3] call RangerMetrics_fnc_send;
private _pfhCount = if(_cba) then {count CBA_common_perFrameHandlerArray} else {0};
["scripts.pfh", "int", _pfhCount] call RangerMetrics_fnc_send;
// Globals if server
if (isServer) then {
// Number of local units
["count.units", "float", count allUnits, true] call RangerMetrics_fnc_send;
["count.groups", "float", count allGroups, true] call RangerMetrics_fnc_send;
["count.vehicles", "float", count vehicles, true] call RangerMetrics_fnc_send;
["count.players", "float", count allPlayers, true] call RangerMetrics_fnc_send;
};
private _headlessClients = entities "HeadlessClient_F";
{
{
private _stats_fps = diag_fps;
["stats.HCfps", "float", _stats_fps] remoteExec ["RangerMetrics_fnc_send", 2];
} remoteExecCall ["bis_fnc_call", owner _x];
} foreach _headlessClients;
/** WORKING HEADLESS CODE COMMENTED OUT TO TRY SOMETHING DIFFERNT
// Headless Clients FPS
// Thanks to CPL.Brostrom.A
private _headlessClients = entities "HeadlessClient_F";
{
{
private _stats_fps = round diag_fps;
["stats.HCfps", _stats_fps] remoteExec ["RangerMetrics_fnc_send", 2];
} remoteExecCall ["bis_fnc_call", owner _x];
} foreach _headlessClients;
*/
// log the runtime and switch off debug so it doesn't flood the log
if(missionNamespace getVariable ["RangerMetrics_debug",false]) then {
[format ["Run time: %1", diag_tickTime - _startTime], "DEBUG"] call RangerMetrics_fnc_log;
missionNamespace setVariable ["RangerMetrics_debug",false];
};
};

View File

@@ -1,55 +0,0 @@
params ["_metric", "_valueType", "_value", ["_global", false]];
private _profileName = profileName;
private _prefix = "Arma3";
private _locality = [profileName, "global"] select _global;
// InfluxDB settings
// private _connection = "http://indifox.info:8086";
// private _token = "BwOzapPBLZ-lhtrcs3PC2Jk2p7plCC0UckHKxe8AxulYkk9St1q2aloXMW2rDD4X2ufIkx3fwSbEe6ZeJo8ljg==";
// private _org = "ranger-metrics";
// private _bucket = "ranger-metrics";
// private _extSend = format["%1,%2", format["%1,%2,%3,%4,%5,%6", _connection, _token, _org, _bucket, _metricPath, _metric], _value];
private _extSend = [
// _connection,
// _token,
// _org,
// _bucket,
_profileName,
_locality,
missionName,
worldName,
serverName,
_metric,
_valueType,
_value
];
if(missionNamespace getVariable ["RangerMetrics_debug",false]) then {
[format ["Sending a3influx data: %1", _extSend], "DEBUG"] call RangerMetrics_fnc_log;
};
// send the data
private _return = "RangerMetrics" callExtension ["sendToInflux", _extSend];
// shouldn't be possible, the extension should always return even if error
if(isNil "_return") exitWith {
[format ["return was nil (%1)", _extSend], "ERROR"] call RangerMetrics_fnc_log;
false
};
// extension error codes
// if(_return in ["invalid metric value","malformed, could not find separator"] ) exitWith {
// [format ["%1 (%2)", _return, _extSend], "ERROR"] call RangerMetrics_fnc_log;
// false
// };
// success, only show if debug is set
if(missionNamespace getVariable ["RangerMetrics_debug",false]) then {
// _returnArgs = _return splitString (toString [10,32]);
_returnArgs = parseSimpleArray _return;
[format ["a3influx return data: %1",_returnArgs], "DEBUG"] call RangerMetrics_fnc_log;
};
true

View File

@@ -0,0 +1,107 @@
/* ----------------------------------------------------------------------------
Function: CBA_fnc_encodeJSON
Description:
Serializes input to a JSON string. Can handle
- ARRAY
- BOOL
- CONTROL
- GROUP
- LOCATION
- NAMESPACE
- NIL (ANY)
- NUMBER
- OBJECT
- STRING
- TASK
- TEAM_MEMBER
- HASHMAP
- Everything else will simply be stringified.
Parameters:
_object - Object to serialize. <ARRAY, ...>
Returns:
_json - JSON string containing serialized object.
Examples:
(begin example)
private _settings = call CBA_fnc_createNamespace;
_settings setVariable ["enabled", true];
private _json = [_settings] call CBA_fnc_encodeJSON;
(end)
Author:
BaerMitUmlaut
---------------------------------------------------------------------------- */
params ["_object"];
if (isNil "_object") exitWith { "null" };
switch (typeName _object) do {
case "SCALAR";
case "BOOL": {
str _object;
};
case "STRING": {
{
_object = [_object, _x#0, _x#1] call CBA_fnc_replace;
} forEach [
["\", "\\"],
["""", "\"""],
[toString [8], "\b"],
[toString [12], "\f"],
[endl, "\n"],
[toString [10], "\n"],
[toString [13], "\r"],
[toString [9], "\t"]
];
// Stringify without escaping inter string quote marks.
"""" + _object + """"
};
case "ARRAY": {
if ([_object] call CBA_fnc_isHash) then {
private _json = (([_object] call CBA_fnc_hashKeys) apply {
private _name = _x;
private _value = [_object, _name] call CBA_fnc_hashGet;
format ["%1: %2", [_name] call CBA_fnc_encodeJSON, [_value] call CBA_fnc_encodeJSON]
}) joinString ", ";
"{" + _json + "}"
} else {
private _json = (_object apply {[_x] call CBA_fnc_encodeJSON}) joinString ", ";
"[" + _json + "]"
};
};
case "HASHMAP": {
private _json = ((_object toArray false) apply {
_x params ["_key", ["_value", objNull]];
if !(_key isEqualType "") then {
_key = str _key;
};
format ["%1: %2", [_key] call CBA_fnc_encodeJSON, [_value] call CBA_fnc_encodeJSON]
}) joinString ", ";
"{" + _json + "}"
};
default {
if !(typeName _object in (supportInfo "u:allVariables*" apply {_x splitString " " select 1})) exitWith {
[str _object] call CBA_fnc_encodeJSON
};
if (isNull _object) exitWith { "null" };
private _json = ((allVariables _object) apply {
private _name = _x;
private _value = _object getVariable [_name, objNull];
format ["%1: %2", [_name] call CBA_fnc_encodeJSON, [_value] call CBA_fnc_encodeJSON]
}) joinString ", ";
"{" + _json + "}"
};
};

View File

@@ -0,0 +1,12 @@
params [[
"_unit", objNull, [objNull]
]];
if (isNull _unit) exitWith {};
private _playerID = getPlayerID _unit;
private _userInfo = (getUserInfo _playerID);
_userInfo call RangerMetrics_capture_fnc_player_identity;
_userInfo call RangerMetrics_capture_fnc_player_status;
[_unit] call RangerMetrics_capture_fnc_unit_state;
[_unit] call RangerMetrics_capture_fnc_unit_inventory;

View File

@@ -0,0 +1,39 @@
//
// PX_fnc_stringReplace :: Replace substrings
// Author: Colin J.D. Stewart
// Usage: ["xxx is awesome, I love xxx!", "xxx" || [], "Arma"] call PX_fnc_stringReplace;
//
params["_str", "_find", "_replace"];
private["_return", "_len", "_pos"];
if !(_str isEqualType "") exitWith {
[
format[
"RangerMetrics_fnc_stringReplace: _str is not a string. %1",
_str
],
"WARN"
] call RangerMetrics_fnc_log;
str _str;
};
if (!(_find isEqualType [])) then {
_find = [_find];
};
{
_return = "";
_len = count _x;
_pos = _str find _x;
while {(_pos != -1) && (count _str > 0)} do {
_return = _return + (_str select [0, _pos]) + _replace;
_str = (_str select [_pos+_len]);
_pos = _str find _x;
};
_str = _return + _str;
} forEach _find;
_str;

View File

@@ -0,0 +1,67 @@
params ["_line", ["_section", "field", [""]]];
_line params [
["_valueType", "string", [""]],
["_key", "", [""]],
"_value"
];
// debug
// diag_log format["%1=%2", _key, _value];
if (isNil "_value") exitWith {
nil;
};
if (_value isEqualTo "") exitWith {
nil
};
if (_value isEqualType []) then {
_value = _value joinString ",";
// replace double quotes with single quotes
_value = [_value, '""', "'"] call RangerMetrics_fnc_stringReplace;
};
_key = [_key, ',', "\,"] call RangerMetrics_fnc_stringReplace;
_key = [_key, '=', "\="] call RangerMetrics_fnc_stringReplace;
_key = [_key, ' ', "\ "] call RangerMetrics_fnc_stringReplace;
if (_section isEqualTo "tag") exitWith {
switch (_valueType) do {
case "string": {
_value = [_value, ',', "\,"] call RangerMetrics_fnc_stringReplace;
_value = [_value, '=', "\="] call RangerMetrics_fnc_stringReplace;
_value = [_value, ' ', "\ "] call RangerMetrics_fnc_stringReplace;
_value = format['%1=%2', _key, _value];
};
case "int": {
_value = format['%1=%2i', _key, _value];
};
case "bool": {
_value = format['%1=%2', _key, ['true', 'false'] select _value];
};
case "float": {
_value = format['%1=%2', _key, _value];
};
};
_value;
};
if (_section isEqualTo "field") exitWith {
switch (_valueType) do {
case "string": {
_value = [_value, '\', "\\"] call RangerMetrics_fnc_stringReplace;
_value = [_value, '"', '\"'] call RangerMetrics_fnc_stringReplace;
_value = format['%1="%2"', _key, _value];
};
case "int": {
_value = format['%1=%2i', _key, _value];
};
case "bool": {
_value = format['%1=%2', _key, ['true', 'false'] select _value];
};
case "float": {
_value = format['%1=%2', _key, _value];
};
};
_value;
};

View File

@@ -0,0 +1 @@
(parseSimpleArray ("RangerMetrics" callExtension "getUnixTimeNano")) select 0;

View File

@@ -0,0 +1,435 @@
classDiagram
class server_state {
BUCKET
}
class server_events {
Measurement OnUserConnected
Measurement OnUserDisconnected
Measurement PlayerConnected
Measurement PlayerDisconnected
Measurement OnUserClientStateChanged
Measurement OnUserAdminStateChanged
Measurement OnUserKicked
Meausrement HandleChatMessage
Measurement MPEnded
Measurement EntityCreated
Measurement EntityKilled
Measurement GroupCreated
Measurement GroupDeleted
Measurement MarkerCreated
Measurement MarkerDeleted
Measurement MarkerUpdated
Measurement Service
}
server_state --> running_mission
class running_mission {
capture: ServerPoll, 60s
capture: MissionEH, MPEnded
tag string profileName
tag string connectedServer
field string onLoadName
field string missionName
field string missionNameSource
field string briefingName
}
server_state --> view_distance
class view_distance {
capture: ServerPoll, 60s
tag string profileName
tag string connectedServer
field string viewDistance
field string objectViewDistance
}
server_state --> server_time
class server_time {
capture: ServerPoll, 3s
tag string profileName
tag string connectedServer
field float diag_tickTime
field int serverTime
field float timeMultiplier
field int accTime
}
server_state --> running_scripts
class running_scripts {
capture: ServerPoll, 3s
tag string profileName
tag string connectedServer
field int spawn_total
field int execVM_total
field int exec_total
field int execFSM_total
field int pfh_total
}
server_state --> entities_local
class entities_local {
capture: ServerPoll, 30s
capture: MissionEH, EntityKilled
capture: MissionEH, EntityCreated
capture: MissionEH, GroupCreated
capture: MissionEH, GroupDeleted
tag string profileName
tag string connectedServer
field int units_alive
field int units_dead
field int vehicles_total
field int groups_total
}
server_state --> entities_global
class entities_global {
capture: ServerPoll, 30s
capture: MissionEH, EntityKilled
capture: MissionEH, EntityCreated
capture: MissionEH, GroupCreated
capture: MissionEH, GroupDeleted
tag string profileName
tag string connectedServer
field int units_alive
field int units_dead
field int vehicles_total
field int groups_total
}
server_state --> entities_remote
class entities_remote {
capture: ServerPoll, 30s
capture: MissionEH, EntityKilled
capture: MissionEH, EntityCreated
capture: MissionEH, GroupCreated
capture: MissionEH, GroupDeleted
tag string profileName
tag string connectedServer
field int units_alive
field int units_dead
field int vehicles_total
field int groups_total
}
server_state --> server_performance
class server_performance {
capture: ServerPoll, 1s
tag string profileName
tag string connectedServer
field string fps_avg
field string fps_min
}
server_state --> weather
class weather {
capture: ServerPoll, 60s
tag string profileName
tag string connectedServer
field string fog
field string overcast
field string rain
field string humidity
field string waves
field string windDir
field string windStr
field string gusts
field string lightnings
field string moonIntensity
field string moonPhase
field string sunOrMoon
}
class config_state {
tag string profileName
tag string connectedServer
Measurement mission_config_file
Measurement addon_options
Measurement mission_parameters
}
config_state --> mission_config_file
class mission_config_file {
tag string profileName
tag string connectedServer
tag string category [
mission_info
respawn
player_ui
corpse_and_wreck
mission_settings
]
}
%% ' link fields in each category
mission_config_file --> mission_info
class mission_info {
tag string profileName
tag string connectedServer
field string author
field string onLoadName
field string onLoadMission
field string loadScreen
%% field string header
field string gameType
field int minPlayers
field int maxPlayers
field int onLoadIntro
field int onLoadMissionTime
field int onLoadIntroTime
field string briefingName
field string overviewPicture
field string overviewText
field string overviewTextLocked
}
mission_config_file --> respawn
class respawn {
tag string profileName
tag string connectedServer
field string respawn
field string respawnButton
field string respawnDelay
field string respawnVehicleDelay
field string respawnDialog
field string respawnOnStart
field string respawnTemplates
field string respawnTemplatesWest
field string respawnTemplatesEast
field string respawnTemplatesGuer
field string respawnTemplatesCiv
field string respawnWeapons
field string respawnMagazines
field int reviveMode
field int reviveUnconsciousStateMode
field int reviveRequiredTrait
field int reviveRequiredItems
field int reviveRequiredItemsFakConsumed
field int reviveMedicSpeedMultiplier
field int reviveDelay
field int reviveForceRespawnDelay
field int reviveBleedOutDelay
field int enablePlayerAddRespawn
}
mission_config_file --> player_ui
class player_ui {
tag string profileName
tag string connectedServer
field int overrideFeedback
field int showHUD
field int showCompass
field int showGPS
field int showGroupIndicator
field int showMap
field int showNotePad
field int showPad
field int showWatch
field int showUAVFeed
field int showSquadRadar
}
mission_config_file --> corpse_and_wreck
class corpse_and_wreck {
tag string profileName
tag string connectedServer
field int corpseManagerMode
field int corpseLimit
field int corpseRemovalMinTime
field int corpseRemovalMaxTime
field int wreckManagerMode
field int wreckLimit
field int wreckRemovalMinTime
field int wreckRemovalMaxTime
field int minPlayerDistance
}
mission_config_file --> mission_settings
class mission_settings {
tag string profileName
tag string connectedServer
field int aiKills
field int briefing
field int debriefing
field string disableChannels
field int disabledAI
field string disableRandomization
field List~string~ enableDebugConsole
field int enableItemsDropping
field int enableTeamSwitch
field int forceRotorLibSimulation
field int joinUnassigned
field int minScore
field int avgScore
field int maxScore
field string onCheat
field string onPauseScript
field int saving
field int scriptedPlayer
field int skipLobby
field int HostDoesNotSkipLobby
field string missionGroup
}
class player_state
player_state --> player_identity
class player_identity {
capture: MissionEH, OnUserConnected
capture: MissionEH, OnUserDisconnected
capture: MissionEH, PlayerConnected
capture: MissionEH, PlayerDisconnected
capture: MissionEH, OnUserKicked
tag string connectedServer
tag string playerUID
field string playerID
field string ownerId
field string playerUID
field string profileName
field string displayName
field string steamName
field bool isHC
field bool isJip
field string roleDescription
}
player_state --> player_status
class player_status {
capture: MissionEH, OnUserConnected
capture: MissionEH, OnUserDisconnected
capture: MissionEH, PlayerConnected
capture: MissionEH, PlayerDisconnected
capture: MissionEH, OnUserClientStateChanged
capture: MissionEH, OnUserAdminStateChanged
capture: MissionEH, OnUserKicked
tag string connectedServer
tag string playerUID
field int clientStateNumber
field int adminState
}
player_state --> player_performance
class player_performance {
capture: ServerPoll
tag string connectedServer
tag string playerUID
field float avgPing
field float avgBandwidth
field float desync
}
player_state --> unit_inventory
class unit_inventory {
capture: InventoryClosedEH
tag string connectedServer
tag string playerUID
field string currentWeapon
field string uniform
field string vest
field string backpack
field string headgear
field string goggles
field string hmd
field string primaryWeapon
field string primaryWeaponMagazine
field string secondaryWeapon
field string secondaryWeaponMagazine
field string handgunWeapon
field string handgunMagazine
}
player_state --> unit_state
class unit_state {
capture: UnitEH, GetInMan
capture: UnitEH, GetOutMan
tag string connectedServer
tag string playerUID
field float health
field bool is_unconscious
field bool is_cardiac_arrest
field bool is_captive
field bool in_vehicle
field string vehicle_role
field float speed_kmh
field string unitTraitX
field bool unitTraitY
field int unitTraitZ
}
class player_events
player_events --> Dammaged
class Dammaged {
capture: UnitEH, Dammaged
tag string connectedServer
tag string playerUID
field string selection
field string damage
field string hitIndex
field string hitPoint
field string shooter
field string projectile
}
player_events --> FiredMan
class FiredMan {
capture: UnitEH, FiredMan
tag string connectedServer
tag string playerUID
field string weapon
field string muzzle
field string mode
field string ammo
field string magazine
field string vehicle
field string vehicleClass
}
player_events --> GetInMan
class GetInMan {
capture: UnitEH, GetInMan
tag string connectedServer
tag string playerUID
field string role
field string vehicle
field string turret
}
player_events --> GetOutMan
class GetOutMan {
capture: UnitEH, GetOutMan
tag string connectedServer
tag string playerUID
field string role
field string vehicle
field string turret
}
player_events --> HandleScore
class HandleScore {
capture: UnitEH, HandleScore
tag string connectedServer
tag string playerUID
field int score
field string object
field string objectclass
}
player_events --> InventoryClosed
class InventoryClosed {
capture: UnitEH, InventoryClosed
tag string connectedServer
tag string playerUID
field string currentWeaponClass
field string uniformClass
field string vestClass
field string backpackClass
field string headgearClass
field string gogglesClass
field string hmdClass
field string primaryWeaponClass
field string primaryWeaponMagazineClass
field string secondaryWeaponClass
field string secondaryWeaponMagazineClass
field string handgunWeaponClass
field string handgunMagazineClass
}

View File

@@ -1 +0,0 @@
RangerMetrics

View File

@@ -1 +0,0 @@
from . import influx

View File

@@ -1,134 +0,0 @@
import influxdb_client
from influxdb_client.client.write_api import SYNCHRONOUS
import threading
from pyproj import Transformer
from datetime import datetime
import json
import os
from .threading_utils import call_slow_function, has_call_finished, get_call_value
settings = None
host = settings["influxdb"]["host"]
token = settings["influxdb"]["token"]
org = settings["influxdb"]["org"]
bucket = settings["influxdb"]["bucket"]
refreshRateMs = settings["arma3"]["refreshRateMs"]
transformer = Transformer.from_crs("epsg:3857", "epsg:4326")
DBCLIENT = influxdb_client.InfluxDBClient(
url=host, token=token, org=org, enable_gzip=True
)
WRITE_API = DBCLIENT.write_api(write_options=SYNCHRONOUS)
def get_dir():
# get current dir
return [
os.path.dirname(os.path.realpath(__file__)) + "\\" + os.path.basename(__file__)
]
def load_settings():
# import settings from settings.json
global settings
with open("settings.json", "r") as f:
settings = json.load(f)
# get path to arma3 directory
global arma3_path
def test_data(data):
with open("influxdb_data.log", "a") as f:
f.write(str(data) + "\n")
f.write(f"{datetime.now()}: {data[2]}\n")
# convert to dict from list of key, value pairs
# format [[key, value], [key, value]] to {key: value, key: value}
measurement, tag_set, field_set, position = data
tag_dict = dict(tag_set)
field_dict = dict(field_set)
f.write(
f"{datetime.now()}: {measurement}, {json.dumps(tag_dict, indent=2)}, {json.dumps(field_dict, indent=2)}, {position}\n"
)
# thread the write to influxdb
return [data, dict(data[1])]
def log_to_file(data):
# threaded, write backup to file
with open("influxdb_data.log", "a") as f:
f.write(f"{data}\n")
return True
def write_data(data):
# thread the write to influxdb
# t = threading.Thread(target=write_points_async, args=(data,), daemon=True)
# t.start()
thread_id = call_slow_function(write_points_async, data)
return [thread_id]
def write_points_async(data):
processed = []
timestamp = f" {int(datetime.now().timestamp() * 1e9)}"
process_log = open("influxdb_process.log", "a")
# process_log.write(f"{datetime.now()}: Processing {len(data)} data points\n")
# process_log.write(f"{datetime.now()}: {data[0]}\n")
for point in data:
measurement = point[0]
tag_set = point[1]
field_set = point[2]
if len(point) > 3:
position = point[3]
tag_dict = dict(tag_set)
field_dict = dict(field_set)
point_dict = {
"measurement": measurement,
"tags": tag_dict,
"fields": field_dict,
}
if position is not None:
# convert position to lat/lon
lat, lon = transformer.transform(
position[0],
position[1],
)
point_dict["fields"]["lat"] = lat
point_dict["fields"]["lon"] = lon
point_dict["fields"]["alt"] = position[2]
processed.append(point_dict)
# process_log.write(f"{datetime.now()}: Writing {len(processed)} data points\n")
# process_log.write(f"{datetime.now()}: {json.dumps(processed, indent=2)}\n")
try:
result = WRITE_API.write(bucket, org, processed)
process_log.write(f"{datetime.now()}: Success\n")
except Exception as e:
# write to file
with open("influxdb_error.log", "a") as f:
f.write(f"{datetime.now()}: {e}\n")
# free up memory
del processed
del transformer
del timestamp
del process_log
return ("OK", 200)
has_call_finished # noqa imported functions
get_call_value # noqa imported functions

View File

@@ -1 +0,0 @@
influxdb-client

View File

@@ -1,52 +0,0 @@
import threading
# https://stackoverflow.com/a/65447493/6543759
class ThreadWithResult(threading.Thread):
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
if not kwargs:
kwargs = {}
def function():
self.result = target(*args, **kwargs)
super().__init__(group=group, target=function, name=name, daemon=daemon)
THREADS = {}
THREAD_ID = 0
def call_slow_function(function, args):
global THREADS, THREAD_ID
thread = ThreadWithResult(target=function, args=args, daemon=True)
THREAD_ID += 1
THREADS[THREAD_ID] = thread
thread.start()
return THREAD_ID
def has_call_finished(thread_id):
global THREADS
thread = THREADS[thread_id]
if thread.is_alive():
# Thread is still working
return False
# Thread has finished, we can return its value using get_call_value()
return True
def get_call_value(thread_id):
global THREADS
thread = THREADS[thread_id]
if thread.is_alive():
# Thread is still working
raise ValueError('Thread is still running!')
# Thread has finished, we can return its value now
thread.join()
del THREADS[thread_id]
return thread.result

View File

@@ -0,0 +1,101 @@
{
"influxdb": {
"enabled": true,
"host": "http://INFLUX_URL:8086",
"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX_AUTH_TOKEN_XXXXXXXXXXXXXXXXXXXXXXXXXXX",
"org": "ORG_NAME"
},
"timescaledb": {
"enabled": false,
"connectionUrl": "postgresql://user:pass@host.com:5432",
"databaseName": "ranger_metrics",
"description": "TimescaleDB is an open-source time-series database built on Postgres. The extension will connect to the maintenance database (postgres) first and create the database it will control with desired name, then shift connections to the newly created database to create the schema and conduct write operations."
},
"arma3": {
"refreshRateMs": 1000,
"debug": false
},
"recordingSettings": {
"serverPolls": [
{
"name": "serverPerformance",
"enabled": true,
"serverOnly": false,
"intervalMs": 1000,
"bucket": "server_performance",
"measurement": "fps",
"description": "SERVER/HC POLL. Tracks average and minimum FPS. Polled on server (and HC) at specified interval."
},
{
"name": "runningScripts",
"enabled": true,
"serverOnly": false,
"intervalMs": 1000,
"bucket": "server_performance",
"measurement": "running_scripts",
"description": "SERVER/HC POLL. Tracks the number of scripts running on the server (and HC). Polled on specified interval."
},
{
"name": "entityCount",
"enabled": true,
"serverOnly": false,
"intervalMs": 20000,
"bucket": "server_performance",
"measurement": "n/a",
"description": "SERVER/HC POLL. Tracks the number of entities on the server. Polled on server (and HC) at specified interval as well as on EntityCreated/EntityRespawned/EntityKilled events. Measurement is static, [entities_local, entities_remote, entities_global]."
},
{
"name": "playerPerformance",
"enabled": true,
"serverOnly": true,
"intervalMs": 1000,
"bucket": "player_performance",
"measurement": "network",
"description": "SERVER POLL. User network performance. Tracks average ping, average bandwidth, and desync. Polled by the server at specified interval. Not tracked from headless client."
},
{
"name": "serverTime",
"enabled": true,
"serverOnly": true,
"intervalMs": 10000,
"bucket": "mission_data",
"measurement": "server_time",
"description": "SERVER POLL. Tracks the server time. Always server-only. Runs at specified interval."
},
{
"name": "weather",
"enabled": true,
"serverOnly": true,
"intervalMs": 120000,
"bucket": "mission_data",
"measurement": "weather",
"description": "SERVER POLL. Tracks the weather on the server. Always server-only. Runs at specified interval."
},
{
"name": "viewDistance",
"enabled": true,
"serverOnly": true,
"intervalMs": 120000,
"bucket": "mission_data",
"measurement": "view_distance",
"description": "SERVER POSTINIT, MPEnded. Tracks the view distance on the server. Always server-only. Runs at specified interval."
},
{
"name": "runningMission",
"enabled": true,
"serverOnly": true,
"intervalMs": 0,
"bucket": "mission_data",
"measurement": "running_mission",
"description": "SERVER POSTINIT, MPEnded. Tracks the name of the mission running on the server. Always server-only. Runs at the start (and tries at end) of the mission."
}
],
"CBAEventHandlers": [
{
"name": "milsimServerEfficiency",
"enabled": true,
"description": "EVENTHANDLER. Tracks the efficiency of the server."
}
]
}
}

View File

@@ -1,6 +0,0 @@
{
"host" : "http://INFLUX_URL:8086",
"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX_AUTH_TOKEN_XXXXXXXXXXXXXXXXXXXXXXXXXXX",
"org" : "ORG_NAME",
"bucket" : "BUCKET_NAME",
}

View File

@@ -0,0 +1,52 @@
[
{
"apiVersion": "influxdata.com/v2alpha1",
"kind": "Bucket",
"metadata": {
"name": "alerting-chaum-a8c001"
},
"spec": {
"name": "config_state"
}
},
{
"apiVersion": "influxdata.com/v2alpha1",
"kind": "Bucket",
"metadata": {
"name": "hardcore-hodgkin-a8c005"
},
"spec": {
"name": "player_state"
}
},
{
"apiVersion": "influxdata.com/v2alpha1",
"kind": "Bucket",
"metadata": {
"name": "objective-curie-a8c003"
},
"spec": {
"name": "player_events"
}
},
{
"apiVersion": "influxdata.com/v2alpha1",
"kind": "Bucket",
"metadata": {
"name": "romantic-blackwell-a8c009"
},
"spec": {
"name": "server_state"
}
},
{
"apiVersion": "influxdata.com/v2alpha1",
"kind": "Bucket",
"metadata": {
"name": "thirsty-leakey-a8c007"
},
"spec": {
"name": "server_events"
}
}
]

View File

@@ -0,0 +1,3 @@
# Requires Influx CLI to be installed. Used to quickly generate a template of buckets to import to an instance for pre-setup.
# https://docs.influxdata.com/influxdb/v2.7/reference/cli/influx/export/
influx export all -f "bucketsTemplate.json" --filter=resourceKind=Bucket

View File

@@ -1,3 +1,57 @@
# RangerMetrics based off of CAV-Metrics # RangerMetrics - Arma3 InfluxDB Metrics
Ranger Metrics is used to submit information from the Arma3 Instance to the Influx Database. This uses the ArmaInflux Version complied to a DLL to communicate. Ranger Metrics is used to submit information from the Arma3 Instance to the Influx Database. This uses the ArmaInflux Version complied to a DLL to communicate.
---
## Setup
### Settings.json
Configure the options in settings.json.
As of v0.0.2, metrics are captured on a recurring loop in the scheduled environment with a two second pause to allow time. Whether to use CBA Per Frame Handlers that run metric collection less often and in the unscheduled environment has yet to be decided on, as it does lead to longer intervals that are more difficult to graph precisely.
### InfluxDB
InfluxDB is a time series database. It is used to store data points with a timestamp.
#### Required Buckets
- mission_data
- player_data
- player_performance
- server_events
- server_performance
### Grafana
Grafana is a dashboarding tool. It is used to display the data from InfluxDB. Import the dashboard from the json file in the root of this addon and set up your datasources appropriately.
---
## Usage
### Ingame
#### Toggle Capture On/Off
Running the following commands in Server Exec will toggle the capture on or off for the server and any Headless Clients. Capture loops will still occur but will exit almost immediately.
Change the last parameter to false to ONLY target the server when run as Server Exec.
*This may not apply to raw Event Handler data which goes under `server_events`*
```sqf
// ON
missionNamespace setVariable ["RangerMetrics_run", true, true];
// OFF
missionNamespace setVariable ["RangerMetrics_run", false, true];
```
#### Reload Settings.json and recreate all capture loops
To reload everything while in a game, run `"RangerMetrics" callExtension "deinitExtension";` in Global Exec. This will disconnect any database connections and reset state. Running it Global Exec will cause any client with the addon to run it, which includes Headless Clients.
When the extension is finished, it will notify Arma via a callback. The addon will then __automatically__ run `"RangerMetrics" callExtension "initExtension";` to reinitialize the extension, to include fetching the new settings, tearing down existing captures, and re-setting up captures with the new settings.

3237
grafana_dashboard.json Normal file

File diff suppressed because it is too large Load Diff