{"id":13533223,"url":"https://github.com/tmm1/stackprof","last_synced_at":"2025-05-13T20:02:30.300Z","repository":{"id":11107088,"uuid":"13461949","full_name":"tmm1/stackprof","owner":"tmm1","description":"a sampling call-stack profiler for ruby 2.2+","archived":false,"fork":false,"pushed_at":"2025-02-28T11:35:18.000Z","size":341,"stargazers_count":2131,"open_issues_count":55,"forks_count":132,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-05-06T19:51:51.591Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tmm1.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-10-10T04:25:22.000Z","updated_at":"2025-05-03T19:11:02.000Z","dependencies_parsed_at":"2023-02-12T01:31:43.478Z","dependency_job_id":"53081f91-2ded-44be-bc32-360e7ccd25e9","html_url":"https://github.com/tmm1/stackprof","commit_stats":{"total_commits":257,"total_committers":64,"mean_commits":4.015625,"dds":0.6848249027237354,"last_synced_commit":"d90ad352e5cd519f22d2b3e7159e9b1786db2b5a"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmm1%2Fstackprof","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmm1%2Fstackprof/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmm1%2Fstackprof/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmm1%2Fstackprof/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tmm1","download_url":"https://codeload.github.com/tmm1/stackprof/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253411522,"owners_count":21904147,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T07:01:17.772Z","updated_at":"2025-05-13T20:02:30.242Z","avatar_url":"https://github.com/tmm1.png","language":"Ruby","readme":"# Stackprof\n\nA sampling call-stack profiler for Ruby.\n\nInspired heavily by [gperftools](https://code.google.com/p/gperftools/), and written as a replacement for [perftools.rb](https://github.com/tmm1/perftools.rb).\n\n## Requirements\n\n* Ruby 2.2+\n* Linux-based OS\n\n## Getting Started\n\n### Install\n\nIn your Gemfile add:\n\n```ruby\ngem 'stackprof'\n```\n\nThen run `$ bundle install`. Alternatively you can run `$ gem install stackprof`.\n\n\n### Run\n\nin ruby:\n\n``` ruby\nStackProf.run(mode: :cpu, out: 'tmp/stackprof-cpu-myapp.dump') do\n  #...\nend\n```\n\nvia rack:\n\n``` ruby\nuse StackProf::Middleware, enabled: true,\n                           mode: :cpu,\n                           interval: 1000,\n                           save_every: 5\n```\n\nreporting:\n\n```\n$ stackprof tmp/stackprof-cpu-*.dump --text --limit 1\n  ==================================\n    Mode: cpu(1000)\n    Samples: 60395 (1.09% miss rate)\n    GC: 2851 (4.72%)\n  ==================================\n       TOTAL    (pct)     SAMPLES    (pct)     FRAME\n        1660   (2.7%)        1595   (2.6%)     String#blank?\n\n$ stackprof tmp/stackprof-cpu-*.dump --method 'String#blank?'\n  String#blank? (gems/activesupport-2.3.14.github30/lib/active_support/core_ext/object/blank.rb:80)\n    samples:  1595 self (2.6%)  /   1660 total (2.7%)\n    callers:\n       373  (   41.0%)  ApplicationHelper#current_user\n       192  (   21.1%)  ApplicationHelper#current_repository\n    callers:\n       803  (   48.4%)  Object#present?\n    code:\n                                    |    80  |   def blank?\n   1225    (2.0%) /  1225   (2.0%)  |    81  |     self !~ /[^[:space:]]/\n                                    |    82  |   end\n\n$ stackprof tmp/stackprof-cpu-*.dump --method 'Object#present?'\n  Object#present? (gems/activesupport-2.3.14.github30/lib/active_support/core_ext/object/blank.rb:20)\n    samples:    59 self (0.1%)  /    910 total (1.5%)\n    callees (851 total):\n       803  (   94.4%)  String#blank?\n        32  (    3.8%)  Object#blank?\n        16  (    1.9%)  NilClass#blank?\n    code:\n                                    |    20  |   def present?\n    910    (1.5%) /    59   (0.1%)  |    21  |     !blank?\n                                    |    22  |   end\n```\n\nFor an experimental version of WebUI reporting of stackprof, see [stackprof-webnav](https://github.com/alisnic/stackprof-webnav)\n\nTo generate flamegraphs with Stackprof, additional data must be collected using the `raw: true` flag. Once you've collected results with this flag enabled, generate a flamegraph with:\n\n```\n$ stackprof --flamegraph tmp/stackprof-cpu-myapp.dump \u003e tmp/flamegraph\n```\n\nAfter the flamegraph has been generated, you can generate a viewer command with:\n\n```\n$ stackprof --flamegraph-viewer=tmp/flamegraph\n```\n\nThe `--flamegraph-viewer` command will output the exact shell command you need to run in order to open the `tmp/flamegraph` you generated with the built-in stackprof flamegraph viewer:\n\n![Flamegraph Viewer](http://i.imgur.com/EwndrgD.png)\n\nAlternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):\n\n```\n$ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump \u003e flamegraph.html\n```\n\nAnd just open the result by your browser.\n\n## Sampling\n\nFour sampling modes are supported:\n\n  - `:wall` (using `ITIMER_REAL` and `SIGALRM`) [default mode]\n  - `:cpu` (using `ITIMER_PROF` and `SIGPROF`)\n  - `:object` (using `RUBY_INTERNAL_EVENT_NEWOBJ`)\n  - `:custom` (user-defined via `StackProf.sample`)\n\nSamplers have a tuneable interval which can be used to reduce overhead or increase granularity:\n\n  - Wall time: sample every _interval_ microseconds of wallclock time (default: 1000)\n\n```ruby\nStackProf.run(mode: :wall, out: 'tmp/stackprof.dump', interval: 1000) do\n  #...\nend\n```\n\n  - CPU time: sample every _interval_ microseconds of CPU activity (default: 1000 = 1 millisecond)\n\n```ruby\nStackProf.run(mode: :cpu, out: 'tmp/stackprof.dump', interval: 1000) do\n  #...\nend\n```\n\n  - Object allocation: sample every _interval_ allocations (default: 1)\n\n\n```ruby\nStackProf.run(mode: :object, out: 'tmp/stackprof.dump', interval: 1) do\n  #...\nend\n```\n\nBy default, samples taken during garbage collection will show as garbage collection frames\nincluding both mark and sweep phases. For longer traces, these can leave gaps in a flamegraph\nthat are hard to follow. They can be disabled by setting the `ignore_gc` option to true.\nGarbage collection time will still be present in the profile but not explicitly marked with\nits own frame.\n\nSamples are taken using a combination of three new C-APIs in ruby 2.1:\n\n  - Signal handlers enqueue a sampling job using `rb_postponed_job_register_one`.\n    this ensures callstack samples can be taken safely, in case the VM is garbage collecting\n    or in some other inconsistent state during the interruption.\n\n  - Stack frames are collected via `rb_profile_frames`, which provides low-overhead C-API access\n    to the VM's call stack. No object allocations occur in this path, allowing stackprof to collect\n    callstacks in allocation mode.\n\n  - In allocation mode, samples are taken via `rb_tracepoint_new(RUBY_INTERNAL_EVENT_NEWOBJ)`,\n    which provides a notification every time the VM allocates a new object.\n\n## Aggregation\n\nEach sample consists of N stack frames, where a frame looks something like `MyClass#method` or `block in MySingleton.method`.\nFor each of these frames in the sample, the profiler collects a few pieces of metadata:\n\n  - `samples`: Number of samples where this was the topmost frame\n  - `total_samples`: Samples where this frame was in the stack\n  - `lines`: Samples per line number in this frame\n  - `edges`: Samples per callee frame (methods invoked by this frame)\n\nThe aggregation algorithm is roughly equivalent to the following pseudo code:\n\n``` ruby\ntrap('PROF') do\n  top, *rest = caller\n\n  top.samples += 1\n  top.lines[top.lineno] += 1\n  top.total_samples += 1\n\n  prev = top\n  rest.each do |frame|\n    frame.edges[prev] += 1\n    frame.total_samples += 1\n    prev = frame\n  end\nend\n```\n\nThis technique builds up an incremental call graph from the samples. On any given frame,\nthe sum of the outbound edge weights is equal to total samples collected on that frame\n(`frame.total_samples == frame.edges.values.sum`).\n\n## Reporting\n\nMultiple reporting modes are supported:\n  - Text\n  - Dotgraph\n  - Source annotation\n\n### `StackProf::Report.new(data).print_text`\n\n```\n     TOTAL    (pct)     SAMPLES    (pct)     FRAME\n        91  (48.4%)          91  (48.4%)     A#pow\n        58  (30.9%)          58  (30.9%)     A.newobj\n        34  (18.1%)          34  (18.1%)     block in A#math\n       188 (100.0%)           3   (1.6%)     block (2 levels) in \u003cmain\u003e\n       185  (98.4%)           1   (0.5%)     A#initialize\n        35  (18.6%)           1   (0.5%)     A#math\n       188 (100.0%)           0   (0.0%)     \u003cmain\u003e\n       188 (100.0%)           0   (0.0%)     block in \u003cmain\u003e\n       188 (100.0%)           0   (0.0%)     \u003cmain\u003e\n```\n\n### `StackProf::Report.new(data).print_graphviz`\n\n```\ndigraph profile {\n  70346498324780 [size=23.5531914893617] [fontsize=23.5531914893617] [shape=box] [label=\"A#pow\\n91 (48.4%)\\r\"];\n  70346498324680 [size=18.638297872340424] [fontsize=18.638297872340424] [shape=box] [label=\"A.newobj\\n58 (30.9%)\\r\"];\n  70346498324480 [size=15.063829787234042] [fontsize=15.063829787234042] [shape=box] [label=\"block in A#math\\n34 (18.1%)\\r\"];\n  70346498324220 [size=10.446808510638299] [fontsize=10.446808510638299] [shape=box] [label=\"block (2 levels) in \u003cmain\u003e\\n3 (1.6%)\\rof 188 (100.0%)\\r\"];\n  70346498324220 -\u003e 70346498324900 [label=\"185\"];\n  70346498324900 [size=10.148936170212766] [fontsize=10.148936170212766] [shape=box] [label=\"A#initialize\\n1 (0.5%)\\rof 185 (98.4%)\\r\"];\n  70346498324900 -\u003e 70346498324780 [label=\"91\"];\n  70346498324900 -\u003e 70346498324680 [label=\"58\"];\n  70346498324900 -\u003e 70346498324580 [label=\"35\"];\n  70346498324580 [size=10.148936170212766] [fontsize=10.148936170212766] [shape=box] [label=\"A#math\\n1 (0.5%)\\rof 35 (18.6%)\\r\"];\n  70346498324580 -\u003e 70346498324480 [label=\"34\"];\n  70346497983360 [size=10.0] [fontsize=10.0] [shape=box] [label=\"\u003cmain\u003e\\n0 (0.0%)\\rof 188 (100.0%)\\r\"];\n  70346497983360 -\u003e 70346498325080 [label=\"188\"];\n  70346498324300 [size=10.0] [fontsize=10.0] [shape=box] [label=\"block in \u003cmain\u003e\\n0 (0.0%)\\rof 188 (100.0%)\\r\"];\n  70346498324300 -\u003e 70346498324220 [label=\"188\"];\n  70346498325080 [size=10.0] [fontsize=10.0] [shape=box] [label=\"\u003cmain\u003e\\n0 (0.0%)\\rof 188 (100.0%)\\r\"];\n  70346498325080 -\u003e 70346498324300 [label=\"188\"];\n}\n```\n\n### `StackProf::Report.new(data).print_method(/pow|newobj|math/)`\n\n```\nA#pow (/Users/tmm1/code/stackprof/sample.rb:11)\n                         |    11  |   def pow\n   91  (48.4% / 100.0%)  |    12  |     2 ** 100\n                         |    13  |   end\nA.newobj (/Users/tmm1/code/stackprof/sample.rb:15)\n                         |    15  |   def self.newobj\n   33  (17.6% /  56.9%)  |    16  |     Object.new\n   25  (13.3% /  43.1%)  |    17  |     Object.new\n                         |    18  |   end\nA#math (/Users/tmm1/code/stackprof/sample.rb:20)\n                         |    20  |   def math\n    1   (0.5% / 100.0%)  |    21  |     2.times do\n                         |    22  |       2 + 3 * 4 ^ 5 / 6\nblock in A#math (/Users/tmm1/code/stackprof/sample.rb:21)\n                         |    21  |     2.times do\n   34  (18.1% / 100.0%)  |    22  |       2 + 3 * 4 ^ 5 / 6\n                         |    23  |     end\n```\n\n## Usage\n\nThe profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.\nThe `run` method takes a block of code and returns a profile as a simple hash.\n\n``` ruby\n# sample after every 1ms of cpu activity\nprofile = StackProf.run(mode: :cpu, interval: 1000) do\n  MyCode.execute\nend\n```\n\nThis profile data structure is part of the public API, and is intended to be saved\n(as json/marshal for example) for later processing. The reports above can be generated\nby passing this structure into `StackProf::Report.new`.\n\nThe format itself is very simple. It contains a header and a list of frames. Each frame has a unique ID and\nidentifying information such as its name, file, and line. The frame also contains sampling data, including per-line\nsamples, and a list of relationships to other frames represented as weighted edges.\n\n``` ruby\n{:version=\u003e1.0,\n :mode=\u003e:cpu,\n :inteval=\u003e1000,\n :samples=\u003e188,\n :missed_samples=\u003e0,\n :frames=\u003e\n  {70346498324780=\u003e\n    {:name=\u003e\"A#pow\",\n     :file=\u003e\"/Users/tmm1/code/stackprof/sample.rb\",\n     :line=\u003e11,\n     :total_samples=\u003e91,\n     :samples=\u003e91,\n     :lines=\u003e{12=\u003e91}},\n   70346498324900=\u003e\n    {:name=\u003e\"A#initialize\",\n     :file=\u003e\"/Users/tmm1/code/stackprof/sample.rb\",\n     :line=\u003e5,\n     :total_samples=\u003e185,\n     :samples=\u003e1,\n     :edges=\u003e{70346498324780=\u003e91, 70346498324680=\u003e58, 70346498324580=\u003e35},\n     :lines=\u003e{8=\u003e1}},\n```\n\nAbove, `A#pow` was involved in 91 samples, and in all cases it was at the top of the stack on line 12.\n\n`A#initialize` was in 185 samples, but it was at the top of the stack in only 1 sample. The rest of the samples are\ndivided up between its callee edges. All 91 calls to `A#pow` came from `A#initialize`, as seen by the edge numbered\n`70346498324780`.\n\n## Advanced usage\n\nThe profiler can be started and stopped manually. Results are accumulated until retrieval, across\nmultiple `start`/`stop` invocations.\n\n``` ruby\nStackProf.running? # =\u003e false\nStackProf.start(mode: :cpu)\nStackProf.running? # =\u003e true\nStackProf.stop\nStackProf.results('/tmp/some.file')\n```\n\n## All options\n\n`StackProf.run` accepts an options hash. Currently, the following options are recognized:\n\nOption      | Meaning\n-------     | ---------\n`mode`      | Mode of sampling: `:cpu`, `:wall`, `:object`, or `:custom` [c.f.](#sampling)\n`out`       | The target file, which will be overwritten\n`interval`  | Mode-relative sample rate [c.f.](#sampling)\n`ignore_gc` | Ignore garbage collection frames\n`aggregate` | Defaults: `true` - if `false` disables [aggregation](#aggregation)\n`raw`       | Defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types\n`metadata`  | Defaults to `{}`. Must be a `Hash`. metadata associated with this profile\n`save_every`| (Rack middleware only) write the target file after this many requests\n\n## Todo\n\n* file/iseq blacklist\n* restore signal handlers on stop\n","funding_links":[],"categories":["Ruby","Profiler and Optimization","Gems","Performance tools","Awesome Ruby CLIs"],"sub_categories":["Profiling and Performance","Profiling"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmm1%2Fstackprof","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftmm1%2Fstackprof","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmm1%2Fstackprof/lists"}