Stats: Map deprecated RPC tags when record against new tags. (#1854)

diff --git a/impl_core/src/main/java/io/opencensus/implcore/stats/RecordUtils.java b/impl_core/src/main/java/io/opencensus/implcore/stats/RecordUtils.java
index e97c107..a9bba2c 100644
--- a/impl_core/src/main/java/io/opencensus/implcore/stats/RecordUtils.java
+++ b/impl_core/src/main/java/io/opencensus/implcore/stats/RecordUtils.java
@@ -17,6 +17,7 @@
 package io.opencensus.implcore.stats;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import io.opencensus.common.Function;
 import io.opencensus.common.Functions;
@@ -62,6 +63,19 @@
 
   @javax.annotation.Nullable @VisibleForTesting static final TagValue UNKNOWN_TAG_VALUE = null;
 
+  // TODO(songy23): remove the mapping once we completely remove the deprecated RPC constants.
+  @VisibleForTesting static final TagKey RPC_STATUS = TagKey.create("canonical_status");
+  @VisibleForTesting static final TagKey RPC_METHOD = TagKey.create("method");
+  @VisibleForTesting static final TagKey GRPC_CLIENT_STATUS = TagKey.create("grpc_client_status");
+  @VisibleForTesting static final TagKey GRPC_CLIENT_METHOD = TagKey.create("grpc_client_method");
+  @VisibleForTesting static final TagKey GRPC_SERVER_STATUS = TagKey.create("grpc_server_status");
+  @VisibleForTesting static final TagKey GRPC_SERVER_METHOD = TagKey.create("grpc_server_method");
+  private static final Map<TagKey, TagKey[]> RPC_TAG_MAPPINGS =
+      ImmutableMap.<TagKey, TagKey[]>builder()
+          .put(RPC_STATUS, new TagKey[] {GRPC_CLIENT_STATUS, GRPC_SERVER_STATUS})
+          .put(RPC_METHOD, new TagKey[] {GRPC_CLIENT_METHOD, GRPC_SERVER_METHOD})
+          .build();
+
   static Map<TagKey, TagValueWithMetadata> getTagMap(TagContext ctx) {
     if (ctx instanceof TagMapImpl) {
       return ((TagMapImpl) ctx).getTags();
@@ -83,8 +97,12 @@
     for (int i = 0; i < columns.size(); ++i) {
       TagKey tagKey = columns.get(i);
       if (!tags.containsKey(tagKey)) {
-        // replace not found key values by null.
-        tagValues.add(UNKNOWN_TAG_VALUE);
+        @javax.annotation.Nullable TagValue tagValue = UNKNOWN_TAG_VALUE;
+        TagKey[] newKeys = RPC_TAG_MAPPINGS.get(tagKey);
+        if (newKeys != null) {
+          tagValue = getTagValueForDeprecatedRpcTag(tags, newKeys);
+        }
+        tagValues.add(tagValue);
       } else {
         tagValues.add(tags.get(tagKey).getTagValue());
       }
@@ -92,6 +110,19 @@
     return tagValues;
   }
 
+  // TODO(songy23): remove the mapping once we completely remove the deprecated RPC constants.
+  @javax.annotation.Nullable
+  private static TagValue getTagValueForDeprecatedRpcTag(
+      Map<? extends TagKey, TagValueWithMetadata> tags, TagKey[] newKeys) {
+    for (TagKey newKey : newKeys) {
+      TagValueWithMetadata valueWithMetadata = tags.get(newKey);
+      if (valueWithMetadata != null) {
+        return valueWithMetadata.getTagValue();
+      }
+    }
+    return UNKNOWN_TAG_VALUE;
+  }
+
   /**
    * Create an empty {@link MutableAggregation} based on the given {@link Aggregation}.
    *
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java
index 5a70a19..3328166 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/RecordUtilsTest.java
@@ -62,10 +62,22 @@
   private static final TagKey METHOD = TagKey.create("method");
   private static final TagValue CALLER_V = TagValue.create("some caller");
   private static final TagValue METHOD_V = TagValue.create("some method");
+  private static final TagValue METHOD_V_2 = TagValue.create("some other method");
+  private static final TagValue METHOD_V_3 = TagValue.create("the third method");
+  private static final TagValue STATUS_V = TagValue.create("ok");
+  private static final TagValue STATUS_V_2 = TagValue.create("error");
   private static final TagValueWithMetadata CALLER_V_WITH_MD =
       TagValueWithMetadata.create(CALLER_V, METADATA_UNLIMITED_PROPAGATION);
   private static final TagValueWithMetadata METHOD_V_WITH_MD =
       TagValueWithMetadata.create(METHOD_V, METADATA_UNLIMITED_PROPAGATION);
+  private static final TagValueWithMetadata METHOD_V_2_WITH_MD =
+      TagValueWithMetadata.create(METHOD_V_2, METADATA_UNLIMITED_PROPAGATION);
+  private static final TagValueWithMetadata METHOD_V_3_WITH_MD =
+      TagValueWithMetadata.create(METHOD_V_3, METADATA_UNLIMITED_PROPAGATION);
+  private static final TagValueWithMetadata STATUS_V_WITH_MD =
+      TagValueWithMetadata.create(STATUS_V, METADATA_UNLIMITED_PROPAGATION);
+  private static final TagValueWithMetadata STATUS_V_2_WITH_MD =
+      TagValueWithMetadata.create(STATUS_V_2, METADATA_UNLIMITED_PROPAGATION);
 
   @Test
   public void testConstants() {
@@ -84,6 +96,75 @@
   }
 
   @Test
+  public void testGetTagValues_MapDeprecatedRpcTag() {
+    List<TagKey> columns = Arrays.asList(RecordUtils.RPC_STATUS, RecordUtils.RPC_METHOD);
+    Map<TagKey, TagValueWithMetadata> tags =
+        ImmutableMap.of(
+            RecordUtils.GRPC_CLIENT_METHOD, METHOD_V_WITH_MD,
+            RecordUtils.GRPC_CLIENT_STATUS, STATUS_V_WITH_MD);
+
+    assertThat(RecordUtils.getTagValues(tags, columns))
+        .containsExactly(STATUS_V, METHOD_V)
+        .inOrder();
+  }
+
+  @Test
+  public void testGetTagValues_MapDeprecatedRpcTag_WithServerTag() {
+    List<TagKey> columns = Arrays.asList(RecordUtils.RPC_STATUS, RecordUtils.RPC_METHOD);
+    Map<TagKey, TagValueWithMetadata> tags =
+        ImmutableMap.of(
+            RecordUtils.GRPC_SERVER_METHOD, METHOD_V_WITH_MD,
+            RecordUtils.GRPC_SERVER_STATUS, STATUS_V_WITH_MD);
+
+    assertThat(RecordUtils.getTagValues(tags, columns))
+        .containsExactly(STATUS_V, METHOD_V)
+        .inOrder();
+  }
+
+  @Test
+  public void testGetTagValues_MapDeprecatedRpcTag_PreferClientTag() {
+    List<TagKey> columns = Arrays.asList(RecordUtils.RPC_STATUS, RecordUtils.RPC_METHOD);
+    Map<TagKey, TagValueWithMetadata> tags =
+        ImmutableMap.of(
+            RecordUtils.GRPC_SERVER_METHOD, METHOD_V_WITH_MD,
+            RecordUtils.GRPC_SERVER_STATUS, STATUS_V_WITH_MD,
+            RecordUtils.GRPC_CLIENT_METHOD, METHOD_V_2_WITH_MD,
+            RecordUtils.GRPC_CLIENT_STATUS, STATUS_V_2_WITH_MD);
+
+    // When both client and server new tags are present, client values take precedence.
+    assertThat(RecordUtils.getTagValues(tags, columns))
+        .containsExactly(STATUS_V_2, METHOD_V_2)
+        .inOrder();
+  }
+
+  @Test
+  public void testGetTagValues_WithOldMethodTag() {
+    List<TagKey> columns = Arrays.asList(RecordUtils.RPC_METHOD);
+    Map<TagKey, TagValueWithMetadata> tags =
+        ImmutableMap.of(
+            RecordUtils.GRPC_SERVER_METHOD, METHOD_V_WITH_MD,
+            RecordUtils.GRPC_CLIENT_METHOD, METHOD_V_2_WITH_MD,
+            RecordUtils.RPC_METHOD, METHOD_V_3_WITH_MD);
+
+    // When the old "method" tag is set, it always takes precedence.
+    assertThat(RecordUtils.getTagValues(tags, columns)).containsExactly(METHOD_V_3).inOrder();
+  }
+
+  @Test
+  public void testGetTagValues_WithNewTags() {
+    List<TagKey> columns =
+        Arrays.asList(RecordUtils.GRPC_CLIENT_METHOD, RecordUtils.GRPC_SERVER_METHOD);
+    Map<TagKey, TagValueWithMetadata> tags =
+        ImmutableMap.of(
+            RecordUtils.GRPC_SERVER_METHOD, METHOD_V_WITH_MD,
+            RecordUtils.GRPC_CLIENT_METHOD, METHOD_V_2_WITH_MD);
+
+    assertThat(RecordUtils.getTagValues(tags, columns))
+        .containsExactly(METHOD_V_2, METHOD_V)
+        .inOrder();
+  }
+
+  @Test
   public void createMutableAggregation() {
     BucketBoundaries bucketBoundaries = BucketBoundaries.create(Arrays.asList(-1.0, 0.0, 1.0));
 
diff --git a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java
index c87140c..1f1e470 100644
--- a/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java
+++ b/impl_core/src/test/java/io/opencensus/implcore/stats/StatsRecorderImplTest.java
@@ -296,6 +296,30 @@
   }
 
   @Test
+  public void record_MapDeprecatedRpcConstants() {
+    View view =
+        View.create(
+            VIEW_NAME,
+            "description",
+            MEASURE_DOUBLE,
+            Sum.create(),
+            Arrays.asList(RecordUtils.RPC_METHOD));
+
+    viewManager.registerView(view);
+    MeasureMap statsRecord = statsRecorder.newMeasureMap().put(MEASURE_DOUBLE, 1.0);
+    statsRecord.record(new SimpleTagContext(Tag.create(RecordUtils.GRPC_CLIENT_METHOD, VALUE)));
+    ViewData viewData = viewManager.getView(VIEW_NAME);
+
+    // There should be two entries.
+    StatsTestUtil.assertAggregationMapEquals(
+        viewData.getAggregationMap(),
+        ImmutableMap.of(
+            Arrays.asList(VALUE),
+            StatsTestUtil.createAggregationData(Sum.create(), MEASURE_DOUBLE, 1.0)),
+        1e-6);
+  }
+
+  @Test
   @SuppressWarnings("deprecation")
   public void record_StatsDisabled() {
     View view =