github.com/grafana/pyroscope@v1.18.0/examples/language-sdk-instrumentation/ruby/README.md (about) 1 ## Continuous Profiling for Ruby applications 2 3 ### Profiling a Ruby Rideshare App with Pyroscope 4 5  6 7 Note: For documentation on the Pyroscope ruby gem visit [our website](https://grafana.com/docs/pyroscope/latest/configure-client/language-sdks/ruby/). 8 9 ## Live Demo 10 11 Feel free to check out the [live demo](https://play.grafana.org/a/grafana-pyroscope-app/profiles-explorer?searchText=&panelType=time-series&layout=grid&hideNoData=off&explorationType=flame-graph&var-serviceName=pyroscope-rideshare-ruby&var-profileMetricId=process_cpu:cpu:nanoseconds:cpu:nanoseconds&var-dataSource=grafanacloud-profiles) of this example on our demo page. 12 13 ## Background 14 15 In this example we show a simplified, basic use case of Pyroscope. We simulate a "ride share" company which has three endpoints found in `server.rb`: 16 17 - `/bike` : calls the `order_bike(search_radius)` function to order a bike 18 - `/car` : calls the `order_car(search_radius)` function to order a car 19 - `/scooter` : calls the `order_scooter(search_radius)` function to order a scooter 20 21 We also simulate running 3 distinct servers in 3 different regions: 22 23 - us-east 24 - eu-north 25 - ap-south 26 27 One of the most useful capabilities of Pyroscope is the ability to tag your data in a way that is meaningful to you. In this case, we have two natural divisions, and so we "tag" our data to represent those: 28 29 - `region`: statically tags the region of the server running the code 30 - `vehicle`: dynamically tags the endpoint (similar to how one might tag a controller rails) 31 32 ## Tagging static region 33 34 Tagging something static, like the `region`, can be done in the initialization code in the `config.tags` variable: 35 36 ```ruby 37 Pyroscope.configure do |config| 38 config.app_name = "ride-sharing-app" 39 config.server_address = "http://pyroscope:4040" 40 config.tags = { 41 "region": ENV["REGION"], # Tags the region based of the environment variable 42 } 43 end 44 ``` 45 46 ## Tagging dynamically within functions 47 48 Tagging something more dynamically, like we do for the `vehicle` tag can be done inside our utility `find_nearest_vehicle()` function using a `Pyroscope.tag_wrapper` block 49 50 ```ruby 51 def find_nearest_vehicle(n, vehicle) 52 Pyroscope.tag_wrapper({ "vehicle" => vehicle }) do 53 ...code to find nearest vehicle 54 end 55 end 56 ``` 57 58 What this block does, is: 59 60 1. Add the tag `{ "vehicle" => "car" }` 61 2. execute the `find_nearest_vehicle()` function 62 3. Before the block ends it will (behind the scenes) remove the `{ "vehicle" => "car" }` from the application since that block is complete 63 64 ## Resulting flame graph / performance results from the example 65 66 ### Running the example 67 68 To run the example run the following commands in the `rideshare` directory: 69 70 ```shell 71 # Pull latest pyroscope and grafana images: 72 docker pull grafana/pyroscope:latest 73 docker pull grafana/grafana:latest 74 75 # Run the example project: 76 docker compose up --build 77 78 # Reset the database (if needed): 79 docker compose down 80 ``` 81 82 The Rails version of the example is available in the `raidshare_rails` directory. 83 84 What this example will do is run all the code mentioned above and also send some mock-load to the 3 servers as well as their respective 3 endpoints. If you select our application: `ride-sharing-app` from the dropdown, you should see a flame graph that looks like this. After we give 20-30 seconds for the flame graph to update and then click the refresh button we see our 3 functions at the bottom of the flame graph taking CPU resources _proportional to the size_ of their respective `search_radius` parameters. 85 86 ## Where's the performance bottleneck? 87 88  89 90 The first step when analyzing a profile outputted from your application, is to take note of the _largest node_ which is where your application is spending the most resources. In this case, it happens to be the `order_car` function. 91 92 The benefit of using the Pyroscope package, is that now that we can investigate further as to _why_ the `order_car()` function is problematic. Tagging both `region` and `vehicle` allows us to test two good hypotheses: 93 94 - Something is wrong with the `/car` endpoint code 95 - Something is wrong with one of our regions 96 97 To analyze this we can select one or more tags from the "Select Tag" dropdown: 98 99  100 101 ## Narrowing in on the Issue Using Tags 102 103 Knowing there is an issue with the `order_car()` function we automatically select that tag. Then, after inspecting multiple `region` tags, it becomes clear by looking at the timeline that there is an issue with the `eu-north` region, where it alternates between high-cpu times and low-cpu times. 104 105 We can also see that the `mutex_lock()` function is consuming almost 70% of CPU resources during this time period. 106 107  108 109 ## Visualizing diff between two flame graphs 110 111 While the difference _in this case_ is stark enough to see in the comparison view, sometimes the diff between the two flame graphs is better visualized with them overlayed over each other. Without changing any parameters, we can simply select the diff view tab and see the difference represented in a color-coded diff flame graph. 112 113  114 115 ### More use cases 116 117 We have been beta testing this feature with several different companies and some of the ways that we've seen companies tag their performance data: 118 - Linking profiles with trace data 119 - Tagging controllers 120 - Tagging regions 121 - Tagging jobs from a redis or sidekiq queue 122 - Tagging commits 123 - Tagging staging / production environments 124 - Tagging different parts of their testing suites 125 - Etc... 126 127 ### Future Roadmap 128 129 We would love for you to try out this example and see what ways you can adapt this to your ruby application. Continuous profiling has become an increasingly popular tool for the monitoring and debugging of performance issues (arguably the fourth pillar of observability). 130 131 We'd love to continue to improve this gem by adding things like integrations with popular tools, memory profiling, etc. and we would love to hear what features _you would like to see_.