Browse Source

Version 1.0.1 imported from releases

main v1.0.1
chksm 2 years ago
parent
commit
b828592db9
  1. 308
      README.md
  2. 351
      control.lua
  3. 328
      control.lua.v1
  4. 310
      control.lua.v2
  5. 9
      info.json
  6. 8
      locale/en/base.cfg
  7. 7
      locale/en/settings.cfg
  8. 26
      public/index.html
  9. 5
      public/manifest.json
  10. 8
      public/manifest.json.save
  11. 8
      public/manifest.json.save.1
  12. 57
      script/commands.lua
  13. 165
      script/fetcher.lua
  14. 82
      script/init.lua
  15. 64
      script/publisher.lua
  16. 40
      script/settings.lua
  17. 52
      script/utilities.lua
  18. 24
      settings.lua
  19. BIN
      thumbnail.png
  20. 55
      watcher/main.js
  21. BIN
      watcher/node_modules/lynx/.npmignore.gz
  22. BIN
      watcher/node_modules/lynx/.travis.yml.gz
  23. 24
      watcher/node_modules/lynx/CONTRIBUTING.md
  24. 10
      watcher/node_modules/lynx/LICENSE
  25. 174
      watcher/node_modules/lynx/README.md
  26. 664
      watcher/node_modules/lynx/lib/lynx.js
  27. 83
      watcher/node_modules/lynx/package.json
  28. 12
      watcher/node_modules/lynx/tests/counts-test.js
  29. 44
      watcher/node_modules/lynx/tests/errors-test.js
  30. 5
      watcher/node_modules/lynx/tests/fixtures/counts.json
  31. 6
      watcher/node_modules/lynx/tests/fixtures/errors.json
  32. 3
      watcher/node_modules/lynx/tests/fixtures/gauges.json
  33. 5
      watcher/node_modules/lynx/tests/fixtures/scopes.json
  34. 3
      watcher/node_modules/lynx/tests/fixtures/sets.json
  35. 13
      watcher/node_modules/lynx/tests/fixtures/stream-recv.json
  36. 10
      watcher/node_modules/lynx/tests/fixtures/stream-send.json
  37. 5
      watcher/node_modules/lynx/tests/fixtures/timings.json
  38. 10
      watcher/node_modules/lynx/tests/gauges-test.js
  39. 11
      watcher/node_modules/lynx/tests/global_leaks.js
  40. 290
      watcher/node_modules/lynx/tests/macros.js
  41. 110
      watcher/node_modules/lynx/tests/sampling-test.js
  42. 12
      watcher/node_modules/lynx/tests/scopes-test.js
  43. 10
      watcher/node_modules/lynx/tests/sets-test.js
  44. 130
      watcher/node_modules/lynx/tests/stream-test.js
  45. 64
      watcher/node_modules/lynx/tests/timings-test.js
  46. BIN
      watcher/node_modules/mersenne/.npmignore.gz
  47. 27
      watcher/node_modules/mersenne/LICENSE
  48. 13
      watcher/node_modules/mersenne/README.md
  49. 1
      watcher/node_modules/mersenne/index.js
  50. 285
      watcher/node_modules/mersenne/lib/mersenne.js
  51. 56
      watcher/node_modules/mersenne/package.json
  52. 65
      watcher/node_modules/mersenne/test.js
  53. 13
      watcher/node_modules/statsd-parser/LICENSE
  54. 106
      watcher/node_modules/statsd-parser/README.md
  55. 351
      watcher/node_modules/statsd-parser/index.js
  56. 57
      watcher/node_modules/statsd-parser/package.json
  57. 25
      watcher/package-lock.json

308
README.md

