From 7934e471a7ca736bcd46536952984d803b790041 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Mon, 2 Dec 2024 20:27:59 +0000 Subject: [PATCH 1/4] Provide production statistics for all surfaces --- plugins/statistics_exporter/instance.ts | 74 +++++++++++++------ plugins/statistics_exporter/module/export.lua | 47 ++++++------ 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/plugins/statistics_exporter/instance.ts b/plugins/statistics_exporter/instance.ts index b824992d..e09e131c 100644 --- a/plugins/statistics_exporter/instance.ts +++ b/plugins/statistics_exporter/instance.ts @@ -6,14 +6,18 @@ const { Gauge } = lib; type IpcStats = { game_tick: number, player_count: number, - game_flow_statistics: { - pollution_statistics: IpcFlowType, - }, - force_flow_statistics: { - [key:string]: { - [key:string]: IpcFlowType, + surface_statistics: { + [key: string]: { + game_flow_statistics: { + pollution_statistics: IpcFlowType, + }, + force_flow_statistics: { + [key:string]: { + [key:string]: IpcFlowType, + } + }, } - }, + } } type IpcFlowType = { @@ -34,29 +38,44 @@ const instanceGameTicksTotal = new Gauge( const instanceForceFlowStatistics = new Gauge( "clusterio_statistics_exporter_instance_force_flow_statistics", "Items/fluids/enemies/buildings produced/built/killed by a force", - { labels: ["instance_id", "force", "statistic", "direction", "name"] }, + { labels: ["instance_id", "surface", "force", "statistic", "direction", "name"] }, ); const instanceGameFlowStatistics = new Gauge( "clusterio_statistics_exporter_instance_game_flow_statistics", "Pollution produced/consumed in the game", - { labels: ["instance_id", "statistic", "direction", "name"] }, + { labels: ["instance_id", "surface", "statistic", "direction", "name"] }, ); function setForceFlowStatistic( instanceId: number, + surfaceName: string, forceName: string, statisticName: string, direction: string, item: string, value: number, ) { - instanceForceFlowStatistics.labels(String(instanceId), forceName, statisticName, direction, item).set(value); + instanceForceFlowStatistics.labels( + String(instanceId), + surfaceName, + forceName, + statisticName, + direction, + item + ).set(value); // For item and fluid statistics it's useful to compare the input flow with the // output flow, to simplify the comparison ensure both directions have a value. if (["item_production_statistics", "fluid_production_statistic"].includes(statisticName)) { let reverseDirection = direction === "input" ? "output" : "input"; - instanceForceFlowStatistics.labels(String(instanceId), forceName, statisticName, reverseDirection, item); + instanceForceFlowStatistics.labels( + String(instanceId), + surfaceName, + forceName, + statisticName, + reverseDirection, + item + ).set(value); } } @@ -84,21 +103,32 @@ export class InstancePlugin extends BaseInstancePlugin { instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick); instancePlayerCount.labels(String(instanceId)).set(stats.player_count); - for (let [forceName, flowStatistics] of Object.entries(stats.force_flow_statistics)) { - for (let [statisticName, statistic] of Object.entries(flowStatistics)) { - for (let [direction, counts] of Object.entries(statistic)) { - for (let [item, value] of Object.entries(counts)) { - setForceFlowStatistic(instanceId, forceName, statisticName, direction, item, value); + for (let [surfaceName, surfaceStats] of Object.entries(stats.surface_statistics)) { + for (let [forceName, flowStatistics] of Object.entries(surfaceStats.force_flow_statistics)) { + for (let [statisticName, statistic] of Object.entries(flowStatistics)) { + for (let [direction, counts] of Object.entries(statistic)) { + // eslint-disable-next-line max-depth + for (let [item, value] of Object.entries(counts)) { + setForceFlowStatistic( + instanceId, + surfaceName, + forceName, + statisticName, + direction, + item, + value + ); + } } } } - } - for (let [direction, counts] of Object.entries(stats.game_flow_statistics.pollution_statistics)) { - for (let [item, value] of Object.entries(counts)) { - instanceGameFlowStatistics.labels( - String(instanceId), "pollution_statistics", direction, item - ).set(value); + for (let [direction, counts] of Object.entries(surfaceStats.game_flow_statistics.pollution_statistics)) { + for (let [item, value] of Object.entries(counts)) { + instanceGameFlowStatistics.labels( + String(instanceId), surfaceName, "pollution_statistics", direction, item + ).set(value); + } } } } diff --git a/plugins/statistics_exporter/module/export.lua b/plugins/statistics_exporter/module/export.lua index 8537d0f8..f98c7f21 100644 --- a/plugins/statistics_exporter/module/export.lua +++ b/plugins/statistics_exporter/module/export.lua @@ -11,32 +11,37 @@ local statistics = { statistics_exporter = {} function statistics_exporter.export() - --- @diagnostic disable-next-line undefined-field - local pollution = v2_stats and game.get_pollution_statistics(1) or game.pollution_statistics local stats = { game_tick = game.tick, player_count = #game.connected_players, - game_flow_statistics = { - pollution_statistics = { - --- TODO support multi surface pollution statistics - input = pollution.input_counts, - output = pollution.output_counts, - }, - }, - force_flow_statistics = {} + surface_statistics = {} } - for _, force in pairs(game.forces) do - local flow_statistics = {} - for _, statName in pairs(statistics) do - local stat = v2_stats and force["get_" .. statName](1) or force[statName] - flow_statistics[statName] = { - input = stat.input_counts, - output = stat.output_counts, - } - end - stats.force_flow_statistics[force.name] = flow_statistics - end + for _, surface in pairs(game.surfaces) do + local surface_stats = { + game_flow_statistics = { + pollution_statistics = { + input = (v2_stats and game.get_pollution_statistics(surface.index) or surface.pollution_statistics).input_counts, + output = (v2_stats and game.get_pollution_statistics(surface.index) or surface.pollution_statistics).output_counts, + }, + }, + force_flow_statistics = {} + } + + for _, force in pairs(game.forces) do + local flow_statistics = {} + for _, statName in pairs(statistics) do + local stat = v2_stats and force["get_" .. statName](surface.index) or force[statName] + flow_statistics[statName] = { + input = stat.input_counts, + output = stat.output_counts, + } + end + surface_stats.force_flow_statistics[force.name] = flow_statistics + end + + stats.surface_statistics[surface.name] = surface_stats + end rcon.print(compat.table_to_json(stats)) end From fd741f9b491e7fe0a966ef379d67a46a4e9ad53e Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Mon, 2 Dec 2024 21:05:24 +0000 Subject: [PATCH 2/4] Add clusterio_statistics_exporter_platform_mapping This allows queries to map between the platform surfaces tracked in the statistics and the name assigned by the player --- plugins/statistics_exporter/instance.ts | 25 +++++++++++++++++++ plugins/statistics_exporter/module/export.lua | 14 ++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/statistics_exporter/instance.ts b/plugins/statistics_exporter/instance.ts index e09e131c..d371f735 100644 --- a/plugins/statistics_exporter/instance.ts +++ b/plugins/statistics_exporter/instance.ts @@ -17,6 +17,12 @@ type IpcStats = { } }, } + }, + platforms: { + [key: string]: { + force: string, + surface: string, + } } } @@ -45,6 +51,11 @@ const instanceGameFlowStatistics = new Gauge( "Pollution produced/consumed in the game", { labels: ["instance_id", "surface", "statistic", "direction", "name"] }, ); +const instancePlatformMapping = new Gauge( + "clusterio_statistics_exporter_platform_mapping", + "Mapping of platform name to force, surface and instance", + { labels: ["instance_id", "platform_name", "force", "surface"] } +); function setForceFlowStatistic( instanceId: number, @@ -103,6 +114,19 @@ export class InstancePlugin extends BaseInstancePlugin { instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick); instancePlayerCount.labels(String(instanceId)).set(stats.player_count); + // Remove existing platform mappings for this instance + instancePlatformMapping.removeAll({ instance_id: String(instanceId) }); + + // Set new platform mappings + for (let [platformName, platform] of Object.entries(stats.platforms)) { + instancePlatformMapping.labels( + String(instanceId), + platformName, + platform.force, + platform.surface + ).set(1); + } + for (let [surfaceName, surfaceStats] of Object.entries(stats.surface_statistics)) { for (let [forceName, flowStatistics] of Object.entries(surfaceStats.force_flow_statistics)) { for (let [statisticName, statistic] of Object.entries(flowStatistics)) { @@ -151,3 +175,4 @@ export const _instancePlayerCount = instancePlayerCount; export const _instanceGameTicksTotal = instanceGameTicksTotal; export const _instanceForceFlowStatistics = instanceForceFlowStatistics; export const _instanceGameFlowStatistics = instanceGameFlowStatistics; +export const _instancePlatformMapping = instancePlatformMapping; diff --git a/plugins/statistics_exporter/module/export.lua b/plugins/statistics_exporter/module/export.lua index f98c7f21..fe9d4c16 100644 --- a/plugins/statistics_exporter/module/export.lua +++ b/plugins/statistics_exporter/module/export.lua @@ -14,7 +14,8 @@ function statistics_exporter.export() local stats = { game_tick = game.tick, player_count = #game.connected_players, - surface_statistics = {} + surface_statistics = {}, + platforms = {} } for _, surface in pairs(game.surfaces) do @@ -43,5 +44,16 @@ function statistics_exporter.export() stats.surface_statistics[surface.name] = surface_stats end + if v2_stats then + for _, force in pairs(game.forces) do + for _, platform in pairs(force.platforms) do + stats.platforms[platform.name] = { + force = force.name, + surface = platform.surface.name + } + end + end + end + rcon.print(compat.table_to_json(stats)) end From 9464541d61f5e8aba81fe321dc3ca9fe010ce606 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Mon, 2 Dec 2024 21:11:59 +0000 Subject: [PATCH 3/4] Update tests for multi surface support --- plugins/statistics_exporter/test/plugin.js | 35 +++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/plugins/statistics_exporter/test/plugin.js b/plugins/statistics_exporter/test/plugin.js index 18251e27..5fd12c8b 100644 --- a/plugins/statistics_exporter/test/plugin.js +++ b/plugins/statistics_exporter/test/plugin.js @@ -30,18 +30,28 @@ describe("statistics_exporter plugin", function() { JSON.stringify({ game_tick: 100, player_count: 3, - force_flow_statistics: { - player: { - item_production_statistics: { - input: { "inserter": 10 }, - output: { "iron-plate": 24 }, + surface_statistics: { + "nauvis": { + game_flow_statistics: { + pollution_statistics: { + input: { "boiler": 2000 }, + output: { "tree-proxy": 560 }, + }, + }, + force_flow_statistics: { + player: { + item_production_statistics: { + input: { "inserter": 10 }, + output: { "iron-plate": 24 }, + }, + }, }, }, }, - game_flow_statistics: { - pollution_statistics: { - input: { "boiler": 2000 }, - output: { "tree-proxy": 560 }, + platforms: { + "platform-1": { + force: "player", + surface: "nauvis", }, }, }) @@ -52,11 +62,14 @@ describe("statistics_exporter plugin", function() { assert.equal(instance._instancePlayerCount.labels("7357").get(), 3); assert.equal(instance._instanceGameTicksTotal.labels("7357").get(), 100); assert.equal(instance._instanceForceFlowStatistics.labels( - "7357", "player", "item_production_statistics", "input", "inserter").get(), + "7357", "nauvis", "player", "item_production_statistics", "input", "inserter").get(), 10); assert.equal(instance._instanceGameFlowStatistics.labels( - "7357", "pollution_statistics", "input", "boiler").get(), + "7357", "nauvis", "pollution_statistics", "input", "boiler").get(), 2000); + assert.equal(instance._instancePlatformMapping.labels( + "7357", "platform-1", "player", "nauvis").get(), + 1); }); it("should pass on JSON parse errors", async function() { let string = "An error occured\n"; From 8e22de9d2df6620569eedb3ce145c1203d03f23d Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Mon, 2 Dec 2024 21:34:19 +0000 Subject: [PATCH 4/4] Add platform speed and weight metrics clusterio_statistics_exporter_platform_speed clusterio_statistics_exporter_platform_weight --- plugins/statistics_exporter/instance.ts | 32 +++++++++++++++++-- plugins/statistics_exporter/module/export.lua | 4 ++- plugins/statistics_exporter/test/plugin.js | 8 +++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/plugins/statistics_exporter/instance.ts b/plugins/statistics_exporter/instance.ts index d371f735..91e68284 100644 --- a/plugins/statistics_exporter/instance.ts +++ b/plugins/statistics_exporter/instance.ts @@ -22,6 +22,8 @@ type IpcStats = { [key: string]: { force: string, surface: string, + speed: number, + weight: number, } } } @@ -56,6 +58,16 @@ const instancePlatformMapping = new Gauge( "Mapping of platform name to force, surface and instance", { labels: ["instance_id", "platform_name", "force", "surface"] } ); +const instancePlatformSpeed = new Gauge( + "clusterio_statistics_exporter_platform_speed", + "Current speed of the platform", + { labels: ["instance_id", "force", "surface"] } +); +const instancePlatformWeight = new Gauge( + "clusterio_statistics_exporter_platform_weight", + "Current weight/mass of the platform", + { labels: ["instance_id", "force", "surface"] } +); function setForceFlowStatistic( instanceId: number, @@ -114,10 +126,12 @@ export class InstancePlugin extends BaseInstancePlugin { instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick); instancePlayerCount.labels(String(instanceId)).set(stats.player_count); - // Remove existing platform mappings for this instance + // Remove existing platform mappings and stats for this instance instancePlatformMapping.removeAll({ instance_id: String(instanceId) }); + instancePlatformSpeed.removeAll({ instance_id: String(instanceId) }); + instancePlatformWeight.removeAll({ instance_id: String(instanceId) }); - // Set new platform mappings + // Set new platform mappings and stats for (let [platformName, platform] of Object.entries(stats.platforms)) { instancePlatformMapping.labels( String(instanceId), @@ -125,6 +139,18 @@ export class InstancePlugin extends BaseInstancePlugin { platform.force, platform.surface ).set(1); + + instancePlatformSpeed.labels( + String(instanceId), + platform.force, + platform.surface + ).set(platform.speed); + + instancePlatformWeight.labels( + String(instanceId), + platform.force, + platform.surface + ).set(platform.weight); } for (let [surfaceName, surfaceStats] of Object.entries(stats.surface_statistics)) { @@ -176,3 +202,5 @@ export const _instanceGameTicksTotal = instanceGameTicksTotal; export const _instanceForceFlowStatistics = instanceForceFlowStatistics; export const _instanceGameFlowStatistics = instanceGameFlowStatistics; export const _instancePlatformMapping = instancePlatformMapping; +export const _instancePlatformSpeed = instancePlatformSpeed; +export const _instancePlatformWeight = instancePlatformWeight; diff --git a/plugins/statistics_exporter/module/export.lua b/plugins/statistics_exporter/module/export.lua index fe9d4c16..33626000 100644 --- a/plugins/statistics_exporter/module/export.lua +++ b/plugins/statistics_exporter/module/export.lua @@ -49,7 +49,9 @@ function statistics_exporter.export() for _, platform in pairs(force.platforms) do stats.platforms[platform.name] = { force = force.name, - surface = platform.surface.name + surface = platform.surface.name, + speed = platform.speed, + weight = platform.weight, } end end diff --git a/plugins/statistics_exporter/test/plugin.js b/plugins/statistics_exporter/test/plugin.js index 5fd12c8b..4d315fbc 100644 --- a/plugins/statistics_exporter/test/plugin.js +++ b/plugins/statistics_exporter/test/plugin.js @@ -52,6 +52,8 @@ describe("statistics_exporter plugin", function() { "platform-1": { force: "player", surface: "nauvis", + speed: 1, + weight: 2, }, }, }) @@ -70,6 +72,12 @@ describe("statistics_exporter plugin", function() { assert.equal(instance._instancePlatformMapping.labels( "7357", "platform-1", "player", "nauvis").get(), 1); + assert.equal(instance._instancePlatformSpeed.labels( + "7357", "player", "nauvis").get(), + 1); + assert.equal(instance._instancePlatformWeight.labels( + "7357", "player", "nauvis").get(), + 2); }); it("should pass on JSON parse errors", async function() { let string = "An error occured\n";