77
88import tenacity
99
10- from ddtrace .utils import deprecation
1110from ddtrace .utils .formats import parse_tags_str
1211from ddtrace .vendor import six
1312from ddtrace .vendor .six .moves import http_client
2827PYTHON_VERSION = platform .python_version ().encode ()
2928
3029
31- class InvalidEndpoint (ValueError ):
32- pass
33-
34-
3530class UploadFailed (exporter .ExportError ):
3631 """Upload failure."""
3732
38- def __init__ (self , exception ):
33+ def __init__ (self , exception , msg = None ):
3934 """Create a failed upload error based on raised exceptions."""
4035 self .exception = exception
41- super (UploadFailed , self ).__init__ ("Unable to upload profile: " + _traceback .format_exception (exception ))
42-
43-
44- def _get_api_key ():
45- legacy = _attr .from_env ("DD_PROFILING_API_KEY" , "" , str )()
46- if legacy :
47- deprecation .deprecation ("DD_PROFILING_API_KEY" , "Use DD_API_KEY" )
48- return legacy
49- return _attr .from_env ("DD_API_KEY" , "" , str )()
50-
51-
52- ENDPOINT_TEMPLATE = "https://intake.profile.{}/v1/input"
53-
54-
55- def _get_endpoint ():
56- legacy = _attr .from_env ("DD_PROFILING_API_URL" , "" , str )()
57- if legacy :
58- deprecation .deprecation ("DD_PROFILING_API_URL" , "Use DD_SITE" )
59- return legacy
60- site = _attr .from_env ("DD_SITE" , "datadoghq.com" , str )()
61- return ENDPOINT_TEMPLATE .format (site )
62-
63-
64- def _validate_enpoint (instance , attribute , value ):
65- if not value :
66- raise InvalidEndpoint ("Endpoint is empty" )
36+ msg = _traceback .format_exception (exception ) if msg is None else msg
37+ super (UploadFailed , self ).__init__ ("Unable to upload profile: " + msg )
6738
6839
6940@attr .s
7041class PprofHTTPExporter (pprof .PprofExporter ):
7142 """PProf HTTP exporter."""
7243
73- endpoint = attr .ib (factory = _get_endpoint , type = str , validator = _validate_enpoint )
74- api_key = attr .ib (factory = _get_api_key , type = str )
44+ endpoint = attr .ib ()
45+ api_key = attr .ib (default = None )
7546 timeout = attr .ib (factory = _attr .from_env ("DD_PROFILING_API_TIMEOUT" , 10 , float ), type = float )
7647 service = attr .ib (default = None )
7748 env = attr .ib (default = None )
@@ -138,16 +109,25 @@ def _get_tags(self, service):
138109 tags .update ({k : six .ensure_binary (v ) for k , v in user_tags .items ()})
139110 return tags
140111
112+ @staticmethod
113+ def _retry_on_http_5xx (exception ):
114+ if isinstance (exception , error .HTTPError ):
115+ return 500 <= exception .code < 600
116+ return True
117+
141118 def export (self , events , start_time_ns , end_time_ns ):
142119 """Export events to an HTTP endpoint.
143120
144121 :param events: The event dictionary from a `ddtrace.profiling.recorder.Recorder`.
145122 :param start_time_ns: The start time of recording.
146123 :param end_time_ns: The end time of recording.
147124 """
148- common_headers = {
149- "DD-API-KEY" : self .api_key .encode (),
150- }
125+ if self .api_key :
126+ headers = {
127+ "DD-API-KEY" : self .api_key .encode (),
128+ }
129+ else :
130+ headers = {}
151131
152132 profile = super (PprofHTTPExporter , self ).export (events , start_time_ns , end_time_ns )
153133 s = six .BytesIO ()
@@ -170,7 +150,6 @@ def export(self, events, start_time_ns, end_time_ns):
170150 service = self .service or os .path .basename (profile .string_table [profile .mapping [0 ].filename ])
171151
172152 content_type , body = self ._encode_multipart_formdata (fields , tags = self ._get_tags (service ),)
173- headers = common_headers .copy ()
174153 headers ["Content-Type" ] = content_type
175154
176155 # urllib uses `POST` if `data` is supplied (Python 2 version does not handle `method` kwarg)
@@ -180,12 +159,20 @@ def export(self, events, start_time_ns, end_time_ns):
180159 # Retry after 1s, 2s, 4s, 8s with some randomness
181160 wait = tenacity .wait_random_exponential (multiplier = 0.5 ),
182161 stop = tenacity .stop_after_delay (self .max_retry_delay ),
183- retry = tenacity .retry_if_exception_type (
184- (error .HTTPError , error .URLError , http_client .HTTPException , OSError , IOError )
185- ),
162+ retry = tenacity .retry_if_exception_type ((http_client .HTTPException , OSError , IOError ))
163+ & tenacity .retry_if_exception (self ._retry_on_http_5xx ),
186164 )
187165
188166 try :
189167 retry (request .urlopen , req , timeout = self .timeout )
190168 except tenacity .RetryError as e :
191169 raise UploadFailed (e .last_attempt .exception ())
170+ except error .HTTPError as e :
171+ if e .code == 404 and not self .api_key :
172+ msg = (
173+ "Datadog Agent is not accepting profiles. "
174+ "Agent-based profiling deployments require Datadog Agent >= 7.20"
175+ )
176+ else :
177+ msg = None
178+ raise UploadFailed (e , msg )
0 commit comments