@ -1,70 +1,282 @@
# Statorio
<div align=center>
<h1>Statorio</h1>
A statistics export plugin for Factorio. Choose which metrics and a frequency for exporting to JSON file on disk.
<h4>A Factorio mod to export realtime JSON statistics</h4>
Also includes a Node JS script for moving the JSON into `statsd` or similar, and some custom dashboards for Netdata. Or roll your own stats directly from the JSON.
Choose your metrics and desired frequency<br>
Receive sweet JSON on disk<br>
<sup>Numbers go brrr</sup>
</div>
* Game data
- Cheat mode
* Force (player and enemy) statistics
- Mining
- Fluids
- Manufacturing
- Building
- Rocket launches
- Kills and demolision
- Current and previous research
- Evolution
- Modifiers
* Surface (planet) statistics
- Pollution
- Wind
- Time of day
- Day/night cycles
<hr>
The goal is to expose as much of the game API as possible (now and in the future, vanilla or modded, single and multiplayer) and export it in a machine-readable way.
## Use cases
This is experimental, you may encounter (and please report) crashes, performance hits, security issues.
* Graphs and dashboards
- Bring a spare screen into your game with interesting graphs and metrics
- Watch and publish the state of a multiplayer game
- Use a graph or gauge as a source in OBS streams etc
* Alerts from alarms
- Strong attacks
- Low power
- Production problems
* Custom applications
- Chat bots
- Feedback and control
- IoT sillyness
- Other variables and flags are included in the data
- Its just JSON 🤷
## How to use
1. Install the mod
1. Start playing
1. Check `{GAME_DIR}/script-output/stats.json` for JSON data (see [game directory help](https://wiki.factorio.com/Application_directory))
1. Choose metrics with the `/stat` console command
### Installation
Just like any other mod. For GUI versions:
* Launch the game
* Choose <kbd>Mods</kbd>
* Choose <kbd>Install</kbd> tab
* Wait for list to populate
* Search for `Statorio`
* Click the search result
* Click <kbd>Install</kbd> on the right pane
* Click <kbd>Confirm</kbd>
Or for headless versions:
* Copy this mod's ZIP (or directory) to Factorio's `/mod` directory
* Restart the server
### Mod settings
Global runtime settings can be changed from the "Mod Settings" menu in game. Look for the Statorio section. On multiplayer servers this can only be changed by admins.
#### Frequency
How often (in game ticks) to collect metrics and write to disk. 60 game ticks is roughly equal to 1 second. A good value depends on the number of metrics you watch (more is slower), the cost of each metric (some eat CPU), etc.
#### Filename
Use a simple filename like `stats.json` or a path like `statorio/out.json`. Files are written to Factorio's `script-output` directory.
Changing the filename setting will delete the old file (if it exists).
### Start playing
Play any single player game on your local computer and Statorio will start running. If you save the game then Statorio will become a dependency of your save, and request installation (if its missing) each time the game is loaded. Its safe to remove if you don't want to use it any more.
For multiplayer games Statorio must be installed on the server as well as for all players in the game. You can't just run it locally for public servers, for example.
**Limitation:** If installed on a multiplayer game then all connected clients will also calculate and export statistics locally. This is a bug *and* planned feature. Classy.
### Check the output data
Look in Factorio's data directory for a folder called `script-output`. This is the only location on disk that mods are permitted to write files. By default Statorio creates a file called `stats.json` (can be changed in configuration).
**Windows** users might look in `C:\Users\{USERNAME}\AppData\Roaming\Factorio\script-output\`
**Linux** users could look in `~/.factorio/script-output/`
You might see glorious JSON key pairs like this:
```
{
"game.forces.player.item_production_statistics.get_flow_count.iron-plate.input.one_minute": 532.77591973244,
"game.forces.player.item_production_statistics.get_flow_count.iron-plate.output.one_minute": 359.19732441472
}
```
Actually its probably an empty file, because we haven't defined any metrics yet:
```
{}
```
Also, metrics won't be listed in the JSON output if a value is blank, or missing, or an error occurred. However values of 0 should make it through.
Available JSON data types are used, so expect floating point numbers, integers, boolean values and strings.
**For large files:** There is an issue where Factorio is not writing the file atomically. If you use something like `fswatch` to watch for changes to the JSON then add a short delay to allow Factorio to finish writing to the file.
### Console command
`/stat test <metric>` will test if a metric works, if so display the current value
`/stat add <metric>` will add a metric (see examples below)
`/stat list` will show all metrics being exported right now
`/stat del <number>` will delete a metric with that number (see the list output)
### Some examples
| Metric | Description |
|:-------|:------------|
| `game.forces.player.get_entity_count.gun-turret` | Turrets deployed by the vanilla player force |
| `game.forces.*.get_entity_count.biter-spawner` | Biter spawners deployed by the vanilla AI enemy force |
| `game.forces.*.get_entity_count.transport-belt` | Transport belts deployed for all forces |
| `game.forces.player.logistic_networks.nauvis.*.available_logistic_robots` | Number of available logistics bots on all player networks |
| `game.players.*.name` and `game.players.*.online_time` | Get all player names and total play time on this map |
| `game.forces.player.item_production_statistics.get_flow_count.iron-plate.count.one_minute` | Iron plates produced in the last minute |
| `game.forces.*.entity_build_count_statistics.get_flow_count.stone-furnace.input.one_hour` | Stone furnaces built by each force in the last hour |
| `game.forces.*.get_entity_count.stone-furnace` | Total number of stone furnaces for each force |
| `game.forces.player.current_research.research_unit_count` | Current research progress |
| `game.forces.enemy.evolution_factor` | Enemy evolution factor |
| `game.forces.*.evolution_factor` | All forces evolution factor |
| `game.forces.*.ai_controllable` | Human vs AI forces |
| `game.forces.player.entity_build_count_statistics.input_counts.stone-furnace` | Stone furnaces produced |
| `game.forces.player.entity_build_count_statistics.output_counts.stone-furnace` | Stone furnaces consumed |
| `game.forces.*.get_entity_count.stone-furnace` | |
### Metric names
At its core the metric naming convention follows the structure of Factorio's `game` object, an instance of [LuaGameScript](https://lua-api.factorio.com/latest/LuaGameScript.html). All metrics begin with `game.` to show this (and allow for future expansion).
Maybe one day this could be useful for tournaments, speed runs, and other game types.
Reading basic properties is simply a matter of following the LuaGameScript API documentation above. For example `game.difficulty` or `game.ticks_played`. Simple data types like `uint`, `boolean`, `string`, `float`, `double` etc is straightforward enough.
## Dependencies
Accessing child classes is easy by reference using the dot notation. For example `game.map_settings.path_finder.short_cache_size`
On its own this mod will only output JSON data to disk, which only requires Factorio v1.1.x. You can consume the JSON data in your own applications for fun and profit.
Traversable data types like `CustomDictionary` can be referenced by an entity's name or index in the table. For example `game.forces.player.rockets_launched` reads the force called `player`, and `game.forces.1.rockets_launched` reads the first force defined in the table.
To store data and generate a dashboard of graphs, anything that uses a `statsd` (or compatible) UDP network socket for receiving metrics will be compatible with the included Node script. I highly recommend Netdata which can run locally or on a separate computer, uses very little resource, and includes plenty of functionality and hackability, open source at no cost.
Wildcards can be used for traversable data types. For example `game.forces.*.rockets_launched` returns a metric for each force.
Some functions can be used but implementation is an ongoing effort. Currently supported are:
#### get_entity_count(*prototype*)
## Using the mod
Used on anything that supports `get_entity_count`.
### Install the mod (GUI)
Requires a Factorio prototype as an argument.
For example `game.forces.player.get_entity_count.stone-furnace`
This has a performance hit. No wildcard support.
#### get_flow_count(*prototype*, *[input|output]*, *precisionIndex*)
Used to get production and consumption total counts, or flow count values for a given time frame, from anywhere in the API that implements `LuaFlowStatistics`.
*prototype* is a Factorio prototype to get flow count for. Wildcard `*` is accepted.
*[input|output]* in the context of flow statistics describe on which side of the associated GUI the values are shown. Input values are shown on the left side, output values on the right side. For items and liquids input=production and output=consumption, power input=consumption and output=production., for kill statistics input=kills and output=deaths.
*precisionIndex* can be one of Factorio's [defines.flow_precision_index](https://lua-api.factorio.com/latest/defines.html#defines.flow_precision_index) like `one_second`, `one_minute`, `ten_minutes`, `one_hour`.
For example `game.forces.player.kill_count_statistics.get_flow_count.*.input.one_hour` for all kills or `game.forces.player.kill_count_statistics.get_flow_count.character.output.one_hour` deaths. `game.pollution_statistics.get_flow_count.*.input.one_hour` for all pollutants on the map.
## Exploring the vanilla game API
If you can't find what you need in Factorio's game API then here are some clues to what's available.
### Game
Where almost everything happens. Accessed with `game.`. See [API docs for LuaGameScript](https://lua-api.factorio.com/latest/LuaGameScript.html).
Some examples...
| Metric | Output |
|:-------|-------:|
| `game.difficulty` | |
| `game.tick` | 14829 |
| `game.ticks_played` | 14829 |
| `game.ticks_paused` | 442 |
### Players
Anything related to human players who have connected to the game. Access an array of all players who have ever joined with `game.players` or players connected now with `game.connected_players`. See [API docs for LuaPlayer](https://lua-api.factorio.com/latest/LuaPlayer.html).
Get a list of connected players with `/stat test game.players.*.name` to begin exploring.
Some examples...
| Metric | Output |
|:-------|-------:|
| `game.players.1.name` | platypus |
| `game.players.1.connected` | true |
| `game.players.1.admin` | true |
| `game.players.1.online_time` | 42914 |
| `game.players.1.afk_time` | 4914 |
| `game.players.1.last_online` | |
| `game.players.1.in_combat` | false |
| `game.players.1.crafting_queue_size` | |
| `game.players.1.crafting_queue_progress` | |
### Forces
Forces are like teams, and most statistics are found here. Access an array of forces with `game.forces`. Vanilla games contain `player`, `enemy` and `neutral` forces. See [API docs for LuaForce](https://lua-api.factorio.com/latest/LuaForce.html).
Get a list of forces with `/stat test game.forces.*.name` to begin exploring.
Example team modifiers and bonuses...
| Metric | Output |
|:-------|-------:|
| `game.forces.player.worker_robots_speed_modifier` | |
| `game.forces.player.character_inventory_slots_bonus` | |
| `game.forces.player.maximum_following_robot_count` | |
| `game.forces.player.research_progress` | |
| `game.forces.player.rockets_launched` | 0 |
| `game.forces.enemy.evolution_factor` | 0.0013831931 |
| `game.forces.player.friendly_fire` | true |
### Surfaces
Access information about the land/environment through an array of available surfaces. Vanilla games have one surface called `nauvis`. Access from `game.surfaces`. See [API Docs for LuaSurface](https://lua-api.factorio.com/latest/LuaSurface.html).
| Metric | Output |
|:-------|-------:|
| `game.surfaces.nauvis.daytime` | |
| `game.surfaces.nauvis.wind_speed` | |
| `game.surfaces.nauvis.wind_orientation` | |
| `game.surfaces.nauvis.peaceful_mode` | |
| `game.surfaces.nauvis.ticks_per_day` | |
### Pollution
`game.pollution_statistics...` for map pollutants (input) and offsetting pollution sinks (output). For example `game.pollution_statistics.get_flow_count.boiler.input.one_minute` to see how much pollution boilers are causing.
### Much more
I ran out of time. Check out the API docs and please tell me if you think of examples that should be listed here.
## Why?
* Graphs and dashboards
- Bring a spare screen into your game with interesting graphs and metrics
- Watch and publish the state of a multiplayer game
- Use a graph or gauge as a source in OBS streams etc
* Chat bots
* Feedback and control (maybe)
* It's just JSON 🤷
### Install the mod (headless server)
### Configure the mod
## Roadmap
### Check JSON output
Pull requests for any of these are very welcome:
## Using Netdata
- [x] Console commands for admins only
- [x] Cached settings for performance
- [ ] Check for multiplayer desyncs
- [ ] More game API functions exposed
- [ ] Robust API calling methods
- [ ] Multiplayer settings
- [ ] Test and set commands parse icons as entity names
- [ ] Monitor own mod performance and server impact
- [ ] Expose metrics to other mods
- [ ] Snarf metrics from other mods?
- [ ] LTN
- [ ] ...
- [ ] Extremely basic UI output
- [ ] More output file formats beyond JSON
- [ ] Translations of the mod and supporting documentation
### Install Netdata
If you'd like to add anything else then its worth reaching out first.
### Install custom dashboards
### Run the JSON parser
## Join in
Please use the Factorio forum for feedback. I'd love to hear about bugs, improvement suggestions from players, server admins and mod makers, and screenshots of how you use the mod in your group :)

351
control.lua

@ -1,324 +1,65 @@
local ParcelDefinition = {
server = {
game = {
id = nil,
},
modes = {
cheat_mode_enabled = false,
cheat_mode_enabled_ever = false,
},
},
forces = {}
}
-----------------------------------
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
-- --------------------------------
-- Collect statistics from Factorio
-- game APIs and write them to disk
-- in JSON for consumption in other
-- dashboard software or something.
--
-- This is the runtime script which
-- is executed during gameplay.
-----------------------------------
-- http://lua-users.org/wiki/CopyTable
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- get_flow_stats() uses these to generate stats
-- in theory, its better for external graphing to manage time
-- and simply send last 1 minute of data
local times = {
one_minute = defines.flow_precision_index.one_minute,
-- ten_minutes = defines.flow_precision_index.ten_minutes,
-- one_hour = defines.flow_precision_index.one_hour,
-- ten_hours = defines.flow_precision_index.ten_hours,
-- fifty_hours = defines.flow_precision_index.fifty_hours,
-- two_hundred_fifty_hours = defines.flow_precision_index.two_hundred_fifty_hours,
-- one_thousand_hours = defines.flow_precision_index.one_thousand_hours
}
local function get_technology_stats(research)
if research == nil then
return nil
end
return {
name = research.name,
localised_name = research.localised_name,
localised_description = research.localised_description,
enabled = research.enabled,
visible_when_disabled = research.visible_when_disabled,
upgrade = research.upgrade,
researched = research.researched,
research_unit_count = research.research_unit_count,
research_unit_energy = research.research_unit_energy,
level = research.level,
research_unit_count_formula = research.research_unit_count_formula
}
end
local function get_surface_stats(surface)
return {
-- chunks
-- trains
name = surface.name,
total_pollution = surface.get_total_pollution(),
pollution_statistics = {
input_counts = game.pollution_statistics.input_counts,
output_counts = game.pollution_statistics.output_counts
},
active_entities_count = game.get_active_entities_count(surface.index),
always_day = surface.always_day,
daytime = surface.daytime,
darkness = surface.darkness,
wind_speed = surface.wind_speed,
wind_orientation = surface.wind_orientation,
peaceful_mode = surface.peaceful_mode,
freeze_daytime = surface.freeze_daytime,
ticks_per_day = surface.ticks_per_day,
dusk = surface.dusk,
dawn = surface.dawn,
evening = surface.evening,
morning = surface.morning,
solar_power_multiplier = surface.solar_power_multiplier,
min_brightness = surface.min_brightness
}
end
local function get_flow_stats(flow, prototypes)
local stats = {
input_counts = flow.input_counts,
output_counts = flow.output_counts,
count = {},
per_time_frame = {}
}
for time_name, time_definition in pairs(times) do
stats.count[time_name] = {}
stats.per_time_frame[time_name] = {}
for index, val in pairs(stats.input_counts) do
stats.count[time_name][index] = flow.get_flow_count({
name = prototypes[index].name,
input=true,
precision_index=time_definition,
count=true
})
stats.per_time_frame[time_name][index] = flow.get_flow_count({
name = prototypes[index].name,
input=true,
precision_index=time_definition,
count=false
})
end
end
--input_counts_5m = flow.get_flow_count()
return stats
end
local function table_length(table)
if table == nil then return 0 end
local count = 0
for k,v in pairs(table) do count = count + 1 end
return count
end
local function get_force_surface_details(force, surface)
return {
spawn_position = force.get_spawn_position(surface),
logistic_networks_count = table_length(force.logistic_networks[surface])
}
end
local function get_force_stats(force)
local force_stats = {
index = force.index,
name = force.name,
-- CAPABILITIES
ai_controllable = force.ai_controllable,
ghost_time_to_live = force.ghost_time_to_live,
deconstruction_time_to_live = force.deconstruction_time_to_live,
max_successful_attempts_per_tick_per_construction_queue = force.max_successful_attempts_per_tick_per_construction_queue,
max_failed_attempts_per_tick_per_construction_queue = force.max_failed_attempts_per_tick_per_construction_queue,
--auto_character_trash_slots = force.auto_character_trash_slots,
zoom_to_world_enabled = force.zoom_to_world_enabled,
zoom_to_world_ghost_building_enabled = force.zoom_to_world_ghost_building_enabled,
zoom_to_world_blueprint_enabled = force.zoom_to_world_blueprint_enabled,
zoom_to_world_deconstruction_planner_enabled = force.zoom_to_world_deconstruction_planner_enabled,
zoom_to_world_selection_tool_enabled = force.zoom_to_world_selection_tool_enabled,
character_logistic_requests = force.character_logistic_requests,
friendly_fire = force.friendly_fire,
share_chart = force.share_chart,
research_queue_enabled = force.research_queue_enabled,
research_enabled = force.research_enabled,
-- EVOLUTION
evolution_factor = force.evolution_factor,
evolution_factor_by_pollution = force.evolution_factor_by_pollution,
evolution_factor_by_time = force.evolution_factor_by_time,
evolution_factor_by_killing_spawners = force.evolution_factor_by_killing_spawners,
-- BONUSES
manual_mining_speed_modifier = force.manual_mining_speed_modifier,
laboratory_speed_modifier = force.laboratory_speed_modifier,
laboratory_productivity_bonus = force.laboratory_productivity_bonus,
worker_robots_speed_modifier = force.worker_robots_speed_modifier,
worker_robots_battery_modifier = force.worker_robots_battery_modifier,
worker_robots_storage_bonus = force.worker_robots_storage_bonus,
inserter_stack_size_bonus = force.inserter_stack_size_bonus,
stack_inserter_capacity_bonus = force.stack_inserter_capacity_bonus,
character_trash_slot_count = force.character_trash_slot_count,
maximum_following_robot_count = force.maximum_following_robot_count,
following_robots_lifetime_modifier = force.following_robots_lifetime_modifier,
-- load dependencies
character_running_speed_modifier = force.character_running_speed_modifier,
artillery_range_modifier = force.artillery_range_modifier,
character_build_distance_bonus = force.character_build_distance_bonus,
character_item_drop_distance_bonus = force.character_item_drop_distance_bonus,
character_reach_distance_bonus = force.character_reach_distance_bonus,
character_resource_reach_distance_bonus = force.character_resource_reach_distance_bonus,
character_item_pickup_distance_bonus = force.character_item_pickup_distance_bonus,
character_loot_pickup_distance_bonus = force.character_loot_pickup_distance_bonus,
character_inventory_slots_bonus = force.character_inventory_slots_bonus,
character_health_bonus = force.character_health_bonus,
mining_drill_productivity_bonus = force.mining_drill_productivity_bonus,
train_braking_force_bonus = force.train_braking_force_bonus,
-- technologies
-- recipes
-- general utility functions
require "script.utilities"
-- mod settings cache
Settings = require("script.settings")
-- SURFACES
surfaces = {},
-- initialize mod, set event handlers
-- this triggers OnNthTick() below
require "script.init"
-- console command definitions
require "script.commands"
-- ENTITIES
--get_entity_count(name) -- this has performance hit apparently
-- used to write or broadcast metrics
Publisher = require("script.publisher")
-- and ammo and turret prototypes for modifiers
-- keeps a list of metrics to watch, fetches
-- them when requested from the game api
Fetcher = require("script.fetcher")
-- loop through item entities:
-- get_item_launched()
-- loop through surfaces
-- get_trains()
-- players
-- logistic_networks
-- main loop
OnNthTick = function()
-- stats per opposing force?
-- iterate through requested metrics
for index, name in pairs(global.statorio.metrics) do
-- fetch metric
if not Fetcher.traverseApiForValues(name, function(name, val)
entity_build_count_statistics = get_flow_stats(force.entity_build_count_statistics, game.entity_prototypes),
item_production_statistics = get_flow_stats(force.item_production_statistics, game.item_prototypes),
fluid_production_statistics = get_flow_stats(force.fluid_production_statistics, game.fluid_prototypes),
kill_count_statistics = get_flow_stats(force.kill_count_statistics, game.entity_prototypes),
rockets_launched = force.rockets_launched,
items_launched = force.items_launched,
--connected_players = force.connected_players,
previous_research = get_technology_stats(force.previous_research),
current_research = get_technology_stats(force.current_research),
--research_queue
}
-- cache value for publication
Publisher.addMetric(name, data)
end)
then
for index, surface in pairs(game.surfaces) do
force_stats.surfaces[index] = get_force_surface_details(force, surface)
end
return force_stats
end
local dump_stats = function()
local parcel = deepcopy(ParcelDefinition)
-- failed
log("Failed to fetch metric "..name)
parcel.server.game.id = global.statorio.game_id
parcel.server.modes.cheat_mode_enabled = global.statorio.cheat_mode_enabled
parcel.server.modes.cheat_mode_enabled_ever = global.statorio.cheat_mode_enabled_ever
-- forces
for index, force in pairs(game.forces) do
parcel.forces[index] = get_force_stats(force)
end
end
-- Load other data onto here
-- game.print("Writing statistics to file")
game.write_file("stats.json", game.table_to_json(parcel))
end
local initialize = function()
-- when game starts, or mod added to existing save
-- use to initialize global variables
global.statorio = {
game_id = math.random(),
cheat_mode_enabled = false,
cheat_mode_enabled_ever = false
}
end
local OnTick = function()
-- A tick occured before the mod initialized
if global.statorio == nil then return end
-- Generate stats
dump_stats()
end
local registerEvents = function()
script.on_event(defines.events.on_player_cheat_mode_enabled, function(e)
global.statorio.cheat_mode_enabled_ever = true
global.statorio.cheat_mode_enabled = true
end)
script.on_event(defines.events.on_player_cheat_mode_disabled, function(e)
global.statorio.cheat_mode_enabled = false
end)
-- script.on_event(defines.events.on_tick, OnTick)
script.on_nth_tick(nil)
script.on_nth_tick(300, OnTick)
end
script.on_load(function()
registerEvents()
end)
script.on_init(function()
initialize()
registerEvents()
log("Statorio initialized")
end)
script.on_configuration_changed(function(data)
registerEvents()
log("Statorio configuration updated")
end)
-- publish all cached metrics
Publisher.publish()
end

328
control.lua.v1

@ -1,328 +0,0 @@
-- IDEAS: https://github.com/ncabatoff/promfacto
-- https://github.com/narc0tiq/YARM
-- get_flow_stats() uses these to generate stats
-- in theory, its better for external graphing to manage time
-- and simply send last 1 minute of data
local times = {
one_minute = defines.flow_precision_index.one_minute,
-- ten_minutes = defines.flow_precision_index.ten_minutes,
-- one_hour = defines.flow_precision_index.one_hour,
-- ten_hours = defines.flow_precision_index.ten_hours,
-- fifty_hours = defines.flow_precision_index.fifty_hours,
-- two_hundred_fifty_hours = defines.flow_precision_index.two_hundred_fifty_hours,
-- one_thousand_hours = defines.flow_precision_index.one_thousand_hours
}
local function get_technology_stats(research)
if research == nil then
return nil
end
return {
name = research.name,
localised_name = research.localised_name,
localised_description = research.localised_description,
enabled = research.enabled,
visible_when_disabled = research.visible_when_disabled,
upgrade = research.upgrade,
researched = research.researched,
research_unit_count = research.research_unit_count,
research_unit_energy = research.research_unit_energy,
level = research.level,
research_unit_count_formula = research.research_unit_count_formula
}
end
local function get_surface_stats(surface)
return {
-- chunks
-- trains
name = surface.name,
total_pollution = surface.get_total_pollution(),
pollution_statistics = {
input_counts = game.pollution_statistics.input_counts,
output_counts = game.pollution_statistics.output_counts
},
active_entities_count = game.get_active_entities_count(surface.index),
always_day = surface.always_day,
daytime = surface.daytime,
darkness = surface.darkness,
wind_speed = surface.wind_speed,
wind_orientation = surface.wind_orientation,
peaceful_mode = surface.peaceful_mode,
freeze_daytime = surface.freeze_daytime,
ticks_per_day = surface.ticks_per_day,
dusk = surface.dusk,
dawn = surface.dawn,
evening = surface.evening,
morning = surface.morning,
solar_power_multiplier = surface.solar_power_multiplier,
min_brightness = surface.min_brightness
}
end
local function get_flow_stats(flow, prototypes)
local stats = {
input_counts = flow.input_counts,
output_counts = flow.output_counts,
count = {},
per_time_frame = {}
}
for time_name, time_definition in pairs(times) do
stats.count[time_name] = {}
stats.per_time_frame[time_name] = {}
for index, val in pairs(stats.input_counts) do
stats.count[time_name][index] = flow.get_flow_count({
name = prototypes[index].name,
input=true,
precision_index=time_definition,
count=true
})
stats.per_time_frame[time_name][index] = flow.get_flow_count({
name = prototypes[index].name,
input=true,
precision_index=time_definition,
count=false
})
end
end
--input_counts_5m = flow.get_flow_count()
return stats
end
local function table_length(table)
if table == nil then return 0 end
local count = 0
for k,v in pairs(table) do count = count + 1 end
return count
end
local function get_force_surface_details(force, surface)
return {
spawn_position = force.get_spawn_position(surface),
logistic_networks_count = table_length(force.logistic_networks[surface])
}
end
local function get_force_stats(force)
local force_stats = {
index = force.index,
name = force.name,
-- CAPABILITIES
ai_controllable = force.ai_controllable,
ghost_time_to_live = force.ghost_time_to_live,
deconstruction_time_to_live = force.deconstruction_time_to_live,
max_successful_attempts_per_tick_per_construction_queue = force.max_successful_attempts_per_tick_per_construction_queue,
max_failed_attempts_per_tick_per_construction_queue = force.max_failed_attempts_per_tick_per_construction_queue,
auto_character_trash_slots = force.auto_character_trash_slots,
zoom_to_world_enabled = force.zoom_to_world_enabled,
zoom_to_world_ghost_building_enabled = force.zoom_to_world_ghost_building_enabled,
zoom_to_world_blueprint_enabled = force.zoom_to_world_blueprint_enabled,
zoom_to_world_deconstruction_planner_enabled = force.zoom_to_world_deconstruction_planner_enabled,
zoom_to_world_selection_tool_enabled = force.zoom_to_world_selection_tool_enabled,
character_logistic_requests = force.character_logistic_requests,
friendly_fire = force.friendly_fire,
share_chart = force.share_chart,
research_queue_enabled = force.research_queue_enabled,
research_enabled = force.research_enabled,
-- EVOLUTION
evolution_factor = force.evolution_factor,
evolution_factor_by_pollution = force.evolution_factor_by_pollution,
evolution_factor_by_time = force.evolution_factor_by_time,
evolution_factor_by_killing_spawners = force.evolution_factor_by_killing_spawners,
-- BONUSES
manual_mining_speed_modifier = force.manual_mining_speed_modifier,
laboratory_speed_modifier = force.laboratory_speed_modifier,
laboratory_productivity_bonus = force.laboratory_productivity_bonus,
worker_robots_speed_modifier = force.worker_robots_speed_modifier,
worker_robots_battery_modifier = force.worker_robots_battery_modifier,
worker_robots_storage_bonus = force.worker_robots_storage_bonus,
inserter_stack_size_bonus = force.inserter_stack_size_bonus,
stack_inserter_capacity_bonus = force.stack_inserter_capacity_bonus,
character_trash_slot_count = force.character_trash_slot_count,
maximum_following_robot_count = force.maximum_following_robot_count,
following_robots_lifetime_modifier = force.following_robots_lifetime_modifier,
character_running_speed_modifier = force.character_running_speed_modifier,
artillery_range_modifier = force.artillery_range_modifier,
character_build_distance_bonus = force.character_build_distance_bonus,
character_item_drop_distance_bonus = force.character_item_drop_distance_bonus,
character_reach_distance_bonus = force.character_reach_distance_bonus,
character_resource_reach_distance_bonus = force.character_resource_reach_distance_bonus,
character_item_pickup_distance_bonus = force.character_item_pickup_distance_bonus,
character_loot_pickup_distance_bonus = force.character_loot_pickup_distance_bonus,
character_inventory_slots_bonus = force.character_inventory_slots_bonus,
character_health_bonus = force.character_health_bonus,
mining_drill_productivity_bonus = force.mining_drill_productivity_bonus,
train_braking_force_bonus = force.train_braking_force_bonus,
-- technologies
-- recipes
-- SURFACES
surfaces = {},
-- ENTITIES
--get_entity_count(name) -- this has performance hit apparently
-- and ammo and turret prototypes for modifiers
-- loop through item entities:
-- get_item_launched()
-- loop through surfaces
-- get_trains()
-- players
-- logistic_networks
-- stats per opposing force?
entity_build_count_statistics = get_flow_stats(force.entity_build_count_statistics, game.entity_prototypes),
item_production_statistics = get_flow_stats(force.item_production_statistics, game.item_prototypes),
fluid_production_statistics = get_flow_stats(force.fluid_production_statistics, game.fluid_prototypes),
kill_count_statistics = get_flow_stats(force.kill_count_statistics, game.entity_prototypes),
rockets_launched = force.rockets_launched,
items_launched = force.items_launched,
--connected_players = force.connected_players,
previous_research = get_technology_stats(force.previous_research),
current_research = get_technology_stats(force.current_research),
--research_queue
}
for index, surface in pairs(game.surfaces) do
force_stats.surfaces[index] = get_force_surface_details(force, surface)
end
return force_stats
end
local function dump_stats()
for index, surface in pairs(game.surfaces) do
global.statorio.surfaces[index] = get_surface_stats(surface)
end
-- forces
for index, force in pairs(game.forces) do
global.statorio.forces[index] = get_force_stats(force)
end
game.print("Writing statistics to file "..game.tick..".json")
game.write_file(game.tick..".json", game.table_to_json(global.statorio))
end
local function set_next_update_tick()
global.statorio.next_update_tick = game.tick + 600
end
script.on_init(function()
-- when game starts, or mod added to existing save
-- use to initialize global variables
global.statorio = {}
global.statorio.server.game_modes.cheat_mode_enabled_ever = false
global.statorio.server.game_modes.cheat_mode_enabled = false
global.statorio.game_id = math.random()
global.statorio.forces = {}
global.statorio.surfaces = {}
global.statorio.players = {}
global.statorio.game = {}
global.statorio.game.is_demo = game.is_demo()
global.statorio.game.is_multiplayer = game.is_multiplayer()
-- too big to save each time
-- global.statorio.game.map_exchange_string = game.get_map_exchange_string()
-- map settings
-- difficulty settings
global.statorio.game.difficulty = game.difficulty
global.statorio.game.active_mods = game.active_mods
global.statorio.game.player_joins = 0
global.statorio.game.player_bans = 0
global.statorio.game.autosave_enabled = game.autosave_enabled
-- global.statorio.pollution_statistics = {}
set_next_update_tick()
end)
script.on_configuration_changed(function(data)
-- mod changes, deal with prototype or game setting changes
-- data contains changes
-- http://lua-api.factorio.com/latest/Concepts.html#ConfigurationChangedData
end)
script.on_event(defines.events.on_tick, function(e)
-- A tick occured before the mod initialized
if global.statorio == nil then return end
-- The mod is waiting
if game.tick < global.statorio.next_update_tick then return end
set_next_update_tick()
-- Generate stats
dump_stats()
end)
script.on_event(defines.events.on_player_cheat_mode_enabled, function(e)
global.statorio.server.game_modes.cheat_mode_enabled_ever = true
global.statorio.server.game_modes.cheat_mode_enabled = true
end)
script.on_event(defines.events.on_player_cheat_mode_disabled, function(e)
global.statorio.server.game_modes.cheat_mode_enabled = false
end)
script.on_event(defines.events.on_player_joined_game, function(e)
if global.statorio.players[e.player_index] == nil then
-- create blank player entry
global.statorio.players[e.player_index] = {
index = e.player_index,
name = game.players[e.player_index].name,
tag = game.players[e.player_index].tag,
color = game.players[e.player_index].color,
chat_color = game.players[e.player_index].chat_color,
joins = 0,
bans = 0,
connected = game.players[e.player_index].connected,
admin = game.players[e.player_index].admin,
afk_time = game.players[e.player_index].afk_time,
online_time = game.players[e.player_index].online_time,
last_online = game.players[e.player_index].last_online
}
end
global.statorio.players[e.player_index].joins = global.statorio.players[e.player_index].joins + 1
global.statorio.game.player_joins = global.statorio.game.player_joins + 1
end)
script.on_event(defines.events.on_player_banned, function(e)
global.statorio.players[e.player_index].bans = global.statorio.players[e.player_index].bans + 1
global.statorio.game.player_bans = global.statorio.game.player_bans + 1
end)

310
control.lua.v2

@ -1,310 +0,0 @@
local ParcelDefinition = {
server = {
game = {
id = nil,
},
modes = {
cheat_mode_enabled = false,
cheat_mode_enabled_ever = false,
},
},
forces = {}
}
-- http://lua-users.org/wiki/CopyTable
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- get_flow_stats() uses these to generate stats
-- in theory, its better for external graphing to manage time
-- and simply send last 1 minute of data
local times = {
one_minute = defines.flow_precision_index.one_minute,
-- ten_minutes = defines.flow_precision_index.ten_minutes,
-- one_hour = defines.flow_precision_index.one_hour,
-- ten_hours = defines.flow_precision_index.ten_hours,
-- fifty_hours = defines.flow_precision_index.fifty_hours,
-- two_hundred_fifty_hours = defines.flow_precision_index.two_hundred_fifty_hours,
-- one_thousand_hours = defines.flow_precision_index.one_thousand_hours
}
local function get_technology_stats(research)
if research == nil then
return nil
end
return {
name = research.name,
localised_name = research.localised_name,
localised_description = research.localised_description,
enabled = research.enabled,
visible_when_disabled = research.visible_when_disabled,
upgrade = research.upgrade,
researched = research.researched,
research_unit_count = research.research_unit_count,
research_unit_energy = research.research_unit_energy,
level = research.level,
research_unit_count_formula = research.research_unit_count_formula
}
end
local function get_surface_stats(surface)
return {
-- chunks
-- trains
name = surface.name,
total_pollution = surface.get_total_pollution(),
pollution_statistics = {
input_counts = game.pollution_statistics.input_counts,
output_counts = game.pollution_statistics.output_counts
},
active_entities_count = game.get_active_entities_count(surface.index),
always_day = surface.always_day,
daytime = surface.daytime,
darkness = surface.darkness,
wind_speed = surface.wind_speed,
wind_orientation = surface.wind_orientation,
peaceful_mode = surface.peaceful_mode,
freeze_daytime = surface.freeze_daytime,
ticks_per_day = surface.ticks_per_day,
dusk = surface.dusk,
dawn = surface.dawn,
evening = surface.evening,
morning = surface.morning,
solar_power_multiplier = surface.solar_power_multiplier,
min_brightness = surface.min_brightness
}
end
local function get_flow_stats(flow, prototypes)
local stats = {
input_counts = flow.input_counts,
output_counts = flow.output_counts,
count = {},
per_time_frame = {}
}
for time_name, time_definition in pairs(times) do
stats.count[time_name] = {}
stats.per_time_frame[time_name] = {}
for index, val in pairs(stats.input_counts) do
stats.count[time_name][index] = flow.get_flow_count({
name = prototypes[index].name,
input=true,
precision_index=time_definition,
count=true
})
stats.per_time_frame[time_name][index] = flow.get_flow_count({
name = prototypes[index].name,
input=true,
precision_index=time_definition,
count=false
})
end
end
--input_counts_5m = flow.get_flow_count()
return stats
end
local function table_length(table)
if table == nil then return 0 end
local count = 0
for k,v in pairs(table) do count = count + 1 end
return count
end
local function get_force_surface_details(force, surface)
return {
spawn_position = force.get_spawn_position(surface),
logistic_networks_count = table_length(force.logistic_networks[surface])
}
end
local function get_force_stats(force)
local force_stats = {
index = force.index,
name = force.name,
-- CAPABILITIES
ai_controllable = force.ai_controllable,
ghost_time_to_live = force.ghost_time_to_live,
deconstruction_time_to_live = force.deconstruction_time_to_live,
max_successful_attempts_per_tick_per_construction_queue = force.max_successful_attempts_per_tick_per_construction_queue,
max_failed_attempts_per_tick_per_construction_queue = force.max_failed_attempts_per_tick_per_construction_queue,
auto_character_trash_slots = force.auto_character_trash_slots,
zoom_to_world_enabled = force.zoom_to_world_enabled,
zoom_to_world_ghost_building_enabled = force.zoom_to_world_ghost_building_enabled,
zoom_to_world_blueprint_enabled = force.zoom_to_world_blueprint_enabled,
zoom_to_world_deconstruction_planner_enabled = force.zoom_to_world_deconstruction_planner_enabled,
zoom_to_world_selection_tool_enabled = force.zoom_to_world_selection_tool_enabled,
character_logistic_requests = force.character_logistic_requests,
friendly_fire = force.friendly_fire,
share_chart = force.share_chart,
research_queue_enabled = force.research_queue_enabled,
research_enabled = force.research_enabled,
-- EVOLUTION
evolution_factor = force.evolution_factor,
evolution_factor_by_pollution = force.evolution_factor_by_pollution,
evolution_factor_by_time = force.evolution_factor_by_time,
evolution_factor_by_killing_spawners = force.evolution_factor_by_killing_spawners,
-- BONUSES
manual_mining_speed_modifier = force.manual_mining_speed_modifier,
laboratory_speed_modifier = force.laboratory_speed_modifier,
laboratory_productivity_bonus = force.laboratory_productivity_bonus,
worker_robots_speed_modifier = force.worker_robots_speed_modifier,
worker_robots_battery_modifier = force.worker_robots_battery_modifier,
worker_robots_storage_bonus = force.worker_robots_storage_bonus,
inserter_stack_size_bonus = force.inserter_stack_size_bonus,
stack_inserter_capacity_bonus = force.stack_inserter_capacity_bonus,
character_trash_slot_count = force.character_trash_slot_count,
maximum_following_robot_count = force.maximum_following_robot_count,
following_robots_lifetime_modifier = force.following_robots_lifetime_modifier,
character_running_speed_modifier = force.character_running_speed_modifier,
artillery_range_modifier = force.artillery_range_modifier,
character_build_distance_bonus = force.character_build_distance_bonus,
character_item_drop_distance_bonus = force.character_item_drop_distance_bonus,
character_reach_distance_bonus = force.character_reach_distance_bonus,
character_resource_reach_distance_bonus = force.character_resource_reach_distance_bonus,
character_item_pickup_distance_bonus = force.character_item_pickup_distance_bonus,
character_loot_pickup_distance_bonus = force.character_loot_pickup_distance_bonus,
character_inventory_slots_bonus = force.character_inventory_slots_bonus,
character_health_bonus = force.character_health_bonus,
mining_drill_productivity_bonus = force.mining_drill_productivity_bonus,
train_braking_force_bonus = force.train_braking_force_bonus,
-- technologies
-- recipes
-- SURFACES
surfaces = {},
-- ENTITIES
--get_entity_count(name) -- this has performance hit apparently
-- and ammo and turret prototypes for modifiers
-- loop through item entities:
-- get_item_launched()
-- loop through surfaces
-- get_trains()
-- players
-- logistic_networks
-- stats per opposing force?
entity_build_count_statistics = get_flow_stats(force.entity_build_count_statistics, game.entity_prototypes),
item_production_statistics = get_flow_stats(force.item_production_statistics, game.item_prototypes),
fluid_production_statistics = get_flow_stats(force.fluid_production_statistics, game.fluid_prototypes),
kill_count_statistics = get_flow_stats(force.kill_count_statistics, game.entity_prototypes),
rockets_launched = force.rockets_launched,
items_launched = force.items_launched,
--connected_players = force.connected_players,
previous_research = get_technology_stats(force.previous_research),
current_research = get_technology_stats(force.current_research),
--research_queue
}
for index, surface in pairs(game.surfaces) do
force_stats.surfaces[index] = get_force_surface_details(force, surface)
end
return force_stats
end
local dump_stats = function()
local parcel = deepcopy(ParcelDefinition)
parcel.server.game.id = global.statorio.game_id
parcel.server.modes.cheat_mode_enabled = global.statorio.cheat_mode_enabled
parcel.server.modes.cheat_mode_enabled_ever = global.statorio.cheat_mode_enabled_ever
-- forces
for index, force in pairs(game.forces) do
parcel.forces[index] = get_force_stats(force)
end
-- Load other data onto here
game.print("Writing statistics to file "..game.tick..".json")
-- game.write_file(game.tick..".json", game.table_to_json(parcel))
game.write_file("stats.json", game.table_to_json(parcel))
end
script.on_init(function()
-- when game starts, or mod added to existing save
-- use to initialize global variables
global.statorio = {
game_id = math.random(),
next_update_tick = game.tick,
cheat_mode_enabled = false,
cheat_mode_enabled_ever = false,
}
end)
script.on_event(defines.events.on_tick, function(e)
-- A tick occured before the mod initialized
if global.statorio == nil then return end
-- Nothing to do yet
if game.tick < global.statorio.next_update_tick then return end
-- Generate stats
dump_stats()
-- Set next update tick
global.statorio.next_update_tick = game.tick + 600
end)
script.on_configuration_changed(function(data)
-- mod changes, deal with prototype or game setting changes
-- data contains changes
-- http://lua-api.factorio.com/latest/Concepts.html#ConfigurationChangedData
end)
script.on_event(defines.events.on_player_cheat_mode_enabled, function(e)
global.statorio.cheat_mode_enabled_ever = true
global.statorio.cheat_mode_enabled = true
end)
script.on_event(defines.events.on_player_cheat_mode_disabled, function(e)
global.statorio.cheat_mode_enabled = false
end)

9
info.json

@ -1,10 +1,11 @@
{
"name": "statorio",
"version": "1.0.0",
"version": "1.0.1",
"title": "Statorio",
"author": "aoi",
"author": "aoi44",
"homepage": "https://factorio.яю.com/statorio/",
"contact": "a@яю.com",
"factorio_version": "1.1",
"dependencies": ["base >= 1.0"],
"description": "Generates statistics"
}
"description": "Experimental! Export realtime JSON statistics to disk. Use the stat command to add, list and remove game API metrics. See homepage for usage"
}

8
locale/en/base.cfg

@ -0,0 +1,8 @@
[mod-name]
Statorio=Statorio
[mod-description]
Statorio=Generate statistics as a web page
[statorio-ui]
tab-server=Game Server

7
locale/en/settings.cfg

@ -0,0 +1,7 @@
[mod-setting-name]
statorio-frequency=Frequency (ticks)
statorio-filename=Filename
[mod-setting-description]
statorio-frequency=How often to generate game statistics.\n60 = every second, 3600 = every minute
statorio-filename=What to call the output JSON file (written to {GAME_DIR}/script-output)

26
public/index.html

@ -1,26 +0,0 @@
<!doctype html>
<html>
<head>
</head>
<body>
<p>ting</p>
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", "filename", true);
xhttp.send();
</script>
</body>
</html>

5
public/manifest.json

@ -1,5 +0,0 @@
{
"manifest_version": 2,
"version": "1.0",
"name": "Statorio"
}

8
public/manifest.json.save

@ -1,8 +0,0 @@
{
"manifest_version": 2,
"version": "1.0",
"name": "Statorio",
"permissions": [
""
]
}

8
public/manifest.json.save.1

@ -1,8 +0,0 @@
{
"manifest_version": 2,
"version": "1.0",
"name": "Statorio",
"permissions": [
""
]
}

57
script/commands.lua

@ -0,0 +1,57 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
-- Defines in-game console commands
-- used to control the metrics list
-- and test new metrics.
commands.add_command("stat", "Used to control metrics in Statorio mod\n[add|list|del] ", function(param)
player = game.players[param.player_index]
if not player then return end
if not player.admin then
player.print("Statorio commands are only available to admins")
return
end
argsArr = split(param.parameter, " ")
if argsArr[1] == "list" then
if table_size(global.statorio.metrics) > 0 then
for index, val in pairs(global.statorio.metrics) do
player.print("["..index.."] "..val)
end
else
player.print("No metrics to show")
end
elseif argsArr[1] == "add" then
table.insert(global.statorio.metrics, argsArr[2])
player.print("Added metric ["..table_size(global.statorio.metrics).."]")
elseif argsArr[1] == "del" then
table.remove(global.statorio.metrics, argsArr[2])
player.print("Removed metric ["..argsArr[2].."]")
elseif argsArr[1] == "test" then
if not Fetcher.traverseApiForValues(argsArr[2], function(name, val)
player.print(name..": "..safeString(val))
end)
then
player.print("Error - please check API reference")
end
else
player.print("Try list, add, del or test. For example:\n/stat list")
end
end)

165
script/fetcher.lua

@ -0,0 +1,165 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
-- Fetch metrics via attributes and
-- functions from the game's API.
local this = {}
this.overlays = {}
this.overlays.get_entity_count = function(parent, entity)
res, data = pcall(function()
return parent.get_entity_count(entity)
end)
return data
end
this.overlays.get_flow_count = function(parent, prototypeName, isInput, precisionIndex)
if type(isInput) ~= "boolean" then return nil end
if defines.flow_precision_index[precisionIndex] == nil then return nil end
res, data = pcall(function()
return parent.get_flow_count({
name=prototypeName,
input=isInput,
precision_index=defines.flow_precision_index[precisionIndex],
count=true
})
end)
return data
end
this.callOverlayFunction = function(functionName, callback, name, parent, args)
-- entity counts
if functionName == "get_entity_count" then
--data = parent.get_entity_count(args[1])
data = this.overlays.get_entity_count(parent, args[1])
this.doCallback(callback, name, data)
-- flow statistics
elseif functionName == "get_flow_count" and parent.object_name == "LuaFlowStatistics" then
if args[2] == 'input' then isInput = true else isInput = false end
if args[1] == "*" then
if (isInput) then definedRef = parent.input_counts else definedRef = parent.output_counts end
for index, val in pairs(definedRef) do
data = this.overlays.get_flow_count(parent, index, isInput, args[3])
-- replace the wildcard in the name for subsequent metrics
iterationName = string.gsub(name, '%*', index, 1)
this.doCallback(callback, iterationName, data)
end
return true
else
data = this.overlays.get_flow_count(parent, args[1], isInput, args[3])
this.doCallback(callback, name, data)
return true
end
end
end
-- call the user-supplied function after a metric has been fetched
this.doCallback = function(callback, name, val)
pcall(callback, name, val)
end
-- recursive function to walk the game API for the requested name
-- (given in dot notation)
-- if successful calls callback(name, value) and returns true
-- otherwise returns false
-- returns true if wildcard with no matches
this.traverseApiForValues = function(name, callback, remainingPath, parent)
-- name is a required parameter
if not name then return false end
-- optional parameter used in recursion
remainingPath = remainingPath or name
-- pop next key from start of dot notation string
key = string.match(remainingPath, "[^.]+")
if not key then return false end
-- remove key from remaining path
remainingPath = string.sub(remainingPath, string.len(key)+2)
-- typecast numeric keys for table index references
if isNumeric(key) then key = tonumber(key) end
-- first function iteration uses game object
if not parent then
return this.traverseApiForValues(name, callback, remainingPath, game)
end
-- ignore requests for things that don't exist
if key ~= "*" and not checkTableKeyExists(parent, key) then
return false
end
-- callable functions
if type(parent[key]) == "function" then
-- parse the remaining path for function args
args = split(remainingPath, ".")
-- call the function
data = this.callOverlayFunction(key, callback, name, parent, args)
if data then return true end
return false
end
-- wildcards
if key == "*" and type(parent) == "table" then
wildcardRemainingPath = remainingPath
if not table_size(parent) then
return false
end
for idx, item in pairs(parent) do
-- choose a key for this wildcard match
iterationKey = idx
if checkTableKeyExists(item, name) then iterationKey = item.name end
-- replace the wildcard in the name for subsequent metrics
iterationName = string.gsub(name, '%*', iterationKey, 1)
this.traverseApiForValues(iterationName, callback, wildcardRemainingPath, item)
end
return true
end
-- return if nothing else to traverse
if string.len(remainingPath)<1 then
this.doCallback(callback, name, parent[key])
return true
end
return this.traverseApiForValues(name, callback, remainingPath, parent[key])
end
return this

82
script/init.lua

@ -0,0 +1,82 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
-- Mod init routines for when first
-- loaded, upgraded, etc.
local initialize = function()
-- when game starts, or mod added to existing save
-- use to initialize global variables
log("initialize()")
-- global.statorio is persisted between sessions
global.statorio = {}
-- store user's metric list here for persistence
global.statorio.metrics = {}
end
local reload = function()
-- cache game settings
Settings.init()
-- prepare publisher
Publisher.init()
end
local registerEvents = function()
script.on_nth_tick(nil)
script.on_nth_tick(Settings.getRuntimeGlobal("statorio-frequency"), OnNthTick)
end
-- called when a save game is loaded that previously
-- had this mod
script.on_load(function()
reload()
registerEvents()
end)
-- called when a new game is created with this mod
-- or when the mod is first used on an existing game
script.on_init(function()
reload()
initialize()
registerEvents()
log("Statorio initialized")
end)
-- called any time the game version changes and any
-- time mod versions change, including adding or
-- removing mod
script.on_configuration_changed(function(data)
-- TODO: upgrade path
-- reload()
-- registerEvents()
-- log("Statorio configuration updated")
end)
script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
if not event then return end
if not event.setting then return end
-- settings are received from the game api, so are assumed to be valid
if event.setting_type == "runtime-global" then
Settings.setRuntimeGlobal(event.setting, settings.global[event.setting].value)
-- this change needs to re-register the on_nth_tick event
if event.setting == "statorio-frequency" then registerEvents() end
-- this change needs to clean up the old file
if event.setting == "statorio-filename" then Publisher.setFilename(settings.global[event.setting].value) end
end
end)

64
script/publisher.lua

@ -0,0 +1,64 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
-- Caches the latest metrics during
-- collection and writes data files
-- on demand.
local this = {}
-- keep filename in case it needs to be deleted
-- after the setting is changed
this.filename = nil
-- local cache of metrics before publication
this.metrics = {}
this.init = function()
this.setFilename(Settings.getRuntimeGlobal("statorio-filename"))
end
-- queue a metric to be published
this.addMetric = function(name, val)
-- TODO
-- option to swap bool to 1|0
-- option to swap nil to 0
this.metrics[name] = val
end
-- publish stored metrics
this.publish = function()
-- generate json
jsonString = game.table_to_json(this.metrics)
-- write to disk
game.write_file(this.filename, jsonString)
-- clear json
jsonString = nil
-- reset local cache
this.metrics = {}
end
this.setFilename = function(filename)
if this.filename ~= nil then
-- remove old file first
log("Deleting old stats file "..this.filename)
game.remove_path(this.filename)
end
-- set as filename for subsequent writes
this.filename = filename
end
return this

40
script/settings.lua

@ -0,0 +1,40 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
--
-- Cache the mod's settings locally
-- so frequent reads cannot cause a
-- performance hit in the game API.
-- This isn't exposed on the global
-- .statorio object. Other mods can
-- already access them with the API
-- if they ever want to.
local this = {}
this.runtimeGlobals = {}
this.init = function()
-- load all settings from the game API (expensive)
this.runtimeGlobals["statorio-filename"] = settings.global["statorio-filename"].value
this.runtimeGlobals["statorio-frequency"] = tonumber(settings.global["statorio-frequency"].value)
log("Settings loaded into cache")
end
this.getRuntimeGlobal = function(key)
return this.runtimeGlobals[key]
end
this.setRuntimeGlobal = function(key, val)
if isNumeric(val) then
this.runtimeGlobals[key] = tonumber(val)
else
this.runtimeGlobals[key] = val
end
end
return this

52
script/utilities.lua

@ -0,0 +1,52 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
--
-- Collect statistics from Factorio
-- game APIs and write them to disk
-- in JSON for consumption in other
-- dashboard software or something.
--
-- General utility functions
function split(str, character)
result = {}
index = 1
if str == nil then return result end
if character == nil then return result end
for s in string.gmatch(str, "[^"..character.."]+") do
result[index] = s
index = index + 1
end
return result
end
function safeString(val)
if type(val) == "string" then return val end
if type(val) == "number" then return val end
if type(val) == "boolean" then
if (val) then return "true" else return "false" end
end
return "nil"
end
function checkTableKeyExists(tableName, needle)
res, data = pcall(function() return tableName[needle] end)
if res and data ~= nil then return true end
if tonumber(needle) ~= nil and table_size(tableName) <= tonumber(needle) then return true end
return false
end
function isNumeric(x)
if tonumber(x) ~= nil then
return true
end
return false
end

24
settings.lua

@ -0,0 +1,24 @@
-- ___ _ _ _
-- / __| |_ __ _| |_ ___ _ _(_)___
-- \__ \ _/ _` | _/ _ \ '_| / _ \
-- |___/\__\__,_|\__\___/_| |_\___/
--
-- This file declares the available
-- mod settings for players to set.
data:extend({
{
type = "int-setting",
name = "statorio-frequency",
setting_type = "runtime-global",
default_value = 180, -- every 3 seconds
minimum_value = 30, -- half a second
maximum_value = 3600*5, -- every 5 minutes
},
{
type = "string-setting",
name = "statorio-filename",
setting_type = "runtime-global",
default_value = "stats.json"
}
})

BIN
thumbnail.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

55
watcher/main.js

@ -1,55 +0,0 @@
const fs = require('fs');
var lynx = require('lynx');
var metrics = new lynx('localhost', 8125);
const filePath = '../../../script-output/stats.json';
processFileMetrics();
fs.watch(filePath, function(eventName, filename) {
if (filename){
setTimeout(
() => processFileMetrics(),
0
);
}
});
function processFileMetrics() {
try {
const file = fs.readFileSync(filePath);
console.log(JSON.parse(file));
/* data = flattenObject({ 'factorio': JSON.parse(file) });
for (const key in data) {
metrics.gauge(key, data[key]);
}
*/
} catch (e) {}
}
function processData() {
}
// https://stackoverflow.com/a/53739792/4623317
function flattenObject(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object' && ob[i] !== null) {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
}

BIN
watcher/node_modules/lynx/.npmignore.gz generated vendored

Binary file not shown.

BIN
watcher/node_modules/lynx/.travis.yml.gz generated vendored

Binary file not shown.

24
watcher/node_modules/lynx/CONTRIBUTING.md generated vendored

@ -1,24 +0,0 @@
# Contributing
Everyone is welcome to contribute with patches, bug-fixes and new features
1. Create an [issue][2] on github so the community can comment on your idea
2. Fork `lynx` in github
3. Create a new branch `git checkout -b my_branch`
4. Create tests for the changes you made
5. Make sure you pass both existing and newly inserted tests
6. Commit your changes
7. Push to your branch `git push origin my_branch`
8. Create a pull request
## Tests
Tests are written in `node-tap`. If you want to create a new test create a file named `my-test-name-test.js` under the `tests` directory and it will be automatically picked up by `tap`. Before you do that however please check if a test doesn't already exist.
Tests run against a udp ""server"", which is on `tests/macros.js`. If your test name is `foo-bar` this helper function will read a fixtures located in `tests/fixtures/foo-bar.json`.
Each fixture is an array containing strings, the strings that we expect the client to send when we issue certain commands.
Check `tests/counting-test.js` and `tests/fixtures/counting.js` for an example
[2]: http://github.com/dscape/lynx/issues

10
watcher/node_modules/lynx/LICENSE generated vendored

@ -1,10 +0,0 @@
MIT License
Copyright 2012 Lloyd Hilaiel, Nuno Job. All rights reserved.
Copyright 2011 Steve Ivy. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

174
watcher/node_modules/lynx/README.md generated vendored

@ -1,174 +0,0 @@
# lynx
![NPM Downloads](http://img.shields.io/npm/dm/lynx.svg?style=flat) ![NPM Version](http://img.shields.io/npm/v/lynx.svg?style=flat)
A minimalistic node.js client for [statsd] server. Fork of original work by [sivy]
`lynx` features:
* **Minimalistic** — there is only a minimum of abstraction between you and
statsd
* **Streams** — You can stream in and out of a `lynx` connection
* **Re-usable UDP Connections** — Keeps UDP connections open for a certain time
* **Errors** — Pluggable error handling, by default errors are ignored
## Quick Start
```
$ npm install lynx
$ node
> var lynx = require('lynx');
//
// Options in this instantiation include:
// * `on_error` function to be executed when we have errors
// * `socket` if you wish to just use a existing udp socket
// * `scope` to define the a prefix for all stats, e.g. with `scope`
// 'product1' and stat 'somestat' the key would actually be
// 'product1.somestat'
//
> var metrics = new lynx('localhost', 8125);
{ host: 'localhost', port: 8125 }
> metrics.increment('node_test.int');
> metrics.decrement('node_test.int');
> metrics.timing('node_test.some_service.task.time', 500); // time in ms
> metrics.gauge('gauge.one', 100);
> metrics.set('set.one', 10);
```
This is the equivalent to:
``` sh
echo "node_test.int:1|c" | nc -w 0 -u localhost 8125
echo "node_test.int:-1|c" | nc -w 0 -u localhost 8125
echo "node_test.some_service.task.time:500|ms" | nc -w 0 -u localhost 8125
echo "gauge.one:100|g" | nc -w 0 -u localhost 8125
echo "set.one:10|s" | nc -w 0 -u localhost 8125
```
The protocol is super simple, so feel free to check out the source code to understand how everything works.
## Advanced
### Sampling
If you want to track something that happens really, really frequently, it can overwhelm StatsD with UDP packets. To work around that, use the optional sampling rate for metrics. This will only send packets a certain percentage of time. For very frequent events, this will give you a statistically accurate representation of your data.
Sample rate is an optional parameter to all of the metric API calls. A valid sample rate is 0.0 - 1.0. Values of 0.0 will never send any packets, and values of 1.0 will send every packet.
In these examples we are samping at a rate of 0.1, meaning 1-in-10 calls to send a sample will actually be sent to StatsD.
```
var metrics = new lynx('localhost', 8125);
metrics.increment('node_test.int', 0.1);
metrics.decrement('node_test.int', 0.1);
metrics.timing('node_test.some_service.task.time', 500, 0.1);
metrics.gauge('gauge.one', 100, 0.1);
metrics.set('set.one', 10, 0.1);
var timer2 = metrics.createTimer('node_test.some_service.task2.time', 0.1);
timer2.stop();
```
### Streams
You can stream to `lynx`:
``` js
fs.createReadStream('file.statsd')
.pipe(new lynx('localhost', port))
.pipe(fs.createReadStream('file-fixed.statsd'))
;
```
Feel free to check the `stream-test` for more info.
### Timers
If you wish to measure timing you can use the `timer()` functionality.
``` js
var metrics = new lynx('localhost', 8125)
, timer = metrics.createTimer('some.interval')
;
//
// Should send something like "some.interval:100|ms"
//
setTimeout(function () {
timer.stop();
}, 100);
```
Timers use `Date.getTime()` which is known for being imprecise at the ms level. If this is a problem to you please submit a pull request and I'll take it.
### Batching
Batching is possible for `increment`, `decrement`, and count:
``` js
metrics.decrement(['uno', 'two', 'trezentos']);
```
If you want to mix more than one type of metrics in a single packet you can use `send`, however you need to construct the values yourself. An example:
``` js
//
// This code is only to exemplify the functionality
//
// As of the current implementation the sample rate is processed per group
// of stats and not per individual stat, meaning either all would be send
// or none would be sent.
//
metrics.send(
{ "foo" : "-1|c" // count
, "bar" : "15|g" // gauge
, "baz" : "500|ms" // timing
, "boaz": "40|s" // set
}, 0.1); // sample rate at `0.1`
```
### Closing your socket
You can close your open socket when you no longer need it by using `metrics.close()`.
### Errors
By default `errors` get logged. If you wish to change this behavior simply specify a `on_error` function when instantiating the `lynx` client.
``` js
function on_error(err) {
console.log(err.message);
}
var connection = new lynx('localhost', 1234, {on_error: on_error});
```
Source code is super minimal, if you want try to get familiar with when errors occur check it out. If you would like to change behavior on how this is handled send a pull request justifying why and including the alterations you would like to propose.
## Tests
Run the tests with `npm`.
``` sh
npm test
```
## Meta
`\. ,/'
|\\____//|
)/_ `' _\(
,'/-`__'-\`\
/. (_><_) ,\
` )/`--'\(`' atc
` '
* travis: [![build status](https://secure.travis-ci.org/dscape/lynx.png)](http://travis-ci.org/dscape/lynx)
* code: `git clone git://github.com/dscape/lynx.git`
* home: <http://github.com/dscape/lynx>
* bugs: <http://github.com/dscape/lynx/issues>
`(oo)--',-` in [caos]
[caos]: http://caos.di.uminho.pt
[sivy]: https://github.com/sivy/node-statsd
[statsd]: https://github.com/etsy/statsd

664
watcher/node_modules/lynx/lib/lynx.js generated vendored

@ -1,664 +0,0 @@
var dgram = require('dgram')
, Stream = require('stream').Stream
, util = require('util')
, parser = require('statsd-parser')
//
// `Math.random` doesn't cut it, based on tests from sampling.js
// Variations are wild for large data sets
//
, mersenne = require('mersenne')
, mt = new mersenne.MersenneTwister19937()
, noop = function noop() {}
;
function makeError(opts) {
var error = new Error(opts.message);
error.f = opts.f;
error.args = opts.args;
return error;
}
//
// Max idle time for a ephemeral socket
//
var EPHEMERAL_LIFETIME_MS = 1000;
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors ~~
//
//
// ### constructor Lynx(host, port, socket)
// #### @host {String} Server host name
// #### @port {Number} Server port
// #### @options {Object} Aditional options
// #### @options.socket {Object} Optional socket if we want to share
// #### @options.on_error {Function} A function to execute on errors
// #### @options.scope {String} define the a prefix for all stats,
// e.g. with `scope` 'product1' and stat 'somestat' the key would
// actually be 'product1.somestat'.
//
// var client = new lynx('localhost', 8125);
//
// Returns a new `Lynx` client
//
function Lynx(host, port, options) {
if (!(this instanceof Lynx)) {
return new Lynx(host, port, options);
}
var self = this;
//
// Server hostname and port
//
this.host = host || '127.0.0.1';
this.port = port || 8125;
//
// Optional shared socket
//
this.socket = options && options.socket;
//
// Handle prefix
//
this.scope = options && options.scope || options && options.prefix || '';
//
// groups in graphite are delimited by `.` so we need to make sure our
// scope ends with `.`. If it doesn't we just add it (unless we have no
// scope defined).
//
if(typeof this.scope === 'string' && this.scope !== '' &&
!/\.$/.test(this.scope)) {
this.scope += '.';
}
//
// When a *shared* socked isn't provided, an ephemeral
// socket is demand allocated. This ephemeral socket is closed
// after being idle for EPHEMERAL_LIFETIME_MS.
//
this.ephemeral_socket = undefined;
this.last_used_timer = undefined;
//
// Set out error handling code
//
this.on_error = options && typeof options.on_error === 'function'
? options.on_error
: this._default_error_handler
;
//
// Stream properties
//
this.readable = true;
this.writable = true;
this.parser = parser.createStream();
this.parser.on('error', this.on_error);
this.parser.on('stat', function (text, stat_obj) {
var stat = {};
//
// Construct a statsd value|type pair
//
stat[stat_obj.stat] = stat_obj.value + '|' + stat_obj.type;
//
// Add sample rate if one exists
//
if(stat_obj.sample_rate) {
stat[stat_obj.stat] += '@' + stat_obj.sample_rate;
self.send(stat, parseFloat(stat_obj.sample_rate));
}
else {
self.send(stat);
}
});
}
util.inherits(Lynx, Stream);
//
// ### constructor Timer(stat, sample_rate)
// #### @stat {String} Stat key, in `foo:1|ms` would be foo
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// var timer = client.Timer('foo');
//
// //
// // Sends something like: `foo:100|ms` via udp to the server
// //
// setTimeout(function {
// timer.stop();
// }, 100);
//
// Returns a timer. When stopped, this transmits an interval
//
Lynx.prototype.createTimer = function createTimer(stat, sample_rate) {
var self = this
, start_time = new Date ().getTime()
, stopped = false
, duration
, start_hrtime
;
if (typeof process.hrtime === "function") {
var start_hrtime = process.hrtime();
}
//
// ### function stop()
//
// Stops the timer and issues the respective interval.
// Check example above
//
function stop() {
//
// If timer is already stopped just ignore the request
//
if(stopped) {
self.on_error(
makeError({ message : "Can't stop a timer twice"
, f : 'stop'
}));
return;
}
//
// Calculate duration
//
if (start_hrtime) {
var stop_hrtime = process.hrtime()
, seconds = stop_hrtime[0] - start_hrtime[0]
, nanos = stop_hrtime[1] - start_hrtime[1]
;
duration = seconds * 1000 + nanos / 1000000
} else {
duration = new Date ().getTime() - start_time;
}
//
// Emit
//
self.timing(stat, duration, sample_rate);
//
// So no one stops a timer twice (causing two emits)
//
stopped = true;
}
//
// The closure that is returned
//
return {
stat : stat
, sample_rate : sample_rate
, start_time : start_time
, stop : stop
};
};
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ api ~~
//
//
// ### function increment(stats, sample_rate)
// #### @stats {String|Array} Stat key, in `foo:1|ms` would be foo
// Optionally an array of `stats`.
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// client.increment('getho');
// client.increment(['not', 'cool']);
//
// Incremenents the desired stat(s)
//
Lynx.prototype.increment = function increment(stats, sample_rate) {
this.count(stats, 1, sample_rate);
};
//
// ### function decrement(stats, sample_rate)
// #### @stats {String|Array} Stat key, in `foo:1|ms` would be foo
// Optionally an array of `stats`.
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// client.decrement('hey.you');
//
// Decrements the desired stat(s)
//
Lynx.prototype.decrement = function decrement(stats, sample_rate) {
this.count(stats, -1, sample_rate);
};
//
// ### function count(stats, delta, sample_rate)
// #### @stats {String|Array} Stat key, in `foo:1|ms` would be foo
// Optionally an array of `stats`.
// #### @delta {Number} Amount to add (or remove) from given stat
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// client.count('python.fun', -100);
//
// Sends counting information to statsd. Normally this is invoked via
// `increment` or `decrement`
//
Lynx.prototype.count = function count(stats, delta, sample_rate) {
//
// If we are given a string stat (key) then transform it into array
//
if (typeof stats === 'string') {
stats = [stats];
}
//
// By now stats must be an array
//
if(!Array.isArray(stats)) {
//
// Error: Can't set if its not even an array by now
//
this.on_error(
makeError({ message : "Can't set if its not even an array by now"
, f : 'count'
, args : arguments
}));
return;
}
//
// Delta is required and must exist or we will send crap to statsd
//
if (typeof delta!=='number' && typeof delta!=='string' || isNaN(delta)) {
//
// Error: Must be either a number or a string, we cant send other stuff
//
this.on_error(
makeError({ message : 'Must be either a number or a string'
, f : 'count'
, args : arguments
}));
return;
}
//
// Batch up all these stats to send
//
var batch = {};
for(var i in stats) {
batch[stats[i]] = delta + '|c';
}
//
// Send all these stats
//
this.send(batch, sample_rate);
};
//
// ### function timing(stat, duration, sample_rate)
// #### @stat {String} Stat key, in `foo:1|ms` would be foo
// #### @duration {Number} Timing duration in ms.
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// client.timing('foo.bar.time', 500);
//
// Sends timing information for a given stat.
//
Lynx.prototype.timing = function timing(stat, duration, sample_rate) {
var stats = {};
stats[stat] = duration + '|ms';
this.send(stats, sample_rate);
};
//
// ### function set(stat, value, sample_rate)
// #### @stat {String} Stat key, in `foo:1|s` would be foo
// #### @value {Number} Value for this set
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// client.set('set1.bar', 567);
//
// Set for a specific stat
//
Lynx.prototype.set = function set(stat, value, sample_rate) {
var stats = {};
stats[stat] = value + '|s';
this.send(stats, sample_rate);
};
//
// ### function gauge(stat, value, sample_rate)
// #### @stat {String} Stat key, in `foo:1|g` would be foo
// #### @value {Number} Value for this set
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var client = new lynx('localhost', 8125);
// client.gauge('gauge1.bar', 567);
//
// Send a gauge to statsd
//
Lynx.prototype.gauge = function gauge(stat, value, sample_rate) {
var stats = {};
stats[stat] = value + '|g';
this.send(stats, sample_rate);
};
//
// ### function send(stats, sample_rate)
// #### @stats {Object} A stats object
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var lynx = require('lynx');
// var client = new lynx('localhost', 8125);
// client.send(
// { "foo" : "1|c"
// , "bar" : "-1|c"
// , "baz" : "500|ms"
// });
//
// Will sample this data for a given sample_rate. If a random generated
// number matches that sample_rate then stats get returned and the sample
// rate gets appended ("|@0.5" in this case). Else we get an empty object.
//
Lynx.prototype.send = function send(stats, sample_rate) {
var self = this
, sampled_stats = Lynx.sample(stats, sample_rate)
, all_stats = Object.keys(sampled_stats)
//
// Data to be sent
//
, send_data
;
//
// If this object is empty (enumerable properties)
//
if(all_stats.length === 0) {
//
// Error: Nothing to send
//
this.on_error(
makeError({ message : 'Nothing to send'
, f : 'send'
, args : arguments
}));
return;
}
//
// Construct our send request
// If we have multiple stats send them in the same udp package
// This is achieved by having newline separated stats.
//
send_data = all_stats.map(function construct_stat(stat) {
return self.scope + stat + ':' + sampled_stats[stat];
}).join('\n');
//
// Encode our data to a buffer
//
var buffer = new Buffer(send_data, 'utf8')
, socket
;
//
// Do we already have a socket object we can use?
//
if (this.socket === undefined) {
//
// Do we have an ephemeral socket we can use?
//
if (!this.ephemeral_socket) {
//
// Create one
//
this.ephemeral_socket = dgram.createSocket('udp4');
//
// Register on error: Failed sending the buffer
//
this.ephemeral_socket.on('error', function (err) {
err.reason = err.message;
err.f = 'send';
err.message = 'Failed sending the buffer';
err.args = arguments;
self.on_error(err);
return;
});
}
socket = this.ephemeral_socket;
} else {
//
// Reuse our socket
//
socket = this.socket;
}
//
// Update the last time this socket was used
// This is used to make the socket ephemeral
//
this._update_last_used();
//
// Send the data
//
this.emit('data', buffer);
socket.send(buffer, 0, buffer.length, this.port, this.host, noop);
};
//
// ### function close()
//
// var client = new lynx('localhost', 8125);
// client.increment("zigzag");
// client.close();
//
// Closes our socket object after we are done with it
//
Lynx.prototype.close = function close() {
//
// User defined socket
//
if (this.socket) {
this.socket.close();
this.socket = undefined;
}
//
// Ephemeral socket
//
if (this.ephemeral_socket) {
this.ephemeral_socket.close();
this.ephemeral_socket = undefined;
}
//
// Timer
//
if (this.last_used_timer) {
clearTimeout(this.last_used_timer);
this.last_used_timer = undefined;
}
};
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ streams ~~
//
//
// ### function write()
//
// Implements `Stream.prototype.write()`.
//
Lynx.prototype.write = function write(buffer) {
this.parser.write(buffer);
};
//
// ### function end()
//
// Implements `Stream.prototype.end()`.
//
Lynx.prototype.end = function end(buffer) {
//
// If there's stuff to flush please do
//
if (arguments.length) {
this.write(buffer);
}
//
// Make this not writable
//
this.writable = false;
};
//
// ### function destroy()
//
// Implements `Stream.prototype.destroy()`. Nothing to do here, we don't
// open any stuff
//
Lynx.prototype.destroy = function destroy() {
this.writable = false;
};
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ aux ~~
//
//
// ### function sample(stats, sample_rate)
// #### @stats {Object} A stats object
// #### @sample_rate {Number} Determines the sampling rate, e.g. how many
// packets should be sent. If set to 0.1 it sends 1 in each 10.
//
// var lynx = require('lynx');
// lynx.sample(
// { "foo" : "1|c"
// , "bar" : "-1|c"
// , "baz" : "500|ms"
// }, 0.5);
//
// Will sample this data for a given sample_rate. If a random generated
// number matches that sample_rate then stats get returned and the sample
// rate gets appended ("|@0.5" in this case). Else we get an empty object.
//
Lynx.sample = function sample(stats, sample_rate) {
//
// If we don't have a sample rate between 0 and 1
//
if (typeof sample_rate !== 'number' || sample_rate > 1 || sample_rate < 0) {
//
// Had to ignore the invalid sample rate
// Most of the times this is because sample_rate is undefined
//
return stats;
}
var sampled_stats = {};
//
// Randomly determine if we should sample this specific instance
//
if (mt.genrand_real2(0,1) <= sample_rate) {
//
// Note: Current implementation either sends all stats for a specific
// sample rate or sends none. Makes one wonder if granularity
// should be at the individual stat level
//
Object.keys(stats).forEach(function construct_sampled(stat) {
var value = stats[stat];
sampled_stats[stat] = value + '|@' + sample_rate;
});
}
return sampled_stats;
};
//
// ### function _update_last_used()
//
// An internal function update the last time the socket was
// used. This function is called when the socket is used
// and causes demand allocated ephemeral sockets to be closed
// after a period of inactivity.
//
Lynx.prototype._update_last_used = function _update_last_used() {
var self = this;
//
// Only update on the ephemeral socket
//
if (this.ephemeral_socket) {
//
// Clear existing timeouts
//
if (this.last_used_timer) {
clearTimeout(this.last_used_timer);
}
//
// Update last_used_timer
//
this.last_used_timer = setTimeout(function() {
//
// If we have an open socket close it
//
if (self.ephemeral_socket) {
self.ephemeral_socket.close();
}
//
// Delete the socket
//
delete self.ephemeral_socket;
}, EPHEMERAL_LIFETIME_MS);
}
};
//
// ### function default_error_handler()
// #### @err {Object} The error object. Includes:
// err.message, err.*
//
// Function that defines what to do on error.
// Errors are soft errors, and while interesting they are mostly informative
// A simple console log would do but that doesn't allow people to do
// custom stuff with errors
//
Lynx.prototype._default_error_handler = function _default_error_handler(e) {
this.emit('error', e);
};
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ exports ~~
//
module.exports = Lynx;

83
watcher/node_modules/lynx/package.json generated vendored

@ -1,83 +0,0 @@
{
"_from": "lynx",
"_id": "lynx@0.2.0",
"_inBundle": false,
"_integrity": "sha1-eeZnRTDaQYPoeVO9aGFx4HDaULk=",
"_location": "/lynx",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "lynx",
"name": "lynx",
"escapedName": "lynx",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/lynx/-/lynx-0.2.0.tgz",
"_shasum": "79e6674530da4183e87953bd686171e070da50b9",
"_spec": "lynx",
"_where": "/home/col/.factorio/mods/statorio_1.0.0/watcher",
"author": {
"name": "Lloyd Hilaiel"
},
"bugs": {
"url": "https://github.com/dscape/lynx/issues"
},
"bundleDependencies": false,
"contributors": [
{
"name": "Nuno Job",
"email": "nunojobpinto@gmail.com",
"url": "http://nunojob.com"
},
{
"name": "Mark Bogdanoff",
"url": "https://github.com/bog"
}
],
"dependencies": {
"mersenne": "~0.0.3",
"statsd-parser": "~0.0.4"
},
"deprecated": false,
"description": "Minimalistic StatsD client for Node.js programs",
"devDependencies": {
"tap": "~0.3.2"
},
"directories": {
"lib": "./lib/"
},
"homepage": "https://github.com/dscape/lynx#readme",
"keywords": [
"stats",
"metrics",
"metricsd",
"statsd",
"etsy",
"statsd client",
"statsd driver",
"graphite"
],
"licenses": [
{
"type": "MIT",
"url": "http://github.com/dscape/lynx/raw/master/LICENSE"
}
],
"main": "./lib/lynx",
"name": "lynx",
"repository": {
"type": "git",
"url": "git://github.com/dscape/lynx.git"
},
"scripts": {
"test": "tap tests/*-test.js"
},
"version": "0.2.0"
}

12
watcher/node_modules/lynx/tests/counts-test.js generated vendored

@ -1,12 +0,0 @@
var macros = require('./macros');
//
// Our `counting` tests
// Should match `tests/fixtures/counting.json`
//
macros.matchFixturesTest('counts', function runTest(connection) {
connection.increment('foo.bar');
connection.decrement('foo.baz');
connection.decrement(['uno', 'two', 'trezentos']);
connection.count('boaz', 101);
});

44
watcher/node_modules/lynx/tests/errors-test.js generated vendored

@ -1,44 +0,0 @@
var macros = require('./macros')
, lynx = macros.lynx
, port = macros.udpServerPort
, test = macros.test
, fixture = require('./fixtures/errors.json')
;
test('errors', function (t) {
function on_error(actual) {
var expected = fixture.shift();
//
// Should return the function that invoked this and the arguments
// for inspection
//
t.ok(actual.f, 'should have a reference to the function');
t.ok(actual.args, 'args should be supplied');
//
// Message should match fixture
//
t.equal(expected, actual.message);
//
// No more tests to run
//
if(fixture.length === 0) {
//
// Nothing more to do.
//
t.end();
}
}
//
// Wrong host
//
var connection = new lynx('locahost', port, {on_error: on_error});
connection.count(1);
connection.count('foo', NaN);
connection.count('foo', undefined);
connection.send({'foo': '1|c'}, 0);
connection.count('foo', 10);
});

5
watcher/node_modules/lynx/tests/fixtures/counts.json generated vendored

@ -1,5 +0,0 @@
[ "foo.bar:1|c"
, "foo.baz:-1|c"
, "uno:-1|c\ntwo:-1|c\ntrezentos:-1|c"
, "boaz:101|c"
]

6
watcher/node_modules/lynx/tests/fixtures/errors.json generated vendored

@ -1,6 +0,0 @@
[ "Can't set if its not even an array by now"
, "Must be either a number or a string"
, "Must be either a number or a string"
, "Nothing to send"
, "Failed sending the buffer"
]

3
watcher/node_modules/lynx/tests/fixtures/gauges.json generated vendored

@ -1,3 +0,0 @@
[ "foo.gauge.1:500|g"
, "foo.gauge.2:15|g"
]

5
watcher/node_modules/lynx/tests/fixtures/scopes.json generated vendored

@ -1,5 +0,0 @@
[ "scope.bar:1|c"
, "scope.baz:-1|c"
, "scope.uno:-1|c\nscope.two:-1|c\nscope.trezentos:-1|c"
, "scope.boaz:101|c"
]

3
watcher/node_modules/lynx/tests/fixtures/sets.json generated vendored

@ -1,3 +0,0 @@
[ "set1.foo:765|s"
, "set1.bar:567|s"
]

13
watcher/node_modules/lynx/tests/fixtures/stream-recv.json generated vendored

@ -1,13 +0,0 @@
[ "foo.bar:1|c"
, "foo.baz:-1|c"
, "uno:-1|c"
, "two:-1|c"
, "trezentos:-1|c"
, "boaz:101|c"
, "foo.bar.time:500|ms"
, "set1.bar:567|s"
, "foo.gauge.2:15|g"
, "boaz:101|c"
, "set1.bar:567|s"
, "foo.gauge.2:15|g"
]

10
watcher/node_modules/lynx/tests/fixtures/stream-send.json generated vendored

@ -1,10 +0,0 @@
[ "foo.bar:1|c"
, "foo.baz:-1|c"
, "uno:-1|c\n two:-1|c\n trezentos:-1|c"
, "boaz:101|c"
, "foo.bar.time:500|ms\n set1.bar:567|s \n foo.gauge.2:15|g"
, "boaz:1"
, "01"
, "|c\n set"
, "1.bar:567|s \n foo.gauge.2:15|g"
]

5
watcher/node_modules/lynx/tests/fixtures/timings.json generated vendored

@ -1,5 +0,0 @@
[ "foo.baz.time:10|ms"
, "foo.bar.time:500|ms"
, "foo.interval:~200|ms"
, "bar.comes.first:~100|ms"
]

10
watcher/node_modules/lynx/tests/gauges-test.js generated vendored

@ -1,10 +0,0 @@
var macros = require('./macros');
//
// Our `gauges` tests
// Should match `tests/fixtures/gauges.json`
//
macros.matchFixturesTest('gauges', function runTest(connection) {
connection.gauge('foo.gauge.1', 500);
connection.gauge('foo.gauge.2', 15);
});

11
watcher/node_modules/lynx/tests/global_leaks.js generated vendored

@ -1,11 +0,0 @@
var specify = require('specify')
, lynx = require('../lib/lynx')
;
specify('errors', function (assert) {
var connection = new lynx('locahost', 86875);
connection.increment('a');
assert.ok(true);
});
specify.run();

290
watcher/node_modules/lynx/tests/macros.js generated vendored

@ -1,290 +0,0 @@
var path = require('path')
, dgram = require('dgram')
, test = require('tap').test
, lynx = require('../lib/lynx')
, macros = exports
;
//
// Percentage allowed for errors in aproximations
// Like, duration is around 100ms. Means for 10% error can be 10ms.
//
var MAX_APROX_ERROR = process.env.MAX_APROX_ERROR
? parseInt(MAX_APROX_ERROR, 10)
: 10
;
//
// Set the server port
//
macros.udpServerPort = 9753;
//
// Create a connection
//
macros.connection = new lynx('localhost', macros.udpServerPort);
//
// ### function udpServer(testName, onTest)
// #### @onMessage {Function} Function to be run on each message
//
// Start a `udp` server.
//
macros.udpServer = function udpServer(onMessage) {
var socket = dgram.createSocket('udp4', onMessage);
//
// Listen in some (not so) random port
//
socket.bind(macros.udpServerPort, 'localhost');
return socket;
};
//
// ### function udpFixturesServer(testName, onTest)
// #### @testName {String} The test that is calling this, so we can load
// the respective fixture
// #### @onTest {Function} Function that returns the result of a specific
// test
//
// Start a `udp` server that will expect an event that is
// mocked in `fixtures`
//
macros.udpFixturesServer = function udpServer(testName, t, onTest) {
//
// Set the path for the fixture we want to load
//
var fixturePath = path.join('fixtures', testName + '.json');
//
// Try to load the fixture.
// This will break your program if you delete by mistake
//
var fixture = require('./' + fixturePath);
//
// The number of requests we expect to get
//
var nrRequests = fixture.length
, iRequests = 0
;
//
// Create a UDP Socket
//
var socket = macros.udpServer(function (message, remote) {
//
// We got another one
//
iRequests++;
//
// `remote.address` for remote address
// `remote.port` for remote port
// `remote.size` for data lenght
// `message.toString('ascii', 0, remote.size)` for textual contents
//
var actual = macros.parseMessage(message, remote.size)
, iExpected = fixture.indexOf(actual)
;
//
// Let's check if its an aproximation
//
if(!~iExpected) {
//
// In aproximations we note them as `foo:~10|s`
// Lets see if we have any with the right key that is an aproximation
//
var aprox_fixtures_with_right_stat = fixture.filter(function (expctd) {
var stat = expctd.split(':')[0] // expected stat key
, aStat = actual.split(':')[0] // actual stat key
, isAprox = ~expctd.indexOf('~') // is expected an aproximation?
;
return stat === aStat && isAprox;
});
var aprox_actual = aprox_fixtures_with_right_stat[0];
iExpected = fixture.indexOf(aprox_actual);
}
//
// Found it
//
if (~iExpected) {
var expected = fixture[iExpected];
//
// Remove the found item from fixture to test
//
fixture.splice(iExpected, 1);
//
// Return our test results
//
onTest(true, {expected: expected, actual: actual, remaining: fixture});
}
//
// We didn't find that response in the response array
//
else {
onTest(false, { expected: null, actual: actual, remaining: fixture});
}
//
// If we are done
//
if(iRequests === nrRequests) {
//
// Close the server
//
socket.close();
//
// Tests are complete
//
t.end();
}
});
};
//
// ### function matchFixturesTest(testName, onTest)
// #### @resource {String} The resource we are testing (gauges, sets, counts)
// #### @f {Function} The actual udp client calls to be received by
// our mock server
//
// 1. Loads fixtures for this resource and checks how many client requests
// are going to exist
// 2. Runs a tests that:
// 2.1. Start a `udp` server that will expect a event that
// is mocked in `fixtures`
// 2.2. Runs client code that should match what has been mocked
//
macros.matchFixturesTest = function genericTest(resource, f) {
var currentFixture = require('./fixtures/' + resource);
//
// All of our counting tests
//
test(resource + ' test', function (t) {
//
// Setup our server
//
macros.udpFixturesServer(resource, t, function onEachRequest(err, info) {
//
// Aproximation
//
if(info.expected && ~info.expected.indexOf('~')) {
//
// foobar : ~ 10 |ms
// /(.*)? : ~ (.*)? \|ms/
//
// ? means non eager, like don't eat multiple `:`.
//
var matchE = /(.*)?:~(.*)?\|ms/.exec(info.expected)
//
// Actual doesnt have `~`
//
, matchA = /(.*)?:(.*)?\|ms/.exec(info.actual)
;
//
// If we were able to extract values from both
//
if(matchE && typeof matchE[2] === 'string' &&
matchA && typeof matchA[2] === 'string') {
//
// Get our aproximate number
//
var aproximation = parseInt(matchE[2], 10)
, valueA = parseInt(matchA[2], 10)
;
//
// Our upper bound
//
var ubound = aproximation + (aproximation * MAX_APROX_ERROR / 100);
t.ok(ubound >= valueA, 'value deviated from ' + aproximation +
' by more than +' + MAX_APROX_ERROR + '%. [' + valueA + ']');
//
// Our lower bound
//
var lbound = aproximation - (aproximation * MAX_APROX_ERROR / 100);
t.ok(lbound <= valueA, 'value deviated from ' + aproximation +
' by more than -' + MAX_APROX_ERROR + '%. [' + valueA + ']');
}
else {
//
// Show the expected vs actual string
//
t.equal(info.expected, info.actual,
'Equality check for ' + info.actual);
}
}
//
// On each response check if they are identical
//
else {
//
// Just treat it like any other thing.
// but hey, that fixture is wrong dude!
//
if(typeof info.expected === 'string') {
t.equal(info.expected, info.actual,
'Equality check for ' + info.actual);
}
//
// This failed, let's show the array of possibilities that could
// have matched
//
else {
t.equal(info.remaining, [info.actual],
"Didn't find value " + info.actual +
' in array of possible fixtures');
}
}
});
//
// Run our client code
//
if(resource === 'scopes') {
macros.connection.close();
macros.connection = new lynx('localhost', macros.udpServerPort, {
scope: 'scope' });
}
f(macros.connection);
});
};
//
// ### function parseMessage(testName, onTest)
// #### @message {String} Message to decode
//
// Start a `udp` server.
//
macros.parseMessage = function parseMessage(message, size) {
return message.toString('ascii', 0, size);
};
//
// Export simple `tap` tests
//
macros.test = test;
//
// Export `lynx`
//
macros.lynx = lynx;
//
// Export MAX_APROX_ERROR
//
macros.MAX_APROX_ERROR = MAX_APROX_ERROR;

110
watcher/node_modules/lynx/tests/sampling-test.js generated vendored

@ -1,110 +0,0 @@
var macros = require('./macros')
, statsd = require('statsd-parser')
, lynx = macros.lynx
, test = macros.test
, udpServer = macros.udpServer
, connection = macros.connection
, count = 0
, finished = false
;
//
// TOTAL is the number of iterations to do
// DESIRED is the minimum number of requests expected
// SAMPLE Number of samples to send, e.g. @0.1 (1 in 10)
//
var DESIRED = 90
, TOTAL = 1000
, SAMPLE = 0.1
;
//
// Try to do this a thousand times
// [1,2,3,...,1000]
//
var coll = [];
for(i=0; i<TOTAL; i++) {
coll.push(i);
}
//
// Remove console.log from errors, plenty of nothing to send here
//
connection.on_error = function () {};
//
// We are going to do one thousand `TOTAL` packets
// and see if we hit our minimums
//
// When you specify sampling `lynx` must track that it only send the amount
// of packages you are specifying (e.g. 1 in each 10 for @0.1 as in `SAMPLE`)
//
// To do this we use random numbers, making our process not perfect but
// accurate enough
//
// Because of the randomness that is used to select which packets are sent
// this can never be an exact test and might break while the code is
// perfectly fine
//
test('sampling', function (t) {
var server = udpServer(function (message, remote) {
count++;
//
// Add, check if its a valid statsd message and includes sample rate
// that is teh same as being tested
//
var match = statsd.matchStatsd(message.toString());
t.ok(match, message.toString());
t.equal(SAMPLE.toString(), match.sample_rate);
//
// When we finally hit our lower threshold
//
if(count > DESIRED) {
finished = true;
t.ok(true, 'Reached ' + DESIRED + ' on ' + (TOTAL - coll.length) +
' packets.');
server.close();
}
});
//
// Run all the iterations
//
var runAll = function(coll, callback) {
(function iterate() {
if (coll.length === 0) {
return callback();
}
coll.pop();
setTimeout(function send_packet() {
//
// Send a sample
//
connection.gauge('spl.foo', 500, SAMPLE);
process.nextTick(iterate);
}, Math.ceil(Math.random() * 10));
})();
};
runAll(coll, function() {
if (finished) {
t.ok(true, 'Reached ' + DESIRED + ' on ' + TOTAL + ' packets.');
t.end();
return;
}
//
// If we reached the end and this has not closed by having
// the desired amount of requests
//
t.ok(false, 'Didnt reach the desired amount of packets ' + DESIRED +
'/' + TOTAL + ' was -> ' + count);
server.close();
t.end();
});
});
//
// TODO: Sampling with irregular batches
//

12
watcher/node_modules/lynx/tests/scopes-test.js generated vendored

@ -1,12 +0,0 @@
var macros = require('./macros');
//
// Our `counting` tests
// Should match `tests/fixtures/counting.json`
//
macros.matchFixturesTest('scopes', function runTest(connection) {
connection.increment('bar');
connection.decrement('baz');
connection.decrement(['uno', 'two', 'trezentos']);
connection.count('boaz', 101);
});

10
watcher/node_modules/lynx/tests/sets-test.js generated vendored

@ -1,10 +0,0 @@
var macros = require('./macros');
//
// Our `sets` tests
// Should match `tests/fixtures/sets.json`
//
macros.matchFixturesTest('sets', function runTest(connection) {
connection.set('set1.foo', 765);
connection.set('set1.bar', 567);
});

130
watcher/node_modules/lynx/tests/stream-test.js generated vendored

@ -1,130 +0,0 @@
var Stream = require('stream')
, macros = require('./macros')
, lynx = macros.lynx
, port = macros.udpServerPort
, udpServer = macros.udpServer
, test = macros.test
, fixture_send = require('./fixtures/stream-send.json')
, fixture_recv = require('./fixtures/stream-recv.json')
;
var server = udpServer(function () {});
//
// ### function createDelayedStream()
//
// Creates a stream that reads stuff in fixtures and emits each line every
// 10ms or so
//
function createDelayedStream() {
//
// Make a new plain vanilla readable stream
//
var delayed_stream = new Stream();
delayed_stream.readable = true;
//
// Set an interval to emit every 10ms or so
//
var interval = setInterval(function () {
var current = fixture_send.shift();
if(current) {
//
// Emit the current stream
//
delayed_stream.emit('data', current);
}
else {
//
// We have nothing else to emit, so close this thing
//
delayed_stream.emit('end');
clearInterval(interval);
}
}, 10);
return delayed_stream;
}
//
// ### function createTestStream(t)
// #### @t {Object} A tap test assertion
//
// Tests if the things being passed to this stream match expectations
//
function createTestStream(t) {
//
// Make a new plain vanilla writable stream
//
var test_stream = new Stream();
test_stream.writable = true;
//
// Handle `Stream.prototype.write`
//
test_stream.write = function (buf) {
var expected = fixture_recv.shift();
t.equal(expected, buf.toString(), ' should be equal to ' + expected);
if(fixture_recv.length === 0) {
test_stream.end();
}
};
//
// Handle `Stream.prototype.end`
// And we should end our tests here
//
//
test_stream.end = function end(buf) {
if (arguments.length) {
test_stream.write(buf);
}
//
// Close our server socket
//
server.close();
//
// End our little experiment
//
test_stream.writable = false;
t.end();
};
//
// Handle `Stream.prototype.destroy`
//
test_stream.destroy = function () {
test_stream.writable = false;
};
return test_stream;
}
test('streams', function (t) {
//
// ### function on_error(err)
// #### @err {Error} Error object
//
// Assertion to run if we get any errors
//
function on_error(err) {
t.equal({}, err, "didn't expect any errors");
//
// End early
//
t.end();
}
//
// Our connection
//
var conn = new lynx('localhost', port, {on_error: on_error});
createDelayedStream()
.pipe(conn)
.pipe(createTestStream(t))
;
});

64
watcher/node_modules/lynx/tests/timings-test.js generated vendored

@ -1,64 +0,0 @@
var macros = require('./macros');
//
// Our `timing` fixture tests
// Should match `tests/fixtures/timing.json`
//
macros.matchFixturesTest('timings', function runTest(connection) {
//
// Basic Tests
//
connection.timing('foo.baz.time', 10);
connection.timing('foo.bar.time', 500);
//
// Constructing a timer object
//
var timer = connection.createTimer('foo.interval');
//
// Wait 200ms
//
setTimeout(function () {
//
// Stop the timer
//
timer.stop();
}, 200);
//
// A second timer
//
// Wait 100ms
//
var second_timer = connection.createTimer('bar.comes.first');
setTimeout(function () {
//
// Stop the timer
//
second_timer.stop();
}, 100);
//
// Attempts to stop the timer again but before `foo.interval`
// If someone breaks the `only stop once code` this will cause an error
// because it will emit before the `foo.interval` and it wont be equal
//
// Wait 150ms
//
setTimeout(function () {
//
// Atempt to stop already stopped timer
// Will console.log `Can't stop a timer twice`
//
// We are not testing for this error, cause its just an error message
// but this would be raised on scenarios where a more strict error handler
// is enforced by the user
//
connection.on('error', function (err) {
});
second_timer.stop();
}, 150);
});

BIN
watcher/node_modules/mersenne/.npmignore.gz generated vendored

Binary file not shown.

27
watcher/node_modules/mersenne/LICENSE generated vendored

@ -1,27 +0,0 @@
Copyright 2017 Makoto Matsumoto, Tajuji Nishimura, Yasuharu Okada and Jon
Watte.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

13
watcher/node_modules/mersenne/README.md generated vendored

@ -1,13 +0,0 @@
node-mersenne
=============
Great random number generation for nodejs!
Change log:
* 0.0.4 -- clarify license (BSD)
* 0.0.3 -- fix variable scoping to avoid globals
* 0.0.1 -- initial release

1
watcher/node_modules/mersenne/index.js generated vendored

@ -1 +0,0 @@
module.exports = require('./lib/mersenne.js');

285
watcher/node_modules/mersenne/lib/mersenne.js generated vendored

@ -1,285 +0,0 @@
// this program is a JavaScript version of Mersenne Twister, with concealment and encapsulation in class,
// an almost straight conversion from the original program, mt19937ar.c,
// translated by y. okada on July 17, 2006.
// and modified a little at july 20, 2006, but there are not any substantial differences.
// in this program, procedure descriptions and comments of original source code were not removed.
// lines commented with //c// were originally descriptions of c procedure. and a few following lines are appropriate JavaScript descriptions.
// lines commented with /* and */ are original comments.
// lines commented with // are additional comments in this JavaScript version.
// before using this version, create at least one instance of MersenneTwister19937 class, and initialize the each state, given below in c comments, of all the instances.
/*
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
function MersenneTwister19937()
{
/* constants should be scoped inside the class */
var N, M, MATRIX_A, UPPER_MASK, LOWER_MASK;
/* Period parameters */
//c//#define N 624
//c//#define M 397
//c//#define MATRIX_A 0x9908b0dfUL /* constant vector a */
//c//#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
//c//#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
N = 624;
M = 397;
MATRIX_A = 0x9908b0df; /* constant vector a */
UPPER_MASK = 0x80000000; /* most significant w-r bits */
LOWER_MASK = 0x7fffffff; /* least significant r bits */
//c//static unsigned long mt[N]; /* the array for the state vector */
//c//static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
var mt = new Array(N); /* the array for the state vector */
var mti = N+1; /* mti==N+1 means mt[N] is not initialized */
function unsigned32 (n1) // returns a 32-bits unsiged integer from an operand to which applied a bit operator.
{
return n1 < 0 ? (n1 ^ UPPER_MASK) + UPPER_MASK : n1;
}
function subtraction32 (n1, n2) // emulates lowerflow of a c 32-bits unsiged integer variable, instead of the operator -. these both arguments must be non-negative integers expressible using unsigned 32 bits.
{
return n1 < n2 ? unsigned32((0x100000000 - (n2 - n1)) & 0xffffffff) : n1 - n2;
}
function addition32 (n1, n2) // emulates overflow of a c 32-bits unsiged integer variable, instead of the operator +. these both arguments must be non-negative integers expressible using unsigned 32 bits.
{
return unsigned32((n1 + n2) & 0xffffffff)
}
function multiplication32 (n1, n2) // emulates overflow of a c 32-bits unsiged integer variable, instead of the operator *. these both arguments must be non-negative integers expressible using unsigned 32 bits.
{
var sum = 0;
for (var i = 0; i < 32; ++i){
if ((n1 >>> i) & 0x1){
sum = addition32(sum, unsigned32(n2 << i));
}
}
return sum;
}
/* initializes mt[N] with a seed */
//c//void init_genrand(unsigned long s)
this.init_genrand = function (s)
{
//c//mt[0]= s & 0xffffffff;
mt[0]= unsigned32(s & 0xffffffff);
for (mti=1; mti<N; mti++) {
mt[mti] =
//c//(1812433253 * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
addition32(multiplication32(1812433253, unsigned32(mt[mti-1] ^ (mt[mti-1] >>> 30))), mti);
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
//c//mt[mti] &= 0xffffffff;
mt[mti] = unsigned32(mt[mti] & 0xffffffff);
/* for >32 bit machines */
}
}
/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
//c//void init_by_array(unsigned long init_key[], int key_length)
this.init_by_array = function (init_key, key_length)
{
//c//int i, j, k;
var i, j, k;
//c//init_genrand(19650218);
this.init_genrand(19650218);
i=1; j=0;
k = (N>key_length ? N : key_length);
for (; k; k--) {
//c//mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525))
//c// + init_key[j] + j; /* non linear */
mt[i] = addition32(addition32(unsigned32(mt[i] ^ multiplication32(unsigned32(mt[i-1] ^ (mt[i-1] >>> 30)), 1664525)), init_key[j]), j);
mt[i] =
//c//mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
unsigned32(mt[i] & 0xffffffff);
i++; j++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
if (j>=key_length) j=0;
}
for (k=N-1; k; k--) {
//c//mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941))
//c//- i; /* non linear */
mt[i] = subtraction32(unsigned32((dbg=mt[i]) ^ multiplication32(unsigned32(mt[i-1] ^ (mt[i-1] >>> 30)), 1566083941)), i);
//c//mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
mt[i] = unsigned32(mt[i] & 0xffffffff);
i++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
}
mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
}
/* moved outside of genrand_int32() by jwatte 2010-11-17; generate less garbage */
var mag01 = [0x0, MATRIX_A];
/* generates a random number on [0,0xffffffff]-interval */
//c//unsigned long genrand_int32(void)
this.genrand_int32 = function ()
{
//c//unsigned long y;
//c//static unsigned long mag01[2]={0x0UL, MATRIX_A};
var y;
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (mti >= N) { /* generate N words at one time */
//c//int kk;
var kk;
if (mti == N+1) /* if init_genrand() has not been called, */
//c//init_genrand(5489); /* a default initial seed is used */
this.init_genrand(5489); /* a default initial seed is used */
for (kk=0;kk<N-M;kk++) {
//c//y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
//c//mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
y = unsigned32((mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK));
mt[kk] = unsigned32(mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1]);
}
for (;kk<N-1;kk++) {
//c//y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
//c//mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
y = unsigned32((mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK));
mt[kk] = unsigned32(mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1]);
}
//c//y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
//c//mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];
y = unsigned32((mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK));
mt[N-1] = unsigned32(mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1]);
mti = 0;
}
y = mt[mti++];
/* Tempering */
//c//y ^= (y >> 11);
//c//y ^= (y << 7) & 0x9d2c5680;
//c//y ^= (y << 15) & 0xefc60000;
//c//y ^= (y >> 18);
y = unsigned32(y ^ (y >>> 11));
y = unsigned32(y ^ ((y << 7) & 0x9d2c5680));
y = unsigned32(y ^ ((y << 15) & 0xefc60000));
y = unsigned32(y ^ (y >>> 18));
return y;
}
/* generates a random number on [0,0x7fffffff]-interval */
//c//long genrand_int31(void)
this.genrand_int31 = function ()
{
//c//return (genrand_int32()>>1);
return (this.genrand_int32()>>>1);
}
/* generates a random number on [0,1]-real-interval */
//c//double genrand_real1(void)
this.genrand_real1 = function ()
{
//c//return genrand_int32()*(1.0/4294967295.0);
return this.genrand_int32()*(1.0/4294967295.0);
/* divided by 2^32-1 */
}
/* generates a random number on [0,1)-real-interval */
//c//double genrand_real2(void)
this.genrand_real2 = function ()
{
//c//return genrand_int32()*(1.0/4294967296.0);
return this.genrand_int32()*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on (0,1)-real-interval */
//c//double genrand_real3(void)
this.genrand_real3 = function ()
{
//c//return ((genrand_int32()) + 0.5)*(1.0/4294967296.0);
return ((this.genrand_int32()) + 0.5)*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on [0,1) with 53-bit resolution*/
//c//double genrand_res53(void)
this.genrand_res53 = function ()
{
//c//unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6;
var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
return(a*67108864.0+b)*(1.0/9007199254740992.0);
}
/* These real versions are due to Isaku Wada, 2002/01/09 added */
}
// Exports: Public API
// Export the twister class
exports.MersenneTwister19937 = MersenneTwister19937;
// Export a simplified function to generate random numbers
var gen = new MersenneTwister19937;
gen.init_genrand((new Date).getTime() % 1000000000);
exports.rand = function(N) {
if (!N)
{
N = 32768;
}
return Math.floor(gen.genrand_real2() * N);
}
exports.seed = function(S) {
if (typeof(S) != 'number')
{
throw new Error("seed(S) must take numeric argument; is " + typeof(S));
}
gen.init_genrand(S);
}
exports.seed_array = function(A) {
if (typeof(A) != 'object')
{
throw new Error("seed_array(A) must take array of numbers; is " + typeof(A));
}
gen.init_by_array(A);
}

56
watcher/node_modules/mersenne/package.json generated vendored

@ -1,56 +0,0 @@
{
"_from": "mersenne@~0.0.3",
"_id": "mersenne@0.0.4",
"_inBundle": false,
"_integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=",
"_location": "/mersenne",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "mersenne@~0.0.3",
"name": "mersenne",
"escapedName": "mersenne",
"rawSpec": "~0.0.3",
"saveSpec": null,
"fetchSpec": "~0.0.3"
},
"_requiredBy": [
"/lynx"
],
"_resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz",
"_shasum": "401fdec7ec21cdb9e03cd3d3021398da21b27085",
"_spec": "mersenne@~0.0.3",
"_where": "/home/col/.factorio/mods/statorio_1.0.0/watcher/node_modules/lynx",
"author": {
"name": "Jon Watte",
"url": "http://www.enchantedage.com/"
},
"bundleDependencies": false,
"contributors": [
{
"name": "Yasuharu Okada"
},
{
"name": "Jon Watte",
"url": "http://www.enchantedage.com/"
}
],
"deprecated": false,
"description": "A node.js module for generating high-quality Mersenne Twister random numbers.",
"directories": {
"lib": "lib"
},
"homepage": "http://www.enchantedage.com/node-mersenne",
"keywords": [
"random",
"mersenne",
"twister",
"number",
"generator"
],
"license": "BSD-3-Clause",
"main": "lib/mersenne",
"name": "mersenne",
"version": "0.0.4"
}

65
watcher/node_modules/mersenne/test.js generated vendored

@ -1,65 +0,0 @@
mt = require('./lib/mersenne.js');
function test_gen(f, iter)
{
var a = new Array();
var n = Math.floor(iter / 1000);
if (n * 1000 != iter)
{
throw new Error("Iteration count " + iter + " must be divisible by 1000");
}
for (var i = 0; i < n; i += 1)
{
a[i] = 0;
}
for (var i = 0; i < iter; i += 1)
{
q = Math.floor(f(n));
if (isNaN(q))
{
throw new Error("NaN: " + q + " for iter " + i);
}
a[q % n] += 1;
}
var err = null;
for (var i = 0; i < n; i += 1)
{
if (a[i] < 900 || a[i] > 1100)
{
err = new Error("Index " + i + " out of " + n + " is outside [900,1100]: " + a[i]);
}
}
if (err)
{
throw new Error(err);
}
}
mt.seed(1);
var v = mt.rand();
if (isNaN(v))
{
throw new Error('NaN from mt.rand(): ' + v);
}
var g = new mt.MersenneTwister19937();
g.init_genrand(12345);
var f = function(range)
{
var ret = g.genrand_real1() * range;
return ret;
}
test_gen(f, 100000);
mt.seed(4711);
f = function(range)
{
return mt.rand(range);
}
test_gen(f, 100000);
mt.seed_array([15, 9932, 11147]);
test_gen(f, 100000);
process.exit(0);

