@@ -72,9 +72,11 @@ class HeaderInterceptor implements ClientInterceptor {
7272 DatabaseName .of ("undefined-project" , "undefined-instance" , "undefined-database" );
7373 private static final Metadata .Key <String > SERVER_TIMING_HEADER_KEY =
7474 Metadata .Key .of ("server-timing" , Metadata .ASCII_STRING_MARSHALLER );
75- private static final String SERVER_TIMING_HEADER_PREFIX = "gfet4t7; dur= " ;
75+ private static final String GFE_TIMING_HEADER = "gfet4t7" ;
7676 private static final Metadata .Key <String > GOOGLE_CLOUD_RESOURCE_PREFIX_KEY =
7777 Metadata .Key .of ("google-cloud-resource-prefix" , Metadata .ASCII_STRING_MARSHALLER );
78+ private static final Pattern SERVER_TIMING_PATTERN =
79+ Pattern .compile ("(?<metricName>[a-zA-Z0-9_-]+);\\ s*dur=(?<duration>\\ d+)" );
7880 private static final Pattern GOOGLE_CLOUD_RESOURCE_PREFIX_PATTERN =
7981 Pattern .compile (
8082 ".*projects/(?<project>\\ p{ASCII}[^/]*)(/instances/(?<instance>\\ p{ASCII}[^/]*))?(/databases/(?<database>\\ p{ASCII}[^/]*))?" );
@@ -121,25 +123,31 @@ public void start(Listener<RespT> responseListener, Metadata headers) {
121123 Span span = Span .current ();
122124 DatabaseName databaseName = extractDatabaseName (headers );
123125 String key = databaseName + method .getFullMethodName ();
124- TagContext tagContext = getTagContext (key , method .getFullMethodName (), databaseName );
125- Attributes attributes =
126- getMetricAttributes (key , method .getFullMethodName (), databaseName );
127- Map <String , String > commonBuiltInMetricAttributes =
128- getCommonBuiltInMetricAttributes (key , databaseName );
126+
127+ TagContext openCensusTagContext =
128+ getOpenCensusTagContext (key , method .getFullMethodName (), databaseName );
129+ Attributes customMetricAttributes =
130+ buildCustomMetricAttributes (key , method .getFullMethodName (), databaseName );
131+ Map <String , String > builtInMetricAttributes =
132+ buildBuiltInMetricAttributes (key , databaseName );
129133 super .start (
130134 new SimpleForwardingClientCallListener <RespT >(responseListener ) {
131135 @ Override
132136 public void onHeaders (Metadata metadata ) {
133- Boolean isDirectPathUsed =
134- isDirectPathUsed (getAttributes ().get (Grpc .TRANSPORT_ATTR_REMOTE_ADDR ));
135- addBuiltInMetricAttributes (
136- compositeTracer , commonBuiltInMetricAttributes , isDirectPathUsed );
137- processHeader (
137+ if (compositeTracer != null ) {
138+ builtInMetricAttributes .put (
139+ BuiltInMetricsConstant .DIRECT_PATH_USED_KEY .getKey (),
140+ Boolean .toString (
141+ isDirectPathUsed (
142+ getAttributes ().get (Grpc .TRANSPORT_ATTR_REMOTE_ADDR ))));
143+ compositeTracer .addAttributes (builtInMetricAttributes );
144+ }
145+ processServerTimingHeader (
138146 metadata ,
139- tagContext ,
140- attributes ,
147+ openCensusTagContext ,
148+ customMetricAttributes ,
141149 span ,
142- getBuiltInMetricAttributes ( commonBuiltInMetricAttributes , isDirectPathUsed ) );
150+ builtInMetricAttributes );
143151 super .onHeaders (metadata );
144152 }
145153 },
@@ -152,37 +160,63 @@ public void onHeaders(Metadata metadata) {
152160 };
153161 }
154162
155- private void processHeader (
163+ private void processServerTimingHeader (
156164 Metadata metadata ,
157165 TagContext tagContext ,
158166 Attributes attributes ,
159167 Span span ,
160168 Map <String , String > builtInMetricsAttributes ) {
161169 MeasureMap measureMap = STATS_RECORDER .newMeasureMap ();
162170 String serverTiming = metadata .get (SERVER_TIMING_HEADER_KEY );
163- if (serverTiming != null && serverTiming .startsWith (SERVER_TIMING_HEADER_PREFIX )) {
164- try {
165- long latency = Long .parseLong (serverTiming .substring (SERVER_TIMING_HEADER_PREFIX .length ()));
166- measureMap .put (SPANNER_GFE_LATENCY , latency );
171+ try {
172+ // Previous implementation parsed the GFE latency directly using:
173+ // long latency = Long.parseLong(serverTiming.substring("gfet4t7; dur=".length()));
174+ // This approach assumed the serverTiming header contained exactly one metric "gfet4t7".
175+ // If additional metrics were introduced in the header, older versions of the library
176+ // would fail to parse it correctly. To make the parsing more robust, the logic has been
177+ // updated to handle multiple metrics gracefully.
178+
179+ Map <String , Long > serverTimingMetrics = parseServerTimingHeader (serverTiming );
180+ if (serverTimingMetrics .containsKey (GFE_TIMING_HEADER )) {
181+ long gfeLatency = serverTimingMetrics .get (GFE_TIMING_HEADER );
182+
183+ measureMap .put (SPANNER_GFE_LATENCY , gfeLatency );
167184 measureMap .put (SPANNER_GFE_HEADER_MISSING_COUNT , 0L );
168185 measureMap .record (tagContext );
169186
170- spannerRpcMetrics .recordGfeLatency (latency , attributes );
187+ spannerRpcMetrics .recordGfeLatency (gfeLatency , attributes );
171188 spannerRpcMetrics .recordGfeHeaderMissingCount (0L , attributes );
172- builtInOpenTelemetryMetricsRecorder .recordGFELatency (latency , builtInMetricsAttributes );
189+
190+ builtInOpenTelemetryMetricsRecorder .recordGFELatency (gfeLatency , builtInMetricsAttributes );
173191
174192 if (span != null ) {
175- span .setAttribute ("gfe_latency" , String .valueOf (latency ));
193+ span .setAttribute ("gfe_latency" , String .valueOf (gfeLatency ));
176194 }
177- } catch (NumberFormatException e ) {
178- LOGGER .log (LEVEL , "Invalid server-timing object in header: {}" , serverTiming );
195+ } else {
196+ measureMap .put (SPANNER_GFE_HEADER_MISSING_COUNT , 1L ).record (tagContext );
197+ spannerRpcMetrics .recordGfeHeaderMissingCount (1L , attributes );
179198 }
180- } else {
181- spannerRpcMetrics .recordGfeHeaderMissingCount (1L , attributes );
182- measureMap .put (SPANNER_GFE_HEADER_MISSING_COUNT , 1L ).record (tagContext );
199+ } catch (NumberFormatException e ) {
200+ LOGGER .log (LEVEL , "Invalid server-timing object in header: {}" , serverTiming );
183201 }
184202 }
185203
204+ private Map <String , Long > parseServerTimingHeader (String serverTiming ) {
205+ Map <String , Long > serverTimingMetrics = new HashMap <>();
206+ if (serverTiming != null ) {
207+ Matcher matcher = SERVER_TIMING_PATTERN .matcher (serverTiming );
208+ while (matcher .find ()) {
209+ String metricName = matcher .group ("metricName" );
210+ String durationStr = matcher .group ("duration" );
211+
212+ if (metricName != null && durationStr != null ) {
213+ serverTimingMetrics .put (metricName , Long .valueOf (durationStr ));
214+ }
215+ }
216+ }
217+ return serverTimingMetrics ;
218+ }
219+
186220 private DatabaseName extractDatabaseName (Metadata headers ) throws ExecutionException {
187221 String googleResourcePrefix = headers .get (GOOGLE_CLOUD_RESOURCE_PREFIX_KEY );
188222 if (googleResourcePrefix != null ) {
@@ -211,7 +245,7 @@ private DatabaseName extractDatabaseName(Metadata headers) throws ExecutionExcep
211245 return UNDEFINED_DATABASE_NAME ;
212246 }
213247
214- private TagContext getTagContext (String key , String method , DatabaseName databaseName )
248+ private TagContext getOpenCensusTagContext (String key , String method , DatabaseName databaseName )
215249 throws ExecutionException {
216250 return tagsCache .get (
217251 key ,
@@ -225,8 +259,8 @@ private TagContext getTagContext(String key, String method, DatabaseName databas
225259 .build ());
226260 }
227261
228- private Attributes getMetricAttributes ( String key , String method , DatabaseName databaseName )
229- throws ExecutionException {
262+ private Attributes buildCustomMetricAttributes (
263+ String key , String method , DatabaseName databaseName ) throws ExecutionException {
230264 return attributesCache .get (
231265 key ,
232266 () -> {
@@ -240,8 +274,8 @@ private Attributes getMetricAttributes(String key, String method, DatabaseName d
240274 });
241275 }
242276
243- private Map <String , String > getCommonBuiltInMetricAttributes (
244- String key , DatabaseName databaseName ) throws ExecutionException {
277+ private Map <String , String > buildBuiltInMetricAttributes ( String key , DatabaseName databaseName )
278+ throws ExecutionException {
245279 return builtInAttributesCache .get (
246280 key ,
247281 () -> {
@@ -256,24 +290,6 @@ private Map<String, String> getCommonBuiltInMetricAttributes(
256290 });
257291 }
258292
259- private Map <String , String > getBuiltInMetricAttributes (
260- Map <String , String > commonBuiltInMetricsAttributes , Boolean isDirectPathUsed ) {
261- Map <String , String > builtInMetricAttributes = new HashMap <>(commonBuiltInMetricsAttributes );
262- builtInMetricAttributes .put (
263- BuiltInMetricsConstant .DIRECT_PATH_USED_KEY .getKey (), Boolean .toString (isDirectPathUsed ));
264- return builtInMetricAttributes ;
265- }
266-
267- private void addBuiltInMetricAttributes (
268- CompositeTracer compositeTracer ,
269- Map <String , String > commonBuiltInMetricsAttributes ,
270- Boolean isDirectPathUsed ) {
271- if (compositeTracer != null ) {
272- compositeTracer .addAttributes (
273- getBuiltInMetricAttributes (commonBuiltInMetricsAttributes , isDirectPathUsed ));
274- }
275- }
276-
277293 private Boolean isDirectPathUsed (SocketAddress remoteAddr ) {
278294 if (remoteAddr instanceof InetSocketAddress ) {
279295 InetAddress inetAddress = ((InetSocketAddress ) remoteAddr ).getAddress ();
0 commit comments