Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gcp-observability: Populate global interceptors from observability #9309

Merged

Conversation

DNVindhya
Copy link
Contributor

This PR populates Global Interceptors and Stream Tracers based on observability configuration.
Also, adds stackdriver exporter required for exporting metric and trace to Google cloud.

CC @sanjaypujare

observabilityConfig.getDestinationProjectId(),
globalLoggingTags.getLocationTags(),
globalLoggingTags.getCustomTags(),
observabilityConfig.getFlushMessageCount());
LogHelper helper = new LogHelper(sink, TimeProvider.SYSTEM_TIME_PROVIDER);
ConfigFilterHelper configFilterHelper = ConfigFilterHelper.factory(observabilityConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are passing observabilityConfig to the non-static grpcInit we can construct configFilterHelper in there? Unless I am missing something?

May be the same thing for many other objects being constructed here and passed to the non-static grpcInit such as sink helper etc?

The reason I am saying is because the non-static grpcInit is unit tested/unit-testable so the more code it has the better it is. But we need to look into it carefully

@DNVindhya DNVindhya marked this pull request as ready for review June 27, 2022 21:27

/**
* Initialize grpc-observability.
*
* @throws ProviderNotFoundException if no underlying channel/server provider is available.
*/
public static synchronized GcpObservability grpcInit() throws IOException {
public static synchronized GcpObservability grpcInit() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which other exception type made this necessary from IOException to Exception? Can we just add the additional types (if practical) instead of moving up to Exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stackdriver<Stats/Trace>Exporter throws IllegalStateException. So I will add IllegalStateException to the list of exceptions instead of generic Exception.

try {
StackdriverStatsExporter.createAndRegister(statsConfigurationBuilder.build());
} catch (IOException e) {
throw new IOException("Failed to register Stackdriver stats exporter, " + e.getMessage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unusual: catching an IOException and throwing another IOException with just the message modified. Wouldn't the original message be sufficient - so no need to do this? Same for line 181

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I realized about the IllegalStateException: so we will get that for duplicate registration attempt. Ideally that should not happen because of the guard around grpcInit but a user could have registered the exporter on their own. I guess the only way to resolve that is to document that in the UG? Unless there is a use-case for the user to have a separate registration for their own purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed catching and re-throwing exceptions.

Sink sink = new GcpLogSink(observabilityConfig.getDestinationProjectId(),
globalLoggingTags.getLocationTags(), globalLoggingTags.getCustomTags(),
observabilityConfig.getFlushMessageCount());
Sink sink =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is formatting only change with no real code change - will be good to avoid it to minimize the amount of code showing up as different

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory());
}

if (!clientInterceptors.isEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the latest change in the other PR, you should remove this check in the if. Even if empty you should now set the interceptors or tracers in the GlobalInterceptors.

Hopefully you can unit test this scenario since it's important for correct functioning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit test to verify empty lists are returned when grpcInit is called and none of the observability features are enabled.

RpcViews.registerAllGrpcViews();
StackdriverStatsConfiguration.Builder statsConfigurationBuilder =
StackdriverStatsConfiguration.builder();
if (!Strings.isNullOrEmpty(projectId)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can understand null, do we want to include empty to have the same semantics? 2 Qs

  • will the ObservabilityConfig ever return empty string for the project ID?
  • if it does do we want special or different semantics for empty project ID?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ObservabilityConfig will not return empty, if the value does not exist in config it returns null instead.
Updated the check.

"'global_trace_sampling_rate' needs to be between 0.0 and 1.0");
this.sampler = new Sampler(samplingRate);
"'global_trace_sampling_rate' needs to be between [0.0, 1.0]");
if (samplingRate == 1.0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparison of double numbers is tricky and because of precision (and conversion from String to double) issues this equality check may not go through. Has this been unit tested? https://www.baeldung.com/java-comparing-doubles#comparing_in_plain_Java

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. PTAL

@DNVindhya DNVindhya force-pushed the gcp-o11y-populate-o11y-interceptors branch from 88efda3 to 6e713a0 Compare July 1, 2022 23:39
}
double epsilon = 1e-6;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make this a class final constant with some comment (e.g. why 1e-6)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"only one of 'global_trace_sampler' or 'global_trace_sampling_rate' can be specified");
if (sampler != null) {
this.sampler = new Sampler(SamplerType.valueOf(sampler.toUpperCase()));
if (enableCloudTracing && samplingRate == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler/better if you just remove the check for enableCloudTracing and do it as follows (with epsilon defined as a class constant)?:

      if (samplingRate == null) {
        this.sampler = Samplers.probabilitySampler(0.0);
      } else {
        checkArgument(
            samplingRate >= 0.0 && samplingRate <= 1.0,
            "'global_trace_sampling_rate' needs to be between [0.0, 1.0]");
        // Using alwaysSample() instead of probabilitySampler() because according to
        // {@link io.opencensus.trace.samplers.ProbabilitySampler#shouldSample}
        // there is a (very) small chance of *not* sampling if probability = 1.00.
        if (Math.abs(1 - samplingRate) < EPSILON) {
          this.sampler = Samplers.alwaysSample();
        } else {
          this.sampler = Samplers.probabilitySampler(samplingRate);
        }
      }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.InternalGlobalInterceptors;
// import io.grpc.ManagedChannelProvider;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the commented line?

// Using alwaysSample() instead of probabilitySampler() because according to
// {@link io.opencensus.trace.samplers.ProbabilitySampler#shouldSample}
// there is a (very) small chance of *not* sampling if probability = 1.00.
if (Math.abs(1 - samplingRate) < epsilon) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since samplingRate is always going to be <= 1.0 (because of the check on line 112) the Math.abs is strictly unnecessary. But I am okay to leave it there as a generalized double equality comparison logic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed Math.abs

import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
// import io.grpc.ServerProvider;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another commented import line? Any reason to leave them here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an oversight. Removed both.

mock(InternalLoggingServerInterceptor.Factory.class);
when(serverInterceptorFactory.create()).thenReturn(serverInterceptor);

try (GcpObservability observability =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

observability is unused - I am surprised you don't get a compile or link warning. If that was the case you can rename this var to unused or use the SuppressWarning annotation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Copy link
Contributor

@sanjaypujare sanjaypujare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments and it will be good to get them addressed but otherwise looks good.

@sanjaypujare sanjaypujare merged commit ef89bd3 into grpc:master Jul 14, 2022
@DNVindhya DNVindhya deleted the gcp-o11y-populate-o11y-interceptors branch July 14, 2022 17:37
larry-safran pushed a commit to larry-safran/grpc-java that referenced this pull request Jul 14, 2022
…rpc#9309)

* Populate global interceptors from observability and added stackdriver exporters
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants