This is an automated email from the ASF dual-hosted git repository.
scott pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new f41b2ef [BEAM-6161] Introduce PCollectionConsumerRegistry and add
ElementCount counters to the java SDK
new fb13cb1 Merge pull request #7272: [BEAM-6161] Introduce
PCollectionConsumerRegistry and add ElementCoun…
f41b2ef is described below
commit f41b2ef319d396479d083046280500ad1cbe0be6
Author: Alex Amato <[email protected]>
AuthorDate: Wed Dec 12 17:37:45 2018 -0800
[BEAM-6161] Introduce PCollectionConsumerRegistry and add ElementCount
counters to the java SDK
---
.../fn-execution/src/main/proto/beam_fn_api.proto | 10 +-
.../beam/runners/core/metrics/LabeledMetrics.java | 35 ++++++
.../runners/core/metrics/MetricsContainerImpl.java | 37 ++++--
.../core/metrics/MonitoringInfoMetricName.java | 124 ++++++++++++++++++++
.../core/metrics/SimpleMonitoringInfoBuilder.java | 33 +++++-
.../core/metrics/SpecMonitoringInfoValidator.java | 1 +
.../runners/core/metrics/LabeledMetricsTest.java | 76 ++++++++++++
.../core/metrics/MetricsContainerImplTest.java | 53 +++++++++
.../core/metrics/MonitoringInfoMetricNameTest.java | 127 +++++++++++++++++++++
.../metrics/SimpleMonitoringInfoBuilderTest.java | 8 +-
.../metrics/SpecMonitoringInfoValidatorTest.java | 2 +-
.../fnexecution/control/RemoteExecutionTest.java | 78 +++++++++++--
.../apache/beam/sdk/metrics/DelegatingCounter.java | 64 +++++++++++
.../org/apache/beam/sdk/metrics/MetricResult.java | 5 +-
.../java/org/apache/beam/sdk/metrics/Metrics.java | 41 -------
.../beam/fn/harness/BeamFnDataReadRunner.java | 25 ++--
.../beam/fn/harness/BeamFnDataWriteRunner.java | 7 +-
.../beam/fn/harness/BoundedSourceRunner.java | 10 +-
.../org/apache/beam/fn/harness/CombineRunners.java | 21 ++--
.../fn/harness/DoFnPTransformRunnerFactory.java | 19 +--
.../org/apache/beam/fn/harness/FlattenRunner.java | 15 +--
.../org/apache/beam/fn/harness/MapFnRunners.java | 20 ++--
.../beam/fn/harness/PTransformRunnerFactory.java | 8 +-
.../fn/harness/control/ProcessBundleHandler.java | 21 ++--
.../harness/data/ElementCountFnDataReceiver.java | 54 +++++++++
.../harness/data/PCollectionConsumerRegistry.java | 103 +++++++++++++++++
.../beam/fn/harness/AssignWindowsRunnerTest.java | 13 +--
.../beam/fn/harness/BeamFnDataReadRunnerTest.java | 13 ++-
.../beam/fn/harness/BeamFnDataWriteRunnerTest.java | 8 +-
.../beam/fn/harness/BoundedSourceRunnerTest.java | 10 +-
.../apache/beam/fn/harness/CombineRunnersTest.java | 31 +++--
.../apache/beam/fn/harness/FlattenRunnerTest.java | 26 ++---
.../beam/fn/harness/FnApiDoFnRunnerTest.java | 106 ++++++++---------
.../apache/beam/fn/harness/MapFnRunnersTest.java | 24 ++--
.../harness/control/ProcessBundleHandlerTest.java | 17 ++-
.../data/ElementCountFnDataReceiverTest.java | 70 ++++++++++++
.../data/PCollectionConsumerRegistryTest.java | 88 ++++++++++++++
37 files changed, 1111 insertions(+), 292 deletions(-)
diff --git a/model/fn-execution/src/main/proto/beam_fn_api.proto
b/model/fn-execution/src/main/proto/beam_fn_api.proto
index 623f23d..378de46 100644
--- a/model/fn-execution/src/main/proto/beam_fn_api.proto
+++ b/model/fn-execution/src/main/proto/beam_fn_api.proto
@@ -322,11 +322,6 @@ message MonitoringInfoSpec {
repeated Annotation annotations = 4;
}
-extend google.protobuf.EnumValueOptions {
- // Enum extension to store the MonitoringInfoSpecs.
- MonitoringInfoSpec monitoring_info_spec = 207174266;
-}
-
// The key name and value string of MonitoringInfo annotations.
message Annotation {
string key = 1;
@@ -348,7 +343,7 @@ message MonitoringInfoSpecs {
ELEMENT_COUNT = 1 [(monitoring_info_spec) = {
urn: "beam:metric:element_count:v1",
type_urn: "beam:metrics:sum_int_64",
- required_labels: [ "PTRANSFORM", "PCOLLECTION" ],
+ required_labels: [ "PCOLLECTION" ],
annotations: [ {
key: "description",
value: "The total elements output to a Pcollection by a PTransform."
@@ -411,6 +406,9 @@ message MonitoringInfoLabelProps {
// specifications, constants, etc.
extend google.protobuf.EnumValueOptions {
MonitoringInfoLabelProps label_props = 127337796; // From: commit 0x7970544.
+
+ // Enum extension to store the MonitoringInfoSpecs.
+ MonitoringInfoSpec monitoring_info_spec = 207174266;
}
message MonitoringInfo {
diff --git
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/LabeledMetrics.java
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/LabeledMetrics.java
new file mode 100644
index 0000000..bf6295b
--- /dev/null
+++
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/LabeledMetrics.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.runners.core.metrics;
+
+import org.apache.beam.sdk.metrics.Counter;
+import org.apache.beam.sdk.metrics.DelegatingCounter;
+
+/**
+ * Define a metric on the current MetricContainer with a specific URN and a
set of labels. This is a
+ * more convenient way to collect the necessary fields to repackage the metric
into a MonitoringInfo
+ * proto later. Intended for internal use only (SDK and RunnerHarness
developement).
+ */
+public class LabeledMetrics {
+ /**
+ * Create a metric that can be incremented and decremented, and is
aggregated by taking the sum.
+ */
+ public static Counter counter(MonitoringInfoMetricName metricName) {
+ return new DelegatingCounter(metricName);
+ }
+}
diff --git
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MetricsContainerImpl.java
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MetricsContainerImpl.java
index bd16121..fa2b834 100644
---
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MetricsContainerImpl.java
+++
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MetricsContainerImpl.java
@@ -22,6 +22,7 @@ import static
org.apache.beam.vendor.guava.v20_0.com.google.common.base.Precondi
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Map;
+import java.util.Map.Entry;
import javax.annotation.Nullable;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfo;
import org.apache.beam.runners.core.construction.metrics.MetricKey;
@@ -138,19 +139,39 @@ public class MetricsContainerImpl implements
Serializable, MetricsContainer {
extractUpdates(counters), extractUpdates(distributions),
extractUpdates(gauges));
}
+ /**
+ * @param metricUpdate
+ * @return The MonitoringInfo generated from the metricUpdate.
+ */
+ private MonitoringInfo counterUpdateToMonitoringInfo(MetricUpdate<Long>
metricUpdate) {
+ SimpleMonitoringInfoBuilder builder = new
SimpleMonitoringInfoBuilder(true);
+ MetricName metricName = metricUpdate.getKey().metricName();
+ if (metricName instanceof MonitoringInfoMetricName) {
+ MonitoringInfoMetricName monitoringInfoName = (MonitoringInfoMetricName)
metricName;
+ // Represents a specific MonitoringInfo for a specific URN.
+ builder.setUrn(monitoringInfoName.getUrn());
+ for (Entry<String, String> e :
monitoringInfoName.getLabels().entrySet()) {
+ builder.setLabel(e.getKey(), e.getValue());
+ }
+ } else {
+ // Represents a user counter.
+ builder.setUrnForUserMetric(
+ metricUpdate.getKey().metricName().getNamespace(),
+ metricUpdate.getKey().metricName().getName());
+ }
+ builder.setInt64Value(metricUpdate.getUpdate());
+ builder.setTimestampToNow();
+ return builder.build();
+ }
+
/** Return the cumulative values for any metrics in this container as
MonitoringInfos. */
public Iterable<MonitoringInfo> getMonitoringInfos() {
// Extract user metrics and store as MonitoringInfos.
ArrayList<MonitoringInfo> monitoringInfos = new
ArrayList<MonitoringInfo>();
- MetricUpdates mus = this.getUpdates();
+ MetricUpdates metricUpdates = this.getUpdates();
- for (MetricUpdate<Long> mu : mus.counterUpdates()) {
- SimpleMonitoringInfoBuilder builder = new
SimpleMonitoringInfoBuilder(true);
- builder.setUrnForUserMetric(
- mu.getKey().metricName().getNamespace(),
mu.getKey().metricName().getName());
- builder.setInt64Value(mu.getUpdate());
- builder.setTimestampToNow();
- monitoringInfos.add(builder.build());
+ for (MetricUpdate<Long> metricUpdate : metricUpdates.counterUpdates()) {
+ monitoringInfos.add(counterUpdateToMonitoringInfo(metricUpdate));
}
return monitoringInfos;
}
diff --git
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MonitoringInfoMetricName.java
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MonitoringInfoMetricName.java
new file mode 100644
index 0000000..5d16910
--- /dev/null
+++
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/MonitoringInfoMetricName.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.runners.core.metrics;
+
+import static
org.apache.beam.vendor.guava.v20_0.com.google.common.base.Preconditions.checkArgument;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.apache.beam.sdk.metrics.MetricName;
+import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Strings;
+
+/**
+ * An implementation of {@code MetricKey} based on a MonitoringInfo's URN and
label to represent the
+ * key instead of only a name+namespace. This is useful when defining system
defined metrics with a
+ * specific urn via a {@code CounterContainer}.
+ */
+public class MonitoringInfoMetricName extends MetricName {
+
+ private String urn;
+ @Nullable private String name;
+ @Nullable private String namespace;
+ private HashMap<String, String> labels = new HashMap<String, String>();
+
+ private MonitoringInfoMetricName(String urn, HashMap<String, String> labels)
{
+ checkArgument(!Strings.isNullOrEmpty(urn), "MonitoringInfoMetricName urn
must be non-empty");
+ checkArgument(labels != null, "MonitoringInfoMetricName labels must be
non-null");
+ // TODO(ajamato): Move SimpleMonitoringInfoBuilder to
beam-runner-core-construction-java
+ // and ensure all necessary labels are set for the specific URN.
+ this.urn = urn;
+ for (Entry<String, String> entry : labels.entrySet()) {
+ this.labels.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /** Parse the urn field into a name and namespace field. */
+ private void parseUrn() {
+ if
(this.urn.startsWith(SimpleMonitoringInfoBuilder.USER_COUNTER_URN_PREFIX)) {
+ List<String> split = new
ArrayList<String>(Arrays.asList(this.getUrn().split(":")));
+ this.name = split.get(split.size() - 1);
+ this.namespace = split.get(split.size() - 2);
+ }
+ }
+
+ /** @return the parsed namespace from the user metric URN, otherwise null. */
+ @Override
+ public String getNamespace() {
+ if (this.namespace == null) {
+ parseUrn();
+ }
+ return this.namespace;
+ }
+
+ /** @return the parsed name from the user metric URN, otherwise null. */
+ @Override
+ public String getName() {
+ if (this.name == null) {
+ parseUrn();
+ }
+ return this.name;
+ }
+
+ /** @return the urn of this MonitoringInfo metric. */
+ public String getUrn() {
+ return this.urn;
+ }
+
+ /** @return The labels associated with this MonitoringInfo. */
+ public HashMap<String, String> getLabels() {
+ return this.labels;
+ }
+
+ /** @return a MetricName for a specific urn and labels map. */
+ public static MonitoringInfoMetricName named(String urn, HashMap<String,
String> labels) {
+ return new MonitoringInfoMetricName(urn, labels);
+ }
+
+ @Override
+ public int hashCode() {
+ // Don't include name and namespace, since they are lazily set.
+ return Objects.hash(urn, labels);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ // If the object is compared with itself then return true
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof MonitoringInfoMetricName)) {
+ return false;
+ }
+ MonitoringInfoMetricName other = (MonitoringInfoMetricName) o;
+ return this.urn.equals(other.urn) && this.labels.equals(other.labels);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(this.urn.toString());
+ builder.append(" ");
+ builder.append(this.labels.toString());
+ return builder.toString();
+ }
+}
diff --git
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilder.java
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilder.java
index cdebefc..df1fe50 100644
---
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilder.java
+++
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilder.java
@@ -22,11 +22,14 @@ import java.util.HashMap;
import javax.annotation.Nullable;
import org.apache.beam.model.fnexecution.v1.BeamFnApi;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfo;
+import
org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfo.MonitoringInfoLabels;
+import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfoLabelProps;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfoSpec;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfoSpecs;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfoTypeUrns;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfoUrns;
import org.apache.beam.runners.core.construction.BeamUrns;
+import
org.apache.beam.vendor.guava.v20_0.com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,6 +68,9 @@ public class SimpleMonitoringInfoBuilder {
private static final HashMap<String, MonitoringInfoSpec> specs =
new HashMap<String, MonitoringInfoSpec>();
+ public static final String PCOLLECTION_LABEL =
getLabelString(MonitoringInfoLabels.PCOLLECTION);
+ public static final String PTRANSFORM_LABEL =
getLabelString(MonitoringInfoLabels.TRANSFORM);
+
private final boolean validateAndDropInvalid;
private static final Logger LOG =
LoggerFactory.getLogger(SimpleMonitoringInfoBuilder.class);
@@ -85,6 +91,13 @@ public class SimpleMonitoringInfoBuilder {
}
}
+ /** Returns the label string constant defined in the MonitoringInfoLabel
enum proto. */
+ private static String getLabelString(MonitoringInfoLabels label) {
+ MonitoringInfoLabelProps props =
+
label.getValueDescriptor().getOptions().getExtension(BeamFnApi.labelProps);
+ return props.getName();
+ }
+
public SimpleMonitoringInfoBuilder() {
this(true);
}
@@ -95,7 +108,7 @@ public class SimpleMonitoringInfoBuilder {
}
/** @return The metric URN for a user metric, with a proper URN prefix. */
- private static String userMetricUrn(String metricNamespace, String
metricName) {
+ public static String userMetricUrn(String metricNamespace, String
metricName) {
String fixedMetricNamespace = metricNamespace.replace(':', '_');
String fixedMetricName = metricName.replace(':', '_');
StringBuilder sb = new StringBuilder();
@@ -144,13 +157,13 @@ public class SimpleMonitoringInfoBuilder {
/** Sets the PTRANSFORM MonitoringInfo label to the given param. */
public SimpleMonitoringInfoBuilder setPTransformLabel(String pTransform) {
// TODO(ajamato): Add validation that it is a valid pTransform name in the
bundle descriptor.
- setLabel("PTRANSFORM", pTransform);
+ setLabel(PTRANSFORM_LABEL, pTransform);
return this;
}
/** Sets the PCOLLECTION MonitoringInfo label to the given param. */
public SimpleMonitoringInfoBuilder setPCollectionLabel(String pCollection) {
- setLabel("PCOLLECTION", pCollection);
+ setLabel(PCOLLECTION_LABEL, pCollection);
return this;
}
@@ -161,17 +174,27 @@ public class SimpleMonitoringInfoBuilder {
}
/**
+ * @return A copy of the MonitoringInfo with the timestamp cleared, to allow
comparing two
+ * MonitoringInfos.
+ */
+ @VisibleForTesting
+ public static MonitoringInfo clearTimestamp(MonitoringInfo input) {
+ MonitoringInfo.Builder builder = MonitoringInfo.newBuilder();
+ builder.mergeFrom(input);
+ builder.clearTimestamp();
+ return builder.build();
+ }
+
+ /**
* Builds the provided MonitoringInfo. Returns null if
validateAndDropInvalid set and fields do
* not match respecting MonitoringInfoSpec based on urn.
*/
@Nullable
public MonitoringInfo build() {
final MonitoringInfo result = this.builder.build();
-
if (validateAndDropInvalid && this.validator.validate(result).isPresent())
{
return null;
}
-
return result;
}
}
diff --git
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidator.java
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidator.java
index 07f3acb..b0acbc8 100644
---
a/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidator.java
+++
b/runners/core-java/src/main/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidator.java
@@ -68,6 +68,7 @@ public class SpecMonitoringInfoValidator {
monitoringInfo.getUrn(), spec.getTypeUrn(),
monitoringInfo.getType()));
}
+ // TODO(ajamato): Tighten this restriction to use set equality, to catch
unused
Set<String> requiredLabels = new HashSet<>(spec.getRequiredLabelsList());
if (!monitoringInfo.getLabelsMap().keySet().containsAll(requiredLabels)) {
return Optional.of(
diff --git
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/LabeledMetricsTest.java
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/LabeledMetricsTest.java
new file mode 100644
index 0000000..c1704aa
--- /dev/null
+++
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/LabeledMetricsTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.runners.core.metrics;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import org.apache.beam.sdk.metrics.Counter;
+import org.apache.beam.sdk.metrics.MetricsContainer;
+import org.apache.beam.sdk.metrics.MetricsEnvironment;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+
+/** Tests for {@link LabeledMetrics}. */
+public class LabeledMetricsTest implements Serializable {
+
+ @Rule public final transient ExpectedException thrown =
ExpectedException.none();
+
+ @Test
+ public void testCounterDoesNotFailOperationsWhenNoMetricsContainerPresent() {
+ MetricsEnvironment.setCurrentContainer(null);
+ assertNull(MetricsEnvironment.getCurrentContainer());
+ HashMap<String, String> labels = new HashMap<String, String>();
+ String urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ MonitoringInfoMetricName name = MonitoringInfoMetricName.named(urn,
labels);
+
+ Counter counter = LabeledMetrics.counter(name);
+ counter.inc();
+ counter.inc(5L);
+ counter.dec();
+ counter.dec(5L);
+ }
+
+ @Test
+ public void testOperationsUpdateCounterFromContainerWhenContainerIsPresent()
{
+ HashMap<String, String> labels = new HashMap<String, String>();
+ String urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ MonitoringInfoMetricName name = MonitoringInfoMetricName.named(urn,
labels);
+
+ MetricsContainer mockContainer = Mockito.mock(MetricsContainer.class);
+ Counter mockCounter = Mockito.mock(Counter.class);
+ when(mockContainer.getCounter(name)).thenReturn(mockCounter);
+
+ Counter counter = LabeledMetrics.counter(name);
+
+ MetricsEnvironment.setCurrentContainer(mockContainer);
+ counter.inc();
+ verify(mockCounter).inc(1);
+
+ counter.inc(47L);
+ verify(mockCounter).inc(47);
+
+ counter.dec(5L);
+ verify(mockCounter).inc(-5);
+ }
+}
diff --git
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MetricsContainerImplTest.java
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MetricsContainerImplTest.java
index a4a44d3..ba8749c 100644
---
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MetricsContainerImplTest.java
+++
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MetricsContainerImplTest.java
@@ -24,6 +24,9 @@ import static
org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInA
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfo;
import org.apache.beam.sdk.metrics.MetricName;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -144,4 +147,54 @@ public class MetricsContainerImplTest {
DistributionCell dne = container.tryGetDistribution(MetricName.named("ns",
"dne"));
assertEquals(dne, null);
}
+
+ @Test
+ public void testMonitoringInfosArePopulatedForUserCounters() {
+ MetricsContainerImpl testObject = new MetricsContainerImpl("step1");
+ CounterCell c1 = testObject.getCounter(MetricName.named("ns", "name1"));
+ CounterCell c2 = testObject.getCounter(MetricName.named("ns", "name2"));
+ c1.inc(2L);
+ c2.inc(4L);
+ c1.inc(3L);
+
+ SimpleMonitoringInfoBuilder builder1 = new SimpleMonitoringInfoBuilder();
+ builder1.setUrnForUserMetric("ns", "name1");
+ builder1.setInt64Value(5);
+ builder1.build();
+
+ SimpleMonitoringInfoBuilder builder2 = new SimpleMonitoringInfoBuilder();
+ builder2.setUrnForUserMetric("ns", "name2");
+ builder2.setInt64Value(4);
+ builder2.build();
+
+ ArrayList<MonitoringInfo> actualMonitoringInfos = new
ArrayList<MonitoringInfo>();
+ for (MonitoringInfo mi : testObject.getMonitoringInfos()) {
+
actualMonitoringInfos.add(SimpleMonitoringInfoBuilder.clearTimestamp(mi));
+ }
+
+ assertThat(actualMonitoringInfos, containsInAnyOrder(builder1.build(),
builder2.build()));
+ }
+
+ @Test
+ public void testMonitoringInfosArePopulatedForABeamCounter() {
+ MetricsContainerImpl testObject = new MetricsContainerImpl("step1");
+ HashMap<String, String> labels = new HashMap<String, String>();
+ labels.put(SimpleMonitoringInfoBuilder.PCOLLECTION_LABEL, "pcollection");
+ MetricName name =
+
MonitoringInfoMetricName.named(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN,
labels);
+ CounterCell c1 = testObject.getCounter(name);
+ c1.inc(2L);
+
+ SimpleMonitoringInfoBuilder builder1 = new SimpleMonitoringInfoBuilder();
+ builder1.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN);
+ builder1.setPCollectionLabel("pcollection");
+ builder1.setInt64Value(2);
+ builder1.build();
+
+ ArrayList<MonitoringInfo> actualMonitoringInfos = new
ArrayList<MonitoringInfo>();
+ for (MonitoringInfo mi : testObject.getMonitoringInfos()) {
+
actualMonitoringInfos.add(SimpleMonitoringInfoBuilder.clearTimestamp(mi));
+ }
+ assertThat(actualMonitoringInfos, containsInAnyOrder(builder1.build()));
+ }
}
diff --git
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MonitoringInfoMetricNameTest.java
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MonitoringInfoMetricNameTest.java
new file mode 100644
index 0000000..13dd79d
--- /dev/null
+++
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/MonitoringInfoMetricNameTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.runners.core.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/** Tests for {@link MonitoringInfoMetricName}. */
+public class MonitoringInfoMetricNameTest implements Serializable {
+
+ @Rule public final transient ExpectedException thrown =
ExpectedException.none();
+
+ @Test
+ public void testElementCountConstruction() {
+ HashMap<String, String> labels = new HashMap<String, String>();
+ String urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ MonitoringInfoMetricName name = MonitoringInfoMetricName.named(urn,
labels);
+ assertEquals(null, name.getName());
+ assertEquals(null, name.getNamespace());
+ assertEquals(labels, name.getLabels());
+ assertEquals(urn, name.getUrn());
+
+ assertEquals(name, name); // test self equals;
+
+ // Reconstruct and test equality and hash code equivalence
+ urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ labels = new HashMap<String, String>();
+ MonitoringInfoMetricName name2 = MonitoringInfoMetricName.named(urn,
labels);
+
+ assertEquals(name, name2);
+ assertEquals(name.hashCode(), name2.hashCode());
+ }
+
+ @Test
+ public void testUserCounterUrnConstruction() {
+ String urn = SimpleMonitoringInfoBuilder.userMetricUrn("namespace",
"name");
+ HashMap<String, String> labels = new HashMap<String, String>();
+ MonitoringInfoMetricName name = MonitoringInfoMetricName.named(urn,
labels);
+ assertEquals("name", name.getName());
+ assertEquals("namespace", name.getNamespace());
+ assertEquals(labels, name.getLabels());
+ assertEquals(urn, name.getUrn());
+
+ assertEquals(name, name); // test self equals;
+
+ // Reconstruct and test equality and hash code equivalence
+ urn = SimpleMonitoringInfoBuilder.userMetricUrn("namespace", "name");
+ labels = new HashMap<String, String>();
+ MonitoringInfoMetricName name2 = MonitoringInfoMetricName.named(urn,
labels);
+
+ assertEquals(name, name2);
+ assertEquals(name.hashCode(), name2.hashCode());
+ }
+
+ @Test
+ public void testNotEqualsDiffLabels() {
+ HashMap<String, String> labels = new HashMap<String, String>();
+ String urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ MonitoringInfoMetricName name = MonitoringInfoMetricName.named(urn,
labels);
+
+ // Reconstruct and test equality and hash code equivalence
+ urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ labels = new HashMap<String, String>();
+ labels.put("label", "value1");
+ MonitoringInfoMetricName name2 = MonitoringInfoMetricName.named(urn,
labels);
+
+ assertNotEquals(name, name2);
+ assertNotEquals(name.hashCode(), name2.hashCode());
+ }
+
+ @Test
+ public void testNotEqualsDiffUrn() {
+ HashMap<String, String> labels = new HashMap<String, String>();
+ String urn = SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN;
+ MonitoringInfoMetricName name = MonitoringInfoMetricName.named(urn,
labels);
+
+ // Reconstruct and test equality and hash code equivalence
+ urn = "differentUrn";
+ labels = new HashMap<String, String>();
+ MonitoringInfoMetricName name2 = MonitoringInfoMetricName.named(urn,
labels);
+
+ assertNotEquals(name, name2);
+ assertNotEquals(name.hashCode(), name2.hashCode());
+ }
+
+ @Test
+ public void testNullLabelsThrows() {
+ thrown.expect(IllegalArgumentException.class);
+ HashMap<String, String> labels = null;
+
MonitoringInfoMetricName.named(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN,
labels);
+ }
+
+ @Test
+ public void testNullUrnThrows() {
+ HashMap<String, String> labels = new HashMap<String, String>();
+ thrown.expect(IllegalArgumentException.class);
+ MonitoringInfoMetricName.named(null, labels);
+ }
+
+ @Test
+ public void testEmptyUrnThrows() {
+ HashMap<String, String> labels = new HashMap<String, String>();
+ thrown.expect(IllegalArgumentException.class);
+ MonitoringInfoMetricName.named("", labels);
+ }
+}
diff --git
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilderTest.java
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilderTest.java
index 2697ef8..14fa703 100644
---
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilderTest.java
+++
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SimpleMonitoringInfoBuilderTest.java
@@ -39,15 +39,13 @@ public class SimpleMonitoringInfoBuilderTest {
builder.setInt64Value(1);
assertNull(builder.build());
- builder.setPTransformLabel("myTransform");
- assertNull(builder.build());
-
builder.setPCollectionLabel("myPcollection");
// Pass now that the spec is fully met.
MonitoringInfo monitoringInfo = builder.build();
assertTrue(monitoringInfo != null);
- assertEquals("myTransform",
monitoringInfo.getLabelsOrDefault("PTRANSFORM", null));
- assertEquals("myPcollection",
monitoringInfo.getLabelsOrDefault("PCOLLECTION", null));
+ assertEquals(
+ "myPcollection",
+
monitoringInfo.getLabelsOrDefault(SimpleMonitoringInfoBuilder.PCOLLECTION_LABEL,
null));
assertEquals(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN,
monitoringInfo.getUrn());
assertEquals(SimpleMonitoringInfoBuilder.SUM_INT64_TYPE_URN,
monitoringInfo.getType());
assertEquals(1,
monitoringInfo.getMetric().getCounterData().getInt64Value());
diff --git
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidatorTest.java
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidatorTest.java
index b764e92..7cff0a0 100644
---
a/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidatorTest.java
+++
b/runners/core-java/src/test/java/org/apache/beam/runners/core/metrics/SpecMonitoringInfoValidatorTest.java
@@ -70,7 +70,7 @@ public class SpecMonitoringInfoValidatorTest {
MonitoringInfo.newBuilder()
.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN)
.setType(SimpleMonitoringInfoBuilder.SUM_INT64_TYPE_URN)
- .putLabels("PCOLLECTION", "anotherValue")
+ .putLabels("PTRANSFORM", "unexpectedLabel")
.build();
assertTrue(testObject.validate(testInput).isPresent());
}
diff --git
a/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java
b/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java
index 4c7a56e..09cf59d 100644
---
a/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java
+++
b/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/RemoteExecutionTest.java
@@ -17,6 +17,7 @@
*/
package org.apache.beam.runners.fnexecution.control;
+import static junit.framework.TestCase.assertEquals;
import static
org.apache.beam.vendor.guava.v20_0.com.google.common.base.Preconditions.checkState;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
@@ -95,6 +96,7 @@ import org.apache.beam.sdk.transforms.Flatten;
import org.apache.beam.sdk.transforms.GroupByKey;
import org.apache.beam.sdk.transforms.Impulse;
import org.apache.beam.sdk.transforms.ParDo;
+import org.apache.beam.sdk.transforms.ParDo.SingleOutput;
import org.apache.beam.sdk.transforms.View;
import org.apache.beam.sdk.transforms.WithKeys;
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
@@ -114,13 +116,11 @@ import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterators;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Sets;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.util.concurrent.ThreadFactoryBuilder;
-import org.hamcrest.CoreMatchers;
import org.hamcrest.collection.IsEmptyIterable;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.joda.time.DateTimeUtils;
import org.joda.time.Duration;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -507,13 +507,36 @@ public class RemoteExecutionTest implements Serializable {
"create",
ParDo.of(
new DoFn<byte[], String>() {
+ private boolean emitted = false;
+
@ProcessElement
public void process(ProcessContext ctxt) {
- Metrics.counter(RemoteExecutionTest.class,
counterMetricName).inc();
+ // TODO(BEAM-6467): Impulse is producing two elements
instead of one.
+ // So add this check to only emit these three
elemenets.
+ if (!emitted) {
+ ctxt.output("zero");
+ ctxt.output("one");
+ ctxt.output("two");
+ Metrics.counter(RemoteExecutionTest.class,
counterMetricName).inc();
+ }
+ emitted = true;
}
}))
.setCoder(StringUtf8Coder.of());
+ SingleOutput<String, String> pardo =
+ ParDo.of(
+ new DoFn<String, String>() {
+ @ProcessElement
+ public void process(ProcessContext ctxt) {
+ // Output the element twice to keep unique numbers in asserts,
6 output elements.
+ ctxt.output(ctxt.element());
+ ctxt.output(ctxt.element());
+ }
+ });
+ input.apply("processA", pardo).setCoder(StringUtf8Coder.of());
+ input.apply("processB", pardo).setCoder(StringUtf8Coder.of());
+
RunnerApi.Pipeline pipelineProto = PipelineTranslation.toProto(p);
FusedPipeline fused = GreedyPipelineFuser.fuse(pipelineProto);
Optional<ExecutableStage> optionalStage =
@@ -584,21 +607,52 @@ public class RemoteExecutionTest implements Serializable {
@Override
public void onCompleted(ProcessBundleResponse response) {
// Assert the timestamps are non empty then 0 them out before
comparing.
- List<MonitoringInfo> actualMIs = new ArrayList<>();
+ List<MonitoringInfo> result = new ArrayList<MonitoringInfo>();
for (MonitoringInfo mi : response.getMonitoringInfosList()) {
- MonitoringInfo.Builder builder = MonitoringInfo.newBuilder();
- Assert.assertTrue(mi.getTimestamp().getSeconds() > 0);
- builder.mergeFrom(mi);
- builder.clearTimestamp();
- actualMIs.add(builder.build());
+ result.add(SimpleMonitoringInfoBuilder.clearTimestamp(mi));
}
+ // The user counter is counted in both ParDos, so it should be
counted twice for each
+ // element 4 = 2 x 2 elements.
+ List<MonitoringInfo> expected = new ArrayList<MonitoringInfo>();
+
SimpleMonitoringInfoBuilder builder = new
SimpleMonitoringInfoBuilder();
builder.setUrnForUserMetric(RemoteExecutionTest.class.getName(),
counterMetricName);
+ // TODO(ajamato): Add test having a user counter with the same
name, in two
+ // separate ptransforms, should be labelled differnetly. Currently
this does not work
+ // because the MetricContainer is not being seeded properly with
the step name.
+ builder.setInt64Value(1); // Count just the one inc() in create();
+ expected.add(builder.build());
+
+ // The element counter should be counted only once for the
pcollection.
+ // So there should be only two elements.
+ builder = new SimpleMonitoringInfoBuilder();
+ builder.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN);
+ builder.setPCollectionLabel("impulse.out");
builder.setInt64Value(2);
- MonitoringInfo expectedCounter = builder.build();
-
- assertThat(actualMIs, CoreMatchers.hasItems(expectedCounter));
+ expected.add(builder.build());
+
+ builder = new SimpleMonitoringInfoBuilder();
+ builder.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN);
+ builder.setPCollectionLabel("create/ParMultiDo(Anonymous).output");
+ builder.setInt64Value(3);
+ expected.add(builder.build());
+
+ // Verify that the element count is not double counted if two
PCollections consume it.
+ builder = new SimpleMonitoringInfoBuilder();
+ builder.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN);
+
builder.setPCollectionLabel("processA/ParMultiDo(Anonymous).output");
+ builder.setInt64Value(6);
+ expected.add(builder.build());
+
+ builder = new SimpleMonitoringInfoBuilder();
+ builder.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN);
+
builder.setPCollectionLabel("processB/ParMultiDo(Anonymous).output");
+ builder.setInt64Value(6);
+ expected.add(builder.build());
+
+ assertEquals(5, result.size());
+ assertThat(result, containsInAnyOrder(expected.toArray()));
}
};
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/DelegatingCounter.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/DelegatingCounter.java
new file mode 100644
index 0000000..c2ce493
--- /dev/null
+++
b/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/DelegatingCounter.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.sdk.metrics;
+
+// Intentionally package private, this is meant to be used with Metrics.java
and
+// LabeledMetrics.java
+
+import java.io.Serializable;
+
+/** Implementation of {@link Counter} that delegates to the instance for the
current context. */
+public class DelegatingCounter implements Metric, Counter, Serializable {
+ private final MetricName name;
+
+ public DelegatingCounter(MetricName name) {
+ this.name = name;
+ }
+
+ /** Increment the counter. */
+ @Override
+ public void inc() {
+ inc(1);
+ }
+
+ /** Increment the counter by the given amount. */
+ @Override
+ public void inc(long n) {
+ MetricsContainer container = MetricsEnvironment.getCurrentContainer();
+ if (container != null) {
+ container.getCounter(name).inc(n);
+ }
+ }
+
+ /* Decrement the counter. */
+ @Override
+ public void dec() {
+ inc(-1);
+ }
+
+ /* Decrement the counter by the given amount. */
+ @Override
+ public void dec(long n) {
+ inc(-1 * n);
+ }
+
+ @Override
+ public MetricName getName() {
+ return name;
+ }
+}
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/MetricResult.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/MetricResult.java
index 215528c..d8e0875 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/MetricResult.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/MetricResult.java
@@ -21,7 +21,10 @@ import com.fasterxml.jackson.annotation.JsonFilter;
import org.apache.beam.sdk.annotations.Experimental;
import org.apache.beam.sdk.annotations.Experimental.Kind;
-/** The results of a single current metric. */
+/**
+ * The results of a single current metric. TODO(BEAM-6265): Decouple wire
formats from internal
+ * formats, remove usage of MetricName.
+ */
@Experimental(Kind.METRICS)
@JsonFilter("committedMetrics")
public interface MetricResult<T> {
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/Metrics.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/Metrics.java
index fd22120..df3666a 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/Metrics.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/metrics/Metrics.java
@@ -98,47 +98,6 @@ public class Metrics {
return new DelegatingGauge(MetricName.named(namespace, name));
}
- /** Implementation of {@link Counter} that delegates to the instance for the
current context. */
- private static class DelegatingCounter implements Metric, Counter,
Serializable {
- private final MetricName name;
-
- private DelegatingCounter(MetricName name) {
- this.name = name;
- }
-
- /** Increment the counter. */
- @Override
- public void inc() {
- inc(1);
- }
-
- /** Increment the counter by the given amount. */
- @Override
- public void inc(long n) {
- MetricsContainer container = MetricsEnvironment.getCurrentContainer();
- if (container != null) {
- container.getCounter(name).inc(n);
- }
- }
-
- /* Decrement the counter. */
- @Override
- public void dec() {
- inc(-1);
- }
-
- /* Decrement the counter by the given amount. */
- @Override
- public void dec(long n) {
- inc(-1 * n);
- }
-
- @Override
- public MetricName getName() {
- return name;
- }
- }
-
/**
* Implementation of {@link Distribution} that delegates to the instance for
the current context.
*/
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataReadRunner.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataReadRunner.java
index 9de543e..e44b54b 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataReadRunner.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataReadRunner.java
@@ -21,12 +21,11 @@ import static
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Itera
import com.google.auto.service.AutoService;
import java.io.IOException;
-import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
-import org.apache.beam.fn.harness.data.MultiplexingFnDataReceiver;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.fnexecution.v1.BeamFnApi;
@@ -46,7 +45,6 @@ import org.apache.beam.sdk.fn.data.RemoteGrpcPortRead;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.util.WindowedValue;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -85,7 +83,7 @@ public class BeamFnDataReadRunner<OutputT> {
Map<String, PCollection> pCollections,
Map<String, RunnerApi.Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
@@ -107,10 +105,11 @@ public class BeamFnDataReadRunner<OutputT> {
} else {
coderSpec = null;
}
- // TODO make the receiver aware of its transform context as well.
- Collection<FnDataReceiver<WindowedValue<OutputT>>> consumers =
- (Collection)
-
pCollectionIdsToConsumers.get(getOnlyElement(pTransform.getOutputsMap().values()));
+ FnDataReceiver<WindowedValue<OutputT>> consumer =
+ (FnDataReceiver<WindowedValue<OutputT>>)
+ (FnDataReceiver)
+ pCollectionConsumerRegistry.getMultiplexingConsumer(
+ getOnlyElement(pTransform.getOutputsMap().values()));
BeamFnDataReadRunner<OutputT> runner =
new BeamFnDataReadRunner<>(
@@ -120,7 +119,7 @@ public class BeamFnDataReadRunner<OutputT> {
coderSpec,
coders,
beamFnDataClient,
- consumers);
+ consumer);
startFunctionRegistry.register(pTransformId,
runner::registerInputLocation);
finishFunctionRegistry.register(pTransformId,
runner::blockTillReadFinishes);
return runner;
@@ -128,7 +127,7 @@ public class BeamFnDataReadRunner<OutputT> {
}
private final Endpoints.ApiServiceDescriptor apiServiceDescriptor;
- private final FnDataReceiver<WindowedValue<OutputT>> receiver;
+ private final FnDataReceiver<WindowedValue<OutputT>> consumer;
private final Supplier<String> processBundleInstructionIdSupplier;
private final BeamFnDataClient beamFnDataClient;
private final Coder<WindowedValue<OutputT>> coder;
@@ -143,14 +142,14 @@ public class BeamFnDataReadRunner<OutputT> {
RunnerApi.Coder coderSpec,
Map<String, RunnerApi.Coder> coders,
BeamFnDataClient beamFnDataClient,
- Collection<FnDataReceiver<WindowedValue<OutputT>>> consumers)
+ FnDataReceiver<WindowedValue<OutputT>> consumer)
throws IOException {
RemoteGrpcPort port =
RemoteGrpcPortRead.fromPTransform(grpcReadNode).getPort();
this.apiServiceDescriptor = port.getApiServiceDescriptor();
this.inputTarget = inputTarget;
this.processBundleInstructionIdSupplier =
processBundleInstructionIdSupplier;
this.beamFnDataClient = beamFnDataClient;
- this.receiver = MultiplexingFnDataReceiver.forConsumers(consumers);
+ this.consumer = consumer;
RehydratedComponents components =
RehydratedComponents.forComponents(Components.newBuilder().putAllCoders(coders).build());
@@ -173,7 +172,7 @@ public class BeamFnDataReadRunner<OutputT> {
apiServiceDescriptor,
LogicalEndpoint.of(processBundleInstructionIdSupplier.get(),
inputTarget),
coder,
- receiver);
+ consumer);
}
public void blockTillReadFinishes() throws Exception {
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataWriteRunner.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataWriteRunner.java
index fae1653..4177814 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataWriteRunner.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BeamFnDataWriteRunner.java
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.fnexecution.v1.BeamFnApi;
@@ -44,7 +45,6 @@ import org.apache.beam.sdk.fn.data.RemoteGrpcPortWrite;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.util.WindowedValue;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,7 +83,7 @@ public class BeamFnDataWriteRunner<InputT> {
Map<String, PCollection> pCollections,
Map<String, RunnerApi.Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
@@ -108,9 +108,10 @@ public class BeamFnDataWriteRunner<InputT> {
new BeamFnDataWriteRunner<>(
pTransform, processBundleInstructionId, target, coderSpec,
coders, beamFnDataClient);
startFunctionRegistry.register(pTransformId, runner::registerForOutput);
- pCollectionIdsToConsumers.put(
+ pCollectionConsumerRegistry.register(
getOnlyElement(pTransform.getInputsMap().values()),
(FnDataReceiver) (FnDataReceiver<WindowedValue<InputT>>)
runner::consume);
+
finishFunctionRegistry.register(pTransformId, runner::close);
return runner;
}
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BoundedSourceRunner.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BoundedSourceRunner.java
index ac37582..4923afd 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BoundedSourceRunner.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/BoundedSourceRunner.java
@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.control.ProcessBundleHandler;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.pipeline.v1.RunnerApi;
@@ -43,7 +44,6 @@ import org.apache.beam.sdk.util.WindowedValue;
import
org.apache.beam.vendor.grpc.v1p13p1.com.google.protobuf.InvalidProtocolBufferException;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableList;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
/**
* A runner which creates {@link Reader}s for each {@link BoundedSource} sent
as an input and
@@ -77,15 +77,13 @@ public class BoundedSourceRunner<InputT extends
BoundedSource<OutputT>, OutputT>
Map<String, PCollection> pCollections,
Map<String, Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener) {
-
- // TODO make the receiver aware of its transform context as well.
ImmutableList.Builder<FnDataReceiver<WindowedValue<?>>> consumers =
ImmutableList.builder();
for (String pCollectionId : pTransform.getOutputsMap().values()) {
- consumers.addAll(pCollectionIdsToConsumers.get(pCollectionId));
+
consumers.add(pCollectionConsumerRegistry.getMultiplexingConsumer(pCollectionId));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@@ -97,7 +95,7 @@ public class BoundedSourceRunner<InputT extends
BoundedSource<OutputT>, OutputT>
FnDataReceiver runReadLoop = (FnDataReceiver<WindowedValue<InputT>>)
runner::runReadLoop;
for (String pCollectionId : pTransform.getInputsMap().values()) {
- pCollectionIdsToConsumers.put(pCollectionId, runReadLoop);
+ pCollectionConsumerRegistry.register(pCollectionId, runReadLoop);
}
return runner;
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/CombineRunners.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/CombineRunners.java
index 9a3d936..a6c4e50 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/CombineRunners.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/CombineRunners.java
@@ -19,12 +19,11 @@ package org.apache.beam.fn.harness;
import com.google.auto.service.AutoService;
import java.io.IOException;
-import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
-import org.apache.beam.fn.harness.data.MultiplexingFnDataReceiver;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.pipeline.v1.RunnerApi;
@@ -47,7 +46,6 @@ import org.apache.beam.sdk.values.KV;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.annotations.VisibleForTesting;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
/** Executes different components of Combine PTransforms. */
public class CombineRunners {
@@ -124,7 +122,7 @@ public class CombineRunners {
Map<String, PCollection> pCollections,
Map<String, RunnerApi.Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
@@ -160,24 +158,19 @@ public class CombineRunners {
Coder<AccumT> accumCoder =
(Coder<AccumT>)
rehydratedComponents.getCoder(combinePayload.getAccumulatorCoderId());
- Collection<FnDataReceiver<WindowedValue<KV<KeyT, AccumT>>>> consumers =
- (Collection)
- pCollectionIdsToConsumers.get(
+ FnDataReceiver<WindowedValue<KV<KeyT, AccumT>>> consumer =
+ (FnDataReceiver)
+ pCollectionConsumerRegistry.getMultiplexingConsumer(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()));
// TODO make the receiver aware of its transform context as well.
// Create the runner.
PrecombineRunner<KeyT, InputT, AccumT> runner =
- new PrecombineRunner<>(
- pipelineOptions,
- combineFn,
- MultiplexingFnDataReceiver.forConsumers(consumers),
- keyCoder,
- accumCoder);
+ new PrecombineRunner<>(pipelineOptions, combineFn, consumer,
keyCoder, accumCoder);
// Register the appropriate handlers.
startFunctionRegistry.register(pTransformId, runner::startBundle);
- pCollectionIdsToConsumers.put(
+ pCollectionConsumerRegistry.register(
Iterables.getOnlyElement(pTransform.getInputsMap().values()),
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<KeyT, InputT>>>)
runner::processElement);
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/DoFnPTransformRunnerFactory.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/DoFnPTransformRunnerFactory.java
index 7bf0f2c..f4d478d 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/DoFnPTransformRunnerFactory.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/DoFnPTransformRunnerFactory.java
@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.fn.harness.state.SideInputSpec;
@@ -88,7 +89,7 @@ abstract class DoFnPTransformRunnerFactory<
Map<String, PCollection> pCollections,
Map<String, RunnerApi.Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener) {
@@ -102,7 +103,7 @@ abstract class DoFnPTransformRunnerFactory<
pCollections,
coders,
windowingStrategies,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
splitListener);
RunnerT runner = createRunner(context);
@@ -116,7 +117,7 @@ abstract class DoFnPTransformRunnerFactory<
context.parDoPayload.getSideInputsMap().keySet(),
context.parDoPayload.getTimerSpecsMap().keySet()));
for (String localInputName : mainInput) {
- pCollectionIdsToConsumers.put(
+ pCollectionConsumerRegistry.register(
pTransform.getInputsOrThrow(localInputName),
(FnDataReceiver) (FnDataReceiver<WindowedValue<TransformInputT>>)
runner::processElement);
}
@@ -127,10 +128,12 @@ abstract class DoFnPTransformRunnerFactory<
DoFnSignatures.getTimerSpecOrThrow(
context.doFnSignature.timerDeclarations().get(localName),
context.doFn)
.getTimeDomain();
- pCollectionIdsToConsumers.put(
+ pCollectionConsumerRegistry.register(
pTransform.getInputsOrThrow(localName),
- timer ->
- runner.processTimer(localName, timeDomain,
(WindowedValue<KV<Object, Timer>>) timer));
+ (FnDataReceiver)
+ timer ->
+ runner.processTimer(
+ localName, timeDomain, (WindowedValue<KV<Object,
Timer>>) timer));
}
finishFunctionRegistry.register(pTransformId, runner::finishBundle);
@@ -170,7 +173,7 @@ abstract class DoFnPTransformRunnerFactory<
Map<String, PCollection> pCollections,
Map<String, RunnerApi.Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
BundleSplitListener splitListener) {
this.pipelineOptions = pipelineOptions;
this.beamFnStateClient = beamFnStateClient;
@@ -279,7 +282,7 @@ abstract class DoFnPTransformRunnerFactory<
localNameToConsumerBuilder = ImmutableListMultimap.builder();
for (Map.Entry<String, String> entry :
pTransform.getOutputsMap().entrySet()) {
localNameToConsumerBuilder.putAll(
- entry.getKey(), pCollectionIdsToConsumers.get(entry.getValue()));
+ entry.getKey(),
pCollectionConsumerRegistry.getMultiplexingConsumer(entry.getValue()));
}
localNameToConsumer = localNameToConsumerBuilder.build();
tagToSideInputSpecMap = tagToSideInputSpecMapBuilder.build();
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FlattenRunner.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FlattenRunner.java
index 6a06780..e75b92f 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FlattenRunner.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FlattenRunner.java
@@ -25,7 +25,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
-import org.apache.beam.fn.harness.data.MultiplexingFnDataReceiver;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.pipeline.v1.RunnerApi;
@@ -36,8 +36,6 @@ import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.util.WindowedValue;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableSet;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
/** Executes flatten PTransforms. */
public class FlattenRunner<InputT> {
@@ -64,25 +62,22 @@ public class FlattenRunner<InputT> {
Map<String, PCollection> pCollections,
Map<String, Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
throws IOException {
// Give each input a MultiplexingFnDataReceiver to all outputs of the
flatten.
- ImmutableSet.Builder<FnDataReceiver<WindowedValue<InputT>>>
consumersBuilder =
- new ImmutableSet.Builder<>();
String output = getOnlyElement(pTransform.getOutputsMap().values());
- consumersBuilder.addAll((Iterable)
pCollectionIdsToConsumers.get(output));
+ FnDataReceiver<WindowedValue<?>> receiver =
+ pCollectionConsumerRegistry.getMultiplexingConsumer(output);
- FnDataReceiver<WindowedValue<InputT>> receiver =
- MultiplexingFnDataReceiver.forConsumers(consumersBuilder.build());
FlattenRunner<InputT> runner = new FlattenRunner<>();
// TODO make the receiver aware of its transform context as well.
for (String pCollectionId : pTransform.getInputsMap().values()) {
- pCollectionIdsToConsumers.put(pCollectionId, (FnDataReceiver)
receiver);
+ pCollectionConsumerRegistry.register(pCollectionId, (FnDataReceiver)
receiver);
}
return runner;
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/MapFnRunners.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/MapFnRunners.java
index f3afcd9..6f21506 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/MapFnRunners.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/MapFnRunners.java
@@ -20,12 +20,11 @@ package org.apache.beam.fn.harness;
import static
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables.getOnlyElement;
import java.io.IOException;
-import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
-import org.apache.beam.fn.harness.data.MultiplexingFnDataReceiver;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.pipeline.v1.RunnerApi;
@@ -36,7 +35,6 @@ import org.apache.beam.sdk.fn.function.ThrowingFunction;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
/**
* Utilities to create {@code PTransformRunners} which execute simple map
functions.
@@ -106,22 +104,20 @@ public abstract class MapFnRunners {
Map<String, PCollection> pCollections,
Map<String, RunnerApi.Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
throws IOException {
- Collection<FnDataReceiver<WindowedValue<OutputT>>> consumers =
- (Collection)
-
pCollectionIdsToConsumers.get(getOnlyElement(pTransform.getOutputsMap().values()));
+ FnDataReceiver<WindowedValue<InputT>> consumer =
+ (FnDataReceiver)
+ pCollectionConsumerRegistry.getMultiplexingConsumer(
+ getOnlyElement(pTransform.getOutputsMap().values()));
- // TODO make the receiver aware of its transform context as well.
- Mapper<InputT, OutputT> mapper =
- mapperFactory.create(
- pTransformId, pTransform,
MultiplexingFnDataReceiver.forConsumers(consumers));
+ Mapper<InputT, OutputT> mapper = mapperFactory.create(pTransformId,
pTransform, consumer);
- pCollectionIdsToConsumers.put(
+ pCollectionConsumerRegistry.register(
Iterables.getOnlyElement(pTransform.getInputsMap().values()),
(FnDataReceiver) (FnDataReceiver<WindowedValue<InputT>>)
mapper::map);
return mapper;
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/PTransformRunnerFactory.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/PTransformRunnerFactory.java
index a7f129e..ecd0f64 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/PTransformRunnerFactory.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/PTransformRunnerFactory.java
@@ -22,16 +22,14 @@ import java.util.Map;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.control.BundleSplitListener;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.model.pipeline.v1.RunnerApi;
import org.apache.beam.model.pipeline.v1.RunnerApi.Coder;
import org.apache.beam.model.pipeline.v1.RunnerApi.PCollection;
import org.apache.beam.model.pipeline.v1.RunnerApi.PTransform;
-import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.options.PipelineOptions;
-import org.apache.beam.sdk.util.WindowedValue;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
/** A factory able to instantiate an appropriate handler for a given
PTransform. */
public interface PTransformRunnerFactory<T> {
@@ -50,7 +48,7 @@ public interface PTransformRunnerFactory<T> {
* @param pCollections A mapping from PCollection id to PCollection
definition.
* @param coders A mapping from coder id to coder definition.
* @param windowingStrategies
- * @param pCollectionIdsToConsumers A mapping from PCollection id to a
collection of consumers.
+ * @param pCollectionConsumerRegistry A mapping from PCollection id to a
collection of consumers.
* Note that if this handler is a consumer, it should register itself
within this multimap
* under the appropriate PCollection ids. Also note that all output
consumers needed by this
* PTransform (based on the values of the {@link
PTransform#getOutputsMap()} will have already
@@ -69,7 +67,7 @@ public interface PTransformRunnerFactory<T> {
Map<String, PCollection> pCollections,
Map<String, Coder> coders,
Map<String, RunnerApi.WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
index 0f3988e..26f30a8 100644
---
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
@@ -31,6 +31,7 @@ import java.util.function.Supplier;
import org.apache.beam.fn.harness.PTransformRunnerFactory;
import org.apache.beam.fn.harness.PTransformRunnerFactory.Registrar;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.data.QueueingBeamFnDataClient;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
@@ -53,11 +54,9 @@ import
org.apache.beam.model.pipeline.v1.RunnerApi.PTransform;
import org.apache.beam.model.pipeline.v1.RunnerApi.WindowingStrategy;
import org.apache.beam.runners.core.construction.PTransformTranslation;
import org.apache.beam.runners.core.metrics.MetricsContainerImpl;
-import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.fn.function.ThrowingRunnable;
import org.apache.beam.sdk.metrics.MetricsEnvironment;
import org.apache.beam.sdk.options.PipelineOptions;
-import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.sdk.util.common.ReflectHelpers;
import org.apache.beam.vendor.grpc.v1p13p1.com.google.protobuf.Message;
import org.apache.beam.vendor.grpc.v1p13p1.com.google.protobuf.TextFormat;
@@ -65,7 +64,6 @@ import
org.apache.beam.vendor.guava.v20_0.com.google.common.annotations.VisibleF
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.HashMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Lists;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Multimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.SetMultimap;
@@ -148,7 +146,7 @@ public class ProcessBundleHandler {
Supplier<String> processBundleInstructionId,
ProcessBundleDescriptor processBundleDescriptor,
SetMultimap<String, String> pCollectionIdsToConsumingPTransforms,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
Set<String> processedPTransformIds,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
@@ -169,7 +167,7 @@ public class ProcessBundleHandler {
processBundleInstructionId,
processBundleDescriptor,
pCollectionIdsToConsumingPTransforms,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
processedPTransformIds,
startFunctionRegistry,
finishFunctionRegistry,
@@ -202,7 +200,7 @@ public class ProcessBundleHandler {
processBundleDescriptor.getPcollectionsMap(),
processBundleDescriptor.getCodersMap(),
processBundleDescriptor.getWindowingStrategiesMap(),
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
startFunctionRegistry,
finishFunctionRegistry,
splitListener);
@@ -226,8 +224,7 @@ public class ProcessBundleHandler {
(BeamFnApi.ProcessBundleDescriptor) fnApiRegistry.apply(bundleId);
SetMultimap<String, String> pCollectionIdsToConsumingPTransforms =
HashMultimap.create();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers =
- ArrayListMultimap.create();
+ PCollectionConsumerRegistry pCollectionConsumerRegistry = new
PCollectionConsumerRegistry();
HashSet<String> processedPTransformIds = new HashSet<>();
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
@@ -289,13 +286,17 @@ public class ProcessBundleHandler {
request::getInstructionId,
bundleDescriptor,
pCollectionIdsToConsumingPTransforms,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
processedPTransformIds,
startFunctionRegistry,
finishFunctionRegistry,
splitListener);
}
+ // TODO(ajamato): A MetricsContainerImpl should be created and used for
each PTransform
+ // or the metric should be registered in some other way with the
ptransform name context,
+ // not for the instruction. This fails to associate each user counter
with an appropriate
+ // ptransform.
MetricsContainerImpl metricsContainer = new
MetricsContainerImpl(request.getInstructionId());
try (Closeable closeable =
MetricsEnvironment.scopedMetricsContainer(metricsContainer)) {
@@ -413,7 +414,7 @@ public class ProcessBundleHandler {
Map<String, PCollection> pCollections,
Map<String, Coder> coders,
Map<String, WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener) {
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/ElementCountFnDataReceiver.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/ElementCountFnDataReceiver.java
new file mode 100644
index 0000000..5de116e
--- /dev/null
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/ElementCountFnDataReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.fn.harness.data;
+
+import java.util.HashMap;
+import org.apache.beam.runners.core.metrics.LabeledMetrics;
+import org.apache.beam.runners.core.metrics.MonitoringInfoMetricName;
+import org.apache.beam.runners.core.metrics.SimpleMonitoringInfoBuilder;
+import org.apache.beam.sdk.fn.data.FnDataReceiver;
+import org.apache.beam.sdk.metrics.Counter;
+import org.apache.beam.sdk.util.WindowedValue;
+
+/**
+ * A wrapping {@code FnDataReceiver<WindowedValue<T>>} which counts the number
of elements consumed
+ * by the original {@code FnDataReceiver<WindowedValue<T>>}.
+ *
+ * @param <T> - The receiving type of the PTransform.
+ */
+public class ElementCountFnDataReceiver<T> implements
FnDataReceiver<WindowedValue<T>> {
+
+ private FnDataReceiver<WindowedValue<T>> original;
+ private Counter counter;
+
+ public ElementCountFnDataReceiver(FnDataReceiver<WindowedValue<T>> original,
String pCollection) {
+ this.original = original;
+ HashMap<String, String> labels = new HashMap<String, String>();
+ labels.put(SimpleMonitoringInfoBuilder.PCOLLECTION_LABEL, pCollection);
+ MonitoringInfoMetricName metricName =
+
MonitoringInfoMetricName.named(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN,
labels);
+ this.counter = LabeledMetrics.counter(metricName);
+ }
+
+ @Override
+ public void accept(WindowedValue<T> input) throws Exception {
+ // Increment the counter for each window the element occurs in.
+ this.counter.inc(input.getWindows().size());
+ this.original.accept(input);
+ }
+}
diff --git
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/PCollectionConsumerRegistry.java
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/PCollectionConsumerRegistry.java
new file mode 100644
index 0000000..8b8dbc4
--- /dev/null
+++
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/PCollectionConsumerRegistry.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.fn.harness.data;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.beam.sdk.fn.data.FnDataReceiver;
+import org.apache.beam.sdk.util.WindowedValue;
+import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
+import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
+
+/**
+ * The {@code PCollectionConsumerRegistry} is used to maintain a collection of
consuming
+ * FnDataReceiver for each pCollectionId. Registering with this class allows
inserting an element
+ * count counter for every pCollection. A combined MultiplexingConsumer
(Wrapped with an
+ * ElementCountFnDataReceiver) is returned by calling getMultiplexingConsumer.
+ */
+public class PCollectionConsumerRegistry {
+
+ private ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
pCollectionIdsToConsumers;
+ private Map<String, ElementCountFnDataReceiver>
pCollectionIdsToWrappedConsumer;
+
+ public PCollectionConsumerRegistry() {
+ pCollectionIdsToConsumers = ArrayListMultimap.create();
+ pCollectionIdsToWrappedConsumer = new HashMap<String,
ElementCountFnDataReceiver>();
+ }
+
+ /**
+ * Register the specified consumer to handle the elements in the pCollection
associated with
+ * pCollectionId. All consumers must be registered before extracting the
combined consumer by
+ * calling getMultiplexingConsumer(), or an exception will be thrown.
+ *
+ * @param pCollectionId
+ * @param consumer
+ * @param <T> the element type of the PCollection
+ * @throws RuntimeException if {@code register()} is called after {@code
+ * getMultiplexingConsumer()} is called.
+ */
+ public <T> void register(String pCollectionId,
FnDataReceiver<WindowedValue<T>> consumer) {
+ // Just save these consumers for now, but package them up later with an
+ // ElementCountFnDataReceiver and possibly a MultiplexingFnDataReceiver
+ // if there are multiple consumers.
+ ElementCountFnDataReceiver wrappedConsumer =
+ pCollectionIdsToWrappedConsumer.getOrDefault(pCollectionId, null);
+ if (wrappedConsumer != null) {
+ throw new RuntimeException(
+ "New consumers for a pCollectionId cannot be register()-d after "
+ + "calling getMultiplexingConsumer.");
+ }
+ pCollectionIdsToConsumers.put(pCollectionId, (FnDataReceiver) consumer);
+ }
+
+ /** @return the list of pcollection ids. */
+ public Set<String> keySet() {
+ return pCollectionIdsToConsumers.keySet();
+ }
+
+ /**
+ * New consumers should not be register()-ed after calling this method. This
will cause a
+ * RuntimeException, as this would fail to properly wrap the late-added
consumer to the
+ * ElementCountFnDataReceiver.
+ *
+ * @return A single ElementCountFnDataReceiver which directly wraps all the
registered consumers.
+ */
+ public FnDataReceiver<WindowedValue<?>> getMultiplexingConsumer(String
pCollectionId) {
+ ElementCountFnDataReceiver wrappedConsumer =
+ pCollectionIdsToWrappedConsumer.getOrDefault(pCollectionId, null);
+ if (wrappedConsumer == null) {
+ List<FnDataReceiver<WindowedValue<?>>> consumers =
+ pCollectionIdsToConsumers.get(pCollectionId);
+ FnDataReceiver<WindowedValue<?>> consumer =
+ MultiplexingFnDataReceiver.forConsumers(consumers);
+ wrappedConsumer = new ElementCountFnDataReceiver(consumer,
pCollectionId);
+ pCollectionIdsToWrappedConsumer.put(pCollectionId, wrappedConsumer);
+ }
+ return wrappedConsumer;
+ }
+
+ /**
+ * @return the number of underlying consumers for a pCollectionId, some
tests may wish to check
+ * this.
+ */
+ public List<FnDataReceiver<WindowedValue<?>>> getUnderlyingConsumers(String
pCollectionId) {
+ return pCollectionIdsToConsumers.get(pCollectionId);
+ }
+}
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/AssignWindowsRunnerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/AssignWindowsRunnerTest.java
index e9f3315..ce0a732 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/AssignWindowsRunnerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/AssignWindowsRunnerTest.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import
org.apache.beam.fn.harness.AssignWindowsRunner.AssignWindowsMapFnFactory;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.model.pipeline.v1.RunnerApi.FunctionSpec;
import org.apache.beam.model.pipeline.v1.RunnerApi.PTransform;
import org.apache.beam.model.pipeline.v1.RunnerApi.WindowIntoPayload;
@@ -34,7 +35,6 @@ import
org.apache.beam.runners.core.construction.PTransformTranslation;
import org.apache.beam.runners.core.construction.SdkComponents;
import org.apache.beam.runners.core.construction.WindowingStrategyTranslation;
import org.apache.beam.sdk.coders.Coder;
-import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.fn.function.ThrowingFunction;
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
import org.apache.beam.sdk.transforms.windowing.FixedWindows;
@@ -48,11 +48,8 @@ import
org.apache.beam.sdk.transforms.windowing.SlidingWindows;
import org.apache.beam.sdk.transforms.windowing.WindowFn;
import org.apache.beam.sdk.transforms.windowing.WindowMappingFn;
import org.apache.beam.sdk.util.WindowedValue;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableList;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableSet;
-import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.joda.time.Duration;
import org.joda.time.Instant;
import org.junit.Rule;
@@ -172,8 +169,8 @@ public class AssignWindowsRunnerTest implements
Serializable {
}
};
Collection<WindowedValue<?>> outputs = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> receivers =
ArrayListMultimap.create();
- receivers.put("output", outputs::add);
+ PCollectionConsumerRegistry pCollectionConsumerRegistry = new
PCollectionConsumerRegistry();
+ pCollectionConsumerRegistry.register("output", outputs::add);
SdkComponents components = SdkComponents.create();
components.registerEnvironment(Environments.createDockerEnvironment("java"));
MapFnRunners.forWindowedValueMapFnFactory(new
AssignWindowsMapFnFactory<>())
@@ -199,7 +196,7 @@ public class AssignWindowsRunnerTest implements
Serializable {
null /* pCollections */,
null /* coders */,
null /* windowingStrategies */,
- receivers,
+ pCollectionConsumerRegistry,
null /* startFunctionRegistry */,
null, /* finishFunctionRegistry */
null /* splitListener */);
@@ -212,7 +209,7 @@ public class AssignWindowsRunnerTest implements
Serializable {
new IntervalWindow(new Instant(-22L),
Duration.standardMinutes(5L)),
new IntervalWindow(new Instant(-120000L),
Duration.standardMinutes(3L))),
PaneInfo.ON_TIME_AND_ONLY_FIRING);
- Iterables.getOnlyElement(receivers.get("input")).accept(value);
+ pCollectionConsumerRegistry.getMultiplexingConsumer("input").accept(value);
assertThat(
outputs,
containsInAnyOrder(
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataReadRunnerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataReadRunnerTest.java
index f4e34a3..d6d2769 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataReadRunnerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataReadRunnerTest.java
@@ -39,6 +39,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.beam.fn.harness.PTransformRunnerFactory.Registrar;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.MultiplexingFnDataReceiver;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.model.fnexecution.v1.BeamFnApi;
import org.apache.beam.model.pipeline.v1.Endpoints;
@@ -58,11 +60,9 @@ import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Suppliers;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableList;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.util.concurrent.Uninterruptibles;
import org.hamcrest.collection.IsMapContaining;
import org.junit.Before;
@@ -126,9 +126,9 @@ public class BeamFnDataReadRunnerTest {
List<WindowedValue<String>> outputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
String localOutputId = "outputPC";
- consumers.put(
+ consumers.register(
localOutputId, (FnDataReceiver)
(FnDataReceiver<WindowedValue<String>>) outputValues::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
PTransformFunctionRegistry finishFunctionRegistry = new
PTransformFunctionRegistry();
@@ -193,7 +193,8 @@ public class BeamFnDataReadRunnerTest {
.thenReturn(bundle2Future);
List<WindowedValue<String>> valuesA = new ArrayList<>();
List<WindowedValue<String>> valuesB = new ArrayList<>();
-
+ FnDataReceiver<WindowedValue<String>> consumers =
+ MultiplexingFnDataReceiver.forConsumers(ImmutableList.of(valuesA::add,
valuesB::add));
AtomicReference<String> bundleId = new AtomicReference<>("0");
BeamFnDataReadRunner<String> readRunner =
new BeamFnDataReadRunner<>(
@@ -203,7 +204,7 @@ public class BeamFnDataReadRunnerTest {
CODER_SPEC,
COMPONENTS.getCodersMap(),
mockBeamFnDataClient,
- ImmutableList.of(valuesA::add, valuesB::add));
+ consumers);
// Process for bundle id 0
readRunner.registerInputLocation();
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataWriteRunnerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataWriteRunnerTest.java
index bc012a1..3f8ed0d 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataWriteRunnerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BeamFnDataWriteRunnerTest.java
@@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.beam.fn.harness.PTransformRunnerFactory.Registrar;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.model.fnexecution.v1.BeamFnApi;
import org.apache.beam.model.pipeline.v1.Endpoints;
@@ -48,17 +49,14 @@ import
org.apache.beam.runners.core.construction.CoderTranslation;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.coders.StringUtf8Coder;
import org.apache.beam.sdk.fn.data.CloseableFnDataReceiver;
-import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.fn.data.LogicalEndpoint;
import org.apache.beam.sdk.fn.data.RemoteGrpcPortWrite;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Suppliers;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.hamcrest.collection.IsMapContaining;
import org.junit.Before;
import org.junit.Test;
@@ -116,7 +114,7 @@ public class BeamFnDataWriteRunnerTest {
public void testCreatingAndProcessingBeamFnDataWriteRunner() throws
Exception {
String bundleId = "57L";
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
PTransformFunctionRegistry finishFunctionRegistry = new
PTransformFunctionRegistry();
@@ -181,7 +179,7 @@ public class BeamFnDataWriteRunnerTest {
eq(WIRE_CODER));
assertThat(consumers.keySet(), containsInAnyOrder(localInputId));
-
Iterables.getOnlyElement(consumers.get(localInputId)).accept(valueInGlobalWindow("TestValue"));
+
consumers.getMultiplexingConsumer(localInputId).accept(valueInGlobalWindow("TestValue"));
assertThat(outputValues, contains(valueInGlobalWindow("TestValue")));
outputValues.clear();
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BoundedSourceRunnerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BoundedSourceRunnerTest.java
index 240507d..5214b24 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BoundedSourceRunnerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/BoundedSourceRunnerTest.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.ServiceLoader;
import org.apache.beam.fn.harness.PTransformRunnerFactory.Registrar;
import org.apache.beam.fn.harness.control.ProcessBundleHandler;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.model.pipeline.v1.RunnerApi;
import org.apache.beam.sdk.fn.data.FnDataReceiver;
@@ -41,10 +42,8 @@ import org.apache.beam.sdk.util.SerializableUtils;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.grpc.v1p13p1.com.google.protobuf.ByteString;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Suppliers;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableList;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsMapContaining;
import org.junit.Test;
@@ -125,8 +124,8 @@ public class BoundedSourceRunnerTest {
public void testCreatingAndProcessingSourceFromFactory() throws Exception {
List<WindowedValue<String>> outputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
"outputPC", (FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
outputValues::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
PTransformFunctionRegistry finishFunctionRegistry = new
PTransformFunctionRegistry();
@@ -171,7 +170,8 @@ public class BoundedSourceRunnerTest {
// Check that when passing a source along as an input, the source is
processed.
assertThat(consumers.keySet(), containsInAnyOrder("inputPC", "outputPC"));
- Iterables.getOnlyElement(consumers.get("inputPC"))
+ consumers
+ .getMultiplexingConsumer("inputPC")
.accept(valueInGlobalWindow(CountingSource.upTo(2)));
assertThat(outputValues, contains(valueInGlobalWindow(0L),
valueInGlobalWindow(1L)));
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/CombineRunnersTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/CombineRunnersTest.java
index 05cc948..21f6c4d 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/CombineRunnersTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/CombineRunnersTest.java
@@ -28,6 +28,7 @@ import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.model.pipeline.v1.RunnerApi;
import org.apache.beam.runners.core.construction.PipelineTranslation;
@@ -44,9 +45,7 @@ import org.apache.beam.sdk.transforms.Create;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PCollection;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -121,9 +120,9 @@ public class CombineRunnersTest {
@Test
public void testPrecombine() throws Exception {
// Create a map of consumers and an output target to check output values.
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
Deque<WindowedValue<KV<String, Integer>>> mainOutputValues = new
ArrayDeque<>();
- consumers.put(
+ consumers.register(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()),
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<String, Integer>>>)
mainOutputValues::add);
@@ -154,8 +153,7 @@ public class CombineRunnersTest {
mainOutputValues.clear();
assertThat(consumers.keySet(), containsInAnyOrder(inputPCollectionId,
outputPCollectionId));
- FnDataReceiver<WindowedValue<?>> input =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ FnDataReceiver<WindowedValue<?>> input =
consumers.getMultiplexingConsumer(inputPCollectionId);
input.accept(valueInGlobalWindow(KV.of("A", "1")));
input.accept(valueInGlobalWindow(KV.of("A", "2")));
input.accept(valueInGlobalWindow(KV.of("A", "6")));
@@ -188,9 +186,9 @@ public class CombineRunnersTest {
@Test
public void testMergeAccumulators() throws Exception {
// Create a map of consumers and an output target to check output values.
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
Deque<WindowedValue<KV<String, Integer>>> mainOutputValues = new
ArrayDeque<>();
- consumers.put(
+ consumers.register(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()),
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<String, Integer>>>)
mainOutputValues::add);
@@ -222,8 +220,7 @@ public class CombineRunnersTest {
mainOutputValues.clear();
assertThat(consumers.keySet(), containsInAnyOrder(inputPCollectionId,
outputPCollectionId));
- FnDataReceiver<WindowedValue<?>> input =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ FnDataReceiver<WindowedValue<?>> input =
consumers.getMultiplexingConsumer(inputPCollectionId);
input.accept(valueInGlobalWindow(KV.of("A", Arrays.asList(1, 2, 6))));
input.accept(valueInGlobalWindow(KV.of("B", Arrays.asList(2, 3))));
input.accept(valueInGlobalWindow(KV.of("C", Arrays.asList(5, 2))));
@@ -243,9 +240,9 @@ public class CombineRunnersTest {
@Test
public void testExtractOutputs() throws Exception {
// Create a map of consumers and an output target to check output values.
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
Deque<WindowedValue<KV<String, Integer>>> mainOutputValues = new
ArrayDeque<>();
- consumers.put(
+ consumers.register(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()),
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<String, Integer>>>)
mainOutputValues::add);
@@ -277,8 +274,7 @@ public class CombineRunnersTest {
mainOutputValues.clear();
assertThat(consumers.keySet(), containsInAnyOrder(inputPCollectionId,
outputPCollectionId));
- FnDataReceiver<WindowedValue<?>> input =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ FnDataReceiver<WindowedValue<?>> input =
consumers.getMultiplexingConsumer(inputPCollectionId);
input.accept(valueInGlobalWindow(KV.of("A", 9)));
input.accept(valueInGlobalWindow(KV.of("B", 5)));
input.accept(valueInGlobalWindow(KV.of("C", 7)));
@@ -298,9 +294,9 @@ public class CombineRunnersTest {
@Test
public void testCombineGroupedValues() throws Exception {
// Create a map of consumers and an output target to check output values.
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
Deque<WindowedValue<KV<String, Integer>>> mainOutputValues = new
ArrayDeque<>();
- consumers.put(
+ consumers.register(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()),
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<String, Integer>>>)
mainOutputValues::add);
@@ -332,8 +328,7 @@ public class CombineRunnersTest {
mainOutputValues.clear();
assertThat(consumers.keySet(), containsInAnyOrder(inputPCollectionId,
outputPCollectionId));
- FnDataReceiver<WindowedValue<?>> input =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ FnDataReceiver<WindowedValue<?>> input =
consumers.getMultiplexingConsumer(inputPCollectionId);
input.accept(valueInGlobalWindow(KV.of("A", Arrays.asList("1", "2",
"6"))));
input.accept(valueInGlobalWindow(KV.of("B", Arrays.asList("2", "3"))));
input.accept(valueInGlobalWindow(KV.of("C", Arrays.asList("5", "2"))));
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FlattenRunnerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FlattenRunnerTest.java
index 9f2542a..fe661cd 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FlattenRunnerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FlattenRunnerTest.java
@@ -26,16 +26,13 @@ import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import org.apache.beam.fn.harness.data.MultiplexingFnDataReceiver;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.model.pipeline.v1.RunnerApi;
import org.apache.beam.runners.core.construction.PTransformTranslation;
import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Suppliers;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
-import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -68,8 +65,8 @@ public class FlattenRunnerTest {
.build();
List<WindowedValue<String>> mainOutputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
"mainOutputTarget",
(FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
mainOutputValues::add);
@@ -94,10 +91,10 @@ public class FlattenRunnerTest {
consumers.keySet(),
containsInAnyOrder("inputATarget", "inputBTarget", "inputCTarget",
"mainOutputTarget"));
-
Iterables.getOnlyElement(consumers.get("inputATarget")).accept(valueInGlobalWindow("A1"));
-
Iterables.getOnlyElement(consumers.get("inputATarget")).accept(valueInGlobalWindow("A2"));
-
Iterables.getOnlyElement(consumers.get("inputBTarget")).accept(valueInGlobalWindow("B"));
-
Iterables.getOnlyElement(consumers.get("inputCTarget")).accept(valueInGlobalWindow("C"));
+
consumers.getMultiplexingConsumer("inputATarget").accept(valueInGlobalWindow("A1"));
+
consumers.getMultiplexingConsumer("inputATarget").accept(valueInGlobalWindow("A2"));
+
consumers.getMultiplexingConsumer("inputBTarget").accept(valueInGlobalWindow("B"));
+
consumers.getMultiplexingConsumer("inputCTarget").accept(valueInGlobalWindow("C"));
assertThat(
mainOutputValues,
contains(
@@ -131,8 +128,8 @@ public class FlattenRunnerTest {
.build();
List<WindowedValue<String>> mainOutputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
"mainOutputTarget",
(FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
mainOutputValues::add);
@@ -155,10 +152,9 @@ public class FlattenRunnerTest {
mainOutputValues.clear();
assertThat(consumers.keySet(), containsInAnyOrder("inputATarget",
"mainOutputTarget"));
- assertThat(consumers.get("inputATarget"), hasSize(2));
+ assertThat(consumers.getUnderlyingConsumers("inputATarget"), hasSize(2));
- FnDataReceiver<WindowedValue<?>> input =
- MultiplexingFnDataReceiver.forConsumers(consumers.get("inputATarget"));
+ FnDataReceiver<WindowedValue<?>> input =
consumers.getMultiplexingConsumer("inputATarget");
input.accept(WindowedValue.valueInGlobalWindow("A1"));
input.accept(WindowedValue.valueInGlobalWindow("A2"));
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FnApiDoFnRunnerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FnApiDoFnRunnerTest.java
index 4017c20..64ca20f 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FnApiDoFnRunnerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/FnApiDoFnRunnerTest.java
@@ -27,13 +27,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
-import com.google.auto.value.AutoValue;
import java.io.Closeable;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.ServiceLoader;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.FakeBeamFnStateClient;
import org.apache.beam.model.fnexecution.v1.BeamFnApi.StateKey;
@@ -42,9 +43,12 @@ import
org.apache.beam.model.pipeline.v1.RunnerApi.Environment;
import org.apache.beam.runners.core.construction.PTransformTranslation;
import org.apache.beam.runners.core.construction.PipelineTranslation;
import org.apache.beam.runners.core.construction.SdkComponents;
+import org.apache.beam.runners.core.construction.metrics.MetricKey;
import org.apache.beam.runners.core.metrics.MetricUpdates;
import org.apache.beam.runners.core.metrics.MetricUpdates.MetricUpdate;
import org.apache.beam.runners.core.metrics.MetricsContainerImpl;
+import org.apache.beam.runners.core.metrics.MonitoringInfoMetricName;
+import org.apache.beam.runners.core.metrics.SimpleMonitoringInfoBuilder;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.coders.StringUtf8Coder;
import org.apache.beam.sdk.fn.data.FnDataReceiver;
@@ -85,10 +89,8 @@ import org.apache.beam.sdk.values.TupleTag;
import org.apache.beam.sdk.values.TupleTagList;
import org.apache.beam.vendor.grpc.v1p13p1.com.google.protobuf.ByteString;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Suppliers;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.hamcrest.collection.IsMapContaining;
import org.joda.time.Duration;
import org.joda.time.Instant;
@@ -196,8 +198,8 @@ public class FnApiDoFnRunnerTest implements Serializable {
bagUserStateKey("combine", "X"), encode("X0")));
List<WindowedValue<String>> mainOutputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
outputPCollectionId,
(FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
mainOutputValues::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
@@ -227,7 +229,7 @@ public class FnApiDoFnRunnerTest implements Serializable {
// Ensure that bag user state that is initially empty or populated works.
// Ensure that the key order does not matter when we traverse over KV
pairs.
FnDataReceiver<WindowedValue<?>> mainInput =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ consumers.getMultiplexingConsumer(inputPCollectionId);
mainInput.accept(valueInGlobalWindow(KV.of("X", "X1")));
mainInput.accept(valueInGlobalWindow(KV.of("Y", "Y1")));
mainInput.accept(valueInGlobalWindow(KV.of("X", "X2")));
@@ -355,11 +357,11 @@ public class FnApiDoFnRunnerTest implements Serializable {
List<WindowedValue<String>> mainOutputValues = new ArrayList<>();
List<WindowedValue<String>> additionalOutputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
outputPCollectionId,
(FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
mainOutputValues::add);
- consumers.put(
+ consumers.register(
additionalPCollectionId,
(FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
additionalOutputValues::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
@@ -391,7 +393,7 @@ public class FnApiDoFnRunnerTest implements Serializable {
// Ensure that bag user state that is initially empty or populated works.
// Ensure that the bagUserStateKey order does not matter when we traverse
over KV pairs.
FnDataReceiver<WindowedValue<?>> mainInput =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ consumers.getMultiplexingConsumer(inputPCollectionId);
mainInput.accept(valueInGlobalWindow("X"));
mainInput.accept(valueInGlobalWindow("Y"));
assertThat(
@@ -422,8 +424,9 @@ public class FnApiDoFnRunnerTest implements Serializable {
private static class TestSideInputIsAccessibleForDownstreamCallersDoFn
extends DoFn<String, Iterable<String>> {
+ public static final String USER_COUNTER_NAME = "userCountedElems";
private final Counter countedElements =
-
Metrics.counter(TestSideInputIsAccessibleForDownstreamCallersDoFn.class,
"countedElems");
+
Metrics.counter(TestSideInputIsAccessibleForDownstreamCallersDoFn.class,
USER_COUNTER_NAME);
private final PCollectionView<Iterable<String>> iterableSideInput;
@@ -486,8 +489,8 @@ public class FnApiDoFnRunnerTest implements Serializable {
FakeBeamFnStateClient fakeClient = new FakeBeamFnStateClient(stateData);
List<WindowedValue<Iterable<String>>> mainOutputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()),
(FnDataReceiver) (FnDataReceiver<WindowedValue<Iterable<String>>>)
mainOutputValues::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
@@ -517,7 +520,7 @@ public class FnApiDoFnRunnerTest implements Serializable {
// Ensure that bag user state that is initially empty or populated works.
// Ensure that the bagUserStateKey order does not matter when we traverse
over KV pairs.
FnDataReceiver<WindowedValue<?>> mainInput =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ consumers.getMultiplexingConsumer(inputPCollectionId);
mainInput.accept(valueInWindow("X", windowA));
mainInput.accept(valueInWindow("Y", windowB));
assertThat(mainOutputValues, hasSize(2));
@@ -532,21 +535,9 @@ public class FnApiDoFnRunnerTest implements Serializable {
assertEquals(stateData, fakeClient.getData());
}
- /**
- * A simple Tuple class for creating a list of ExpectedMetrics using the
stepName, metricName and
- * value of the MetricUpdate classes.
- */
- @AutoValue
- public abstract static class ExpectedMetric implements Serializable {
- static ExpectedMetric create(String stepName, MetricName metricName, long
value) {
- return new AutoValue_FnApiDoFnRunnerTest_ExpectedMetric(stepName,
metricName, value);
- }
-
- public abstract String stepName();
-
- public abstract MetricName metricName();
-
- public abstract long value();
+ /** @return a test MetricUpdate for expected metrics to compare against */
+ public MetricUpdate create(String stepName, MetricName name, long value) {
+ return MetricUpdate.create(MetricKey.create(stepName, name), value);
}
@Test
@@ -575,6 +566,7 @@ public class FnApiDoFnRunnerTest implements Serializable {
SdkComponents sdkComponents = SdkComponents.create(p.getOptions());
RunnerApi.Pipeline pProto = PipelineTranslation.toProto(p, sdkComponents,
true);
String inputPCollectionId =
sdkComponents.registerPCollection(valuePCollection);
+ String outputPCollectionId =
sdkComponents.registerPCollection(outputPCollection);
RunnerApi.PTransform pTransform =
pProto
@@ -597,8 +589,8 @@ public class FnApiDoFnRunnerTest implements Serializable {
FakeBeamFnStateClient fakeClient = new FakeBeamFnStateClient(stateData);
List<WindowedValue<Iterable<String>>> mainOutputValues = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
Iterables.getOnlyElement(pTransform.getOutputsMap().values()),
(FnDataReceiver) (FnDataReceiver<WindowedValue<Iterable<String>>>)
mainOutputValues::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
@@ -623,31 +615,41 @@ public class FnApiDoFnRunnerTest implements Serializable {
Iterables.getOnlyElement(startFunctionRegistry.getFunctions()).run();
mainOutputValues.clear();
+ assertThat(consumers.keySet(), containsInAnyOrder(inputPCollectionId,
outputPCollectionId));
+
// Ensure that bag user state that is initially empty or populated works.
// Ensure that the bagUserStateKey order does not matter when we traverse
over KV pairs.
FnDataReceiver<WindowedValue<?>> mainInput =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ consumers.getMultiplexingConsumer(inputPCollectionId);
mainInput.accept(valueInWindow("X", windowA));
mainInput.accept(valueInWindow("Y", windowB));
MetricsContainer mc = MetricsEnvironment.getCurrentContainer();
+
+ List<MetricUpdate> expectedMetrics = new ArrayList<MetricUpdate>();
MetricName metricName =
-
MetricName.named(TestSideInputIsAccessibleForDownstreamCallersDoFn.class,
"countedElems");
- List<ExpectedMetric> expectedMetrics = new ArrayList<ExpectedMetric>();
- expectedMetrics.add(ExpectedMetric.create("testUsingMetrics", metricName,
2));
+ MetricName.named(
+ TestSideInputIsAccessibleForDownstreamCallersDoFn.class,
+
TestSideInputIsAccessibleForDownstreamCallersDoFn.USER_COUNTER_NAME);
+ expectedMetrics.add(create("testUsingMetrics", metricName, 2));
+
+ HashMap<String, String> labels = new HashMap<String, String>();
+
+ labels.put(SimpleMonitoringInfoBuilder.PCOLLECTION_LABEL,
"Window.Into()/Window.Assign.out");
+ metricName =
+
MonitoringInfoMetricName.named(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN,
labels);
+ expectedMetrics.add(create("testUsingMetrics", metricName, 2));
+
+ labels.put(
+ SimpleMonitoringInfoBuilder.PCOLLECTION_LABEL,
+
"pTransformId/ParMultiDo(TestSideInputIsAccessibleForDownstreamCallers).output");
+ metricName =
+
MonitoringInfoMetricName.named(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN,
labels);
+ expectedMetrics.add(create("testUsingMetrics", metricName, 2));
closeable.close();
MetricUpdates updates = metricsContainer.getUpdates();
-
- // Validate MetricUpdates
- int i = 0;
- for (MetricUpdate mu : updates.counterUpdates()) {
- assertEquals(expectedMetrics.get(i).metricName(),
mu.getKey().metricName());
- assertEquals(expectedMetrics.get(i).stepName(), mu.getKey().stepName());
- assertEquals(expectedMetrics.get(i).value(), mu.getUpdate());
- i++;
- }
- assertEquals(1, i); // Validate the length.
+ assertThat(updates.counterUpdates(),
containsInAnyOrder(expectedMetrics.toArray()));
}
private static class TestTimerfulDoFn extends DoFn<KV<String, String>,
String> {
@@ -746,15 +748,15 @@ public class FnApiDoFnRunnerTest implements Serializable {
List<WindowedValue<String>> mainOutputValues = new ArrayList<>();
List<WindowedValue<KV<String, Timer>>> eventTimerOutputValues = new
ArrayList<>();
List<WindowedValue<KV<String, Timer>>> processingTimerOutputValues = new
ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put(
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register(
outputPCollectionId,
(FnDataReceiver) (FnDataReceiver<WindowedValue<String>>)
mainOutputValues::add);
- consumers.put(
+ consumers.register(
eventTimerOutputPCollectionId,
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<String, Timer>>>)
eventTimerOutputValues::add);
- consumers.put(
+ consumers.register(
processingTimerOutputPCollectionId,
(FnDataReceiver)
(FnDataReceiver<WindowedValue<KV<String, Timer>>>)
processingTimerOutputValues::add);
@@ -806,11 +808,11 @@ public class FnApiDoFnRunnerTest implements Serializable {
// Ensure that bag user state that is initially empty or populated works.
// Ensure that the key order does not matter when we traverse over KV
pairs.
FnDataReceiver<WindowedValue<?>> mainInput =
- Iterables.getOnlyElement(consumers.get(inputPCollectionId));
+ consumers.getMultiplexingConsumer(inputPCollectionId);
FnDataReceiver<WindowedValue<?>> eventTimerInput =
- Iterables.getOnlyElement(consumers.get(eventTimerInputPCollectionId));
+ consumers.getMultiplexingConsumer(eventTimerInputPCollectionId);
FnDataReceiver<WindowedValue<?>> processingTimerInput =
-
Iterables.getOnlyElement(consumers.get(processingTimerInputPCollectionId));
+ consumers.getMultiplexingConsumer(processingTimerInputPCollectionId);
mainInput.accept(timestampedValueInGlobalWindow(KV.of("X", "X1"), new
Instant(1000L)));
mainInput.accept(timestampedValueInGlobalWindow(KV.of("Y", "Y1"), new
Instant(1100L)));
mainInput.accept(timestampedValueInGlobalWindow(KV.of("X", "X2"), new
Instant(1200L)));
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/MapFnRunnersTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/MapFnRunnersTest.java
index 1aa0eb5..d93cb6a 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/MapFnRunnersTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/MapFnRunnersTest.java
@@ -28,10 +28,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.beam.fn.harness.MapFnRunners.ValueMapFnFactory;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.model.pipeline.v1.RunnerApi;
import org.apache.beam.model.pipeline.v1.RunnerApi.PTransform;
-import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.fn.function.ThrowingFunction;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
@@ -39,10 +39,7 @@ import
org.apache.beam.sdk.transforms.windowing.IntervalWindow;
import org.apache.beam.sdk.transforms.windowing.PaneInfo;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.guava.v20_0.com.google.common.base.Suppliers;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ArrayListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableSet;
-import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Iterables;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import org.joda.time.Duration;
import org.joda.time.Instant;
import org.junit.Test;
@@ -62,8 +59,8 @@ public class MapFnRunnersTest {
@Test
public void testValueOnlyMapping() throws Exception {
List<WindowedValue<?>> outputConsumer = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put("outputPC", outputConsumer::add);
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register("outputPC", outputConsumer::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
PTransformFunctionRegistry finishFunctionRegistry = new
PTransformFunctionRegistry();
@@ -90,7 +87,7 @@ public class MapFnRunnersTest {
assertThat(consumers.keySet(), containsInAnyOrder("inputPC", "outputPC"));
-
Iterables.getOnlyElement(consumers.get("inputPC")).accept(valueInGlobalWindow("abc"));
+
consumers.getMultiplexingConsumer("inputPC").accept(valueInGlobalWindow("abc"));
assertThat(outputConsumer, contains(valueInGlobalWindow("ABC")));
}
@@ -98,8 +95,8 @@ public class MapFnRunnersTest {
@Test
public void testFullWindowedValueMapping() throws Exception {
List<WindowedValue<?>> outputConsumer = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put("outputPC", outputConsumer::add);
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register("outputPC", outputConsumer::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
PTransformFunctionRegistry finishFunctionRegistry = new
PTransformFunctionRegistry();
@@ -125,7 +122,7 @@ public class MapFnRunnersTest {
assertThat(consumers.keySet(), containsInAnyOrder("inputPC", "outputPC"));
-
Iterables.getOnlyElement(consumers.get("inputPC")).accept(valueInGlobalWindow("abc"));
+
consumers.getMultiplexingConsumer("inputPC").accept(valueInGlobalWindow("abc"));
assertThat(outputConsumer, contains(valueInGlobalWindow("ABC")));
}
@@ -133,8 +130,8 @@ public class MapFnRunnersTest {
@Test
public void testFullWindowedValueMappingWithCompressedWindow() throws
Exception {
List<WindowedValue<?>> outputConsumer = new ArrayList<>();
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>> consumers =
ArrayListMultimap.create();
- consumers.put("outputPC", outputConsumer::add);
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ consumers.register("outputPC", outputConsumer::add);
PTransformFunctionRegistry startFunctionRegistry = new
PTransformFunctionRegistry();
PTransformFunctionRegistry finishFunctionRegistry = new
PTransformFunctionRegistry();
@@ -163,7 +160,8 @@ public class MapFnRunnersTest {
IntervalWindow firstWindow = new IntervalWindow(new Instant(0L),
Duration.standardMinutes(10L));
IntervalWindow secondWindow =
new IntervalWindow(new Instant(-10L), Duration.standardSeconds(22L));
- Iterables.getOnlyElement(consumers.get("inputPC"))
+ consumers
+ .getMultiplexingConsumer("inputPC")
.accept(
WindowedValue.of(
"abc",
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java
index 130ebac..1d82009 100644
---
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ProcessBundleHandlerTest.java
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.beam.fn.harness.PTransformRunnerFactory;
import org.apache.beam.fn.harness.data.BeamFnDataClient;
+import org.apache.beam.fn.harness.data.PCollectionConsumerRegistry;
import org.apache.beam.fn.harness.data.PTransformFunctionRegistry;
import org.apache.beam.fn.harness.state.BeamFnStateClient;
import org.apache.beam.fn.harness.state.BeamFnStateGrpcClientCache;
@@ -46,14 +47,12 @@ import org.apache.beam.model.pipeline.v1.RunnerApi.Coder;
import org.apache.beam.model.pipeline.v1.RunnerApi.PCollection;
import org.apache.beam.model.pipeline.v1.RunnerApi.PTransform;
import org.apache.beam.model.pipeline.v1.RunnerApi.WindowingStrategy;
-import org.apache.beam.sdk.fn.data.FnDataReceiver;
import org.apache.beam.sdk.fn.function.ThrowingConsumer;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.util.WindowedValue;
import org.apache.beam.vendor.grpc.v1p13p1.com.google.protobuf.Message;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ImmutableMap;
-import
org.apache.beam.vendor.guava.v20_0.com.google.common.collect.ListMultimap;
import
org.apache.beam.vendor.guava.v20_0.com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Before;
import org.junit.Rule;
@@ -116,7 +115,7 @@ public class ProcessBundleHandlerTest {
pCollections,
coders,
windowingStrategies,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
startFunctionRegistry,
finishFunctionRegistry,
splitListener) -> {
@@ -187,7 +186,7 @@ public class ProcessBundleHandlerTest {
pCollections,
coders,
windowingStrategies,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
startFunctionRegistry,
finishFunctionRegistry,
splitListener) -> {
@@ -233,7 +232,7 @@ public class ProcessBundleHandlerTest {
pCollections,
coders,
windowingStrategies,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
startFunctionRegistry,
finishFunctionRegistry,
splitListener) -> {
@@ -281,7 +280,7 @@ public class ProcessBundleHandlerTest {
pCollections,
coders,
windowingStrategies,
- pCollectionIdsToConsumers,
+ pCollectionConsumerRegistry,
startFunctionRegistry,
finishFunctionRegistry,
splitListener) -> {
@@ -367,8 +366,7 @@ public class ProcessBundleHandlerTest {
Map<String, PCollection> pCollections,
Map<String, Coder> coders,
Map<String, WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
- pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
@@ -430,8 +428,7 @@ public class ProcessBundleHandlerTest {
Map<String, PCollection> pCollections,
Map<String, Coder> coders,
Map<String, WindowingStrategy> windowingStrategies,
- ListMultimap<String, FnDataReceiver<WindowedValue<?>>>
- pCollectionIdsToConsumers,
+ PCollectionConsumerRegistry pCollectionConsumerRegistry,
PTransformFunctionRegistry startFunctionRegistry,
PTransformFunctionRegistry finishFunctionRegistry,
BundleSplitListener splitListener)
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/data/ElementCountFnDataReceiverTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/data/ElementCountFnDataReceiverTest.java
new file mode 100644
index 0000000..a18c2e9
--- /dev/null
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/data/ElementCountFnDataReceiverTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.fn.harness.data;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import java.io.Closeable;
+import org.apache.beam.model.fnexecution.v1.BeamFnApi.MonitoringInfo;
+import org.apache.beam.runners.core.metrics.MetricsContainerImpl;
+import org.apache.beam.runners.core.metrics.SimpleMonitoringInfoBuilder;
+import org.apache.beam.sdk.fn.data.FnDataReceiver;
+import org.apache.beam.sdk.metrics.MetricsEnvironment;
+import org.apache.beam.sdk.util.WindowedValue;
+import org.junit.Test;
+
+/** Tests for {@link ElementCountFnDataReceiver}. */
+public class ElementCountFnDataReceiverTest {
+
+ /**
+ * Test that the elements are counted, and a MonitoringInfo can be extracted
from a
+ * metricsContainer, if it is in scope.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testCountsElements() throws Exception {
+ final String pCollectionA = "pCollectionA";
+ final String stepA = "stepA";
+
+ MetricsContainerImpl metricsContainer = new MetricsContainerImpl(stepA);
+ try (Closeable closeable =
MetricsEnvironment.scopedMetricsContainer(metricsContainer)) {
+
+ FnDataReceiver<WindowedValue<String>> consumer =
mock(FnDataReceiver.class);
+ ElementCountFnDataReceiver<String> wrapperConsumer =
+ new ElementCountFnDataReceiver(consumer, pCollectionA);
+ WindowedValue<String> element =
WindowedValue.valueInGlobalWindow("elem");
+ int numElements = 20;
+ for (int i = 0; i < numElements; i++) {
+ wrapperConsumer.accept(element);
+ }
+
+ SimpleMonitoringInfoBuilder builder = new SimpleMonitoringInfoBuilder();
+ builder.setUrn(SimpleMonitoringInfoBuilder.ELEMENT_COUNT_URN);
+ builder.setPCollectionLabel(pCollectionA);
+ builder.setInt64Value(numElements);
+ MonitoringInfo expected = builder.build();
+
+ // Clear the timestamp before compairison.
+ MonitoringInfo first =
metricsContainer.getMonitoringInfos().iterator().next();
+ MonitoringInfo result =
SimpleMonitoringInfoBuilder.clearTimestamp(first);
+ assertEquals(expected, result);
+ }
+ }
+}
diff --git
a/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/data/PCollectionConsumerRegistryTest.java
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/data/PCollectionConsumerRegistryTest.java
new file mode 100644
index 0000000..beba73e
--- /dev/null
+++
b/sdks/java/harness/src/test/java/org/apache/beam/fn/harness/data/PCollectionConsumerRegistryTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.beam.fn.harness.data;
+
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.apache.beam.sdk.fn.data.FnDataReceiver;
+import org.apache.beam.sdk.util.WindowedValue;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link PCollectionConsumerRegistryTest}. */
+@RunWith(JUnit4.class)
+public class PCollectionConsumerRegistryTest {
+
+ @Rule public ExpectedException expectedException = ExpectedException.none();
+
+ /**
+ * Test that the counter increments only once when multiple consumers of
same pCollection read the
+ * same element.
+ */
+ @Test
+ public void multipleConsumersSamePCollection() throws Exception {
+ final String pCollectionA = "pCollectionA";
+
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ FnDataReceiver<WindowedValue<String>> consumerA1 =
mock(FnDataReceiver.class);
+ FnDataReceiver<WindowedValue<String>> consumerA2 =
mock(FnDataReceiver.class);
+
+ consumers.register(pCollectionA, consumerA1);
+ consumers.register(pCollectionA, consumerA2);
+
+ FnDataReceiver<WindowedValue<String>> wrapperConsumer =
+ (FnDataReceiver<WindowedValue<String>>)
+ (FnDataReceiver) consumers.getMultiplexingConsumer(pCollectionA);
+
+ WindowedValue<String> element = WindowedValue.valueInGlobalWindow("elem");
+ int numElements = 20;
+ for (int i = 0; i < numElements; i++) {
+ wrapperConsumer.accept(element);
+ }
+
+ // Check that the underlying consumers are each invoked per element.
+ verify(consumerA1, times(numElements)).accept(element);
+ verify(consumerA2, times(numElements)).accept(element);
+
+ assertThat(consumers.keySet(), contains(pCollectionA));
+ assertThat(consumers.getUnderlyingConsumers(pCollectionA),
contains(consumerA1, consumerA2));
+ }
+
+ @Test
+ public void throwsOnRegisteringAfterMultiplexingConsumerWasInitialized()
throws Exception {
+ final String pCollectionA = "pCollectionA";
+
+ PCollectionConsumerRegistry consumers = new PCollectionConsumerRegistry();
+ FnDataReceiver<WindowedValue<String>> consumerA1 =
mock(FnDataReceiver.class);
+ FnDataReceiver<WindowedValue<String>> consumerA2 =
mock(FnDataReceiver.class);
+
+ consumers.register(pCollectionA, consumerA1);
+ consumers.getMultiplexingConsumer(pCollectionA);
+
+ expectedException.expect(RuntimeException.class);
+ expectedException.expectMessage("cannot be register()-d after");
+ consumers.register(pCollectionA, consumerA2);
+ }
+}