prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Capture details of Celery Chains and Chords
  Celery has some more advanced features to join multiple jobs into one. The agent needs testing and investigation into how they can be best instrumented.
  
  </issue>
  <code>
  [start of src/scout_apm/celery.py]
  1 # coding=utf-8
  2 from __future__ import absolute_import, division, print_function, unicode_literals
  3 
  4 import datetime as dt
  5 
  6 from celery.signals import before_task_publish, task_postrun, task_prerun
  7 
  8 import scout_apm.core
  9 from scout_apm.compat import datetime_to_timestamp
  10 from scout_apm.core.tracked_request import TrackedRequest
  11 
  12 
  13 def before_publish_callback(headers=None, properties=None, **kwargs):
  14     if "scout_task_start" not in headers:
  15         headers["scout_task_start"] = datetime_to_timestamp(dt.datetime.utcnow())
  16 
  17 
  18 def prerun_callback(task=None, **kwargs):
  19     tracked_request = TrackedRequest.instance()
  20     tracked_request.mark_real_request()
  21 
  22     start = getattr(task.request, "scout_task_start", None)
  23     if start is not None:
  24         now = datetime_to_timestamp(dt.datetime.utcnow())
  25         try:
  26             queue_time = now - start
  27         except TypeError:
  28             pass
  29         else:
  30             tracked_request.tag("queue_time", queue_time)
  31 
  32     delivery_info = task.request.delivery_info
  33     tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
  34     tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
  35     tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
  36     tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
  37 
  38     tracked_request.start_span(operation=("Job/" + task.name))
  39 
  40 
  41 def postrun_callback(task=None, **kwargs):
  42     tracked_request = TrackedRequest.instance()
  43     tracked_request.stop_span()
  44 
  45 
  46 def install():
  47     installed = scout_apm.core.install()
  48     if not installed:
  49         return
  50 
  51     before_task_publish.connect(before_publish_callback)
  52     task_prerun.connect(prerun_callback)
  53     task_postrun.connect(postrun_callback)
  54 
  55 
  56 def uninstall():
  57     before_task_publish.disconnect(before_publish_callback)
  58     task_prerun.disconnect(prerun_callback)
  59     task_postrun.disconnect(postrun_callback)
  60
  [end of src/scout_apm/celery.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/src/scout_apm/celery.py b/src/scout_apm/celery.py
  --- a/src/scout_apm/celery.py
  +++ b/src/scout_apm/celery.py
  @@ -29,6 +29,13 @@
           else:
               tracked_request.tag("queue_time", queue_time)
   
  +    task_id = getattr(task.request, "id", None)
  +    if task_id:
  +        tracked_request.tag("task_id", task_id)
  +    parent_task_id = getattr(task.request, "parent_id", None)
  +    if parent_task_id:
  +        tracked_request.tag("parent_task_id", parent_task_id)
  +
       delivery_info = task.request.delivery_info
       tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
       tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
  
verification_info: {"golden_diff": "diff --git a/src/scout_apm/celery.py b/src/scout_apm/celery.py\n--- a/src/scout_apm/celery.py\n+++ b/src/scout_apm/celery.py\n@@ -29,6 +29,13 @@\n         else:\n             tracked_request.tag(\"queue_time\", queue_time)\n \n+    task_id = getattr(task.request, \"id\", None)\n+    if task_id:\n+        tracked_request.tag(\"task_id\", task_id)\n+    parent_task_id = getattr(task.request, \"parent_id\", None)\n+    if parent_task_id:\n+        tracked_request.tag(\"parent_task_id\", parent_task_id)\n+\n     delivery_info = task.request.delivery_info\n     tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\n     tracked_request.tag(\"exchange\", delivery_info.get(\"exchange\", \"unknown\"))\n", "issue": "Capture details of Celery Chains and Chords\nCelery has some more advanced features to join multiple jobs into one. The agent needs testing and investigation into how they can be best instrumented.\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\n\nfrom celery.signals import before_task_publish, task_postrun, task_prerun\n\nimport scout_apm.core\nfrom scout_apm.compat import datetime_to_timestamp\nfrom scout_apm.core.tracked_request import TrackedRequest\n\n\ndef before_publish_callback(headers=None, properties=None, **kwargs):\n    if \"scout_task_start\" not in headers:\n        headers[\"scout_task_start\"] = datetime_to_timestamp(dt.datetime.utcnow())\n\n\ndef prerun_callback(task=None, **kwargs):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.mark_real_request()\n\n    start = getattr(task.request, \"scout_task_start\", None)\n    if start is not None:\n        now = datetime_to_timestamp(dt.datetime.utcnow())\n        try:\n            queue_time = now - start\n        except TypeError:\n            pass\n        else:\n            tracked_request.tag(\"queue_time\", queue_time)\n\n    delivery_info = task.request.delivery_info\n    tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\n    tracked_request.tag(\"exchange\", delivery_info.get(\"exchange\", \"unknown\"))\n    tracked_request.tag(\"routing_key\", delivery_info.get(\"routing_key\", \"unknown\"))\n    tracked_request.tag(\"queue\", delivery_info.get(\"queue\", \"unknown\"))\n\n    tracked_request.start_span(operation=(\"Job/\" + task.name))\n\n\ndef postrun_callback(task=None, **kwargs):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.stop_span()\n\n\ndef install():\n    installed = scout_apm.core.install()\n    if not installed:\n        return\n\n    before_task_publish.connect(before_publish_callback)\n    task_prerun.connect(prerun_callback)\n    task_postrun.connect(postrun_callback)\n\n\ndef uninstall():\n    before_task_publish.disconnect(before_publish_callback)\n    task_prerun.disconnect(prerun_callback)\n    task_postrun.disconnect(postrun_callback)\n", "path": "src/scout_apm/celery.py"}]}