diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..64400a94b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +data +rs-backend/target diff --git a/rs-backend/Cargo.lock b/rs-backend/Cargo.lock new file mode 100644 index 000000000..78b590b65 --- /dev/null +++ b/rs-backend/Cargo.lock @@ -0,0 +1,438 @@ +[root] +name = "rustc-perf" +version = "0.1.0" +dependencies = [ + "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "json 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "persistent 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "router 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "conduit-mime-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cookie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dbghelp-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "error-chain" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gcc" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hpack" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iron" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "json" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mime" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "modifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "persistent" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "plugin" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "route-recognizer" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "router" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "route-recognizer 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "solicit" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "traitobject" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicase" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unsafe-any" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum backtrace 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "346d7644f0b5f9bc73082d3b2236b69a05fd35cce0cfa3724e184e6a5c9e2a2f" +"checksum backtrace-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ff73785ae8e06bb4a7b09e09f06d7434f9748b86d2f67bdf334b603354497e08" +"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" +"checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" +"checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" +"checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" +"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" +"checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +"checksum error-chain 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e9f138f617e9d48b8e36278b886a09b244faf77e6e72ed6cf77784ae2880752b" +"checksum gcc 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "dcb000abd6df9df4c637f75190297ebe56c1d7e66b56bbf3b4aa7aece15f61a2" +"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" +"checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" +"checksum hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "eb27e8a3e8f17ac43ffa41bbda9cf5ad3f9f13ef66fa4873409d4902310275f7" +"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" +"checksum iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fb1b2d809f84bf347e472d5758762b5c804e0c622970235f156d82673e4d334" +"checksum json 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6bd92ccf3002e73109a19528128d1678b076309df958c058c391659727b635e" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +"checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" +"checksum libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "39dfaaa0f4da0f1a06876c5d94329d739ad0150868069cc235f1ddf80a0480e7" +"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" +"checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" +"checksum mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf93a79c700c9df8227ec6a4f0f27a8948373c079312bac24549d944cef85f64" +"checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" +"checksum num 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "d2ee34a0338c16ae67afb55824aaf8852700eb0f77ccd977807ccb7606b295f6" +"checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92" +"checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c" +"checksum num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "95e58eac34596aac30ab134c8a8da9aa2dc99caa4b4b4838e6fc6e298016278f" +"checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3" +"checksum persistent 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebaf2f9f9881f73e82ba23164a40d3500112d8e7ad056cdde451874f1814a4d9" +"checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" +"checksum route-recognizer 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4f0a750d020adb1978f5964ea7bca830585899b09da7cbb3f04961fc2400122d" +"checksum router 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff665ba113dc57ef54604ded19375c5ddd23ec44b550a3667c595205b5f98b42" +"checksum rustc-demangle 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c4c2d35b2ed94cec4fad26a36eee4d6eff394ce70a8ceea064b0b6ca42ea4cf0" +"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "172382bac9424588d7840732b250faeeef88942e37b6e35317dce98cafdd75b2" +"checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" +"checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" +"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" +"checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +"checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" +"checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" +"checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" +"checksum unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b351086021ebc264aea3ab4f94d61d889d98e5e9ec2d985d993f50133537fd3a" +"checksum url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8ab4ca6f0107350f41a59a51cb0e71a04d905bc6a29181d2cb42fa4f040c65c9" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/rs-backend/Cargo.toml b/rs-backend/Cargo.toml new file mode 100644 index 000000000..f3ee07249 --- /dev/null +++ b/rs-backend/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["Mark-Simulacrum ", "Nicholas Cameron ", "The rustc-perf contributors"] +name = "rustc-perf" +version = "0.1.0" + +[dependencies] +chrono = "0.2.22" +error-chain = "0.2.2" +json = "0.10.1" +iron = "0.4.0" +router = "0.2.0" +persistent = "0.2.0" diff --git a/rs-backend/src/errors.rs b/rs-backend/src/errors.rs new file mode 100644 index 000000000..12c3f036b --- /dev/null +++ b/rs-backend/src/errors.rs @@ -0,0 +1,13 @@ +error_chain! { + links { + } + + foreign_links { + ::std::io::Error, Io, "io error"; + ::json::Error, Json, "json error"; + ::chrono::ParseError, Chrono, "chrono parsing error"; + } + + errors { + } +} diff --git a/rs-backend/src/load.rs b/rs-backend/src/load.rs new file mode 100644 index 000000000..b701c4cb6 --- /dev/null +++ b/rs-backend/src/load.rs @@ -0,0 +1,438 @@ +use std::collections::{HashMap, HashSet}; +use std::cmp::{Ordering, max}; +use std::fs::{self, File}; +use std::path::Path; +use std::io::Read; + +use chrono::{Duration, NaiveDateTime}; +use json::{self, JsonValue}; + +use util::start_idx; +use errors::*; + +const WEEKS_IN_SUMMARY: usize = 12; + +/// Contains the TestRun loaded from the file system. +/// Useful to avoid passing around each individual field, instead passing the +/// entire struct. +pub struct InputData { + pub summary_rustc: Summary, + pub summary_benchmarks: Summary, + + /// A set containing all crate names. + pub crate_list: HashSet, + + /// A set containing all phase names, across all crates. + pub phase_list: HashSet, + + /// TODO: Better docs. It's unknown how this is used on the client. A set + /// of test names. Test names are found from the file path, everything + /// before the `--` is included. + pub benchmarks: HashSet, + + /// The last date that was seen while loading files. The DateTime variant is + /// used here since the date may or may not contain a time. Since the + /// timezone is not important, it isn't stored, hence the Naive variant. + pub last_date: NaiveDateTime, + + /// Private due to access being exposed through by_kind method. + /// Care must be taken to sort these after modifying them. + data_rustc: Vec, + data_benchmarks: Vec, +} + +/// Allows storing `InputData` in Iron's persistent data structure. +impl ::iron::typemap::Key for InputData { + type Value = InputData; +} + +impl InputData { + /// Initialize `InputData from the file system. + pub fn from_fs() -> Result { + // TODO: Read this at runtime, not hardcoded. + let repo_loc = Path::new("../data"); + + let mut last_date = None; + let mut crate_list = HashSet::new(); + let mut phase_list = HashSet::new(); + let mut skipped = 0; + let mut c_benchmarks_add = 0; + + let mut data_rustc = Vec::new(); + let mut data_benchmarks = Vec::new(); + let mut benchmarks = HashSet::new(); + + // Read all files from repo_loc/processed + let mut file_count = 0; + for entry in fs::read_dir(repo_loc.join("processed"))? { + let entry = entry?; + if entry.file_type()?.is_dir() { + continue; + } + file_count += 1; + + let filename = entry.file_name(); + let filename = filename.to_str().unwrap(); + let mut file = File::open(entry.path())?; + let mut file_contents = String::new(); + // Skip files whose size is 0. + if file.read_to_string(&mut file_contents)? == 0 { + skipped += 1; + continue; + } + + let contents = match json::parse(&file_contents) { + Ok(json) => json, + Err(err) => { + println!("Failed to parse JSON for {}: {:?}", filename, err); + skipped += 1; + continue; + } + }; + if contents["times"].is_empty() { + skipped += 1; + continue; + } + + /// Parse date from JSON header in file contents. + fn parse_from_header(date: &str) -> Result { + // TODO: Determine correct format of header date and move into + // variable/constant + NaiveDateTime::parse_from_str(date, "%c %z") + .map_err(|err| err.into()) + } + + /// Parse date from filename. + fn parse_from_filename(filename: &str) -> Result { + let date_str = &filename[(filename.find("--").unwrap() + 2)..filename.find(".json").unwrap()]; + + // Ignore seconds: they aren't key to the data processing, and + // parsing them requires logic to replace them with 0 when + // they are absent. + NaiveDateTime::parse_from_str(date_str, "%Y-%m-%d-%H-%M") + .map_err(|err| err.into()) + } + + let header = &contents["header"]; + let date = header["date"].as_str().unwrap(); + let date = parse_from_header(date).or_else(|_| parse_from_filename(&filename))?; + + let test_name = &filename[0..filename.find("--").unwrap()]; + + let times = &contents["times"]; + if test_name == "rustc" { + data_rustc.push(TestRun::new(date, header, times)); + + for timing in times.members() { + let crate_name = timing["crate"].as_str().unwrap().to_string(); + crate_list.insert(crate_name); + } + } else { + benchmarks.insert(test_name.to_string()); + let index = data_benchmarks.iter().position(|benchmark: &TestRun| benchmark.date == date); + if let Some(index) = index { + c_benchmarks_add += 1; + let crate_name = times[0]["crate"].as_str().unwrap(); + data_benchmarks[index].by_crate.insert(test_name.to_string(), + make_times(times).remove(crate_name).unwrap()); + } else { + data_benchmarks.push(TestRun::new(date, header, times)); + } + } + + for timing in times.members() { + for (phase, _) in timing["times"].entries() { + phase_list.insert(phase.to_string()); + } + } + + if last_date.is_none() || last_date.as_ref().unwrap() < &date { + last_date = Some(date); + } + } + + let last_date = last_date.expect("No dates found"); + + data_rustc.sort(); + data_benchmarks.sort(); + + // Post processing to generate the summary data. + let summary_rustc = Summary::new(&data_rustc, last_date); + let summary_benchmarks = Summary::new(&data_benchmarks, last_date); + + println!("{} total files", file_count); + println!("{} skipped files", skipped); + println!("{} bootstrap times", data_rustc.len()); + println!("{} benchmarks times", data_benchmarks.len()); + println!("{} benchmarks times (appended)", c_benchmarks_add); + + Ok(InputData { + summary_rustc: summary_rustc, + summary_benchmarks: summary_benchmarks, + crate_list: crate_list, + phase_list: phase_list, + benchmarks: benchmarks, + last_date: last_date, + data_rustc: data_rustc, + data_benchmarks: data_benchmarks, + }) + } + + pub fn by_kind(&self, kind: Kind) -> &[TestRun] { + match kind { + Kind::Benchmarks => &self.data_benchmarks, + Kind::Rustc => &self.data_rustc + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Kind { + Benchmarks, + Rustc, +} + +impl Kind { + pub fn from_str(kind: &str) -> Option { + match kind { + "rustc" => Some(Kind::Rustc), + "benchmarks" => Some(Kind::Benchmarks), + _ => None + } + } +} + +/// The data loaded for a single date, and all associated crates. +pub struct TestRun { + pub date: NaiveDateTime, + pub commit: String, + + /// Map of crate names to a map of phases to timings per phase. + pub by_crate: HashMap> +} + +impl PartialEq for TestRun { + fn eq(&self, other: &TestRun) -> bool { + self.commit == other.commit && self.date == other.date + } +} + +impl Eq for TestRun {} + +impl PartialOrd for TestRun { + fn partial_cmp(&self, other: &TestRun) -> Option { + Some(::cmp(self, other)) + } +} + +impl Ord for TestRun { + fn cmp(&self, other: &TestRun) -> Ordering { + self.date.cmp(&other.date) + } +} + +impl TestRun { + fn new(date: NaiveDateTime, header: &JsonValue, times: &JsonValue) -> TestRun { + TestRun { + date: date, + commit: header["commit"].as_str().unwrap().to_string(), + by_crate: make_times(times) + } + } +} + +/// Contains a single timing, associated with a phase (though the phase name +/// is not included in the timing). +#[derive(Debug, PartialEq)] +pub struct Timing { + pub percent: f64, + pub time: f64, + pub rss: Option, +} + +impl Timing { + fn new() -> Timing { + Timing { + percent: 0.0, + time: 0.0, + rss: Some(0) + } + } +} + +/// Run through the timings for a date and construct the `by_crate` field of TestRun. +fn make_times(timings: &JsonValue) -> HashMap> { + let mut by_crate = HashMap::new(); + let mut totals = HashMap::new(); + + for timing in timings.members() { + let mut times = HashMap::new(); + for (phase_name, phase) in timing["times"].entries() { + times.insert(phase_name.to_string(), Timing { + percent: phase["percent"].as_f64().unwrap(), + time: phase["time"].as_f64().unwrap(), + rss: timing["rss"][phase_name].as_u64(), + }); + } + + let mut mem_values = Vec::new(); + if timing["rss"].is_object() { + for (_, value) in timing["rss"].entries() { + mem_values.push(value.as_u64().unwrap()); + } + } + + times.insert("total".into(), Timing { + percent: 100.0, + time: timing["total"].as_f64().unwrap(), + rss: Some(mem_values.into_iter().max().unwrap_or(0)) + }); + + for phase in times.keys() { + let mut entry = totals.entry(phase.to_string()).or_insert_with(Timing::new); + entry.time += times[phase].time; + entry.rss = max(times[phase].rss, entry.rss); + } + + by_crate.insert(timing["crate"].as_str().unwrap().to_string(), times); + } + + by_crate.insert("total".into(), totals); + // TODO: calculate percentages + by_crate +} + +#[derive(Debug)] +pub struct SummarizedWeek { + pub date: NaiveDateTime, + + /// Maps crate names to a map of phases to each phase's percent change + /// from the previous date. + pub by_crate: HashMap> +} + +pub struct Summary { + pub total: SummarizedWeek, + pub summary: Vec, +} + +impl Summary { + // Compute summary data. For each week, we find the last 3 weeks, and use + // the median timing as the basis of the current week's summary. + fn new(data: &[TestRun], last_date: NaiveDateTime) -> Summary { + // 13 week long mapping of crate names to by-phase medians. + // We steal using summarized week type here even though we're not + // storing the percent changes yet. + let mut medians: Vec = Vec::new(); + + // In order to compute summaries for the previous 12 weeks, we need 13 + // weeks, using the 0th week to compute difference with first week and + // totals. + for i in 0..(WEEKS_IN_SUMMARY + 1) { + let date = last_date - Duration::weeks(i as i64); + + // For a given date we'll get the three most recent sets of TestRun + // and take the the median for each value. + let start_idx = start_idx(data, &date); + assert!(start_idx >= 3, "Less than 3 days of data"); + let mut weeks = Vec::new(); + for idx in 0..3 { + weeks.push(&data[start_idx - idx].by_crate); + } + + medians.push(SummarizedWeek { + date: date, + by_crate: HashMap::new(), + }); + for crate_name in weeks[0].keys() { + if !weeks[1].contains_key(crate_name) || !weeks[2].contains_key(crate_name) { + continue; + } + + let mut crate_medians = HashMap::new(); + for phase in weeks[0][crate_name].keys() { + if !weeks[1][crate_name].contains_key(phase) || + !weeks[2][crate_name].contains_key(phase) { + continue; + } + // Find the median value. + let mut values = [ + weeks[0][crate_name][phase].time, + weeks[1][crate_name][phase].time, + weeks[2][crate_name][phase].time + ]; + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + let median = values[1]; + + crate_medians.insert(phase.to_string(), median); + } + + medians.last_mut().unwrap().by_crate.insert(crate_name.to_string(), crate_medians); + } + } + + /// Compute the percent change for a given number from the week N-1 to + /// week N, where week N-1 is the previous and week N is the current + /// week. + fn get_percent_change(previous: f64, current: f64) -> f64 { + ((current - previous) / previous) * 100.0 + } + + fn compare_weeks(week0: &SummarizedWeek, week1: &SummarizedWeek) -> SummarizedWeek { + let mut current_week = HashMap::new(); + for crate_name in week0.by_crate.keys() { + if !week1.by_crate.contains_key(crate_name) { + continue; + } + + let mut current_crate = HashMap::new(); + for phase in week0.by_crate[crate_name].keys() { + if !week1.by_crate[crate_name].contains_key(phase) { + continue; + } + + // TODO: Some form of warning is a good idea? + // If this is triggered for both the 0th overall week and + // the last week, then that phase won't be recorded in + // totals no matter what happens in between. + if week0.by_crate[crate_name][phase] > 0.0 && + week1.by_crate[crate_name][phase] > 0.0 { + current_crate.insert(phase.to_string(), + get_percent_change(week0.by_crate[crate_name][phase], + week1.by_crate[crate_name][phase])); + } + } + current_week.insert(crate_name.to_string(), current_crate); + } + SummarizedWeek { + date: week1.date, + by_crate: current_week, + } + } + + // 12 week long mapping of crate names to by-phase percent changes with + // the previous week. + let mut weeks: Vec = Vec::new(); + for window in medians.windows(2) { + weeks.push(compare_weeks(&window[0], &window[1])); + } + + assert_eq!(weeks.len(), 12); + + let totals = compare_weeks(&medians[0], medians.last().unwrap()); + + for week in &mut weeks { + for crate_name in totals.by_crate.keys() { + if !week.by_crate.contains_key(crate_name) { + week.by_crate.insert(crate_name.to_string(), HashMap::new()); + } + } + } + + Summary { + total: totals, + summary: weeks + } + } +} diff --git a/rs-backend/src/main.rs b/rs-backend/src/main.rs new file mode 100644 index 000000000..aa3722f3d --- /dev/null +++ b/rs-backend/src/main.rs @@ -0,0 +1,22 @@ +#![feature(question_mark)] + +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate json; +extern crate chrono; +extern crate iron; +extern crate router; +extern crate persistent; + +pub const SERVER_ADDRESS: &'static str = "0.0.0.0:2346"; + +mod util; +mod load; +mod server; +mod errors; + +fn main() { + let data = load::InputData::from_fs().unwrap(); + server::start(data); +} diff --git a/rs-backend/src/server.rs b/rs-backend/src/server.rs new file mode 100644 index 000000000..3d4aad076 --- /dev/null +++ b/rs-backend/src/server.rs @@ -0,0 +1,519 @@ +use std::collections::{HashMap}; +use std::cmp::{max, min}; +use std::io::Read; + +use iron::prelude::*; +use iron::status; +use router::Router; +use persistent::State; +use chrono::{Duration, NaiveDate, NaiveDateTime}; +use json::{self, JsonValue}; + +use SERVER_ADDRESS; +use errors::*; +use load::{SummarizedWeek, Kind, TestRun, InputData}; +use util::{start_idx, end_idx}; + +const JS_DATE_FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S.000Z"; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +enum GroupBy { + Crate, + Phase, +} + +impl GroupBy { + fn from_str(kind: &str) -> Option { + match kind { + "crate" => Some(GroupBy::Crate), + "phase" => Some(GroupBy::Phase), + _ => None + } + } +} + +fn get_summary(_req_body: Option, data: &InputData) -> Result { + let dates = data.summary_rustc.summary.iter() + .map(|s| s.date.format(JS_DATE_FORMAT).to_string()) + .collect::>(); + + fn summarize(benchmark: &SummarizedWeek, rustc: &SummarizedWeek) -> String { + let mut sum = 0.0; + let mut count = 0; + for krate in benchmark.by_crate.values() { + if krate.contains_key("total") { + sum += krate["total"]; + count += 1; + } + } + + if rustc.by_crate["total"].contains_key("total") { + sum += 2.0 * rustc.by_crate["total"]["total"]; + count += 2; + } + + format!("{:.1}", sum / (count as f64)) + } + + // overall number for each week + let summaries = data.summary_benchmarks.summary.iter().enumerate().map(|(i, s)| { + summarize(s, &data.summary_rustc.summary[i]) + }).collect::>(); + + fn breakdown(benchmark: &SummarizedWeek, rustc: &SummarizedWeek) -> JsonValue { + let mut per_bench = HashMap::new(); + + for (crate_name, krate) in &benchmark.by_crate { + let val = krate.get("total").cloned().unwrap_or(0.0); + per_bench.insert(crate_name.to_string(), format!("{:.1}", val).into()); + } + + let bootstrap = if rustc.by_crate["total"].contains_key("total") { + rustc.by_crate["total"]["total"] + } else { + 0.0 + }; + per_bench.insert("bootstrap".to_string(), format!("{:.1}", bootstrap).into()); + + per_bench.into() + } + + // per benchmark, per week + let breakdown_data = data.summary_benchmarks.summary.iter().enumerate().map(|(i, s)| { + breakdown(s, &data.summary_rustc.summary[i]) + }).collect::>(); + + Ok(object! { + "total_summary" => summarize(&data.summary_benchmarks.total, &data.summary_rustc.total), + "total_breakdown" => breakdown(&data.summary_benchmarks.total, &data.summary_rustc.total), + "breakdown" => breakdown_data, + "summaries" => summaries, + "dates" => dates + }) +} + +fn get_info(_req_body: Option, data: &InputData) -> Result { + Ok(object! { + "crates" => data.crate_list.iter().cloned().collect::>(), + "phases" => data.phase_list.iter().cloned().collect::>(), + "benchmarks" => data.benchmarks.iter().cloned().collect::>() + }) +} + +fn assert_request_body_present(body: Option) -> Result { + match body { + Some(body) => Ok(body), + None => Err("Missing request body".into()) + } +} + +fn get_kind_from_body(body: &JsonValue) -> Result { + let kind = body["kind"].as_str().unwrap(); + match Kind::from_str(kind) { + Some(kind) => Ok(kind), + None => Err(format!("bad value kind: {}", kind).into()) + } +} + +fn get_group_by_from_body(body: &JsonValue) -> Result { + let group_by = body["group_by"].as_str().unwrap(); + match GroupBy::from_str(group_by) { + Some(group_by) => Ok(group_by), + None => Err(format!("bad value group_by: {}", group_by).into()) + } +} + +fn parse_date(s: &str) -> Result { + let date = match NaiveDate::parse_from_str(s, "%a %b %d %Y") { + Ok(date) => date, + Err(err) => return Err(err).chain_err(|| format!("while parsing {}", s)) + }; + + Ok(date.and_hms(0, 0, 0)) +} + +fn end_date(body: &JsonValue, last_date: &NaiveDateTime) -> NaiveDateTime { + // Handle missing end by using the last available date. + if !body["end"].is_empty() { + parse_date(body["end"].as_str().unwrap()).unwrap() + } else { + *last_date + } +} + +fn start_date(body: &JsonValue, last_date: &NaiveDateTime) -> NaiveDateTime { + // Handle missing start by returning 30 days before end. + if !body["start"].is_empty() { + parse_date(body["start"].as_str().unwrap()).unwrap() + } else { + let end = end_date(body, last_date); + let start = (end - Duration::days(30)).timestamp(); + NaiveDateTime::from_timestamp(start, 0) + } +} + +fn get_data_for_date(day: &TestRun, crates: &JsonValue, phases: &JsonValue, group_by: GroupBy) -> JsonValue { + let mut data = JsonValue::new_object(); + if group_by == GroupBy::Crate { + for crate_name in crates.members() { + let crate_name = crate_name.as_str().unwrap(); + if let Some(krate) = day.by_crate.get(&*crate_name) { + let mut mem = 0; + let mut total = 0.0; + for phase in phases.members() { + let phase = phase.as_str().unwrap(); + if let Some(phase) = krate.get(phase) { + total += phase.time; + mem = max(mem, phase.rss.unwrap()); + } + } + data[crate_name] = object! { + "time" => total, + "rss" => mem + }; + } + } + } else { // group_by == GroupBy::Phase + for phase in phases.members() { + let phase = phase.as_str().unwrap(); + let mut total = 0.0; + let mut mem = 0; + for crate_name in crates.members() { + let crate_name = crate_name.as_str().unwrap(); + if let Some(krate) = day.by_crate.get(crate_name) { + if let Some(phase) = krate.get(phase) { + total += phase.time; + mem = max(mem, phase.rss.unwrap()); + } + } + } + data[phase] = object! { + "time" => total, + "rss" => mem + }; + } + } + + object! { + "date" => day.date.format(JS_DATE_FORMAT).to_string(), + "commit" => day.commit.clone(), + "data" => data + } +} + +// Expected fields on body: { +// kind: 'rustc' | 'benchmarks', +// start: Date, // optional +// end: Date, // optional +// crates: [str], // crate == benchmarks for benchmark mode +// phases: [str], +// group_by: 'crate' | 'phase', +// } +// crate (rustc only) or phase can be 'total' +fn get_data(req_body: Option, data: &InputData) -> Result { + let body = assert_request_body_present(req_body)?; + let kind = get_kind_from_body(&body)?; + let group_by = get_group_by_from_body(&body)?; + + let mut result = Vec::new(); + let mut first_idx = None; + let mut last_idx = 0; + // Iterate over date range. + let start_idx = start_idx(data.by_kind(kind), &start_date(&body, &data.last_date)); + let end_idx = end_idx(data.by_kind(kind), &end_date(&body, &data.last_date)); + for i in start_idx..(end_idx + 1) { + let today_data = get_data_for_date(&data.by_kind(kind)[i], &body["crates"], &body["phases"], group_by); + + if !today_data["data"].is_empty() { + last_idx = i - start_idx; + if first_idx == None { + first_idx = Some(i - start_idx); + } + } + + result.push(today_data); + } + + // Trim the data + let result = result.drain(first_idx.unwrap()..(last_idx+1)).collect::>(); + Ok(result.into()) +} + +fn handle_date(date_str: &str) -> Result { + match parse_date(date_str) { + Ok(date) => Ok(date), + Err(err) => Err(err).chain_err(|| format!("bad date: {:?}", date_str)) + } +} + +fn get_date_from_body(body: &JsonValue) -> Result { + if let Some(date_str) = body["date"].as_str() { + handle_date(date_str) + } else { + Err(format!("non-string date: {:?}", body["date"]).into()) + } +} + +// Expected fields on body: { +// kind: 'rustc' | 'benchmarks', +// date: Date +// } +fn get_tabular(req_body: Option, data: &InputData) -> Result { + let body = assert_request_body_present(req_body)?; + let kind = get_kind_from_body(&body)?; + let date = get_date_from_body(&body)?; + + let data = data.by_kind(kind); + let day = &data[end_idx(data, &date)]; + let mut by_crate = JsonValue::new_object(); + for (crate_name, krate) in &day.by_crate { + let mut krate_obj = JsonValue::new_object(); + for (phase_name, timing) in krate { + krate_obj[phase_name] = object! { + "percent" => timing.percent, + "time" => timing.time, + "rss" => timing.rss + }; + } + + by_crate[crate_name] = krate_obj; + } + + Ok(object! { + "date" => day.date.format(JS_DATE_FORMAT).to_string(), + "commit" => day.commit.clone(), + "data" => by_crate + }) +} + +// Expected fields on body: { +// kind: 'rustc' | 'benchmarks', +// dates: [Date], +// crates: [str], // crate == benchmarks for benchmark mode +// phases: [str], +// group_by: 'crate' | 'phase' +// } +// crate or phase can be 'total' +fn get_days(req_body: Option, data: &InputData) -> Result { + let body = assert_request_body_present(req_body)?; + let kind = get_kind_from_body(&body)?; + let group_by = get_group_by_from_body(&body)?; + + let data = data.by_kind(kind); + let mut result = JsonValue::new_array(); + for orig_date in body["dates"].members() { + let date = match handle_date(orig_date.as_str().unwrap()) { + Ok(date) => date, + Err(err) => { + println!("bad date {:?}: {:?}", orig_date, err); + continue; + } + }; + let day = get_data_for_date(&data[end_idx(data, &date)], &body["crates"], &body["phases"], group_by); + result.push(day)?; + } + Ok(result) +} + +// Expected fields on body: { +// kind: 'rustc' | 'benchmarks', +// start: Date, // optional +// end: Date, // optional +// crates: [str], // crate == benchmarks for benchmark mode +// phases: [str] +// } +// crate (rustc only) or phase can be 'total' +fn get_stats(req_body: Option, data: &InputData) -> Result { + let body = assert_request_body_present(req_body)?; + let kind = get_kind_from_body(&body)?; + + if kind == Kind::Benchmarks && body["crates"].contains("total") { + return Err("unexpected total crate with benchmarks kind".into()); + } + + let mut start_date = start_date(&body, &data.last_date); + let mut end_date = end_date(&body, &data.last_date); + + let mut counted = Vec::new(); + // Iterate over date range. + let start_idx = start_idx(data.by_kind(kind), &start_date); + let end_idx = end_idx(data.by_kind(kind), &end_date); + for i in start_idx..(end_idx + 1) { + let today_data = &data.by_kind(kind)[i]; + if !today_data.by_crate.is_empty() { + if counted.is_empty() { + start_date = today_data.date; + } + end_date = today_data.date; + counted.push(today_data); + } + } + + let mut crates = JsonValue::new_object(); + for crate_name in body["crates"].members() { + let crate_name = crate_name.as_str().unwrap(); + crates[crate_name] = mk_stats(&counted, crate_name, &body["phases"]); + } + + Ok(object! { + "startDate" => start_date.format(JS_DATE_FORMAT).to_string(), + "endDate" => end_date.format(JS_DATE_FORMAT).to_string(), + "crates" => crates + }) +} + +fn mk_stats(data: &[&TestRun], crate_name: &str, phases: &JsonValue) -> JsonValue { + let mut count = 0; + let mut first = 0; + let skip_list = data.iter().enumerate().map(|(i, d)| { + if d.by_crate.contains_key(crate_name) && !d.by_crate[crate_name].is_empty() { + if count == 0 { + first = i; + } + + count += 1; + false + } else { + true + } + }).collect::>(); + + if count == 0 { + return object! { + "first" => 0, + "last" => 0, + "min" => 0, + "max" => 0, + "mean" => 0, + "variance" => 0, + "trend" => 0, + "trend_b" => 0, + "n" => 0 + }; + } + + let sums = data.iter().enumerate().map(|(i, d)| { + if skip_list[i] { + return 0.0; + } + + let krate = &d.by_crate[crate_name]; + let mut sum = 0.0; + for phase in phases.members() { + sum += krate[phase.as_str().unwrap()].time; + } + sum + }).collect::>(); + + let first = sums[first]; + let last = *sums.last().unwrap(); + + let mut min = first; + let mut max = first; + let q1_idx = data.len() / 4; + let q4_idx = 3 * data.len() / 4; + let mut total = 0.0; + let mut q1_total = 0.0; + let mut q4_total = 0.0; + for i in 0..data.len() { + if skip_list[i] { + continue; + } + let cur = sums[i]; + total += cur; + min = min.min(cur); + max = max.max(cur); + if i < q1_idx { // Within the first quartile + q1_total += cur; + } + if i >= q4_idx { // Within the fourth quartile + q4_total += cur; + } + } + + // Calculate the variance + let mean = total / (count as f64); + let mut var_total = 0.0; + for i in 0..data.len() { + if skip_list[i] { + continue; + } + let diff = sums[i] - mean; + var_total += diff * diff; + } + let variance = var_total / ((count - 1) as f64); + + let trend = if count >= 10 && count == data.len() { + let q1_mean = q1_total / (q1_idx as f64); + let q4_mean = q4_total / ((data.len() - q4_idx) as f64); + 100.0 * ((q4_mean - q1_mean) / first) + } else { + 0.0 + }; + let trend_b = 100.0 * ((last - first) / first); + + object! { + "first" => first, + "last" => last, + "min" => min, + "max" => max, + "mean" => mean, + "variance" => variance, + "trend" => trend, + "trend_b" => trend_b, + "n" => count + } +} + +/// Pre and post processes the request and response to prepare it for the +/// handler passed in. Specifically, it parses JSON data from the request if +/// the request had greater than 0 length, and hands that into the handler. +/// Post-processing consists of applying access control headers and [potentially] +/// printing the error message. +fn get_handler(handler: F, req: &mut Request) -> IronResult + where F: Fn(Option, &InputData) -> Result { + use std::ops::Deref; + use iron::headers::{ContentType, AccessControlAllowOrigin}; + use iron::mime::{Mime, TopLevel, SubLevel}; + use iron::modifiers::Header; + + let rwlock = req.get::>().unwrap(); + let data = rwlock.read().unwrap(); + + let mut buf = String::new(); + let res = match req.body.read_to_string(&mut buf).unwrap() { + 0 => handler(None, data.deref()), + _ => handler(Some(json::parse(&buf).unwrap()), data.deref()) + }; + + let mut resp = match res { + Ok(json) => { + let mut resp = Response::with((status::Ok, json.dump())); + resp.set_mut(Header(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![])))); + resp + }, + Err(err) => { + // TODO: Print to stderr + println!("An error occurred: {:?}", err); + Response::with((status::InternalServerError, err.to_string())) + } + }; + resp.set_mut(Header(AccessControlAllowOrigin::Any)); + Ok(resp) +} + +pub fn start(data: InputData) { + let mut router = Router::new(); + + router.get("/summary", |r: &mut Request| get_handler(get_summary, r)); + router.get("/info", |r: &mut Request| get_handler(get_info, r)); + router.post("/data", |r: &mut Request| get_handler(get_data, r)); + router.post("/get_tabular", |r: &mut Request| get_handler(get_tabular, r)); + router.post("/get", |r: &mut Request| get_handler(get_days, r)); + router.post("/stats", |r: &mut Request| get_handler(get_stats, r)); + + let mut chain = Chain::new(router); + chain.link(State::::both(data)); + + Iron::new(chain).http(SERVER_ADDRESS).unwrap(); +} diff --git a/rs-backend/src/util.rs b/rs-backend/src/util.rs new file mode 100644 index 000000000..687cac1c9 --- /dev/null +++ b/rs-backend/src/util.rs @@ -0,0 +1,32 @@ +use load::TestRun; +use chrono::NaiveDateTime; + +/// Returns where the passed date is or should go in the sorted data slice. +fn get_insert_location(data: &[TestRun], date: &NaiveDateTime) -> ::std::result::Result { + data.binary_search_by(|probe| probe.date.cmp(date)) +} + +/// Return the start index for an iterator from the passed date to the index +/// returned by the companion function, `end_idx`. +pub fn start_idx(data: &[TestRun], date: &NaiveDateTime) -> usize { + match get_insert_location(data, date) { + Ok(idx) => idx, + Err(idx) => if idx != 0 { + idx - 1 + } else { + 0 + } + } +} + +/// Returns the end index for an iterator from the `start_idx()` to this date. +pub fn end_idx(data: &[TestRun], date: &NaiveDateTime) -> usize { + match get_insert_location(data, date) { + Ok(idx) => idx, + Err(idx) => if idx < data.len() { + idx + } else { + data.len() - 1 + } + } +}