We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Total span limit
For *really* long running background jobs it'd be possible to have a tracked request with so many spans it causes memory problems. Following #471 we should probably have a (very high) total cap on the number of captured spans.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/tracked_request.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import datetime as dt
5 import logging
6 from uuid import uuid4
7 
8 from scout_apm.core import backtrace, objtrace
9 from scout_apm.core.commands import BatchCommand
10 from scout_apm.core.n_plus_one_tracker import NPlusOneTracker
11 from scout_apm.core.samplers.memory import get_rss_in_mb
12 from scout_apm.core.samplers.thread import SamplersThread
13 from scout_apm.core.socket import CoreAgentSocketThread
14 
15 logger = logging.getLogger(__name__)
16 
17 
18 class TrackedRequest(object):
19     """
20     This is a container which keeps track of all module instances for a single
21     request. For convenience they are made available as attributes based on
22     their keyname
23     """
24 
25     __slots__ = (
26         "request_id",
27         "start_time",
28         "end_time",
29         "active_spans",
30         "complete_spans",
31         "tags",
32         "is_real_request",
33         "_memory_start",
34         "n_plus_one_tracker",
35     )
36 
37     @classmethod
38     def instance(cls):
39         from scout_apm.core.context import context
40 
41         return context.get_tracked_request()
42 
43     def __init__(self):
44         self.request_id = "req-" + str(uuid4())
45         self.start_time = dt.datetime.utcnow()
46         self.end_time = None
47         self.active_spans = []
48         self.complete_spans = []
49         self.tags = {}
50         self.is_real_request = False
51         self._memory_start = get_rss_in_mb()
52         self.n_plus_one_tracker = NPlusOneTracker()
53         logger.debug("Starting request: %s", self.request_id)
54 
55     def __repr__(self):
56         # Incomplete to avoid TMI
57         return "<TrackedRequest(request_id={}, tags={})>".format(
58             repr(self.request_id), repr(self.tags)
59         )
60 
61     def tag(self, key, value):
62         if key in self.tags:
63             logger.debug(
64                 "Overwriting previously set tag for request %s: %s",
65                 self.request_id,
66                 key,
67             )
68         self.tags[key] = value
69 
70     def start_span(
71         self,
72         operation,
73         ignore=False,
74         ignore_children=False,
75         should_capture_backtrace=True,
76     ):
77         parent = self.current_span()
78         if parent is not None:
79             parent_id = parent.span_id
80             if parent.ignore_children:
81                 ignore = True
82                 ignore_children = True
83         else:
84             parent_id = None
85 
86         new_span = Span(
87             request_id=self.request_id,
88             operation=operation,
89             ignore=ignore,
90             ignore_children=ignore_children,
91             parent=parent_id,
92             should_capture_backtrace=should_capture_backtrace,
93         )
94         self.active_spans.append(new_span)
95         return new_span
96 
97     def stop_span(self):
98         try:
99             stopping_span = self.active_spans.pop()
100         except IndexError as exc:
101             logger.debug("Exception when stopping span", exc_info=exc)
102         else:
103             stopping_span.stop()
104             if not stopping_span.ignore:
105                 stopping_span.annotate()
106                 self.complete_spans.append(stopping_span)
107 
108         if len(self.active_spans) == 0:
109             self.finish()
110 
111     def current_span(self):
112         if self.active_spans:
113             return self.active_spans[-1]
114         else:
115             return None
116 
117     # Request is done, release any info we have about it.
118     def finish(self):
119         logger.debug("Stopping request: %s", self.request_id)
120         if self.end_time is None:
121             self.end_time = dt.datetime.utcnow()
122         if self.is_real_request:
123             self.tag("mem_delta", self._get_mem_delta())
124             if not self.is_ignored():
125                 batch_command = BatchCommand.from_tracked_request(self)
126                 CoreAgentSocketThread.send(batch_command)
127             SamplersThread.ensure_started()
128 
129         from scout_apm.core.context import context
130 
131         context.clear_tracked_request(self)
132 
133     def _get_mem_delta(self):
134         current_mem = get_rss_in_mb()
135         if current_mem > self._memory_start:
136             return current_mem - self._memory_start
137         return 0.0
138 
139     # A request is ignored if the tag "ignore_transaction" is set to True
140     def is_ignored(self):
141         return self.tags.get("ignore_transaction", False)
142 
143 
144 class Span(object):
145     __slots__ = (
146         "span_id",
147         "start_time",
148         "end_time",
149         "request_id",
150         "operation",
151         "ignore",
152         "ignore_children",
153         "parent",
154         "tags",
155         "start_objtrace_counts",
156         "end_objtrace_counts",
157         "should_capture_backtrace",
158     )
159 
160     def __init__(
161         self,
162         request_id=None,
163         operation=None,
164         ignore=False,
165         ignore_children=False,
166         parent=None,
167         should_capture_backtrace=True,
168     ):
169         self.span_id = "span-" + str(uuid4())
170         self.start_time = dt.datetime.utcnow()
171         self.end_time = None
172         self.request_id = request_id
173         self.operation = operation
174         self.ignore = ignore
175         self.ignore_children = ignore_children
176         self.parent = parent
177         self.tags = {}
178         self.start_objtrace_counts = objtrace.get_counts()
179         self.end_objtrace_counts = (0, 0, 0, 0)
180         self.should_capture_backtrace = should_capture_backtrace
181 
182     def __repr__(self):
183         # Incomplete to avoid TMI
184         return "<Span(span_id={}, operation={}, ignore={}, tags={})>".format(
185             repr(self.span_id), repr(self.operation), repr(self.ignore), repr(self.tags)
186         )
187 
188     def stop(self):
189         self.end_time = dt.datetime.utcnow()
190         self.end_objtrace_counts = objtrace.get_counts()
191 
192     def tag(self, key, value):
193         if key in self.tags:
194             logger.debug(
195                 "Overwriting previously set tag for span %s: %s", self.span_id, key
196             )
197         self.tags[key] = value
198 
199     # In seconds
200     def duration(self):
201         if self.end_time is not None:
202             return (self.end_time - self.start_time).total_seconds()
203         else:
204             # Current, running duration
205             return (dt.datetime.utcnow() - self.start_time).total_seconds()
206 
207     # Add any interesting annotations to the span. Assumes that we are in the
208     # process of stopping this span.
209     def annotate(self):
210         self.add_allocation_tags()
211         if not self.should_capture_backtrace:
212             return
213         slow_threshold = 0.5
214         if self.duration() > slow_threshold:
215             self.capture_backtrace()
216 
217     def add_allocation_tags(self):
218         if not objtrace.is_extension:
219             return
220 
221         start_allocs = (
222             self.start_objtrace_counts[0]
223             + self.start_objtrace_counts[1]
224             + self.start_objtrace_counts[2]
225         )
226         end_allocs = (
227             self.end_objtrace_counts[0]
228             + self.end_objtrace_counts[1]
229             + self.end_objtrace_counts[2]
230         )
231 
232         # If even one of the counters rolled over, we're pretty much
233         # guaranteed to have end_allocs be less than start_allocs.
234         # This should rarely happen. Max Unsigned Long Long is a big number
235         if end_allocs - start_allocs < 0:
236             logger.debug(
237                 "End allocation count smaller than start allocation "
238                 "count for span %s: start = %d, end = %d",
239                 self.span_id,
240                 start_allocs,
241                 end_allocs,
242             )
243             return
244 
245         self.tag("allocations", end_allocs - start_allocs)
246         self.tag("start_allocations", start_allocs)
247         self.tag("stop_allocations", end_allocs)
248 
249     def capture_backtrace(self):
250         self.tag("stack", backtrace.capture())
251
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/core/tracked_request.py b/src/scout_apm/core/tracked_request.py
--- a/src/scout_apm/core/tracked_request.py
+++ b/src/scout_apm/core/tracked_request.py
@@ -32,8 +32,12 @@
         "is_real_request",
         "_memory_start",
         "n_plus_one_tracker",
