{"id":22757438,"url":"https://github.com/clemapfel/love_profiler","last_synced_at":"2025-06-18T21:41:25.201Z","repository":{"id":267157791,"uuid":"867394670","full_name":"Clemapfel/love_profiler","owner":"Clemapfel","description":"easy-to-install, easy-to-use, statistical profiler for Love2D and other LuaJIT projects","archived":false,"fork":false,"pushed_at":"2024-10-11T16:41:07.000Z","size":25,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-10T01:34:27.797Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Lua","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/Clemapfel.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2024-10-04T01:36:02.000Z","updated_at":"2025-01-11T19:47:52.000Z","dependencies_parsed_at":"2024-12-08T18:55:00.447Z","dependency_job_id":null,"html_url":"https://github.com/Clemapfel/love_profiler","commit_stats":null,"previous_names":["clemapfel/love_profiler"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Clemapfel/love_profiler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clemapfel%2Flove_profiler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clemapfel%2Flove_profiler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clemapfel%2Flove_profiler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clemapfel%2Flove_profiler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Clemapfel","download_url":"https://codeload.github.com/Clemapfel/love_profiler/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clemapfel%2Flove_profiler/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260639481,"owners_count":23040457,"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-12-11T07:18:23.453Z","updated_at":"2025-06-18T21:41:20.188Z","avatar_url":"https://github.com/Clemapfel.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Clem's Löve Profiler\n\nA profiler is a program that runs in the background of your project, tracking performance data for optimization purposes. Traditional Lua profilers using `debug.sethook` do not work with Löve because it uses LuaJIT, which is not guaranteed to invoke these hooks, and profilers using this method come with great performance overhead. Additionally, the [LuaJIT-provided profiler](https://luajit.org/ext_profiler.html) is incompatible with Löve, as it requires running the profiler executable directly on a lua script, whereas Löve projects use their own executable. \n\nClem's Löve Profiler offers the following advantages over other profilers:\n\n+ Requires only one line to install and three lines to include and use in your project\n+ Has no performance overhead, ensuring your program runs at its usual speed without skewing data\n+ Presents the data in a well-formatted manner\n\n\u003e **NOTE:** This project requires LuaJIT and will not work with regular Lua. \n\n# Installation\n\nIn a git-managed project, run:\n\n```bash\ngit clone https://github.com/clemapfel/love_profiler\n```\n\nAlternatively, download the project manually from [this link](https://github.com/Clemapfel/love_profiler/archive/refs/heads/main.zip), and add it to your project using your operating system's file explorer. Using git is the recommended method to install this library. \n\nEnsure the `love_profiler` folder is in the root of your game directory. Then, in Lua, add:\n\n```lua\nprofiler = require \"love_profiler.profiler\"\n```\n\n# Usage\n\nA statistical profiler samples the program at regular intervals rather than noting every function call. These intervals are frequent enough that it's statistically unlikely to miss anything significant, but it's not impossible. To improve accuracy, the profiler should run for at least a few seconds; 5 seconds or more is recommended. If profiling a short piece of code, loop through it often enough to spend at least 5 seconds executing it.\n\nThe profiler uses a \"zone stack\" to track active zones. Use `profiler.push(\"zone_name\")` to add a zone to the stack and `profiler.pop()` to remove the last pushed zone. After sufficient time has passed, use `print(profiler.report())` to get a nicely formatted report.\n\nFor example, if your game has been experiencing FPS drops and you want to identify the problematic part of `love.draw`, you can do:\n\n```lua\n-- in main.lua\nprofiler = require \"love_profiler.profiler\"\n\nlove.draw = function()\n    profiler.push(\"draw\") -- start \"draw\" zone\n    -- (...) your draw code here\n    profiler.pop() -- end \"draw\" zone\nend\n\nlove.keypressed = function(which)\n    if which == \"space\" then\n        print(profiler.report()) -- print results to console at any time \n    end\nend\n```\n\nHere, the \"draw\" zone is opened and closed during each frame, so the profiler only collects data from within this zone. A callback allows you to press the space bar at any time to trigger reporting. \n\nLet the game run for about 30 seconds; the longer it runs, the more accurate the results. Press space to get the report printed to the console:\n\n```\n | Zone `draw` (1752 samples | 466 samples/s)\n | Ran for 3.759228s on `Fri Oct  4 02:10:11 2024`\n | GC  : 0 % (0)\n | JIT : 0.17 % (3)\n |\n | Percentage (%) | # Samples | Name                                                      |\n |----------------|-----------|-----------------------------------------------------------|\n | 77.05          | 1350      | xpcall @ [builtin#21]                                     |\n | 38.52          | 675       | [love \"boot.lua\"]:423 @ [love \"boot.lua\"]:456             |\n | 38.52          | 675       | draw @ main.lua:86                                        |\n | 38.52          | 675       | _run @ common/game_state.lua:305                          |\n | 38.52          | 675       | main.lua:96 @ main.lua:97                                 |\n | 38.52          | 675       | [love \"boot.lua\"]:434 @ [love \"boot.lua\"]:447             |\n | 37.95          | 665       | _draw @ common/game_state.lua:465                         |\n | 14.09          | 247       | draw @ menu/inventory_scene.lua:380                       |\n | 12.5           | 219       | draw @ menu/tab_bar.lua:190                               |\n | 12.1           | 212       | draw @ common/shape_rectangle.lua:29                      |\n | 9.98           | 175       | draw @ menu/inventory_scene.lua:382                       |\n | 8.44           | 148       | draw @ common/label.lua:64                                |\n | 7.7            | 135       | draw @ common/glyph.lua:272                               |\n | 7.7            | 135       | _draw_glyph @ common/glyph.lua:237                        |\n | 6.73           | 118       | draw @ common/vertex_shape.lua:199                        |\n | 6.5            | 114       | draw @ common/sprite.lua:65                               |\n | 4.9            | 86        | draw @ menu/inventory_scene.lua:368                       |\n | 4.62           | 81        | _bind_stencil @ common/frame.lua:65                       |\n (...)\n | 0.22           | 4         | draw @ common/spacer.lua:29                               |\n | 0.22           | 4         | draw @ menu/tab_bar.lua:194                               |\n | 0.22           | 4         | draw @ common/frame.lua:85                                |\n | 0.17           | 3         | draw @ common/scrollbar.lua:79                            |\n | 0.17           | 3         | draw @ menu/inventory_scene.lua:379                       |\n | 0.11           | 2         | draw @ common/shape_rectangle.lua:16                      |\n | 0.11           | 2         | bind @ common/shader.lua:31                               |\n | 0.11           | 2         | draw @ common/glyph.lua:268                               |\n | 0.11           | 2         | draw @ common/frame.lua:77                                |\n | 0.11           | 2         | stencil @ common/graphics.lua:181                         |\n | 0.11           | 2         | send @ common/shader.lua:20                               |\n | 0.11           | 2         | draw @ common/control_indicator.lua:200                   |\n | 0.11           | 2         | draw @ common/sprite.lua:67                               |\n | \u003c 0.1          | 16        | ...    \n```\n\nThe (...) indicates omitted rows for brevity in this `README` only. The actual output may be longer.\n\nInterpreting these results, first look at the header:\n\n```\n| Zone `draw` (1754 samples \n| 466 samples/s) \n| Ran for 3.764198s on `Fri Oct 4 00:10:11 2024` \n| GC : 0 % (0) \n| JIT : 0.17 % (3) \n|\n```\n\nWe are in zone draw, and over the approximately 30 seconds our game ran, 3.76 seconds were spent in this zone. We collected 1754 samples, indicating how many times the profiler collected data. `GC` shows the percentage of samples spent on garbage collection; since we are in draw, a `GC` of 0% is expected. `JIT` shows the time spent on JIT (just-in-time) compilation, which is 0.17%, or 3 out of 1754 samples.\n\nNext, examine the list of function names. The first column shows the percentage of samples collected, the second column is the absolute number of samples, and the last column is the function name, formatted as `\u003cfunction_name\u003e @ \u003cfile_name\u003e:\u003cline_number\u003e`.\n\n```\n | Percentage (%) | # Samples | Name                                                      |\n |----------------|-----------|-----------------------------------------------------------|\n | 77.05          | 1350      | xpcall @ [builtin#21]                                     |\n | 38.52          | 675       | [love \"boot.lua\"]:423 @ [love \"boot.lua\"]:456             |\n | 38.52          | 675       | draw @ main.lua:86                                        |\n | 38.52          | 675       | _run @ common/game_state.lua:305                          |\n | 38.52          | 675       | main.lua:96 @ main.lua:97                                 |\n | 38.52          | 675       | [love \"boot.lua\"]:434 @ [love \"boot.lua\"]:447             |\n | 37.95          | 665       | _draw @ common/game_state.lua:465                         |\n | 14.09          | 247       | draw @ menu/inventory_scene.lua:380                       |\n | 12.5           | 219       | draw @ menu/tab_bar.lua:190                               |\n | 12.1           | 212       | draw @ common/shape_rectangle.lua:29                      |\n | 9.98           | 175       | draw @ menu/inventory_scene.lua:382                       |\n | 8.44           | 148       | draw @ common/label.lua:64                                |\n```\n\nMost of the time was spent in `xpcall`, love's `boot.lua`, and `love.draw`. These results are expected, as our zone was inside those functions. In general, the first few lines often don't provide any useful information, as they're usually not our functions and we have no control over them. The first interesting lines are:\n\n```\n | 37.95          | 665       | _draw @ common/game_state.lua:465                         |\n | 14.09          | 247       | draw @ menu/inventory_scene.lua:380                       |\n | 12.5           | 219       | draw @ menu/tab_bar.lua:190                               |\n | 12.1           | 212       | draw @ common/shape_rectangle.lua:29                      |\n```\n\nHere, 38% of the draw time was spent in the `_draw` function of our game state. Within those 38%, the `draw` methods of various objects are included. For example, 12% of the overall time was spent on drawing `tab_bar`, which could indicate a performance issue or that `tab_bar` is a particularly common or complex object. These results can guide performance optimization, or you may want to examine less common functions further down the list.\n\nNote that these results do not indicate that `tab_bar:draw` was called 219 times. Instead, during the statistical process of periodically checking which function we are currently in, 219 times, we happened to be in `tab_bar:draw`. This allows us to deduce with reasonable accuracy that about `219 / 1754 = 12.5%` of runtime was spent in that function. The more total samples collected, the higher the likelihood of accurate results.\n\nAt the bottom of the table, we have:\n\n```\n| 0.11           | 2         | draw @ common/control_indicator.lua:200                   |\n| 0.11           | 2         | draw @ common/sprite.lua:67                               |\n| \u003c 0.1          | 16        | ...    \n```\n\nAny function taking less than 0.1% of collected samples is abbreviated, and the total sum of samples of these functions is printed in the last column. We see that 16 samples were taken up by these negligible functions.\n\nLastly, eagle-eyed readers may notice that the total sample count and percentage of all the rows do not add up to 1754, or 100%, respectively. This is because function calls are **nested**. For example, if function `A` calls function `B`, which calls function `C`, like this:\n\n```lua\nfunction C()\n    -- our sample is here\nend\n\nfunction B() \n    C() \nend\n\nfunction A() \n    B() \nend\n\nprofiler.push()\nA()\nprofiler.pop()\n```\n\nThen if the profiler indicates the current sample is in `C`, we know that the sample is also necessarily in `B` and `A`. Thus, `A`, `B`, and `C` will each get a sample, adding to their percentage and sample count. In our example, this is seen with `xpcall`, which calls `boot.lua`, which calls `love.draw`, which then calls our functions. This is why those three functions had the highest percentage, as most functions were nested calls of them.\n\n# Credits\n\nThis library was written and designed by [Clem Cords](clemens-cords.com). Consider donating via GitHub Sponsor to reward past work and help with the continued development of this and other Löve-related projects. If you need assistance or want to hire me for freelance work (on your game or other projects), feel free to reach out via email or Discord, username `clemapfel`. Thank you for your consideration.\n\n# License\n\nThis library is licensed under the MIT License, meaning it is available for free without restrictions in both commercial and non-commercial projects.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclemapfel%2Flove_profiler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclemapfel%2Flove_profiler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclemapfel%2Flove_profiler/lists"}