├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── overhead.rs ├── examples └── nested.rs └── src ├── display.rs ├── internal.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | flame*.svg 4 | perf.* 5 | benches_*.txt 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | script: 11 | - cargo build --verbose 12 | - cargo test --verbose 13 | - cargo test --release --verbose 14 | - cargo doc -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi 0.3.9", 12 | ] 13 | 14 | [[package]] 15 | name = "async-channel" 16 | version = "1.6.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" 19 | dependencies = [ 20 | "concurrent-queue", 21 | "event-listener", 22 | "futures-core", 23 | ] 24 | 25 | [[package]] 26 | name = "async-executor" 27 | version = "1.4.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" 30 | dependencies = [ 31 | "async-task", 32 | "concurrent-queue", 33 | "fastrand", 34 | "futures-lite", 35 | "once_cell", 36 | "slab", 37 | ] 38 | 39 | [[package]] 40 | name = "async-global-executor" 41 | version = "2.0.2" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" 44 | dependencies = [ 45 | "async-channel", 46 | "async-executor", 47 | "async-io", 48 | "async-mutex", 49 | "blocking", 50 | "futures-lite", 51 | "num_cpus", 52 | "once_cell", 53 | ] 54 | 55 | [[package]] 56 | name = "async-io" 57 | version = "1.6.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" 60 | dependencies = [ 61 | "concurrent-queue", 62 | "futures-lite", 63 | "libc", 64 | "log", 65 | "once_cell", 66 | "parking", 67 | "polling", 68 | "slab", 69 | "socket2", 70 | "waker-fn", 71 | "winapi 0.3.9", 72 | ] 73 | 74 | [[package]] 75 | name = "async-lock" 76 | version = "2.4.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" 79 | dependencies = [ 80 | "event-listener", 81 | ] 82 | 83 | [[package]] 84 | name = "async-mutex" 85 | version = "1.4.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" 88 | dependencies = [ 89 | "event-listener", 90 | ] 91 | 92 | [[package]] 93 | name = "async-std" 94 | version = "1.10.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" 97 | dependencies = [ 98 | "async-channel", 99 | "async-global-executor", 100 | "async-io", 101 | "async-lock", 102 | "crossbeam-utils 0.8.7", 103 | "futures-channel", 104 | "futures-core", 105 | "futures-io", 106 | "futures-lite", 107 | "gloo-timers", 108 | "kv-log-macro", 109 | "log", 110 | "memchr", 111 | "num_cpus", 112 | "once_cell", 113 | "pin-project-lite", 114 | "pin-utils", 115 | "slab", 116 | "wasm-bindgen-futures", 117 | ] 118 | 119 | [[package]] 120 | name = "async-task" 121 | version = "4.1.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" 124 | 125 | [[package]] 126 | name = "atomic-waker" 127 | version = "1.0.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" 130 | 131 | [[package]] 132 | name = "atty" 133 | version = "0.2.14" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 136 | dependencies = [ 137 | "hermit-abi", 138 | "libc", 139 | "winapi 0.3.9", 140 | ] 141 | 142 | [[package]] 143 | name = "autocfg" 144 | version = "1.0.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 147 | 148 | [[package]] 149 | name = "bitflags" 150 | version = "1.3.2" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 153 | 154 | [[package]] 155 | name = "blocking" 156 | version = "1.1.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" 159 | dependencies = [ 160 | "async-channel", 161 | "async-task", 162 | "atomic-waker", 163 | "fastrand", 164 | "futures-lite", 165 | "once_cell", 166 | ] 167 | 168 | [[package]] 169 | name = "bstr" 170 | version = "0.2.17" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 173 | dependencies = [ 174 | "lazy_static", 175 | "memchr", 176 | "regex-automata", 177 | "serde", 178 | ] 179 | 180 | [[package]] 181 | name = "bumpalo" 182 | version = "3.9.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 185 | 186 | [[package]] 187 | name = "byteorder" 188 | version = "1.4.3" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 191 | 192 | [[package]] 193 | name = "bytes" 194 | version = "0.4.12" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 197 | dependencies = [ 198 | "byteorder", 199 | "iovec", 200 | ] 201 | 202 | [[package]] 203 | name = "bytes" 204 | version = "1.1.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 207 | 208 | [[package]] 209 | name = "cache-padded" 210 | version = "1.2.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" 213 | 214 | [[package]] 215 | name = "cast" 216 | version = "0.2.7" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" 219 | dependencies = [ 220 | "rustc_version 0.4.0", 221 | ] 222 | 223 | [[package]] 224 | name = "cc" 225 | version = "1.0.72" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 228 | 229 | [[package]] 230 | name = "cfg-if" 231 | version = "0.1.10" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 234 | 235 | [[package]] 236 | name = "cfg-if" 237 | version = "1.0.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 240 | 241 | [[package]] 242 | name = "clap" 243 | version = "2.34.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 246 | dependencies = [ 247 | "bitflags", 248 | "textwrap", 249 | "unicode-width", 250 | ] 251 | 252 | [[package]] 253 | name = "cloudabi" 254 | version = "0.0.3" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 257 | dependencies = [ 258 | "bitflags", 259 | ] 260 | 261 | [[package]] 262 | name = "concurrent-queue" 263 | version = "1.2.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" 266 | dependencies = [ 267 | "cache-padded", 268 | ] 269 | 270 | [[package]] 271 | name = "criterion" 272 | version = "0.3.5" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" 275 | dependencies = [ 276 | "atty", 277 | "cast", 278 | "clap", 279 | "criterion-plot", 280 | "csv", 281 | "itertools", 282 | "lazy_static", 283 | "num-traits", 284 | "oorandom", 285 | "plotters", 286 | "rayon", 287 | "regex", 288 | "serde", 289 | "serde_cbor", 290 | "serde_derive", 291 | "serde_json", 292 | "tinytemplate", 293 | "walkdir", 294 | ] 295 | 296 | [[package]] 297 | name = "criterion-plot" 298 | version = "0.4.4" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" 301 | dependencies = [ 302 | "cast", 303 | "itertools", 304 | ] 305 | 306 | [[package]] 307 | name = "crossbeam-channel" 308 | version = "0.5.2" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" 311 | dependencies = [ 312 | "cfg-if 1.0.0", 313 | "crossbeam-utils 0.8.7", 314 | ] 315 | 316 | [[package]] 317 | name = "crossbeam-deque" 318 | version = "0.7.4" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" 321 | dependencies = [ 322 | "crossbeam-epoch 0.8.2", 323 | "crossbeam-utils 0.7.2", 324 | "maybe-uninit", 325 | ] 326 | 327 | [[package]] 328 | name = "crossbeam-deque" 329 | version = "0.8.1" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 332 | dependencies = [ 333 | "cfg-if 1.0.0", 334 | "crossbeam-epoch 0.9.7", 335 | "crossbeam-utils 0.8.7", 336 | ] 337 | 338 | [[package]] 339 | name = "crossbeam-epoch" 340 | version = "0.8.2" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 343 | dependencies = [ 344 | "autocfg", 345 | "cfg-if 0.1.10", 346 | "crossbeam-utils 0.7.2", 347 | "lazy_static", 348 | "maybe-uninit", 349 | "memoffset 0.5.6", 350 | "scopeguard", 351 | ] 352 | 353 | [[package]] 354 | name = "crossbeam-epoch" 355 | version = "0.9.7" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" 358 | dependencies = [ 359 | "cfg-if 1.0.0", 360 | "crossbeam-utils 0.8.7", 361 | "lazy_static", 362 | "memoffset 0.6.5", 363 | "scopeguard", 364 | ] 365 | 366 | [[package]] 367 | name = "crossbeam-queue" 368 | version = "0.2.3" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" 371 | dependencies = [ 372 | "cfg-if 0.1.10", 373 | "crossbeam-utils 0.7.2", 374 | "maybe-uninit", 375 | ] 376 | 377 | [[package]] 378 | name = "crossbeam-utils" 379 | version = "0.7.2" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 382 | dependencies = [ 383 | "autocfg", 384 | "cfg-if 0.1.10", 385 | "lazy_static", 386 | ] 387 | 388 | [[package]] 389 | name = "crossbeam-utils" 390 | version = "0.8.7" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" 393 | dependencies = [ 394 | "cfg-if 1.0.0", 395 | "lazy_static", 396 | ] 397 | 398 | [[package]] 399 | name = "csv" 400 | version = "1.1.6" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 403 | dependencies = [ 404 | "bstr", 405 | "csv-core", 406 | "itoa 0.4.8", 407 | "ryu", 408 | "serde", 409 | ] 410 | 411 | [[package]] 412 | name = "csv-core" 413 | version = "0.1.10" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 416 | dependencies = [ 417 | "memchr", 418 | ] 419 | 420 | [[package]] 421 | name = "ctor" 422 | version = "0.1.21" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 425 | dependencies = [ 426 | "quote", 427 | "syn", 428 | ] 429 | 430 | [[package]] 431 | name = "either" 432 | version = "1.6.1" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 435 | 436 | [[package]] 437 | name = "event-listener" 438 | version = "2.5.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" 441 | 442 | [[package]] 443 | name = "fastrand" 444 | version = "1.7.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 447 | dependencies = [ 448 | "instant", 449 | ] 450 | 451 | [[package]] 452 | name = "fnv" 453 | version = "1.0.7" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 456 | 457 | [[package]] 458 | name = "fuchsia-zircon" 459 | version = "0.3.3" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 462 | dependencies = [ 463 | "bitflags", 464 | "fuchsia-zircon-sys", 465 | ] 466 | 467 | [[package]] 468 | name = "fuchsia-zircon-sys" 469 | version = "0.3.3" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 472 | 473 | [[package]] 474 | name = "futures" 475 | version = "0.1.31" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" 478 | 479 | [[package]] 480 | name = "futures" 481 | version = "0.3.21" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 484 | dependencies = [ 485 | "futures-channel", 486 | "futures-core", 487 | "futures-executor", 488 | "futures-io", 489 | "futures-sink", 490 | "futures-task", 491 | "futures-util", 492 | ] 493 | 494 | [[package]] 495 | name = "futures-channel" 496 | version = "0.3.21" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 499 | dependencies = [ 500 | "futures-core", 501 | "futures-sink", 502 | ] 503 | 504 | [[package]] 505 | name = "futures-core" 506 | version = "0.3.21" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 509 | 510 | [[package]] 511 | name = "futures-executor" 512 | version = "0.3.21" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 515 | dependencies = [ 516 | "futures-core", 517 | "futures-task", 518 | "futures-util", 519 | ] 520 | 521 | [[package]] 522 | name = "futures-io" 523 | version = "0.3.21" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 526 | 527 | [[package]] 528 | name = "futures-lite" 529 | version = "1.12.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" 532 | dependencies = [ 533 | "fastrand", 534 | "futures-core", 535 | "futures-io", 536 | "memchr", 537 | "parking", 538 | "pin-project-lite", 539 | "waker-fn", 540 | ] 541 | 542 | [[package]] 543 | name = "futures-macro" 544 | version = "0.3.21" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 547 | dependencies = [ 548 | "proc-macro2", 549 | "quote", 550 | "syn", 551 | ] 552 | 553 | [[package]] 554 | name = "futures-sink" 555 | version = "0.3.21" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 558 | 559 | [[package]] 560 | name = "futures-task" 561 | version = "0.3.21" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 564 | 565 | [[package]] 566 | name = "futures-util" 567 | version = "0.3.21" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 570 | dependencies = [ 571 | "futures-channel", 572 | "futures-core", 573 | "futures-io", 574 | "futures-macro", 575 | "futures-sink", 576 | "futures-task", 577 | "memchr", 578 | "pin-project-lite", 579 | "pin-utils", 580 | "slab", 581 | ] 582 | 583 | [[package]] 584 | name = "gloo-timers" 585 | version = "0.2.3" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" 588 | dependencies = [ 589 | "futures-channel", 590 | "futures-core", 591 | "js-sys", 592 | "wasm-bindgen", 593 | ] 594 | 595 | [[package]] 596 | name = "half" 597 | version = "1.8.2" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 600 | 601 | [[package]] 602 | name = "hermit-abi" 603 | version = "0.1.19" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 606 | dependencies = [ 607 | "libc", 608 | ] 609 | 610 | [[package]] 611 | name = "indoc" 612 | version = "1.0.3" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" 615 | dependencies = [ 616 | "unindent", 617 | ] 618 | 619 | [[package]] 620 | name = "instant" 621 | version = "0.1.12" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 624 | dependencies = [ 625 | "cfg-if 1.0.0", 626 | ] 627 | 628 | [[package]] 629 | name = "iovec" 630 | version = "0.1.4" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 633 | dependencies = [ 634 | "libc", 635 | ] 636 | 637 | [[package]] 638 | name = "itertools" 639 | version = "0.10.3" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 642 | dependencies = [ 643 | "either", 644 | ] 645 | 646 | [[package]] 647 | name = "itoa" 648 | version = "0.4.8" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 651 | 652 | [[package]] 653 | name = "itoa" 654 | version = "1.0.1" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 657 | 658 | [[package]] 659 | name = "js-sys" 660 | version = "0.3.56" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 663 | dependencies = [ 664 | "wasm-bindgen", 665 | ] 666 | 667 | [[package]] 668 | name = "kernel32-sys" 669 | version = "0.2.2" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 672 | dependencies = [ 673 | "winapi 0.2.8", 674 | "winapi-build", 675 | ] 676 | 677 | [[package]] 678 | name = "kv-log-macro" 679 | version = "1.0.7" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 682 | dependencies = [ 683 | "log", 684 | ] 685 | 686 | [[package]] 687 | name = "lazy_static" 688 | version = "1.4.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 691 | 692 | [[package]] 693 | name = "libc" 694 | version = "0.2.117" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" 697 | 698 | [[package]] 699 | name = "lock_api" 700 | version = "0.3.4" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 703 | dependencies = [ 704 | "scopeguard", 705 | ] 706 | 707 | [[package]] 708 | name = "lock_api" 709 | version = "0.4.6" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 712 | dependencies = [ 713 | "scopeguard", 714 | ] 715 | 716 | [[package]] 717 | name = "log" 718 | version = "0.4.14" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 721 | dependencies = [ 722 | "cfg-if 1.0.0", 723 | "value-bag", 724 | ] 725 | 726 | [[package]] 727 | name = "mach" 728 | version = "0.3.2" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 731 | dependencies = [ 732 | "libc", 733 | ] 734 | 735 | [[package]] 736 | name = "matchers" 737 | version = "0.1.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 740 | dependencies = [ 741 | "regex-automata", 742 | ] 743 | 744 | [[package]] 745 | name = "maybe-uninit" 746 | version = "2.0.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 749 | 750 | [[package]] 751 | name = "memchr" 752 | version = "2.4.1" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 755 | 756 | [[package]] 757 | name = "memoffset" 758 | version = "0.5.6" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" 761 | dependencies = [ 762 | "autocfg", 763 | ] 764 | 765 | [[package]] 766 | name = "memoffset" 767 | version = "0.6.5" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 770 | dependencies = [ 771 | "autocfg", 772 | ] 773 | 774 | [[package]] 775 | name = "mio" 776 | version = "0.6.23" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 779 | dependencies = [ 780 | "cfg-if 0.1.10", 781 | "fuchsia-zircon", 782 | "fuchsia-zircon-sys", 783 | "iovec", 784 | "kernel32-sys", 785 | "libc", 786 | "log", 787 | "miow 0.2.2", 788 | "net2", 789 | "slab", 790 | "winapi 0.2.8", 791 | ] 792 | 793 | [[package]] 794 | name = "mio" 795 | version = "0.7.14" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 798 | dependencies = [ 799 | "libc", 800 | "log", 801 | "miow 0.3.7", 802 | "ntapi", 803 | "winapi 0.3.9", 804 | ] 805 | 806 | [[package]] 807 | name = "mio-uds" 808 | version = "0.6.8" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 811 | dependencies = [ 812 | "iovec", 813 | "libc", 814 | "mio 0.6.23", 815 | ] 816 | 817 | [[package]] 818 | name = "miow" 819 | version = "0.2.2" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 822 | dependencies = [ 823 | "kernel32-sys", 824 | "net2", 825 | "winapi 0.2.8", 826 | "ws2_32-sys", 827 | ] 828 | 829 | [[package]] 830 | name = "miow" 831 | version = "0.3.7" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 834 | dependencies = [ 835 | "winapi 0.3.9", 836 | ] 837 | 838 | [[package]] 839 | name = "net2" 840 | version = "0.2.37" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 843 | dependencies = [ 844 | "cfg-if 0.1.10", 845 | "libc", 846 | "winapi 0.3.9", 847 | ] 848 | 849 | [[package]] 850 | name = "ntapi" 851 | version = "0.3.6" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 854 | dependencies = [ 855 | "winapi 0.3.9", 856 | ] 857 | 858 | [[package]] 859 | name = "num-traits" 860 | version = "0.2.14" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 863 | dependencies = [ 864 | "autocfg", 865 | ] 866 | 867 | [[package]] 868 | name = "num_cpus" 869 | version = "1.13.1" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 872 | dependencies = [ 873 | "hermit-abi", 874 | "libc", 875 | ] 876 | 877 | [[package]] 878 | name = "num_threads" 879 | version = "0.1.3" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" 882 | dependencies = [ 883 | "libc", 884 | ] 885 | 886 | [[package]] 887 | name = "once_cell" 888 | version = "1.9.0" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 891 | 892 | [[package]] 893 | name = "oorandom" 894 | version = "11.1.3" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 897 | 898 | [[package]] 899 | name = "parking" 900 | version = "2.0.0" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 903 | 904 | [[package]] 905 | name = "parking_lot" 906 | version = "0.9.0" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" 909 | dependencies = [ 910 | "lock_api 0.3.4", 911 | "parking_lot_core 0.6.2", 912 | "rustc_version 0.2.3", 913 | ] 914 | 915 | [[package]] 916 | name = "parking_lot" 917 | version = "0.11.2" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 920 | dependencies = [ 921 | "instant", 922 | "lock_api 0.4.6", 923 | "parking_lot_core 0.8.5", 924 | ] 925 | 926 | [[package]] 927 | name = "parking_lot_core" 928 | version = "0.6.2" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" 931 | dependencies = [ 932 | "cfg-if 0.1.10", 933 | "cloudabi", 934 | "libc", 935 | "redox_syscall 0.1.57", 936 | "rustc_version 0.2.3", 937 | "smallvec 0.6.14", 938 | "winapi 0.3.9", 939 | ] 940 | 941 | [[package]] 942 | name = "parking_lot_core" 943 | version = "0.8.5" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 946 | dependencies = [ 947 | "cfg-if 1.0.0", 948 | "instant", 949 | "libc", 950 | "redox_syscall 0.2.10", 951 | "smallvec 1.8.0", 952 | "winapi 0.3.9", 953 | ] 954 | 955 | [[package]] 956 | name = "pin-project" 957 | version = "1.0.10" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" 960 | dependencies = [ 961 | "pin-project-internal", 962 | ] 963 | 964 | [[package]] 965 | name = "pin-project-internal" 966 | version = "1.0.10" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" 969 | dependencies = [ 970 | "proc-macro2", 971 | "quote", 972 | "syn", 973 | ] 974 | 975 | [[package]] 976 | name = "pin-project-lite" 977 | version = "0.2.8" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 980 | 981 | [[package]] 982 | name = "pin-utils" 983 | version = "0.1.0" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 986 | 987 | [[package]] 988 | name = "plotters" 989 | version = "0.3.1" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" 992 | dependencies = [ 993 | "num-traits", 994 | "plotters-backend", 995 | "plotters-svg", 996 | "wasm-bindgen", 997 | "web-sys", 998 | ] 999 | 1000 | [[package]] 1001 | name = "plotters-backend" 1002 | version = "0.3.2" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" 1005 | 1006 | [[package]] 1007 | name = "plotters-svg" 1008 | version = "0.3.1" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" 1011 | dependencies = [ 1012 | "plotters-backend", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "polling" 1017 | version = "2.2.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" 1020 | dependencies = [ 1021 | "cfg-if 1.0.0", 1022 | "libc", 1023 | "log", 1024 | "wepoll-ffi", 1025 | "winapi 0.3.9", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "proc-macro2" 1030 | version = "1.0.36" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 1033 | dependencies = [ 1034 | "unicode-xid", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "quanta" 1039 | version = "0.9.3" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8" 1042 | dependencies = [ 1043 | "crossbeam-utils 0.8.7", 1044 | "libc", 1045 | "mach", 1046 | "once_cell", 1047 | "raw-cpuid", 1048 | "wasi", 1049 | "web-sys", 1050 | "winapi 0.3.9", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "quote" 1055 | version = "1.0.15" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 1058 | dependencies = [ 1059 | "proc-macro2", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "raw-cpuid" 1064 | version = "10.2.0" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "929f54e29691d4e6a9cc558479de70db7aa3d98cd6fe7ab86d7507aa2886b9d2" 1067 | dependencies = [ 1068 | "bitflags", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "rayon" 1073 | version = "1.5.1" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" 1076 | dependencies = [ 1077 | "autocfg", 1078 | "crossbeam-deque 0.8.1", 1079 | "either", 1080 | "rayon-core", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "rayon-core" 1085 | version = "1.9.1" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" 1088 | dependencies = [ 1089 | "crossbeam-channel", 1090 | "crossbeam-deque 0.8.1", 1091 | "crossbeam-utils 0.8.7", 1092 | "lazy_static", 1093 | "num_cpus", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "redox_syscall" 1098 | version = "0.1.57" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 1101 | 1102 | [[package]] 1103 | name = "redox_syscall" 1104 | version = "0.2.10" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1107 | dependencies = [ 1108 | "bitflags", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "regex" 1113 | version = "1.5.4" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 1116 | dependencies = [ 1117 | "regex-syntax", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "regex-automata" 1122 | version = "0.1.10" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1125 | dependencies = [ 1126 | "regex-syntax", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "regex-syntax" 1131 | version = "0.6.25" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1134 | 1135 | [[package]] 1136 | name = "reqray" 1137 | version = "0.4.4-alpha.0" 1138 | dependencies = [ 1139 | "async-std", 1140 | "criterion", 1141 | "futures 0.3.21", 1142 | "indoc", 1143 | "quanta", 1144 | "tokio 1.16.1", 1145 | "tracing", 1146 | "tracing-appender", 1147 | "tracing-futures", 1148 | "tracing-subscriber", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "rustc_version" 1153 | version = "0.2.3" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1156 | dependencies = [ 1157 | "semver 0.9.0", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "rustc_version" 1162 | version = "0.4.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1165 | dependencies = [ 1166 | "semver 1.0.5", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "ryu" 1171 | version = "1.0.9" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 1174 | 1175 | [[package]] 1176 | name = "same-file" 1177 | version = "1.0.6" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1180 | dependencies = [ 1181 | "winapi-util", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "scopeguard" 1186 | version = "1.1.0" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1189 | 1190 | [[package]] 1191 | name = "semver" 1192 | version = "0.9.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1195 | dependencies = [ 1196 | "semver-parser", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "semver" 1201 | version = "1.0.5" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" 1204 | 1205 | [[package]] 1206 | name = "semver-parser" 1207 | version = "0.7.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1210 | 1211 | [[package]] 1212 | name = "serde" 1213 | version = "1.0.136" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 1216 | 1217 | [[package]] 1218 | name = "serde_cbor" 1219 | version = "0.11.2" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" 1222 | dependencies = [ 1223 | "half", 1224 | "serde", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "serde_derive" 1229 | version = "1.0.136" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 1232 | dependencies = [ 1233 | "proc-macro2", 1234 | "quote", 1235 | "syn", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "serde_json" 1240 | version = "1.0.78" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" 1243 | dependencies = [ 1244 | "itoa 1.0.1", 1245 | "ryu", 1246 | "serde", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "sharded-slab" 1251 | version = "0.1.4" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1254 | dependencies = [ 1255 | "lazy_static", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "signal-hook-registry" 1260 | version = "1.4.0" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1263 | dependencies = [ 1264 | "libc", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "slab" 1269 | version = "0.4.5" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1272 | 1273 | [[package]] 1274 | name = "smallvec" 1275 | version = "0.6.14" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" 1278 | dependencies = [ 1279 | "maybe-uninit", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "smallvec" 1284 | version = "1.8.0" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1287 | 1288 | [[package]] 1289 | name = "socket2" 1290 | version = "0.4.4" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1293 | dependencies = [ 1294 | "libc", 1295 | "winapi 0.3.9", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "syn" 1300 | version = "1.0.86" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 1303 | dependencies = [ 1304 | "proc-macro2", 1305 | "quote", 1306 | "unicode-xid", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "textwrap" 1311 | version = "0.11.0" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1314 | dependencies = [ 1315 | "unicode-width", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "thread_local" 1320 | version = "1.1.4" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1323 | dependencies = [ 1324 | "once_cell", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "time" 1329 | version = "0.3.7" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" 1332 | dependencies = [ 1333 | "itoa 1.0.1", 1334 | "libc", 1335 | "num_threads", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "tinytemplate" 1340 | version = "1.2.1" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 1343 | dependencies = [ 1344 | "serde", 1345 | "serde_json", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "tokio" 1350 | version = "0.1.22" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" 1353 | dependencies = [ 1354 | "bytes 0.4.12", 1355 | "futures 0.1.31", 1356 | "mio 0.6.23", 1357 | "num_cpus", 1358 | "tokio-codec", 1359 | "tokio-current-thread", 1360 | "tokio-executor", 1361 | "tokio-fs", 1362 | "tokio-io", 1363 | "tokio-reactor", 1364 | "tokio-sync", 1365 | "tokio-tcp", 1366 | "tokio-threadpool", 1367 | "tokio-timer", 1368 | "tokio-udp", 1369 | "tokio-uds", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "tokio" 1374 | version = "1.16.1" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" 1377 | dependencies = [ 1378 | "bytes 1.1.0", 1379 | "libc", 1380 | "memchr", 1381 | "mio 0.7.14", 1382 | "num_cpus", 1383 | "once_cell", 1384 | "parking_lot 0.11.2", 1385 | "pin-project-lite", 1386 | "signal-hook-registry", 1387 | "tokio-macros", 1388 | "winapi 0.3.9", 1389 | ] 1390 | 1391 | [[package]] 1392 | name = "tokio-codec" 1393 | version = "0.1.2" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" 1396 | dependencies = [ 1397 | "bytes 0.4.12", 1398 | "futures 0.1.31", 1399 | "tokio-io", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "tokio-current-thread" 1404 | version = "0.1.7" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" 1407 | dependencies = [ 1408 | "futures 0.1.31", 1409 | "tokio-executor", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "tokio-executor" 1414 | version = "0.1.10" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" 1417 | dependencies = [ 1418 | "crossbeam-utils 0.7.2", 1419 | "futures 0.1.31", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "tokio-fs" 1424 | version = "0.1.7" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" 1427 | dependencies = [ 1428 | "futures 0.1.31", 1429 | "tokio-io", 1430 | "tokio-threadpool", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "tokio-io" 1435 | version = "0.1.13" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" 1438 | dependencies = [ 1439 | "bytes 0.4.12", 1440 | "futures 0.1.31", 1441 | "log", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "tokio-macros" 1446 | version = "1.7.0" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 1449 | dependencies = [ 1450 | "proc-macro2", 1451 | "quote", 1452 | "syn", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "tokio-reactor" 1457 | version = "0.1.12" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" 1460 | dependencies = [ 1461 | "crossbeam-utils 0.7.2", 1462 | "futures 0.1.31", 1463 | "lazy_static", 1464 | "log", 1465 | "mio 0.6.23", 1466 | "num_cpus", 1467 | "parking_lot 0.9.0", 1468 | "slab", 1469 | "tokio-executor", 1470 | "tokio-io", 1471 | "tokio-sync", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "tokio-sync" 1476 | version = "0.1.8" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" 1479 | dependencies = [ 1480 | "fnv", 1481 | "futures 0.1.31", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "tokio-tcp" 1486 | version = "0.1.4" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" 1489 | dependencies = [ 1490 | "bytes 0.4.12", 1491 | "futures 0.1.31", 1492 | "iovec", 1493 | "mio 0.6.23", 1494 | "tokio-io", 1495 | "tokio-reactor", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "tokio-threadpool" 1500 | version = "0.1.18" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" 1503 | dependencies = [ 1504 | "crossbeam-deque 0.7.4", 1505 | "crossbeam-queue", 1506 | "crossbeam-utils 0.7.2", 1507 | "futures 0.1.31", 1508 | "lazy_static", 1509 | "log", 1510 | "num_cpus", 1511 | "slab", 1512 | "tokio-executor", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "tokio-timer" 1517 | version = "0.2.13" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" 1520 | dependencies = [ 1521 | "crossbeam-utils 0.7.2", 1522 | "futures 0.1.31", 1523 | "slab", 1524 | "tokio-executor", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "tokio-udp" 1529 | version = "0.1.6" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" 1532 | dependencies = [ 1533 | "bytes 0.4.12", 1534 | "futures 0.1.31", 1535 | "log", 1536 | "mio 0.6.23", 1537 | "tokio-codec", 1538 | "tokio-io", 1539 | "tokio-reactor", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "tokio-uds" 1544 | version = "0.2.7" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" 1547 | dependencies = [ 1548 | "bytes 0.4.12", 1549 | "futures 0.1.31", 1550 | "iovec", 1551 | "libc", 1552 | "log", 1553 | "mio 0.6.23", 1554 | "mio-uds", 1555 | "tokio-codec", 1556 | "tokio-io", 1557 | "tokio-reactor", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "tracing" 1562 | version = "0.1.30" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" 1565 | dependencies = [ 1566 | "cfg-if 1.0.0", 1567 | "pin-project-lite", 1568 | "tracing-attributes", 1569 | "tracing-core", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "tracing-appender" 1574 | version = "0.2.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "94571df2eae3ed4353815ea5a90974a594a1792d8782ff2cbcc9392d1101f366" 1577 | dependencies = [ 1578 | "crossbeam-channel", 1579 | "time", 1580 | "tracing-subscriber", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "tracing-attributes" 1585 | version = "0.1.19" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" 1588 | dependencies = [ 1589 | "proc-macro2", 1590 | "quote", 1591 | "syn", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "tracing-core" 1596 | version = "0.1.22" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" 1599 | dependencies = [ 1600 | "lazy_static", 1601 | "valuable", 1602 | ] 1603 | 1604 | [[package]] 1605 | name = "tracing-futures" 1606 | version = "0.2.5" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 1609 | dependencies = [ 1610 | "futures 0.3.21", 1611 | "futures-task", 1612 | "pin-project", 1613 | "tokio 0.1.22", 1614 | "tracing", 1615 | ] 1616 | 1617 | [[package]] 1618 | name = "tracing-log" 1619 | version = "0.1.2" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" 1622 | dependencies = [ 1623 | "lazy_static", 1624 | "log", 1625 | "tracing-core", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "tracing-subscriber" 1630 | version = "0.3.8" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa" 1633 | dependencies = [ 1634 | "ansi_term", 1635 | "lazy_static", 1636 | "matchers", 1637 | "regex", 1638 | "sharded-slab", 1639 | "smallvec 1.8.0", 1640 | "thread_local", 1641 | "tracing", 1642 | "tracing-core", 1643 | "tracing-log", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "unicode-width" 1648 | version = "0.1.9" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1651 | 1652 | [[package]] 1653 | name = "unicode-xid" 1654 | version = "0.2.2" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1657 | 1658 | [[package]] 1659 | name = "unindent" 1660 | version = "0.1.7" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" 1663 | 1664 | [[package]] 1665 | name = "valuable" 1666 | version = "0.1.0" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1669 | 1670 | [[package]] 1671 | name = "value-bag" 1672 | version = "1.0.0-alpha.8" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" 1675 | dependencies = [ 1676 | "ctor", 1677 | "version_check", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "version_check" 1682 | version = "0.9.4" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1685 | 1686 | [[package]] 1687 | name = "waker-fn" 1688 | version = "1.1.0" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 1691 | 1692 | [[package]] 1693 | name = "walkdir" 1694 | version = "2.3.2" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1697 | dependencies = [ 1698 | "same-file", 1699 | "winapi 0.3.9", 1700 | "winapi-util", 1701 | ] 1702 | 1703 | [[package]] 1704 | name = "wasi" 1705 | version = "0.10.2+wasi-snapshot-preview1" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1708 | 1709 | [[package]] 1710 | name = "wasm-bindgen" 1711 | version = "0.2.79" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 1714 | dependencies = [ 1715 | "cfg-if 1.0.0", 1716 | "wasm-bindgen-macro", 1717 | ] 1718 | 1719 | [[package]] 1720 | name = "wasm-bindgen-backend" 1721 | version = "0.2.79" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 1724 | dependencies = [ 1725 | "bumpalo", 1726 | "lazy_static", 1727 | "log", 1728 | "proc-macro2", 1729 | "quote", 1730 | "syn", 1731 | "wasm-bindgen-shared", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "wasm-bindgen-futures" 1736 | version = "0.4.29" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" 1739 | dependencies = [ 1740 | "cfg-if 1.0.0", 1741 | "js-sys", 1742 | "wasm-bindgen", 1743 | "web-sys", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "wasm-bindgen-macro" 1748 | version = "0.2.79" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 1751 | dependencies = [ 1752 | "quote", 1753 | "wasm-bindgen-macro-support", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "wasm-bindgen-macro-support" 1758 | version = "0.2.79" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 1761 | dependencies = [ 1762 | "proc-macro2", 1763 | "quote", 1764 | "syn", 1765 | "wasm-bindgen-backend", 1766 | "wasm-bindgen-shared", 1767 | ] 1768 | 1769 | [[package]] 1770 | name = "wasm-bindgen-shared" 1771 | version = "0.2.79" 1772 | source = "registry+https://github.com/rust-lang/crates.io-index" 1773 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 1774 | 1775 | [[package]] 1776 | name = "web-sys" 1777 | version = "0.3.56" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" 1780 | dependencies = [ 1781 | "js-sys", 1782 | "wasm-bindgen", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "wepoll-ffi" 1787 | version = "0.1.2" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 1790 | dependencies = [ 1791 | "cc", 1792 | ] 1793 | 1794 | [[package]] 1795 | name = "winapi" 1796 | version = "0.2.8" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1799 | 1800 | [[package]] 1801 | name = "winapi" 1802 | version = "0.3.9" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1805 | dependencies = [ 1806 | "winapi-i686-pc-windows-gnu", 1807 | "winapi-x86_64-pc-windows-gnu", 1808 | ] 1809 | 1810 | [[package]] 1811 | name = "winapi-build" 1812 | version = "0.1.1" 1813 | source = "registry+https://github.com/rust-lang/crates.io-index" 1814 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1815 | 1816 | [[package]] 1817 | name = "winapi-i686-pc-windows-gnu" 1818 | version = "0.4.0" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1821 | 1822 | [[package]] 1823 | name = "winapi-util" 1824 | version = "0.1.5" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1827 | dependencies = [ 1828 | "winapi 0.3.9", 1829 | ] 1830 | 1831 | [[package]] 1832 | name = "winapi-x86_64-pc-windows-gnu" 1833 | version = "0.4.0" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1836 | 1837 | [[package]] 1838 | name = "ws2_32-sys" 1839 | version = "0.2.1" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1842 | dependencies = [ 1843 | "winapi 0.2.8", 1844 | "winapi-build", 1845 | ] 1846 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reqray" 3 | description = "Log 'request x-rays' for rust programs instrumented with `tracing`." 4 | version = "0.4.4-alpha.0" 5 | authors = ["Peter Kolloch "] 6 | edition = "2018" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/kolloch/reqray" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | tracing = "0.1" 14 | tracing-subscriber = { version = "0.3", features = ["registry"] } 15 | quanta = "0.9" 16 | 17 | [dev-dependencies] 18 | criterion = "0.3" 19 | indoc = "1.0" 20 | tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } 21 | tracing-futures = { version = "0.2", features = ["tokio", "futures-03", "std-future"]} 22 | futures = "0.3" 23 | tokio = { version = "1.15.0", features = ["full"] } 24 | async-std = "1.8" 25 | tracing-appender = "0.2.0" 26 | 27 | [[bench]] 28 | name = "overhead" 29 | harness = false 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reqray 2 | 3 | [![Build Status](https://api.travis-ci.com/kolloch/reqray.svg?branch=main&status=started)](https://travis-ci.com/kolloch/reqray) 4 | [![Crate](https://img.shields.io/crates/v/reqray.svg)](https://crates.io/crates/reqray) 5 | [![Docs](https://docs.rs/reqray/badge.svg)](https://docs.rs/reqray) 6 | 7 | Log "request x-rays" for rust programs instrumented with [tracing](https://github.com/tokio-rs/tracing). This 8 | includes aggregated wall/own times as frequently found in flame graphs in a human-friendly text format. 9 | 10 | To deploy it, you don't need some complicated services, just some local code added to your instrumented program. 11 | 12 | This makes answers to these question often trivial to answer: 13 | 14 | * What part of the request takes the most time? 15 | * How many DB requests am I performing? Does the DB request aggregation work? 16 | * How far did the execution get before the error that aborted the request? 17 | 18 | It looks like this: 19 | 20 | ``` 21 | 2022-02-06T20:01:57.103747Z INFO Call summary of request@examples/nested.rs:51 22 | 23 | # calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree 24 | ────────────┼──────────────┼──────────────┼────────────-──┼─────────────────────── 25 | 0 001 ┊ 258.910 ┊ 258.890 ┊ 0.106 ┊ ┬ request 26 | 0 001 ┊ 87.204 ┊ 87.190 ┊ 19.299 ┊ ├┬ nested 27 | 0 001 ┊ 0.036 ┊ 0.021 ┊ 0.021 ┊ ┊├─ random 28 | 1 000 ┊ 75.738 ┊ 61.912 ┊ 61.912 ┊ ┊╰─ repeated 29 | 0 002 ┊ 0.051 ┊ 0.027 ┊ 0.027 ┊ ├─ repeated 30 | 0 001 ┊ 1.644 ┊ 1.632 ┊ 0.019 ┊ ├┬ nest_deeply 31 | 0 001 ┊ 1.619 ┊ 1.607 ┊ 0.025 ┊ ┊╰┬ nest_deeply 32 | 0 001 ┊ 1.593 ┊ 1.577 ┊ 0.024 ┊ ┊ ╰┬ nest_deeply 33 | 0 001 ┊ 1.561 ┊ 1.547 ┊ 0.022 ┊ ┊ ╰┬ nest_deeply 34 | 0 001 ┊ 1.532 ┊ 1.520 ┊ 0.023 ┊ ┊ ╰┬ nest_deeply 35 | 0 001 ┊ 1.504 ┊ 1.492 ┊ 0.023 ┊ ┊ ╰┬ nest_deeply 36 | 0 001 ┊ 1.476 ┊ 1.463 ┊ 0.025 ┊ ┊ ╰┬ nest_deeply 37 | 0 001 ┊ 1.446 ┊ 1.433 ┊ 0.025 ┊ ┊ ╰┬ nest_deeply 38 | 0 001 ┊ 1.415 ┊ 1.402 ┊ 1.402 ┊ ┊ ╰─ nest_deeply 39 | 0 001 ┊ 169.915 ┊ 169.905 ┊ 17.883 ┊ ╰┬ nested2 40 | 0 001 ┊ 0.010 ┊ 0.001 ┊ 0.001 ┊ ├─ random 41 | 1 000 ┊ 88.793 ┊ 76.081 ┊ 76.081 ┊ ├─ repeated 42 | 0 001 ┊ 70.386 ┊ 70.377 ┊ 19.332 ┊ ╰┬ nested 43 | 0 001 ┊ 0.011 ┊ 0.001 ┊ 0.001 ┊ ├─ random 44 | 1 000 ┊ 58.468 ┊ 45.280 ┊ 45.280 ┊ ╰─ repeated 45 | ``` 46 | 47 | * **calls**: The total number of spans created at this call path. 48 | * **∑ alive ms**: The total time spans at this call path were alive i.e. sum of times between new and close events. 49 | * **∑ busy ms**: The total time spans at this call path were entered i.e. sum of times between enter and leave events. 50 | * **∑ own busy ms**: The total time spans at this call path were entered without any children entered. 51 | 52 | It looked like this until 0.3.x: 53 | 54 | ``` 55 | Dec 20 18:48:32.405 INFO Call summary of request@examples/nested.rs:47 56 | 57 | # calls │ ∑ wall ms │ ∑ own ms │ span tree 58 | ────────────┼──────────────┼──────────────┼─────────────────────── 59 | 0 001 ┊ 377.886 ┊ 0.260 ┊ ┬ request 60 | 0 001 ┊ 120.704 ┊ 48.896 ┊ ├┬ nested 61 | 0 001 ┊ 0.008 ┊ 0.008 ┊ ┊├─ random 62 | 1 000 ┊ 64.347 ┊ 64.347 ┊ ┊╰─ repeated 63 | 0 002 ┊ 0.118 ┊ 0.118 ┊ ├─ repeated 64 | 0 001 ┊ 3.818 ┊ 0.049 ┊ ├┬ nest_deeply 65 | 0 001 ┊ 3.762 ┊ 0.053 ┊ ┊╰┬ nest_deeply 66 | 0 001 ┊ 3.702 ┊ 0.057 ┊ ┊ ╰┬ nest_deeply 67 | 0 001 ┊ 3.637 ┊ 0.056 ┊ ┊ ╰┬ nest_deeply 68 | 0 001 ┊ 3.574 ┊ 0.058 ┊ ┊ ╰┬ nest_deeply 69 | 0 001 ┊ 3.503 ┊ 0.061 ┊ ┊ ╰┬ nest_deeply 70 | 0 001 ┊ 3.435 ┊ 0.063 ┊ ┊ ╰┬ nest_deeply 71 | 0 001 ┊ 3.365 ┊ 0.066 ┊ ┊ ╰┬ nest_deeply 72 | 0 001 ┊ 3.292 ┊ 3.292 ┊ ┊ ╰─ nest_deeply 73 | 0 001 ┊ 252.949 ┊ 49.258 ┊ ╰┬ nested2 74 | 0 001 ┊ 0.006 ┊ 0.006 ┊ ├─ random 75 | 1 000 ┊ 63.343 ┊ 63.343 ┊ ├─ repeated 76 | 0 001 ┊ 132.841 ┊ 54.091 ┊ ╰┬ nested 77 | 0 001 ┊ 0.007 ┊ 0.007 ┊ ├─ random 78 | 1 000 ┊ 70.875 ┊ 70.875 ┊ ╰─ repeated 79 | 80 | ``` 81 | 82 | ## Setup 83 | 84 | For a quick startm, add/edit these `[dependencies]` in `Cargo.toml`: 85 | 86 | ``` 87 | tracing = "0.1" 88 | tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] } 89 | reqray = "0.4" 90 | ``` 91 | 92 | And add/edit your tracing layer setup: 93 | 94 | ```rust 95 | use reqray::CallTreeCollector; 96 | use tracing_subscriber::{EnvFilter, util::SubscriberInitExt, fmt, prelude::*}; 97 | 98 | let fmt_layer = fmt::layer() 99 | .with_target(false); 100 | let filter_layer = EnvFilter::try_from_default_env() 101 | .or_else(|_| EnvFilter::try_new("info")) 102 | .unwrap(); 103 | 104 | tracing_subscriber::registry() 105 | // ----------------------------------------------- 106 | .with(CallTreeCollector::default()) 107 | // ----------------------------------------------- 108 | .with(filter_layer) 109 | .with(fmt_layer) 110 | .init(); 111 | ``` 112 | 113 | Instead of `CallTreeCollector::default()` you can chose a more explicit config: 114 | 115 | ```rust 116 | // ... 117 | let call_tree_collector = CallTreeCollectorBuilder::default() 118 | .max_call_depth(10) 119 | .build_with_collector( 120 | LoggingCallTreeCollectorBuilder::default() 121 | .left_margin(20) 122 | .build(), 123 | ); 124 | 125 | tracing_subscriber::registry() 126 | .with(call_tree_collector) 127 | // ... 128 | ``` 129 | 130 | ## Compatibility with `tracing-subscriber 0.2` 131 | 132 | Use reqray 0.2.x for integration with tracing-subscriber 0.2.x. Otherwise, the API 133 | should be identical. 134 | 135 | E.g. `color_eyre` 0.5.x depends on `tracing-error` 0.1.x which requires `tracing-subscriber` 0.2. 136 | 137 | ## Overhead 138 | 139 | I did basic performance testing (see benches) to check for obvious gotchas 140 | -- I didn't spot any. If your code actually does talk to a database 141 | or anything expensive, it should be in the same order of magnitude as logging 142 | overhead with the tracing library in general. 143 | 144 | In my totally unrepresentative example with some log statements which does 145 | nothing else really, the logging overhead increased by 30-50% -- this is roughly 146 | the amount of actual log lines added to the log output in this case. 147 | 148 | Generally, you should only instrument relevant calls in your program not every 149 | one of them, especially not those in a CPU-bound loop. If you have those, 150 | it might make sense to filter those before the CallTreeCollector is invoked. 151 | 152 | I am very curious to hear actual stats in real life programs! 153 | 154 | ## Inspiration 155 | 156 | When working together with Klas Kalass, he created something similar for Java: 157 | the [Fuava CTProfiler](https://github.com/freiheit-com/fuava_ctprofiler). 158 | 159 | It proved to be immensely useful at a nearly daily basis. Thank you, Klas! 160 | 161 | Since then, I have worked with sophisticated distributed tracing systems, 162 | but they often lacked aggregation possibilities. Others hacked some interesting 163 | aggregation scripts on top and I myself became somewhat obsessed with creating 164 | similar scripts. 165 | 166 | ## Thanks 167 | 168 | I felt very welcome when I suggested something like this in issue 169 | [tracing#639](https://github.com/tokio-rs/tracing/issues/639). Thank you, @hawkw! 170 | 171 | Similarly, Eliza was very supportive and helpful in the tracing discod channel. 172 | Thank you, Eliza! 173 | 174 | ## Contributions 175 | 176 | Giving feedback or saying thanks on [Twitter](https://twitter.com/pkolloch) or 177 | on the tracing discord channel is appreciated. 178 | 179 | Contributions in the form of documentation and bug fixes are highly welcome. 180 | Please start a discussion with me (e.g. via an issue) before working on larger 181 | features. 182 | 183 | I'd really appreciate tests for all new features. Please run `cargo test` 184 | before submitting a pull request. Just use `cargo fmt` for formatting. 185 | 186 | Feature ideas are also welcome -- just know that this is a pure hobby side 187 | project and I will not allocate a lot of bandwidth to this. Therefore, important 188 | bug fixes are always prioritised. 189 | 190 | By submitting a pull request, you agree to license your changes via all the 191 | current licenses of the project. 192 | -------------------------------------------------------------------------------- /benches/overhead.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 7 | use quanta::Mock; 8 | use reqray::{CallTreeCollector, CallTreeCollectorBuilder, FinishedCallTreeProcessor}; 9 | 10 | use tracing::info; 11 | use tracing_subscriber::fmt; 12 | use tracing_subscriber::prelude::*; 13 | 14 | #[tracing::instrument] 15 | fn one_ns(mock: &Mock) { 16 | info!("one_ns"); 17 | mock.increment(1); 18 | } 19 | 20 | #[tracing::instrument] 21 | fn compound_call(mock: &Mock) { 22 | mock.increment(10); 23 | one_ns(mock); 24 | mock.increment(100); 25 | one_ns(mock); 26 | for _ in 0..10 { 27 | one_ns(mock); 28 | } 29 | mock.increment(1000); 30 | } 31 | 32 | pub fn sync_compound(c: &mut Criterion) { 33 | let (_clock, mock) = quanta::Clock::mock(); 34 | c.bench_function("without subscriber", |b| { 35 | b.iter(|| compound_call(black_box(&mock))) 36 | }); 37 | c.bench_function("log with layers", |b| { 38 | let f = File::create("benches_without_calltree.txt").unwrap(); 39 | let fmt_layer = fmt::layer() 40 | .with_thread_ids(true) 41 | .without_time() 42 | .with_target(false) 43 | .with_writer(f); 44 | let subscriber = tracing_subscriber::registry().with(fmt_layer); 45 | tracing::subscriber::with_default(subscriber, || { 46 | b.iter(|| compound_call(black_box(&mock))) 47 | }); 48 | }); 49 | c.bench_function("log with call tree collector", |b| { 50 | let call_tree_collector = CallTreeCollector::default(); 51 | let f = File::create("benches_with_calltree.txt").unwrap(); 52 | let fmt_layer = fmt::layer() 53 | .with_thread_ids(true) 54 | .without_time() 55 | .with_target(false) 56 | .with_writer(f); 57 | let subscriber = tracing_subscriber::registry() 58 | .with(call_tree_collector) 59 | .with(fmt_layer); 60 | tracing::subscriber::with_default(subscriber, || { 61 | b.iter(|| compound_call(black_box(&mock))) 62 | }); 63 | }); 64 | 65 | c.bench_function("log with silent call tree collector", |b| { 66 | let counting = CountingCallTreeProcessor::default(); 67 | let call_tree_collector = 68 | CallTreeCollectorBuilder::default().build_with_collector(counting.clone()); 69 | let f = File::create("benches_with_silent_calltree.txt").unwrap(); 70 | let fmt_layer = fmt::layer() 71 | .with_thread_ids(true) 72 | .without_time() 73 | .with_target(false) 74 | .with_writer(f); 75 | let subscriber = tracing_subscriber::registry() 76 | .with(call_tree_collector) 77 | .with(fmt_layer); 78 | tracing::subscriber::with_default(subscriber, || { 79 | b.iter(|| compound_call(black_box(&mock))) 80 | }); 81 | assert!(*counting.root_child_count.lock().unwrap() > 0); 82 | }); 83 | } 84 | 85 | #[derive(Default, Clone)] 86 | struct CountingCallTreeProcessor { 87 | root_child_count: Arc>, 88 | } 89 | 90 | impl FinishedCallTreeProcessor for CountingCallTreeProcessor { 91 | fn process_finished_call(&self, pool: reqray::CallPathPool) { 92 | let mut locked = self.root_child_count.lock().unwrap(); 93 | *locked += pool.root().children().count(); 94 | } 95 | } 96 | 97 | criterion_group!(benches, sync_compound); 98 | criterion_main!(benches); 99 | -------------------------------------------------------------------------------- /examples/nested.rs: -------------------------------------------------------------------------------- 1 | use reqray::{display::LoggingCallTreeCollectorBuilder, CallTreeCollectorBuilder}; 2 | use tracing_subscriber::{fmt, prelude::*, util::SubscriberInitExt, EnvFilter}; 3 | 4 | fn main() { 5 | let fmt_layer = fmt::layer().with_target(false); 6 | let filter_layer = EnvFilter::try_from_default_env() 7 | .or_else(|_| EnvFilter::try_new("info")) 8 | .unwrap(); 9 | 10 | let call_tree_collector = CallTreeCollectorBuilder::default() 11 | .max_call_depth(10) 12 | .build_with_collector( 13 | LoggingCallTreeCollectorBuilder::default() 14 | .left_margin(20) 15 | .build(), 16 | ); 17 | 18 | tracing_subscriber::registry() 19 | .with(call_tree_collector) 20 | .with(filter_layer) 21 | .with(fmt_layer) 22 | .init(); 23 | 24 | use tracing::{info, instrument}; 25 | 26 | #[instrument] 27 | fn repeated(repetition: i32) { 28 | info!("repetition: {}", repetition); 29 | } 30 | 31 | #[instrument] 32 | fn random() {} 33 | 34 | #[instrument] 35 | fn nested() { 36 | random(); 37 | for i in 1..=1000 { 38 | repeated(i); 39 | } 40 | } 41 | 42 | #[instrument] 43 | fn nested2() { 44 | random(); 45 | for i in 1..=1000 { 46 | repeated(i); 47 | } 48 | nested(); 49 | } 50 | 51 | #[instrument] 52 | fn request() { 53 | nested(); 54 | repeated(-1); 55 | repeated(-2); 56 | nest_deeply(100); 57 | // Even though the name is the same, this is a different span. 58 | // let name_clash_span = info_span!("nested"); 59 | // let _enter = name_clash_span.enter(); 60 | nested2(); 61 | } 62 | 63 | #[instrument] 64 | fn nest_deeply(nest: usize) { 65 | if nest == 0 { 66 | return; 67 | } 68 | 69 | nest_deeply(nest - 1); 70 | } 71 | 72 | request(); 73 | } 74 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use crate::{CallPathPool, CallPathTiming, FinishedCallTreeProcessor}; 4 | 5 | pub struct LoggingCallTreeCollector { 6 | max_call_depth: usize, 7 | left_margin: usize, 8 | } 9 | 10 | pub struct LoggingCallTreeCollectorBuilder { 11 | max_call_depth: usize, 12 | left_margin: usize, 13 | } 14 | 15 | impl LoggingCallTreeCollectorBuilder { 16 | pub fn max_call_depth(mut self, max_call_depth: usize) -> Self { 17 | self.max_call_depth = max_call_depth; 18 | self 19 | } 20 | 21 | pub fn left_margin(mut self, left_margin: usize) -> Self { 22 | self.left_margin = left_margin; 23 | self 24 | } 25 | 26 | pub fn build(self) -> LoggingCallTreeCollector { 27 | LoggingCallTreeCollector { 28 | max_call_depth: self.max_call_depth, 29 | left_margin: self.left_margin, 30 | } 31 | } 32 | } 33 | 34 | impl Default for LoggingCallTreeCollectorBuilder { 35 | fn default() -> Self { 36 | LoggingCallTreeCollectorBuilder { 37 | max_call_depth: 10, 38 | left_margin: 20, 39 | } 40 | } 41 | } 42 | 43 | impl FinishedCallTreeProcessor for LoggingCallTreeCollector { 44 | fn process_finished_call(&self, pool: CallPathPool) { 45 | let root = pool.root(); 46 | tracing::info!( 47 | "Call summary of {}@{}:{}\n\n{}", 48 | root.static_span_meta().name(), 49 | root.static_span_meta().file().unwrap_or("unknown"), 50 | root.static_span_meta().line().unwrap_or(0), 51 | DisplayableCallPathTiming { 52 | max_call_depth: self.max_call_depth, 53 | left_margin: self.left_margin, 54 | pool: &pool, 55 | root 56 | } 57 | ) 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | struct DisplayableCallPathTiming<'a> { 63 | max_call_depth: usize, 64 | left_margin: usize, 65 | pool: &'a CallPathPool, 66 | root: &'a CallPathTiming, 67 | } 68 | 69 | impl<'a> fmt::Display for DisplayableCallPathTiming<'a> { 70 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 71 | writeln!( 72 | f, 73 | "{:indent$} # calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree", 74 | "", 75 | indent = self.left_margin 76 | )?; 77 | writeln!( 78 | f, 79 | "{:indent$}────────────┼──────────────┼──────────────┼────────────-──┼───────────────────────", 80 | "", 81 | indent = self.left_margin 82 | )?; 83 | let mut last = Vec::with_capacity(self.max_call_depth); 84 | last.push(true); 85 | self.fmt(&mut last, self.root, f) 86 | } 87 | } 88 | 89 | impl DisplayableCallPathTiming<'_> { 90 | fn fmt( 91 | &self, 92 | // this is wasteful 93 | last: &mut Vec, 94 | node: &CallPathTiming, 95 | f: &mut fmt::Formatter<'_>, 96 | ) -> fmt::Result { 97 | write!( 98 | f, 99 | "{:indent$}{: >7} {:0>3} ┊ {: >8}.{:0>3} ┊ {: >8}.{:0>3} ┊ {: >8}.{:0>3} ┊ ", 100 | "", 101 | node.call_count() / 1000, 102 | node.call_count() % 1000, 103 | node.span_alive().as_micros() / 1000, 104 | node.span_alive().as_micros() % 1000, 105 | node.sum_with_children().as_micros() / 1000, 106 | node.sum_with_children().as_micros() % 1000, 107 | node.sum_without_children().as_micros() / 1000, 108 | node.sum_without_children().as_micros() % 1000, 109 | indent = self.left_margin 110 | )?; 111 | 112 | let child_connector = if node.children().next().is_none() { 113 | "─" 114 | } else { 115 | "┬" 116 | }; 117 | match last.len() { 118 | 1 => writeln!(f, "{} {}", child_connector, node.static_span_meta().name())?, 119 | _ => { 120 | if last.len() > 2 { 121 | for is_last in last.iter().skip(1).take(last.len() - 2) { 122 | f.write_str(if *is_last { " " } else { "┊" })?; 123 | } 124 | } 125 | 126 | let connect_me = if *last.iter().last().unwrap() { 127 | "╰" 128 | } else { 129 | "├" 130 | }; 131 | f.write_str(connect_me)?; 132 | f.write_str(child_connector)?; 133 | 134 | writeln!(f, " {}", node.static_span_meta().name())?; 135 | } 136 | }; 137 | 138 | let mut children = node.children().copied().collect::>(); 139 | if !children.is_empty() { 140 | children.sort(); 141 | let last_dx = children.len() - 1; 142 | for (idx, child_idx) in children.iter().enumerate() { 143 | let child = &self.pool[*child_idx]; 144 | last.push(idx == last_dx); 145 | self.fmt(last, child, f)?; 146 | last.pop(); 147 | } 148 | } 149 | Ok(()) 150 | } 151 | } 152 | 153 | #[cfg(test)] 154 | mod test { 155 | use std::sync::Arc; 156 | 157 | use quanta::Mock; 158 | 159 | use crate::internal::test::{collect_call_trees, compound_call, cooking_party, one_ns}; 160 | 161 | #[test] 162 | fn display_one_ns() { 163 | let str = display_call_trees(|mock| one_ns(&mock)); 164 | assert_eq!( 165 | &str, 166 | indoc::indoc! {r#" 167 | # calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree 168 | ────────────┼──────────────┼──────────────┼────────────-──┼─────────────────────── 169 | 0 001 ┊ 0.000 ┊ 0.000 ┊ 0.000 ┊ ─ one_ns 170 | 171 | "#}, 172 | "got:\n{}", 173 | str 174 | ); 175 | } 176 | 177 | #[test] 178 | fn display_compound_call() { 179 | let str = display_call_trees(|mock| compound_call(&mock)); 180 | assert_eq!( 181 | &str, 182 | indoc::indoc! {r#" 183 | # calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree 184 | ────────────┼──────────────┼──────────────┼────────────-──┼─────────────────────── 185 | 0 001 ┊ 0.001 ┊ 0.001 ┊ 0.001 ┊ ┬ compound_call 186 | 0 003 ┊ 0.000 ┊ 0.000 ┊ 0.000 ┊ ╰─ one_ns 187 | 188 | "#}, 189 | "got:\n{}", 190 | str 191 | ); 192 | } 193 | 194 | #[tracing::instrument] 195 | fn nest_deeply(mock: &Mock, nest: usize) { 196 | if nest == 0 { 197 | return; 198 | } 199 | 200 | mock.increment(1_000); 201 | nest_deeply(mock, nest - 1); 202 | mock.increment(1_000_000); 203 | } 204 | 205 | #[test] 206 | fn display_nest_deeply() { 207 | let str = display_call_trees(|mock| nest_deeply(&mock, 11)); 208 | assert_eq!( 209 | &str, 210 | indoc::indoc! {r#" 211 | # calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree 212 | ────────────┼──────────────┼──────────────┼────────────-──┼─────────────────────── 213 | 0 001 ┊ 11.011 ┊ 11.011 ┊ 1.001 ┊ ┬ nest_deeply 214 | 0 001 ┊ 10.010 ┊ 10.010 ┊ 1.001 ┊ ╰┬ nest_deeply 215 | 0 001 ┊ 9.009 ┊ 9.009 ┊ 1.001 ┊ ╰┬ nest_deeply 216 | 0 001 ┊ 8.008 ┊ 8.008 ┊ 1.001 ┊ ╰┬ nest_deeply 217 | 0 001 ┊ 7.007 ┊ 7.007 ┊ 1.001 ┊ ╰┬ nest_deeply 218 | 0 001 ┊ 6.006 ┊ 6.006 ┊ 1.001 ┊ ╰┬ nest_deeply 219 | 0 001 ┊ 5.005 ┊ 5.005 ┊ 1.001 ┊ ╰┬ nest_deeply 220 | 0 001 ┊ 4.004 ┊ 4.004 ┊ 1.001 ┊ ╰┬ nest_deeply 221 | 0 001 ┊ 3.003 ┊ 3.003 ┊ 1.001 ┊ ╰┬ nest_deeply 222 | 0 001 ┊ 2.002 ┊ 2.002 ┊ 2.002 ┊ ╰─ nest_deeply 223 | 224 | "#}, 225 | "got:\n{}", 226 | str 227 | ); 228 | } 229 | 230 | #[test] 231 | fn display_with_futures() { 232 | let str = display_call_trees(|mock| { 233 | // let rt = tokio::runtime::Runtime::new().unwrap(); 234 | // rt.block_on(async { 235 | async_std::task::block_on(async { 236 | cooking_party(mock).await; 237 | }); 238 | }); 239 | 240 | // The clock increments from other threads can leak over, unfortunately. 241 | // Therefore, we use XXXs for the non-deterministic values. 242 | let pattern = indoc::indoc! {r#" 243 | # calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree 244 | ────────────┼──────────────┼──────────────┼────────────-──┼─────────────────────── 245 | 0 001 ┊ 101.XXX ┊ 101.XXX ┊ 101.XXX ┊ ┬ cooking_party 246 | 0 001 ┊ 0.03X ┊ 0.03X ┊ 0.03X ┊ ├─ cook_three 247 | 0 001 ┊ 0.0X3 ┊ 0.0X3 ┊ 0.0X3 ┊ ╰─ eat_three 248 | 249 | "#}; 250 | 251 | pattern_matches(pattern, &str); 252 | } 253 | 254 | fn pattern_matches(pattern: &str, actual: &str) { 255 | assert_eq!( 256 | pattern.len(), 257 | actual.len(), 258 | "unexpected length:\n{}", 259 | actual 260 | ); 261 | 262 | let mut all_matches = true; 263 | let mut matches = String::new(); 264 | for (p, a) in pattern.chars().zip(actual.chars()) { 265 | if p != 'X' && p != a { 266 | all_matches = false; 267 | matches += "!"; 268 | } else { 269 | matches.push(p); 270 | } 271 | } 272 | 273 | assert!( 274 | all_matches, 275 | "positions at ! didn't match:\n{}\n{}\n", 276 | matches, actual 277 | ); 278 | } 279 | 280 | fn display_call_trees(call: impl Fn(Arc)) -> String { 281 | use std::fmt::Write; 282 | 283 | let call_trees = collect_call_trees(call); 284 | 285 | let mut out = String::new(); 286 | for call_tree in call_trees { 287 | writeln!( 288 | &mut out, 289 | "{}", 290 | super::DisplayableCallPathTiming { 291 | max_call_depth: 10, 292 | left_margin: 0, 293 | pool: &call_tree, 294 | root: call_tree.root() 295 | } 296 | ) 297 | .unwrap(); 298 | } 299 | out 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/internal.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt, thread::ThreadId, time::Duration}; 2 | use tracing::{ 3 | span::{self}, 4 | Id, Subscriber, warn, 5 | }; 6 | use tracing_subscriber::{ 7 | layer::Context, 8 | registry::{ExtensionsMut, LookupSpan}, 9 | Layer, 10 | }; 11 | 12 | use std::ops::{Index, IndexMut}; 13 | 14 | use tracing::{callsite, Metadata}; 15 | 16 | /// Use a [CallPathPoolId] to index a [CallPathTiming] in a [CallPathPool]. 17 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 18 | pub struct CallPathPoolId(usize); 19 | 20 | /// A [CallPathPool] contains all [CallPathTiming]s of a call tree 21 | /// indexed by [CallPathPoolId]s. 22 | #[derive(Debug)] 23 | pub struct CallPathPool { 24 | pool: Vec, 25 | } 26 | 27 | impl CallPathPool { 28 | pub fn root(&self) -> &CallPathTiming { 29 | &self[CallPathPoolId(0)] 30 | } 31 | } 32 | 33 | impl Index for CallPathPool { 34 | type Output = CallPathTiming; 35 | 36 | fn index(&self, CallPathPoolId(idx): CallPathPoolId) -> &Self::Output { 37 | &self.pool[idx] 38 | } 39 | } 40 | 41 | impl IndexMut for CallPathPool { 42 | fn index_mut(&mut self, CallPathPoolId(idx): CallPathPoolId) -> &mut Self::Output { 43 | &mut self.pool[idx] 44 | } 45 | } 46 | 47 | /// A CallPathTiming is an aggregation of all spans with the same 48 | /// call path. That means that their `callsite::Identifier` is 49 | /// the same and all the `callsite::Identifier`s of their ancestor 50 | /// spans are also the same. 51 | #[derive(Debug, Clone)] 52 | pub struct CallPathTiming { 53 | depth: usize, 54 | call_count: usize, 55 | span_meta: &'static Metadata<'static>, 56 | children: HashMap, 57 | span_life_time: Duration, 58 | sum_with_children: Duration, 59 | sum_own: Duration, 60 | } 61 | 62 | impl CallPathTiming { 63 | /// The metadata associated with the called instrumented span, 64 | /// includes e.g. the name of the function that is being executed. 65 | pub fn static_span_meta(&self) -> &'static Metadata<'static> { 66 | self.span_meta 67 | } 68 | 69 | /// The number of times a new span with this call path was created. 70 | /// 71 | /// Typically, the number of times a function was called. 72 | pub fn call_count(&self) -> usize { 73 | self.call_count 74 | } 75 | 76 | /// The sum between span new and close events. 77 | pub fn span_alive(&self) -> Duration { 78 | self.span_life_time 79 | } 80 | 81 | /// The total sum of durations between entering and leaving spans 82 | /// with this call path. The time spent in sub spans is included. 83 | pub fn sum_with_children(&self) -> Duration { 84 | self.sum_with_children 85 | } 86 | 87 | /// The total sum of durations between entering and leaving spans 88 | /// with this call path but the durations where we entered a sub 89 | /// span are excluded. 90 | pub fn sum_without_children(&self) -> Duration { 91 | self.sum_own 92 | } 93 | 94 | /// An iterator over the IDs of all children. 95 | pub fn children(&self) -> impl Iterator { 96 | self.children.values() 97 | } 98 | } 99 | 100 | /// The span specific information. 101 | /// 102 | /// The sums are folded into the referenced [CallPathTiming] when 103 | /// the span is closed. 104 | #[derive(Debug, Clone)] 105 | struct SpanTimingInfo { 106 | call_path_idx: CallPathPoolId, 107 | /// The time at which the span was first created. 108 | created_at: u64, 109 | sum_with_children: Duration, 110 | sum_own: Duration, 111 | /// Per thread info. We always access SpanTimingInfo in a thread-safe way 112 | /// but we still need to keep some info per-thread: 113 | /// While not typical, the same span can be entered multiple times from multiple threads. 114 | per_thread: HashMap, 115 | } 116 | 117 | #[derive(Debug, Clone, Default)] 118 | struct PerThreadInfo { 119 | last_enter: u64, 120 | last_enter_own: u64, 121 | } 122 | 123 | impl SpanTimingInfo { 124 | fn for_call_path_idx(call_path_idx: CallPathPoolId, created_at: u64) -> SpanTimingInfo { 125 | SpanTimingInfo { 126 | call_path_idx, 127 | created_at, 128 | sum_with_children: Duration::default(), 129 | sum_own: Duration::default(), 130 | per_thread: HashMap::new(), 131 | } 132 | } 133 | } 134 | 135 | // Implementation idea: 136 | // 137 | // Each Span has a [SpanTimingInfo]. In parallel, we build 138 | // an aggregated hierarchy for every call path of [CallPathTiming]. 139 | // We have a [CallPathPool] owning all [CallPathTiming]s at the root span. 140 | // Whenever a Span is closed, we fold its aggregation values in 141 | // the corresponding [CallPathTiming]. 142 | // 143 | // This way, when entering/leaving a span, we only touch the 144 | // span specific data without fancy lookups. This is important 145 | // in async code where a span might be entered/left many times. 146 | impl Layer for crate::CallTreeCollector 147 | where 148 | S: Subscriber + for<'span> LookupSpan<'span> + fmt::Debug, 149 | H: crate::FinishedCallTreeProcessor + 'static, 150 | { 151 | fn on_new_span(&self, _attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { 152 | let span = ctx.span(id).expect("no span in new_span"); 153 | match span.parent() { 154 | None => { 155 | // root 156 | let pool = vec![CallPathTiming { 157 | depth: 0, 158 | call_count: 0, 159 | span_meta: span.metadata(), 160 | children: HashMap::new(), 161 | span_life_time: Duration::default(), 162 | sum_with_children: Duration::default(), 163 | sum_own: Duration::default(), 164 | }]; 165 | let mut extensions: ExtensionsMut = span.extensions_mut(); 166 | extensions.insert(CallPathPool { pool }); 167 | let created_at = self.clock.start(); 168 | extensions.insert(SpanTimingInfo::for_call_path_idx( 169 | CallPathPoolId(0), 170 | created_at, 171 | )); 172 | } 173 | Some(parent) => { 174 | let mut parent_extensions = parent.extensions_mut(); 175 | let parent_span_info = parent_extensions.get_mut::(); 176 | if parent_span_info.is_none() { 177 | // We are beyond the maximum tracing depth. 178 | return; 179 | } 180 | 181 | let parent_call_path_idx = parent_span_info 182 | .expect("parent has no SpanTimingInfo") 183 | .call_path_idx; 184 | let root = span 185 | .scope() 186 | .from_root() 187 | .next() 188 | .expect("span has a parent but no root"); 189 | let mut root_extensions: ExtensionsMut = if root.id() == parent.id() { 190 | parent_extensions 191 | } else { 192 | // Do not keep multiple extensions locked at the same time. 193 | std::mem::drop(parent_extensions); 194 | root.extensions_mut() 195 | }; 196 | let pool: &mut CallPathPool = root_extensions.get_mut::().unwrap(); 197 | let new_idx = CallPathPoolId(pool.pool.len()); 198 | let parent_call_path_timing = &mut pool[parent_call_path_idx]; 199 | let new_depth = parent_call_path_timing.depth + 1; 200 | if new_depth >= self.max_call_depth { 201 | return; 202 | } 203 | let idx = parent_call_path_timing 204 | .children 205 | .get(&span.metadata().callsite()); 206 | let call_path_idx = match idx { 207 | Some(idx) => *idx, 208 | None => { 209 | parent_call_path_timing 210 | .children 211 | .insert(span.metadata().callsite(), new_idx); 212 | pool.pool.push(CallPathTiming { 213 | depth: new_depth, 214 | call_count: 0, 215 | span_meta: span.metadata(), 216 | children: HashMap::new(), 217 | span_life_time: Duration::default(), 218 | sum_with_children: Duration::default(), 219 | sum_own: Duration::default(), 220 | }); 221 | new_idx 222 | } 223 | }; 224 | // Do not keep multiple extensions locked at the same time. 225 | std::mem::drop(root_extensions); 226 | let mut extensions: ExtensionsMut = span.extensions_mut(); 227 | let created_at = self.clock.start(); 228 | extensions.insert(SpanTimingInfo::for_call_path_idx(call_path_idx, created_at)); 229 | } 230 | }; 231 | } 232 | 233 | fn on_enter(&self, _id: &tracing::Id, ctx: Context) { 234 | let leave_parent = self.clock.end(); 235 | let span = ctx.lookup_current().expect("no span in new_span"); 236 | if span.extensions().get::().is_none() { 237 | // yes, this is an extra check but: 238 | // * it has to occur before we check for the parent 239 | // * taking the "start" clock value below should be one of the last 240 | // operations 241 | return; 242 | } 243 | 244 | if let Some(parent) = span.parent() { 245 | let mut extensions = parent.extensions_mut(); 246 | if let Some(timing_info) = extensions.get_mut::() { 247 | if let Some(thread_info) = timing_info.per_thread.get(&std::thread::current().id()) { 248 | let last_enter_own = thread_info.last_enter_own; 249 | let delta = self.clock.delta(last_enter_own, leave_parent); 250 | timing_info.sum_own += delta; 251 | } 252 | } 253 | } 254 | 255 | let mut extensions = span.extensions_mut(); 256 | if let Some(timing_info) = extensions.get_mut::() { 257 | let mut per_thread = timing_info 258 | .per_thread 259 | .entry(std::thread::current().id()) 260 | .or_default(); 261 | let start = self.clock.start(); 262 | per_thread.last_enter = start; 263 | per_thread.last_enter_own = start; 264 | } 265 | } 266 | 267 | fn on_exit(&self, id: &tracing::Id, ctx: Context<'_, S>) { 268 | let end = self.clock.end(); 269 | let span = ctx.span(id).unwrap(); 270 | 271 | let mut extensions = span.extensions_mut(); 272 | let timing_info = extensions.get_mut::(); 273 | if timing_info.is_none() { 274 | return; 275 | } 276 | let timing_info = timing_info.unwrap(); 277 | 278 | if let Some(per_thread) = &timing_info.per_thread.get(&std::thread::current().id()) { 279 | let wall_duration = self.clock.delta(per_thread.last_enter, end); 280 | timing_info.sum_with_children += wall_duration; 281 | let own_duration = self.clock.delta(per_thread.last_enter_own, end); 282 | timing_info.sum_own += own_duration; 283 | 284 | // It is likely that we will be entered by the same thread again, 285 | // but we do not want to bloat memory if we are constantly entered 286 | // in different threads. 287 | timing_info.per_thread.remove(&std::thread::current().id()); 288 | } else { 289 | // In on_enter we ensure that the per thread info exists -- so I don't exactly understand 290 | // when this can happen. 291 | warn!("Missing thread info for current thread on exit. \n\ 292 | Cannot account own time correctly. \n\ 293 | If you use .in_current_span() or .or_current(), a span might be entered and exited multiple times.\n\ 294 | Future versions of reqray might support this properly. Sorry for the inconvenience.\n"); 295 | } 296 | 297 | // Make sure that we do not hold two extension locks at once. 298 | std::mem::drop(extensions); 299 | 300 | if let Some(parent) = span.parent() { 301 | let mut extensions = parent.extensions_mut(); 302 | let timing_info = extensions 303 | .get_mut::() 304 | .expect("parent has no SpanTimingInfo"); 305 | let enter_own = self.clock.start(); 306 | timing_info 307 | .per_thread 308 | .entry(std::thread::current().id()) 309 | .and_modify(|per_thread| { 310 | per_thread.last_enter_own = enter_own; 311 | }); 312 | } 313 | } 314 | 315 | fn on_close(&self, id: Id, ctx: Context) { 316 | let closed = self.clock.end(); 317 | let span = ctx.span(&id).expect("no span in close"); 318 | let mut extensions = span.extensions_mut(); 319 | let timing_info = extensions.remove::(); 320 | if timing_info.is_none() { 321 | return; 322 | } 323 | let timing_info = timing_info.unwrap(); 324 | let root_extensions_opt = span.scope().from_root().next(); 325 | let mut root_extensions: ExtensionsMut = match root_extensions_opt.as_ref() { 326 | Some(re) => { 327 | // Make sure that we do not hold two extension locks at once. 328 | std::mem::drop(extensions); 329 | re.extensions_mut() 330 | } 331 | None => extensions, 332 | }; 333 | 334 | let pool: &mut CallPathPool = root_extensions 335 | .get_mut::() 336 | .expect("no pool in root Span"); 337 | let call_path_timing: &mut CallPathTiming = &mut pool[timing_info.call_path_idx]; 338 | call_path_timing.call_count += 1; 339 | call_path_timing.span_life_time += self.clock.delta(timing_info.created_at, closed); 340 | call_path_timing.sum_with_children += timing_info.sum_with_children; 341 | call_path_timing.sum_own += timing_info.sum_own; 342 | 343 | if span.parent().is_none() { 344 | let pool = root_extensions 345 | .remove::() 346 | .expect("no pool in root Span"); 347 | 348 | self.processor.process_finished_call(pool); 349 | } 350 | } 351 | } 352 | 353 | #[cfg(test)] 354 | pub(crate) mod test { 355 | use std::{ 356 | sync::{Arc, Mutex}, 357 | time::Duration, 358 | }; 359 | 360 | use futures::channel::mpsc::{channel, Receiver, Sender}; 361 | use quanta::{Clock, Mock}; 362 | use tracing::{info, Instrument}; 363 | use tracing_subscriber::fmt; 364 | 365 | use crate::{CallPathPool, CallTreeCollectorBuilder, FinishedCallTreeProcessor}; 366 | 367 | #[tracing::instrument] 368 | pub fn one_ns(mock: &Mock) { 369 | mock.increment(1); 370 | } 371 | 372 | #[test] 373 | fn test_simple() { 374 | let call_trees = collect_call_trees(|mock| { 375 | one_ns(&mock); 376 | }); 377 | 378 | assert_eq!(call_trees.len(), 1, "{:#?}", call_trees); 379 | 380 | let first_call = &call_trees[0]; 381 | assert_eq!(first_call.pool.len(), 1, "{:#?}", first_call.pool); 382 | let first_call_root = first_call.root(); 383 | assert_eq!( 384 | first_call_root.static_span_meta().name(), 385 | "one_ns", 386 | "{:#?}", 387 | first_call 388 | ); 389 | assert_eq!(first_call_root.call_count(), 1, "{:#?}", first_call); 390 | assert_eq!( 391 | first_call_root.sum_with_children(), 392 | Duration::from_nanos(1), 393 | "{:#?}", 394 | first_call 395 | ); 396 | assert_eq!( 397 | first_call_root.sum_without_children(), 398 | Duration::from_nanos(1), 399 | "{:#?}", 400 | first_call 401 | ); 402 | } 403 | 404 | #[tracing::instrument] 405 | pub fn compound_call(mock: &Mock) { 406 | mock.increment(10); 407 | one_ns(mock); 408 | mock.increment(100); 409 | one_ns(mock); 410 | one_ns(mock); 411 | mock.increment(1000); 412 | } 413 | 414 | #[test] 415 | fn test_compound() { 416 | let call_trees = collect_call_trees(|mock| { 417 | compound_call(&mock); 418 | }); 419 | 420 | assert_eq!(call_trees.len(), 1, "{:#?}", call_trees); 421 | 422 | let first_call = &call_trees[0]; 423 | assert_eq!(first_call.pool.len(), 2, "{:#?}", first_call.pool); 424 | 425 | let first_call_root = first_call.root(); 426 | assert_eq!( 427 | first_call_root.static_span_meta().name(), 428 | "compound_call", 429 | "{:#?}", 430 | first_call 431 | ); 432 | assert_eq!(first_call_root.call_count(), 1, "{:#?}", first_call); 433 | assert_eq!( 434 | first_call_root.sum_with_children(), 435 | Duration::from_nanos(1113), 436 | "{:#?}", 437 | first_call 438 | ); 439 | assert_eq!( 440 | first_call_root.sum_without_children(), 441 | Duration::from_nanos(1110), 442 | "{:#?}", 443 | first_call 444 | ); 445 | assert_eq!(first_call_root.children().count(), 1, "{:#?}", call_trees); 446 | 447 | let nested_call_idx = *first_call_root.children().next().unwrap(); 448 | let nested_call = &first_call[nested_call_idx]; 449 | assert_eq!(nested_call.static_span_meta().name(), "one_ns"); 450 | assert_eq!(nested_call.call_count(), 3); 451 | assert_eq!(nested_call.sum_with_children(), Duration::from_nanos(3)); 452 | assert_eq!(nested_call.sum_without_children(), Duration::from_nanos(3)); 453 | } 454 | 455 | #[tracing::instrument(skip(mock, receiver))] 456 | pub async fn eat_three(mock: Arc, mut receiver: Receiver) { 457 | use futures::StreamExt; 458 | for _ in 0..3 { 459 | let _next = receiver.next().await.unwrap(); 460 | info!("increment 1_000"); 461 | mock.increment(1_000); 462 | } 463 | } 464 | 465 | #[tracing::instrument(skip(mock, sender))] 466 | pub async fn cook_three(mock: Arc, mut sender: Sender) { 467 | use futures::SinkExt; 468 | for _ in 0..3 { 469 | info!("increment 10_000"); 470 | mock.increment(10_000); 471 | sender.send(0).await.unwrap(); 472 | } 473 | } 474 | 475 | #[tracing::instrument(skip(mock))] 476 | pub async fn cooking_party(mock: Arc) { 477 | // Use "no" buffer (which means a buffer of one for each sender) 478 | // to enforce a deterministic order. 479 | let (sender, receiver) = channel(0); 480 | use tracing_futures::WithSubscriber; 481 | info!("CP increment 1_000_000"); 482 | mock.increment(1_000_000); 483 | 484 | let handle = async_std::task::spawn({ 485 | let mock = mock.clone(); 486 | async { 487 | eat_three(mock, receiver).await; 488 | } 489 | .in_current_span() 490 | .with_current_subscriber() 491 | }); 492 | cook_three(mock.clone(), sender).await; 493 | 494 | handle.await; 495 | info!("CP increment 100_000_000"); 496 | mock.increment(100_000_000); 497 | } 498 | 499 | #[test] 500 | fn test_with_futures() { 501 | let call_tree = collect_call_trees(|mock| { 502 | // let rt = tokio::runtime::Runtime::new().unwrap(); 503 | // rt.block_on(async { 504 | async_std::task::block_on(async { 505 | cooking_party(mock).await; 506 | }); 507 | }); 508 | 509 | println!("{:#?}", call_tree); 510 | } 511 | 512 | pub fn collect_call_trees(call: impl Fn(Arc)) -> Vec { 513 | use tracing_subscriber::prelude::*; 514 | 515 | let call_trees = FinishedCallTreeStore::default(); 516 | { 517 | let (clock, mock) = Clock::mock(); 518 | let call_tree_collector = CallTreeCollectorBuilder::default() 519 | .clock(clock) 520 | .build_with_collector(call_trees.clone()); 521 | let fmt_layer = fmt::layer() 522 | .with_thread_ids(true) 523 | .without_time() 524 | .with_target(false); 525 | let subscriber = tracing_subscriber::registry() 526 | .with(call_tree_collector) 527 | .with(fmt_layer); 528 | tracing::subscriber::with_default(subscriber, || { 529 | call(mock); 530 | }); 531 | } 532 | call_trees.into_vec() 533 | } 534 | 535 | #[derive(Clone, Default)] 536 | struct FinishedCallTreeStore { 537 | store: Arc>>, 538 | } 539 | 540 | impl FinishedCallTreeStore { 541 | pub fn into_vec(self) -> Vec { 542 | let mut arc = self.store; 543 | // Really not sure why we have some asynchronous use here. 544 | let store = loop { 545 | match Arc::try_unwrap(arc) { 546 | Ok(store) => break store, 547 | Err(a) => arc = a, 548 | } 549 | }; 550 | 551 | store.into_inner().unwrap() 552 | } 553 | } 554 | 555 | impl FinishedCallTreeProcessor for FinishedCallTreeStore { 556 | fn process_finished_call(&self, pool: CallPathPool) { 557 | let mut guard = self.store.lock().expect("getting collect log"); 558 | guard.push(pool); 559 | } 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Log "request x-rays" for rust programs instrumented with 2 | //! [tracing](https://github.com/tokio-rs/tracing). This includes aggregated 3 | //! wall/own times as frequently found in flame graphs in a human-friendly text 4 | //! format. Example: 5 | //! 6 | //! ```text 7 | //! 2022-02-06T20:01:57.103747Z INFO Call summary of request@examples/nested.rs:51 8 | //! 9 | //! ## calls │ ∑ alive ms │ ∑ busy ms │ ∑ own busy ms │ span tree 10 | //! ────────────┼──────────────┼──────────────┼────────────-──┼─────────────────────── 11 | //! 0 001 ┊ 258.910 ┊ 258.890 ┊ 0.106 ┊ ┬ request 12 | //! 0 001 ┊ 87.204 ┊ 87.190 ┊ 19.299 ┊ ├┬ nested 13 | //! 0 001 ┊ 0.036 ┊ 0.021 ┊ 0.021 ┊ ┊├─ random 14 | //! 1 000 ┊ 75.738 ┊ 61.912 ┊ 61.912 ┊ ┊╰─ repeated 15 | //! 0 002 ┊ 0.051 ┊ 0.027 ┊ 0.027 ┊ ├─ repeated 16 | //! 0 001 ┊ 1.644 ┊ 1.632 ┊ 0.019 ┊ ├┬ nest_deeply 17 | //! 0 001 ┊ 1.619 ┊ 1.607 ┊ 0.025 ┊ ┊╰┬ nest_deeply 18 | //! 0 001 ┊ 1.593 ┊ 1.577 ┊ 0.024 ┊ ┊ ╰┬ nest_deeply 19 | //! 0 001 ┊ 1.561 ┊ 1.547 ┊ 0.022 ┊ ┊ ╰┬ nest_deeply 20 | //! 0 001 ┊ 1.532 ┊ 1.520 ┊ 0.023 ┊ ┊ ╰┬ nest_deeply 21 | //! 0 001 ┊ 1.504 ┊ 1.492 ┊ 0.023 ┊ ┊ ╰┬ nest_deeply 22 | //! 0 001 ┊ 1.476 ┊ 1.463 ┊ 0.025 ┊ ┊ ╰┬ nest_deeply 23 | //! 0 001 ┊ 1.446 ┊ 1.433 ┊ 0.025 ┊ ┊ ╰┬ nest_deeply 24 | //! 0 001 ┊ 1.415 ┊ 1.402 ┊ 1.402 ┊ ┊ ╰─ nest_deeply 25 | //! 0 001 ┊ 169.915 ┊ 169.905 ┊ 17.883 ┊ ╰┬ nested2 26 | //! 0 001 ┊ 0.010 ┊ 0.001 ┊ 0.001 ┊ ├─ random 27 | //! 1 000 ┊ 88.793 ┊ 76.081 ┊ 76.081 ┊ ├─ repeated 28 | //! 0 001 ┊ 70.386 ┊ 70.377 ┊ 19.332 ┊ ╰┬ nested 29 | //! 0 001 ┊ 0.011 ┊ 0.001 ┊ 0.001 ┊ ├─ random 30 | //! 1 000 ┊ 58.468 ┊ 45.280 ┊ 45.280 ┊ ╰─ repeated 31 | //! ``` 32 | //! 33 | //! * **calls**: The total number of spans created at this call path. 34 | //! * **∑ alive ms**: The total time spans at this call path were alive i.e. sum of times between new and close events. 35 | //! * **∑ busy ms**: The total time spans at this call path were entered i.e. sum of times between enter and leave events. 36 | //! * **∑ own busy ms**: The total time spans at this call path were entered without any children entered. 37 | //! 38 | //! 39 | //! Under the hood, `reqray` provides a [CallTreeCollector] tracing `Layer` 40 | //! which, unsurprisingly, collects call trees. Once the root span (e.g. the top 41 | //! most span = the top most instrumented call) has been closed, the finished 42 | //! call tree is handed over to a [FinishedCallTreeProcessor]. 43 | //! [LoggingCallTreeCollector] implements [FinishedCallTreeProcessor] and logs 44 | //! each call tree in human-friendly way as shown above. 45 | //! 46 | //! Let's assume that you already have an explicit setup for `tracing` like 47 | //! this, then you simply need to add the highlighted line: 48 | //! 49 | //! ``` 50 | //! use reqray::CallTreeCollector; 51 | //! use tracing_subscriber::{EnvFilter, util::SubscriberInitExt, fmt, prelude::*}; 52 | //! 53 | //! let fmt_layer = fmt::layer() 54 | //! .with_target(false); 55 | //! let filter_layer = EnvFilter::try_from_default_env() 56 | //! .or_else(|_| EnvFilter::try_new("info")) 57 | //! .unwrap(); 58 | //! 59 | //! tracing_subscriber::registry() 60 | //! // ----------------------------------------------- 61 | //! .with(CallTreeCollector::default()) 62 | //! // ----------------------------------------------- 63 | //! .with(filter_layer) 64 | //! .with(fmt_layer) 65 | //! .init(); 66 | //! ``` 67 | //! 68 | //! Instead of `CallTreeCollector::default()` you can chose a more explicit 69 | //! config using [CallTreeCollectorBuilder] and 70 | //! [LoggingCallTreeCollectorBuilder]. 71 | //! 72 | //! ``` 73 | //! use reqray::{CallTreeCollectorBuilder, display::LoggingCallTreeCollectorBuilder}; 74 | //! use tracing_subscriber::{EnvFilter, util::SubscriberInitExt, fmt, prelude::*}; 75 | //! 76 | //! # let fmt_layer = fmt::layer().with_target(false); 77 | //! # let filter_layer = EnvFilter::try_from_default_env() 78 | //! # .or_else(|_| EnvFilter::try_new("info")) 79 | //! # .unwrap(); 80 | //! // ... 81 | //! let call_tree_collector = CallTreeCollectorBuilder::default() 82 | //! .max_call_depth(10) 83 | //! .build_with_collector( 84 | //! LoggingCallTreeCollectorBuilder::default() 85 | //! .left_margin(20) 86 | //! .build(), 87 | //! ); 88 | //! 89 | //! tracing_subscriber::registry() 90 | //! .with(call_tree_collector) 91 | //! // ... 92 | //! # .with(filter_layer) 93 | //! # .with(fmt_layer) 94 | //! # .init(); 95 | //! ``` 96 | 97 | pub mod display; 98 | mod internal; 99 | 100 | use display::{LoggingCallTreeCollector, LoggingCallTreeCollectorBuilder}; 101 | use quanta::Clock; 102 | 103 | // These are internal and republished here to force code in the 104 | // display model to use the public interface. 105 | pub use internal::{CallPathPool, CallPathPoolId, CallPathTiming}; 106 | 107 | /// A [tracing::Subscriber] which collects call trees and hands finished trees 108 | /// to a [FinishedCallTreeProcessor]. 109 | /// 110 | /// Use [CallTreeCollector::default()] if you want to log all call trees with 111 | /// the standard configuration. 112 | /// 113 | /// Use [CallTreeCollectorBuilder] together with e.g. 114 | /// [LoggingCallTreeCollectorBuilder] to customize your setup. 115 | pub struct CallTreeCollector { 116 | /// The clock to use for determing call timings. 117 | clock: Clock, 118 | /// Ignore calls beyond this depth. 119 | max_call_depth: usize, 120 | processor: H, 121 | } 122 | 123 | impl Default for CallTreeCollector { 124 | fn default() -> Self { 125 | CallTreeCollectorBuilder::default() 126 | .build_with_collector(LoggingCallTreeCollectorBuilder::default().build()) 127 | } 128 | } 129 | 130 | /// A [FinishedCallTreeProcessor] uses the aggregated call tree for 131 | /// something useful. 132 | /// 133 | /// Expected use cases: 134 | /// 135 | /// * Log the call tree 136 | /// * Generate metrics from the call tree 137 | /// * Do anamoly detection on the call tree 138 | /// * Send the call tree elswhere for further aggregation 139 | pub trait FinishedCallTreeProcessor { 140 | fn process_finished_call(&self, pool: CallPathPool); 141 | } 142 | 143 | /// Configure & Build [CallTreeCollector]s. 144 | /// 145 | /// Example: 146 | /// 147 | /// ``` 148 | /// use reqray::{CallTreeCollectorBuilder, display::LoggingCallTreeCollectorBuilder}; 149 | /// 150 | /// let collector = 151 | /// CallTreeCollectorBuilder::default() 152 | /// .max_call_depth(42) 153 | /// .build_with_collector( 154 | /// LoggingCallTreeCollectorBuilder::default() 155 | /// .left_margin(20) 156 | /// .build() 157 | /// ); 158 | /// ``` 159 | pub struct CallTreeCollectorBuilder { 160 | clock: Option, 161 | max_call_depth: usize, 162 | } 163 | 164 | impl Default for CallTreeCollectorBuilder { 165 | fn default() -> Self { 166 | CallTreeCollectorBuilder { 167 | clock: None, 168 | max_call_depth: 10, 169 | } 170 | } 171 | } 172 | 173 | impl CallTreeCollectorBuilder { 174 | /// The clock to use for measure execution time. 175 | /// 176 | /// The default is to use a real clock, but you can pass 177 | /// in a mock clock for testing. 178 | pub fn clock(mut self, clock: Clock) -> Self { 179 | self.clock = Some(clock); 180 | self 181 | } 182 | 183 | /// The maximum call depth of the call tree to record -- must be 184 | /// at least `2`. 185 | /// 186 | /// Call paths below this depth are capped -- so their execution 187 | /// is recorded as if they were inlined. 188 | pub fn max_call_depth(mut self, max_call_depth: usize) -> Self { 189 | self.max_call_depth = max_call_depth; 190 | self 191 | } 192 | 193 | /// Build the [CallTreeCollector] handing over the finished call trees 194 | /// to `collector`. 195 | pub fn build_with_collector(self, processor: H) -> CallTreeCollector 196 | where 197 | H: FinishedCallTreeProcessor + 'static, 198 | { 199 | CallTreeCollector { 200 | clock: self.clock.unwrap_or_else(Clock::new), 201 | max_call_depth: core::cmp::max(2, self.max_call_depth), 202 | processor, 203 | } 204 | } 205 | } 206 | --------------------------------------------------------------------------------