1 /* 2 * Copyright 2022 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.gcp.observability; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.mockito.Mockito.mock; 21 import static org.mockito.Mockito.when; 22 23 import com.google.cloud.monitoring.v3.MetricServiceClient; 24 import com.google.cloud.monitoring.v3.MetricServiceClient.ListTimeSeriesPagedResponse; 25 import com.google.monitoring.v3.ListTimeSeriesRequest; 26 import com.google.monitoring.v3.ProjectName; 27 import com.google.monitoring.v3.TimeInterval; 28 import com.google.monitoring.v3.TimeSeries; 29 import com.google.protobuf.util.Timestamps; 30 import io.grpc.ManagedChannelBuilder; 31 import io.grpc.Server; 32 import io.grpc.ServerBuilder; 33 import io.grpc.StaticTestingClassLoader; 34 import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor; 35 import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor; 36 import io.grpc.gcp.observability.logging.GcpLogSink; 37 import io.grpc.gcp.observability.logging.Sink; 38 import io.grpc.testing.GrpcCleanupRule; 39 import io.grpc.testing.protobuf.SimpleServiceGrpc; 40 import java.io.IOException; 41 import java.util.Collections; 42 import java.util.Map; 43 import java.util.concurrent.TimeUnit; 44 import java.util.regex.Pattern; 45 import org.junit.ClassRule; 46 import org.junit.Ignore; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.junit.runners.JUnit4; 50 51 @RunWith(JUnit4.class) 52 public class MetricsTest { 53 54 @ClassRule 55 public static final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); 56 57 private static final String PROJECT_ID = "PROJECT"; 58 private static final String TEST_CLIENT_METHOD = "grpc.testing.SimpleService/UnaryRpc"; 59 private static final String CUSTOM_TAG_KEY = "Version"; 60 private static final String CUSTOM_TAG_VALUE = 61 String.format("C67J9A-%s", String.valueOf(System.currentTimeMillis())); 62 private static final Map<String, String> CUSTOM_TAGS = Collections.singletonMap(CUSTOM_TAG_KEY, 63 CUSTOM_TAG_VALUE); 64 65 private final StaticTestingClassLoader classLoader = 66 new StaticTestingClassLoader(getClass().getClassLoader(), 67 Pattern.compile("io\\.grpc\\..*|io\\.opencensus\\..*")); 68 69 /** 70 * End to end cloud monitoring test. 71 * 72 * <p>Ignoring test, because it calls external Cloud Monitoring APIs. To test cloud monitoring 73 * setup locally, 74 * 1. Set up Cloud auth credentials 75 * 2. Assign permissions to service account to write metrics to project specified by variable 76 * PROJECT_ID 77 * 3. Comment @Ignore annotation 78 * 4. This test is expected to pass when ran with above setup. This has been verified manually. 79 */ 80 @Ignore 81 @Test testMetricsExporter()82 public void testMetricsExporter() throws Exception { 83 Class<?> runnable = 84 classLoader.loadClass(MetricsTest.StaticTestingClassTestMetricsExporter.class.getName()); 85 ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); 86 } 87 88 public static final class StaticTestingClassTestMetricsExporter implements Runnable { 89 90 @Override run()91 public void run() { 92 Sink mockSink = mock(GcpLogSink.class); 93 ObservabilityConfig mockConfig = mock(ObservabilityConfig.class); 94 InternalLoggingChannelInterceptor.Factory mockChannelInterceptorFactory = 95 mock(InternalLoggingChannelInterceptor.Factory.class); 96 InternalLoggingServerInterceptor.Factory mockServerInterceptorFactory = 97 mock(InternalLoggingServerInterceptor.Factory.class); 98 99 when(mockConfig.isEnableCloudMonitoring()).thenReturn(true); 100 when(mockConfig.getProjectId()).thenReturn(PROJECT_ID); 101 102 try { 103 GcpObservability observability = 104 GcpObservability.grpcInit( 105 mockSink, mockConfig, mockChannelInterceptorFactory, mockServerInterceptorFactory); 106 observability.registerStackDriverExporter(PROJECT_ID, CUSTOM_TAGS); 107 108 Server server = 109 ServerBuilder.forPort(0) 110 .addService(new ObservabilityTestHelper.SimpleServiceImpl()) 111 .build() 112 .start(); 113 int port = cleanupRule.register(server).getPort(); 114 SimpleServiceGrpc.SimpleServiceBlockingStub stub = 115 SimpleServiceGrpc.newBlockingStub( 116 cleanupRule.register( 117 ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build())); 118 assertThat(ObservabilityTestHelper.makeUnaryRpcViaClientStub("buddy", stub)) 119 .isEqualTo("Hello buddy"); 120 // Adding sleep to ensure metrics are exported before querying cloud monitoring backend 121 TimeUnit.SECONDS.sleep(40); 122 123 // This checks Cloud monitoring for the new metrics that was just exported. 124 MetricServiceClient metricServiceClient = MetricServiceClient.create(); 125 // Restrict time to last 1 minute 126 long startMillis = System.currentTimeMillis() - ((60 * 1) * 1000); 127 TimeInterval interval = 128 TimeInterval.newBuilder() 129 .setStartTime(Timestamps.fromMillis(startMillis)) 130 .setEndTime(Timestamps.fromMillis(System.currentTimeMillis())) 131 .build(); 132 // Timeseries data 133 String metricsFilter = 134 String.format( 135 "metric.type=\"custom.googleapis.com/opencensus/grpc.io/client/completed_rpcs\"" 136 + " AND metric.labels.grpc_client_method=\"%s\"" 137 + " AND metric.labels.%s=%s", 138 TEST_CLIENT_METHOD, CUSTOM_TAG_KEY, CUSTOM_TAG_VALUE); 139 ListTimeSeriesRequest metricsRequest = 140 ListTimeSeriesRequest.newBuilder() 141 .setName(ProjectName.of(PROJECT_ID).toString()) 142 .setFilter(metricsFilter) 143 .setInterval(interval) 144 .build(); 145 ListTimeSeriesPagedResponse response = metricServiceClient.listTimeSeries(metricsRequest); 146 assertThat(response.iterateAll()).isNotEmpty(); 147 for (TimeSeries ts : response.iterateAll()) { 148 assertThat(ts.getMetric().getLabelsMap().get("grpc_client_method")) 149 .isEqualTo(TEST_CLIENT_METHOD); 150 assertThat(ts.getMetric().getLabelsMap().get("grpc_client_status")).isEqualTo("OK"); 151 assertThat(ts.getPoints(0).getValue().getInt64Value()).isEqualTo(1); 152 } 153 observability.close(); 154 } catch (IOException | InterruptedException e) { 155 throw new AssertionError("Exception while testing metrics", e); 156 } 157 } 158 } 159 } 160