+        "hit_max",
     )
 
+    # Stop adding new spans at this point, to avoid exhausting memory
+    MAX_COMPLETE_SPANS = 1500
+
     @classmethod
     def instance(cls):
         from scout_apm.core.context import context
@@ -50,6 +54,7 @@
         self.is_real_request = False
         self._memory_start = get_rss_in_mb()
         self.n_plus_one_tracker = NPlusOneTracker()
+        self.hit_max = False
         logger.debug("Starting request: %s", self.request_id)
 
     def __repr__(self):
@@ -83,6 +88,15 @@
         else:
             parent_id = None
 
+        if len(self.complete_spans) >= self.MAX_COMPLETE_SPANS:
+            if not self.hit_max:
+                logger.warning(
+                    "Hit the maximum number of spans, this trace will be incomplete."
+                )
+                self.hit_max = True
+            ignore = True
+            ignore_children = True
+
         new_span = Span(
             request_id=self.request_id,
             operation=operation,

{"golden_diff": "diff --git a/src/scout_apm/core/tracked_request.py b/src/scout_apm/core/tracked_request.py\n--- a/src/scout_apm/core/tracked_request.py\n+++ b/src/scout_apm/core/tracked_request.py\n@@ -32,8 +32,12 @@\n         \"is_real_request\",\n         \"_memory_start\",\n         \"n_plus_one_tracker\",\n+        \"hit_max\",\n     )\n \n+    # Stop adding new spans at this point, to avoid exhausting memory\n+    MAX_COMPLETE_SPANS = 1500\n+\n     @classmethod\n     def instance(cls):\n         from scout_apm.core.context import context\n@@ -50,6 +54,7 @@\n         self.is_real_request = False\n         self._memory_start = get_rss_in_mb()\n         self.n_plus_one_tracker = NPlusOneTracker()\n+        self.hit_max = False\n         logger.debug(\"Starting request: %s\", self.request_id)\n \n     def __repr__(self):\n@@ -83,6 +88,15 @@\n         else:\n             parent_id = None\n \n+        if len(self.complete_spans) >= self.MAX_COMPLETE_SPANS:\n+            if not self.hit_max:\n+                logger.warning(\n+                    \"Hit the maximum number of spans, this trace will be incomplete.\"\n+                )\n+                self.hit_max = True\n+            ignore = True\n+            ignore_children = True\n+\n         new_span = Span(\n             request_id=self.request_id,\n             operation=operation,\n", "issue": "Total span limit\nFor *really* long running background jobs it'd be possible to have a tracked request with so many spans it causes memory problems. Following #471 we should probably have a (very high) total cap on the number of captured spans.\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport logging\nfrom uuid import uuid4\n\nfrom scout_apm.core import backtrace, objtrace\nfrom scout_apm.core.commands import BatchCommand\nfrom scout_apm.core.n_plus_one_tracker import NPlusOneTracker\nfrom scout_apm.core.samplers.memory import get_rss_in_mb\nfrom scout_apm.core.samplers.thread import SamplersThread\nfrom scout_apm.core.socket import CoreAgentSocketThread\n\nlogger = logging.getLogger(__name__)\n\n\nclass TrackedRequest(object):\n    \"\"\"\n    This is a container which keeps track of all module instances for a single\n    request. For convenience they are made available as attributes based on\n    their keyname\n    \"\"\"\n\n    __slots__ = (\n        \"request_id\",\n        \"start_time\",\n        \"end_time\",\n        \"active_spans\",\n        \"complete_spans\",\n        \"tags\",\n        \"is_real_request\",\n        \"_memory_start\",\n        \"n_plus_one_tracker\",\n    )\n\n    @classmethod\n    def instance(cls):\n        from scout_apm.core.context import context\n\n        return context.get_tracked_request()\n\n    def __init__(self):\n        self.request_id = \"req-\" + str(uuid4())\n        self.start_time = dt.datetime.utcnow()\n        self.end_time = None\n        self.active_spans = []\n        self.complete_spans = []\n        self.tags = {}\n        self.is_real_request = False\n        self._memory_start = get_rss_in_mb()\n        self.n_plus_one_tracker = NPlusOneTracker()\n        logger.debug(\"Starting request: %s\", self.request_id)\n\n    def __repr__(self):\n        # Incomplete to avoid TMI\n        return \"<TrackedRequest(request_id={}, tags={})>\".format(\n            repr(self.request_id), repr(self.tags)\n        )\n\n    def tag(self, key, value):\n        if key in self.tags:\n            logger.debug(\n                \"Overwriting previously set tag for request %s: %s\",\n                self.request_id,\n                key,\n            )\n        self.tags[key] = value\n\n    def start_span(\n        self,\n        operation,\n        ignore=False,\n        ignore_children=False,\n        should_capture_backtrace=True,\n    ):\n        parent = self.current_span()\n        if parent is not None:\n            parent_id = parent.span_id\n            if parent.ignore_children:\n                ignore = True\n                ignore_children = True\n        else:\n            parent_id = None\n\n        new_span = Span(\n            request_id=self.request_id,\n            operation=operation,\n            ignore=ignore,\n            ignore_children=ignore_children,\n            parent=parent_id,\n            should_capture_backtrace=should_capture_backtrace,\n        )\n        self.active_spans.append(new_span)\n        return new_span\n\n    def stop_span(self):\n        try:\n            stopping_span = self.active_spans.pop()\n        except IndexError as exc:\n            logger.debug(\"Exception when stopping span\", exc_info=exc)\n        else:\n            stopping_span.stop()\n            if not stopping_span.ignore:\n                stopping_span.annotate()\n                self.complete_spans.append(stopping_span)\n\n        if len(self.active_spans) == 0:\n            self.finish()\n\n    def current_span(self):\n        if self.active_spans:\n            return self.active_spans[-1]\n        else:\n            return None\n\n    # Request is done, release any info we have about it.\n    def finish(self):\n        logger.debug(\"Stopping request: %s\", self.request_id)\n        if self.end_time is None:\n            self.end_time = dt.datetime.utcnow()\n        if self.is_real_request:\n            self.tag(\"mem_delta\", self._get_mem_delta())\n            if not self.is_ignored():\n                batch_command = BatchCommand.from_tracked_request(self)\n                CoreAgentSocketThread.send(batch_command)\n            SamplersThread.ensure_started()\n\n        from scout_apm.core.context import context\n\n        context.clear_tracked_request(self)\n\n    def _get_mem_delta(self):\n        current_mem = get_rss_in_mb()\n        if current_mem > self._memory_start:\n            return current_mem - self._memory_start\n        return 0.0\n\n    # A request is ignored if the tag \"ignore_transaction\" is set to True\n    def is_ignored(self):\n        return self.tags.get(\"ignore_transaction\", False)\n\n\nclass Span(object):\n    __slots__ = (\n        \"span_id\",\n        \"start_time\",\n        \"end_time\",\n        \"request_id\",\n        \"operation\",\n        \"ignore\",\n        \"ignore_children\",\n        \"parent\",\n        \"tags\",\n        \"start_objtrace_counts\",\n        \"end_objtrace_counts\",\n        \"should_capture_backtrace\",\n    )\n\n    def __init__(\n        self,\n        request_id=None,\n        operation=None,\n        ignore=False,\n        ignore_children=False,\n        parent=None,\n        should_capture_backtrace=True,\n    ):\n        self.span_id = \"span-\" + str(uuid4())\n        self.start_time = dt.datetime.utcnow()\n        self.end_time = None\n        self.request_id = request_id\n        self.operation = operation\n        self.ignore = ignore\n        self.ignore_children = ignore_children\n        self.parent = parent\n        self.tags = {}\n        self.start_objtrace_counts = objtrace.get_counts()\n        self.end_objtrace_counts = (0, 0, 0, 0)\n        self.should_capture_backtrace = should_capture_backtrace\n\n    def __repr__(self):\n        # Incomplete to avoid TMI\n        return \"<Span(span_id={}, operation={}, ignore={}, tags={})>\".format(\n            repr(self.span_id), repr(self.operation), repr(self.ignore), repr(self.tags)\n        )\n\n    def stop(self):\n        self.end_time = dt.datetime.utcnow()\n        self.end_objtrace_counts = objtrace.get_counts()\n\n    def tag(self, key, value):\n        if key in self.tags:\n            logger.debug(\n                \"Overwriting previously set tag for span %s: %s\", self.span_id, key\n            )\n        self.tags[key] = value\n\n    # In seconds\n    def duration(self):\n        if self.end_time is not None:\n            return (self.end_time - self.start_time).total_seconds()\n        else:\n            # Current, running duration\n            return (dt.datetime.utcnow() - self.start_time).total_seconds()\n\n    # Add any interesting annotations to the span. Assumes that we are in the\n    # process of stopping this span.\n    def annotate(self):\n        self.add_allocation_tags()\n        if not self.should_capture_backtrace:\n            return\n        slow_threshold = 0.5\n        if self.duration() > slow_threshold:\n            self.capture_backtrace()\n\n    def add_allocation_tags(self):\n        if not objtrace.is_extension:\n            return\n\n        start_allocs = (\n            self.start_objtrace_counts[0]\n            + self.start_objtrace_counts[1]\n            + self.start_objtrace_counts[2]\n        )\n        end_allocs = (\n            self.end_objtrace_counts[0]\n            + self.end_objtrace_counts[1]\n            + self.end_objtrace_counts[2]\n        )\n\n        # If even one of the counters rolled over, we're pretty much\n        # guaranteed to have end_allocs be less than start_allocs.\n        # This should rarely happen. Max Unsigned Long Long is a big number\n        if end_allocs - start_allocs < 0:\n            logger.debug(\n                \"End allocation count smaller than start allocation \"\n                \"count for span %s: start = %d, end = %d\",\n                self.span_id,\n                start_allocs,\n                end_allocs,\n            )\n            return\n\n        self.tag(\"allocations\", end_allocs - start_allocs)\n        self.tag(\"start_allocations\", start_allocs)\n        self.tag(\"stop_allocations\", end_allocs)\n\n    def capture_backtrace(self):\n        self.tag(\"stack\", backtrace.capture())\n", "path": "src/scout_apm/core/tracked_request.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport logging\nfrom uuid import uuid4\n\nfrom scout_apm.core import backtrace, objtrace\nfrom scout_apm.core.commands import BatchCommand\nfrom scout_apm.core.n_plus_one_tracker import NPlusOneTracker\nfrom scout_apm.core.samplers.memory import get_rss_in_mb\nfrom scout_apm.core.samplers.thread import SamplersThread\nfrom scout_apm.core.socket import CoreAgentSocketThread\n\nlogger = logging.getLogger(__name__)\n\n\nclass TrackedRequest(object):\n    \"\"\"\n    This is a container which keeps track of all module instances for a single\n    request. For convenience they are made available as attributes based on\n    their keyname\n    \"\"\"\n\n    __slots__ = (\n        \"request_id\",\n        \"start_time\",\n        \"end_time\",\n        \"active_spans\",\n        \"complete_spans\",\n        \"tags\",\n        \"is_real_request\",\n        \"_memory_start\",\n        \"n_plus_one_tracker\",\n        \"hit_max\",\n    )\n\n    # Stop adding new spans at this point, to avoid exhausting memory\n    MAX_COMPLETE_SPANS = 1500\n\n    @classmethod\n    def instance(cls):\n        from scout_apm.core.context import context\n\n        return context.get_tracked_request()\n\n    def __init__(self):\n        self.request_id = \"req-\" + str(uuid4())\n        self.start_time = dt.datetime.utcnow()\n        self.end_time = None\n        self.active_spans = []\n        self.complete_spans = []\n        self.tags = {}\n        self.is_real_request = False\n        self._memory_start = get_rss_in_mb()\n        self.n_plus_one_tracker = NPlusOneTracker()\n        self.hit_max = False\n        logger.debug(\"Starting request: %s\", self.request_id)\n\n    def __repr__(self):\n        # Incomplete to avoid TMI\n        return \"<TrackedRequest(request_id={}, tags={})>\".format(\n            repr(self.request_id), repr(self.tags)\n        )\n\n    def tag(self, key, value):\n        if key in self.tags:\n            logger.debug(\n                \"Overwriting previously set tag for request %s: %s\",\n                self.request_id,\n                key,\n            )\n        self.tags[key] = value\n\n    def start_span(\n        self,\n        operation,\n        ignore=False,\n        ignore_children=False,\n        should_capture_backtrace=True,\n    ):\n        parent = self.current_span()\n        if parent is not None:\n            parent_id = parent.span_id\n            if parent.ignore_children:\n                ignore = True\n                ignore_children = True\n        else:\n            parent_id = None\n\n        if len(self.complete_spans) >= self.MAX_COMPLETE_SPANS:\n            if not self.hit_max:\n                logger.warning(\n                    \"Hit the maximum number of spans, this trace will be incomplete.\"\n                )\n                self.hit_max = True\n            ignore = True\n            ignore_children = True\n\n        new_span = Span(\n            request_id=self.request_id,\n            operation=operation,\n            ignore=ignore,\n            ignore_children=ignore_children,\n            parent=parent_id,\n            should_capture_backtrace=should_capture_backtrace,\n        )\n        self.active_spans.append(new_span)\n        return new_span\n\n    def stop_span(self):\n        try:\n            stopping_span = self.active_spans.pop()\n        except IndexError as exc:\n            logger.debug(\"Exception when stopping span\", exc_info=exc)\n        else:\n            stopping_span.stop()\n            if not stopping_span.ignore:\n                stopping_span.annotate()\n                self.complete_spans.append(stopping_span)\n\n        if len(self.active_spans) == 0:\n            self.finish()\n\n    def current_span(self):\n        if self.active_spans:\n            return self.active_spans[-1]\n        else:\n            return None\n\n    # Request is done, release any info we have about it.\n    def finish(self):\n        logger.debug(\"Stopping request: %s\", self.request_id)\n        if self.end_time is None:\n            self.end_time = dt.datetime.utcnow()\n        if self.is_real_request:\n            self.tag(\"mem_delta\", self._get_mem_delta())\n            if not self.is_ignored():\n                batch_command = BatchCommand.from_tracked_request(self)\n                CoreAgentSocketThread.send(batch_command)\n            SamplersThread.ensure_started()\n\n        from scout_apm.core.context import context\n\n        context.clear_tracked_request(self)\n\n    def _get_mem_delta(self):\n        current_mem = get_rss_in_mb()\n        if current_mem > self._memory_start:\n            return current_mem - self._memory_start\n        return 0.0\n\n    # A request is ignored if the tag \"ignore_transaction\" is set to True\n    def is_ignored(self):\n        return self.tags.get(\"ignore_transaction\", False)\n\n\nclass Span(object):\n    __slots__ = (\n        \"span_id\",\n        \"start_time\",\n        \"end_time\",\n        \"request_id\",\n        \"operation\",\n        \"ignore\",\n        \"ignore_children\",\n        \"parent\",\n        \"tags\",\n        \"start_objtrace_counts\",\n        \"end_objtrace_counts\",\n        \"should_capture_backtrace\",\n    )\n\n    def __init__(\n        self,\n        request_id=None,\n        operation=None,\n        ignore=False,\n        ignore_children=False,\n        parent=None,\n        should_capture_backtrace=True,\n    ):\n        self.span_id = \"span-\" + str(uuid4())\n        self.start_time = dt.datetime.utcnow()\n        self.end_time = None\n        self.request_id = request_id\n        self.operation = operation\n        self.ignore = ignore\n        self.ignore_children = ignore_children\n        self.parent = parent\n        self.tags = {}\n        self.start_objtrace_counts = objtrace.get_counts()\n        self.end_objtrace_counts = (0, 0, 0, 0)\n        self.should_capture_backtrace = should_capture_backtrace\n\n    def __repr__(self):\n        # Incomplete to avoid TMI\n        return \"<Span(span_id={}, operation={}, ignore={}, tags={})>\".format(\n            repr(self.span_id), repr(self.operation), repr(self.ignore), repr(self.tags)\n        )\n\n    def stop(self):\n        self.end_time = dt.datetime.utcnow()\n        self.end_objtrace_counts = objtrace.get_counts()\n\n    def tag(self, key, value):\n        if key in self.tags:\n            logger.debug(\n                \"Overwriting previously set tag for span %s: %s\", self.span_id, key\n            )\n        self.tags[key] = value\n\n    # In seconds\n    def duration(self):\n        if self.end_time is not None:\n            return (self.end_time - self.start_time).total_seconds()\n        else:\n            # Current, running duration\n            return (dt.datetime.utcnow() - self.start_time).total_seconds()\n\n    # Add any interesting annotations to the span. Assumes that we are in the\n    # process of stopping this span.\n    def annotate(self):\n        self.add_allocation_tags()\n        if not self.should_capture_backtrace:\n            return\n        slow_threshold = 0.5\n        if self.duration() > slow_threshold:\n            self.capture_backtrace()\n\n    def add_allocation_tags(self):\n        if not objtrace.is_extension:\n            return\n\n        start_allocs = (\n            self.start_objtrace_counts[0]\n            + self.start_objtrace_counts[1]\n            + self.start_objtrace_counts[2]\n        )\n        end_allocs = (\n            self.end_objtrace_counts[0]\n            + self.end_objtrace_counts[1]\n            + self.end_objtrace_counts[2]\n        )\n\n        # If even one of the counters rolled over, we're pretty much\n        # guaranteed to have end_allocs be less than start_allocs.\n        # This should rarely happen. Max Unsigned Long Long is a big number\n        if end_allocs - start_allocs < 0:\n            logger.debug(\n                \"End allocation count smaller than start allocation \"\n                \"count for span %s: start = %d, end = %d\",\n                self.span_id,\n                start_allocs,\n                end_allocs,\n            )\n            return\n\n        self.tag(\"allocations\", end_allocs - start_allocs)\n        self.tag(\"start_allocations\", start_allocs)\n        self.tag(\"stop_allocations\", end_allocs)\n\n    def capture_backtrace(self):\n        self.tag(\"stack\", backtrace.capture())\n", "path": "src/scout_apm/core/tracked_request.py"}]}