This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 36d53183a [spark] Add task metrics (#2498)
36d53183a is described below
commit 36d53183ac9a2fbec8610e1af21d63641641ea69
Author: Yann Byron <[email protected]>
AuthorDate: Wed Dec 13 16:42:45 2023 +0800
[spark] Add task metrics (#2498)
---
.../paimon/spark/SparkInputPartitionReader.java | 72 ------------
.../apache/paimon/spark/SparkReaderFactory.java | 84 --------------
.../org/apache/paimon/spark/PaimonBaseScan.scala | 18 ++-
.../org/apache/paimon/spark/PaimonBatch.scala | 3 +-
.../org/apache/paimon/spark/PaimonMetrics.scala | 124 +++++++++++++++++++++
.../paimon/spark/PaimonPartitionReader.scala | 82 ++++++++++++++
.../spark/PaimonPartitionReaderFactory.scala | 60 ++++++++++
.../spark/sources/PaimonMicroBatchStream.scala | 4 +-
.../main/scala/org/apache/spark/sql/Utils.scala | 5 +
9 files changed, 290 insertions(+), 162 deletions(-)
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkInputPartitionReader.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkInputPartitionReader.java
deleted file mode 100644
index 0c299cabe..000000000
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkInputPartitionReader.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.paimon.spark;
-
-import org.apache.paimon.disk.IOManager;
-import org.apache.paimon.reader.RecordReaderIterator;
-
-import org.apache.spark.sql.catalyst.InternalRow;
-import org.apache.spark.sql.connector.read.PartitionReader;
-import org.apache.spark.sql.connector.read.PartitionReaderFactory;
-
-import java.io.IOException;
-
-/** A spark 3 {@link PartitionReader} for paimon, created by {@link
PartitionReaderFactory}. */
-public class SparkInputPartitionReader implements PartitionReader<InternalRow>
{
-
- private final IOManager ioManager;
- private final RecordReaderIterator<org.apache.paimon.data.InternalRow>
iterator;
- private final SparkInternalRow row;
-
- public SparkInputPartitionReader(
- IOManager ioManager,
- RecordReaderIterator<org.apache.paimon.data.InternalRow> iterator,
- SparkInternalRow row) {
- this.ioManager = ioManager;
- this.iterator = iterator;
- this.row = row;
- }
-
- @Override
- public boolean next() {
- if (iterator.hasNext()) {
- row.replace(iterator.next());
- return true;
- }
- return false;
- }
-
- @Override
- public InternalRow get() {
- return row;
- }
-
- @Override
- public void close() throws IOException {
- try {
- iterator.close();
- } catch (Exception e) {
- throw new IOException(e);
- }
- try {
- ioManager.close();
- } catch (Exception e) {
- throw new IOException(e);
- }
- }
-}
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkReaderFactory.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkReaderFactory.java
deleted file mode 100644
index 69a029715..000000000
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkReaderFactory.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.paimon.spark;
-
-import org.apache.paimon.data.InternalRow;
-import org.apache.paimon.disk.IOManager;
-import org.apache.paimon.reader.RecordReader;
-import org.apache.paimon.reader.RecordReaderIterator;
-import org.apache.paimon.table.source.ReadBuilder;
-
-import org.apache.spark.sql.connector.read.InputPartition;
-import org.apache.spark.sql.connector.read.PartitionReader;
-import org.apache.spark.sql.connector.read.PartitionReaderFactory;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.Objects;
-
-import static org.apache.paimon.spark.SparkUtils.createIOManager;
-
-/** A Spark {@link PartitionReaderFactory} for paimon. */
-public class SparkReaderFactory implements PartitionReaderFactory {
-
- private static final long serialVersionUID = 1L;
-
- private final ReadBuilder readBuilder;
-
- public SparkReaderFactory(ReadBuilder readBuilder) {
- this.readBuilder = readBuilder;
- }
-
- @Override
- public PartitionReader<org.apache.spark.sql.catalyst.InternalRow>
createReader(
- InputPartition partition) {
- RecordReader<InternalRow> reader;
- IOManager ioManager = createIOManager();
- try {
- reader =
- readBuilder
- .newRead()
- .withIOManager(ioManager)
- .createReader(((SparkInputPartition)
partition).split());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- RecordReaderIterator<InternalRow> iterator = new
RecordReaderIterator<>(reader);
- SparkInternalRow row = new SparkInternalRow(readBuilder.readType());
- return new SparkInputPartitionReader(ioManager, iterator, row);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- SparkReaderFactory that = (SparkReaderFactory) o;
- return this.readBuilder.equals(that.readBuilder);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(readBuilder);
- }
-}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
index d954c9e14..4258df483 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
@@ -18,15 +18,14 @@
package org.apache.paimon.spark
import org.apache.paimon.spark.sources.PaimonMicroBatchStream
-import org.apache.paimon.table.{DataTable, Table}
+import org.apache.paimon.table.{DataTable, FileStoreTable, Table}
import org.apache.paimon.table.source.{ReadBuilder, Split}
+import org.apache.spark.sql.connector.metric.CustomMetric
import org.apache.spark.sql.connector.read.{Batch, Scan, Statistics,
SupportsReportStatistics}
import org.apache.spark.sql.connector.read.streaming.MicroBatchStream
import org.apache.spark.sql.types.StructType
-import java.util.OptionalLong
-
import scala.collection.JavaConverters._
abstract class PaimonBaseScan(table: Table, readBuilder: ReadBuilder, desc:
String)
@@ -60,4 +59,17 @@ abstract class PaimonBaseScan(table: Table, readBuilder:
ReadBuilder, desc: Stri
splits
}
+ override def supportedCustomMetrics: Array[CustomMetric] = {
+ val paimonMetrics: Array[CustomMetric] = table match {
+ case _: FileStoreTable =>
+ Array(
+ PaimonNumSplitMetric(),
+ PaimonSplitSizeMetric(),
+ PaimonAvgSplitSizeMetric()
+ )
+ case _ =>
+ Array.empty[CustomMetric]
+ }
+ super.supportedCustomMetrics() ++ paimonMetrics
+ }
}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
index f1aa4a353..3cecf9698 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
@@ -29,7 +29,8 @@ case class PaimonBatch(splits: Array[Split], readBuilder:
ReadBuilder) extends B
override def planInputPartitions(): Array[InputPartition] =
splits.map(new SparkInputPartition(_).asInstanceOf[InputPartition])
- override def createReaderFactory(): PartitionReaderFactory = new
SparkReaderFactory(readBuilder)
+ override def createReaderFactory(): PartitionReaderFactory = new
PaimonPartitionReaderFactory(
+ readBuilder)
override def equals(obj: Any): Boolean = {
obj match {
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonMetrics.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonMetrics.scala
new file mode 100644
index 000000000..7940af224
--- /dev/null
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonMetrics.scala
@@ -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.paimon.spark
+
+import org.apache.spark.sql.Utils
+import org.apache.spark.sql.connector.metric.{CustomAvgMetric,
CustomSumMetric, CustomTaskMetric}
+
+import java.text.DecimalFormat
+
+object PaimonMetrics {
+
+ val NUM_SPLITS = "numSplits"
+
+ val SPLIT_SIZE = "splitSize"
+
+ val AVG_SPLIT_SIZE = "avgSplitSize"
+}
+
+// paimon's task metric
+sealed trait PaimonTaskMetric extends CustomTaskMetric
+
+case class PaimonNumSplitsTaskMetric(override val value: Long) extends
PaimonTaskMetric {
+
+ override def name(): String = PaimonMetrics.NUM_SPLITS
+
+}
+
+case class PaimonSplitSizeTaskMetric(override val value: Long) extends
PaimonTaskMetric {
+
+ override def name(): String = PaimonMetrics.SPLIT_SIZE
+
+}
+
+case class PaimonAvgSplitSizeTaskMetric(override val value: Long) extends
PaimonTaskMetric {
+
+ override def name(): String = PaimonMetrics.AVG_SPLIT_SIZE
+
+}
+
+// paimon's sum metric
+sealed trait PaimonSumMetric extends CustomSumMetric {
+
+ protected def aggregateTaskMetrics0(taskMetrics: Array[Long]): Long = {
+ var sum: Long = 0L
+ for (taskMetric <- taskMetrics) {
+ sum += taskMetric
+ }
+ sum
+ }
+
+ override def aggregateTaskMetrics(taskMetrics: Array[Long]): String = {
+ String.valueOf(aggregateTaskMetrics0(taskMetrics))
+ }
+
+}
+
+case class PaimonNumSplitMetric() extends PaimonSumMetric {
+
+ override def name(): String = PaimonMetrics.NUM_SPLITS
+
+ override def description(): String = "number of splits read"
+
+}
+
+case class PaimonSplitSizeMetric() extends PaimonSumMetric {
+
+ override def name(): String = PaimonMetrics.SPLIT_SIZE
+
+ override def description(): String = "size of splits read"
+
+ override def aggregateTaskMetrics(taskMetrics: Array[Long]): String = {
+ Utils.bytesToString(aggregateTaskMetrics0(taskMetrics))
+ }
+}
+
+// paimon's avg metric
+sealed trait PaimonAvgMetric extends CustomAvgMetric {
+
+ protected def aggregateTaskMetrics0(taskMetrics: Array[Long]): Double = {
+ if (taskMetrics.length > 0) {
+ var sum = 0L
+ for (taskMetric <- taskMetrics) {
+ sum += taskMetric
+ }
+ sum.toDouble / taskMetrics.length
+ } else {
+ 0d
+ }
+ }
+
+ override def aggregateTaskMetrics(taskMetrics: Array[Long]): String = {
+ val average = aggregateTaskMetrics0(taskMetrics)
+ new DecimalFormat("#0.000").format(average)
+ }
+
+}
+
+case class PaimonAvgSplitSizeMetric() extends PaimonAvgMetric {
+
+ override def name(): String = PaimonMetrics.AVG_SPLIT_SIZE
+
+ override def description(): String = "avg size of splits read"
+
+ override def aggregateTaskMetrics(taskMetrics: Array[Long]): String = {
+ val average = aggregateTaskMetrics0(taskMetrics).round
+ Utils.bytesToString(average)
+ }
+
+}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
new file mode 100644
index 000000000..d0564c127
--- /dev/null
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
@@ -0,0 +1,82 @@
+/*
+ * 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.paimon.spark
+
+import org.apache.paimon.data.{InternalRow => PaimonInternalRow}
+import org.apache.paimon.reader.{RecordReader, RecordReaderIterator}
+import org.apache.paimon.table.source.{DataSplit, Split}
+
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.connector.metric.CustomTaskMetric
+import org.apache.spark.sql.connector.read.PartitionReader
+
+import java.io.IOException
+
+import scala.collection.JavaConverters._
+
+case class PaimonPartitionReader(
+ readFunc: Split => RecordReader[PaimonInternalRow],
+ partition: SparkInputPartition,
+ row: SparkInternalRow
+) extends PartitionReader[InternalRow] {
+
+ private lazy val split: Split = partition.split
+
+ private lazy val iterator = {
+ val reader = readFunc(split)
+ new RecordReaderIterator[PaimonInternalRow](reader)
+ }
+
+ override def next(): Boolean = {
+ if (iterator.hasNext) {
+ row.replace(iterator.next())
+ true
+ } else {
+ false
+ }
+ }
+
+ override def get(): InternalRow = {
+ row
+ }
+
+ override def currentMetricsValues(): Array[CustomTaskMetric] = {
+ val paimonMetricsValues: Array[CustomTaskMetric] = split match {
+ case dataSplit: DataSplit =>
+ val splitSize = dataSplit.dataFiles().asScala.map(_.fileSize).sum
+ Array(
+ PaimonNumSplitsTaskMetric(1L),
+ PaimonSplitSizeTaskMetric(splitSize),
+ PaimonAvgSplitSizeTaskMetric(splitSize)
+ )
+
+ case _ =>
+ Array.empty[CustomTaskMetric]
+ }
+ super.currentMetricsValues() ++ paimonMetricsValues
+ }
+
+ override def close(): Unit = {
+ try {
+ iterator.close()
+ } catch {
+ case e: Exception =>
+ throw new IOException(e)
+ }
+ }
+}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
new file mode 100644
index 000000000..ae3bff2b2
--- /dev/null
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
@@ -0,0 +1,60 @@
+/*
+ * 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.paimon.spark
+
+import org.apache.paimon.data
+import org.apache.paimon.disk.IOManager
+import org.apache.paimon.reader.RecordReader
+import org.apache.paimon.spark.SparkUtils.createIOManager
+import org.apache.paimon.table.source.{ReadBuilder, Split}
+
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.connector.read.{InputPartition, PartitionReader,
PartitionReaderFactory}
+
+import java.util.Objects
+
+case class PaimonPartitionReaderFactory(readBuilder: ReadBuilder) extends
PartitionReaderFactory {
+
+ private lazy val ioManager: IOManager = createIOManager()
+
+ private lazy val row: SparkInternalRow = new
SparkInternalRow(readBuilder.readType())
+
+ override def createReader(partition: InputPartition):
PartitionReader[InternalRow] = {
+ partition match {
+ case paimonInputPartition: SparkInputPartition =>
+ val readFunc: Split => RecordReader[data.InternalRow] =
+ (split: Split) =>
readBuilder.newRead().withIOManager(ioManager).createReader(split)
+ PaimonPartitionReader(readFunc, paimonInputPartition, row)
+ case _ =>
+ throw new RuntimeException(s"It's not a Paimon input partition,
$partition")
+ }
+ }
+
+ override def equals(obj: Any): Boolean = {
+ obj match {
+ case other: PaimonPartitionReaderFactory =>
+ this.readBuilder.equals(other.readBuilder)
+
+ case _ => false
+ }
+ }
+
+ override def hashCode(): Int = {
+ Objects.hashCode(readBuilder)
+ }
+}
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
index 702065a13..0777be4ae 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
@@ -18,7 +18,7 @@
package org.apache.paimon.spark.sources
import org.apache.paimon.options.Options
-import org.apache.paimon.spark.{PaimonImplicits, SparkConnectorOptions,
SparkInputPartition, SparkReaderFactory}
+import org.apache.paimon.spark.{PaimonImplicits, PaimonPartitionReaderFactory,
SparkConnectorOptions, SparkInputPartition}
import org.apache.paimon.table.DataTable
import org.apache.paimon.table.source.ReadBuilder
@@ -126,7 +126,7 @@ class PaimonMicroBatchStream(
}
override def createReaderFactory(): PartitionReaderFactory = {
- new SparkReaderFactory(readBuilder)
+ new PaimonPartitionReaderFactory(readBuilder)
}
override def initialOffset(): Offset = {
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/Utils.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/Utils.scala
index 058664270..e8f1b418a 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/Utils.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/Utils.scala
@@ -22,6 +22,7 @@ import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
import org.apache.spark.sql.connector.expressions.{FieldReference,
NamedReference}
import org.apache.spark.sql.execution.datasources.DataSourceStrategy
import org.apache.spark.sql.sources.Filter
+import org.apache.spark.util.{Utils => SparkUtils}
/**
* Some classes or methods defined in the spark project are marked as private
under
@@ -63,4 +64,8 @@ object Utils {
def fieldReference(name: String): NamedReference = {
FieldReference.column(name)
}
+
+ def bytesToString(size: Long): String = {
+ SparkUtils.bytesToString(size)
+ }
}