{"id":13879182,"url":"https://github.com/Riskified/timeasure","last_synced_at":"2025-07-16T15:31:40.559Z","repository":{"id":59159180,"uuid":"115856877","full_name":"Riskified/timeasure","owner":"Riskified","description":"Transparent method-level wrapper for profiling purposes in Ruby","archived":false,"fork":false,"pushed_at":"2025-02-10T22:09:42.000Z","size":49,"stargazers_count":198,"open_issues_count":0,"forks_count":6,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-06-19T01:38:07.821Z","etag":null,"topics":["measurements","performance","rails","riskified","ruby"],"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/Riskified.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-12-31T10:32:08.000Z","updated_at":"2025-02-14T15:52:36.000Z","dependencies_parsed_at":"2025-05-20T15:04:56.895Z","dependency_job_id":"5d5f5c27-ca6c-4862-a5f1-f4d762a59781","html_url":"https://github.com/Riskified/timeasure","commit_stats":{"total_commits":52,"total_committers":2,"mean_commits":26.0,"dds":0.07692307692307687,"last_synced_commit":"20cc8da6decb7bf8b7280b12e5f97e0727c7b649"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Riskified/timeasure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riskified%2Ftimeasure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riskified%2Ftimeasure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riskified%2Ftimeasure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riskified%2Ftimeasure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Riskified","download_url":"https://codeload.github.com/Riskified/timeasure/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riskified%2Ftimeasure/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265521431,"owners_count":23781501,"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":["measurements","performance","rails","riskified","ruby"],"created_at":"2024-08-06T08:02:12.459Z","updated_at":"2025-07-16T15:31:40.286Z","avatar_url":"https://github.com/Riskified.png","language":"Ruby","readme":"[![Gem Version](https://badge.fury.io/rb/timeasure.svg)](https://badge.fury.io/rb/timeasure)\n[![Maintainability](https://api.codeclimate.com/v1/badges/0ceacd5b50b0cd45fb8f/maintainability)](https://codeclimate.com/github/Riskified/timeasure/maintainability)\n\n# Timeasure\n\n**What Is It?**\n\nTimeasure is a transparent method-level wrapper for profiling purposes. See a live example [right here!](https://timeasure-demo.herokuapp.com/)\n\nTimeasure is a Ruby gem that allows measuring the runtime of methods in production environments\nwithout having to alter the code of the methods themselves.\n\nTimeasure allows you to declare tracked methods to be measured transparently upon each call.\nMeasured calls are then reported to Timeasure's Profiler, which aggregates the measurements on the method level.\nThis part is configurable and if you wish you can report measurements to another profiler of your choice.\n\n**Why Use It?**\n\nTimeasure was created in order to serve as an easy-to-use, self-contained framework for method-level profiling\nthat is safe to use in production. Testing runtime in non-production environments is helpful, but there is\ngreat value to the knowledge gained by measuring what really goes on at real time.\n\n**What To Do With the Data?**\n\nThe imagined usage of measured methods timing is to aggregate it along a certain transaction and report it to a live\nBI service such as [NewRelic Insights](https://newrelic.com/insights) or [Keen.io](https://keen.io/);\nhowever, different usages might prove helpful as well, such as writing the data to a database or a file.\n\n**General Notes**\n\nTimeasure uses minimal intervention in the Ruby Object Model for tracked modules and classes.\nIt integrates well within Rails and non-Rails apps.\n\nTimeasure is inspired by [Metaprogramming Ruby 2](https://pragprog.com/book/ppmetr2/metaprogramming-ruby-2)\nby [Paolo Perrotta](https://twitter.com/nusco)\nand by [this](https://hashrocket.com/blog/posts/module-prepend-a-super-story) blog post by Hashrocket.\n\nTimeasure is developed and maintained by [Eliav Lavi](http://www.eliavlavi.com) \u0026 [Riskified](https://www.riskified.com/).\n\n## Requirements\n\nRuby 2.1 or a later version is mandatory. (Timeasure uses `Module#prepend` introduced in Ruby 2.0 and `Process::CLOCK_MONOTONIC` introduced in Ruby 2.1.)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'timeasure'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install timeasure\n\n## Usage\n#### 1. Include Timeasure in Modules and Classes\nSimply include the Timeasure module in any class or module and declare the desired methods to track:\n\n```ruby\nclass Foo\n  include Timeasure\n  tracked_class_methods :bar\n  tracked_instance_methods :baz, :qux\n  \n  def self.bar\n    # some class-level stuff that can benefit from measuring runtime...\n  end\n    \n  def baz\n    # some instance-level stuff that can benefit from measuring runtime...\n  end\n  \n  def qux\n    # some other instance-level stuff that can benefit from measuring runtime...\n  end\nend\n```\n**An Important Note Regarding Private Methods**\n\nIf you need to track any private methods - either class methods or instance methods - use the designated class macros for that:\n\n```ruby\nclass Foo\n  include Timeasure\n  tracked_class_methods :a_class_method_that_calls_private_methods\n  tracked_private_class_methods :a_scoped_private_class_method, :an_inline_private_class_method\n  \n  class \u003c\u003c self\n    def a_class_method_that_calls_private_methods\n      a_scoped_private_class_method\n      an_inline_private_class_method\n    end\n    \n    def a_scoped_private_class_method\n      # some private class-level stuff that can benefit from measuring runtime...\n    end\n  end\n  \n  def self.an_inline_private_class_method\n    # some other private class-level stuff that can benefit from measuring runtime...\n  end\nend\n```\n\nAnd the instance-level equivalent:\n\n```ruby\nclass Foo\n  include Timeasure\n  tracked_instance_methods :a_instance_method_that_calls_private_methods\n  tracked_private_instance_methods :a_scoped_private_instance_method, :an_inline_private_instance_method\n  \n  class \u003c\u003c self\n    def a_instance_method_that_calls_private_methods\n      a_scoped_private_instance_method\n      an_inline_private_instance_method\n    end\n    \n    def a_scoped_private_instance_method\n      # some private instance-level stuff that can benefit from measuring runtime...\n    end\n  end\n  \n  def self.an_inline_private_instance_method\n    # some other private instance-level stuff that can benefit from measuring runtime...\n  end\nend\n```\n\n**ATTENTION!**\n\n**Declaring the tracking of private methods with `tracked_class_methods` or `tracked_instance_methods` will end up in `NoMethodError` upon calling their triggering method!**\n\nAlso, tracking your public methods with `tracked_private_class_methods` or `tracked_private_instance_methods` will make your class' interface inaccessible.\nThe reason for these two is that since Timeasure is declared at the top of the class,\nit cannot know in advance which methods will be declared as private, so you need to specify this explicitly.\n\nAs a side note, it could be claimed that as a rule of thumb, if you find yourself measuring private methods,\nthis might be a good idea to invest in refactoring this area of code and [Extract Class](https://refactoring.guru/extract-class).\nHowever, this is not always possible, of course, especially when working on legacy code.\nHence, this feature of Timeasure should be considered as somewhat of a last resort and be handled with care.   \n\n#### 2. Define the Boundaries of the Tracked Transaction\n**Preparing for Method Tracking**\n\nThe user is responsible for managing the final reporting and the clean-up of the aggregated data after each transation.\nIt is recommended to prepare the profiler at the beginning of a transaction in which tracked methods exist with\n\n```ruby\nTimeasure::Profiling::Manager.prepare\n```\nand to re-prepare it again at the end of it in order to ensure a \"clean slate\" -\nafter you have handled the aggregated data in some way.\n\n**Getting Hold of the Data**\n\nIn order to get hold of the reported methods data, use \n```ruby\nTimeasure::Profiling::Manager.export\n````\nThis will return an array of `ReportedMethod`s. Each `ReportedMethod` object holds the aggregated timing data per\neach tracked method call. This means that no matter how many times you call a tracked method, Timeasure's Profiler will\nstill hold a single `ReportedMethod` object to represent it.\n\n`ReportedMethod` allows reading the following attributes:  \n* `klass_name`: Name of the class in which the tracked method resides.\n* `method_name`: Name of the tracked method.\n* `segment`: See [Segmented Method Tracking](#segmented-method-tracking) below.\n* `metadata`: See [Carrying Metadata](#carrying-metadata) below.\n* `method_path`: `klass_name` and `method_name` concatenated.\n* `full_path`: Same as `method_path` unless segmentation is declared,\nin which case the segment will be concatenated to the string as well. See [Segmented Method Tracking](#segmented-method-tracking) below.\n* `runtime_sum`: The aggregated time it took the reported method in question to run across all calls.\n* `call_count`: The times the reported method in question was called across all calls.\n \n\n## Advanced Usage\n#### Segmented Method Tracking\nTimeasure was designed to separate regular code from its time measurement declaration.\nThis is achieved by Timeasure's class macros `tracked_class_methods` and `tracked_instance_methods`.\nSometimes, however, the need for additional data might arise. Imagine this method:\n\n```ruby\nclass Foo\n  def bar(baz)\n    # some stuff that can benefit from measuring runtime\n    # yet its runtime is also highly affected by the value of baz...\n  end\nend\n```\n\nWe've seen how Timeasure makes it easy to measure the `bar` method.\nHowever, if we wish to segment each call by the value of `baz`,\nwe may use Timeasure's direct interface and send this value as a **segment**:\n\n```ruby\nclass Foo\n  def bar(baz)\n    Timeasure.measure(klass_name: 'Foo', method_name: 'bar', segment: { baz: baz }) do\n      # the code to be measured\n    end\n  end\nend\n```\n\nFor such calls, Timeasure's Profiler will aggregate the data in `ReportedMethod` objects grouped by\nclass, method and segment.\n\nThis approach obviously violates Timeasure's idea of separating code and measurement-declaration,\nbut it allows for much more detailed investigations, if needed.\nThis will result in different `ReportedMethod` object in Timeasure's Profiler for\neach combination of class, method and segment. Accordingly, such `ReportedMethod` object will include\nthese three elements, concatenated, as the value for `ReportedMethod#full_path`. \n\n#### Carrying Metadata\nThis feature was developed in order to complement the segmented method tracking.\n\nSometimes carrying data with measurement that does not define a segment might be needed.\nFor example, assuming we save all our `ReportedMethod`s to some table called `reported_methods`,\nwe might want to supply a custom table name for specific measurements.\nThis might be achieved by using `metadata`:\n \n```ruby\nclass Foo\n  def bar\n    Timeasure.measure(klass_name: 'Foo', method_name: 'bar', metadata: { table_name: 'my_custom_table' }) do\n      # the code to be measured\n    end\n  end\nend\n```\n\nUnlike Segments, Timeasure only carries the Metadata onwards.\nIt is up to the user to make use of this data, probably after calling `Timeasure::Profiling::Manager.export`.\n\n## Notes\n#### Compatibility with RSpec\n\nIf you run your test suite with Timeasure installed and modules, classes and methods tracked and all works fine - hurray!\nHowever, due to the mechanics of Timeasure - namely, its usage of prepended modules - there exist a problem with\n**stubbing** Timeasure-tracked method (RSpec does not support stubbing methods that appear in a prepended module).\nTo be accurate, that means that if you are tracking method `#foo`, you can not\ndeclare something like `allow(bar).to receive(:foo).and_return(bar)`. Your specs will refuse to run in this case.\nTo solve that problem you can configure Timeasure's `enable_timeasure_proc` **not** to run under certain conditions.\n\nIf you are on Rails, add the following as a Rails initializer:\n\n```ruby\nrequire 'timeasure'\n\nTimeasure.configure do |configuration|\n  configuration.enable_timeasure_proc = lambda { !Rails.env.test? }\nend\n```  \n\nTimeasure will not come into action if the expression in the block evaluates to `false`.\nBy default this block evaluates to `true`.\n\nIn case you are loading files manually (probably not on Rails), you can add this to *spec_helper.rb*:\n\n```ruby\nRSpec.configure do |config|\n  config.before(:suite) do\n    Timeasure.configure do |configuration|\n      configuration.enable_timeasure_proc = lambda { false }\n    end\n  end\nend\n``` \n\n\n## Feature Requests\n\nTimeasure is open for changes and requests!\nIf you have an idea, a question or some need, feel free to contact me here or at eliavlavi@gmail.com.\n\n## Contributing\n\n1. Fork it ( https://github.com/riskified/timeasure/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRiskified%2Ftimeasure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRiskified%2Ftimeasure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRiskified%2Ftimeasure/lists"}