This is an automated email from the ASF dual-hosted git repository.
richox pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/auron.git
The following commit(s) were added to refs/heads/master by this push:
new 52139936 [AURON-1332] Introduce JniBridge and AuronAdaptor for
auron-core module (#1333)
52139936 is described below
commit 52139936ca4712de6b964b4ff674f97f1287d318
Author: zhangmang <[email protected]>
AuthorDate: Sun Sep 28 17:47:37 2025 +0800
[AURON-1332] Introduce JniBridge and AuronAdaptor for auron-core module
(#1333)
* [AURON-1332] Introduce JniBridge and AuronAdaptor for common module
* [AURON #1332] Introduce JniBridge and AuronAdaptor for auron-core module
* remove pom dependency
* add AuronTestUtils
* fix scala verison
* add auron prefix
---
auron-core/pom.xml | 38 +++++
.../auron/configuration/AuronConfiguration.java | 16 ++
.../java/org/apache/auron/jni/AuronAdaptor.java | 122 ++++++++++++++
.../apache/auron/jni/AuronCallNativeWrapper.java | 187 +++++++++++++++++++++
.../main/java/org/apache/auron/jni/JniBridge.java | 99 +++++++++++
.../apache/auron/memory/OnHeapSpillManager.java | 125 ++++++++++++++
.../java/org/apache/auron/metric/MetricNode.java | 50 ++++++
.../org/apache/auron/jni/AuronAdaptorTest.java | 41 +++++
.../org/apache/auron/jni/MockAuronAdaptor.java | 35 ++++
9 files changed, 713 insertions(+)
diff --git a/auron-core/pom.xml b/auron-core/pom.xml
index fd07f7b0..67d6cfc9 100644
--- a/auron-core/pom.xml
+++ b/auron-core/pom.xml
@@ -37,6 +37,44 @@
<version>1.3.9</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.arrow</groupId>
+ <artifactId>arrow-c-data</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.arrow</groupId>
+ <artifactId>arrow-compression</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.arrow</groupId>
+ <artifactId>arrow-memory-unsafe</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.arrow</groupId>
+ <artifactId>arrow-vector</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.auron</groupId>
+ <artifactId>proto</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.auron</groupId>
+ <artifactId>hadoop-shim_${scalaVersion}</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-client-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
diff --git
a/auron-core/src/main/java/org/apache/auron/configuration/AuronConfiguration.java
b/auron-core/src/main/java/org/apache/auron/configuration/AuronConfiguration.java
index 33861fa8..cd04569e 100644
---
a/auron-core/src/main/java/org/apache/auron/configuration/AuronConfiguration.java
+++
b/auron-core/src/main/java/org/apache/auron/configuration/AuronConfiguration.java
@@ -23,6 +23,22 @@ import java.util.Optional;
*/
public abstract class AuronConfiguration {
+ public static final ConfigOption<Integer> BATCH_SIZE =
ConfigOptions.key("auron.batchSize")
+ .description("Suggested batch size for arrow batches.")
+ .intType()
+ .defaultValue(10000);
+
+ public static final ConfigOption<Double> MEMORY_FRACTION =
ConfigOptions.key("auron.memoryFraction")
+ .description("Suggested fraction of off-heap memory used in native
execution. "
+ + "actual off-heap memory usage is expected to be
spark.executor.memoryOverhead * fraction.")
+ .doubleType()
+ .defaultValue(0.6);
+
+ public static final ConfigOption<String> NATIVE_LOG_LEVEL =
ConfigOptions.key("auron.native.log.level")
+ .description("Log level for native execution.")
+ .stringType()
+ .defaultValue("info");
+
public abstract <T> Optional<T> getOptional(ConfigOption<T> option);
public abstract <T> Optional<T> getOptional(String key);
diff --git a/auron-core/src/main/java/org/apache/auron/jni/AuronAdaptor.java
b/auron-core/src/main/java/org/apache/auron/jni/AuronAdaptor.java
new file mode 100644
index 00000000..4c1d27b8
--- /dev/null
+++ b/auron-core/src/main/java/org/apache/auron/jni/AuronAdaptor.java
@@ -0,0 +1,122 @@
+/*
+ * 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.auron.jni;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.auron.configuration.AuronConfiguration;
+import org.apache.auron.memory.OnHeapSpillManager;
+
+/**
+ * Auron Adaptor abstraction for Auron Native engine.
+ * Each engine must implement its own {@link AuronAdaptor} to interact with
the Auron Native engine.
+ */
+public abstract class AuronAdaptor {
+
+ /**
+ * The static instance of the AuronAdaptor.
+ */
+ private static AuronAdaptor INSTANCE = null;
+
+ /**
+ * Initializes a static instance of the AuronAdaptor.
+ *
+ * @param auronAdaptor The implementation of AuronAdaptor to be set as the
static instance.
+ */
+ public static synchronized void initInstance(AuronAdaptor auronAdaptor) {
+ if (INSTANCE == null) {
+ INSTANCE = auronAdaptor;
+ }
+ }
+
+ /**
+ * Retrieves the static instance of the AuronAdaptor.
+ *
+ * @return The current AuronAdaptor instance, or null if none has been set.
+ */
+ public static AuronAdaptor getInstance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Loads the native Auron library (libauron.dylib or equivalent).
+ * <p>This method must be implemented by subclasses to provide the native
library loading logic.</p>
+ */
+ public abstract void loadAuronLib();
+
+ /**
+ * Retrieves the limit for JVM total memory usage.
+ *
+ * @return The maximum allowed memory usage (in bytes), defaulting to
Long.MAX_VALUE.
+ */
+ public long getJVMTotalMemoryLimited() {
+ return Long.MAX_VALUE;
+ }
+
+ /**
+ * Checks if a task is currently running, false to abort native
computation.
+ *
+ * @return true if a task is running, false otherwise. Default
implementation returns true.
+ */
+ public boolean isTaskRunning() {
+ return true;
+ }
+
+ /**
+ * Creates a temporary file for direct write spill-to-disk operations.
+ *
+ * @return Absolute path of the created temporary file.
+ * @throws IOException If the temporary file cannot be created.
+ */
+ public String getDirectWriteSpillToDiskFile() throws IOException {
+ File tempFile = File.createTempFile("auron-spill-", ".tmp");
+ tempFile.deleteOnExit();
+ return tempFile.getAbsolutePath();
+ }
+
+ /**
+ * Retrieves the context classloader of the current thread.
+ *
+ * @return The context classloader of the current thread.
+ */
+ public Object getThreadContext() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ /**
+ * Sets the context classloader for the current thread.
+ *
+ * @param context The classloader to set as the context classloader.
+ */
+ public void setThreadContext(Object context) {
+ Thread.currentThread().setContextClassLoader((ClassLoader) context);
+ }
+
+ /**
+ * Retrieves the on-heap spill manager implementation.
+ *
+ * @return An instance of OnHeapSpillManager, defaulting to a disabled
manager.
+ */
+ public OnHeapSpillManager getOnHeapSpillManager() {
+ return OnHeapSpillManager.getDisabledOnHeapSpillManager();
+ }
+
+ /**
+ * Retrieves the AuronConfiguration, It bundles the corresponding engine's
Config.
+ */
+ public abstract AuronConfiguration getAuronConfiguration();
+}
diff --git
a/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java
b/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java
new file mode 100644
index 00000000..cbdf1f86
--- /dev/null
+++ b/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java
@@ -0,0 +1,187 @@
+/*
+ * 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.auron.jni;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import org.apache.arrow.c.ArrowArray;
+import org.apache.arrow.c.ArrowSchema;
+import org.apache.arrow.c.CDataDictionaryProvider;
+import org.apache.arrow.c.Data;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.types.pojo.Schema;
+import org.apache.auron.configuration.AuronConfiguration;
+import org.apache.auron.metric.MetricNode;
+import org.apache.auron.protobuf.PartitionId;
+import org.apache.auron.protobuf.PhysicalPlanNode;
+import org.apache.auron.protobuf.TaskDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuronCallNativeWrapper {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AuronCallNativeWrapper.class);
+
+ private final PhysicalPlanNode nativePlan;
+ private final MetricNode metrics;
+ private final BufferAllocator arrowAllocator;
+ private final int partitionId;
+ private final int stageId;
+ private final int taskId;
+ private final AtomicReference<Throwable> error = new
AtomicReference<>(null);
+ private final CDataDictionaryProvider dictionaryProvider = new
CDataDictionaryProvider();
+ private Schema arrowSchema;
+ private long nativeRuntimePtr;
+ private Consumer<VectorSchemaRoot> batchConsumer;
+
+ // initialize native environment
+ static {
+ LOG.info("Initializing native environment (batchSize="
+ +
AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.BATCH_SIZE)
+ ", "
+ + "memoryFraction="
+ +
AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.MEMORY_FRACTION)
+ ")");
+
+ // arrow configuration
+ System.setProperty("arrow.struct.conflict.policy", "CONFLICT_APPEND");
+
+ // preload JNI bridge classes
+ try {
+ Class.forName("org.apache.auron.jni.JniBridge");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Cannot load JniBridge class", e);
+ }
+
+ AuronAdaptor.getInstance().loadAuronLib();
+ Runtime.getRuntime().addShutdownHook(new Thread(JniBridge::onExit));
+ }
+
+ public AuronCallNativeWrapper(
+ BufferAllocator arrowAllocator,
+ PhysicalPlanNode nativePlan,
+ MetricNode metrics,
+ int partitionId,
+ int stageId,
+ int taskId,
+ long nativeMemory) {
+ this.arrowAllocator = arrowAllocator;
+ this.nativePlan = nativePlan;
+ this.metrics = metrics;
+ this.partitionId = partitionId;
+ this.stageId = stageId;
+ this.taskId = taskId;
+
+ LOG.warn("Start executing native plan");
+ this.nativeRuntimePtr = JniBridge.callNative(
+ nativeMemory,
+
AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.NATIVE_LOG_LEVEL),
+ this);
+ }
+
+ /**
+ * Loads and processes the next batch of data from the native plan.
+ * <p>
+ * This method attempts to fetch the next batch of data from the native
execution runtime.
+ * If successful, it passes the data to the provided consumer callback. If
the native runtime
+ * pointer is invalid (0), or if there are no more batches, the method
returns false.
+ * </p>
+ *
+ * @param batchConsumer The consumer lambda that processes the loaded data
batch.
+ * This lambda receives a `VectorSchemaRoot` containing the
batch data.
+ * @return true If a batch was successfully loaded and processed; false if
no more batches are available.
+ * @throws RuntimeException If the native runtime encounters an error
during batch processing.
+ */
+ public boolean loadNextBatch(Consumer<VectorSchemaRoot> batchConsumer) {
+ // load next batch
+ try {
+ this.batchConsumer = batchConsumer;
+ if (nativeRuntimePtr != 0 &&
JniBridge.nextBatch(nativeRuntimePtr)) {
+ return true;
+ }
+ } finally {
+ // if error has been set, throw set error instead of this caught
exception
+ checkError();
+ }
+
+ // if no more batches, close the native runtime and return false
+ close();
+ return false;
+ }
+
+ protected MetricNode getMetrics() {
+ return metrics;
+ }
+
+ protected void importSchema(long ffiSchemaPtr) {
+ try (ArrowSchema ffiSchema = ArrowSchema.wrap(ffiSchemaPtr)) {
+ arrowSchema = Data.importSchema(arrowAllocator, ffiSchema,
dictionaryProvider);
+ }
+ }
+
+ protected void importBatch(long ffiArrayPtr) {
+ if (nativeRuntimePtr == 0) {
+ throw new RuntimeException("Native runtime is finalized");
+ }
+
+ try (ArrowArray ffiArray = ArrowArray.wrap(ffiArrayPtr)) {
+ try (VectorSchemaRoot root = VectorSchemaRoot.create(arrowSchema,
arrowAllocator)) {
+ Data.importIntoVectorSchemaRoot(arrowAllocator, ffiArray,
root, dictionaryProvider);
+ batchConsumer.accept(root);
+ }
+ }
+ }
+
+ protected void setError(Throwable error) {
+ this.error.set(error);
+ }
+
+ protected void checkError() {
+ Throwable throwable = error.getAndSet(null);
+ if (throwable != null) {
+ close();
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ protected byte[] getRawTaskDefinition() {
+ PartitionId partition = PartitionId.newBuilder()
+ .setPartitionId(partitionId)
+ .setStageId(stageId)
+ .setTaskId(taskId)
+ .build();
+
+ TaskDefinition taskDefinition = TaskDefinition.newBuilder()
+ .setTaskId(partition)
+ .setPlan(nativePlan)
+ .build();
+
+ return taskDefinition.toByteArray();
+ }
+
+ private synchronized void close() {
+ if (nativeRuntimePtr != 0) {
+ JniBridge.finalizeNative(nativeRuntimePtr);
+ nativeRuntimePtr = 0;
+ try {
+ dictionaryProvider.close();
+ } catch (Exception e) {
+ LOG.error("Error closing dictionary provider", e);
+ }
+ checkError();
+ }
+ }
+}
diff --git a/auron-core/src/main/java/org/apache/auron/jni/JniBridge.java
b/auron-core/src/main/java/org/apache/auron/jni/JniBridge.java
new file mode 100644
index 00000000..85831550
--- /dev/null
+++ b/auron-core/src/main/java/org/apache/auron/jni/JniBridge.java
@@ -0,0 +1,99 @@
+/*
+ * 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.auron.jni;
+
+import java.io.IOException;
+import java.lang.management.BufferPoolMXBean;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.auron.hadoop.fs.FSDataInputWrapper;
+import org.apache.auron.hadoop.fs.FSDataOutputWrapper;
+import org.apache.auron.memory.OnHeapSpillManager;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+/**
+ * This class is the entry point for the JNI bridge.
+ */
+@SuppressWarnings("unused")
+public class JniBridge {
+ public static final ConcurrentHashMap<String, Object> resourcesMap = new
ConcurrentHashMap<>();
+
+ private static final List<BufferPoolMXBean> directMXBeans =
+ ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
+
+ public static native long callNative(long initNativeMemory, String
logLevel, AuronCallNativeWrapper wrapper);
+
+ public static native boolean nextBatch(long ptr);
+
+ public static native void finalizeNative(long ptr);
+
+ public static native void onExit();
+
+ public static ClassLoader getContextClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ public static void setContextClassLoader(ClassLoader cl) {
+ Thread.currentThread().setContextClassLoader(cl);
+ }
+
+ public static Object getResource(String key) {
+ return resourcesMap.remove(key);
+ }
+
+ public static FSDataInputWrapper openFileAsDataInputWrapper(FileSystem fs,
String path) throws Exception {
+ // the path is a URI string, so we need to convert it to a URI object
+ return FSDataInputWrapper.wrap(fs.open(new Path(new URI(path))));
+ }
+
+ public static FSDataOutputWrapper createFileAsDataOutputWrapper(FileSystem
fs, String path) throws Exception {
+ return FSDataOutputWrapper.wrap(fs.create(new Path(new URI(path))));
+ }
+
+ public static long getDirectMemoryUsed() {
+ return directMXBeans.stream()
+ .mapToLong(BufferPoolMXBean::getTotalCapacity)
+ .sum();
+ }
+
+ public static OnHeapSpillManager getTaskOnHeapSpillManager() {
+ return AuronAdaptor.getInstance().getOnHeapSpillManager();
+ }
+
+ public static long getTotalMemoryLimited() {
+ return AuronAdaptor.getInstance().getJVMTotalMemoryLimited();
+ }
+
+ public static boolean isTaskRunning() {
+ return AuronAdaptor.getInstance().isTaskRunning();
+ }
+
+ public static Object getThreadContext() {
+ return AuronAdaptor.getInstance().getThreadContext();
+ }
+
+ public static void setThreadContext(Object tc) {
+ AuronAdaptor.getInstance().setThreadContext(tc);
+ }
+
+ public static String getDirectWriteSpillToDiskFile() throws IOException {
+ return AuronAdaptor.getInstance().getDirectWriteSpillToDiskFile();
+ }
+}
diff --git
a/auron-core/src/main/java/org/apache/auron/memory/OnHeapSpillManager.java
b/auron-core/src/main/java/org/apache/auron/memory/OnHeapSpillManager.java
new file mode 100644
index 00000000..5f98f8e1
--- /dev/null
+++ b/auron-core/src/main/java/org/apache/auron/memory/OnHeapSpillManager.java
@@ -0,0 +1,125 @@
+/*
+ * 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.auron.memory;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Interface for managing on-heap spill operations.
+ * This interface provides methods to handle memory spilling to disk when
on-heap memory is insufficient.
+ */
+public abstract class OnHeapSpillManager {
+
+ /**
+ * Check if on-heap memory is available for allocation.
+ *
+ * @return true if on-heap memory is available, false otherwise
+ */
+ abstract boolean isOnHeapAvailable();
+
+ /**
+ * Create a new spill operation and return its identifier.
+ *
+ * @return spill identifier for the newly created spill
+ */
+ abstract int newSpill();
+
+ /**
+ * Write data from a ByteBuffer to the spill identified by spillId.
+ *
+ * @param spillId the identifier of the spill to write to
+ * @param buffer the ByteBuffer containing data to be written
+ */
+ abstract void writeSpill(int spillId, ByteBuffer buffer);
+
+ /**
+ * Read data from the spill identified by spillId into the provided
ByteBuffer.
+ *
+ * @param spillId the identifier of the spill to read from
+ * @param buffer the ByteBuffer to read data into
+ * @return the number of bytes actually read
+ */
+ abstract int readSpill(int spillId, ByteBuffer buffer);
+
+ /**
+ * Get the disk usage in bytes for the spill identified by spillId.
+ *
+ * @param spillId the identifier of the spill
+ * @return the disk usage in bytes
+ */
+ abstract long getSpillDiskUsage(int spillId);
+
+ /**
+ * Get the total disk I/O time in nanoseconds for the spill identified by
spillId.
+ *
+ * @param spillId the identifier of the spill
+ * @return the disk I/O time in nanoseconds
+ */
+ abstract long getSpillDiskIOTime(int spillId);
+
+ /**
+ * Release and clean up resources associated with the spill identified by
spillId.
+ *
+ * @param spillId the identifier of the spill to release
+ */
+ abstract void releaseSpill(int spillId);
+
+ /**
+ * Get the disabled on-heap spill manager instance.
+ *
+ * @return the disabled on-heap spill manager instance
+ */
+ public static OnHeapSpillManager getDisabledOnHeapSpillManager() {
+ return new OnHeapSpillManager() {
+
+ @Override
+ boolean isOnHeapAvailable() {
+ return false;
+ }
+
+ @Override
+ int newSpill() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ void writeSpill(int spillId, ByteBuffer buffer) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ int readSpill(int spillId, ByteBuffer buffer) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ long getSpillDiskUsage(int spillId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ long getSpillDiskIOTime(int spillId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ void releaseSpill(int spillId) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/auron-core/src/main/java/org/apache/auron/metric/MetricNode.java
b/auron-core/src/main/java/org/apache/auron/metric/MetricNode.java
new file mode 100644
index 00000000..06e517fb
--- /dev/null
+++ b/auron-core/src/main/java/org/apache/auron/metric/MetricNode.java
@@ -0,0 +1,50 @@
+/*
+ * 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.auron.metric;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstract class representing a metric node in the Auron system.
+ * This class provides functionality for hierarchical metrics tracking.
+ */
+public abstract class MetricNode {
+ private final List<MetricNode> children = new ArrayList<>();
+
+ public MetricNode(List<MetricNode> children) {
+ this.children.addAll(children);
+ }
+
+ /**
+ * Gets a child metric node with the specified ID.
+ *
+ * @param id The identifier for the child node
+ * @return The child MetricNode associated with the given ID
+ */
+ public MetricNode getChild(int id) {
+ return children.get(id);
+ }
+
+ /**
+ * Adds a metric value with a specified name.
+ *
+ * @param name The name of the metric
+ * @param value The value to add for the metric (as a long)
+ */
+ public abstract void add(String name, long value);
+}
diff --git
a/auron-core/src/test/java/org/apache/auron/jni/AuronAdaptorTest.java
b/auron-core/src/test/java/org/apache/auron/jni/AuronAdaptorTest.java
new file mode 100644
index 00000000..d24b5591
--- /dev/null
+++ b/auron-core/src/test/java/org/apache/auron/jni/AuronAdaptorTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.auron.jni;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.auron.configuration.AuronConfiguration;
+import org.junit.Test;
+
+/**
+ * This is a test class for {@link AuronAdaptor}.
+ */
+public class AuronAdaptorTest {
+
+ @Test
+ public void testLoadAuronLib() {
+ MockAuronAdaptor auronAdaptor = new MockAuronAdaptor();
+ AuronAdaptor.initInstance(auronAdaptor);
+ AuronAdaptor.getInstance().loadAuronLib();
+ assertNotNull(AuronAdaptor.getInstance().getAuronConfiguration());
+ AuronConfiguration auronConfig =
AuronAdaptor.getInstance().getAuronConfiguration();
+ assertEquals(auronConfig.getInteger(AuronConfiguration.BATCH_SIZE),
10000);
+
assertEquals(auronConfig.getDouble(AuronConfiguration.MEMORY_FRACTION), 0.6);
+
assertEquals(auronConfig.getString(AuronConfiguration.NATIVE_LOG_LEVEL),
"info");
+ }
+}
diff --git
a/auron-core/src/test/java/org/apache/auron/jni/MockAuronAdaptor.java
b/auron-core/src/test/java/org/apache/auron/jni/MockAuronAdaptor.java
new file mode 100644
index 00000000..c2cac51b
--- /dev/null
+++ b/auron-core/src/test/java/org/apache/auron/jni/MockAuronAdaptor.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.auron.jni;
+
+import org.apache.auron.configuration.AuronConfiguration;
+import org.apache.auron.configuration.MockAuronConfiguration;
+
+/**
+ * This is a mock class for testing the AuronAdaptor.
+ */
+public class MockAuronAdaptor extends AuronAdaptor {
+ @Override
+ public void loadAuronLib() {
+ // Mock implementation, no need to load auron library
+ }
+
+ @Override
+ public AuronConfiguration getAuronConfiguration() {
+ return new MockAuronConfiguration();
+ }
+}