├── .github ├── renovate.json └── workflows │ └── rust.yml ├── .gitignore ├── .kodiak.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── complex │ ├── Cargo.toml │ └── src │ │ └── main.rs └── simple │ ├── Cargo.toml │ └── src │ └── main.rs ├── rust-toolchain.toml └── src ├── collector.rs ├── hook.rs ├── jemalloc_adapter.rs ├── lib.rs └── profiler.rs /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":disableDependencyDashboard", 6 | ":maintainLockFilesWeekly", 7 | ":rebaseStalePrs", 8 | "docker:pinDigests", 9 | "group:linters", 10 | "group:recommended", 11 | "helpers:pinGitHubActionDigests" 12 | ], 13 | "includeForks": false, 14 | "labels": ["dependencies"], 15 | "packageRules": [ 16 | { 17 | "description": "Automerge trivial updates", 18 | "matchUpdateTypes": ["patch", "digest", "lockFileMaintenance"], 19 | "addLabels": ["automerge"] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [approve] 4 | auto_approve_usernames = ["dependabot", "renovate"] 5 | 6 | [merge.automerge_dependencies] 7 | usernames = ["dependabot", "renovate"] 8 | 9 | [update] 10 | ignored_usernames = ["dependabot", "renovate"] 11 | -------------------------------------------------------------------------------- /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.24.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "getrandom", 28 | "once_cell", 29 | "version_check", 30 | "zerocopy", 31 | ] 32 | 33 | [[package]] 34 | name = "aho-corasick" 35 | version = "1.1.3" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 38 | dependencies = [ 39 | "memchr", 40 | ] 41 | 42 | [[package]] 43 | name = "anyhow" 44 | version = "1.0.86" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 47 | 48 | [[package]] 49 | name = "arrayvec" 50 | version = "0.7.4" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 53 | 54 | [[package]] 55 | name = "autocfg" 56 | version = "1.3.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 59 | 60 | [[package]] 61 | name = "backtrace" 62 | version = "0.3.74" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 65 | dependencies = [ 66 | "addr2line", 67 | "cfg-if", 68 | "libc", 69 | "miniz_oxide", 70 | "object", 71 | "rustc-demangle", 72 | "windows-targets", 73 | ] 74 | 75 | [[package]] 76 | name = "bindgen" 77 | version = "0.59.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" 80 | dependencies = [ 81 | "bitflags 1.3.2", 82 | "cexpr", 83 | "clang-sys", 84 | "lazy_static", 85 | "lazycell", 86 | "peeking_take_while", 87 | "proc-macro2", 88 | "quote", 89 | "regex", 90 | "rustc-hash", 91 | "shlex", 92 | ] 93 | 94 | [[package]] 95 | name = "bitflags" 96 | version = "1.3.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 99 | 100 | [[package]] 101 | name = "bitflags" 102 | version = "2.6.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 105 | 106 | [[package]] 107 | name = "block-buffer" 108 | version = "0.10.4" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 111 | dependencies = [ 112 | "generic-array", 113 | ] 114 | 115 | [[package]] 116 | name = "bytemuck" 117 | version = "1.16.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" 120 | 121 | [[package]] 122 | name = "byteorder" 123 | version = "1.5.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 126 | 127 | [[package]] 128 | name = "bytes" 129 | version = "1.6.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" 132 | 133 | [[package]] 134 | name = "cc" 135 | version = "1.0.105" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "5208975e568d83b6b05cc0a063c8e7e9acc2b43bee6da15616a5b73e109d7437" 138 | 139 | [[package]] 140 | name = "cexpr" 141 | version = "0.6.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 144 | dependencies = [ 145 | "nom", 146 | ] 147 | 148 | [[package]] 149 | name = "cfg-if" 150 | version = "1.0.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 153 | 154 | [[package]] 155 | name = "clang-sys" 156 | version = "1.8.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 159 | dependencies = [ 160 | "glob", 161 | "libc", 162 | "libloading", 163 | ] 164 | 165 | [[package]] 166 | name = "complex" 167 | version = "0.1.0" 168 | dependencies = [ 169 | "croaring", 170 | "heappy", 171 | "prost 0.7.0", 172 | ] 173 | 174 | [[package]] 175 | name = "cpp_demangle" 176 | version = "0.4.3" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" 179 | dependencies = [ 180 | "cfg-if", 181 | ] 182 | 183 | [[package]] 184 | name = "cpufeatures" 185 | version = "0.2.12" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 188 | dependencies = [ 189 | "libc", 190 | ] 191 | 192 | [[package]] 193 | name = "croaring" 194 | version = "0.5.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "aff1eea8a79ffa2a743c1322d5c3853d45699b7842197160c7c32a18c32c1866" 197 | dependencies = [ 198 | "byteorder", 199 | "croaring-sys", 200 | "libc", 201 | ] 202 | 203 | [[package]] 204 | name = "croaring-sys" 205 | version = "0.5.2" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "21d77e33a1d5e04573f79846692e72f2e02e0fdd942b90f023c45f146d3447db" 208 | dependencies = [ 209 | "bindgen", 210 | "cc", 211 | "libc", 212 | ] 213 | 214 | [[package]] 215 | name = "crypto-common" 216 | version = "0.1.6" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 219 | dependencies = [ 220 | "generic-array", 221 | "typenum", 222 | ] 223 | 224 | [[package]] 225 | name = "debugid" 226 | version = "0.8.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" 229 | dependencies = [ 230 | "uuid", 231 | ] 232 | 233 | [[package]] 234 | name = "digest" 235 | version = "0.10.7" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 238 | dependencies = [ 239 | "block-buffer", 240 | "crypto-common", 241 | ] 242 | 243 | [[package]] 244 | name = "either" 245 | version = "1.13.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 248 | 249 | [[package]] 250 | name = "equivalent" 251 | version = "1.0.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 254 | 255 | [[package]] 256 | name = "errno" 257 | version = "0.3.9" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 260 | dependencies = [ 261 | "libc", 262 | "windows-sys", 263 | ] 264 | 265 | [[package]] 266 | name = "fastrand" 267 | version = "2.1.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 270 | 271 | [[package]] 272 | name = "findshlibs" 273 | version = "0.10.2" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" 276 | dependencies = [ 277 | "cc", 278 | "lazy_static", 279 | "libc", 280 | "winapi", 281 | ] 282 | 283 | [[package]] 284 | name = "fixedbitset" 285 | version = "0.4.2" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 288 | 289 | [[package]] 290 | name = "generic-array" 291 | version = "0.14.7" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 294 | dependencies = [ 295 | "typenum", 296 | "version_check", 297 | ] 298 | 299 | [[package]] 300 | name = "getrandom" 301 | version = "0.2.15" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 304 | dependencies = [ 305 | "cfg-if", 306 | "libc", 307 | "wasi", 308 | ] 309 | 310 | [[package]] 311 | name = "gimli" 312 | version = "0.31.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" 315 | 316 | [[package]] 317 | name = "glob" 318 | version = "0.3.1" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 321 | 322 | [[package]] 323 | name = "hashbrown" 324 | version = "0.14.5" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 327 | 328 | [[package]] 329 | name = "heappy" 330 | version = "0.1.0" 331 | dependencies = [ 332 | "backtrace", 333 | "bytes", 334 | "lazy_static", 335 | "libc", 336 | "pprof", 337 | "spin", 338 | "thiserror", 339 | "tikv-jemalloc-sys", 340 | ] 341 | 342 | [[package]] 343 | name = "heck" 344 | version = "0.5.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 347 | 348 | [[package]] 349 | name = "hermit-abi" 350 | version = "0.3.9" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 353 | 354 | [[package]] 355 | name = "indexmap" 356 | version = "2.2.6" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 359 | dependencies = [ 360 | "equivalent", 361 | "hashbrown", 362 | ] 363 | 364 | [[package]] 365 | name = "inferno" 366 | version = "0.11.19" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" 369 | dependencies = [ 370 | "ahash", 371 | "indexmap", 372 | "is-terminal", 373 | "itoa", 374 | "log", 375 | "num-format", 376 | "once_cell", 377 | "quick-xml", 378 | "rgb", 379 | "str_stack", 380 | ] 381 | 382 | [[package]] 383 | name = "is-terminal" 384 | version = "0.4.12" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 387 | dependencies = [ 388 | "hermit-abi", 389 | "libc", 390 | "windows-sys", 391 | ] 392 | 393 | [[package]] 394 | name = "itertools" 395 | version = "0.9.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 398 | dependencies = [ 399 | "either", 400 | ] 401 | 402 | [[package]] 403 | name = "itertools" 404 | version = "0.12.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 407 | dependencies = [ 408 | "either", 409 | ] 410 | 411 | [[package]] 412 | name = "itoa" 413 | version = "1.0.11" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 416 | 417 | [[package]] 418 | name = "lazy_static" 419 | version = "1.5.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 422 | 423 | [[package]] 424 | name = "lazycell" 425 | version = "1.3.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 428 | 429 | [[package]] 430 | name = "libc" 431 | version = "0.2.167" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 434 | 435 | [[package]] 436 | name = "libloading" 437 | version = "0.8.4" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" 440 | dependencies = [ 441 | "cfg-if", 442 | "windows-targets", 443 | ] 444 | 445 | [[package]] 446 | name = "linux-raw-sys" 447 | version = "0.4.14" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 450 | 451 | [[package]] 452 | name = "lock_api" 453 | version = "0.4.12" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 456 | dependencies = [ 457 | "autocfg", 458 | "scopeguard", 459 | ] 460 | 461 | [[package]] 462 | name = "log" 463 | version = "0.4.22" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 466 | 467 | [[package]] 468 | name = "memchr" 469 | version = "2.7.4" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 472 | 473 | [[package]] 474 | name = "memmap2" 475 | version = "0.9.4" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" 478 | dependencies = [ 479 | "libc", 480 | ] 481 | 482 | [[package]] 483 | name = "minimal-lexical" 484 | version = "0.2.1" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 487 | 488 | [[package]] 489 | name = "miniz_oxide" 490 | version = "0.8.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 493 | dependencies = [ 494 | "adler2", 495 | ] 496 | 497 | [[package]] 498 | name = "multimap" 499 | version = "0.10.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" 502 | 503 | [[package]] 504 | name = "nix" 505 | version = "0.26.4" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 508 | dependencies = [ 509 | "bitflags 1.3.2", 510 | "cfg-if", 511 | "libc", 512 | ] 513 | 514 | [[package]] 515 | name = "nom" 516 | version = "7.1.3" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 519 | dependencies = [ 520 | "memchr", 521 | "minimal-lexical", 522 | ] 523 | 524 | [[package]] 525 | name = "num-format" 526 | version = "0.4.4" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" 529 | dependencies = [ 530 | "arrayvec", 531 | "itoa", 532 | ] 533 | 534 | [[package]] 535 | name = "object" 536 | version = "0.36.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" 539 | dependencies = [ 540 | "memchr", 541 | ] 542 | 543 | [[package]] 544 | name = "once_cell" 545 | version = "1.19.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 548 | 549 | [[package]] 550 | name = "parking_lot" 551 | version = "0.12.3" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 554 | dependencies = [ 555 | "lock_api", 556 | "parking_lot_core", 557 | ] 558 | 559 | [[package]] 560 | name = "parking_lot_core" 561 | version = "0.9.10" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 564 | dependencies = [ 565 | "cfg-if", 566 | "libc", 567 | "redox_syscall", 568 | "smallvec", 569 | "windows-targets", 570 | ] 571 | 572 | [[package]] 573 | name = "peeking_take_while" 574 | version = "0.1.2" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 577 | 578 | [[package]] 579 | name = "petgraph" 580 | version = "0.6.5" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 583 | dependencies = [ 584 | "fixedbitset", 585 | "indexmap", 586 | ] 587 | 588 | [[package]] 589 | name = "pprof" 590 | version = "0.13.0" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb" 593 | dependencies = [ 594 | "backtrace", 595 | "cfg-if", 596 | "findshlibs", 597 | "inferno", 598 | "libc", 599 | "log", 600 | "nix", 601 | "once_cell", 602 | "parking_lot", 603 | "prost 0.12.6", 604 | "prost-build", 605 | "prost-derive 0.12.6", 606 | "protobuf", 607 | "sha2", 608 | "smallvec", 609 | "symbolic-demangle", 610 | "tempfile", 611 | "thiserror", 612 | ] 613 | 614 | [[package]] 615 | name = "prettyplease" 616 | version = "0.2.20" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" 619 | dependencies = [ 620 | "proc-macro2", 621 | "syn 2.0.87", 622 | ] 623 | 624 | [[package]] 625 | name = "proc-macro2" 626 | version = "1.0.86" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 629 | dependencies = [ 630 | "unicode-ident", 631 | ] 632 | 633 | [[package]] 634 | name = "prost" 635 | version = "0.7.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" 638 | dependencies = [ 639 | "bytes", 640 | "prost-derive 0.7.0", 641 | ] 642 | 643 | [[package]] 644 | name = "prost" 645 | version = "0.12.6" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" 648 | dependencies = [ 649 | "bytes", 650 | "prost-derive 0.12.6", 651 | ] 652 | 653 | [[package]] 654 | name = "prost-build" 655 | version = "0.12.6" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" 658 | dependencies = [ 659 | "bytes", 660 | "heck", 661 | "itertools 0.12.1", 662 | "log", 663 | "multimap", 664 | "once_cell", 665 | "petgraph", 666 | "prettyplease", 667 | "prost 0.12.6", 668 | "prost-types", 669 | "regex", 670 | "syn 2.0.87", 671 | "tempfile", 672 | ] 673 | 674 | [[package]] 675 | name = "prost-derive" 676 | version = "0.7.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" 679 | dependencies = [ 680 | "anyhow", 681 | "itertools 0.9.0", 682 | "proc-macro2", 683 | "quote", 684 | "syn 1.0.109", 685 | ] 686 | 687 | [[package]] 688 | name = "prost-derive" 689 | version = "0.12.6" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" 692 | dependencies = [ 693 | "anyhow", 694 | "itertools 0.12.1", 695 | "proc-macro2", 696 | "quote", 697 | "syn 2.0.87", 698 | ] 699 | 700 | [[package]] 701 | name = "prost-types" 702 | version = "0.12.6" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" 705 | dependencies = [ 706 | "prost 0.12.6", 707 | ] 708 | 709 | [[package]] 710 | name = "protobuf" 711 | version = "2.28.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" 714 | 715 | [[package]] 716 | name = "quick-xml" 717 | version = "0.26.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" 720 | dependencies = [ 721 | "memchr", 722 | ] 723 | 724 | [[package]] 725 | name = "quote" 726 | version = "1.0.36" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 729 | dependencies = [ 730 | "proc-macro2", 731 | ] 732 | 733 | [[package]] 734 | name = "redox_syscall" 735 | version = "0.5.2" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 738 | dependencies = [ 739 | "bitflags 2.6.0", 740 | ] 741 | 742 | [[package]] 743 | name = "regex" 744 | version = "1.10.5" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 747 | dependencies = [ 748 | "aho-corasick", 749 | "memchr", 750 | "regex-automata", 751 | "regex-syntax", 752 | ] 753 | 754 | [[package]] 755 | name = "regex-automata" 756 | version = "0.4.7" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 759 | dependencies = [ 760 | "aho-corasick", 761 | "memchr", 762 | "regex-syntax", 763 | ] 764 | 765 | [[package]] 766 | name = "regex-syntax" 767 | version = "0.8.4" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 770 | 771 | [[package]] 772 | name = "rgb" 773 | version = "0.8.42" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "3eeba50c58624afb3be6d04abad8cb7a259d52017068c9f828975aa870a5daf5" 776 | dependencies = [ 777 | "bytemuck", 778 | ] 779 | 780 | [[package]] 781 | name = "rustc-demangle" 782 | version = "0.1.24" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 785 | 786 | [[package]] 787 | name = "rustc-hash" 788 | version = "1.1.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 791 | 792 | [[package]] 793 | name = "rustix" 794 | version = "0.38.34" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 797 | dependencies = [ 798 | "bitflags 2.6.0", 799 | "errno", 800 | "libc", 801 | "linux-raw-sys", 802 | "windows-sys", 803 | ] 804 | 805 | [[package]] 806 | name = "scopeguard" 807 | version = "1.2.0" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 810 | 811 | [[package]] 812 | name = "sha2" 813 | version = "0.10.8" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 816 | dependencies = [ 817 | "cfg-if", 818 | "cpufeatures", 819 | "digest", 820 | ] 821 | 822 | [[package]] 823 | name = "shlex" 824 | version = "1.3.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 827 | 828 | [[package]] 829 | name = "simple" 830 | version = "0.1.0" 831 | dependencies = [ 832 | "heappy", 833 | "prost 0.7.0", 834 | ] 835 | 836 | [[package]] 837 | name = "smallvec" 838 | version = "1.13.2" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 841 | 842 | [[package]] 843 | name = "spin" 844 | version = "0.9.8" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 847 | dependencies = [ 848 | "lock_api", 849 | ] 850 | 851 | [[package]] 852 | name = "stable_deref_trait" 853 | version = "1.2.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 856 | 857 | [[package]] 858 | name = "str_stack" 859 | version = "0.1.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" 862 | 863 | [[package]] 864 | name = "symbolic-common" 865 | version = "12.9.2" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" 868 | dependencies = [ 869 | "debugid", 870 | "memmap2", 871 | "stable_deref_trait", 872 | "uuid", 873 | ] 874 | 875 | [[package]] 876 | name = "symbolic-demangle" 877 | version = "12.9.2" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "424fa2c9bf2c862891b9cfd354a752751a6730fd838a4691e7f6c2c7957b9daf" 880 | dependencies = [ 881 | "cpp_demangle", 882 | "rustc-demangle", 883 | "symbolic-common", 884 | ] 885 | 886 | [[package]] 887 | name = "syn" 888 | version = "1.0.109" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 891 | dependencies = [ 892 | "proc-macro2", 893 | "quote", 894 | "unicode-ident", 895 | ] 896 | 897 | [[package]] 898 | name = "syn" 899 | version = "2.0.87" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 902 | dependencies = [ 903 | "proc-macro2", 904 | "quote", 905 | "unicode-ident", 906 | ] 907 | 908 | [[package]] 909 | name = "tempfile" 910 | version = "3.10.1" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 913 | dependencies = [ 914 | "cfg-if", 915 | "fastrand", 916 | "rustix", 917 | "windows-sys", 918 | ] 919 | 920 | [[package]] 921 | name = "thiserror" 922 | version = "1.0.69" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 925 | dependencies = [ 926 | "thiserror-impl", 927 | ] 928 | 929 | [[package]] 930 | name = "thiserror-impl" 931 | version = "1.0.69" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 934 | dependencies = [ 935 | "proc-macro2", 936 | "quote", 937 | "syn 2.0.87", 938 | ] 939 | 940 | [[package]] 941 | name = "tikv-jemalloc-sys" 942 | version = "0.5.4+5.3.0-patched" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" 945 | dependencies = [ 946 | "cc", 947 | "libc", 948 | ] 949 | 950 | [[package]] 951 | name = "typenum" 952 | version = "1.17.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 955 | 956 | [[package]] 957 | name = "unicode-ident" 958 | version = "1.0.12" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 961 | 962 | [[package]] 963 | name = "uuid" 964 | version = "1.9.1" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" 967 | 968 | [[package]] 969 | name = "version_check" 970 | version = "0.9.4" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 973 | 974 | [[package]] 975 | name = "wasi" 976 | version = "0.11.0+wasi-snapshot-preview1" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 979 | 980 | [[package]] 981 | name = "winapi" 982 | version = "0.3.9" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 985 | dependencies = [ 986 | "winapi-i686-pc-windows-gnu", 987 | "winapi-x86_64-pc-windows-gnu", 988 | ] 989 | 990 | [[package]] 991 | name = "winapi-i686-pc-windows-gnu" 992 | version = "0.4.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 995 | 996 | [[package]] 997 | name = "winapi-x86_64-pc-windows-gnu" 998 | version = "0.4.0" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1001 | 1002 | [[package]] 1003 | name = "windows-sys" 1004 | version = "0.52.0" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1007 | dependencies = [ 1008 | "windows-targets", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "windows-targets" 1013 | version = "0.52.6" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1016 | dependencies = [ 1017 | "windows_aarch64_gnullvm", 1018 | "windows_aarch64_msvc", 1019 | "windows_i686_gnu", 1020 | "windows_i686_gnullvm", 1021 | "windows_i686_msvc", 1022 | "windows_x86_64_gnu", 1023 | "windows_x86_64_gnullvm", 1024 | "windows_x86_64_msvc", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "windows_aarch64_gnullvm" 1029 | version = "0.52.6" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1032 | 1033 | [[package]] 1034 | name = "windows_aarch64_msvc" 1035 | version = "0.52.6" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1038 | 1039 | [[package]] 1040 | name = "windows_i686_gnu" 1041 | version = "0.52.6" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1044 | 1045 | [[package]] 1046 | name = "windows_i686_gnullvm" 1047 | version = "0.52.6" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1050 | 1051 | [[package]] 1052 | name = "windows_i686_msvc" 1053 | version = "0.52.6" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1056 | 1057 | [[package]] 1058 | name = "windows_x86_64_gnu" 1059 | version = "0.52.6" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1062 | 1063 | [[package]] 1064 | name = "windows_x86_64_gnullvm" 1065 | version = "0.52.6" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1068 | 1069 | [[package]] 1070 | name = "windows_x86_64_msvc" 1071 | version = "0.52.6" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1074 | 1075 | [[package]] 1076 | name = "zerocopy" 1077 | version = "0.7.35" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1080 | dependencies = [ 1081 | "zerocopy-derive", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "zerocopy-derive" 1086 | version = "0.7.35" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1089 | dependencies = [ 1090 | "proc-macro2", 1091 | "quote", 1092 | "syn 2.0.87", 1093 | ] 1094 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heappy" 3 | version = "0.1.0" 4 | authors = [ "Marko Mikulicic " ] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | 8 | [workspace] 9 | members = [ 10 | "examples/complex", 11 | "examples/simple", 12 | ] 13 | 14 | [profile.release] 15 | debug = true 16 | 17 | [features] 18 | default = [] 19 | jemalloc_shim = [ "tikv-jemalloc-sys" ] 20 | enable_heap_profiler = [ "jemalloc_shim" ] 21 | measure_free = [] 22 | 23 | [dependencies] 24 | backtrace = "0.3.70" 25 | bytes = "1.5.0" 26 | lazy_static = "1.4.0" 27 | libc = { version = "^0.2.154", default-features = false } 28 | pprof = {version = "^0.13.0", features = [ "prost-codec", "flamegraph", "protobuf" ] } 29 | spin = "0.9.8" 30 | tikv-jemalloc-sys = { version = "0.5.4", optional = true, features = [ "stats" ] } 31 | thiserror = "^1.0.59" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 TiKV Project Authors. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # heappy 2 | 3 | `heappy` is an experimental rust crate for in-process memory profiling. 4 | 5 | I'd like to eventually contribute this back to [pprof-rs](https://github.com/tikv/pprof-rs). -------------------------------------------------------------------------------- /examples/complex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "complex" 3 | version = "0.1.0" 4 | authors = [ "Marko Mikulicic " ] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | croaring = "0.5.0" 9 | heappy = { path = "../..", features = [ "enable_heap_profiler", "measure_free" ] } 10 | prost = "0.7" 11 | -------------------------------------------------------------------------------- /examples/complex/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU64, Ordering}; 2 | use std::{thread, time}; 3 | 4 | use croaring::Bitmap; 5 | 6 | static ITERATIONS: AtomicU64 = AtomicU64::new(0); 7 | 8 | #[derive(Debug)] 9 | #[allow(dead_code)] 10 | struct Foo { 11 | i: i32, 12 | v: Vec, 13 | } 14 | 15 | fn push(v: &mut Vec, i: u8) { 16 | v.push(i); 17 | } 18 | 19 | impl Foo { 20 | fn new(i: i32) -> Self { 21 | let mut v = Vec::with_capacity(1000); 22 | for i in 0..1000 { 23 | push(&mut v, i as u8); 24 | } 25 | Self { i, v } 26 | } 27 | 28 | fn foo(&self) { 29 | // println!("{:?}", self.v.iter().fold(0u8, |a, b| a.wrapping_add(*b))); 30 | } 31 | } 32 | 33 | fn foo(fs: &[Foo]) { 34 | for f in fs { 35 | f.foo(); 36 | } 37 | } 38 | 39 | fn work() { 40 | let mut rb1 = Bitmap::create(); 41 | rb1.add(1); 42 | rb1.add(2); 43 | rb1.add(3); 44 | rb1.add(4); 45 | rb1.add(5); 46 | rb1.add(100); 47 | rb1.add(1000); 48 | rb1.run_optimize(); 49 | 50 | for i in (2000..(100 * 1024 * 1024)).step_by(64 * 1024 + 1) { 51 | rb1.add(i); 52 | } 53 | rb1.run_optimize(); 54 | 55 | assert!(rb1.contains(3)); 56 | 57 | // loop { 58 | let v = vec![1, 2, 3, 4]; 59 | let mut m: Vec = v.into_iter().map(Foo::new).collect(); 60 | 61 | foo(&m); 62 | //} 63 | m.remove(0); 64 | std::mem::forget(m); 65 | } 66 | 67 | fn worker() { 68 | loop { 69 | work(); 70 | ITERATIONS.fetch_add(1, Ordering::Relaxed); 71 | } 72 | } 73 | 74 | fn demo() { 75 | let heap_profiler_guard = heappy::HeapProfilerGuard::new(1).unwrap(); 76 | 77 | println!("start demo"); 78 | 79 | for _ in 0..4 { 80 | thread::spawn(worker); 81 | } 82 | thread::sleep(time::Duration::from_secs(4)); 83 | let iterations = ITERATIONS.fetch_add(0, Ordering::SeqCst); 84 | println!("Iterations: {}", iterations); 85 | 86 | let report = heap_profiler_guard.report(); 87 | 88 | let filename = "/tmp/memflame.svg"; 89 | println!("Writing to {}", filename); 90 | let mut file = std::fs::File::create(filename).unwrap(); 91 | report.flamegraph(&mut file); 92 | 93 | let filename = "/tmp/memflame.pb"; 94 | println!("Writing to {}", filename); 95 | let mut file = std::fs::File::create(filename).unwrap(); 96 | report.write_pprof(&mut file).unwrap(); 97 | } 98 | 99 | fn main() { 100 | demo(); 101 | println!("end demo"); 102 | } 103 | -------------------------------------------------------------------------------- /examples/simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple" 3 | version = "0.1.0" 4 | authors = [ "Marko Mikulicic " ] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | heappy = { path = "../..", features = [ "enable_heap_profiler", "measure_free" ] } 9 | prost = "0.7" 10 | -------------------------------------------------------------------------------- /examples/simple/src/main.rs: -------------------------------------------------------------------------------- 1 | fn work() { 2 | let v = vec![65_u8, 66, 66, 10]; 3 | println!("{:?}", &v); 4 | } 5 | 6 | fn demo() { 7 | // Using a period of 1 to catch all allocations. 8 | let heap_profiler_guard = heappy::HeapProfilerGuard::new(1).unwrap(); 9 | 10 | work(); 11 | 12 | let report = heap_profiler_guard.report(); 13 | 14 | let filename = "/tmp/memflame.svg"; 15 | println!("Writing to {}", filename); 16 | let mut file = std::fs::File::create(filename).unwrap(); 17 | report.flamegraph(&mut file); 18 | 19 | let filename = "/tmp/memflame.pb"; 20 | println!("Writing to {}", filename); 21 | let mut file = std::fs::File::create(filename).unwrap(); 22 | report.write_pprof(&mut file).unwrap(); 23 | } 24 | 25 | fn main() { 26 | // cause some print before the demo or the memory profiler will show also the (expensive) lazy initialization of the print subsystem 27 | println!("starting demo"); 28 | demo(); 29 | println!("bye"); 30 | } 31 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.70" 3 | components = [ "rustfmt", "clippy" ] 4 | -------------------------------------------------------------------------------- /src/collector.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Eq; 2 | use core::default::Default; 3 | use core::hash::Hash; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Default, Debug, Clone)] 7 | pub struct MemProfileRecord { 8 | pub alloc_bytes: isize, 9 | pub alloc_objects: isize, 10 | #[cfg(feature = "measure_free")] 11 | pub free_bytes: isize, 12 | #[cfg(feature = "measure_free")] 13 | pub free_objects: isize, 14 | } 15 | 16 | #[cfg(feature = "measure_free")] 17 | impl MemProfileRecord { 18 | pub fn in_use_bytes(&self) -> isize { 19 | self.alloc_bytes - self.free_bytes 20 | } 21 | 22 | pub fn in_use_objects(&self) -> isize { 23 | self.alloc_objects - self.free_objects 24 | } 25 | } 26 | 27 | pub struct Collector { 28 | map: HashMap, 29 | } 30 | 31 | impl Collector { 32 | pub fn new() -> Self { 33 | Self { 34 | map: HashMap::new(), 35 | } 36 | } 37 | 38 | pub fn record(&mut self, key: K, bytes: isize) { 39 | let rec = self.map.entry(key).or_insert_with(Default::default); 40 | match bytes.cmp(&0) { 41 | std::cmp::Ordering::Greater => { 42 | rec.alloc_bytes += bytes; 43 | rec.alloc_objects += 1; 44 | } 45 | #[cfg(feature = "measure_free")] 46 | std::cmp::Ordering::Less => { 47 | rec.free_bytes += -bytes; 48 | rec.free_objects += 1; 49 | } 50 | #[cfg(not(feature = "measure_free"))] 51 | std::cmp::Ordering::Less => { 52 | unreachable!("the measure_free feature flag is disabled yet I've measured a free") 53 | } 54 | std::cmp::Ordering::Equal => { 55 | // ignore 56 | } 57 | } 58 | } 59 | } 60 | 61 | impl IntoIterator for Collector { 62 | type Item = (K, MemProfileRecord); 63 | type IntoIter = std::collections::hash_map::IntoIter; 64 | 65 | fn into_iter(self) -> Self::IntoIter { 66 | self.map.into_iter() 67 | } 68 | } 69 | 70 | impl Default for Collector { 71 | fn default() -> Self { 72 | Self::new() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/hook.rs: -------------------------------------------------------------------------------- 1 | //! The standard API includes: the [`malloc`], [`calloc`], [`realloc`], and 2 | //! [`free`], which conform to to ISO/IEC 9899:1990 (“ISO C90”), 3 | //! [`posix_memalign`] which conforms to conforms to POSIX.1-2016, and 4 | //! [`aligned_alloc`]. 5 | 6 | use crate::adapter::*; 7 | use crate::profiler::Profiler; 8 | use libc::{c_int, c_void, size_t}; 9 | 10 | // On linux we need to reference at least one symbol in a module for it to not be pruned at link time. 11 | pub(crate) fn dummy_force_link() {} 12 | 13 | #[no_mangle] 14 | pub unsafe extern "C" fn malloc(size: size_t) -> *mut c_void { 15 | let res = sys_malloc(size); 16 | Profiler::track_allocated(sys_malloc_usable_size(res) as isize); 17 | res 18 | } 19 | 20 | #[no_mangle] 21 | pub unsafe extern "C" fn calloc(number: size_t, size: size_t) -> *mut c_void { 22 | let res = sys_calloc(number, size); 23 | Profiler::track_allocated(sys_malloc_usable_size(res) as isize); 24 | res 25 | } 26 | 27 | #[no_mangle] 28 | pub unsafe extern "C" fn free(ptr: *mut c_void) { 29 | #[cfg(feature = "measure_free")] 30 | { 31 | let size = sys_malloc_usable_size(ptr) as isize; 32 | Profiler::track_allocated(-size); 33 | } 34 | sys_free(ptr) 35 | } 36 | 37 | #[no_mangle] 38 | pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: size_t) -> *mut c_void { 39 | let old_size = sys_malloc_usable_size(ptr) as isize; 40 | let res = sys_realloc(ptr, size); 41 | Profiler::track_allocated(sys_malloc_usable_size(res) as isize - old_size); 42 | res 43 | } 44 | 45 | #[no_mangle] 46 | pub unsafe extern "C" fn malloc_usable_size(ptr: *const c_void) -> size_t { 47 | sys_malloc_usable_size(ptr) 48 | } 49 | 50 | #[no_mangle] 51 | pub unsafe extern "C" fn posix_memalign( 52 | ptr: *mut *mut c_void, 53 | alignment: size_t, 54 | size: size_t, 55 | ) -> c_int { 56 | sys_posix_memalign(ptr, alignment, size) 57 | } 58 | 59 | #[no_mangle] 60 | pub unsafe extern "C" fn aligned_alloc(alignment: size_t, size: size_t) -> *mut c_void { 61 | let res = sys_aligned_alloc(alignment, size); 62 | Profiler::track_allocated(sys_malloc_usable_size(res) as isize); 63 | res 64 | } 65 | -------------------------------------------------------------------------------- /src/jemalloc_adapter.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_int, c_void, size_t}; 2 | 3 | #[link(name = "jemalloc")] 4 | extern "C" { 5 | #[link_name = "_rjem_malloc"] 6 | pub fn sys_malloc(size: size_t) -> *mut c_void; 7 | 8 | #[link_name = "_rjem_calloc"] 9 | pub fn sys_calloc(number: size_t, size: size_t) -> *mut c_void; 10 | 11 | #[link_name = "_rjem_free"] 12 | pub fn sys_free(ptr: *mut c_void); 13 | 14 | #[link_name = "_rjem_realloc"] 15 | pub fn sys_realloc(ptr: *mut c_void, size: size_t) -> *mut c_void; 16 | 17 | #[link_name = "_rjem_malloc_usable_size"] 18 | pub fn sys_malloc_usable_size(ptr: *const c_void) -> size_t; 19 | 20 | #[link_name = "_rjem_posix_memalign"] 21 | pub fn sys_posix_memalign(ptr: *mut *mut c_void, alignment: size_t, size: size_t) -> c_int; 22 | 23 | #[cfg(target_os = "macos")] 24 | #[link_name = "_rjem_posix_aligned_alloc"] 25 | pub fn sys_aligned_alloc(alignment: size_t, size: size_t) -> *mut c_void; 26 | 27 | #[cfg(not(target_os = "macos"))] 28 | #[link_name = "_rjem_aligned_alloc"] 29 | pub fn sys_aligned_alloc(alignment: size_t, size: size_t) -> *mut c_void; 30 | } 31 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod profiler; 2 | pub use profiler::*; 3 | 4 | mod collector; 5 | #[cfg(feature = "enable_heap_profiler")] 6 | mod hook; 7 | 8 | #[cfg(feature = "jemalloc_shim")] 9 | mod jemalloc_adapter; 10 | 11 | #[cfg(feature = "jemalloc_shim")] 12 | use jemalloc_adapter as adapter; 13 | 14 | // On linux you need to reference at least one symbol in a module if we want it be be actually linked. 15 | // Otherwise the hooks like `pub unsafe extern "C" fn malloc(size: size_t) -> *mut c_void` defined in the shim 16 | // module won't override the respective weak symbols from libc, since they don't ever get linked in the final executable. 17 | // On macos this is not necessary, but it doesn't hurt. 18 | // (e.g. the functions that override weak symbols exported by libc) 19 | #[cfg(feature = "enable_heap_profiler")] 20 | pub fn dummy_force_link() { 21 | hook::dummy_force_link(); 22 | } 23 | -------------------------------------------------------------------------------- /src/profiler.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::hash::{Hash, Hasher}; 4 | use std::io::Write; 5 | use std::mem::MaybeUninit; 6 | use std::sync::{ 7 | atomic::{AtomicBool, Ordering}, 8 | Mutex, MutexGuard, 9 | }; 10 | use std::time::SystemTime; 11 | 12 | use backtrace::Frame; 13 | use pprof::protos::Message; 14 | use spin::RwLock; 15 | use thiserror::Error; 16 | 17 | use crate::collector; 18 | 19 | const MAX_DEPTH: usize = 32; 20 | 21 | static HEAP_PROFILER_ENABLED: AtomicBool = AtomicBool::new(false); 22 | 23 | lazy_static::lazy_static! { 24 | static ref HEAP_PROFILER_STATE: RwLock> = RwLock::new(Default::default()); 25 | static ref HEAP_PROFILER_ENTER: Mutex<()> = Mutex::new(()); 26 | } 27 | 28 | #[derive(Error, Debug)] 29 | pub enum Error { 30 | #[error("attempting to run a heap profiler while the another heap profiler is being run")] 31 | ConcurrentHeapProfiler, 32 | } 33 | 34 | pub type Result = std::result::Result; 35 | 36 | /// RAII structure used to stop profiling when dropped. It is the only interface to access the heap profiler. 37 | pub struct HeapProfilerGuard { 38 | _guard: MutexGuard<'static, ()>, 39 | } 40 | 41 | impl HeapProfilerGuard { 42 | pub fn new(period: usize) -> Result { 43 | let guard = HEAP_PROFILER_ENTER 44 | .try_lock() 45 | .map_err(|_| Error::ConcurrentHeapProfiler)?; 46 | Profiler::start(period); 47 | Ok(Self { _guard: guard }) 48 | } 49 | 50 | pub fn report(self) -> HeapReport { 51 | std::mem::drop(self); 52 | HeapReport::new() 53 | } 54 | } 55 | 56 | impl Drop for HeapProfilerGuard { 57 | fn drop(&mut self) { 58 | Profiler::stop(); 59 | } 60 | } 61 | 62 | pub struct Profiler; 63 | 64 | impl Profiler { 65 | fn enabled() -> bool { 66 | HEAP_PROFILER_ENABLED.load(Ordering::SeqCst) 67 | } 68 | 69 | fn set_enabled(value: bool) { 70 | HEAP_PROFILER_ENABLED.store(value, Ordering::SeqCst) 71 | } 72 | 73 | fn start(period: usize) { 74 | let mut profiler = HEAP_PROFILER_STATE.write(); 75 | *profiler = ProfilerState::new(period); 76 | std::mem::drop(profiler); 77 | 78 | Self::set_enabled(true); 79 | } 80 | 81 | fn stop() { 82 | Self::set_enabled(false); 83 | } 84 | 85 | // Called by malloc hooks to record a memory allocation event. 86 | pub(crate) unsafe fn track_allocated(size: isize) { 87 | thread_local!(static ENTERED: Cell = Cell::new(false)); 88 | 89 | struct ResetOnDrop; 90 | 91 | impl Drop for ResetOnDrop { 92 | fn drop(&mut self) { 93 | ENTERED.with(|b| b.set(false)); 94 | } 95 | } 96 | 97 | if !ENTERED.with(|b| b.replace(true)) { 98 | let _reset_on_drop = ResetOnDrop; 99 | if Self::enabled() { 100 | let mut profiler = HEAP_PROFILER_STATE.write(); 101 | let mut sample_now = false; 102 | match size.cmp(&0) { 103 | std::cmp::Ordering::Greater => { 104 | profiler.allocated_objects += 1; 105 | profiler.allocated_bytes += size; 106 | 107 | if profiler.allocated_bytes >= profiler.next_sample { 108 | profiler.next_sample = 109 | profiler.allocated_bytes + profiler.period as isize; 110 | sample_now = true; 111 | } 112 | } 113 | #[cfg(not(feature = "measure_free"))] 114 | std::cmp::Ordering::Less => { 115 | // ignore 116 | } 117 | #[cfg(feature = "measure_free")] 118 | std::cmp::Ordering::Less => { 119 | profiler.freed_objects += 1; 120 | profiler.freed_bytes += -size; 121 | 122 | if profiler.freed_bytes >= profiler.next_free_sample { 123 | profiler.next_free_sample = 124 | profiler.freed_bytes + profiler.period as isize; 125 | sample_now = true; 126 | } 127 | } 128 | std::cmp::Ordering::Equal => { 129 | // ignore 130 | } 131 | } 132 | 133 | if sample_now { 134 | let mut bt = Frames::new(); 135 | // we're already holding a lock 136 | backtrace::trace_unsynchronized(|frame| bt.push(frame)); 137 | 138 | profiler.collector.record(bt, size); 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | #[derive(Debug)] 146 | pub struct HeapReport { 147 | data: HashMap, 148 | period: usize, 149 | } 150 | 151 | impl HeapReport { 152 | fn new() -> Self { 153 | let mut profiler = HEAP_PROFILER_STATE.write(); 154 | let collector = std::mem::take(&mut profiler.collector); 155 | 156 | let data = collector 157 | .into_iter() 158 | .map(|(frames, rec)| (frames.into(), rec)) 159 | .collect(); 160 | Self { 161 | data, 162 | period: profiler.period, 163 | } 164 | } 165 | 166 | /// flamegraph will write an svg flamegraph into writer. 167 | pub fn flamegraph(&self, writer: W) 168 | where 169 | W: Write, 170 | { 171 | // the pprof crate already has all the necessary plumbing for the embedded flamegraph library, let's just render 172 | // the alloc_bytes stat with it. 173 | let data = self 174 | .data 175 | .iter() 176 | .map(|(frames, rec)| (frames.clone(), rec.alloc_bytes)) 177 | .collect(); 178 | 179 | let timing = Default::default(); 180 | 181 | let report = pprof::Report { data, timing }; 182 | 183 | let mut options: pprof::flamegraph::Options = Default::default(); 184 | 185 | options.count_name = "bytes".to_string(); 186 | options.colors = 187 | pprof::flamegraph::color::Palette::Basic(pprof::flamegraph::color::BasicPalette::Mem); 188 | 189 | report 190 | .flamegraph_with_options(writer, &mut options) 191 | .unwrap(); 192 | } 193 | 194 | fn inner_pprof(&self) -> pprof::protos::Profile { 195 | use pprof::protos; 196 | let data = self.data.clone(); 197 | 198 | let mut dudup_str = HashSet::new(); 199 | for key in data.iter().map(|(key, _)| key) { 200 | for frame in key.frames.iter() { 201 | for symbol in frame { 202 | dudup_str.insert(symbol.name()); 203 | dudup_str.insert(symbol.sys_name().into_owned()); 204 | dudup_str.insert(symbol.filename().into_owned()); 205 | } 206 | } 207 | } 208 | // string table's first element must be an empty string 209 | let mut string_table = vec!["".to_owned()]; 210 | string_table.extend(dudup_str.into_iter()); 211 | 212 | let mut strings = HashMap::new(); 213 | for (index, name) in string_table.iter().enumerate() { 214 | strings.insert(name.as_str(), index); 215 | } 216 | 217 | let mut samples = vec![]; 218 | let mut loc_tbl = vec![]; 219 | let mut fn_tbl = vec![]; 220 | let mut functions = HashMap::new(); 221 | for (key, rec) in data.iter() { 222 | let mut locs = vec![]; 223 | for frame in key.frames.iter() { 224 | for symbol in frame { 225 | let name = symbol.name(); 226 | if let Some(loc_idx) = functions.get(&name) { 227 | locs.push(*loc_idx); 228 | continue; 229 | } 230 | let sys_name = symbol.sys_name(); 231 | let filename = symbol.filename(); 232 | let lineno = symbol.lineno(); 233 | let function_id = fn_tbl.len() as u64 + 1; 234 | let function = protos::Function { 235 | id: function_id, 236 | name: *strings.get(name.as_str()).unwrap() as i64, 237 | system_name: *strings.get(sys_name.as_ref()).unwrap() as i64, 238 | filename: *strings.get(filename.as_ref()).unwrap() as i64, 239 | ..protos::Function::default() 240 | }; 241 | functions.insert(name, function_id); 242 | let line = protos::Line { 243 | function_id, 244 | line: lineno as i64, 245 | }; 246 | let loc = protos::Location { 247 | id: function_id, 248 | line: vec![line], 249 | ..protos::Location::default() 250 | }; 251 | // the fn_tbl has the same length with loc_tbl 252 | fn_tbl.push(function); 253 | loc_tbl.push(loc); 254 | // current frame locations 255 | locs.push(function_id); 256 | } 257 | } 258 | let sample = protos::Sample { 259 | location_id: locs, 260 | #[cfg(feature = "measure_free")] 261 | value: vec![ 262 | rec.alloc_objects as i64, 263 | rec.alloc_bytes as i64, 264 | rec.free_objects as i64, 265 | rec.free_bytes as i64, 266 | rec.in_use_objects() as i64, 267 | rec.in_use_bytes() as i64, 268 | ], 269 | #[cfg(not(feature = "measure_free"))] 270 | value: vec![rec.alloc_objects as i64, rec.alloc_bytes as i64], 271 | ..protos::Sample::default() 272 | }; 273 | samples.push(sample); 274 | } 275 | 276 | let mut push_string = |s: &str| { 277 | let idx = string_table.len(); 278 | string_table.push(s.to_string()); 279 | idx as i64 280 | }; 281 | 282 | let alloc_objects_idx = push_string("alloc_objects"); 283 | let count_idx = push_string("count"); 284 | let alloc_space_idx = push_string("alloc_space"); 285 | let bytes_idx = push_string("bytes"); 286 | #[cfg(feature = "measure_free")] 287 | let free_objects_idx = push_string("free_objects"); 288 | #[cfg(feature = "measure_free")] 289 | let free_space_idx = push_string("free_space"); 290 | #[cfg(feature = "measure_free")] 291 | let inuse_objects_idx = push_string("inuse_objects"); 292 | #[cfg(feature = "measure_free")] 293 | let inuse_space_idx = push_string("inuse_space"); 294 | let space_idx = push_string("space"); 295 | 296 | let sample_type = vec![ 297 | protos::ValueType { 298 | ty: alloc_objects_idx, 299 | unit: count_idx, 300 | }, 301 | protos::ValueType { 302 | ty: alloc_space_idx, 303 | unit: bytes_idx, 304 | }, 305 | #[cfg(feature = "measure_free")] 306 | protos::ValueType { 307 | ty: free_objects_idx, 308 | unit: count_idx, 309 | }, 310 | #[cfg(feature = "measure_free")] 311 | protos::ValueType { 312 | ty: free_space_idx, 313 | unit: count_idx, 314 | }, 315 | #[cfg(feature = "measure_free")] 316 | protos::ValueType { 317 | ty: inuse_objects_idx, 318 | unit: count_idx, 319 | }, 320 | #[cfg(feature = "measure_free")] 321 | protos::ValueType { 322 | ty: inuse_space_idx, 323 | unit: bytes_idx, 324 | }, 325 | ]; 326 | 327 | let period_type = Some(pprof::protos::ValueType { 328 | ty: space_idx, 329 | unit: bytes_idx, 330 | }); 331 | 332 | protos::Profile { 333 | sample_type, 334 | default_sample_type: alloc_space_idx, 335 | sample: samples, 336 | string_table, 337 | period_type, 338 | period: self.period as i64, 339 | function: fn_tbl, 340 | location: loc_tbl, 341 | ..protos::Profile::default() 342 | } 343 | } 344 | 345 | /// produce a pprof proto (for use with go tool pprof and compatible visualizers) 346 | pub fn pprof(&self) -> pprof::protos::Profile { 347 | let mut proto = self.inner_pprof(); 348 | 349 | let drop_frames_idx = proto.string_table.len(); 350 | proto 351 | .string_table 352 | .push(".*::Profiler::track_allocated".to_string()); 353 | proto.drop_frames = drop_frames_idx as i64; 354 | 355 | proto 356 | } 357 | 358 | pub fn write_pprof(&self, writer: &mut W) -> std::io::Result<()> { 359 | let mut buf = vec![]; 360 | self.pprof().encode(&mut buf)?; 361 | writer.write_all(&buf) 362 | } 363 | } 364 | 365 | // Current profiler state, collection of sampled frames. 366 | struct ProfilerState { 367 | collector: collector::Collector>, 368 | allocated_objects: isize, 369 | allocated_bytes: isize, 370 | #[cfg(feature = "measure_free")] 371 | freed_objects: isize, 372 | #[cfg(feature = "measure_free")] 373 | freed_bytes: isize, 374 | // take a sample when allocated crosses this threshold 375 | next_sample: isize, 376 | // take a sample when free crosses this threshold 377 | #[cfg(feature = "measure_free")] 378 | next_free_sample: isize, 379 | // take a sample every period bytes. 380 | period: usize, 381 | } 382 | 383 | impl ProfilerState { 384 | fn new(period: usize) -> Self { 385 | Self { 386 | collector: collector::Collector::new(), 387 | period, 388 | allocated_objects: 0, 389 | allocated_bytes: 0, 390 | #[cfg(feature = "measure_free")] 391 | freed_objects: 0, 392 | #[cfg(feature = "measure_free")] 393 | freed_bytes: 0, 394 | next_sample: period as isize, 395 | #[cfg(feature = "measure_free")] 396 | next_free_sample: period as isize, 397 | } 398 | } 399 | } 400 | 401 | impl Default for ProfilerState { 402 | fn default() -> Self { 403 | Self::new(1) 404 | } 405 | } 406 | 407 | struct Frames { 408 | frames: [MaybeUninit; N], 409 | size: usize, 410 | ts: SystemTime, 411 | } 412 | 413 | impl Clone for Frames { 414 | fn clone(&self) -> Self { 415 | let mut n = Self::new(); 416 | for i in 0..self.size { 417 | n.frames[i].write(unsafe { self.frames[i].assume_init_ref().clone() }); 418 | } 419 | n.size = self.size; 420 | n.ts = self.ts; 421 | n 422 | } 423 | } 424 | 425 | impl Frames { 426 | fn new() -> Self { 427 | Self { 428 | frames: std::array::from_fn(|_| MaybeUninit::uninit()), 429 | size: 0, 430 | ts: SystemTime::now(), 431 | } 432 | } 433 | 434 | /// Push will push up to N frames in the frames array. 435 | fn push(&mut self, frame: &Frame) -> bool { 436 | assert!(self.size < N); 437 | self.frames[self.size].write(frame.clone()); 438 | self.size += 1; 439 | self.size < N 440 | } 441 | 442 | fn iter(&self) -> FramesIterator { 443 | FramesIterator(self, 0) 444 | } 445 | } 446 | 447 | impl Hash for Frames { 448 | fn hash(&self, state: &mut H) { 449 | self.iter() 450 | .for_each(|frame| frame.symbol_address().hash(state)); 451 | } 452 | } 453 | 454 | impl PartialEq for Frames { 455 | fn eq(&self, other: &Self) -> bool { 456 | Iterator::zip(self.iter(), other.iter()) 457 | .map(|(s1, s2)| s1.symbol_address() == s2.symbol_address()) 458 | .all(|equal| equal) 459 | } 460 | } 461 | 462 | impl Eq for Frames {} 463 | 464 | struct FramesIterator<'a, const N: usize>(&'a Frames, usize); 465 | 466 | impl<'a, const N: usize> Iterator for FramesIterator<'a, N> { 467 | type Item = &'a Frame; 468 | 469 | fn next(&mut self) -> Option { 470 | if self.1 < self.0.size { 471 | let res = Some(unsafe { self.0.frames[self.1].assume_init_ref() }); 472 | self.1 += 1; 473 | res 474 | } else { 475 | None 476 | } 477 | } 478 | } 479 | 480 | impl From> for pprof::Frames { 481 | fn from(bt: Frames) -> Self { 482 | let frames = bt 483 | .iter() 484 | .map(|frame| { 485 | let mut symbols = Vec::new(); 486 | backtrace::resolve_frame(frame, |symbol| { 487 | if let Some(name) = symbol.name() { 488 | let name = format!("{:#}", name); 489 | if !name.starts_with("alloc::alloc::") 490 | && name != "::allocate" 491 | { 492 | symbols.push(symbol.into()); 493 | } 494 | } 495 | }); 496 | symbols 497 | }) 498 | .collect(); 499 | Self { 500 | frames, 501 | thread_name: "".to_string(), 502 | thread_id: 0, 503 | sample_timestamp: bt.ts, 504 | } 505 | } 506 | } 507 | 508 | #[cfg(test)] 509 | mod test { 510 | use super::*; 511 | 512 | #[test] 513 | fn test_reentrant() { 514 | let _guard = HeapProfilerGuard::new(1).unwrap(); 515 | 516 | assert!(matches!( 517 | HeapProfilerGuard::new(1), 518 | Err(Error::ConcurrentHeapProfiler) 519 | )); 520 | } 521 | } 522 | --------------------------------------------------------------------------------