github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/examples/python/rideshare/README.md (about)

     1  ## Continuous Profiling for Python applications
     2  
     3  ### Profiling a Python Rideshare App with Pyroscope
     4  
     5  ![python_example_architecture_new_00](https://user-images.githubusercontent.com/23323466/173369382-267af200-6126-4bd0-8607-a933e8400dbb.gif)
     6  
     7  #### _Read this in other languages._
     8  
     9  <kbd>[简体中文](README_zh.md)</kbd>
    10  
    11  Note: For documentation on the Pyroscope pip package visit [our website](https://pyroscope.io/docs/python/)
    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.py`:
    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 (via [docker-compose.yml](https://github.com/pyroscope-io/pyroscope/blob/main/examples/python/docker-compose.yml))
    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  ```python
    37  pyroscope.configure(
    38      application_name       = "ride-sharing-app",
    39      server_address         = "http://pyroscope:4040",
    40      tags                   = {
    41          "region":   f'{os.getenv("REGION")}', # Tags the region based off the environment variable
    42      }
    43  )
    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 `with pyroscope.tag_wrapper()` block
    49  
    50  ```python
    51  def find_nearest_vehicle(n, vehicle):
    52      with pyroscope.tag_wrapper({ "vehicle": vehicle}):
    53          i = 0
    54          start_time = time.time()
    55          while time.time() - start_time < n:
    56              i += 1
    57  ```
    58  
    59  What this block does, is:
    60  
    61  1. Add the tag `{ "vehicle" => "car" }`
    62  2. execute the `find_nearest_vehicle()` function
    63  3. Before the block ends it will (behind the scenes) remove the `{ "vehicle" => "car" }` from the application since that block is complete
    64  
    65  ## Resulting flamegraph / performance results from the example
    66  
    67  ### Running the example
    68  
    69  To run the example run the following commands:
    70  
    71  ```shell
    72  # Pull latest pyroscope image:
    73  docker pull pyroscope/pyroscope: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  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.cpu` from the dropdown, you should see a flamegraph that looks like this (below). After we give 20-30 seconds for the flamegraph to update and then click the refresh button we see our 3 functions at the bottom of the flamegraph taking CPU resources _proportional to the size_ of their respective `search_radius` parameters.
    83  
    84  ## Where's the performance bottleneck?
    85  
    86  Profiling is most effective for applications that contain tags. The first step when analyzing performance from your application, is to use the Tag Explorer page in order to determine if any tags are consuming more resources than others.
    87  
    88  ![vehicle_tag_breakdown](https://user-images.githubusercontent.com/23323466/191306637-a601f463-a247-4588-a285-639424a08b87.png)
    89  
    90  ![image](https://user-images.githubusercontent.com/23323466/191319887-8fff2605-dc74-48ba-b0b7-918e3c95ed91.png)
    91  
    92  The benefit of using Pyroscope, is that by tagging both `region` and `vehicle` and looking at the Tag Explorer page we can hypothesize:
    93  
    94  - Something is wrong with the `/car` endpoint code where `car` vehicle tag is consuming **68% of CPU**
    95  - Something is wrong with one of our regions where `eu-north` region tag is consuming **54% of CPU**
    96  
    97  From the flamegraph we can see that for the `eu-north` tag the biggest performance impact comes from the `find_nearest_vehicle()` function which consumes close to **68% of cpu**. To analyze this we can go directly to the comparison page using the comparison dropdown.
    98  
    99  ## Comparing two time periods
   100  
   101  Using Pyroscope's "comparison view" we can actually select two different queries and compare the resulting flamegraphs:
   102  - Left flamegraph: `{ region != "eu-north", ... }`
   103  - Right flamegraph: `{ region = "eu-north", ... }`
   104  
   105  When we select a period of low-cpu utilization and a period of high-cpu utilization we can see that there is clearly different behavior in the `find_nearest_vehicle()` function where it takes:
   106  - Left flamegraph: **22% of CPU** when `{ region != "eu-north", ... }`
   107  - right flamgraph: **82% of CPU** when `{ region = "eu-north", ... }`
   108  
   109  ![python_pop_out_library_comparison_00](https://user-images.githubusercontent.com/23323466/191374975-d374db02-4cb1-48d5-bc1a-6194193a9f09.png)
   110  
   111  ## Visualizing Diff Between Two Flamegraphs
   112  
   113  While the difference _in this case_ is stark enough to see in the comparison view, sometimes the diff between the two flamegraphs 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 flamegraph.
   114  ![find_nearest_vehicle_diff](https://user-images.githubusercontent.com/23323466/191320888-b49eb7de-06d5-4e6b-b9ac-198d7c9e2fcf.png)
   115  
   116  
   117  ### More use cases
   118  
   119  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:
   120  - Linking profiles with trace data
   121  - Tagging controllers
   122  - Tagging regions
   123  - Tagging jobs from a redis / sidekiq / rabbitmq queue
   124  - Tagging commits
   125  - Tagging staging / production environments
   126  - Tagging different parts of their testing suites
   127  - Etc...
   128  
   129  ### Live Demo
   130  
   131  Feel free to check out the [live demo](https://demo.pyroscope.io/explore?query=rideshare-app-python.cpu%7B%7D&groupBy=region&groupByValue=All) of this example on our demo page.
   132  
   133  ### Future Roadmap
   134  
   135  We would love for you to try out this example and see what ways you can adapt this to your python application. Continuous profiling has become an increasingly popular tool for the monitoring and debugging of performance issues (arguably the fourth pillar of observability).
   136  
   137  We'd love to continue to improve this pip package by adding things like integrations with popular tools, memory profiling, etc. and we would love to hear what features _you would like to see_.