diff --git a/README.md b/README.md index 3d0a162..1cd9365 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,282 @@ -# Statorio +
+

Statorio

-A statistics export plugin for Factorio. Choose which metrics and a frequency for exporting to JSON file on disk. +

A Factorio mod to export realtime JSON statistics

-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
+ Receive sweet JSON on disk
+ Numbers go brrr +
-* 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 +
+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 Mods +* Choose Install tab +* Wait for list to populate +* Search for `Statorio` +* Click the search result +* Click Install on the right pane +* Click Confirm + +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 ` will test if a metric works, if so display the current value + +`/stat add ` will add a metric (see examples below) + +`/stat list` will show all metrics being exported right now + +`/stat del ` 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 :) \ No newline at end of file diff --git a/control.lua b/control.lua index 7702baa..1fb4e6a 100644 --- a/control.lua +++ b/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 \ No newline at end of file diff --git a/control.lua.v1 b/control.lua.v1 deleted file mode 100644 index 78208dd..0000000 --- a/control.lua.v1 +++ /dev/null @@ -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) diff --git a/control.lua.v2 b/control.lua.v2 deleted file mode 100644 index c82d44c..0000000 --- a/control.lua.v2 +++ /dev/null @@ -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) diff --git a/info.json b/info.json index 7b9af4d..b7bb4fd 100644 --- a/info.json +++ b/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" +} \ No newline at end of file diff --git a/locale/en/base.cfg b/locale/en/base.cfg new file mode 100644 index 0000000..48e60a4 --- /dev/null +++ b/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 diff --git a/locale/en/settings.cfg b/locale/en/settings.cfg new file mode 100644 index 0000000..5471b2a --- /dev/null +++ b/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) \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 70162b1..0000000 --- a/public/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - -

ting

- - - - - - diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 2918c7a..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "manifest_version": 2, - "version": "1.0", - "name": "Statorio" -} diff --git a/public/manifest.json.save b/public/manifest.json.save deleted file mode 100644 index 6ff2e7c..0000000 --- a/public/manifest.json.save +++ /dev/null @@ -1,8 +0,0 @@ -{ - "manifest_version": 2, - "version": "1.0", - "name": "Statorio", - "permissions": [ - "" - ] -} diff --git a/public/manifest.json.save.1 b/public/manifest.json.save.1 deleted file mode 100644 index 6ff2e7c..0000000 --- a/public/manifest.json.save.1 +++ /dev/null @@ -1,8 +0,0 @@ -{ - "manifest_version": 2, - "version": "1.0", - "name": "Statorio", - "permissions": [ - "" - ] -} diff --git a/script/commands.lua b/script/commands.lua new file mode 100644 index 0000000..22a0a71 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/script/fetcher.lua b/script/fetcher.lua new file mode 100644 index 0000000..b035fbf --- /dev/null +++ b/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 \ No newline at end of file diff --git a/script/init.lua b/script/init.lua new file mode 100644 index 0000000..3ff9ff7 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/script/publisher.lua b/script/publisher.lua new file mode 100644 index 0000000..40f8b03 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/script/settings.lua b/script/settings.lua new file mode 100644 index 0000000..5d501d8 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/script/utilities.lua b/script/utilities.lua new file mode 100644 index 0000000..18f9887 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/settings.lua b/settings.lua new file mode 100644 index 0000000..d3804cc --- /dev/null +++ b/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" + } +}) \ No newline at end of file diff --git a/thumbnail.png b/thumbnail.png new file mode 100644 index 0000000..4794f4d Binary files /dev/null and b/thumbnail.png differ diff --git a/watcher/main.js b/watcher/main.js deleted file mode 100644 index 6544367..0000000 --- a/watcher/main.js +++ /dev/null @@ -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; -} diff --git a/watcher/node_modules/lynx/.npmignore.gz b/watcher/node_modules/lynx/.npmignore.gz deleted file mode 100644 index f123086..0000000 Binary files a/watcher/node_modules/lynx/.npmignore.gz and /dev/null differ diff --git a/watcher/node_modules/lynx/.travis.yml.gz b/watcher/node_modules/lynx/.travis.yml.gz deleted file mode 100644 index 18f34fb..0000000 Binary files a/watcher/node_modules/lynx/.travis.yml.gz and /dev/null differ diff --git a/watcher/node_modules/lynx/CONTRIBUTING.md b/watcher/node_modules/lynx/CONTRIBUTING.md deleted file mode 100644 index e72c63d..0000000 --- a/watcher/node_modules/lynx/CONTRIBUTING.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/watcher/node_modules/lynx/LICENSE b/watcher/node_modules/lynx/LICENSE deleted file mode 100644 index c38a457..0000000 --- a/watcher/node_modules/lynx/LICENSE +++ /dev/null @@ -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. \ No newline at end of file diff --git a/watcher/node_modules/lynx/README.md b/watcher/node_modules/lynx/README.md deleted file mode 100644 index ae61c2a..0000000 --- a/watcher/node_modules/lynx/README.md +++ /dev/null @@ -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: -* bugs: - -`(oo)--',-` in [caos] - -[caos]: http://caos.di.uminho.pt -[sivy]: https://github.com/sivy/node-statsd -[statsd]: https://github.com/etsy/statsd diff --git a/watcher/node_modules/lynx/lib/lynx.js b/watcher/node_modules/lynx/lib/lynx.js deleted file mode 100644 index c9fbbb9..0000000 --- a/watcher/node_modules/lynx/lib/lynx.js +++ /dev/null @@ -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; diff --git a/watcher/node_modules/lynx/package.json b/watcher/node_modules/lynx/package.json deleted file mode 100644 index 0f02e7a..0000000 --- a/watcher/node_modules/lynx/package.json +++ /dev/null @@ -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" -} diff --git a/watcher/node_modules/lynx/tests/counts-test.js b/watcher/node_modules/lynx/tests/counts-test.js deleted file mode 100644 index 0d33670..0000000 --- a/watcher/node_modules/lynx/tests/counts-test.js +++ /dev/null @@ -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); -}); diff --git a/watcher/node_modules/lynx/tests/errors-test.js b/watcher/node_modules/lynx/tests/errors-test.js deleted file mode 100644 index 7491748..0000000 --- a/watcher/node_modules/lynx/tests/errors-test.js +++ /dev/null @@ -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); -}); \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/counts.json b/watcher/node_modules/lynx/tests/fixtures/counts.json deleted file mode 100644 index 3fd9e57..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/counts.json +++ /dev/null @@ -1,5 +0,0 @@ -[ "foo.bar:1|c" -, "foo.baz:-1|c" -, "uno:-1|c\ntwo:-1|c\ntrezentos:-1|c" -, "boaz:101|c" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/errors.json b/watcher/node_modules/lynx/tests/fixtures/errors.json deleted file mode 100644 index 073e3de..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/errors.json +++ /dev/null @@ -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" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/gauges.json b/watcher/node_modules/lynx/tests/fixtures/gauges.json deleted file mode 100644 index b9fc3ae..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/gauges.json +++ /dev/null @@ -1,3 +0,0 @@ -[ "foo.gauge.1:500|g" -, "foo.gauge.2:15|g" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/scopes.json b/watcher/node_modules/lynx/tests/fixtures/scopes.json deleted file mode 100644 index f19122e..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/scopes.json +++ /dev/null @@ -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" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/sets.json b/watcher/node_modules/lynx/tests/fixtures/sets.json deleted file mode 100644 index 681b622..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/sets.json +++ /dev/null @@ -1,3 +0,0 @@ -[ "set1.foo:765|s" -, "set1.bar:567|s" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/stream-recv.json b/watcher/node_modules/lynx/tests/fixtures/stream-recv.json deleted file mode 100644 index a01c3a1..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/stream-recv.json +++ /dev/null @@ -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" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/stream-send.json b/watcher/node_modules/lynx/tests/fixtures/stream-send.json deleted file mode 100644 index 21b76f3..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/stream-send.json +++ /dev/null @@ -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" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/fixtures/timings.json b/watcher/node_modules/lynx/tests/fixtures/timings.json deleted file mode 100644 index 1c395b9..0000000 --- a/watcher/node_modules/lynx/tests/fixtures/timings.json +++ /dev/null @@ -1,5 +0,0 @@ -[ "foo.baz.time:10|ms" -, "foo.bar.time:500|ms" -, "foo.interval:~200|ms" -, "bar.comes.first:~100|ms" -] \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/gauges-test.js b/watcher/node_modules/lynx/tests/gauges-test.js deleted file mode 100644 index 90d10ba..0000000 --- a/watcher/node_modules/lynx/tests/gauges-test.js +++ /dev/null @@ -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); -}); \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/global_leaks.js b/watcher/node_modules/lynx/tests/global_leaks.js deleted file mode 100644 index 997d4b7..0000000 --- a/watcher/node_modules/lynx/tests/global_leaks.js +++ /dev/null @@ -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(); \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/macros.js b/watcher/node_modules/lynx/tests/macros.js deleted file mode 100644 index 8e74989..0000000 --- a/watcher/node_modules/lynx/tests/macros.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/sampling-test.js b/watcher/node_modules/lynx/tests/sampling-test.js deleted file mode 100644 index 51719c5..0000000 --- a/watcher/node_modules/lynx/tests/sampling-test.js +++ /dev/null @@ -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 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 -// \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/scopes-test.js b/watcher/node_modules/lynx/tests/scopes-test.js deleted file mode 100644 index f5651f2..0000000 --- a/watcher/node_modules/lynx/tests/scopes-test.js +++ /dev/null @@ -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); -}); diff --git a/watcher/node_modules/lynx/tests/sets-test.js b/watcher/node_modules/lynx/tests/sets-test.js deleted file mode 100644 index fee0d3a..0000000 --- a/watcher/node_modules/lynx/tests/sets-test.js +++ /dev/null @@ -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); -}); \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/stream-test.js b/watcher/node_modules/lynx/tests/stream-test.js deleted file mode 100644 index a062b7d..0000000 --- a/watcher/node_modules/lynx/tests/stream-test.js +++ /dev/null @@ -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)) - ; -}); \ No newline at end of file diff --git a/watcher/node_modules/lynx/tests/timings-test.js b/watcher/node_modules/lynx/tests/timings-test.js deleted file mode 100644 index f3b39b8..0000000 --- a/watcher/node_modules/lynx/tests/timings-test.js +++ /dev/null @@ -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); -}); diff --git a/watcher/node_modules/mersenne/.npmignore.gz b/watcher/node_modules/mersenne/.npmignore.gz deleted file mode 100644 index 539b281..0000000 Binary files a/watcher/node_modules/mersenne/.npmignore.gz and /dev/null differ diff --git a/watcher/node_modules/mersenne/LICENSE b/watcher/node_modules/mersenne/LICENSE deleted file mode 100644 index 49d298b..0000000 --- a/watcher/node_modules/mersenne/LICENSE +++ /dev/null @@ -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. diff --git a/watcher/node_modules/mersenne/README.md b/watcher/node_modules/mersenne/README.md deleted file mode 100644 index adfb3c3..0000000 --- a/watcher/node_modules/mersenne/README.md +++ /dev/null @@ -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 - diff --git a/watcher/node_modules/mersenne/index.js b/watcher/node_modules/mersenne/index.js deleted file mode 100644 index 5d793a6..0000000 --- a/watcher/node_modules/mersenne/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib/mersenne.js'); diff --git a/watcher/node_modules/mersenne/lib/mersenne.js b/watcher/node_modules/mersenne/lib/mersenne.js deleted file mode 100644 index 990ae96..0000000 --- a/watcher/node_modules/mersenne/lib/mersenne.js +++ /dev/null @@ -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> 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> 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> 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); -} - - diff --git a/watcher/node_modules/mersenne/package.json b/watcher/node_modules/mersenne/package.json deleted file mode 100644 index 9d666e6..0000000 --- a/watcher/node_modules/mersenne/package.json +++ /dev/null @@ -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" -} diff --git a/watcher/node_modules/mersenne/test.js b/watcher/node_modules/mersenne/test.js deleted file mode 100644 index a885dd8..0000000 --- a/watcher/node_modules/mersenne/test.js +++ /dev/null @@ -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); diff --git a/watcher/node_modules/statsd-parser/LICENSE b/watcher/node_modules/statsd-parser/LICENSE deleted file mode 100644 index d1f1e7f..0000000 --- a/watcher/node_modules/statsd-parser/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -copyright 2012 nuno job (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. \ No newline at end of file diff --git a/watcher/node_modules/statsd-parser/README.md b/watcher/node_modules/statsd-parser/README.md deleted file mode 100644 index 850292f..0000000 --- a/watcher/node_modules/statsd-parser/README.md +++ /dev/null @@ -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: -* bugs: - -`(oO)--',-` in [caos] - -[npm]: http://npmjs.org -[issues]: http://github.com/dscape/statsd-parser/issues -[caos]: http://caos.di.uminho.pt/ \ No newline at end of file diff --git a/watcher/node_modules/statsd-parser/index.js b/watcher/node_modules/statsd-parser/index.js deleted file mode 100644 index 3da5b47..0000000 --- a/watcher/node_modules/statsd-parser/index.js +++ /dev/null @@ -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 - // - // 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; -} \ No newline at end of file diff --git a/watcher/node_modules/statsd-parser/package.json b/watcher/node_modules/statsd-parser/package.json deleted file mode 100644 index 89d8929..0000000 --- a/watcher/node_modules/statsd-parser/package.json +++ /dev/null @@ -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" -} diff --git a/watcher/package-lock.json b/watcher/package-lock.json deleted file mode 100644 index 2466a40..0000000 --- a/watcher/package-lock.json +++ /dev/null @@ -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=" - } - } -}