11import json
22import logging
33import random
4- import re
54import urllib .parse
65from base64 import b64encode
76from decimal import Decimal
87from io import StringIO
9- from typing import Optional , Tuple
8+ from typing import Optional , Tuple , Pattern
109
1110import coverage
1211import requests
@@ -43,7 +42,7 @@ def __init__(
4342 self ,
4443 cov_storage : CodecovCoverageStorageManager ,
4544 sample_rate : Decimal ,
46- name_regex : re . Pattern = None ,
45+ name_regex : Pattern = None ,
4746 ):
4847 self ._cov_storage = cov_storage
4948 self ._sample_rate = sample_rate
@@ -71,11 +70,13 @@ def __init__(
7170 repository_token : str ,
7271 profiling_identifier : str ,
7372 codecov_endpoint : str ,
73+ untracked_export_rate : float ,
7474 ):
7575 self ._cov_storage = cov_storage
7676 self ._repository_token = repository_token
7777 self ._profiling_identifier = profiling_identifier
7878 self ._codecov_endpoint = codecov_endpoint
79+ self ._untracked_export_rate = untracked_export_rate
7980
8081 def _load_codecov_dict (self , span , cov ):
8182 k = StringIO ()
@@ -92,17 +93,20 @@ def _load_codecov_dict(self, span, cov):
9293 return coverage_dict
9394
9495 def export (self , spans ):
95- data = []
96+ tracked_spans = []
9697 untracked_spans = []
9798 for span in spans :
9899 span_id = span .context .span_id
99100 cov = self ._cov_storage .pop_cov_for_span (span_id )
100101 s = json .loads (span .to_json ())
101102 if cov is not None :
102103 s ["codecov" ] = self ._load_codecov_dict (span , cov )
103- data .append (s )
104+ tracked_spans .append (s )
104105 else :
105- untracked_spans .append (s )
106+ if random .random () < self ._untracked_export_rate :
107+ untracked_spans .append (s )
108+ if not tracked_spans :
109+ return SpanExportResult .SUCCESS
106110 url = urllib .parse .urljoin (self ._codecov_endpoint , "/profiling/uploads" )
107111 res = requests .post (
108112 url ,
@@ -118,7 +122,7 @@ def export(self, spans):
118122 requests .put (
119123 location ,
120124 headers = {"Content-Type" : "application/txt" },
121- data = json .dumps ({"spans" : data , "untracked" : untracked_spans }).encode (),
125+ data = json .dumps ({"spans" : tracked_spans , "untracked" : untracked_spans }).encode (),
122126 )
123127 return SpanExportResult .SUCCESS
124128
@@ -127,7 +131,7 @@ def get_codecov_opentelemetry_instances(
127131 repository_token : str ,
128132 profiling_identifier : str ,
129133 sample_rate : float ,
130- name_regex : Optional [re . Pattern ],
134+ name_regex : Optional [Pattern ],
131135 codecov_endpoint : str = None ,
132136 writeable_folder : str = None ,
133137) -> Tuple [CodecovCoverageGenerator , CoverageExporter ]:
@@ -139,7 +143,7 @@ def get_codecov_opentelemetry_instances(
139143 repository_token (str): The profiling-capable authentication token
140144 profiling_identifier (str): The identifier for what profiling one is doing
141145 sample_rate (float): The sampling rate for codecov
142- name_regex (Optional[re. Pattern]): A regex to filter which spans should be
146+ name_regex (Optional[Pattern]): A regex to filter which spans should be
143147 sampled
144148 codecov_endpoint (str, optional): For configuring the endpoint in case
145149 the user is in enterprise (not supported yet). Default is "https://api.codecov.io/"
@@ -151,7 +155,13 @@ def get_codecov_opentelemetry_instances(
151155 codecov_endpoint = "https://api.codecov.io"
152156 manager = CodecovCoverageStorageManager (writeable_folder )
153157 generator = CodecovCoverageGenerator (manager , sample_rate , name_regex )
158+ # untracked rate set to make it so we export roughly as many tracked and untracked spans
159+ untracked_export_rate = sample_rate / (1 - sample_rate ) if sample_rate < 1 else 0
154160 exporter = CoverageExporter (
155- manager , repository_token , profiling_identifier , codecov_endpoint
161+ manager ,
162+ repository_token ,
163+ profiling_identifier ,
164+ codecov_endpoint ,
165+ untracked_export_rate ,
156166 )
157167 return (generator , exporter )
0 commit comments