summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2017-07-14 19:03:23 +0200
committerschneefux <schneefux+commit@schneefux.xyz>2017-07-14 19:03:23 +0200
commita62c2004d7767d7f977050345eae2d850518a19b (patch)
tree4fe532d3d3699e4d7072f4a8aa019d5d1c1e3306
parenta5b43e106ae8a3441690ed7d32c711f0200d4be7 (diff)
downloadshrinker-a62c2004d7767d7f977050345eae2d850518a19b.tar.gz
shrinker-a62c2004d7767d7f977050345eae2d850518a19b.zip
2.14.0 rewrite: split from processor
-rw-r--r--package-lock.json876
-rw-r--r--package.json13
-rw-r--r--worker.js537
3 files changed, 951 insertions, 475 deletions
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..0189428
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,876 @@
+{
+ "name": "sampler",
+ "version": "2.14.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@types/geojson": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.2.tgz",
+ "integrity": "sha1-sC0QqwKOKSisWSoFGqpJgaGUHQM="
+ },
+ "@types/node": {
+ "version": "6.0.82",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.82.tgz",
+ "integrity": "sha512-uFjwSPo9dyoK0bhxMIuCsqIyQW9EHN2RcadPlA2s67TxzGDL9MJKjNAsi+TCn8ProGHcntLhgvIpbZmbtk2uCQ=="
+ },
+ "ajv": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "requires": {
+ "co": "4.6.0",
+ "json-stable-stringify": "1.0.1"
+ }
+ },
+ "amqplib": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz",
+ "integrity": "sha1-fMz+ur5WwumE6noiQ/fO/m+/xs8=",
+ "requires": {
+ "bitsyntax": "0.0.4",
+ "bluebird": "3.5.0",
+ "buffer-more-ints": "0.0.2",
+ "readable-stream": "1.1.14"
+ }
+ },
+ "ansicolors": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz",
+ "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8="
+ },
+ "asn1": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+ "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+ },
+ "assert-plus": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
+ },
+ "async": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
+ "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k="
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "aws-sign2": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
+ },
+ "aws4": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+ "optional": true,
+ "requires": {
+ "tweetnacl": "0.14.5"
+ }
+ },
+ "bitsyntax": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz",
+ "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=",
+ "requires": {
+ "buffer-more-ints": "0.0.2"
+ }
+ },
+ "bluebird": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
+ "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw="
+ },
+ "boom": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "buffer-more-ints": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz",
+ "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw="
+ },
+ "cardinal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz",
+ "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=",
+ "requires": {
+ "ansicolors": "0.2.1",
+ "redeyed": "1.0.1"
+ }
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "cls-bluebird": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.0.1.tgz",
+ "integrity": "sha1-wlmkgK4CwOUGE0MHuxPbMERu4uc=",
+ "requires": {
+ "is-bluebird": "1.0.2",
+ "shimmer": "1.1.0"
+ }
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+ },
+ "colors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
+ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs="
+ },
+ "combined-stream": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+ "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+ "requires": {
+ "delayed-stream": "1.0.0"
+ }
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cross-env": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-3.2.4.tgz",
+ "integrity": "sha1-ngWF8neGTtQhznVvgamA/w1piro=",
+ "requires": {
+ "cross-spawn": "5.1.0",
+ "is-windows": "1.0.1"
+ }
+ },
+ "cross-spawn": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+ "requires": {
+ "lru-cache": "4.1.1",
+ "shebang-command": "1.2.0",
+ "which": "1.2.14"
+ }
+ },
+ "cryptiles": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "requires": {
+ "boom": "2.10.1"
+ }
+ },
+ "cycle": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
+ "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI="
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "debug": {
+ "version": "2.6.8",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "denque": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-1.1.1.tgz",
+ "integrity": "sha1-ECKcK4juwb0V/4LF/eNW5762254="
+ },
+ "depd": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
+ "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
+ },
+ "dottie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz",
+ "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA="
+ },
+ "ecc-jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+ "optional": true,
+ "requires": {
+ "jsbn": "0.1.1"
+ }
+ },
+ "env-cmd": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-5.1.0.tgz",
+ "integrity": "sha1-AjbbOTw/AzAFIE/NCpLuQHI6nJ4=",
+ "requires": {
+ "cross-spawn": "5.1.0"
+ }
+ },
+ "esprima": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.0.0.tgz",
+ "integrity": "sha1-U88kes2ncxPlUcOqLnM0LT+099k="
+ },
+ "extend": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+ },
+ "extsprintf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+ "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+ },
+ "eyes": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
+ "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+ "requires": {
+ "asynckit": "0.4.0",
+ "combined-stream": "1.0.5",
+ "mime-types": "2.1.15"
+ }
+ },
+ "generate-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+ "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ="
+ },
+ "generic-pool": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.1.7.tgz",
+ "integrity": "sha1-2sIrLHp6BOQXMvfY0tJaMDyI9mI="
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "har-schema": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+ "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4="
+ },
+ "har-validator": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+ "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+ "requires": {
+ "ajv": "4.11.8",
+ "har-schema": "1.0.5"
+ }
+ },
+ "hawk": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "requires": {
+ "boom": "2.10.1",
+ "cryptiles": "2.0.5",
+ "hoek": "2.16.3",
+ "sntp": "1.0.9"
+ }
+ },
+ "hoek": {
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ },
+ "http-signature": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+ "requires": {
+ "assert-plus": "0.2.0",
+ "jsprim": "1.4.0",
+ "sshpk": "1.13.1"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
+ "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA=="
+ },
+ "inflection": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz",
+ "integrity": "sha1-W//LEZetPoEFD44X4hZoCH7p6y8="
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "is-bluebird": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz",
+ "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "is-windows": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz",
+ "integrity": "sha1-MQ23D3QtJZoWo2kgK1GvhCMzENk="
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "optional": true
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "requires": {
+ "jsonify": "0.0.0"
+ }
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
+ },
+ "jsprim": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
+ "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.0.2",
+ "json-schema": "0.2.3",
+ "verror": "1.3.6"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "lodash": {
+ "version": "4.17.4",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+ "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+ },
+ "long": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
+ "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s="
+ },
+ "lru-cache": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+ "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+ "requires": {
+ "pseudomap": "1.0.2",
+ "yallist": "2.1.2"
+ }
+ },
+ "mime-db": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
+ "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
+ },
+ "mime-types": {
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
+ "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+ "requires": {
+ "mime-db": "1.27.0"
+ }
+ },
+ "moment": {
+ "version": "2.18.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
+ "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
+ },
+ "moment-timezone": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz",
+ "integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=",
+ "requires": {
+ "moment": "2.18.1"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "mysql2": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.3.6.tgz",
+ "integrity": "sha1-1cFJF94LFYAabBiDWZvPb7J8Gh4=",
+ "requires": {
+ "cardinal": "1.0.0",
+ "denque": "1.1.1",
+ "generate-function": "2.0.0",
+ "iconv-lite": "0.4.18",
+ "long": "3.2.0",
+ "lru-cache": "4.1.1",
+ "named-placeholders": "1.1.1",
+ "object-assign": "4.1.1",
+ "readable-stream": "2.3.2",
+ "safe-buffer": "5.1.1",
+ "seq-queue": "0.0.5",
+ "sqlstring": "2.2.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "readable-stream": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.2.tgz",
+ "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "1.0.7",
+ "safe-buffer": "5.1.1",
+ "string_decoder": "1.0.3",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ }
+ }
+ },
+ "named-placeholders": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.1.tgz",
+ "integrity": "sha1-O3oNJiA910s6nfTJz7gnsvuQfmQ=",
+ "requires": {
+ "lru-cache": "2.5.0"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz",
+ "integrity": "sha1-2COIrpyWC+y+oMc7uet5tsbOmus="
+ }
+ }
+ },
+ "node-loggly-bulk": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/node-loggly-bulk/-/node-loggly-bulk-2.0.0.tgz",
+ "integrity": "sha1-5M+UOY15jroSQwUsLybX3ZuSEE0=",
+ "requires": {
+ "json-stringify-safe": "5.0.1",
+ "request": "2.81.0",
+ "timespan": "2.3.0"
+ }
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "performance-now": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+ "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU="
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ },
+ "qs": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+ "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
+ },
+ "readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "string_decoder": "0.10.31"
+ }
+ },
+ "redeyed": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz",
+ "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=",
+ "requires": {
+ "esprima": "3.0.0"
+ }
+ },
+ "request": {
+ "version": "2.81.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+ "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+ "requires": {
+ "aws-sign2": "0.6.0",
+ "aws4": "1.6.0",
+ "caseless": "0.12.0",
+ "combined-stream": "1.0.5",
+ "extend": "3.0.1",
+ "forever-agent": "0.6.1",
+ "form-data": "2.1.4",
+ "har-validator": "4.2.1",
+ "hawk": "3.1.3",
+ "http-signature": "1.1.1",
+ "is-typedarray": "1.0.0",
+ "isstream": "0.1.2",
+ "json-stringify-safe": "5.0.1",
+ "mime-types": "2.1.15",
+ "oauth-sign": "0.8.2",
+ "performance-now": "0.2.0",
+ "qs": "6.4.0",
+ "safe-buffer": "5.1.1",
+ "stringstream": "0.0.5",
+ "tough-cookie": "2.3.2",
+ "tunnel-agent": "0.6.0",
+ "uuid": "3.1.0"
+ }
+ },
+ "retry-as-promised": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.2.0.tgz",
+ "integrity": "sha1-sEY9f9PPWy/tZFAKtui4pJxbjmw=",
+ "requires": {
+ "bluebird": "3.5.0",
+ "cross-env": "3.2.4",
+ "debug": "2.6.8"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+ },
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+ },
+ "seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4="
+ },
+ "sequelize": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.3.2.tgz",
+ "integrity": "sha1-FHrRnW6lk++YME6MsLimkrS50aM=",
+ "requires": {
+ "bluebird": "3.5.0",
+ "cls-bluebird": "2.0.1",
+ "debug": "2.6.8",
+ "depd": "1.1.0",
+ "dottie": "2.0.0",
+ "env-cmd": "5.1.0",
+ "generic-pool": "3.1.7",
+ "inflection": "1.10.0",
+ "lodash": "4.17.4",
+ "moment": "2.18.1",
+ "moment-timezone": "0.5.13",
+ "retry-as-promised": "2.2.0",
+ "semver": "5.3.0",
+ "terraformer-wkt-parser": "1.1.2",
+ "toposort-class": "1.0.1",
+ "uuid": "3.1.0",
+ "validator": "6.3.0",
+ "wkx": "0.4.1"
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "requires": {
+ "shebang-regex": "1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+ },
+ "shimmer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz",
+ "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU="
+ },
+ "sntp": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "sqlstring": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.2.0.tgz",
+ "integrity": "sha1-wxNcTqirzX5+50GklmqJHYak8ZE="
+ },
+ "sshpk": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+ "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+ "requires": {
+ "asn1": "0.2.3",
+ "assert-plus": "1.0.0",
+ "bcrypt-pbkdf": "1.0.1",
+ "dashdash": "1.14.1",
+ "ecc-jsbn": "0.1.1",
+ "getpass": "0.1.7",
+ "jsbn": "0.1.1",
+ "tweetnacl": "0.14.5"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "stack-trace": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
+ "stringstream": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+ "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
+ },
+ "terraformer": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz",
+ "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=",
+ "requires": {
+ "@types/geojson": "1.0.2"
+ }
+ },
+ "terraformer-wkt-parser": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz",
+ "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=",
+ "requires": {
+ "terraformer": "1.0.8"
+ }
+ },
+ "timespan": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz",
+ "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk="
+ },
+ "toposort-class": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
+ "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg="
+ },
+ "tough-cookie": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+ "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+ "requires": {
+ "punycode": "1.4.1"
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "optional": true
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "uuid": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+ "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
+ },
+ "validator": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
+ "integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g="
+ },
+ "verror": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+ "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+ "requires": {
+ "extsprintf": "1.0.2"
+ }
+ },
+ "which": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
+ "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+ "requires": {
+ "isexe": "2.0.0"
+ }
+ },
+ "winston": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz",
+ "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=",
+ "requires": {
+ "async": "1.0.0",
+ "colors": "1.0.3",
+ "cycle": "1.0.3",
+ "eyes": "0.1.8",
+ "isstream": "0.1.2",
+ "stack-trace": "0.0.10"
+ }
+ },
+ "winston-loggly-bulk": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/winston-loggly-bulk/-/winston-loggly-bulk-2.0.0.tgz",
+ "integrity": "sha1-olOCeBihS7b+CwbMHCLY0+19EoA=",
+ "requires": {
+ "node-loggly-bulk": "2.0.0",
+ "winston": "2.3.1"
+ }
+ },
+ "wkx": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.1.tgz",
+ "integrity": "sha1-L8FxtenLVcYlb+9L3h8hvkE77+4=",
+ "requires": {
+ "@types/node": "6.0.82"
+ }
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+ }
+ }
+}
diff --git a/package.json b/package.json
index ba632fc..2a372c7 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,14 @@
{
- "name": "processor",
- "version": "2.0.0",
+ "name": "sampler",
+ "version": "2.14.0",
"description": "",
- "main": "model.js",
+ "main": "worker.js",
"dependencies": {
"amqplib": "^0.5.1",
- "mysql": "^2.13.0",
- "sequelize": "^3.30.4",
- "sleep-promise": "^2.0.0",
+ "mysql2": "^1.3.6",
+ "sequelize": "^4.3.2",
"winston": "^2.3.1",
- "winston-loggly-bulk": "^1.4.2"
+ "winston-loggly-bulk": "^2.0.0"
},
"devDependencies": {},
"scripts": {
diff --git a/worker.js b/worker.js
index 5dd1411..fb1253e 100644
--- a/worker.js
+++ b/worker.js
@@ -2,11 +2,7 @@
/* jshint esnext:true */
"use strict";
-/* processor inserts API data into the database.
- * It listens to the queue `process` and expects a JSON
- * which is a match structure or a player structure.
- * It will forward notifications to web.
- */
+/* shrinker aggregates Telemetry data and inserts it into the database. */
const amqp = require("amqplib"),
Promise = require("bluebird"),
@@ -14,10 +10,7 @@ const amqp = require("amqplib"),
loggly = require("winston-loggly-bulk"),
Seq = require("sequelize"),
api_name_mappings = require("../orm/mappings").map,
- isAbility = require("../orm/mappings").isAbility,
- isItem = require("../orm/mappings").isItem,
- isHero = require("../orm/mappings").isHero,
- sleep = require("sleep-promise");
+ isAbility = require("../orm/mappings").isAbility;
const RABBITMQ_URI = process.env.RABBITMQ_URI,
DATABASE_URI = process.env.DATABASE_URI,
@@ -47,72 +40,36 @@ if (LOGGLY_TOKEN)
logger.add(winston.transports.Loggly, {
inputToken: LOGGLY_TOKEN,
subdomain: "kvahuja",
- tags: ["backend", "processor", QUEUE],
+ tags: ["backend", "shrinker", QUEUE],
json: true
});
-// helpers
-const camelCaseRegExp = new RegExp(/([a-z])([A-Z]+)/g);
-function camelToSnake(text) {
- return text.replace(camelCaseRegExp, (m, $1, $2) =>
- $1 + "_" + $2.toLowerCase());
-}
-
-// MadGlory API uses snakeCase, our db uses camel_case
-function snakeCaseKeys(obj) {
- Object.keys(obj).forEach((key) => {
- const new_key = camelToSnake(key);
- if (new_key == key) return;
- obj[new_key] = obj[key];
- delete obj[key];
- });
- return obj;
-}
-
// split an array into arrays of max chunksize
function* chunks(arr) {
for (let c=0, len=arr.length; c<len; c+=CHUNKSIZE)
yield arr.slice(c, c+CHUNKSIZE);
}
-// helper to convert API response into flat JSON
-// db structure is (almost) 1:1 the API structure
-// so we can insert the flat API response as-is
-function flatten(obj) {
- const attrs = obj.attributes || {},
- stats = attrs.stats || {};
- let o = Object.assign({}, obj, attrs, stats);
- o.api_id = o.id; // rename
- delete o.id;
- delete o.type;
- delete o.attributes;
- delete o.stats;
- delete o.relationships;
- return snakeCaseKeys(o);
-}
-
-// main code
-(async () => {
- let seq, model, rabbit, ch;
+amqp.connect(RABBITMQ_URI).then(async (rabbit) => {
+ global.process.on("SIGINT", () => {
+ rabbit.close();
+ global.process.exit();
+ });
// connect to rabbit & db
- while (true) {
- try {
- seq = new Seq(DATABASE_URI, {
- logging: false,
- max: MAXCONNS
- });
- rabbit = await amqp.connect(RABBITMQ_URI, { heartbeat: 120 });
- ch = await rabbit.createChannel();
- await ch.assertQueue(QUEUE, {durable: true});
- break;
- } catch (err) {
- logger.error("Error connecting", err);
- await sleep(5000);
- }
- }
+ const seq = new Seq(DATABASE_URI, {
+ logging: false,
+ max: MAXCONNS
+ });
- model = require("../orm/model")(seq, Seq);
+ const ch = await rabbit.createChannel();
+ await ch.assertQueue(QUEUE, { durable: true });
+ await ch.assertQueue(QUEUE + "_failed", { durable: true });
+ // as long as the queue is filled, msg are not ACKed
+ // server sends as long as there are less than `prefetch` unACKed
+ await ch.prefetch(BATCHSIZE);
+
+ const model = require("../orm/model")(seq, Seq);
// performance logging
let load_timer = undefined,
@@ -121,67 +78,29 @@ function flatten(obj) {
// Maps to quickly convert API names to db ids
let item_db_map = new Map(), // "Halcyon Potion" to id
- hero_db_map = new Map(), // "*SAW*" to id
- series_db_map = new Map(), // date to series id
- game_mode_db_map = new Map(), // "ranked" to id
- role_db_map = new Map(), // "captain" to id
- hero_role_map = new Map(); // SAW.id to "carry"
+ hero_db_map = new Map(); // "*SAW*" to id
// populate maps
await Promise.all([
model.Item.findAll()
.map((item) => item_db_map.set(item.name, item.id)),
model.Hero.findAll()
- .map((hero) => hero_db_map.set(hero.name, hero.id)),
- model.Series.findAll()
- .map((series) => {
- if (series.dimension_on == "player")
- series_db_map.set(series.name, series.id);
- }),
- model.GameMode.findAll()
- .map((mode) => game_mode_db_map.set(mode.name, mode.id)),
- model.Role.findAll()
- .map((role) => role_db_map.set(role.name, role.id))
+ .map((hero) => hero_db_map.set(hero.name, hero.id))
]);
-
- // as long as the queue is filled, msg are not ACKed
- // server sends as long as there are less than `prefetch` unACKed
- await ch.prefetch(BATCHSIZE);
+ if (item_db_map.size == 0 ||
+ hero_db_map.size == 0) {
+ logger.error("mapping tables are not seeded!!! quitting");
+ process.exit();
+ }
// buffers that will be filled until BATCHSIZE is reached
// to make db transactions more efficient
- let player_data = new Set(),
- match_data = new Set(),
- telemetry_data = new Set(),
+ let telemetry_data = new Set(),
msg_buffer = new Set();
ch.consume(QUEUE, async (msg) => {
- if (msg.properties.type == "player") {
- // bridge sends a single object
- player_data.add(JSON.parse(msg.content));
- msg_buffer.add(msg);
- }
- if (msg.properties.type == "match") {
- // apigrabber sends a single object
- const match = JSON.parse(msg.content);
- // deduplicate and reject immediately
- if (await model.Match.count({ where: { api_id: match.id } }) > 0) {
- if (msg.properties.headers.notify != undefined) {
- await ch.publish("amq.topic",
- msg.properties.headers.notify,
- new Buffer("matches_dupe"))
- // send match_dupe to web player.ign.api_id
- await ch.publish("amq.topic",
- msg.properties.headers.notify + "." + match.id,
- new Buffer("match_dupe"))
- }
- } else match_data.add(match);
- msg_buffer.add(msg);
- }
- if (msg.properties.type == "telemetry") {
- telemetry_data.add(JSON.parse(msg.content));
- msg_buffer.add(msg);
- }
+ telemetry_data.add(JSON.parse(msg.content));
+ msg_buffer.add(msg);
// fill queue until batchsize or idle
// for logging of the time between batch fill and batch process
@@ -194,188 +113,39 @@ function flatten(obj) {
clearTimeout(idle_timer);
idle_timer = setTimeout(process, IDLE_TIMEOUT);
// maximum data pressure
- if (match_data.size + player_data.size + telemetry_data.size == BATCHSIZE)
- await process();
+ if (telemetry_data.size == BATCHSIZE)
+ await tryProcess();
}, { noAck: false });
- // finish a whole batch
- async function process() {
+ // wrap process() in message handler
+ async function tryProcess() {
+ const msgs = new Set(msg_buffer);
+ msg_buffer.clear();
+
profiler.done("buffer filled");
profiler = undefined;
logger.info("processing batch", {
- players: player_data.size,
- matches: match_data.size,
telemetries: telemetry_data.size
});
// clean up to allow processor to accept while we wait for db
clearTimeout(idle_timer);
clearTimeout(load_timer);
- const player_objects = new Set(player_data),
- match_objects = new Set(match_data),
- telemetry_objects = new Set(telemetry_data),
- msgs = new Set(msg_buffer);
+ }
+
+ // finish a whole batch
+ async function process() {
+ const telemetry_objects = new Set(telemetry_data);
idle_timer = undefined;
load_timer = undefined;
- player_data.clear();
- match_data.clear();
telemetry_data.clear();
- msg_buffer.clear();
-
- const processed_players = new Set(); // to sort out duplicates
// aggregate record objects to do a bulk insert
- let match_records = [],
- roster_records = [],
- participant_records = [],
- participant_stats_records = [],
- participant_phase_records = [], // Telemetry
- player_records = [],
- player_records_direct = [], // via `/players`
- asset_records = [];
-
- // populate `_records`
- // data from `/players`
- // `each` executes serially so there are
- // no race conditions within one batch
- await Promise.each(player_objects, async (p) => {
- const player = flatten(p);
- if (processed_players.has(player.api_id)) {
- // duplicate within one batch
- logger.warn("got player in additional region",
- { name: player.name, region: player.shard_id });
- const duplicate = player_records_direct.find((pr) =>
- pr.api_id == player.api_id);
- if (duplicate == undefined) return; // TODO wtf???
- if (duplicate.created_at < player.created_at) {
- // replace by newer one as below
- player_records_direct.splice(
- player_records_direct.indexOf(duplicate), 1);
- } else {
- logger.warn("ignoring player from additional region",
- { name: player.name, region: player.shard_id });
- return;
- }
- } else {
- processed_players.add(player.api_id);
- }
- // player objects that arrive here came from a search
- // with search, updater can't update last_update
- player.last_update = seq.fn("NOW");
- logger.info("processing player",
- { name: player.name, region: player.shard_id });
- // duplicate in batch and db
- // check whether there is a player in db
- // that has a more recent `created_at`
- // this is only the case with region changes
- const count = await model.Player.count({ where: {
- api_id: player.api_id,
- created_at: {
- $gt: player.created_at // equal: just update last_update
- }
- }});
- if (count > 0) {
- logger.warn("ignoring player who seems to have switched from region",
- { name: player.name, region: player.shard_id });
- return;
- } else player_records_direct.push(player);
- });
-
- // reject invalid matches (handling API bugs) TODO should happen in apigrabber
- match_objects.forEach((match, idx) => {
- // it is really `"null"`.
- if (match.rosters[0].id == "null") delete match_objects[idx];
- });
-
- // data from `/matches`
- match_objects.forEach((match) => {
- // flatten jsonapi nested response into our db structure-like shape
- // also, push missing fields
- match.rosters = match.rosters.map((roster) => {
- roster.matchApiId = match.id;
- // TODO backwards compatibility, all objects have shardId since May 10th
- roster.attributes.shardId = roster.attributes.shardId || match.attributes.shardId;
- roster.createdAt = match.createdAt;
- // TODO API workaround: roster does not have `winner`
- if (roster.participants.length > 0)
- roster.attributes.stats.winner = roster.participants[0].stats.winner;
- else // Blitz 2v0, see 095e86e4-1bd3-11e7-b0b1-0297c91b7699 on eu
- roster.attributes.stats.winner = false;
-
- roster.participants = roster.participants.map((participant) => {
- // ! attributes added here need to be added via `calculate_participant_stats` too
- participant.attributes.shardId = participant.attributes.shardId || roster.attributes.shardId;
- participant.rosterApiId = roster.id;
- participant.matchApiId = match.id;
- participant.createdAt = roster.createdAt;
- participant.playerApiId = participant.player.id;
-
- // API bug fixes (TODO)
- // items on AFK is `null` not `{}`
- participant.attributes.stats.itemGrants = participant.attributes.stats.itemGrants || {};
- participant.attributes.stats.itemSells = participant.attributes.stats.itemSells || {};
- participant.attributes.stats.itemUses = participant.attributes.stats.itemUses || {};
- // jungle_kills is `null` in BR
- participant.attributes.stats.jungleKills = participant.attributes.stats.jungleKills || 0;
-
- // map items: names/id -> name -> db
- const item_id = ((i) => item_db_map.get(api_name_mappings.get(i)));
- let itms = [];
-
- const pas = participant.attributes.stats; // I'm lazy
- // csv
- participant.attributes.stats.items =
- pas.items.map((i) => item_id(i).toString()).join(",");
- // csv with count seperated by ;
- participant.attributes.stats.itemGrants =
- Object.keys(pas.itemGrants)
- .map((key) => item_id(key) + ";" + pas.itemGrants[key]).join(",");
- participant.attributes.stats.itemUses =
- Object.keys(pas.itemUses)
- .map((key) => item_id(key) + ";" + pas.itemUses[key]).join(",");
- participant.attributes.stats.itemSells =
- Object.keys(pas.itemSells)
- .map((key) => item_id(key) + ";" + pas.itemSells[key]).join(",");
-
- participant.player.attributes.shardId = participant.player.attributes.shardId || participant.attributes.shardId;
- participant.player = flatten(participant.player);
- return flatten(participant);
- });
- return flatten(roster);
- });
- match.assets = match.assets.map((asset) => {
- asset.matchApiId = match.id;
- asset.attributes.shardId = asset.attributes.shardId || match.attributes.shardId;
- return flatten(asset);
- });
- match = flatten(match);
-
- // after conversion, create the array of records
- match_records.push(match);
- match.rosters.forEach((r) => {
- roster_records.push(r);
- r.participants.forEach((p) => {
- const p_pstats = calculate_participant_stats(match, r, p);
- // participant gets split into participant and p_stats
- participant_records.push(p_pstats[0]);
- participant_stats_records.push(p_pstats[1]);
- // deduplicate player
- // in a batch, it is very likely that players are duplicated
- // so this improves performance a bit
- if (!processed_players.has(p.player.api_id)) {
- processed_players.add(p.player.api_id);
- player_records.push(p.player);
- }
- });
- });
- match.assets.forEach((a) => asset_records.push(a));
- });
-
+ let participant_phase_records = [];
// data from Telemetry, one phase (early/mid/late/…) per obj
await Promise.map(telemetry_objects, async (telemetry) => {
- if (telemetry.data.length == 0) return; // TODO rm me
// api -> telemetry format
const sideToTeam = (s) => s == "left/blue"? "Left" : "Right",
// yes there is yet another format and yes it's strings
@@ -442,13 +212,7 @@ function flatten(obj) {
p.actor == t.payload.Killed
&& p.team == t.payload.KilledTeam)[0];
});
- /*
- telemetry.data.forEach((ev) => { // TODO debug
- if (ev.payload.Ability == undefined || ev.payload.IsHero == 0) return;
- if (!api_name_mappings.has(ev.payload.Ability))
- console.error("ab to name map missing", ev.payload.Ability);
- });
- */
+
const participants_phase = participants.map((p) => { return {
// TODO ban data workaround
start: telemetry.start < 0? 0 : telemetry.start, // in seconds
@@ -713,58 +477,10 @@ function flatten(obj) {
// upsert whole batch in parallel
logger.info("inserting batch into db");
await seq.transaction({ autocommit: false }, async (transaction) => {
- await Promise.map(chunks(match_records), async (m_r) =>
- model.Match.bulkCreate(m_r, {
- ignoreDuplicates: true, // if this happens, something is wrong
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
- await Promise.map(chunks(roster_records), async (r_r) =>
- model.Roster.bulkCreate(r_r, {
- ignoreDuplicates: true,
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
- await Promise.map(chunks(participant_records), async (p_r) =>
- model.Participant.bulkCreate(p_r, {
- ignoreDuplicates: true,
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
- await Promise.map(chunks(participant_stats_records), async (p_s_r) =>
- model.ParticipantStats.bulkCreate(p_s_r, {
- ignoreDuplicates: true,
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
await Promise.map(chunks(participant_phase_records), async (p_p_r) =>
model.ParticipantPhases.bulkCreate(p_p_r, {
- /* ignoreDuplicates: true, TODO DEBUG */
- ignoreDuplicates: false,
- updateOnDuplicate: [],
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
- await Promise.map(chunks(player_records), async (pl_r) =>
- model.Player.bulkCreate(pl_r, {
- ignoreDuplicates: true,
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
- await Promise.map(chunks(player_records_direct), async (p_r_d) =>
- model.Player.bulkCreate(player_records_direct, {
- // if set to [] (all), upsert messes with autoincrement
- updateOnDuplicate: [
- "shard_id", "api_id", "name", "last_update",
- "created_at", "level", "xp", "lifetime_gold",
- "skill_tier"
- ],
- transaction: transaction
- }), { concurrency: MAXCONNS }
- );
- await Promise.map(chunks(asset_records), async (a_r) =>
- model.Asset.bulkCreate(a_r, {
ignoreDuplicates: true,
+ updateOnDuplicate: [],
transaction: transaction
}), { concurrency: MAXCONNS }
);
@@ -773,153 +489,38 @@ function flatten(obj) {
logger.info("acking batch", { size: msgs.size });
await Promise.map(msgs, async (m) => await ch.ack(m));
} catch (err) {
- // this should only happen for Deadlocks in prod
- // it *must not* fail due to broken schema or missing dependency
- // TODO: eliminate such cases earlier in the chain
- // and immediately NACK those broken matches, requeueing only the rest
- logger.error("SQL error", err);
- await Promise.map(msgs, async (m) => await ch.nack(m, false, true));
- return; // give up
+ if (err instanceof Seq.TimeoutError) {
+ // deadlocks / timeout
+ logger.error("SQL error", err);
+ await Promise.map(msgs, async (m) =>
+ await ch.nack(m, false, true)); // retry
+ } else {
+ // log, move to error queue and NACK
+ logger.error(err);
+ await Promise.map(msgs, async (m) => {
+ await ch.sendToQueue(QUEUE + "_failed", m.content, {
+ persistent: true,
+ headers: m.properties.headers
+ });
+ await ch.nack(m, false, false);
+ });
+ }
}
transaction_profiler.done("database transaction");
// notify web
await Promise.map(msgs, async (m) => {
if (m.properties.headers.notify == undefined) return;
- let notif = "error";
- // new match
- if (m.properties.type == "match") {
- notif = "matches_update";
- // TODO this sends match_update for every match in the batch to every player
- // notify player.name.api_id about match_update
- await Promise.map(match_records, async (mat) =>
- await ch.publish("amq.topic",
- m.properties.headers.notify + "." + mat.api_id,
- new Buffer("match_update"))
- );
- await ch.publish("amq.topic", m.properties.headers.notify,
- new Buffer("match_update"));
- }
- // player obj updated
- if (m.properties.type == "player") notif = "stats_update";
// new phases
- if (m.properties.type == "telemetry") {
- // notify match.api_id about phase_update
- await ch.publish("amq.topic",
- m.properties.headers.notify,
- new Buffer("phase_update"))
- }
-
- if (m.properties.headers.donotify == true) // TODO remove later
- await ch.publish("amq.topic", m.properties.headers.notify,
- new Buffer(notif));
+ // notify match.api_id about phase_update
+ await ch.publish("amq.topic",
+ m.properties.headers.notify,
+ new Buffer("phase_update"))
});
- // …global about new matches
- if (match_records.length > 0)
- await ch.publish("amq.topic", "global", new Buffer("matches_update"));
- // notify follow up services
- if (DOANALYZEMATCH)
- await Promise.each(match_records, async (m) =>
- await ch.sendToQueue(ANALYZE_QUEUE, new Buffer(m.api_id),
- { persistent: true }));
}
+});
- // Split participant API data into participant and participant_stats
- // Should not need to query db here.
- function calculate_participant_stats(match, roster, participant) {
- let p_s = {}, // participant_stats_record
- p = {}; // participant_record
-
- // copy all values that are required in db `participant` to `p`/`p_s` here
- // meta
- p_s.participant_api_id = participant.api_id;
- p_s.final = true; // these are the stats at the end of the match
- p_s.updated_at = new Date();
- p_s.created_at = new Date(Date.parse(match.created_at));
- p_s.created_at.setMinutes(p_s.created_at.getMinutes() + match.duration / 60);
- p_s.items = participant.items;
- p_s.item_grants = participant.item_grants;
- p_s.item_uses = participant.item_uses;
- p_s.item_sells = participant.item_sells;
- p_s.duration = match.duration;
- p.created_at = match.created_at;
- // mappings
- // hero names additionally need to be mapped old to new names
- // (Sayoc = Taka)
- p.hero_id = hero_db_map.get(api_name_mappings.get(participant.actor));
- if (match.patch_version != "")
- p.series_id = series_db_map.get("Patch " + match.patch_version);
- else {
- if (p_s.created_at < new Date("2017-03-28T15:00:00"))
- p.series_id = series_db_map.get("Patch 2.2");
- else if (p_s.created_at < new Date("2017-04-26T15:00:00"))
- p.series_id = series_db_map.get("Patch 2.3");
- else p.series_id = series_db_map.get("Patch 2.4");
- }
- p.game_mode_id = game_mode_db_map.get(match.game_mode);
-
- // attributes to copy from API to participant
- // these don't change over the duration of the match
- // (or aren't in Telemetry)
- ["api_id", "shard_id", "player_api_id", "roster_api_id", "match_api_id",
- "winner", "went_afk", "first_afk_time",
- "skin_key", "skill_tier", "level",
- "karma_level", "actor"].map((attr) =>
- p[attr] = participant[attr]);
-
- // attributes to copy from API to participant_stats
- // with Telemetry, these will be calculated in intervals
- ["kills", "deaths", "assists", "minion_kills",
- "jungle_kills", "non_jungle_minion_kills",
- "crystal_mine_captures", "gold_mine_captures",
- "kraken_captures", "turret_captures",
- "gold", "farm"].map((attr) =>
- p_s[attr] = participant[attr]);
-
- let role = classify_role(p_s);
-
- // score calculations
- let impact_score = 50;
- switch (role) {
- case "carry":
- impact_score = -0.47249153 + 0.50145197 * p_s.assists - 0.7136091 * p_s.deaths + 0.18712844 * p_s.kills + 0.00531455 * p_s.farm;
- p_s.nacl_score = 1 * p_s.kills + 0.5 * p_s.assists + 0.03 * p_s.farm + 5 * p.winner;
- break;
- case "jungler":
- impact_score = -0.54510754 + 0.19982097 * p_s.assists - 0.35694721 * p_s.deaths + 0.09942473 * p_s.kills + 0.01256313 * p_s.farm;
- p_s.nacl_score = 1 * p_s.kills + 0.5 * p_s.assists + 0.04 * p_s.farm + 5 * p.winner;
- break;
- case "captain":
- impact_score = -0.46473539 + 0.09968104 * p_s.assists - 0.38401479 * p_s.deaths + 0.14753133 * p_s.kills + 0.03431293 * p_s.farm;
- p_s.nacl_score = 1 * p_s.kills + 1 * p_s.assists + 5 * p.winner;
- break;
- }
- p_s.impact_score = (impact_score - (-4.5038622921659375) ) / (4.431094119937388 - (-4.5038622921659375) );
-
-
- // classifications
- p.role_id = role_db_map.get(role);
-
- // traits calculations
- if (roster.hero_kills == 0) p_s.kill_participation = 0;
- else p_s.kill_participation = (p_s.kills + p_s.assists) / roster.hero_kills;
-
- return [p, p_s];
- }
-
- // return "captain" "carry" "jungler"
- function classify_role(participant_stats) {
- const is_captain_score = 2.34365487 + (-0.06188674 * participant_stats.non_jungle_minion_kills) + (-0.10575069 * participant_stats.jungle_kills), // about 88% accurate, trained on Hero.is_captain
- is_carry_score = -1.88524473 + (0.05593593 * participant_stats.non_jungle_minion_kills) + (-0.0881661 * participant_stats.jungle_kills), // about 90% accurate, trained on Hero.is_carry
- is_jungle_score = -0.78327066 + (-0.03324596 * participant_stats.non_jungle_minion_kills) + (0.10514832 * participant_stats.jungle_kills); // about 88% accurate
- if (is_captain_score > is_carry_score && is_captain_score > is_jungle_score)
- return "captain";
- if (is_carry_score > is_jungle_score)
- return "carry";
- return "jungler";
- }
-})();
-
-process.on("unhandledRejection", function(reason, promise) {
- logger.error(reason);
+process.on("unhandledRejection", (err) => {
+ logger.error(err);
+ process.exit(1); // fail hard and die
});