feat(prometheus-exporter): add metrics-pole for power metrics
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/factorio
|
||||||
3
.vscode/.gitignore
vendored
Normal file
3
.vscode/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!launch.json
|
||||||
9
.vscode/launch.json
vendored
Normal file
9
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "factoriomod",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Factorio Mod Debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,83 +1,55 @@
|
|||||||
-- Config
|
local metrics = require("utils.metrics")
|
||||||
local steps_per_tick = 1 -- process 1 surface×force per tick
|
|
||||||
local flush_interval_ticks = 60 * 15 -- flush every n ticks
|
|
||||||
|
|
||||||
-- State
|
-- config
|
||||||
|
local flush_interval_ticks = 60 -- flush every n ticks
|
||||||
|
|
||||||
|
-- state
|
||||||
|
--- @type table<number, fun()>
|
||||||
local queue = {}
|
local queue = {}
|
||||||
local buffer = {}
|
|
||||||
local last_flush_tick = 0
|
|
||||||
|
|
||||||
-- Initialize queue with all surface×force pairs
|
--- @type table<number, string>
|
||||||
|
local buffer = {}
|
||||||
|
local last_reset = 0
|
||||||
|
|
||||||
|
-- queue operations to perform for each round of metric processing
|
||||||
|
-- that way, avoid doing too much work in a single game tick and causing UPS inconsistencies
|
||||||
local function prepare_queue()
|
local function prepare_queue()
|
||||||
queue = {}
|
queue = {}
|
||||||
for surface_name, _ in pairs(game.surfaces) do
|
for _, surface in pairs(game.surfaces) do
|
||||||
for force_name, _ in pairs(game.forces) do
|
for _, force in pairs(game.forces) do
|
||||||
table.insert(queue, {surface_name = surface_name, force_name = force_name})
|
table.insert(queue, metrics.calc_item_production_statistics(buffer, surface, force))
|
||||||
|
table.insert(queue, metrics.calc_fluid_production_statistics(buffer, surface, force))
|
||||||
end
|
end
|
||||||
|
table.insert(queue, metrics.calc_power_statistics(buffer, surface))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Process a few items from the queue each tick
|
|
||||||
local function process_queue()
|
local function process_queue()
|
||||||
for i = 1, steps_per_tick do
|
local entry = table.remove(queue, 1)
|
||||||
local entry = table.remove(queue, 1)
|
if entry then
|
||||||
if not entry then break end
|
entry()
|
||||||
|
|
||||||
local surface_name, force_name = entry.surface_name, entry.force_name
|
|
||||||
local force = game.forces[force_name]
|
|
||||||
|
|
||||||
-- Items
|
|
||||||
local item_stats = force.get_item_production_statistics(surface_name)
|
|
||||||
for item, count in pairs(item_stats.input_counts) do
|
|
||||||
table.insert(buffer, string.format(
|
|
||||||
"factorio_production_total{type=\"item\",surface=\"%s\",force=\"%s\",name=\"%s\"} %d",
|
|
||||||
surface_name, force_name, item, count
|
|
||||||
))
|
|
||||||
end
|
|
||||||
for item, count in pairs(item_stats.output_counts) do
|
|
||||||
table.insert(buffer, string.format(
|
|
||||||
"factorio_consumption_total{type=\"item\",surface=\"%s\",force=\"%s\",name=\"%s\"} %d",
|
|
||||||
surface_name, force_name, item, count
|
|
||||||
))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Fluids
|
|
||||||
local fluid_stats = force.get_fluid_production_statistics(surface_name)
|
|
||||||
for fluid, count in pairs(fluid_stats.input_counts) do
|
|
||||||
table.insert(buffer, string.format(
|
|
||||||
"factorio_production_total{type=\"fluid\",surface=\"%s\",force=\"%s\",name=\"%s\"} %d",
|
|
||||||
surface_name, force_name, fluid, count
|
|
||||||
))
|
|
||||||
end
|
|
||||||
for fluid, count in pairs(fluid_stats.output_counts) do
|
|
||||||
table.insert(buffer, string.format(
|
|
||||||
"factorio_consumption_total{type=\"fluid\",surface=\"%s\",force=\"%s\",name=\"%s\"} %d",
|
|
||||||
surface_name, force_name, fluid, count
|
|
||||||
))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Flush buffer to file periodically
|
local function tick()
|
||||||
local function maybe_flush()
|
|
||||||
if #queue == 0 and game.tick - last_flush_tick >= flush_interval_ticks then
|
|
||||||
if #buffer > 0 then
|
|
||||||
helpers.write_file("metrics.prom", table.concat(buffer, "\n"), false)
|
|
||||||
buffer = {}
|
|
||||||
end
|
|
||||||
last_flush_tick = game.tick
|
|
||||||
prepare_queue()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Main tick handler
|
|
||||||
script.on_event(defines.events.on_tick, function()
|
|
||||||
process_queue()
|
process_queue()
|
||||||
maybe_flush()
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Bootstrap
|
if #queue == 0 and #buffer > 0 then
|
||||||
|
helpers.write_file("metrics.prom", table.concat(buffer, "\n"), false)
|
||||||
|
buffer = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if #queue == 0 and game.tick - last_reset >= flush_interval_ticks then
|
||||||
|
prepare_queue()
|
||||||
|
last_reset = game.tick
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- main tick handler
|
||||||
|
script.on_event(defines.events.on_tick, tick)
|
||||||
|
|
||||||
|
-- startup
|
||||||
script.on_init(function()
|
script.on_init(function()
|
||||||
last_flush_tick = game.tick
|
last_reset = game.tick
|
||||||
prepare_queue()
|
prepare_queue()
|
||||||
end)
|
end)
|
||||||
|
|||||||
1
prometheus-exporter/data.lua
Normal file
1
prometheus-exporter/data.lua
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require("prototypes.metrics-pole").setup()
|
||||||
62
prometheus-exporter/prototypes/metrics-pole.lua
Normal file
62
prometheus-exporter/prototypes/metrics-pole.lua
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
local name = "metrics-pole"
|
||||||
|
|
||||||
|
--- @class MetricsPole
|
||||||
|
--- @field name string
|
||||||
|
--- @field setup fun()
|
||||||
|
local M = {
|
||||||
|
name = name,
|
||||||
|
setup = function()
|
||||||
|
local function tint(alpha)
|
||||||
|
return { r = 1, g = 0, b = 1, a = alpha }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- POLE
|
||||||
|
local pole = table.deepcopy(data.raw["electric-pole"]["small-electric-pole"])
|
||||||
|
pole.name = name
|
||||||
|
pole.minable.result = name
|
||||||
|
pole.icons = {
|
||||||
|
{
|
||||||
|
icon = pole.icon,
|
||||||
|
icon_size = pole.icon_size,
|
||||||
|
tint = tint(0.3)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pole.is_military_target = false
|
||||||
|
pole.hidden_in_factoriopedia = true
|
||||||
|
pole.localised_name = "Metrics Pole"
|
||||||
|
pole.supply_area_distance = 0
|
||||||
|
|
||||||
|
for _, layer in pairs(pole.pictures.layers) do
|
||||||
|
layer.tint = tint(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
data:extend({ pole })
|
||||||
|
|
||||||
|
-- ITEM
|
||||||
|
local item = table.deepcopy(data.raw["item"]["small-electric-pole"])
|
||||||
|
item.name = name
|
||||||
|
item.place_result = name
|
||||||
|
item.localised_name = "Metrics Pole"
|
||||||
|
item.icons = {
|
||||||
|
{
|
||||||
|
icon = item.icon,
|
||||||
|
icon_size = item.icon_size,
|
||||||
|
tint = tint(0.3)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data:extend({ item })
|
||||||
|
|
||||||
|
-- RECIPE
|
||||||
|
data:extend({
|
||||||
|
{
|
||||||
|
type = "recipe",
|
||||||
|
name = name,
|
||||||
|
enabled = true,
|
||||||
|
ingredients = { { name = "wood", amount = 1, type = "item" } },
|
||||||
|
results = { { name = name, type = "item", amount = 1 } }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return M
|
||||||
75
prometheus-exporter/utils/metrics.lua
Normal file
75
prometheus-exporter/utils/metrics.lua
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
local prometheus = require("utils.prometheus")
|
||||||
|
local metrics_pole = require('prototypes.metrics-pole')
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- @param metrics_table table<number, string>
|
||||||
|
--- @param surface LuaSurface
|
||||||
|
--- @param force LuaForce
|
||||||
|
--- @return fun()
|
||||||
|
function M.calc_item_production_statistics(metrics_table, surface, force)
|
||||||
|
return function()
|
||||||
|
local item_stats = force.get_item_production_statistics(surface.name)
|
||||||
|
for item, count in pairs(item_stats.input_counts) do
|
||||||
|
table.insert(metrics_table,
|
||||||
|
prometheus.counter("production", "",
|
||||||
|
{ type = "item", surface = surface.name, force = force.name, name = item }, count))
|
||||||
|
end
|
||||||
|
for item, count in pairs(item_stats.output_counts) do
|
||||||
|
table.insert(metrics_table,
|
||||||
|
prometheus.counter("consumption", "",
|
||||||
|
{ type = "item", surface = surface.name, force = force.name, name = item }, count))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param metrics_table table<number, string>
|
||||||
|
--- @param surface LuaSurface
|
||||||
|
--- @param force LuaForce
|
||||||
|
--- @return fun()
|
||||||
|
function M.calc_fluid_production_statistics(metrics_table, surface, force)
|
||||||
|
return function()
|
||||||
|
local fluid_stats = force.get_fluid_production_statistics(surface.name)
|
||||||
|
for item, count in pairs(fluid_stats.input_counts) do
|
||||||
|
table.insert(metrics_table,
|
||||||
|
prometheus.counter("production", "",
|
||||||
|
{ type = "fluid", surface = surface.name, force = force.name, name = item }, count))
|
||||||
|
end
|
||||||
|
for item, count in pairs(fluid_stats.output_counts) do
|
||||||
|
table.insert(metrics_table,
|
||||||
|
prometheus.counter("consumption", "",
|
||||||
|
{ type = "fluid", surface = surface.name, force = force.name, name = item }, count))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param metrics_table table<number, string>
|
||||||
|
--- @param surface LuaSurface
|
||||||
|
--- @return fun()
|
||||||
|
function M.calc_power_statistics(metrics_table, surface)
|
||||||
|
return function()
|
||||||
|
local seen = {}
|
||||||
|
for _, pole in pairs(surface.find_entities_filtered { name = metrics_pole.name }) do
|
||||||
|
if table[pole.electric_network_id] then -- deduplication
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
table.insert(seen, pole.electric_network_id)
|
||||||
|
|
||||||
|
local stats = pole.electric_network_statistics
|
||||||
|
for name, count in pairs(stats.input_counts) do
|
||||||
|
table.insert(metrics_table,
|
||||||
|
prometheus.counter("energy_consumption", "_joules",
|
||||||
|
{ surface = surface.name, network_id = pole.electric_network_id, name = name }, count))
|
||||||
|
end
|
||||||
|
for name, count in pairs(stats.output_counts) do
|
||||||
|
table.insert(metrics_table,
|
||||||
|
prometheus.counter("energy_production", "_joules",
|
||||||
|
{ surface = surface.name, network_id = pole.electric_network_id, name = name }, count))
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
17
prometheus-exporter/utils/prometheus.lua
Normal file
17
prometheus-exporter/utils/prometheus.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Formats a name + table of labels as a Prometheus metric
|
||||||
|
--- @param name string
|
||||||
|
--- @param suffix string
|
||||||
|
--- @param labels table
|
||||||
|
--- @param value number
|
||||||
|
function M.counter(name, suffix, labels, value)
|
||||||
|
local label_parts = {}
|
||||||
|
for k, v in pairs(labels) do
|
||||||
|
table.insert(label_parts, string.format('%s="%s"', k, v))
|
||||||
|
end
|
||||||
|
local label_str = table.concat(label_parts, ",")
|
||||||
|
return string.format("factorio_%s_total%s{%s} %d", name, suffix, label_str, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
Reference in New Issue
Block a user