working. refining schema, fix double quotes in tag values

This commit is contained in:
2023-04-09 19:02:57 -07:00
parent 4d8d8e44f5
commit 98f5339fef
32 changed files with 900 additions and 399 deletions

View File

@@ -10,6 +10,34 @@ class CfgPatches {
};
class CfgFunctions {
class RangerMetrics_cDefinitions {
class functions {
file = "\RangerMetrics\functions\captureDefinitions";
class server_poll {};
class server_missionEH {};
class client_poll {};
// class clientEvent {};
};
};
class RangerMetrics_capture {
// these names represent measurement names send to InfluxDB - snake case
class functions {
file = "\RangerMetrics\functions\capture";
class chat_message {};
class entities_global {};
class entities_local {};
class mission_config_file {};
class player_identity {};
class player_loadout {};
class player_performance {};
class player_status {};
class running_mission {};
class running_scripts {};
class server_performance {};
class server_time {};
class weather {};
};
};
class RangerMetrics {
class core {
file = "\RangerMetrics\functions\core";
@@ -18,11 +46,14 @@ class CfgFunctions {
class log {};
class queue {};
class send {};
class callbackHandler {};
class sendClientPoll {};
class startServerPoll {};
};
class eventHandlers {
file = "\RangerMetrics\functions\eventHandlers";
class addHandlers {};
class callbackHandler {};
};
class helpers {
file = "\RangerMetrics\functions\helpers";
@@ -31,19 +62,5 @@ class CfgFunctions {
class stringReplace {};
class unixTimestamp {};
};
class measurements {
file = "\RangerMetrics\functions\measurements";
class chat_message {};
class entities_global {};
class entities_local {};
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 {};
};
};
};

View File

@@ -0,0 +1,40 @@
params ["_channel", "_owner", "_from", "_text", "_person", "_name", "_strID", "_forcedDisplay", "_isPlayerMessage", "_sentenceType", "_chatMessageType"];
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]
];
// 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 (isNil "_person") then {
_playerUid = "";
} else {
if !(objNull isEqualType _person) then {
_playerUid = getPlayerUID _person;
} else {
_playerUid = "";
};
};
if (_playerUid isNotEqualTo "") then {
_fields pushBack ["string", "playerUid", _playerUid];
};
[
"server_events",
"HandleChatMessage",
nil,
_fields
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,7 @@
// Number of global units
["server_state", "entities_global", nil, [
["int", "units_alive", count allUnits ],
["int", "units_dead", count allDeadMen],
["int", "groups_total", count allGroups],
["int", "vehicles_total", count vehicles]
]] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,7 @@
// Number of local units
["server_state", "entities_local", nil, [
["int", "units_alive", { local _x} count allUnits ],
["int", "units_dead", { local _x } count allDeadMen],
["int", "groups_total", { local _x } count allGroups],
["int", "vehicles_total", { local _x } count vehicles]
]] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,210 @@
// 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,30 @@
params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit", ["_jip", false]];
// _networkInfo params ["_avgPing", "_avgBandwidth", "_desync"];
private _fields = [
["string", "playerID", _playerID],
["string", "ownerId", _ownerId],
["string", "playerUID", _playerUID],
["string", "profileName", _profileName],
["string", "displayName", _displayName],
["string", "steamName", _steamName],
["bool", "isHC", _isHC],
["bool", "isJip", _jip]
];
if (!isNil "_unit") then {
private _roleDescription = roleDescription _unit;
if (_roleDescription isNotEqualTo "") then {
_fields pushBack ["string", "roleDescription", _roleDescription];
};
};
[
"player_state",
"player_identity",
[
["string", "playerUID", getPlayerUID player]
],
_fields,
nil
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,44 @@
// loadout data, captured clientside
if (isNull player) exitWith {};
params ["_handleName"];
private _lastLoadout = player getVariable "RangerMetrics_myLoadout";
if (isNil "_lastLoadout") then {
_lastLoadout = [];
};
private _currentLoadout = [
["string", "currentWeapon", currentWeapon player],
["string", "uniform", uniform player],
["string", "vest", vest player],
["string", "backpack", backpack player],
["string", "headgear", headgear player],
["string", "goggles", goggles player],
["string", "hmd", hmd player],
["string", "primaryWeapon", primaryWeapon player],
["string", "primaryWeaponMagazine", primaryWeaponMagazine player],
["string", "secondaryWeapon", secondaryWeapon player],
["string", "secondaryWeaponMagazine", secondaryWeaponMagazine player],
["string", "handgunWeapon", handgunWeapon player],
["string", "handgunMagazine", handgunMagazine player]
];
// exit if loadout hasn't changed
if (_lastLoadout isEqualTo _currentLoadout) exitWith {};
// continue if loadout has changed
// store loadout data locally
player setVariable ["RangerMetrics_myLoadout", _currentLoadout];
// send loadout data to server
[
"player_state", // bucket to store the data
"player_loadout", // measurement classifier inside of bucket
[ // tags
["string", "playerUID", getPlayerUID player]
],
_currentLoadout, // fields
nil
] remoteExec ["RangerMetrics_fnc_queue", 2];

View File

@@ -0,0 +1,17 @@
{
_x params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
_networkInfo params ["_avgPing", "_avgBandwidth", "_desync"];
[
"player_state",
"player_performance",
[["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,12 @@
params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
// _networkInfo params ["_avgPing", "_avgBandwidth", "_desync"];
["player_state", "player_status",
[["string", "playerUID", _playerUID]],
[
["int", "clientStateNumber", _clientState],
["int", "adminState", _adminState]
],
nil
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,16 @@
// Mission name
[
"server_state", // bucket to store the data
"running_mission", // measurement classifier inside of bucket
nil, // tags
[ // fields
[
"string",
"onLoadName",
getMissionConfigValue ["onLoadName", ""]
],
["string","briefingName", briefingName],
["string","missionName", missionName],
["string","missionNameSource", missionNameSource]
]
] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,7 @@
["server_state", "running_scripts", 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,4 @@
["server_state", "server_performance", nil, [
["float", "avg", diag_fps toFixed 2],
["float", "min", diag_fpsMin toFixed 2]
]] call RangerMetrics_fnc_queue;

View File

@@ -0,0 +1,6 @@
["server_state", "server_time", 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,19 @@
[
"server_state", // bucket to store the data
"weather", // measurement classifier inside of bucket
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,81 @@
[
["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;
}],
["OnUserConnected", {
params ["_networkId", "_clientStateNumber", "_clientState"];
(getUserInfo _networkId) call RangerMetrics_capture_fnc_player_identity;
(getUserInfo _networkId) call RangerMetrics_capture_fnc_player_status;
["server_events", "UserConnected", nil, [
["string", "networkId", _networkId],
["int", "clientStateNumber", _clientStateNumber],
["string", "clientState", _clientState]
]] call RangerMetrics_fnc_queue;
}],
["OnUserDisconnected", {
params ["_networkId", "_clientStateNumber", "_clientState"];
(getUserInfo _networkId) call RangerMetrics_capture_fnc_player_identity;
(getUserInfo _networkId) call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserDisconnected", nil, [
["string", "networkId", _networkId],
["int", "clientStateNumber", _clientStateNumber],
["string", "clientState", _clientState]
]] call RangerMetrics_fnc_queue;
}],
["PlayerConnected", {
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
(getUserInfo _idstr) call RangerMetrics_capture_fnc_player_identity;
(getUserInfo _idstr) call RangerMetrics_capture_fnc_player_status;
["server_events", "PlayerConnected", nil, [
["int", "id", _id],
["string", "uid", _uid],
["string", "name", _name],
["bool", "jip", _jip],
["int", "owner", _owner],
["string", "idstr", _idstr]
]] call RangerMetrics_fnc_queue;
}],
["PlayerDisconnected", {
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
(getUserInfo _idstr) call RangerMetrics_capture_fnc_player_identity;
(getUserInfo _idstr) call RangerMetrics_capture_fnc_player_status;
["server_events", "PlayerDisconnected", nil, [
["int", "id", _id],
["string", "uid", _uid],
["string", "name", _name],
["bool", "jip", _jip],
["int", "owner", _owner],
["string", "idstr", _idstr]
]] call RangerMetrics_fnc_queue;
}],
["OnUserClientStateChanged", {
params ["_networkId", "_clientStateNumber", "_clientState"];
(getUserInfo _networkId) call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserClientStateChanged", nil, [
["string", "networkId", _networkId],
["int", "clientStateNumber", _clientStateNumber],
["string", "clientState", _clientState]
]] call RangerMetrics_fnc_queue;
}],
["OnUserAdminStateChanged", {
params ["_networkId", "_loggedIn", "_votedIn"];
(getUserInfo _networkId) call RangerMetrics_capture_fnc_player_status;
["server_events", "OnUserAdminStateChanged", nil, [
["string", "networkId", _networkId],
["bool", "loggedIn", _loggedIn],
["bool", "votedIn", _votedIn]
]] call RangerMetrics_fnc_queue;
}],
["HandleChatMessage", {
_this call RangerMetrics_capture_fnc_chat_message;
// don't interfaere with the chat message
false;
}]
]

View File

@@ -0,0 +1,19 @@
[
[
1, // interval
[ // functions to run
RangerMetrics_capture_fnc_server_performance,
RangerMetrics_capture_fnc_running_scripts,
RangerMetrics_capture_fnc_server_time,
RangerMetrics_capture_fnc_entities_local,
RangerMetrics_capture_fnc_entities_global,
RangerMetrics_capture_fnc_player_performance
]
],
[
60,
[
RangerMetrics_capture_fnc_weather
]
]
]

View File

@@ -0,0 +1,14 @@
params ["_name", "_function", "_data"];
if (_name == "RangerMetrics") then {
if (isNil "_data") then {_data = ""};
try {
if (_data isEqualType "") exitWith {
_data = parseSimpleArray _data;
_data call RangerMetrics_fnc_log;
};
diag_log format ["Callback unsupported type: %1: %2", _function, _data];
} catch {
_data = format ["%1", _data];
};
};

View File

@@ -1,28 +1,24 @@
// function adapted from YAINA by MartinCo at http://yaina.eu
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_run",false]
missionNamespace getVariable ["RangerMetrics_debug",false]
) then {
private _startTime = diag_tickTime;
call RangerMetrics_fnc_server_performance;
call RangerMetrics_fnc_running_scripts;
call RangerMetrics_fnc_server_time;
call RangerMetrics_fnc_entities_local;
call RangerMetrics_fnc_entities_global;
private _allUsers = allUsers apply {getUserInfo _x};
{
_x call RangerMetrics_fnc_player_performance;
_x call RangerMetrics_fnc_player_status;
} 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];
};
};
[format ["Run time: %1", diag_tickTime - _startTime], "DEBUG"] call RangerMetrics_fnc_log;
// missionNamespace setVariable ["RangerMetrics_debug",false];
};

View File

@@ -10,7 +10,6 @@ RangerMetrics_run = false;
RangerMetrics_activeThreads = [];
RangerMetrics_messageQueue = createHashMap;
RangerMetrics_sendBatchHandle = scriptNull;
RangerMetrics_captureBatchHandle = scriptNull;
[format ["Instance name: %1", profileName]] call RangerMetrics_fnc_log;
[format ["CBA detected: %1", RangerMetrics_cbaPresent]] call RangerMetrics_fnc_log;
@@ -64,58 +63,71 @@ addMissionEventHandler ["ExtensionCallback", {
_this call RangerMetrics_fnc_callbackHandler;
}];
// define the metrics to capture by sideloading definition files
// this keeps the main file clean and easy to read
// the definition files are in the format of a hashmap, where the key is the category and the value is an array of arrays, where each sub-array is a capture definition
RangerMetrics_captureDefinitions = createHashMapFromArray [
[
"ServerEvent",
createHashMapFromArray [
[
"MissionEventHandlers",
call RangerMetrics_cDefinitions_fnc_server_missionEH
]
]],
["ClientEvent", []],
[
"ServerPoll",
call RangerMetrics_cDefinitions_fnc_server_poll
],
[
"ClientPoll",
call RangerMetrics_cDefinitions_fnc_client_poll
]
];
// add missionEventHandlers on server
{_x params ["_handleName", "_code"];
missionNamespace setVariable [
("RangerMetrics" + "_MEH_" + _handleName),
(addMissionEventHandler [_handleName, _code])
];
} forEach ((RangerMetrics_captureDefinitions get "ServerEvent") get "MissionEventHandlers");
// begin server polling
{
_x call RangerMetrics_fnc_startServerPoll;
} forEach (RangerMetrics_captureDefinitions get "ServerPoll");
// remoteExec client polling - send data to start handles
{
_x call RangerMetrics_fnc_sendClientPoll;
} forEach (RangerMetrics_captureDefinitions get "ClientPoll");
// {
// } forEach (call RangerMetrics_captureDefinitions_fnc_clientEvent);
// begin client polling
// start sending
[{
params ["_args", "_idPFH"];
if (scriptDone RangerMetrics_sendBatchHandle) then {
RangerMetrics_sendBatchHandle = [] spawn RangerMetrics_fnc_send;
};
}, 2, []] call CBA_fnc_addPerFrameHandler;
RangerMetrics_initialized = true;
RangerMetrics_run = true;
call RangerMetrics_fnc_addHandlers;
call RangerMetrics_capture_fnc_running_mission;
if (RangerMetrics_cbaPresent) then { // CBA is running, use PFH
/*
This capture method is dynamic.
Every 5 seconds, two script handles are checked. One is for capturing, one is for sending.
The capturing script will go through and capture data, getting nanosecond precision timestamps from the extension to go alongside each data point, then saving it to a queue. It will go through all assigned interval-based checks then exit, and on the next interval of this parent PFH, the capturing script will be spawned again.
The queue is a hashmap where keys are buckets and values are arrays of data points in [string] line protocol format.
The sending script will go through and send data, sending it in batches per bucket and per 2000 data points, as the max extension call with args is 2048 elements.
The sending script will also check if the queue is empty, and if it is, it will exit. This means scriptDone will be true, and on the next interval of this parent PFH, the sending script will be spawned again.
This system means that capture and sending are occurring in the scheduled environment, not blocking the server, while maintaining the timestamps of when each point was captured. The cycles of each will only occur at most once per 5 seconds, leaving plenty of time, and there will never be more than one call for each at a time.
*/
[{
params ["_args", "_idPFH"];
if (scriptDone RangerMetrics_captureBatchHandle) then {
RangerMetrics_captureBatchHandle = [] spawn RangerMetrics_fnc_captureLoop;
};
if (scriptDone RangerMetrics_sendBatchHandle) then {
RangerMetrics_sendBatchHandle = [] spawn RangerMetrics_fnc_send;
};
}, 5, []] call CBA_fnc_addPerFrameHandler;
// runs on interval
// [{
// params ["_args", "_idPFH"];
// RangerMetrics_unixTime = (parseSimpleArray ("RangerMetrics" callExtension "getUnixTimeNano")) select 0;
// // spawn RangerMetrics_fnc_captureLoop;
// // call RangerMetrics_fnc_send;
// }, 3, []] call CBA_fnc_addPerFrameHandler;
} else { // CBA isn't running, use sleep
[] spawn {
while {true} do {
RangerMetrics_unixTime = (parseSimpleArray ("RangerMetrics" callExtension "getUnixTimeNano")) select 0;
call RangerMetrics_fnc_captureLoop; // nested to match CBA PFH signature
sleep 1;
if (RangerMetrics_sendBatchHandle != -1) exitWith {
RangerMetrics_sendBatchHandle = [] spawn RangerMetrics_fnc_send;
};
if (scriptDone RangerMetrics_sendBatchHandle) exitWith {
RangerMetrics_sendBatchHandle = -1;
};
};
};
};

View File

@@ -2,7 +2,8 @@ params [
["_bucket", "default", [""]],
"_measurement",
["_tags", [], [[], nil]],
["_fields", [], [[], nil]]
["_fields", [], [[], nil]],
["_tagContext", ["profile", "server"], [[]]]
];
@@ -13,16 +14,38 @@ params [
// (_tags apply {format['%1=%2', _x#0, _x#1]}) joinString ","
// ],
_tags pushback ["string", "profileName", profileName];
_tags pushBack ["string", "connectedServer", RangerMetrics_serverProfileName];
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 {
_tags pushBack ["string", "connectedServer", RangerMetrics_serverProfileName];
};
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
(_tags apply {_x call RangerMetrics_fnc_toLineProtocol}) joinString ",",
(_fields apply {_x call RangerMetrics_fnc_toLineProtocol}) joinString ",",
_outTags,
_outFields,
call RangerMetrics_fnc_unixTimestamp
];

View File

@@ -31,6 +31,25 @@ 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 pushBack _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]];

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 {
localNamespace setVariable [
_handleName,
[_code, _interval, _handleName] call CBA_fnc_addPerFrameHandler
];
} else {
localNamespace 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,60 @@
params [
["_interval", 5, [0]],
["_functions", [], [[]]]
];
private _captureHandleName = format ["RangerMetrics_captureBatchHandle_%1", _interval];
if (RangerMetrics_cbaPresent) then { // CBA is running, use PFH
/*
This capture method is dynamic.
Every 5 seconds, two script handles are checked. One is for capturing, one is for sending.
The capturing script will go through and capture data, getting nanosecond precision timestamps from the extension to go alongside each data point, then saving it to a queue. It will go through all assigned interval-based checks then exit, and on the next interval of this parent PFH, the capturing script will be spawned again.
The queue is a hashmap where keys are buckets and values are arrays of data points in [string] line protocol format.
The sending script will go through and send data, sending it in batches per bucket and per 2000 data points, as the max extension call with args is 2048 elements.
The sending script will also check if the queue is empty, and if it is, it will exit. This means scriptDone will be true, and on the next interval of this parent PFH, the sending script will be spawned again.
This system means that capture and sending are occurring in the scheduled environment, not blocking the server, while maintaining the timestamps of when each point was captured. The cycles of each will only occur at most once per 2 seconds, leaving plenty of time, and there will never be more than one call for each at a time.
*/
[{
params ["_args", "_idPFH"];
_args params ["_captureHandleName", "_functions"];
if (!RangerMetrics_run) exitWith {};
// use spawn
// if (scriptDone _captureHandleName) then {
// missionNamespace setVariable [
// _captureHandleName,
// [_functions] spawn {
// {
// call _x;
// } forEach _this;
// }
// ];
// };
// call direct
[format["Running %1 functions for %2", count _functions, _captureHandleName], "DEBUG"] call RangerMetrics_fnc_log;
{
call _x;
} forEach _functions;
}, _interval, [_captureHandleName, _functions]] call CBA_fnc_addPerFrameHandler;
} else { // CBA isn't running, use sleep
[_interval, _functions] spawn {
params ["_interval", "_functions"];
while {true} do {
if (!RangerMetrics_run) exitWith {};
{
call _x;
} forEach _functions;
sleep _interval;
};
};
};

View File

@@ -1,11 +1,5 @@
addMissionEventHandler ["MPEnded", {
private ["_winner", "_reason"];
_winner = "Unknown";
_reason = "Mission Complete";
["server_events", "MPEnded", nil, [
["string", "winner", _winner],
["string", "reason", _reason]
]] call RangerMetrics_fnc_queue;
}];
addMissionEventHandler ["OnUserConnected", {
@@ -37,7 +31,7 @@ addMissionEventHandler ["PlayerConnected", {
["int", "id", _id],
["string", "uid", _uid],
["string", "name", _name],
["bool", "jip", _jip],
["bool", "isJip", _jip],
["int", "owner", _owner],
["string", "idstr", _idstr]
]] call RangerMetrics_fnc_queue;
@@ -50,7 +44,7 @@ addMissionEventHandler ["PlayerDisconnected", {
["int", "id", _id],
["string", "uid", _uid],
["string", "name", _name],
["bool", "jip", _jip],
["bool", "isJip", _jip],
["int", "owner", _owner],
["string", "idstr", _idstr]
]] call RangerMetrics_fnc_queue;

View File

@@ -1,17 +1,63 @@
params [
params ["_line", ["_section", "field", [""]]];
_line params [
["_valueType", "string", [""]],
"_key",
["_key", "", [""]],
"_value"
];
// debug
// diag_log format["%1=%2", _key, _value];
if (isNil "_value") exitWith {
nil;
};
if (_value isEqualTo "") exitWith {
"";
};
if (_valueType isEqualTo "string") exitWith {
format['%1="%2"', _key, _value];
};
format['%1=%2', _key, _value];
nil
};
if (_value isEqualType []) then {
_value = _value joinString ",";
// replace double quotes with single quotes
_value = [_value, '""', "'"] 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

@@ -1,7 +1,19 @@
@startuml classDiagram
classDiagram
class server_state {
BUCKET
}
class server_events {
Measurement MPEnded
Measurement OnUserConnected
Measurement OnUserDisconnected
Measurement PlayerConnected
Measurement PlayerDisconnected
Measurement OnUserClientStateChanged
Measurement OnUserAdminStateChanged
Meausrement HandleChatMessage
}
server_state --> running_mission
class running_mission {
tag string profileName
@@ -12,8 +24,8 @@
field string briefingName
}
server_state --> time
class time {
server_state --> server_time
class server_time {
tag string profileName
tag string connectedServer
field float diag_tickTime
@@ -101,14 +113,16 @@
]
}
' link fields in each category
%% ' 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 header
field string gameType
field int minPlayers
field int maxPlayers
@@ -123,6 +137,8 @@
mission_config_file --> respawn
class respawn {
tag string profileName
tag string connectedServer
field string respawn
field string respawnButton
field string respawnDelay
@@ -150,6 +166,8 @@
mission_config_file --> player_ui
class player_ui {
tag string profileName
tag string connectedServer
field int overrideFeedback
field int showHUD
field int showCompass
@@ -165,6 +183,8 @@
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
@@ -178,6 +198,8 @@
mission_config_file --> mission_settings
class mission_settings {
tag string profileName
tag string connectedServer
field int aiKills
field int briefing
field int debriefing
@@ -204,40 +226,47 @@
config_state --> visual_settings
class visual_settings {
tag string profileName
tag string connectedServer
field string getTIParameters
field string objectViewDistance
}
class player_state {
Measurement identity
Measurement status
}
player_state --> player_identity
class player_identity {
tag string profileName
tag string connectedServer
field string playerID
field string ownerId
field string playerUID
field string profileName
field string displayName
field string steamName
bool string isHC
field bool isHC
}
player_state --> player_status
class player_status {
tag string profileName
tag string connectedServer
field int clientStateNumber
field int adminState
}
player_state --> player_performance
class player_performance {
tag string profileName
tag string connectedServer
field float avgPing
field float avgBandwidth
field float desync
}
@enduml