{"id":22366577,"url":"https://github.com/microsoft/snippet-timekeeper","last_synced_at":"2026-03-01T01:37:19.574Z","repository":{"id":43412119,"uuid":"397340910","full_name":"microsoft/snippet-timekeeper","owner":"microsoft","description":"An android library to measure code execution time. No need to remove the measurement code, automatically becomes no-op in the release variants. Does not compromise with the code readability and comes with features that enhance the developer experience.","archived":false,"fork":false,"pushed_at":"2022-04-11T12:19:52.000Z","size":364,"stargazers_count":76,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-14T09:30:46.238Z","etag":null,"topics":["android","android-application","android-development","android-library","execution-time","java","kotlin","kotlin-android","measurement","measurements","performance","performance-analysis","performance-metrics"],"latest_commit_sha":null,"homepage":"","language":"Java","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/microsoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2021-08-17T17:33:53.000Z","updated_at":"2026-02-02T15:40:58.000Z","dependencies_parsed_at":"2022-08-26T21:40:53.857Z","dependency_job_id":null,"html_url":"https://github.com/microsoft/snippet-timekeeper","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/microsoft/snippet-timekeeper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/microsoft%2Fsnippet-timekeeper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/microsoft%2Fsnippet-timekeeper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/microsoft%2Fsnippet-timekeeper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/microsoft%2Fsnippet-timekeeper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/microsoft","download_url":"https://codeload.github.com/microsoft/snippet-timekeeper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/microsoft%2Fsnippet-timekeeper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29638521,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"ssl_error","status_checked_at":"2026-02-19T22:32:38.330Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["android","android-application","android-development","android-library","execution-time","java","kotlin","kotlin-android","measurement","measurements","performance","performance-analysis","performance-metrics"],"created_at":"2024-12-04T18:14:09.713Z","updated_at":"2026-03-01T01:37:19.546Z","avatar_url":"https://github.com/microsoft.png","language":"Java","readme":"﻿\n \n[![Android Weekly #498](https://androidweekly.net/issues/issue-498/badge)](https://androidweekly.net/issues/issue-498) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-snippet--timekeeper-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/8263)\n\n# Snippet  \n  \n`Snippet` is an extensible android library to measure execution times of the code sections  in a way that does not compromise with the readability and can be shipped to production in a way that the measurement code will be a no-op, without any additional setup. New behaviours can be added in the library by extending Execution paths. 2 execution paths provided with the library are:\n 1. `MeasuredExecutionPath` - The code path the does the measurement code spans\n 2. `ReleaseExecutionPath` - A no-op path (default path) that is usually installed in the release variants.  \n\n  \n# Features  \n  \n1. Easy to integrate and configure  \n2. Switch behavior depending on build type  \n3. Reduces boiler plate  \n4. Data reuse  \n5. Makes PR reviews more quantitative  \n6. APK size impact of 23KB  \n7. Designed to be thread safe \u0026 null safe  \n8. Rich API  \n9. Fully documented, just run java docs!  \n10. Thread safe\n  \n# Vocabulary  \n  \n1. \u003cb\u003e Capture \u003c/b\u003e : Logical span of code. Can be contiguous or non-contiguous.     \n2. \u003cb\u003e Splits \u003c/b\u003e : Sections of code in b/w a capture, measures the delta from last split.      \n3. \u003cb\u003e LogToken\u003c/b\u003e : Tracks noncontiguous captures.      \n4. \u003cb\u003e Execution Path \u003c/b\u003e : An abstraction to route the execution inside the library . Custom paths can be written for user additional add on functionalities and could be installed on different build versions.    \n5. \u003cb\u003e Thread Locks \u003c/b\u003e : Thread starting the measurement should end it.    \n  \n# Usage  \n  \nSetup in 3 easy steps:  \n  \n1. Install the desired `ExecutionPath` , in the `onCreate` of your application class **as early as  possible**. Prior to that `Snippet` APIs will not have any effect as snippet ships with the  default execution path that is no-op path. For usual purposes use `MeasuredExecutionPath` \n2. Set   the filter that you would like to use in the log cat using `newFilter` method, default filter  is \"**Snippet**\"  \n3. Set the flags that determine the amount of verbose in the logs using `addFlag` method. The flags  that Snippet supports are, `FLAG_METADATA_CLASS`, `FLAG_METADATA_METHOD`, `FLAG_METADATA_LINE`   , `FLAG_METADATA_THREAD_INFO`. Some of the filters are added by default.  \n  \n**Below is the sample setup code:**  \n  \n\n    if(BuildConfig.DEBUG) { \n\t\tSnippet.install(new Snippet.MeasuredExecutionPath());      \n        Snippet.newFilter(\"SampleFilter\");      \n        Snippet.addFlag(Snippet.FLAG_METADATA_LINE | Snippet.FLAG_METADATA_THREAD_INFO);      \n    }   \n\n## How to measure the a piece of code.  \n  \n\u003e There are 3 ways to capture the code path.  \n  \n1. `Snippet.capture(Closure closure)` - For continuous section of code, pass code as lambda inside  the closure.  \n\n2. `Snippet.startCapture()/LogToken.endCapture()` - For non contiguous sections of code possibly inside the same file . Or places where `capture(closure)` does not work as in case of some anonymous inner classes.  \n\n4. `Snippet.startCapture(String tag)/Snippet.find(tag).endCapture()` - For code flows spanning over  \n   multiple files. ex. you have started a measurement in `Application.onCreate()` and end the  \n   measurement on the landing activity. Use `Snippet.startCapture(tag)` to start the measurement and  \n   find the token using `Snippet.find(tag)` and call `endCapture()` on that.  \n  \n**Use case 1:** Code that can be passed as lambda.\n  \n\n    @Override  \n    protected void onCreate(@Nullable Bundle savedInstanceState) {  \n        // The capture API can be used to measure the code that can be passed as a lambda.  \n        // Adding this lambda captures the class, line, thread etc automatically into the logcat.\n        // This cannot be use for code that returns a value as the lambda declared for closure is \n        // is a non returning lambda. For the case that could return a value and are a little complex // use the log-token based API demonstrated below.  \n        \n        // Captures the code as a lambda.  \n        Snippet.capture(()-\u003e super.onCreate(savedInstanceState)); \n    }\n\n**Use case 2:** Measurement starts from a different class and ends in a  \ndifferent class. \nBelow the measurement has started in Application class and will end in an Activity class. We use TAG based API to handle this case.  \n  \n\n      public class SampleApplication extends Application {  \n        @Override  \n         public void onCreate() {  \n            super.onCreate();  \n            if(BuildConfig.DEBUG) {  \n                Snippet.install(new Snippet.MeasuredExecutionPath());  \n                Snippet.newFilter(\"SampleFilter\");  \n                Snippet.addFlag(Snippet.FLAG_METADATA_LINE | Snippet.FLAG_METADATA_THREAD_INFO);  \n            }  \n\n           Snippet.startCapture(\"app_start\"); // Start the measurement in Application class  \n      }  \n    }\n        \nand ended in `MainActivity` class\n\n    public class MainActivity extends AppCompatActivity {  \n\n      @Override  \n      protected void onCreate(@Nullable Bundle savedInstanceState) {  \n            super.onCreate(savedInstanceState); \n            // End the measurement that started in the Application class  \n            Snippet.find(\"app_start\").endCapture();\n      }  \n\n      @Override  \n      protected void onStart() {  \n            super.onStart();  \n        }  \n    }\n    \n**Use case 3:** Using Log Tokens. Log tokens can be used within a class or method easily. \n\nIf you need to use log tokens to measure code spread across multiple files and your measurements do not deal with multiple threads, use TAG based API discussed above. \n\n**LogTokens** will shine if you need to fire multiple threads at the same time and measurement is inside the common code that all the threads execute. Then create separate log token and hand over to different threads. We are working to fix this limitation.\n\n     public class MainActivity extends AppCompatActivity {        \n            \n            @Override    \n          protected void onCreate(@Nullable Bundle savedInstanceState) {    \n                // The capture API can be used to measure the code that can be passed as a lambda.    \n                // Calling startCapture gives a log token to the caller that can we used to end the measurement.   \n                // The moment start capture is called, the measurement has started and when end capture will  \n                // be called on the log-token, that is when the measurement will end. Endcapture can be called only once \n                // per log token.      \n                     \n                ILogToken token = Snippet.startCapture();    \n                setContentView(R.layout.activity_main);    \n                token.endCapture(\"Time to set the content view\");    \n          }  \n      \n\nSnippet capture all the context information such as class, method, thread, line number out of the box and creates pretty logs as shown below. So that you can locate your logs easily. \nOn top of that if you need more verbosity, then each API has a string based overload too. You can explore that also.\n \n    2021-12-11 15:11:24.197 11400-11400/com.microsoft.sample D/SampleFilter: [Class = MainActivity]|::::|[Method = onCreate]|::::|\u003cLine no. 21\u003e|::::|[Thread name = main]|::::||::::|(18 ms) \n    2021-12-11 15:11:24.376 11400-11400/com.microsoft.sample D/SampleFilter: Time to set the content view|::::|[Class = MainActivity]|::::|[Method = onCreate]|::::|\u003cLine no. 29\u003e|::::|[Thread name = main]|::::||::::|(178 ms) \n    2021-12-11 15:11:24.377 11400-11400/com.microsoft.sample D/SampleFilter: [Class = MainActivity]|::::|[Method = onCreate]|::::|\u003cLine no. 32\u003e|::::|[Thread name = main]|::::||::::|(295 ms)  \n\nWe can create multiple execution path implementations by extending MeasuredExecutionPath classes,  and do customised work with the data that is provided by the measured path such as logging it in remote servers, putting all the data to a DB, files etc. Check `FileExecutionPath` in the sample  app.  \n  \nCheck out the sample app in `app/` to see the process in action.  \n  \n## Splits  \n  \n**Splits** can be defined as a logical span of code within a capture. Splits can span within same file or different files. The purpose is to double down on the focussed areas, and help in debugging.  \nIt could be used for seeing what is the contribution of a small portion of code within a capture and  find of problem areas.    \n\n**It is advisable to always use splits only for debugging purposes.** **It is not advised to ship splits related code to production.** **Below is the demo on how to use splits**  \n  \n1. Once you get a log token using `Snippet.startCapture()` call.  \n2. You can call `logtoken.addSplit()` call. It will print the amount of time that has passed since  \n   the last call to `addSplit()` was made or if it is a first split then it will measure the time  \n   from the call to `startCapture().`  \n3. There is no limit on the number of splits that can be created, once the `endCapture()` is called,  \n   snippet prints a **\"Split Summary\"** that shows what was the percentage of time each split take  \n   with respect to the capture.  \n  \n*Click on the below like to check out how the split summary looks like.* [How split summary looks like?](https://ibb.co/t8qp7Tk)  \n  \n## ThreadLocks  \n  \nThread lock is the functionality where the thread that started the measurement  \nusing `startCapture()` could only end it. If other thread tries to do that, an error is logged and action is skipped. It can be enabled easily like this  \n  \nSnippet.startCapture().enableThreadLock()  \n  \n## ExecutionPaths  \n  \n* Execution path determines how core the functionality of this library should behave.  \n* It might be possible that we do not want to execute the code entirely in release builds or  \n* may want to add some extra information into the existing information and add it to files.  \n* We can plugin a custom execution path or method through `Snippet.install(executionPath)` method.  \n  \nSnippet comes with a `MeasuredExecutionPath` \u0026 `ReleaseExecutionPath` , measured path is the one that routes the the Snippet API calls to the core library functionality, and `ReleaseExecutionPath` make Snippet no-op. Release path is the default path for Snippet. User has to set the path for specific build types using `Snippet.install(executionPath)`  . Below is an example, where we set the `MeasuredExecutionPath` on DEBUG builds and `FileExecutionPath` on RELEASE builds.\n  \n\n    if(BuildConfig.DEBUG) {\n         Snippet.install(new Snippet.MeasuredExecutionPath());    \n         Snippet.newFilter(\"SampleFilter\");      \n    } else {      \n         Snippet.install(new FileExecutionPath()); \n         Snippet.newFilter(\"ReleaseFilter\");      \n    }      \n    Snippet.addFlag(Snippet.FLAG_METADATA_LINE | Snippet.FLAG_METADATA_THREAD_INFO);  \n\n  \n## Writing a custom execution path  \n  \n1. Extend `ExecutionPath`, in our example we will extend `MeasuredExecutionPath`.  \n\n2. Override `ExecutionPath#capture(Closure)` and  `ExecutionPath#capture(String, Closure)` This will make sure that you are implementing a custom code for lambda based API.  \n4. You need to provide a custom log token also and override the `LogToken#endCapture()`  and `LogToken#endCapture(String)` so that you can perform the custom actions on all types(contiguous/non contiguous) of code.  \n5. For doing this extend ExtendableLogToken and override  `ExtendableLogToken#endCapture(String)`,  \n   and  `ExtendableLogToken#endCapture()`. Once done, return the `ExtendableLogToken` instance from  `Snippet#startCapture(String)`, and  `Snippet#startCapture(String)` methods.  \n  \n**NOTE**: In almost all the cases, every new execution path that would be created, would require a  new extension of `ExtendableLogToken`  \n  \n**Sample:**  \n  \n          \n    /**      \n     * Demo for showing custom implementation of Execution Path. This path, takes the data that is * captured from the MeasuredExecutionPath and passes it to     FileExecutionPath and then the data * is written to the file. * \u003cp\u003e      \n     * We need to override LogToken also, as for the code that is non contiguous all the measurements * are inside the log token that is handed over to the user by Snippet.startCapture() API * So if we want that our new execution to work for both kinds of APIs that ie. * The one passed through a lambda in Snippet.capture(lambda) and Snippet.startCapture()/LogToken.endCapture()\n     * We need to override both the classes. */  \n     \n    public class FileExecutionPath extends Snippet.MeasuredExecutionPath {      \n          \n      @Override      \n      public ILogToken startCapture(String tag) {      \n            return super.startCapture(tag);      \n        }      \n          \n     @NonNull      \n     @Override  \n     public ExecutionContext capture(String message, Snippet.Closure closure) {      \n     \tExecutionContext context = super.capture(message, closure);      \n        Log.d(\"Snippet\", \"Class: \" + context.getClassName() + \"Duration: \" + context.getExecutionDuration());      \n        // Context has all the information that measured path has captured. Use that to write to files.      \n        return writeToFile(context);      \n     }      \n          \n     private ExecutionContext writeToFile(ExecutionContext context) {      \n        // Code to write to a file goes here, create a thread and write.      \n        // Finally return a the execution context(could be the same or a new implementation) with some // of the details that you captured.      \n        // NOTE: always put the relevant information on the context before you start doing IO // so that the execution path could return successfully.  \n\treturn context;      \n     }      \n          \n     @NonNull      \n     @Override  public ExecutionContext capture(Snippet.Closure closure) {      \n            return super.capture(closure);      \n     }      \n          \n     // We need to return a log token implementation that writes to a file when we call endCapture()      \n     // APIs. \n     // USE ExtendableLogToken for the above purpose  @Override      \n     public ILogToken startCapture() {      \n           return new ExtendableLogToken(super.startCapture());      \n     }      \n          \n     @Override      \n     public ILogToken find(String tag) {      \n          return super.find(tag);      \n     }      \n          \n     public class FileWritingLogToken extends ExtendableLogToken {      \n          \n         public FileWritingLogToken(ILogToken logToken) {      \n             super(logToken);      \n         }      \n          \n         @Override      \n         public ExecutionContext endCapture(String message) {      \n            ExecutionContext context = super.endCapture(message);      \n            writeToFile(context);      \n            return context;      \n         }      \n          \n         @Override      \n         public ExecutionContext endCapture() {      \n            ExecutionContext context = super.endCapture();      \n            writeToFile(context);      \n            return context;      \n          }      \n       }    \n    }     \n\n\n\nFinally, install it at the application create,  \n  \n```    \nif(Build.DEBUG) { \n    Snippet.install(new FileExecutionContext());\n}    \n````\n\nCheers,  \n  \n## Contributing  \n  \nThis project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.  \n  \nWhen you submit a pull request, a CLA bot will automatically determine whether you need to  provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.  \n  \nThis project has adopted  \nthe [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see  \nthe [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or      contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.  \n  \n## Trademarks  \n  \nThis project may contain trademarks or logos for projects, products, or services. Authorized use of  \nMicrosoft trademarks or logos is subject to and must follow \n[Microsoft's Trademark \u0026 Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).      \nUse of Microsoft trademarks or logos in modified versions of this project must not cause confusion  \nor imply Microsoft sponsorship.      \nAny use of third-party trademarks or logos are subject to those third-party's policies.\n\n\n## Downloads\nAdd it in your root build.gradle at the end of repositories:\n```css\nallprojects {\n\trepositories {\n\t      ...\n\t      maven { url 'https://jitpack.io' }\n\t}\n}\n```\nAdd the dependency to your project and replace tag with the release tag in the git repository\n```css\ndependencies {\n    implementation 'com.github.microsoft:snippet-timekeeper:Tag'\n}\n```\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmicrosoft%2Fsnippet-timekeeper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmicrosoft%2Fsnippet-timekeeper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmicrosoft%2Fsnippet-timekeeper/lists"}