├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples └── example.rs └── src ├── backtrace_support.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.19.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "alloc-track" 22 | version = "0.3.1" 23 | dependencies = [ 24 | "backtrace", 25 | "dashmap", 26 | "lazy_static", 27 | "libc", 28 | "procfs", 29 | "windows", 30 | ] 31 | 32 | [[package]] 33 | name = "android_system_properties" 34 | version = "0.1.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 37 | dependencies = [ 38 | "libc", 39 | ] 40 | 41 | [[package]] 42 | name = "autocfg" 43 | version = "1.1.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 46 | 47 | [[package]] 48 | name = "backtrace" 49 | version = "0.3.67" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" 52 | dependencies = [ 53 | "addr2line", 54 | "cc", 55 | "cfg-if", 56 | "libc", 57 | "miniz_oxide", 58 | "object", 59 | "rustc-demangle", 60 | ] 61 | 62 | [[package]] 63 | name = "bitflags" 64 | version = "1.3.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 67 | 68 | [[package]] 69 | name = "bumpalo" 70 | version = "3.11.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 73 | 74 | [[package]] 75 | name = "byteorder" 76 | version = "1.4.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 79 | 80 | [[package]] 81 | name = "cc" 82 | version = "1.0.78" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 85 | 86 | [[package]] 87 | name = "cfg-if" 88 | version = "1.0.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 91 | 92 | [[package]] 93 | name = "chrono" 94 | version = "0.4.23" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 97 | dependencies = [ 98 | "iana-time-zone", 99 | "num-integer", 100 | "num-traits", 101 | "winapi", 102 | ] 103 | 104 | [[package]] 105 | name = "codespan-reporting" 106 | version = "0.11.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 109 | dependencies = [ 110 | "termcolor", 111 | "unicode-width", 112 | ] 113 | 114 | [[package]] 115 | name = "core-foundation-sys" 116 | version = "0.8.3" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 119 | 120 | [[package]] 121 | name = "crc32fast" 122 | version = "1.3.2" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 125 | dependencies = [ 126 | "cfg-if", 127 | ] 128 | 129 | [[package]] 130 | name = "cxx" 131 | version = "1.0.85" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" 134 | dependencies = [ 135 | "cc", 136 | "cxxbridge-flags", 137 | "cxxbridge-macro", 138 | "link-cplusplus", 139 | ] 140 | 141 | [[package]] 142 | name = "cxx-build" 143 | version = "1.0.85" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" 146 | dependencies = [ 147 | "cc", 148 | "codespan-reporting", 149 | "once_cell", 150 | "proc-macro2", 151 | "quote", 152 | "scratch", 153 | "syn", 154 | ] 155 | 156 | [[package]] 157 | name = "cxxbridge-flags" 158 | version = "1.0.85" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" 161 | 162 | [[package]] 163 | name = "cxxbridge-macro" 164 | version = "1.0.85" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" 167 | dependencies = [ 168 | "proc-macro2", 169 | "quote", 170 | "syn", 171 | ] 172 | 173 | [[package]] 174 | name = "dashmap" 175 | version = "5.4.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" 178 | dependencies = [ 179 | "cfg-if", 180 | "hashbrown", 181 | "lock_api", 182 | "once_cell", 183 | "parking_lot_core", 184 | ] 185 | 186 | [[package]] 187 | name = "errno" 188 | version = "0.2.8" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 191 | dependencies = [ 192 | "errno-dragonfly", 193 | "libc", 194 | "winapi", 195 | ] 196 | 197 | [[package]] 198 | name = "errno-dragonfly" 199 | version = "0.1.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 202 | dependencies = [ 203 | "cc", 204 | "libc", 205 | ] 206 | 207 | [[package]] 208 | name = "flate2" 209 | version = "1.0.25" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" 212 | dependencies = [ 213 | "crc32fast", 214 | "miniz_oxide", 215 | ] 216 | 217 | [[package]] 218 | name = "gimli" 219 | version = "0.27.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" 222 | 223 | [[package]] 224 | name = "hashbrown" 225 | version = "0.12.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 228 | 229 | [[package]] 230 | name = "hex" 231 | version = "0.4.3" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 234 | 235 | [[package]] 236 | name = "iana-time-zone" 237 | version = "0.1.53" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 240 | dependencies = [ 241 | "android_system_properties", 242 | "core-foundation-sys", 243 | "iana-time-zone-haiku", 244 | "js-sys", 245 | "wasm-bindgen", 246 | "winapi", 247 | ] 248 | 249 | [[package]] 250 | name = "iana-time-zone-haiku" 251 | version = "0.1.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 254 | dependencies = [ 255 | "cxx", 256 | "cxx-build", 257 | ] 258 | 259 | [[package]] 260 | name = "io-lifetimes" 261 | version = "1.0.3" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" 264 | dependencies = [ 265 | "libc", 266 | "windows-sys", 267 | ] 268 | 269 | [[package]] 270 | name = "js-sys" 271 | version = "0.3.60" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 274 | dependencies = [ 275 | "wasm-bindgen", 276 | ] 277 | 278 | [[package]] 279 | name = "lazy_static" 280 | version = "1.4.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 283 | 284 | [[package]] 285 | name = "libc" 286 | version = "0.2.139" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 289 | 290 | [[package]] 291 | name = "link-cplusplus" 292 | version = "1.0.8" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 295 | dependencies = [ 296 | "cc", 297 | ] 298 | 299 | [[package]] 300 | name = "linux-raw-sys" 301 | version = "0.1.4" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 304 | 305 | [[package]] 306 | name = "lock_api" 307 | version = "0.4.9" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 310 | dependencies = [ 311 | "autocfg", 312 | "scopeguard", 313 | ] 314 | 315 | [[package]] 316 | name = "log" 317 | version = "0.4.17" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 320 | dependencies = [ 321 | "cfg-if", 322 | ] 323 | 324 | [[package]] 325 | name = "memchr" 326 | version = "2.5.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 329 | 330 | [[package]] 331 | name = "miniz_oxide" 332 | version = "0.6.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 335 | dependencies = [ 336 | "adler", 337 | ] 338 | 339 | [[package]] 340 | name = "num-integer" 341 | version = "0.1.45" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 344 | dependencies = [ 345 | "autocfg", 346 | "num-traits", 347 | ] 348 | 349 | [[package]] 350 | name = "num-traits" 351 | version = "0.2.15" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 354 | dependencies = [ 355 | "autocfg", 356 | ] 357 | 358 | [[package]] 359 | name = "object" 360 | version = "0.30.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" 363 | dependencies = [ 364 | "memchr", 365 | ] 366 | 367 | [[package]] 368 | name = "once_cell" 369 | version = "1.17.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 372 | 373 | [[package]] 374 | name = "parking_lot_core" 375 | version = "0.9.5" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" 378 | dependencies = [ 379 | "cfg-if", 380 | "libc", 381 | "redox_syscall", 382 | "smallvec", 383 | "windows-sys", 384 | ] 385 | 386 | [[package]] 387 | name = "proc-macro2" 388 | version = "1.0.49" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 391 | dependencies = [ 392 | "unicode-ident", 393 | ] 394 | 395 | [[package]] 396 | name = "procfs" 397 | version = "0.14.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" 400 | dependencies = [ 401 | "bitflags", 402 | "byteorder", 403 | "chrono", 404 | "flate2", 405 | "hex", 406 | "lazy_static", 407 | "rustix", 408 | ] 409 | 410 | [[package]] 411 | name = "quote" 412 | version = "1.0.23" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 415 | dependencies = [ 416 | "proc-macro2", 417 | ] 418 | 419 | [[package]] 420 | name = "redox_syscall" 421 | version = "0.2.16" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 424 | dependencies = [ 425 | "bitflags", 426 | ] 427 | 428 | [[package]] 429 | name = "rustc-demangle" 430 | version = "0.1.21" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 433 | 434 | [[package]] 435 | name = "rustix" 436 | version = "0.36.6" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" 439 | dependencies = [ 440 | "bitflags", 441 | "errno", 442 | "io-lifetimes", 443 | "libc", 444 | "linux-raw-sys", 445 | "windows-sys", 446 | ] 447 | 448 | [[package]] 449 | name = "scopeguard" 450 | version = "1.1.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 453 | 454 | [[package]] 455 | name = "scratch" 456 | version = "1.0.3" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" 459 | 460 | [[package]] 461 | name = "smallvec" 462 | version = "1.10.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 465 | 466 | [[package]] 467 | name = "syn" 468 | version = "1.0.107" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "unicode-ident", 475 | ] 476 | 477 | [[package]] 478 | name = "termcolor" 479 | version = "1.1.3" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 482 | dependencies = [ 483 | "winapi-util", 484 | ] 485 | 486 | [[package]] 487 | name = "unicode-ident" 488 | version = "1.0.6" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 491 | 492 | [[package]] 493 | name = "unicode-width" 494 | version = "0.1.10" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 497 | 498 | [[package]] 499 | name = "wasm-bindgen" 500 | version = "0.2.83" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 503 | dependencies = [ 504 | "cfg-if", 505 | "wasm-bindgen-macro", 506 | ] 507 | 508 | [[package]] 509 | name = "wasm-bindgen-backend" 510 | version = "0.2.83" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 513 | dependencies = [ 514 | "bumpalo", 515 | "log", 516 | "once_cell", 517 | "proc-macro2", 518 | "quote", 519 | "syn", 520 | "wasm-bindgen-shared", 521 | ] 522 | 523 | [[package]] 524 | name = "wasm-bindgen-macro" 525 | version = "0.2.83" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 528 | dependencies = [ 529 | "quote", 530 | "wasm-bindgen-macro-support", 531 | ] 532 | 533 | [[package]] 534 | name = "wasm-bindgen-macro-support" 535 | version = "0.2.83" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 538 | dependencies = [ 539 | "proc-macro2", 540 | "quote", 541 | "syn", 542 | "wasm-bindgen-backend", 543 | "wasm-bindgen-shared", 544 | ] 545 | 546 | [[package]] 547 | name = "wasm-bindgen-shared" 548 | version = "0.2.83" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 551 | 552 | [[package]] 553 | name = "winapi" 554 | version = "0.3.9" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 557 | dependencies = [ 558 | "winapi-i686-pc-windows-gnu", 559 | "winapi-x86_64-pc-windows-gnu", 560 | ] 561 | 562 | [[package]] 563 | name = "winapi-i686-pc-windows-gnu" 564 | version = "0.4.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 567 | 568 | [[package]] 569 | name = "winapi-util" 570 | version = "0.1.5" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 573 | dependencies = [ 574 | "winapi", 575 | ] 576 | 577 | [[package]] 578 | name = "winapi-x86_64-pc-windows-gnu" 579 | version = "0.4.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 582 | 583 | [[package]] 584 | name = "windows" 585 | version = "0.44.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" 588 | dependencies = [ 589 | "windows-targets", 590 | ] 591 | 592 | [[package]] 593 | name = "windows-sys" 594 | version = "0.42.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 597 | dependencies = [ 598 | "windows_aarch64_gnullvm", 599 | "windows_aarch64_msvc", 600 | "windows_i686_gnu", 601 | "windows_i686_msvc", 602 | "windows_x86_64_gnu", 603 | "windows_x86_64_gnullvm", 604 | "windows_x86_64_msvc", 605 | ] 606 | 607 | [[package]] 608 | name = "windows-targets" 609 | version = "0.42.1" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 612 | dependencies = [ 613 | "windows_aarch64_gnullvm", 614 | "windows_aarch64_msvc", 615 | "windows_i686_gnu", 616 | "windows_i686_msvc", 617 | "windows_x86_64_gnu", 618 | "windows_x86_64_gnullvm", 619 | "windows_x86_64_msvc", 620 | ] 621 | 622 | [[package]] 623 | name = "windows_aarch64_gnullvm" 624 | version = "0.42.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 627 | 628 | [[package]] 629 | name = "windows_aarch64_msvc" 630 | version = "0.42.1" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 633 | 634 | [[package]] 635 | name = "windows_i686_gnu" 636 | version = "0.42.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 639 | 640 | [[package]] 641 | name = "windows_i686_msvc" 642 | version = "0.42.1" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 645 | 646 | [[package]] 647 | name = "windows_x86_64_gnu" 648 | version = "0.42.1" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 651 | 652 | [[package]] 653 | name = "windows_x86_64_gnullvm" 654 | version = "0.42.1" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 657 | 658 | [[package]] 659 | name = "windows_x86_64_msvc" 660 | version = "0.42.1" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 663 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alloc-track" 3 | version = "0.3.1" 4 | edition = "2021" 5 | authors = ["Protryon "] 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/Protryon/alloc-track" 8 | description = "Track memory allocations by backtrace or originating thread" 9 | keywords = [ "memory", "alloc", "trace", "segmentation", "leak" ] 10 | 11 | [dependencies] 12 | dashmap = "5.3" 13 | lazy_static = "1.4" 14 | backtrace = { version = "0.3", optional = true } 15 | 16 | [target.'cfg(unix)'.dependencies] 17 | libc = { version = "0.2", optional = true } 18 | procfs = { version = "0.14", optional = true } 19 | [target.'cfg(windows)'.dependencies] 20 | windows = { version = "0.44.0", features = ["Win32_System_Threading", "Win32_Foundation","Win32_System_Diagnostics_ToolHelp"], optional = true } 21 | 22 | [features] 23 | fs = ["procfs", "libc", "windows"] 24 | default = ["backtrace", "fs"] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # alloc-track 3 | 4 | This project allows per-thread and per-backtrace realtime memory profiling. 5 | 6 | ## Use Cases 7 | 8 | * Diagnosing memory fragmentation (in the form of volatile allocations) 9 | * Diagnosing memory leaks 10 | * Profiling memory consumption of individual components 11 | 12 | ## Usage 13 | 14 | 1. Add the following dependency to your project: 15 | ```alloc-track = "0.2.3"``` 16 | 17 | 2. Set a global allocator wrapped by `alloc_track::AllocTrack` 18 | 19 | Default rust allocator: 20 | ``` 21 | 22 | use alloc_track::{AllocTrack, BacktraceMode}; 23 | use std::alloc::System; 24 | 25 | #[global_allocator] 26 | static GLOBAL_ALLOC: AllocTrack = AllocTrack::new(System, BacktraceMode::Short); 27 | ``` 28 | 29 | Jemallocator allocator: 30 | ``` 31 | 32 | use alloc_track::{AllocTrack, BacktraceMode}; 33 | use jemallocator::Jemalloc; 34 | 35 | #[global_allocator] 36 | static GLOBAL_ALLOC: AllocTrack = AllocTrack::new(Jemalloc, BacktraceMode::Short); 37 | ``` 38 | 39 | 3. Call `alloc_track::thread_report()` or `alloc_track::backtrace_report()` to generate a report. Note that `backtrace_report` requires the `backtrace` feature and the `BacktraceMode::Short` or `BacktraceMode::Full` flag to be passed to `AllocTrack::new`. 40 | 41 | ## Performance 42 | 43 | In `BacktraceMode::None` or without the `backtrace` feature enabled, the thread memory profiling is reasonably performant. It is not something you would want to run in a production environment though, so feature-gating is a good idea. 44 | 45 | When backtrace logging is enabled, the performance will degrade substantially depending on the number of allocations and stack depth. Symbol resolution is delaying, but a lot of allocations means a lot of backtraces. `backtrace_report` takes a single argument, which is a filter for individual backtrace records. Filtering out uninteresting backtraces is both easier to read, and substantially faster to generate a report as symbol resolution can be skipped. See `examples/example.rs` for an example. 46 | 47 | ## Real World Example 48 | 49 | At LeakSignal, we had extreme memory segmentation in a high-bandwidth/high-concurrency gRPC service. We suspected a known hyper issue with high concurrency, but needed to confirm the cause and fix the issue ASAP. Existing tooling (bpftrace, valgrind) wasn't able to give us a concrete cause. I had created a prototype of this project back in 2019 or so, and it's time had come to shine. In a staging environment, I added an HTTP endpoint to generate a thread and backtrace report. I was able to identify a location where a large multi-allocation object was being cloned and dropped very often. A quick fix there solved our memory segmentation issue. 50 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | alloc::System, 3 | sync::mpsc, 4 | time::{Duration, Instant}, 5 | }; 6 | 7 | use alloc_track::{AllocTrack, BacktraceMode}; 8 | 9 | #[global_allocator] 10 | static GLOBAL_ALLOC: AllocTrack = AllocTrack::new(System, BacktraceMode::Short); 11 | 12 | fn main() { 13 | let (sender, receiver) = mpsc::channel(); 14 | std::thread::Builder::new() 15 | .name("thread2".to_string()) 16 | .spawn(move || thread(receiver)) 17 | .unwrap(); 18 | 19 | let mut last_print = Instant::now(); 20 | loop { 21 | let buf = vec![1u8; 1024]; 22 | sender.send(buf).ok(); 23 | std::thread::sleep(Duration::from_millis(100)); 24 | if last_print.elapsed() > Duration::from_secs(10) { 25 | last_print = Instant::now(); 26 | let report = alloc_track::backtrace_report(|_, _| true); 27 | println!("BACKTRACES\n{report}"); 28 | println!("BACKTRACES CSV\n{}", report.csv()); 29 | let report = alloc_track::thread_report(); 30 | println!("THREADS\n{report}"); 31 | } 32 | } 33 | } 34 | 35 | fn thread(receiver: mpsc::Receiver>) { 36 | let mut flip = false; 37 | while let Ok(block) = receiver.recv() { 38 | if flip { 39 | drop(block); 40 | } else { 41 | std::mem::forget(block); 42 | } 43 | flip = !flip; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/backtrace_support.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::DefaultHasher; 2 | use std::fmt::{self, Write}; 3 | use std::hash::{Hash, Hasher}; 4 | 5 | pub use backtrace; 6 | use backtrace::{Backtrace, BacktraceFmt, BytesOrWideString, PrintFmt}; 7 | 8 | use crate::{BacktraceMode, Size, SizeF64}; 9 | 10 | #[derive(Clone)] 11 | pub struct HashedBacktrace { 12 | inner: Option, 13 | hash: u64, 14 | } 15 | 16 | pub(super) struct TraceInfo { 17 | pub backtrace: HashedBacktrace, 18 | pub allocated: u64, 19 | pub freed: u64, 20 | pub allocations: u64, 21 | pub mode: BacktraceMode, 22 | } 23 | 24 | struct HashedBacktraceShort<'a>(&'a HashedBacktrace); 25 | 26 | impl<'a> fmt::Display for HashedBacktraceShort<'a> { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | self.0.display_short(f) 29 | } 30 | } 31 | 32 | impl HashedBacktrace { 33 | pub fn capture(mode: BacktraceMode) -> Self { 34 | if matches!(mode, BacktraceMode::None) { 35 | return Self { 36 | inner: None, 37 | hash: 0, 38 | }; 39 | } 40 | let backtrace = Backtrace::new_unresolved(); 41 | let mut hasher = DefaultHasher::new(); 42 | backtrace 43 | .frames() 44 | .iter() 45 | .for_each(|x| hasher.write_u64(x.ip() as u64)); 46 | let hash = hasher.finish(); 47 | Self { 48 | inner: Some(backtrace), 49 | hash, 50 | } 51 | } 52 | 53 | pub fn inner(&self) -> &Backtrace { 54 | self.inner.as_ref().unwrap() 55 | } 56 | 57 | pub fn inner_mut(&mut self) -> &mut Backtrace { 58 | self.inner.as_mut().unwrap() 59 | } 60 | 61 | pub fn hash(&self) -> u64 { 62 | self.hash 63 | } 64 | 65 | fn display_short(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | let full = f.alternate(); 67 | let frames = self.inner().frames(); 68 | 69 | let cwd = std::env::current_dir(); 70 | let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| { 71 | let path = path.into_path_buf(); 72 | if !full { 73 | if let Ok(cwd) = &cwd { 74 | if let Ok(suffix) = path.strip_prefix(cwd) { 75 | return fmt::Display::fmt(&suffix.display(), fmt); 76 | } 77 | } 78 | } 79 | fmt::Display::fmt(&path.display(), fmt) 80 | }; 81 | 82 | let mut f = BacktraceFmt::new(f, PrintFmt::Short, &mut print_path); 83 | f.add_context()?; 84 | for frame in frames { 85 | let symbols = frame.symbols(); 86 | for symbol in symbols { 87 | if let Some(name) = symbol.name().map(|x| x.to_string()) { 88 | let name = name.strip_prefix('<').unwrap_or(&name); 89 | if name.starts_with("alloc_track::") 90 | || name == "__rg_alloc" 91 | || name.starts_with("alloc::") 92 | || name.starts_with("std::panicking::") 93 | || name == "__rust_try" 94 | || name == "_start" 95 | || name == "__libc_start_main_impl" 96 | || name == "__libc_start_call_main" 97 | || name.starts_with("std::rt::") 98 | { 99 | continue; 100 | } 101 | } 102 | 103 | f.frame().backtrace_symbol(frame, symbol)?; 104 | } 105 | if symbols.is_empty() { 106 | f.frame().print_raw(frame.ip(), None, None, None)?; 107 | } 108 | } 109 | f.finish()?; 110 | Ok(()) 111 | } 112 | } 113 | 114 | impl PartialEq for HashedBacktrace { 115 | fn eq(&self, other: &Self) -> bool { 116 | self.hash == other.hash 117 | } 118 | } 119 | 120 | impl Eq for HashedBacktrace {} 121 | 122 | impl Hash for HashedBacktrace { 123 | fn hash(&self, state: &mut H) { 124 | self.hash.hash(state); 125 | } 126 | } 127 | 128 | /// Allocation information pertaining to a specific backtrace. 129 | #[derive(Debug, Clone, Default)] 130 | pub struct BacktraceMetric { 131 | /// Number of bytes allocated 132 | pub allocated: u64, 133 | /// Number of bytes allocated here that have since been freed 134 | pub freed: u64, 135 | /// Number of actual allocations 136 | pub allocations: u64, 137 | /// `mode` as copied from `AllocTrack` 138 | pub mode: BacktraceMode, 139 | } 140 | 141 | impl BacktraceMetric { 142 | /// Number of bytes currently allocated and not freed 143 | pub fn in_use(&self) -> u64 { 144 | self.allocated.saturating_sub(self.freed) 145 | } 146 | 147 | /// Average number of bytes per allocation 148 | pub fn avg_allocation(&self) -> f64 { 149 | if self.allocations == 0 { 150 | 0.0 151 | } else { 152 | self.allocated as f64 / self.allocations as f64 153 | } 154 | } 155 | } 156 | 157 | impl fmt::Display for BacktraceMetric { 158 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 159 | writeln!(f, "allocated: {}", Size(self.allocated))?; 160 | writeln!(f, "allocations: {}", self.allocations)?; 161 | writeln!(f, "avg_allocation: {}", SizeF64(self.avg_allocation()))?; 162 | writeln!(f, "freed: {}", Size(self.freed))?; 163 | writeln!(f, "total_used: {}", Size(self.in_use()))?; 164 | Ok(()) 165 | } 166 | } 167 | 168 | impl BacktraceMetric { 169 | pub fn csv_write(&self, out: &mut impl Write) -> fmt::Result { 170 | write!( 171 | out, 172 | "{},{},{},{},{}", 173 | self.allocated, 174 | self.allocations, 175 | self.avg_allocation(), 176 | self.freed, 177 | self.in_use() 178 | )?; 179 | Ok(()) 180 | } 181 | } 182 | 183 | /// A report of all (post-filter) backtraces and their associated allocations metrics. 184 | pub struct BacktraceReport(pub Vec<(HashedBacktrace, BacktraceMetric)>); 185 | 186 | impl BacktraceReport { 187 | pub fn csv(&self) -> String { 188 | let mut out = String::new(); 189 | write!( 190 | &mut out, 191 | "allocated,allocations,avg_allocation,freed,total_used,backtrace\n" 192 | ) 193 | .unwrap(); 194 | for (backtrace, metric) in &self.0 { 195 | match metric.mode { 196 | BacktraceMode::None => unreachable!(), 197 | BacktraceMode::Short => { 198 | metric.csv_write(&mut out).unwrap(); 199 | writeln!( 200 | &mut out, 201 | ",\"{}\"", 202 | HashedBacktraceShort(backtrace) 203 | .to_string() 204 | .replace("\\", "\\\\") 205 | .replace("\n", "\\n") 206 | ) 207 | .unwrap(); 208 | } 209 | BacktraceMode::Full => { 210 | metric.csv_write(&mut out).unwrap(); 211 | writeln!( 212 | &mut out, 213 | ",\"{}\"", 214 | format!("{:?}", backtrace.inner()) 215 | .replace("\\", "\\\\") 216 | .replace("\n", "\\n") 217 | ) 218 | .unwrap(); 219 | } 220 | } 221 | } 222 | out 223 | } 224 | } 225 | 226 | impl fmt::Display for BacktraceReport { 227 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 228 | for (backtrace, metric) in &self.0 { 229 | match metric.mode { 230 | BacktraceMode::None => unreachable!(), 231 | BacktraceMode::Short => { 232 | writeln!(f, "{}\n{metric}\n\n", HashedBacktraceShort(backtrace))? 233 | } 234 | BacktraceMode::Full => writeln!(f, "{:?}\n{metric}\n\n", backtrace.inner())?, 235 | } 236 | } 237 | Ok(()) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use dashmap::DashMap; 4 | #[allow(unused_imports)] 5 | use std::collections::HashMap; 6 | use std::{ 7 | alloc::{GlobalAlloc, Layout}, 8 | cell::Cell, 9 | collections::BTreeMap, 10 | fmt, 11 | sync::atomic::{AtomicU32, AtomicUsize, Ordering}, 12 | }; 13 | 14 | #[cfg(feature = "backtrace")] 15 | mod backtrace_support; 16 | #[cfg(feature = "backtrace")] 17 | use backtrace_support::*; 18 | #[cfg(feature = "backtrace")] 19 | pub use backtrace_support::{BacktraceMetric, BacktraceReport, HashedBacktrace}; 20 | 21 | /// next thread id incrementor 22 | static THREAD_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); 23 | 24 | /// On linux you can check your system by running `cat /proc/sys/kernel/threads-max` 25 | /// It's almost certain that this limit will be hit in some strange corner cases. 26 | const MAX_THREADS: usize = 1024; 27 | 28 | #[derive(Clone, Copy, Debug)] 29 | struct PointerData { 30 | alloc_thread_id: usize, 31 | #[cfg(feature = "backtrace")] 32 | trace_hash: u64, 33 | } 34 | 35 | lazy_static::lazy_static! { 36 | /// pointer -> data 37 | static ref PTR_MAP: DashMap = DashMap::new(); 38 | // backtrace -> current allocation size 39 | #[cfg(feature = "backtrace")] 40 | static ref TRACE_MAP: DashMap = DashMap::new(); 41 | } 42 | 43 | /// Representation of globally-accessible TLS 44 | struct ThreadStore { 45 | #[allow(dead_code)] 46 | tid: AtomicU32, 47 | alloc: AtomicUsize, 48 | free: [AtomicUsize; MAX_THREADS], 49 | } 50 | 51 | /// A layout compatible representation of `ThreadStore` that is `Copy`. 52 | /// Layout of `AtomicX` is guaranteed to be identical to `X` 53 | /// Used to initialize arrays. 54 | #[allow(dead_code)] 55 | #[derive(Clone, Copy)] 56 | struct ThreadStoreLocal { 57 | tid: u32, 58 | alloc: usize, 59 | free: [usize; MAX_THREADS], 60 | } 61 | 62 | /// Custom psuedo-TLS implementation that allows safe global introspection 63 | static THREAD_STORE: [ThreadStore; MAX_THREADS] = unsafe { 64 | std::mem::transmute( 65 | [ThreadStoreLocal { 66 | tid: 0, 67 | alloc: 0, 68 | free: [0usize; MAX_THREADS], 69 | }; MAX_THREADS], 70 | ) 71 | }; 72 | 73 | thread_local! { 74 | static THREAD_ID: usize = THREAD_ID_COUNTER.fetch_add(1, Ordering::Relaxed); 75 | /// Used to avoid recursive alloc/dealloc calls for interior allocation 76 | static IN_ALLOC: Cell = Cell::new(false); 77 | } 78 | 79 | fn enter_alloc(func: impl FnOnce() -> T) -> T { 80 | let current_value = IN_ALLOC.with(|x| x.get()); 81 | IN_ALLOC.with(|x| x.set(true)); 82 | let output = func(); 83 | IN_ALLOC.with(|x| x.set(current_value)); 84 | output 85 | } 86 | 87 | #[derive(Default, Clone, Copy, Debug, PartialEq)] 88 | pub enum BacktraceMode { 89 | #[default] 90 | /// Report no backtraces 91 | None, 92 | #[cfg(feature = "backtrace")] 93 | /// Report backtraces with unuseful entries removed (i.e. alloc_track, allocator internals) 94 | Short, 95 | /// Report the full backtrace 96 | #[cfg(feature = "backtrace")] 97 | Full, 98 | } 99 | 100 | /// Global memory allocator wrapper that can track per-thread and per-backtrace memory usage. 101 | pub struct AllocTrack { 102 | inner: T, 103 | backtrace: BacktraceMode, 104 | } 105 | 106 | impl AllocTrack { 107 | pub const fn new(inner: T, backtrace: BacktraceMode) -> Self { 108 | Self { inner, backtrace } 109 | } 110 | } 111 | #[cfg(all(unix, feature = "fs"))] 112 | #[inline(always)] 113 | unsafe fn get_sys_tid() -> u32 { 114 | libc::syscall(libc::SYS_gettid) as u32 115 | } 116 | 117 | #[cfg(all(windows, feature = "fs"))] 118 | #[inline(always)] 119 | unsafe fn get_sys_tid() -> u32 { 120 | windows::Win32::System::Threading::GetCurrentThreadId() 121 | } 122 | 123 | unsafe impl GlobalAlloc for AllocTrack { 124 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 125 | if IN_ALLOC.with(|x| x.get()) { 126 | return self.inner.alloc(layout); 127 | } 128 | enter_alloc(|| { 129 | let size = layout.size(); 130 | let ptr = self.inner.alloc(layout); 131 | let tid = THREAD_ID.with(|x| *x); 132 | assert!( 133 | tid < MAX_THREADS, 134 | "Thread ID {tid} is greater than the maximum number of threads {MAX_THREADS}" 135 | ); 136 | #[cfg(feature = "fs")] 137 | if THREAD_STORE[tid].tid.load(Ordering::Relaxed) == 0 { 138 | let os_tid = get_sys_tid(); 139 | THREAD_STORE[tid].tid.store(os_tid, Ordering::Relaxed); 140 | } 141 | THREAD_STORE[tid].alloc.fetch_add(size, Ordering::Relaxed); 142 | #[cfg(feature = "backtrace")] 143 | let trace = HashedBacktrace::capture(self.backtrace); 144 | PTR_MAP.insert( 145 | ptr as usize, 146 | PointerData { 147 | alloc_thread_id: tid, 148 | #[cfg(feature = "backtrace")] 149 | trace_hash: trace.hash(), 150 | }, 151 | ); 152 | #[cfg(feature = "backtrace")] 153 | if !matches!(self.backtrace, BacktraceMode::None) { 154 | let mut trace_info = TRACE_MAP.entry(trace.hash()).or_insert_with(|| TraceInfo { 155 | backtrace: trace, 156 | allocated: 0, 157 | freed: 0, 158 | mode: self.backtrace, 159 | allocations: 0, 160 | }); 161 | trace_info.allocated += size as u64; 162 | trace_info.allocations += 1; 163 | } 164 | ptr 165 | }) 166 | } 167 | 168 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 169 | if IN_ALLOC.with(|x| x.get()) { 170 | self.inner.dealloc(ptr, layout); 171 | return; 172 | } 173 | enter_alloc(|| { 174 | let size = layout.size(); 175 | let (_, target) = PTR_MAP.remove(&(ptr as usize)).expect("double free"); 176 | #[cfg(feature = "backtrace")] 177 | if !matches!(self.backtrace, BacktraceMode::None) { 178 | if let Some(mut info) = TRACE_MAP.get_mut(&target.trace_hash) { 179 | info.freed += size as u64; 180 | } 181 | } 182 | self.inner.dealloc(ptr, layout); 183 | let tid = THREAD_ID.with(|x| *x); 184 | THREAD_STORE[tid].free[target.alloc_thread_id].fetch_add(size, Ordering::SeqCst); 185 | }); 186 | } 187 | } 188 | 189 | /// Size display helper 190 | pub struct Size(pub u64); 191 | 192 | impl fmt::Display for Size { 193 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 194 | if self.0 < 1024 { 195 | write!(f, "{} B", self.0) 196 | } else if self.0 < 1024 * 1024 { 197 | write!(f, "{} KB", self.0 / 1024) 198 | } else { 199 | write!(f, "{} MB", self.0 / 1024 / 1024) 200 | } 201 | } 202 | } 203 | 204 | /// Size display helper 205 | pub struct SizeF64(pub f64); 206 | 207 | impl fmt::Display for SizeF64 { 208 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 209 | if self.0 < 1024.0 { 210 | write!(f, "{:.01} B", self.0) 211 | } else if self.0 < 1024.0 * 1024.0 { 212 | write!(f, "{:.01} KB", self.0 / 1024.0) 213 | } else { 214 | write!(f, "{:.01} MB", self.0 / 1024.0 / 1024.0) 215 | } 216 | } 217 | } 218 | 219 | #[derive(Debug, Clone, Default)] 220 | pub struct ThreadMetric { 221 | /// Total bytes allocated in this thread 222 | pub total_alloc: u64, 223 | /// Total bytes freed in this thread 224 | pub total_did_free: u64, 225 | /// Total bytes allocated in this thread that have been freed 226 | pub total_freed: u64, 227 | /// Total bytes allocated in this thread that are not freed 228 | pub current_used: u64, 229 | /// Total bytes allocated in this thread that have been freed by the given thread 230 | pub freed_by_others: BTreeMap, 231 | } 232 | 233 | impl fmt::Display for ThreadMetric { 234 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 235 | writeln!(f, "total_alloc: {}", Size(self.total_alloc))?; 236 | writeln!(f, "total_did_free: {}", Size(self.total_did_free))?; 237 | writeln!(f, "total_freed: {}", Size(self.total_freed))?; 238 | writeln!(f, "current_used: {}", Size(self.current_used))?; 239 | for (name, size) in &self.freed_by_others { 240 | writeln!(f, "freed by {}: {}", name, Size(*size))?; 241 | } 242 | Ok(()) 243 | } 244 | } 245 | 246 | /// A comprehensive report of all thread allocation metrics 247 | pub struct ThreadReport(pub BTreeMap); 248 | 249 | impl fmt::Display for ThreadReport { 250 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 251 | for (name, metric) in &self.0 { 252 | writeln!(f, "{name}:\n{metric}\n")?; 253 | } 254 | Ok(()) 255 | } 256 | } 257 | 258 | /// Generate a memory usage report for backtraces, if enabled 259 | #[cfg(feature = "backtrace")] 260 | pub fn backtrace_report( 261 | filter: impl Fn(&crate::backtrace::Backtrace, &BacktraceMetric) -> bool, 262 | ) -> BacktraceReport { 263 | IN_ALLOC.with(|x| x.set(true)); 264 | let mut out = vec![]; 265 | for mut entry in TRACE_MAP.iter_mut() { 266 | let metric = BacktraceMetric { 267 | allocated: entry.allocated, 268 | freed: entry.freed, 269 | mode: entry.mode, 270 | allocations: entry.allocations, 271 | }; 272 | if !filter(entry.backtrace.inner(), &metric) { 273 | continue; 274 | } 275 | entry.backtrace.inner_mut().resolve(); 276 | out.push((entry.backtrace.clone(), metric)); 277 | } 278 | out.sort_by_key(|x| x.1.allocated.saturating_sub(x.1.freed) as i64); 279 | IN_ALLOC.with(|x| x.set(false)); 280 | let out2 = out.clone(); 281 | IN_ALLOC.with(|x| x.set(true)); 282 | drop(out); 283 | IN_ALLOC.with(|x| x.set(false)); 284 | BacktraceReport(out2) 285 | } 286 | 287 | #[cfg(all(unix, feature = "fs"))] 288 | fn os_tid_names() -> HashMap { 289 | let mut os_tid_names: HashMap = HashMap::new(); 290 | for task in procfs::process::Process::myself().unwrap().tasks().unwrap() { 291 | let task = task.unwrap(); 292 | os_tid_names.insert( 293 | task.tid as u32, 294 | std::fs::read_to_string(format!("/proc/{}/task/{}/comm", task.pid, task.tid)) 295 | .unwrap() 296 | .trim() 297 | .to_string(), 298 | ); 299 | } 300 | os_tid_names 301 | } 302 | 303 | #[cfg(all(windows, feature = "fs"))] 304 | fn os_tid_names() -> HashMap { 305 | use std::alloc::alloc; 306 | use windows::Win32::Foundation::CloseHandle; 307 | use windows::Win32::System::Diagnostics::ToolHelp::{ 308 | Thread32First, Thread32Next, THREADENTRY32, 309 | }; 310 | let mut os_tid_names: HashMap = HashMap::new(); 311 | unsafe { 312 | let process_id = windows::Win32::System::Threading::GetCurrentProcessId(); 313 | let snapshot = windows::Win32::System::Diagnostics::ToolHelp::CreateToolhelp32Snapshot( 314 | windows::Win32::System::Diagnostics::ToolHelp::TH32CS_SNAPTHREAD, 315 | 0, 316 | ); 317 | if let Ok(snapshot) = snapshot { 318 | let mut thread_entry = alloc(Layout::new::()) as *mut THREADENTRY32; 319 | (*thread_entry).dwSize = std::mem::size_of::() as u32; 320 | if Thread32First(snapshot, thread_entry).as_bool() { 321 | loop { 322 | if (*thread_entry).th32OwnerProcessID == process_id { 323 | let thread_handle = windows::Win32::System::Threading::OpenThread( 324 | windows::Win32::System::Threading::THREAD_QUERY_LIMITED_INFORMATION, 325 | false, 326 | (*thread_entry).th32ThreadID, 327 | ); 328 | if let Ok(handle) = thread_handle { 329 | let result = 330 | windows::Win32::System::Threading::GetThreadDescription(handle); 331 | if let Ok(str) = result { 332 | os_tid_names.insert( 333 | (*thread_entry).th32ThreadID, 334 | str.to_string().unwrap_or("UTF-16 Error".to_string()), 335 | ); 336 | } else { 337 | os_tid_names 338 | .insert((*thread_entry).th32ThreadID, "unknown".to_string()); 339 | } 340 | CloseHandle(handle); 341 | } 342 | } 343 | if !Thread32Next(snapshot, thread_entry).as_bool() { 344 | break; 345 | } 346 | } 347 | } 348 | CloseHandle(snapshot); 349 | } 350 | } 351 | os_tid_names 352 | } 353 | 354 | /// Generate a memory usage report 355 | /// Note that the numbers are not a synchronized snapshot, and have slight timing skew. 356 | pub fn thread_report() -> ThreadReport { 357 | #[cfg(feature = "fs")] 358 | let os_tid_names: HashMap = os_tid_names(); 359 | #[cfg(feature = "fs")] 360 | let mut tid_names: HashMap = HashMap::new(); 361 | #[cfg(feature = "fs")] 362 | let get_tid_name = { 363 | for (i, thread) in THREAD_STORE.iter().enumerate() { 364 | let tid = thread.tid.load(Ordering::Relaxed); 365 | if tid == 0 { 366 | continue; 367 | } 368 | if let Some(name) = os_tid_names.get(&tid) { 369 | tid_names.insert(i, name); 370 | } 371 | } 372 | |id: usize| tid_names.get(&id).map(|x| &**x) 373 | }; 374 | #[cfg(not(feature = "fs"))] 375 | let get_tid_name = { move |id: usize| Some(id.to_string()) }; 376 | 377 | let mut metrics = BTreeMap::new(); 378 | 379 | for (i, thread) in THREAD_STORE.iter().enumerate() { 380 | let Some(name) = get_tid_name(i) else { 381 | continue; 382 | }; 383 | let alloced = thread.alloc.load(Ordering::Relaxed) as u64; 384 | let metric: &mut ThreadMetric = metrics.entry(name.into()).or_default(); 385 | metric.total_alloc += alloced; 386 | 387 | let mut total_free: u64 = 0; 388 | for (j, thread2) in THREAD_STORE.iter().enumerate() { 389 | let Some(name) = get_tid_name(j) else { 390 | continue; 391 | }; 392 | let freed = thread2.free[i].load(Ordering::Relaxed); 393 | if freed == 0 { 394 | continue; 395 | } 396 | total_free += freed as u64; 397 | *metric.freed_by_others.entry(name.into()).or_default() += freed as u64; 398 | } 399 | metric.total_did_free += total_free; 400 | metric.total_freed += thread 401 | .free 402 | .iter() 403 | .map(|x| x.load(Ordering::Relaxed) as u64) 404 | .sum::(); 405 | metric.current_used += alloced.saturating_sub(total_free); 406 | } 407 | ThreadReport(metrics) 408 | } 409 | 410 | #[cfg(test)] 411 | mod tests { 412 | use super::*; 413 | 414 | #[test] 415 | pub fn test_os_tid_names() { 416 | std::thread::Builder::new() 417 | .name("thread2".to_string()) 418 | .spawn(move || { 419 | std::thread::sleep(std::time::Duration::from_secs(100)); 420 | }) 421 | .unwrap(); 422 | 423 | let os_tid_names = os_tid_names(); 424 | println!("{:?}", os_tid_names); 425 | } 426 | } 427 | --------------------------------------------------------------------------------