13
watcher/node_modules/statsd-parser/LICENSE generated vendored

@ -1,13 +0,0 @@
copyright 2012 nuno job <nunojob.com> (oO)--',--
licensed under the apache license, version 2.0 (the "license");
you may not use this file except in compliance with the license.
you may obtain a copy of the license at
http://www.apache.org/licenses/LICENSE-2.0
unless required by applicable law or agreed to in writing, software
distributed under the license is distributed on an "as is" basis,
without warranties or conditions of any kind, either express or implied.
see the license for the specific language governing permissions and
limitations under the license.

106
watcher/node_modules/statsd-parser/README.md generated vendored

@ -1,106 +0,0 @@
# statsd-parser
a streaming parser for the statsd protocol
# installation
## node.js
1. install [npm]
2. `npm install statsd-parser`
3. `var statsd_parser = require('statsd-parser');`
# usage
## basics
``` js
var statsd_parser = require("statsd-parser")
, parser = statsd_parser.parser()
;
parser.onerror = function (e) {
// an error happened. e is the error
};
parser.onstat = function (txt, obj) {
// got some value
};
parser.onend = function () {
// parser stream is done, and ready to have more stuff written to it.
};
parser.write('foo.bar:1|c').close();
```
``` js
//
// stream usage
// takes the same options as the parser
//
var stream = require("statsd-parser").createStream(options);
stream.on("error", function (e) {
// unhandled errors will throw, since this is a proper node
// event emitter.
console.error("error!", e);
// clear the error
this._parser.error = null;
this._parser.resume();
})
stream.on("stat", function (txt, obj) {
// same object as above
});
//
// pipe is supported, and it's readable/writable
// same chunks coming in also go out
//
fs.createReadStream("file.statsd")
.pipe(stream)
.pipe(fs.createReadStream("file-fixed.statsd"))
```
you also have two helper functions to see if a string is a statsd stat `isStatsd` and one to check extract values from a statsd string (or null if it's not statsd)
``` js
> var statsd = require('statsd-parser');
> statsd.isStatsd('a:1|c|@0.1')
true
> statsd.matchStatsd('a:1|c|@0.1')
{ stat: 'a',
value: '1',
type: 'c',
sample_rate: '0.1' }
> statsd.matchStatsd('b:200|s')
{ stat: 'b',
value: '200',
type: 's' }
> statsd.matchStatsd('b:200|s@INVALID')
null
```
# contribute
everyone is welcome to contribute. patches, bug-fixes, new features
1. create an [issue][issues] so the community can comment on your idea
2. fork `statsd-parser`
3. create a new branch `git checkout -b my_branch`
4. create tests for the changes you made
5. make sure you pass both existing and newly inserted tests
6. commit your changes
7. push to your branch `git push origin my_branch`
8. create an pull request
# meta
* code: `git clone git://github.com/dscape/statsd-parser.git`
* home: <http://github.com/dscape/statsd-parser>
* bugs: <http://github.com/dscape/statsd-parser/issues>
`(oO)--',-` in [caos]
[npm]: http://npmjs.org
[issues]: http://github.com/dscape/statsd-parser/issues
[caos]: http://caos.di.uminho.pt/

351
watcher/node_modules/statsd-parser/index.js generated vendored

@ -1,351 +0,0 @@
var Stream = require('stream').Stream
, statsd = exports
;
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ const ~~
//
var EVENTS = ['stat', 'error', 'end', 'ready'];
var isStatsD =
//
// stat :value |type |@sample_rate <optional>
//
// Groups:
// * stat : 1
// * value : 2
// * type : 3
// * sample_rate : 5
//
/^(.+):([\-+]?[0-9]*\.?[0-9]+)\|(s|g|ms|c)(\|@([\-+]?[0-9]*\.?[0-9]*))?\s*$/;
//
// Remove error and end for event handling
//
var F_EVENTS = EVENTS.filter(function (ev) {
return ev !== 'error' && ev !== 'end';
});
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ stream ~~
//
statsd.createStream = function createStream(opt) {
return new StatsdStream(opt);
};
//
// Our streaming parser
//
function StatsdStream (opt) {
//
// If `this` ain't already a stream we need to return one with these opts
//
if (!(this instanceof StatsdStream)) {
return statsd.createStream(opt);
}
var me = this;
//
// Super call
//
Stream.apply(me);
//
// Instantiate our parser
//
this._parser = new StatsdParser(opt);
//
// This stream can write and read (duplex)
//
this.writable = true;
this.readable = true;
//
// Implements the on end function with respect to `this`
//
this._parser.onend = function () {
me.emit('end');
};
//
// Implements the on error function with respect to `this`
//
this._parser.onerror = function (er) {
me.emit('error', er);
me._parser.error = null;
};
//
// Define methods on the stream such as
// stream.onerror
// stream.onstat
//
// These are defined in F_EVENTS, call on the parser functionality and
// are used on the actual `on('stat', f)` handlers
//
F_EVENTS.forEach(function (ev) {
Object.defineProperty(me, 'on' + ev,
{ get : function () { return me._parser['on' + ev]; }
, set : function (h) {
if (!h) {
me.removeAllListeners(ev);
me._parser['on'+ev] = h;
return h;
}
me.on(ev, h);
}
, enumerable : true
, configurable : false
});
});
}
StatsdStream.prototype = Object.create(Stream.prototype,
{ constructor: { value: StatsdStream } });
StatsdStream.prototype.write = function (data) {
this._parser.write(data.toString());
this.emit('data', data);
return true;
};
StatsdStream.prototype.end = function (chunk) {
if (chunk && chunk.length) {
this._parser.write(chunk.toString());
}
this._parser.end();
return true;
};
StatsdStream.prototype.on = function (ev, handler) {
var me = this;
//
// If we dont have this method in the parser and its included in F_EVENTS
//
if (!me._parser['on'+ev] && ~F_EVENTS.indexOf(ev)) {
//
// Define it in the parser
//
me._parser['on'+ev] = function () {
var args = arguments.length === 1
? [arguments[0]]
: Array.apply(null, arguments)
;
args.splice(0, 0, ev);
me.emit.apply(me, args);
};
}
return Stream.prototype.on.call(me, ev, handler);
};
StatsdStream.prototype.destroy = function () {
this.emit('close');
};
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ parser ~~
//
statsd.parser = function (opt) {
return new StatsdParser(opt);
};
function StatsdParser (opt) {
//
// If `this` ain't already a parser we need to return one with these opts
//
if (!(this instanceof StatsdParser)) {
return statsd.parser(opt);
}
var parser = this;
//
// A buffer for our text until we find newline
//
parser.buffer = '';
//
// Is our parser closed?
//
parser.closed = false;
//
// Any possible parser `error`
//
parser.error = null;
//
// If we are looking for a new stat
//
parser.new_stat = true;
// mostly just for error reporting
parser.position = 0;
parser.column = 0;
parser.line = 1;
emit(parser, 'onready');
}
StatsdParser.prototype.end = function end() {
end(this);
};
StatsdParser.prototype.resume = function resume() {
this.error = null;
return this;
};
StatsdParser.prototype.close = function close() {
return this.write(null);
};
StatsdParser.prototype.write = function write (chunk) {
var parser = this;
//
// If there is an error
//
if (this.error) {
throw this.error;
}
//
// Cant write, already closed
//
if (parser.closed) {
return error(parser,
'Cannot write after close. Assign an onready handler.');
}
//
// Nothing to do here
//
if (chunk === null) {
return end(parser);
}
//
// Get ready for some parsing
//
var i = 0
, c = chunk[0]
;
while (c) {
c = chunk.charAt(i++);
if(parser.new_stat && c === ' ' || c === '\t' || c === '\r') {
//
// Ignore whitespace
//
continue;
}
parser.position++;
if (c === '\n') {
emitStat(parser, true);
parser.line ++;
parser.column = 0;
parser.new_stat = true;
} else {
parser.new_stat = false;
if (!(c === '\r' || c === '\t')) {
parser.buffer += c;
}
parser.column ++;
}
}
emitStat(parser);
return parser;
};
statsd.isStatsd = function test(string) {
return isStatsD.test(string);
};
statsd.matchStatsd = function match(string) {
var m = isStatsD.exec(string);
if(m) {
var stat =
{ stat : m[1]
, value : m[2]
, type : m[3]
};
if(typeof m[5] === 'string' && m[5] !== '') {
stat.sample_rate = m[5];
}
return stat;
}
else {
//
// Just like in the original exec
//
return null;
}
};
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ aux ~~
//
function emitStat(parser, fromNewline) {
if(!parser.buffer || parser.buffer === '') {
return;
}
var stat = statsd.matchStatsd(parser.buffer);
if(stat) {
emit(parser, 'onstat', parser.buffer, stat);
parser.buffer = '';
}
else {
if(fromNewline) {
var buf = parser.buffer;
parser.buffer = '';
error(parser, "Buffered Line was not valid stats d `" + buf + "`.");
}
}
}
function emit(parser, event, txtData, jsonData) {
if (parser[event]) {
parser[event](txtData, jsonData);
}
}
function error (parser, er) {
//
// See if we have anything buffered
//
emitStat(parser);
er += '\nLine: ' + parser.line +
'\nColumn: ' + parser.column +
'\nBuffer: ' + parser.buffer;
er = new Error(er);
parser.error = er;
emit(parser, 'onerror', er);
return parser;
}
function end(parser) {
//
// See if we have anything buffered
//
emitStat(parser);
parser.buffer = '';
parser.closed = true;
emit(parser, 'onend');
//
// Restart our parser
//
StatsdParser.call(parser, parser.opt);
return parser;
}

57
watcher/node_modules/statsd-parser/package.json generated vendored

@ -1,57 +0,0 @@
{
"_from": "statsd-parser@~0.0.4",
"_id": "statsd-parser@0.0.4",
"_inBundle": false,
"_integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=",
"_location": "/statsd-parser",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "statsd-parser@~0.0.4",
"name": "statsd-parser",
"escapedName": "statsd-parser",
"rawSpec": "~0.0.4",
"saveSpec": null,
"fetchSpec": "~0.0.4"
},
"_requiredBy": [
"/lynx"
],
"_resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz",
"_shasum": "cbd243953cc42effd548b5d22388ed689ec639bd",
"_spec": "statsd-parser@~0.0.4",
"_where": "/home/col/.factorio/mods/statorio_1.0.0/watcher/node_modules/lynx",
"author": {
"name": "Nuno Job",
"email": "nunojobpinto@gmail.com",
"url": "http://nunojob.com"
},
"bugs": {
"url": "https://github.com/dscape/statsd-parser/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Streaming parser for the statsd protocol",
"homepage": "https://github.com/dscape/statsd-parser#readme",
"keywords": [
"statsd",
"parser",
"streams",
"streaming",
"stats",
"metrics",
"lynx"
],
"license": "Apache 2",
"main": "index.js",
"name": "statsd-parser",
"repository": {
"type": "git",
"url": "git://github.com/dscape/statsd-parser.git"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"version": "0.0.4"
}

25
watcher/package-lock.json generated

@ -1,25 +0,0 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"lynx": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/lynx/-/lynx-0.2.0.tgz",
"integrity": "sha1-eeZnRTDaQYPoeVO9aGFx4HDaULk=",
"requires": {
"mersenne": "~0.0.3",
"statsd-parser": "~0.0.4"
}
},
"mersenne": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz",
"integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU="
},
"statsd-parser": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz",
"integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0="
}
}
}
Loading…
Cancel
Save