{"id":20061661,"url":"https://github.com/tylerrick/unroller","last_synced_at":"2025-09-07T14:10:30.695Z","repository":{"id":66442527,"uuid":"95685","full_name":"TylerRick/unroller","owner":"TylerRick","description":"Ruby Unroller, a code tracing tool","archived":false,"fork":false,"pushed_at":"2012-06-21T18:40:39.000Z","size":213,"stargazers_count":35,"open_issues_count":3,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-05T16:44:44.249Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://unroller.rubyforge.org/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TylerRick.png","metadata":{"files":{"readme":"Readme","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2008-12-23T05:34:39.000Z","updated_at":"2023-06-18T15:41:48.000Z","dependencies_parsed_at":"2023-02-20T04:30:40.665Z","dependency_job_id":null,"html_url":"https://github.com/TylerRick/unroller","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/TylerRick/unroller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerRick%2Funroller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerRick%2Funroller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerRick%2Funroller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerRick%2Funroller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TylerRick","download_url":"https://codeload.github.com/TylerRick/unroller/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerRick%2Funroller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274046000,"owners_count":25212982,"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","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-11-13T13:21:26.021Z","updated_at":"2025-09-07T14:10:30.649Z","avatar_url":"https://github.com/TylerRick.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"= \u003ci\u003eRuby Unroller\u003c/i\u003e -- a code tracing tool for Ruby\n\n[\u003cb\u003eHome page\u003c/b\u003e:]        http://unroller.rubyforge.org/\n[\u003cb\u003eProject site\u003c/b\u003e:]     http://rubyforge.org/projects/unroller/\n[\u003cb\u003eGem install\u003c/b\u003e:]      \u003ctt\u003egem install unroller\u003c/tt\u003e\n[\u003cb\u003eWiki\u003c/b\u003e:]             http://whynotwiki.com/Ruby_Unroller\n[\u003cb\u003eAuthor\u003c/b\u003e:]           Tyler Rick\n[\u003cb\u003eCopyright\u003c/b\u003e:]        2007 QualitySmith, Inc.\n[\u003cb\u003eLicense\u003c/b\u003e:]          {GNU General Public License}[http://www.gnu.org/copyleft/gpl.html]\n\n==Introduction / What it is\n\nRuby Unroller is a tool for generating human-readable \"execution traces\".\nWhile it is enabled, it will watch every Ruby statement and method call that gets executed and will display the source code on your screen in real-time as it is being executed.\n\nYou can use it for...\n\n===Debugging\n\nA very efficient tool for seeing what code actually gets executed... Add it to your toolbox along with the other tricks you already know, like inspecting \u003ctt\u003ecaller()\u003c/tt\u003e or printing \"got here\" at various places in your code.\n\n===Learning/Exploring\n\nIt's a great tool for exploring 3rd-party source code that you aren't familiar with. If you've ever found yourself wondering \"How was that method implemented?\" or \"What's going on behind the scenes when I call such-and-such?\", then this tool is for you!\n\n\n==Usage\n\nJust insert these line before the point you want to start tracing:\n\n  require 'unroller'\n  Unroller::trace\n\nAnd if you want the tracing to stop, just add this line somewhere:\n\n  Unroller::trace_off\n\nYou can also pass a block to \u003ctt\u003eUnroller::trace\u003c/tt\u003e and it will automatically turn off tracing as soon as the block has been executed.\n\n  Unroller::trace do\n  \t...\n  end\n\nIf you want really quick and dirty:\n  require 'unroller/tron'\n  ...\n\nOr, for the interactive debugger:\n  require 'unroller/debug'\n  ...\n\n===Example\n\nSay I have an ActiveRecord model and want to know exactly what actually goes on behind the scenes when I call model.save! . All I have to do is wrap the method call in a \"trace\" block, like this:\n\n  Unroller::trace do\n    model.save!\n  end\n\n, start the script, and then watch as the source code is unfolded before my very eyes!\n\nScreenshot[link:include/screenshot1.png]\n\n(The above shows \u003ctt\u003e:display_style =\u003e :concise\u003c/tt\u003e.)\n\nThis is much more efficient and reliable than manually tracing through the execution yourself!  (Which would involve trying to guess which file the \u003ctt\u003esave!\u003c/tt\u003e method would be defined in, searching for the file in the depths of \u003ctt\u003e/usr/lib/ruby/gems\u003c/tt\u003e, scrolling down to the right line number, and repeating a zillion times...)\n\nHere's an example of the interactive debugger mode:\n\nScreenshot[link:include/screenshot_interactive.png]\n\n(The above shows \u003ctt\u003e:display_style =\u003e :show_entire_method_body\u003c/tt\u003e.)\n\n===Interactive debugger\n\nCurrently there are two basic modes: non-interactive trace mode and interactive debugger mode. To enable the latter, just call \u003ctt\u003eUnroller::debug\u003c/tt\u003e, kind of like this:\n\n  Unroller::debug do\n    whatever()\n  end\n\nIt will prompt you after every line of code and ask you what to do. Currently the options are:\n* \u003cb\u003estep out\u003c/b\u003e\n* \u003cb\u003estep over\u003c/b\u003e\n* \u003cb\u003estep into\u003c/b\u003e\n* \u003cb\u003eshow locals\u003c/b\u003e: lists all local variables and their variables (using pp pretty printer)\n* \u003cb\u003erun\u003c/b\u003e: ends interactive mode\n\n\u003cb\u003eThe interactive debugger is still pretty experimental and is sure to still have some bugs in it!\u003c/b\u003e\n\n===Inspecting variables\n\nIf you'd like to see the values of all arguments/parameters that were passed into the method for each method call, just pass in the \u003ctt\u003e:show_args =\u003e true\u003c/tt\u003e option.\n\nIf you'd like to see the values of all local variables as they exist right before executing the current line, just pass in the \u003ctt\u003e:show_locals =\u003e true\u003c/tt\u003e option.\n\nNot implemented:\n* You might like to know that the state of those variables is _after_ executing that line, too, but currently that's not implemented. (Use the interactive debugger instead.)\n* Also, I haven't yet figured out a way to show the return value it's going to return with when we hit a 'return' event. That would be nice to have, too, if one can figure out how to implement it...\n\n===Only tracing if a condition is met\n\nFor example, a lot of the time you will be in a loop or iterator and you will only be interested in what's going on \u003ci\u003eduring certain iterations\u003c/i\u003e of that loop.\n\nThere are two ways you can accomplish that selectivity:\n\nThe good old simple way:\n\n  Unroller::trace if name =~ /interesting/\n    code_that_may_or_may_not_be_traced\n  Unroller::trace_off\n\nAnd the somewhat trickier but arguably more elegant way that still uses a block (which always gets executed):\n\n  Unroller::trace :if =\u003e { name =~ /interesting/ } do\n    code_that_may_or_may_not_be_traced\n  end\n\nThe other reason that the latter way is preferred is that the return value of the code-being-traced is preserved. With the first method, you could end up breaking things if the trace_off happens to be the last value in your method (because then the value of trace_off will be used as the return value).\n\nNote: The actual application code (the code in the block passed to Unroller::trace, if using the block version) will _always_ get executed, with either of these methods. It is only the tracing that we are toggling, not the execution of the code within the trace(d) block.\n\n===Reducing verbosity\n\nThis can generate some really *verbose* output... Not only can be impractical to try to *read* through the reams of pages it can produce, but it can also take an hour just to output it in the first place!\n\nA couple options are available to help things under control. The two main approaches are to only trace a limited section of code or to trace a large section of code but exclude certain types of calls (for example, low-level methods that you don't care about.)\n\nYou may find that your trace is cluttered/dominated by calls to a small set of methods and classes that you don't care about. These options help you to exclude the worst offenders in a hurry:\n\n\u003ctt\u003e:file_match =\u003e file_match\u003c/tt\u003e ::\n  Unroller will only show a trace for code whose filename matches \u003ctt\u003efile_match\u003c/tt\u003e. If \u003ctt\u003efile_match\u003c/tt\u003e is not already a regular expression it will be escaped and turned into one (\u003ctt\u003e/file_match/\u003c/tt\u003e).\n\u003ctt\u003e:dir_match =\u003e dir_match\u003c/tt\u003e ::\n  Unroller will only show a trace for code whose filename matches \u003ctt\u003edir_match\u003c/tt\u003e. If \u003ctt\u003edir_match\u003c/tt\u003e is not already a regular expression it will be escaped and turned into one (\u003ctt\u003e/^dir_match/\u003c/tt\u003e). Tip: You can simply pass in __FILE__ and it will automatically turn that into File.expand_path(File.dirname(__FILE__)) for you.\n\u003ctt\u003e:exclude_classes\u003c/tt\u003e ::\n  Unroller won't show a trace for any calls to methods from the given class or classes (regular expressions).\n  Pass [/class_name/, :recursive] to also not show the trace for any calls made *from* those uninteresting methods.\n\u003ctt\u003e:exclude_methods\u003c/tt\u003e (not implementd) ::\n  Like \u003ctt\u003e:exclude_classes\u003c/tt\u003e only you give it _method_ names instead of class names. Maybe will have the ability to specify a class name as well as a method name if you want to be more specific (f.e., \u003ctt\u003eThatOneClass#format\u003c/tt\u003e if you still want to include calls to other methods named \u003ctt\u003eformat\u003c/tt\u003e).\n\nThese options are for more general overall control:\n\n\u003ctt\u003e:max_depth =\u003e depth\u003c/tt\u003e ::\n  Use this to prevent from going more than \u003ctt\u003edepth\u003c/tt\u003e levels deep (_relative_ to starting depth) if you find that the trace is cluttered by a bunch of really deep calls.\n\u003ctt\u003e:max_lines\u003c/tt\u003e ::\n  If you don't know where to place the trace(false) and you just want it to stop on its own after so many lines, you could use this...\n\nI'd recommend using \u003ctt\u003e:max_depth\u003c/tt\u003e option with something sane like 3 or 5 most of the time.\n\nExamples:\n\n  Unroller::trace :max_lines =\u003e 100, :exclude_classes =\u003e /Boring/ { ... }\n\n  Unroller::trace :if =\u003e proc{$tracing_enabled}, :max_depth =\u003e 9, :exclude_classes =\u003e\n    [\n      /Benchmark/,\n      /Logger/,\n      /MonitorMixin/,\n      /Set/,\n      /HashWithIndifferentAccess/,\n      /ERB/,\n      /ActiveRecord/,\n      /SQLite3/,\n      /Class/,\n      /ActiveSupport/,\n      /ActiveSupport::Deprecation/,\n      /Pathname/\n    ] do\n      ...\n    end\n\nIgnore a section of code that is within the block passed to +trace+:\n\n  Unroller::trace do\n\t  stuff_you_care_about\n    Unroller::exclude do\n      stuff_that_you_really_dont_care_about\n\t  end\n\t  stuff_you_care_about\n  end\n\n===How did that method even get *called*?\n\nSome of the time, you may be trying to answer the question \"How did that method even get *called*?\" This tool can't directly help answer that question unless you're willing to enable tracing really early and wade through pages and pages of output.\n\nIf you enable tracing from within that method, it will only show the calls that came *after* the mysterious call to the mystery function, not what came *before*, which is what you're interested in.\n\nSo... you could always \u003ctt\u003ep caller(0)\u003c/tt\u003e within your mystery function and then put a trace around one of the calls leading up to this call, a little further up the stack...\n\nI know, wouldn't it be nice if you could just tell it, \"Show me the execution traces for the 3 calls leading up to this call\"? But alas, Unroller can only affect what happens _after_ it gets called, not before.\n*If I ever get around to writing an interactive tree-based UI for this, then I guess we could give the _impression_ of being able to show calls leading up to a certain call. But only by recording *all* calls and then hiding everything except the calls you're interested in...\n*If it is completely reproduceable, then we could even do it without the tree-based UI. We could just keep track of the \"executed line number\" at which the interesting event occured (line_number_of_interest), then subtract however many lines we want to see leading up to that call (introductory_lines_count), re-run the script, and have it stop after (line_number_of_interest - introductory_lines_count) lines are executed.\n\n===When did that constant get set the _first_ time??===\n\nIf you start getting errors like \"warning: already initialized constant OPTIONS\", then you may be wondering when it was first initialized (something the backtrace fails to tell you).\n\nHere's one way you could try to answer that question...\n  Unroller::trace :line_matches =\u003e 'OPTIONS ='.to_re\n\n===Usage in Rails\n\nUnroller can be useful for debugging in development mode as well as in tests.\n\n\u003cb\u003eRequires a terminal\u003c/p\u003e. Keep in mind that it requires a terminal onto which to output, so it will work if you've started your server with \u003ctt\u003e./script/server\u003c/tt\u003e; it will _not_ work if the server is detached from the terminal using \u003ctt\u003e./script/server -d\u003c/tt\u003e, for example, or if it's being executed via FastCGI... Don't worry, it will gently remind you of this with a happy \u003ctt\u003ecan't get terminal parameters (Inappropriate ioctl for device)\u003c/tt\u003e error if you forget about it.\n\nRails has a lot of levels of abstractions, so even a seemingly simple call can generate many, many method calls. You may want to try using some of the filtering options, such as \u003ctt\u003e:exclude_classes\u003c/tt\u003e, to reduce the verbosity of the output.\n\n\u003cb\u003eUnrolling an action in your controller:\u003c/b\u003e\n\nIf you want to see all code that gets executed within a certain action in your controller, you can just add this snippet to your controller, request the page, and watch the console where you have Webrick running as pages and pages of ActionController/ActionRecord code go flying by...\n\n  around_filter do |controller, action|\n    Unroller::trace do\n      action.call\n    end\n  end\n\n==Installation\n\n  sudo gem install unroller --include-dependencies\n\nThe dependencies include: facets, quality_extensions, colored, and extensions\n\n==Status\n\nIt's pretty much fully functional, but may still have a couple rough edges.\n\nGenerally it behaves quite reliably and stably.\n\n(Don't even *think* about leaving this in your production code!)\n\nThe code isn't really clean and there aren't (m)any automated tests yet because I've kind of thrown this together in a big hurry, but I hope to solve both of those problems eventually.\nYou might consider this a \"prototype\" right now -- it works, but it wouldn't hurt at some point to throw it away and re-implement it a bit better.\n\nAlso keep in mind that the API as subject to change as I try to think of better design ideas and as I get feedback from people telling me what they want to see.\n\n==About the name\n\nI called it \"Ruby Unroller\" because it visually \"unrolls\" a stack trace for you (like a scroll?). (And it sounds cooler than \"Ruby Script Execution Tracer\".)\n\nIf there are method calls (even recursion), it will sort of \"unroll\" the method definition for you so you can see it.\n\nIt also alludes to some ideas from compiler design. Think {\"method inlining\"}[http://en.wikipedia.org/wiki/Inline_expansion] and {\"loop unrolling\"}[http://en.wikipedia.org/wiki/Loop_unrolling] in real-time. Not quite the same thing, but close enough.\n\nIt takes something that is actually very stack-based (the path that Ruby interpreter takes while executing your source code) and \"flattens\" it. It still tries to keep things appearing hierarchical (by means of the indent levels), but in essence it takes code from a bunch of different files and merges them into one \"flat\" output stream.\n\nPhew!\n\nOther name ideas are welcome!\n* Unfolder?\n* ...\n\n==Similarity to debuggers/profilers / What it is not\n\nIt's sort of like a cross between a Ruby debugger and a profiler.\n\nIt's like a debugger because it lets you step through code execution one line at time (similar to the \"step into\" / \"next\" commands in most debuggers). It's not a debugger, though, because it doesn't let you inspect variables, set breakpoints, etc. It lets you *watch* the execution ... and that's it! So it's not as powerful as a real debugger, but it can be a lot *faster*! Rather than pressing F8 (or whatever the shortcut is for the 'step' command) 1000 times, you can just tell it \"trace from here to here\" and it will.\n\nAnd like a profiler, it follows every method call, using Ruby's set_trace_func. Like a profiler, it can be helpful for diagnosing performance bottlenecks in your application -- at least those that involve excessive or unwanted method calls or recursion. It's not a profiler, though, because it doesn't _time_ the method calls like a 'real' profiler would.\n\nIt's also sort of like a call stack (caller(0)). But unlike the callstack you usually see, which only shows where you've been, this one doesn't show where you've been; it only shows where you _going_ -- or more accurately, where you *go* after you enable the tracing. So while it works equally well for tracing while the call stack is being unwound (returning from calls, which pops that call off the stack), the most common use of the tracer is to see what happens \"within\" a method call -- that method will in turn call _other_ methods, and the stack will grow temporarily -- but then it will unwind again and you'll get back to the method where you started the trace (hopefully -- eventually?), with a net change in stack size of 0.\n\n(If you _do_ want to watch the trace as the call stack is unwound completely, be sure to pass \u003ctt\u003e:depth =\u003e caller(0).size\u003c/tt\u003e to the Unroller so that it can start the indenting at an appropriate level...)\n\n==Possibly related projects\n\nI didn't see any projects out there that did what I was wanting, so I wrote my own (and was surprised by how easy it was!). But if you know of any similar projects out there, let me know and I'll check it out.\n\nHere are the closest projects I've run across so far...\n* http://rubyforge.org/projects/dev-utils/ : Collects utilities that aid Ruby development, e.g. testing and debugging. Version 1.0 contains simple debug logging, tracing, and escaping to IRB.\n* http://rubyforge.org/projects/ruby-uml/ : Generates uml diagrams by tracing the run of an application for analysation of an existing application and to provide support for refactorisations.\n\nI've also heard rumors that Ruby comes standard with some kind of debugger (?) ... Maybe I wouldn't have written this if I'd known how to use that (is it any good?? is it easy to use??)... but... I still haven't even looked at that.\n\n==To do\n\nYou're welcome to submit comments, ideas, and/or patches.\n\n* Make a GUI interface that lets you quickly collapse/nodes nodes of the tree.\n* :include_classes option in addition to :exclude_classes?\n\n===Presets\n\n* Have some \"presets\" for what you might want to exclude if tracing an ActiveRecord request for example. In that case, you probably don't want to see the internals of any support code, like any methods from ActiveSupport.\n  * :rails =\u003e true\n  * :preset =\u003e :Rails : Exclude ActiveSupport, Dependencies, etc.\n  * :preset =\u003e :ActiveSupport : Exclude ActiveRecord, etc.\n  * :preset =\u003e :'ActiveRecord high level' : excludes the lowel-level database stuff (like the individual adapter (SQLite, MySQL, ...).\n  * :preset =\u003e :'ActiveRecord low level'\n\nMight be more intuitive to say :exclude =\u003e :boring_rails_stuff\n(If it's a symbol passed in instead of a string or regexp, we'll assume it's a preset, which will be turned into the strings/regexp's that it defines)\n\nAlso :include =\u003e :something.\n\nLet them store presets in a ~/.unroller file so they're not limited to my ideas, and don't have to type out a long list of exclusions every time they want to reuse a common set of exclusions.\n\n===\"Watch for\" conditions\n\nOnly traces / shows you when/if a certain condition is met.\n\nYou could use it, for example, to answer the question, \"When did this method get added?? I don't remember adding that?!\"\n\nUseful when you see evidence that something *is* happening but you just can't figure out *where* it's happening or *why*.\n\nYou'd just start the tracer at the very beginning of your application and tell it to watch for lines that might have caused it to be added.\n\nIt wouldn't be perfect by any means, but it it might be better than nothing...\n\n* You'd kind of have to know a lot about Ruby and the various ways methods can get added. (Normal def statements or alias or alias_method or alias_method_chain, etc.)\n* It would be a rather inexact test, as it would probably use string/Regexp matching, which can easily be too broad or too strict\n* It wouldn't work for stuff that happens during c-calls or if people don't specify the correct file/line for their evals (because if they don't do that, we can't read the source code)\n\nMight look something like this...\n\nUnroller::trace, :watch_for =\u003e proc {|event| event.type =~ /line/ \u0026\u0026 (event.code =~ /def/ || event.method =~ /method/) }\nend\n\nOther name ideas: :match_code, :match_event, :event_match, :only_events\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerrick%2Funroller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylerrick%2Funroller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerrick%2Funroller/lists"}