We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Performance Bug in the varmap function
**Describe the bug**: ...
When python code throws an error, and one of the local variables contains a very large list of lists of lists, it takes the elastic python apm client a very long time to process through the list. In our case it was taking over 60 seconds for the code to complete.


**To Reproduce**
To simply replicate the issue, I: (see code snipit  in the elastic forum, link below)

- created a simple script to create a list of list of lists of integers.
- in one method I had my "with elasticAPMTransaction:" statement.
- under this I created the list (200 integers) of list (300 integers) of list (40 integers) aka 24M items total.
- started to iterate through the outer list and picked an arbitrary spot to through an exception. This essentially mimicked the method where the real code threw an exception.
- wrapped this whole thing in a profiler and evaluated the output.
This would take 60 seconds +/- to complete.

Seems like the issue is in the varmap function in elasticapm\utils_init_.py but there maybe lots of ways to solve this issue.

**Environment (please complete the following information)**
- OS: [e.g. Linux]
- Python version: 3.x
- Framework and version [e.g. Django 2.1]:
- APM Server version: N/A
- Agent version: 6.7.2


**Additional context**
Created a case here https://discuss.elastic.co/t/python-client-apm-very-slow-when-local-list-variable-is-very-large/309935. See this for additional details.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/utils/__init__.py`
Content:
```
1 #  BSD 3-Clause License
2 #
3 #  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details
4 #  Copyright (c) 2019, Elasticsearch BV
5 #  All rights reserved.
6 #
7 #  Redistribution and use in source and binary forms, with or without
8 #  modification, are permitted provided that the following conditions are met:
9 #
10 #  * Redistributions of source code must retain the above copyright notice, this
11 #    list of conditions and the following disclaimer.
12 #
13 #  * Redistributions in binary form must reproduce the above copyright notice,
14 #    this list of conditions and the following disclaimer in the documentation
15 #    and/or other materials provided with the distribution.
16 #
17 #  * Neither the name of the copyright holder nor the names of its
18 #    contributors may be used to endorse or promote products derived from
19 #    this software without specific prior written permission.
20 #
21 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 
31 import base64
32 import os
33 import re
34 import urllib.parse
35 from functools import partial
36 from types import FunctionType
37 from typing import Pattern
38 
39 from elasticapm.conf import constants
40 from elasticapm.utils import encoding
41 
42 try:
43     from functools import partialmethod
44 
45     partial_types = (partial, partialmethod)
46 except ImportError:
47     # Python 2
48     partial_types = (partial,)
49 
50 
51 default_ports = {"https": 443, "http": 80, "postgresql": 5432, "mysql": 3306, "mssql": 1433}
52 
53 
54 def varmap(func, var, context=None, name=None, **kwargs):
55     """
56     Executes ``func(key_name, value)`` on all values,
57     recursively discovering dict and list scoped
58     values.
59     """
60     if context is None:
61         context = set()
62     objid = id(var)
63     if objid in context:
64         return func(name, "<...>", **kwargs)
65     context.add(objid)
66     if isinstance(var, dict):
67         # iterate over a copy of the dictionary to avoid "dictionary changed size during iteration" issues
68         ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)
69     elif isinstance(var, (list, tuple)):
70         ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)
71     else:
72         ret = func(name, var, **kwargs)
73     context.remove(objid)
74     return ret
75 
76 
77 def get_name_from_func(func: FunctionType) -> str:
78     # partials don't have `__module__` or `__name__`, so we use the values from the "inner" function
79     if isinstance(func, partial_types):
80         return "partial({})".format(get_name_from_func(func.func))
81     elif hasattr(func, "_partialmethod") and hasattr(func._partialmethod, "func"):
82         return "partial({})".format(get_name_from_func(func._partialmethod.func))
83 
84     module = func.__module__
85 
86     if hasattr(func, "view_class"):
87         view_name = func.view_class.__name__
88     elif hasattr(func, "__name__"):
89         view_name = func.__name__
90     else:  # Fall back if there's no __name__
91         view_name = func.__class__.__name__
92 
93     return "{0}.{1}".format(module, view_name)
94 
95 
96 def build_name_with_http_method_prefix(name, request):
97     return " ".join((request.method, name)) if name else name
98 
99 
100 def is_master_process() -> bool:
101     # currently only recognizes uwsgi master process
102     try:
103         import uwsgi
104 
105         return os.getpid() == uwsgi.masterpid()
106     except ImportError:
107         return False
108 
109 
110 def get_url_dict(url: str) -> dict:
111     parse_result = urllib.parse.urlparse(url)
112 
113     url_dict = {
114         "full": encoding.keyword_field(url),
115         "protocol": parse_result.scheme + ":",
116         "hostname": encoding.keyword_field(parse_result.hostname),
117         "pathname": encoding.keyword_field(parse_result.path),
118     }
119 
120     port = None if parse_result.port is None else str(parse_result.port)
121 
122     if port:
123         url_dict["port"] = port
124     if parse_result.query:
125         url_dict["search"] = encoding.keyword_field("?" + parse_result.query)
126     return url_dict
127 
128 
129 def sanitize_url(url: str) -> str:
130     if "@" not in url:
131         return url
132     parts = urllib.parse.urlparse(url)
133     return url.replace("%s:%s" % (parts.username, parts.password), "%s:%s" % (parts.username, constants.MASK))
134 
135 
136 def get_host_from_url(url: str) -> str:
137     parsed_url = urllib.parse.urlparse(url)
138     host = parsed_url.hostname or " "
139 
140     if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port:
141         host += ":" + str(parsed_url.port)
142 
143     return host
144 
145 
146 def url_to_destination_resource(url: str) -> str:
147     parts = urllib.parse.urlsplit(url)
148     hostname = parts.hostname if parts.hostname else ""
149     # preserve brackets for IPv6 URLs
150     if "://[" in url:
151         hostname = "[%s]" % hostname
152     try:
153         port = parts.port
154     except ValueError:
155         # Malformed port, just use None rather than raising an exception
156         port = None
157     default_port = default_ports.get(parts.scheme, None)
158     name = "%s://%s" % (parts.scheme, hostname)
159     resource = hostname
160     if not port and parts.scheme in default_ports:
161         port = default_ports[parts.scheme]
162     if port:
163         if port != default_port:
164             name += ":%d" % port
165         resource += ":%d" % port
166     return resource
167 
168 
169 def read_pem_file(file_obj) -> bytes:
170     cert = b""
171     for line in file_obj:
172         if line.startswith(b"-----BEGIN CERTIFICATE-----"):
173             break
174     # scan until we find the first END CERTIFICATE marker
175     for line in file_obj:
176         if line.startswith(b"-----END CERTIFICATE-----"):
177             break
178         cert += line.strip()
179     return base64.b64decode(cert)
180 
181 
182 def starmatch_to_regex(pattern: str) -> Pattern:
183     options = re.DOTALL
184     # check if we are case sensitive
185     if pattern.startswith("(?-i)"):
186         pattern = pattern[5:]
187     else:
188         options |= re.IGNORECASE
189     i, n = 0, len(pattern)
190     res = []
191     while i < n:
192         c = pattern[i]
193         i = i + 1
194         if c == "*":
195             res.append(".*")
196         else:
197             res.append(re.escape(c))
198     return re.compile(r"(?:%s)\Z" % "".join(res), options)
199 
200 
201 def nested_key(d: dict, *args):
202     """
203     Traverses a dictionary for nested keys. Returns `None` if the at any point
204     in the traversal a key cannot be found.
205 
206     Example:
207 
208         >>> from elasticapm.utils import nested_key
209         >>> d = {"a": {"b": {"c": 0}}}
210         >>> nested_key(d, "a", "b", "c")
211         0
212         >>> nested_key(d, "a", "b", "d")
213         None
214     """
215     for arg in args:
216         try:
217             d = d[arg]
218         except (TypeError, KeyError):
219             d = None
220             break
221     return d
222
```
--- 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/elasticapm/utils/__init__.py b/elasticapm/utils/__init__.py
--- a/elasticapm/utils/__init__.py
+++ b/elasticapm/utils/__init__.py
@@ -64,10 +64,14 @@
         return func(name, "<...>", **kwargs)
     context.add(objid)
     if isinstance(var, dict):
+        # Apply func() before recursion, so that `shorten()` doesn't have to iterate over all the trimmed values
+        ret = func(name, var, **kwargs)
         # iterate over a copy of the dictionary to avoid "dictionary changed size during iteration" issues
-        ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)
+        ret = dict((k, varmap(func, v, context, k, **kwargs)) for k, v in ret.copy().items())
     elif isinstance(var, (list, tuple)):
-        ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)
+        # Apply func() before recursion, so that `shorten()` doesn't have to iterate over all the trimmed values
+        ret = func(name, var, **kwargs)
+        ret = [varmap(func, f, context, name, **kwargs) for f in ret]
     else:
         ret = func(name, var, **kwargs)
     context.remove(objid)

{"golden_diff": "diff --git a/elasticapm/utils/__init__.py b/elasticapm/utils/__init__.py\n--- a/elasticapm/utils/__init__.py\n+++ b/elasticapm/utils/__init__.py\n@@ -64,10 +64,14 @@\n         return func(name, \"<...>\", **kwargs)\n     context.add(objid)\n     if isinstance(var, dict):\n+        # Apply func() before recursion, so that `shorten()` doesn't have to iterate over all the trimmed values\n+        ret = func(name, var, **kwargs)\n         # iterate over a copy of the dictionary to avoid \"dictionary changed size during iteration\" issues\n-        ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)\n+        ret = dict((k, varmap(func, v, context, k, **kwargs)) for k, v in ret.copy().items())\n     elif isinstance(var, (list, tuple)):\n-        ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)\n+        # Apply func() before recursion, so that `shorten()` doesn't have to iterate over all the trimmed values\n+        ret = func(name, var, **kwargs)\n+        ret = [varmap(func, f, context, name, **kwargs) for f in ret]\n     else:\n         ret = func(name, var, **kwargs)\n     context.remove(objid)\n", "issue": "Performance Bug in the varmap function\n**Describe the bug**: ...\r\nWhen python code throws an error, and one of the local variables contains a very large list of lists of lists, it takes the elastic python apm client a very long time to process through the list. In our case it was taking over 60 seconds for the code to complete.\r\n\r\n\r\n**To Reproduce**\r\nTo simply replicate the issue, I: (see code snipit  in the elastic forum, link below)\r\n\r\n- created a simple script to create a list of list of lists of integers.\r\n- in one method I had my \"with elasticAPMTransaction:\" statement.\r\n- under this I created the list (200 integers) of list (300 integers) of list (40 integers) aka 24M items total.\r\n- started to iterate through the outer list and picked an arbitrary spot to through an exception. This essentially mimicked the method where the real code threw an exception.\r\n- wrapped this whole thing in a profiler and evaluated the output.\r\nThis would take 60 seconds +/- to complete.\r\n\r\nSeems like the issue is in the varmap function in elasticapm\\utils_init_.py but there maybe lots of ways to solve this issue.\r\n\r\n**Environment (please complete the following information)**\r\n- OS: [e.g. Linux]\r\n- Python version: 3.x\r\n- Framework and version [e.g. Django 2.1]:\r\n- APM Server version: N/A\r\n- Agent version: 6.7.2\r\n\r\n\r\n**Additional context**\r\nCreated a case here https://discuss.elastic.co/t/python-client-apm-very-slow-when-local-list-variable-is-very-large/309935. See this for additional details.\n", "before_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\nimport base64\nimport os\nimport re\nimport urllib.parse\nfrom functools import partial\nfrom types import FunctionType\nfrom typing import Pattern\n\nfrom elasticapm.conf import constants\nfrom elasticapm.utils import encoding\n\ntry:\n    from functools import partialmethod\n\n    partial_types = (partial, partialmethod)\nexcept ImportError:\n    # Python 2\n    partial_types = (partial,)\n\n\ndefault_ports = {\"https\": 443, \"http\": 80, \"postgresql\": 5432, \"mysql\": 3306, \"mssql\": 1433}\n\n\ndef varmap(func, var, context=None, name=None, **kwargs):\n    \"\"\"\n    Executes ``func(key_name, value)`` on all values,\n    recursively discovering dict and list scoped\n    values.\n    \"\"\"\n    if context is None:\n        context = set()\n    objid = id(var)\n    if objid in context:\n        return func(name, \"<...>\", **kwargs)\n    context.add(objid)\n    if isinstance(var, dict):\n        # iterate over a copy of the dictionary to avoid \"dictionary changed size during iteration\" issues\n        ret = func(name, dict((k, varmap(func, v, context, k, **kwargs)) for k, v in var.copy().items()), **kwargs)\n    elif isinstance(var, (list, tuple)):\n        ret = func(name, [varmap(func, f, context, name, **kwargs) for f in var], **kwargs)\n    else:\n        ret = func(name, var, **kwargs)\n    context.remove(objid)\n    return ret\n\n\ndef get_name_from_func(func: FunctionType) -> str:\n    # partials don't have `__module__` or `__name__`, so we use the values from the \"inner\" function\n    if isinstance(func, partial_types):\n        return \"partial({})\".format(get_name_from_func(func.func))\n    elif hasattr(func, \"_partialmethod\") and hasattr(func._partialmethod, \"func\"):\n        return \"partial({})\".format(get_name_from_func(func._partialmethod.func))\n\n    module = func.__module__\n\n    if hasattr(func, \"view_class\"):\n        view_name = func.view_class.__name__\n    elif hasattr(func, \"__name__\"):\n        view_name = func.__name__\n    else:  # Fall back if there's no __name__\n        view_name = func.__class__.__name__\n\n    return \"{0}.{1}\".format(module, view_name)\n\n\ndef build_name_with_http_method_prefix(name, request):\n    return \" \".join((request.method, name)) if name else name\n\n\ndef is_master_process() -> bool:\n    # currently only recognizes uwsgi master process\n    try:\n        import uwsgi\n\n        return os.getpid() == uwsgi.masterpid()\n    except ImportError:\n        return False\n\n\ndef get_url_dict(url: str) -> dict:\n    parse_result = urllib.parse.urlparse(url)\n\n    url_dict = {\n        \"full\": encoding.keyword_field(url),\n        \"protocol\": parse_result.scheme + \":\",\n        \"hostname\": encoding.keyword_field(parse_result.hostname),\n        \"pathname\": encoding.keyword_field(parse_result.path),\n    }\n\n    port = None if parse_result.port is None else str(parse_result.port)\n\n    if port:\n        url_dict[\"port\"] = port\n    if parse_result.query:\n        url_dict[\"search\"] = encoding.keyword_field(\"?\" + parse_result.query)\n    return url_dict\n\n\ndef sanitize_url(url: str) -> str:\n    if \"@\" not in url:\n        return url\n    parts = urllib.parse.urlparse(url)\n    return url.replace(\"%s:%s\" % (parts.username, parts.password), \"%s:%s\" % (parts.username, constants.MASK))\n\n\ndef get_host_from_url(url: str) -> str:\n    parsed_url = urllib.parse.urlparse(url)\n    host = parsed_url.hostname or \" \"\n\n    if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port:\n        host += \":\" + str(parsed_url.port)\n\n    return host\n\n\ndef url_to_destination_resource(url: str) -> str:\n    parts = urllib.parse.urlsplit(url)\n    hostname = parts.hostname if parts.hostname else \"\"\n    # preserve brackets for IPv6 URLs\n    if \"://[\" in url:\n        hostname = \"[%s]\" % hostname\n    try:\n        port = parts.port\n    except ValueError:\n        # Malformed port, just use None rather than raising an exception\n        port = None\n    default_port = default_ports.get(parts.scheme, None)\n    name = \"%s://%s\" % (parts.scheme, hostname)\n    resource = hostname\n    if not port and parts.scheme in default_ports:\n        port = default_ports[parts.scheme]\n    if port:\n        if port != default_port:\n            name += \":%d\" % port\n        resource += \":%d\" % port\n    return resource\n\n\ndef read_pem_file(file_obj) -> bytes:\n    cert = b\"\"\n    for line in file_obj:\n        if line.startswith(b\"-----BEGIN CERTIFICATE-----\"):\n            break\n    # scan until we find the first END CERTIFICATE marker\n    for line in file_obj:\n        if line.startswith(b\"-----END CERTIFICATE-----\"):\n            break\n        cert += line.strip()\n    return base64.b64decode(cert)\n\n\ndef starmatch_to_regex(pattern: str) -> Pattern:\n    options = re.DOTALL\n    # check if we are case sensitive\n    if pattern.startswith(\"(?-i)\"):\n        pattern = pattern[5:]\n    else:\n        options |= re.IGNORECASE\n    i, n = 0, len(pattern)\n    res = []\n    while i < n:\n        c = pattern[i]\n        i = i + 1\n        if c == \"*\":\n            res.append(\".*\")\n        else:\n            res.append(re.escape(c))\n    return re.compile(r\"(?:%s)\\Z\" % \"\".join(res), options)\n\n\ndef nested_key(d: dict, *args):\n    \"\"\"\n    Traverses a dictionary for nested keys. Returns `None` if the at any point\n    in the traversal a key cannot be found.\n\n    Example:\n\n        >>> from elasticapm.utils import nested_key\n        >>> d = {\"a\": {\"b\": {\"c\": 0}}}\n        >>> nested_key(d, \"a\", \"b\", \"c\")\n        0\n        >>> nested_key(d, \"a\", \"b\", \"d\")\n        None\n    \"\"\"\n    for arg in args:\n        try:\n            d = d[arg]\n        except (TypeError, KeyError):\n            d = None\n            break\n    return d\n", "path": "elasticapm/utils/__init__.py"}], "after_files": [{"content": "#  BSD 3-Clause License\n#\n#  Copyright (c) 2012, the Sentry Team, see AUTHORS for more details\n#  Copyright (c) 2019, Elasticsearch BV\n#  All rights reserved.\n#\n#  Redistribution and use in source and binary forms, with or without\n#  modification, are permitted provided that the following conditions are met:\n#\n#  * Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n#  * Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n#  * Neither the name of the copyright holder nor the names of its\n#    contributors may be used to endorse or promote products derived from\n#    this software without specific prior written permission.\n#\n#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n#  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n#  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\nimport base64\nimport os\nimport re\nimport urllib.parse\nfrom functools import partial\nfrom types import FunctionType\nfrom typing import Pattern\n\nfrom elasticapm.conf import constants\nfrom elasticapm.utils import encoding\n\ntry:\n    from functools import partialmethod\n\n    partial_types = (partial, partialmethod)\nexcept ImportError:\n    # Python 2\n    partial_types = (partial,)\n\n\ndefault_ports = {\"https\": 443, \"http\": 80, \"postgresql\": 5432, \"mysql\": 3306, \"mssql\": 1433}\n\n\ndef varmap(func, var, context=None, name=None, **kwargs):\n    \"\"\"\n    Executes ``func(key_name, value)`` on all values,\n    recursively discovering dict and list scoped\n    values.\n    \"\"\"\n    if context is None:\n        context = set()\n    objid = id(var)\n    if objid in context:\n        return func(name, \"<...>\", **kwargs)\n    context.add(objid)\n    if isinstance(var, dict):\n        # Apply func() before recursion, so that `shorten()` doesn't have to iterate over all the trimmed values\n        ret = func(name, var, **kwargs)\n        # iterate over a copy of the dictionary to avoid \"dictionary changed size during iteration\" issues\n        ret = dict((k, varmap(func, v, context, k, **kwargs)) for k, v in ret.copy().items())\n    elif isinstance(var, (list, tuple)):\n        # Apply func() before recursion, so that `shorten()` doesn't have to iterate over all the trimmed values\n        ret = func(name, var, **kwargs)\n        ret = [varmap(func, f, context, name, **kwargs) for f in ret]\n    else:\n        ret = func(name, var, **kwargs)\n    context.remove(objid)\n    return ret\n\n\ndef get_name_from_func(func: FunctionType) -> str:\n    # partials don't have `__module__` or `__name__`, so we use the values from the \"inner\" function\n    if isinstance(func, partial_types):\n        return \"partial({})\".format(get_name_from_func(func.func))\n    elif hasattr(func, \"_partialmethod\") and hasattr(func._partialmethod, \"func\"):\n        return \"partial({})\".format(get_name_from_func(func._partialmethod.func))\n\n    module = func.__module__\n\n    if hasattr(func, \"view_class\"):\n        view_name = func.view_class.__name__\n    elif hasattr(func, \"__name__\"):\n        view_name = func.__name__\n    else:  # Fall back if there's no __name__\n        view_name = func.__class__.__name__\n\n    return \"{0}.{1}\".format(module, view_name)\n\n\ndef build_name_with_http_method_prefix(name, request):\n    return \" \".join((request.method, name)) if name else name\n\n\ndef is_master_process() -> bool:\n    # currently only recognizes uwsgi master process\n    try:\n        import uwsgi\n\n        return os.getpid() == uwsgi.masterpid()\n    except ImportError:\n        return False\n\n\ndef get_url_dict(url: str) -> dict:\n    parse_result = urllib.parse.urlparse(url)\n\n    url_dict = {\n        \"full\": encoding.keyword_field(url),\n        \"protocol\": parse_result.scheme + \":\",\n        \"hostname\": encoding.keyword_field(parse_result.hostname),\n        \"pathname\": encoding.keyword_field(parse_result.path),\n    }\n\n    port = None if parse_result.port is None else str(parse_result.port)\n\n    if port:\n        url_dict[\"port\"] = port\n    if parse_result.query:\n        url_dict[\"search\"] = encoding.keyword_field(\"?\" + parse_result.query)\n    return url_dict\n\n\ndef sanitize_url(url: str) -> str:\n    if \"@\" not in url:\n        return url\n    parts = urllib.parse.urlparse(url)\n    return url.replace(\"%s:%s\" % (parts.username, parts.password), \"%s:%s\" % (parts.username, constants.MASK))\n\n\ndef get_host_from_url(url: str) -> str:\n    parsed_url = urllib.parse.urlparse(url)\n    host = parsed_url.hostname or \" \"\n\n    if parsed_url.port and default_ports.get(parsed_url.scheme) != parsed_url.port:\n        host += \":\" + str(parsed_url.port)\n\n    return host\n\n\ndef url_to_destination_resource(url: str) -> str:\n    parts = urllib.parse.urlsplit(url)\n    hostname = parts.hostname if parts.hostname else \"\"\n    # preserve brackets for IPv6 URLs\n    if \"://[\" in url:\n        hostname = \"[%s]\" % hostname\n    try:\n        port = parts.port\n    except ValueError:\n        # Malformed port, just use None rather than raising an exception\n        port = None\n    default_port = default_ports.get(parts.scheme, None)\n    name = \"%s://%s\" % (parts.scheme, hostname)\n    resource = hostname\n    if not port and parts.scheme in default_ports:\n        port = default_ports[parts.scheme]\n    if port:\n        if port != default_port:\n            name += \":%d\" % port\n        resource += \":%d\" % port\n    return resource\n\n\ndef read_pem_file(file_obj) -> bytes:\n    cert = b\"\"\n    for line in file_obj:\n        if line.startswith(b\"-----BEGIN CERTIFICATE-----\"):\n            break\n    # scan until we find the first END CERTIFICATE marker\n    for line in file_obj:\n        if line.startswith(b\"-----END CERTIFICATE-----\"):\n            break\n        cert += line.strip()\n    return base64.b64decode(cert)\n\n\ndef starmatch_to_regex(pattern: str) -> Pattern:\n    options = re.DOTALL\n    # check if we are case sensitive\n    if pattern.startswith(\"(?-i)\"):\n        pattern = pattern[5:]\n    else:\n        options |= re.IGNORECASE\n    i, n = 0, len(pattern)\n    res = []\n    while i < n:\n        c = pattern[i]\n        i = i + 1\n        if c == \"*\":\n            res.append(\".*\")\n        else:\n            res.append(re.escape(c))\n    return re.compile(r\"(?:%s)\\Z\" % \"\".join(res), options)\n\n\ndef nested_key(d: dict, *args):\n    \"\"\"\n    Traverses a dictionary for nested keys. Returns `None` if the at any point\n    in the traversal a key cannot be found.\n\n    Example:\n\n        >>> from elasticapm.utils import nested_key\n        >>> d = {\"a\": {\"b\": {\"c\": 0}}}\n        >>> nested_key(d, \"a\", \"b\", \"c\")\n        0\n        >>> nested_key(d, \"a\", \"b\", \"d\")\n        None\n    \"\"\"\n    for arg in args:\n        try:\n            d = d[arg]\n        except (TypeError, KeyError):\n            d = None\n            break\n    return d\n", "path": "elasticapm/utils/__init__.py"}]}