From d12f5734e64a56fd45c9fe09c1f277acf83af2e2 Mon Sep 17 00:00:00 2001
From: DanGuge <77946882+DanGuge@users.noreply.github.com>
Date: Sat, 19 Aug 2023 13:41:08 +0800
Subject: [PATCH] feat(api&core):  in oltp apis, add statistics info and
 support full info about vertices and edges (#2262)

* chore: improve gitignore file

* feat: add ApiMeasure to collect runtime data

ApiMeasure will count the number of vertices and edges traversed at runtime, and the time the api takes to execute

* feat: Add ApiMeasure to JsonSerializer and Modify the Serializer interface

* JsonSerializer: return measure information in api response

* Serializer: fit the feature that returns complete information about vertices and edges

* refactor: format code based on hugegraph-style.xml

* feat: Add statistics information in all oltp restful apis response and Support full information about vertices and edges

Statistics information:

* add vertexIterCounter and edgeIterCounter in HugeTraverser.java to track traversed vertices and edges at run time

* modify all oltp restful apis to add statistics information in response

Full information about vertices and edges:

* add 'with_vertex' and 'with_edge' parameter option in apis

* modify oltp apis to support vertex and edge information in api response

* add EdgeRecord in HugeTraverser.java to record edges at run time and generate the edge information returned in api response

* modify Path and PathSet in HugeTraverser.java to support full edge information storage

* modify all traversers to support track of edge information at run time

* fix: numeric cast

* fix: Jaccard Similarity api test

* fix: adjust the code style and naming convention

* Empty commit

* Empty commit

* fix:
1. change System.currentTimeMillis() to System.nanoTime();
2. modify addCount()

* fix: rollback change in .gitignore

* fix: rollback ServerOptions.java code style

* fix: rollback API.java code style and add exception in else branch

* fix: fix code style

* fix: name style & code style
* rename edgeRecord to edgeResults
* fix Request class code style in SameNeighborsAPI.java
---
 .../java/org/apache/hugegraph/api/API.java    |  89 ++-
 .../api/traversers/AllShortestPathsAPI.java   |  73 ++-
 .../api/traversers/CrosspointsAPI.java        |  30 +-
 .../traversers/CustomizedCrosspointsAPI.java  | 125 ++--
 .../api/traversers/CustomizedPathsAPI.java    | 103 ++--
 .../hugegraph/api/traversers/EdgesAPI.java    |  28 +-
 .../api/traversers/FusiformSimilarityAPI.java |  66 ++-
 .../api/traversers/JaccardSimilarityAPI.java  |  55 +-
 .../api/traversers/KneighborAPI.java          |  78 ++-
 .../hugegraph/api/traversers/KoutAPI.java     |  89 ++-
 .../traversers/MultiNodeShortestPathAPI.java  |  82 +--
 .../hugegraph/api/traversers/PathsAPI.java    | 102 ++--
 .../hugegraph/api/traversers/RaysAPI.java     |  67 ++-
 .../hugegraph/api/traversers/RingsAPI.java    |  74 ++-
 .../api/traversers/SameNeighborsAPI.java      | 108 +++-
 .../api/traversers/ShortestPathAPI.java       |  67 ++-
 .../SingleSourceShortestPathAPI.java          |  79 ++-
 .../api/traversers/TemplatePathsAPI.java      | 104 ++--
 .../hugegraph/api/traversers/VerticesAPI.java |  28 +-
 .../traversers/WeightedShortestPathAPI.java   |  84 ++-
 .../hugegraph/config/ServerOptions.java       |   2 +-
 .../apache/hugegraph/core/GraphManager.java   |   4 +
 .../hugegraph/serializer/JsonSerializer.java  | 153 +++--
 .../hugegraph/serializer/Serializer.java      |  24 +-
 .../comm/TriangleCountAlgorithm.java          |  38 +-
 .../algorithm/CollectionPathsTraverser.java   |  58 +-
 .../algorithm/CustomizePathsTraverser.java    |  82 +--
 .../CustomizedCrosspointsTraverser.java       | 133 +++--
 .../FusiformSimilarityTraverser.java          |  67 ++-
 .../traversal/algorithm/HugeTraverser.java    | 541 +++++++++++-------
 .../algorithm/JaccardSimilarTraverser.java    |  49 +-
 .../algorithm/KneighborTraverser.java         |  16 +-
 .../traversal/algorithm/KoutTraverser.java    |  24 +-
 .../MultiNodeShortestPathTraverser.java       | 117 ++--
 .../traversal/algorithm/PathTraverser.java    |  25 +-
 .../traversal/algorithm/PathsTraverser.java   |  23 +-
 .../algorithm/SameNeighborTraverser.java      |  57 +-
 .../algorithm/ShortestPathTraverser.java      |  73 ++-
 .../SingleSourceShortestPathTraverser.java    | 206 ++++---
 .../algorithm/SubGraphTraverser.java          | 175 +++---
 .../algorithm/TemplatePathsTraverser.java     |  58 +-
 .../records/ShortestPathRecords.java          |   4 +-
 .../records/SingleWayMultiPathsRecords.java   |  18 +-
 .../traversers/JaccardSimilarityApiTest.java  |  12 +-
 44 files changed, 2268 insertions(+), 1222 deletions(-)

diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java
index afaba499b3..99fe67e5ba 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java
@@ -22,41 +22,39 @@
 import java.util.concurrent.Callable;
 import java.util.function.Consumer;
 
-import jakarta.ws.rs.ForbiddenException;
-import jakarta.ws.rs.NotFoundException;
-import jakarta.ws.rs.NotSupportedException;
-import jakarta.ws.rs.core.MediaType;
-
+import org.apache.commons.lang.mutable.MutableLong;
+import org.apache.hugegraph.HugeException;
+import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.define.Checkable;
 import org.apache.hugegraph.metrics.MetricsUtil;
-import org.slf4j.Logger;
-
-import org.apache.hugegraph.HugeException;
-import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.util.E;
+import org.apache.hugegraph.util.InsertionOrderUtil;
 import org.apache.hugegraph.util.JsonUtil;
 import org.apache.hugegraph.util.Log;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.Meter;
 import com.google.common.collect.ImmutableMap;
 
-public class API {
+import jakarta.ws.rs.ForbiddenException;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.NotSupportedException;
+import jakarta.ws.rs.core.MediaType;
 
-    protected static final Logger LOG = Log.logger(API.class);
+public class API {
 
     public static final String CHARSET = "UTF-8";
-
     public static final String TEXT_PLAIN = MediaType.TEXT_PLAIN;
     public static final String APPLICATION_JSON = MediaType.APPLICATION_JSON;
     public static final String APPLICATION_JSON_WITH_CHARSET =
                                APPLICATION_JSON + ";charset=" + CHARSET;
     public static final String JSON = MediaType.APPLICATION_JSON_TYPE
                                                .getSubtype();
-
     public static final String ACTION_APPEND = "append";
     public static final String ACTION_ELIMINATE = "eliminate";
     public static final String ACTION_CLEAR = "clear";
-
+    protected static final Logger LOG = Log.logger(API.class);
     private static final Meter SUCCEED_METER =
                          MetricsUtil.registerMeter(API.class, "commit-succeed");
     private static final Meter ILLEGAL_ARG_ERROR_METER =
@@ -69,8 +67,7 @@ public class API {
     public static HugeGraph graph(GraphManager manager, String graph) {
         HugeGraph g = manager.graph(graph);
         if (g == null) {
-            throw new NotFoundException(String.format(
-                      "Graph '%s' does not exist",  graph));
+            throw new NotFoundException(String.format("Graph '%s' does not exist", graph));
         }
         return g;
     }
@@ -140,8 +137,7 @@ protected static void checkUpdatingBody(Checkable body) {
         body.checkUpdate();
     }
 
-    protected static void checkCreatingBody(
-                          Collection<? extends Checkable> bodies) {
+    protected static void checkCreatingBody(Collection<? extends Checkable> bodies) {
         E.checkArgumentNotNull(bodies, "The request body can't be empty");
         for (Checkable body : bodies) {
             E.checkArgument(body != null,
@@ -150,8 +146,7 @@ protected static void checkCreatingBody(
         }
     }
 
-    protected static void checkUpdatingBody(
-                          Collection<? extends Checkable> bodies) {
+    protected static void checkUpdatingBody(Collection<? extends Checkable> bodies) {
         E.checkArgumentNotNull(bodies, "The request body can't be empty");
         for (Checkable body : bodies) {
             E.checkArgumentNotNull(body,
@@ -186,8 +181,58 @@ public static boolean checkAndParseAction(String action) {
         } else if (action.equals(ACTION_ELIMINATE)) {
             return false;
         } else {
-            throw new NotSupportedException(
-                      String.format("Not support action '%s'", action));
+            throw new NotSupportedException(String.format("Not support action '%s'", action));
+        }
+    }
+
+    public static class ApiMeasurer {
+
+        public static final String EDGE_ITER = "edge_iterations";
+        public static final String VERTICE_ITER = "vertice_iterations";
+        public static final String COST = "cost(ns)";
+        private final long timeStart;
+        private final Map<String, Object> measures;
+
+        public ApiMeasurer() {
+            this.timeStart = System.nanoTime();
+            this.measures = InsertionOrderUtil.newMap();
+        }
+
+        public Map<String, Object> measures() {
+            measures.put(COST, System.nanoTime() - timeStart);
+            return measures;
+        }
+
+        public void put(String key, String value) {
+            this.measures.put(key, value);
+        }
+
+        public void put(String key, long value) {
+            this.measures.put(key, value);
+        }
+
+        public void put(String key, int value) {
+            this.measures.put(key, value);
+        }
+
+        protected void addCount(String key, long value) {
+            Object current = measures.get(key);
+            if (current == null) {
+                measures.put(key, new MutableLong(value));
+            } else if (current instanceof MutableLong) {
+                ((MutableLong) measures.computeIfAbsent(key, MutableLong::new)).add(value);
+            } else if (current instanceof Long) {
+                Long currentLong = (Long) current;
+                measures.put(key, new MutableLong(currentLong + value));
+            } else {
+                throw new NotSupportedException("addCount() method's 'value' datatype must be " +
+                                                "Long or MutableLong");
+            }
+        }
+
+        public void addIterCount(long verticeIters, long edgeIters) {
+            this.addCount(EDGE_ITER, edgeIters);
+            this.addCount(VERTICE_ITER, verticeIters);
         }
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java
index 030c4e8cc1..e432f81ea7 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java
@@ -20,19 +20,10 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
-
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.slf4j.Logger;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
@@ -44,9 +35,22 @@
 import org.apache.hugegraph.traversal.algorithm.ShortestPathTraverser;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.google.common.collect.ImmutableList;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/allshortestpaths")
 @Singleton
 @Tag(name = "AllShortestPathsAPI")
@@ -68,13 +72,20 @@ public String get(@Context GraphManager manager,
                       @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree,
                       @QueryParam("skip_degree")
                       @DefaultValue("0") long skipDegree,
+                      @QueryParam("with_vertex")
+                      @DefaultValue("false") boolean withVertex,
+                      @QueryParam("with_edge")
+                      @DefaultValue("false") boolean withEdge,
                       @QueryParam("capacity")
                       @DefaultValue(DEFAULT_CAPACITY) long capacity) {
         LOG.debug("Graph [{}] get shortest path from '{}', to '{}' with " +
                   "direction {}, edge label {}, max depth '{}', " +
-                  "max degree '{}', skipped degree '{}' and capacity '{}'",
+                  "max degree '{}', skipped degree '{}', capacity '{}', " +
+                  "with_vertex '{}' and with_edge '{}'",
                   graph, source, target, direction, edgeLabel, depth,
-                  maxDegree, skipDegree, capacity);
+                  maxDegree, skipDegree, capacity, withVertex, withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Id targetId = VertexAPI.checkAndParseVertexId(target);
@@ -85,9 +96,35 @@ public String get(@Context GraphManager manager,
         ShortestPathTraverser traverser = new ShortestPathTraverser(g);
         List<String> edgeLabels = edgeLabel == null ? ImmutableList.of() :
                                   ImmutableList.of(edgeLabel);
-        HugeTraverser.PathSet paths = traverser.allShortestPaths(
-                                      sourceId, targetId, dir, edgeLabels,
-                                      depth, maxDegree, skipDegree, capacity);
-        return manager.serializer(g).writePaths("paths", paths, false);
+        HugeTraverser.PathSet paths = traverser.allShortestPaths(sourceId, targetId, dir,
+                                                                 edgeLabels, depth, maxDegree,
+                                                                 skipDegree, capacity);
+
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
+        }
+        if (withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = paths.getEdges();
+        if (withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
+        }
+
+        return manager.serializer(g, measure.measures())
+                      .writePaths("paths", paths, false,
+                                  iterVertex, iterEdge);
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java
index 39de473b8f..eda042511c 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java
@@ -21,18 +21,6 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.graph.EdgeAPI;
@@ -43,8 +31,20 @@
 import org.apache.hugegraph.traversal.algorithm.PathsTraverser;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.Log;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/crosspoints")
 @Singleton
 @Tag(name = "CrosspointsAPI")
@@ -74,6 +74,7 @@ public String get(@Context GraphManager manager,
                   graph, source, target, direction, edgeLabel,
                   depth, maxDegree, capacity, limit);
 
+        ApiMeasurer measure = new ApiMeasurer();
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Id targetId = VertexAPI.checkAndParseVertexId(target);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -84,6 +85,9 @@ public String get(@Context GraphManager manager,
                                                       dir, edgeLabel, depth,
                                                       maxDegree, capacity,
                                                       limit);
-        return manager.serializer(g).writePaths("crosspoints", paths, true);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+        return manager.serializer(g, measure.measures())
+                      .writePaths("crosspoints", paths, true);
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java
index da35f7325f..cadbd2ce00 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java
@@ -22,38 +22,39 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonAlias;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/customizedcrosspoints")
 @Singleton
 @Tag(name = "CustomizedCrosspointsAPI")
@@ -61,6 +62,21 @@ public class CustomizedCrosspointsAPI extends API {
 
     private static final Logger LOG = Log.logger(CustomizedCrosspointsAPI.class);
 
+    private static List<CustomizedCrosspointsTraverser.PathPattern> pathPatterns(
+            HugeGraph graph, CrosspointsRequest request) {
+        int stepSize = request.pathPatterns.size();
+        List<CustomizedCrosspointsTraverser.PathPattern> pathPatterns = new ArrayList<>(stepSize);
+        for (PathPattern pattern : request.pathPatterns) {
+            CustomizedCrosspointsTraverser.PathPattern pathPattern =
+                    new CustomizedCrosspointsTraverser.PathPattern();
+            for (Step step : pattern.steps) {
+                pathPattern.add(step.jsonToStep(graph));
+            }
+            pathPatterns.add(pathPattern);
+        }
+        return pathPatterns;
+    }
+
     @POST
     @Timed
     @Consumes(APPLICATION_JSON)
@@ -78,55 +94,56 @@ public String post(@Context GraphManager manager,
                         "The steps of crosspoints request can't be empty");
 
         LOG.debug("Graph [{}] get customized crosspoints from source vertex " +
-                  "'{}', with path_pattern '{}', with_path '{}', with_vertex " +
-                  "'{}', capacity '{}' and limit '{}'", graph, request.sources,
-                  request.pathPatterns, request.withPath, request.withVertex,
-                  request.capacity, request.limit);
+                  "'{}', with path_pattern '{}', with path '{}', with_vertex " +
+                  "'{}', capacity '{}', limit '{}' and with_edge '{}'",
+                  graph, request.sources, request.pathPatterns, request.withPath,
+                  request.withVertex, request.capacity, request.limit, request.withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         HugeGraph g = graph(manager, graph);
         Iterator<Vertex> sources = request.sources.vertices(g);
-        List<CustomizedCrosspointsTraverser.PathPattern> patterns;
-        patterns = pathPatterns(g, request);
 
         CustomizedCrosspointsTraverser traverser =
-                                       new CustomizedCrosspointsTraverser(g);
-        CustomizedCrosspointsTraverser.CrosspointsPaths paths;
-        paths = traverser.crosspointsPaths(sources, patterns, request.capacity,
-                                           request.limit);
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (!request.withVertex) {
-            return manager.serializer(g).writeCrosspoints(paths, iter,
-                                                          request.withPath);
-        }
-        Set<Id> ids = new HashSet<>();
+                new CustomizedCrosspointsTraverser(g);
+
+        List<CustomizedCrosspointsTraverser.PathPattern> patterns = pathPatterns(g, request);
+        CustomizedCrosspointsTraverser.CrosspointsPaths paths =
+                traverser.crosspointsPaths(sources, patterns, request.capacity, request.limit);
+
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
         if (request.withPath) {
-            for (HugeTraverser.Path p : paths.paths()) {
-                ids.addAll(p.vertices());
+            for (HugeTraverser.Path path : paths.paths()) {
+                vertexIds.addAll(path.vertices());
             }
         } else {
-            ids = paths.crosspoints();
+            vertexIds = paths.crosspoints();
         }
-        if (!ids.isEmpty()) {
-            iter = g.vertices(ids.toArray());
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        return manager.serializer(g).writeCrosspoints(paths, iter,
-                                                      request.withPath);
-    }
 
-    private static List<CustomizedCrosspointsTraverser.PathPattern>
-                   pathPatterns(HugeGraph graph, CrosspointsRequest request) {
-        int stepSize = request.pathPatterns.size();
-        List<CustomizedCrosspointsTraverser.PathPattern> pathPatterns;
-        pathPatterns = new ArrayList<>(stepSize);
-        for (PathPattern pattern : request.pathPatterns) {
-            CustomizedCrosspointsTraverser.PathPattern pathPattern;
-            pathPattern = new CustomizedCrosspointsTraverser.PathPattern();
-            for (Step step : pattern.steps) {
-                pathPattern.add(step.jsonToStep(graph));
+        Iterator<?> iterEdge = Collections.emptyIterator();
+        if (request.withPath) {
+            Set<Edge> edges = traverser.edgeResults().getEdges(paths.paths());
+            if (request.withEdge) {
+                iterEdge = edges.iterator();
+            } else {
+                iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
             }
-            pathPatterns.add(pathPattern);
         }
-        return pathPatterns;
+
+        return manager.serializer(g, measure.measures())
+                      .writeCrosspoints(paths, iterVertex,
+                                        iterEdge, request.withPath);
     }
 
     private static class CrosspointsRequest {
@@ -143,14 +160,16 @@ private static class CrosspointsRequest {
         public boolean withPath = false;
         @JsonProperty("with_vertex")
         public boolean withVertex = false;
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
 
         @Override
         public String toString() {
             return String.format("CrosspointsRequest{sourceVertex=%s," +
                                  "pathPatterns=%s,withPath=%s,withVertex=%s," +
-                                 "capacity=%s,limit=%s}", this.sources,
-                                 this.pathPatterns, this.withPath,
-                                 this.withVertex, this.capacity, this.limit);
+                                 "capacity=%s,limit=%s,withEdge=%s}", this.sources,
+                                 this.pathPatterns, this.withPath, this.withVertex,
+                                 this.capacity, this.limit, this.withEdge);
         }
     }
 
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java
index 272009ea24..5641e31193 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java
@@ -30,33 +30,33 @@
 import java.util.Map;
 import java.util.Set;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.CustomizePathsTraverser;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.steps.WeightedEdgeStep;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonAlias;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/customizedpaths")
 @Singleton
 @Tag(name = "CustomizedPathsAPI")
@@ -64,6 +64,16 @@ public class CustomizedPathsAPI extends API {
 
     private static final Logger LOG = Log.logger(CustomizedPathsAPI.class);
 
+    private static List<WeightedEdgeStep> step(HugeGraph graph,
+                                               PathRequest request) {
+        int stepSize = request.steps.size();
+        List<WeightedEdgeStep> steps = new ArrayList<>(stepSize);
+        for (Step step : request.steps) {
+            steps.add(step.jsonToStep(graph));
+        }
+        return steps;
+    }
+
     @POST
     @Timed
     @Consumes(APPLICATION_JSON)
@@ -81,10 +91,12 @@ public String post(@Context GraphManager manager,
         }
 
         LOG.debug("Graph [{}] get customized paths from source vertex '{}', " +
-                  "with steps '{}', sort by '{}', capacity '{}', limit '{}' " +
-                  "and with_vertex '{}'", graph, request.sources, request.steps,
+                  "with steps '{}', sort by '{}', capacity '{}', limit '{}', " +
+                  "with_vertex '{}' and with_edge '{}'", graph, request.sources, request.steps,
                   request.sortBy, request.capacity, request.limit,
-                  request.withVertex);
+                  request.withVertex, request.withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         HugeGraph g = graph(manager, graph);
         Iterator<Vertex> sources = request.sources.vertices(g);
@@ -95,6 +107,8 @@ public String post(@Context GraphManager manager,
         List<HugeTraverser.Path> paths;
         paths = traverser.customizedPaths(sources, steps, sorted,
                                           request.capacity, request.limit);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
 
         if (sorted) {
             boolean incr = request.sortBy == SortBy.INCR;
@@ -102,29 +116,35 @@ public String post(@Context GraphManager manager,
                                                      request.limit);
         }
 
-        if (!request.withVertex) {
-            return manager.serializer(g).writePaths("paths", paths, false);
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
         }
-
-        Set<Id> ids = new HashSet<>();
-        for (HugeTraverser.Path p : paths) {
-            ids.addAll(p.vertices());
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (!ids.isEmpty()) {
-            iter = g.vertices(ids.toArray());
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = traverser.edgeResults().getEdges(paths);
+        if (request.withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
         }
-        return manager.serializer(g).writePaths("paths", paths, false, iter);
+
+        return manager.serializer(g, measure.measures())
+                      .writePaths("paths", paths, false,
+                                  iterVertex, iterEdge);
     }
 
-    private static List<WeightedEdgeStep> step(HugeGraph graph,
-                                               PathRequest req) {
-        int stepSize = req.steps.size();
-        List<WeightedEdgeStep> steps = new ArrayList<>(stepSize);
-        for (Step step : req.steps) {
-            steps.add(step.jsonToStep(graph));
-        }
-        return steps;
+    private enum SortBy {
+        INCR,
+        DECR,
+        NONE
     }
 
     private static class PathRequest {
@@ -142,13 +162,16 @@ private static class PathRequest {
         @JsonProperty("with_vertex")
         public boolean withVertex = false;
 
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
+
         @Override
         public String toString() {
             return String.format("PathRequest{sourceVertex=%s,steps=%s," +
                                  "sortBy=%s,capacity=%s,limit=%s," +
-                                 "withVertex=%s}", this.sources, this.steps,
+                                 "withVertex=%s,withEdge=%s}", this.sources, this.steps,
                                  this.sortBy, this.capacity, this.limit,
-                                 this.withVertex);
+                                 this.withVertex, this.withEdge);
         }
     }
 
@@ -190,10 +213,4 @@ private WeightedEdgeStep jsonToStep(HugeGraph g) {
                                         this.defaultWeight, this.sample);
         }
     }
-
-    private enum SortBy {
-        INCR,
-        DECR,
-        NONE
-    }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java
index ca4909a552..da9dfe1779 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java
@@ -22,32 +22,32 @@
 import java.util.Iterator;
 import java.util.List;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.filter.CompressInterceptor.Compress;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.backend.query.ConditionQuery;
 import org.apache.hugegraph.backend.store.Shard;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.type.HugeType;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/edges")
 @Singleton
 @Tag(name = "EdgesAPI")
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java
index fbb330ae12..1b2273dc4a 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java
@@ -23,32 +23,33 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT;
 
 import java.util.Iterator;
-
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
-import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.FusiformSimilarityTraverser;
 import org.apache.hugegraph.traversal.algorithm.FusiformSimilarityTraverser.SimilarsMap;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/fusiformsimilarity")
 @Singleton
 @Tag(name = "FusiformSimilarityAPI")
@@ -64,7 +65,7 @@ public String post(@Context GraphManager manager,
                        @PathParam("graph") String graph,
                        FusiformSimilarityRequest request) {
         E.checkArgumentNotNull(request, "The fusiform similarity " +
-                               "request body can't be null");
+                                        "request body can't be null");
         E.checkArgumentNotNull(request.sources,
                                "The sources of fusiform similarity " +
                                "request can't be null");
@@ -94,28 +95,37 @@ public String post(@Context GraphManager manager,
                   request.minNeighbors, request.alpha, request.minSimilars,
                   request.groupProperty, request.minGroups);
 
+        ApiMeasurer measure = new ApiMeasurer();
         HugeGraph g = graph(manager, graph);
         Iterator<Vertex> sources = request.sources.vertices(g);
         E.checkArgument(sources != null && sources.hasNext(),
                         "The source vertices can't be empty");
 
-        FusiformSimilarityTraverser traverser =
-                                    new FusiformSimilarityTraverser(g);
+        FusiformSimilarityTraverser traverser = new FusiformSimilarityTraverser(g);
         SimilarsMap result = traverser.fusiformSimilarity(
-                             sources, request.direction, request.label,
-                             request.minNeighbors, request.alpha,
-                             request.minSimilars, request.top,
-                             request.groupProperty, request.minGroups,
-                             request.maxDegree, request.capacity,
-                             request.limit, request.withIntermediary);
+                sources, request.direction, request.label,
+                request.minNeighbors, request.alpha,
+                request.minSimilars, request.top,
+                request.groupProperty, request.minGroups,
+                request.maxDegree, request.capacity,
+                request.limit, request.withIntermediary);
 
         CloseableIterator.closeIterator(sources);
 
-        Iterator<Vertex> iterator = QueryResults.emptyIterator();
-        if (request.withVertex && !result.isEmpty()) {
-            iterator = g.vertices(result.vertices().toArray());
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = result.vertices();
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        return manager.serializer(g).writeSimilars(result, iterator);
+
+        return manager.serializer(g, measure.measures())
+                      .writeSimilars(result, iterVertex);
     }
 
     private static class FusiformSimilarityRequest {
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java
index ff187a5918..d5de80351f 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java
@@ -18,41 +18,40 @@
 package org.apache.hugegraph.api.traversers;
 
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY;
-import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_LIMIT;
+import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 
 import java.util.Map;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.structure.HugeVertex;
-import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.traversal.algorithm.JaccardSimilarTraverser;
+import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
-import org.apache.hugegraph.util.JsonUtil;
 import org.apache.hugegraph.util.Log;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableMap;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/jaccardsimilarity")
 @Singleton
 @Tag(name = "JaccardSimilarityAPI")
@@ -75,6 +74,8 @@ public String get(@Context GraphManager manager,
                   "with direction {}, edge label {} and max degree '{}'",
                   graph, vertex, other, direction, edgeLabel, maxDegree);
 
+        ApiMeasurer measure = new ApiMeasurer();
+
         Id sourceId = VertexAPI.checkAndParseVertexId(vertex);
         Id targetId = VertexAPI.checkAndParseVertexId(other);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -82,12 +83,15 @@ public String get(@Context GraphManager manager,
         HugeGraph g = graph(manager, graph);
         double similarity;
         try (JaccardSimilarTraverser traverser =
-                                     new JaccardSimilarTraverser(g)) {
+                     new JaccardSimilarTraverser(g)) {
             similarity = traverser.jaccardSimilarity(sourceId, targetId, dir,
                                                      edgeLabel, maxDegree);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
         }
-        return JsonUtil.toJson(ImmutableMap.of("jaccard_similarity",
-                                               similarity));
+
+        return manager.serializer(g, measure.measures())
+                      .writeMap(ImmutableMap.of("jaccard_similarity", similarity));
     }
 
     @POST
@@ -110,6 +114,8 @@ public String post(@Context GraphManager manager,
                   graph, request.vertex, request.step,
                   request.top, request.capacity);
 
+        ApiMeasurer measure = new ApiMeasurer();
+
         HugeGraph g = graph(manager, graph);
         Id sourceId = HugeVertex.getIdValue(request.vertex);
 
@@ -117,11 +123,14 @@ public String post(@Context GraphManager manager,
 
         Map<Id, Double> results;
         try (JaccardSimilarTraverser traverser =
-                                     new JaccardSimilarTraverser(g)) {
+                     new JaccardSimilarTraverser(g)) {
             results = traverser.jaccardSimilars(sourceId, step, request.top,
                                                 request.capacity);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
         }
-        return manager.serializer(g).writeMap(results);
+        return manager.serializer(g, measure.measures())
+                      .writeMap(ImmutableMap.of("jaccard_similarity", results));
     }
 
     private static class Request {
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java
index 4a7c0a9515..a0e7d0c4ee 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java
@@ -21,6 +21,7 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -40,12 +41,13 @@
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.slf4j.Logger;
 
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.inject.Singleton;
@@ -75,6 +77,8 @@ public String get(@Context GraphManager manager,
                       @QueryParam("direction") String direction,
                       @QueryParam("label") String edgeLabel,
                       @QueryParam("max_depth") int depth,
+                      @QueryParam("count_only")
+                      @DefaultValue("false") boolean countOnly,
                       @QueryParam("max_degree")
                       @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree,
                       @QueryParam("limit")
@@ -85,6 +89,8 @@ public String get(@Context GraphManager manager,
                   graph, sourceV, direction, edgeLabel, depth,
                   maxDegree, limit);
 
+        ApiMeasurer measure = new ApiMeasurer();
+
         Id source = VertexAPI.checkAndParseVertexId(sourceV);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
 
@@ -94,8 +100,14 @@ public String get(@Context GraphManager manager,
         try (KneighborTraverser traverser = new KneighborTraverser(g)) {
             ids = traverser.kneighbor(source, dir, edgeLabel,
                                       depth, maxDegree, limit);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
+        }
+        if (countOnly) {
+            return manager.serializer(g, measure.measures())
+                          .writeMap(ImmutableMap.of("vertices_size", ids.size()));
         }
-        return manager.serializer(g).writeList("vertices", ids);
+        return manager.serializer(g, measure.measures()).writeList("vertices", ids);
     }
 
     @POST
@@ -111,15 +123,18 @@ public String post(@Context GraphManager manager,
         E.checkArgument(request.step != null,
                         "The steps of request can't be null");
         if (request.countOnly) {
-            E.checkArgument(!request.withVertex && !request.withPath,
-                            "Can't return vertex or path when count only");
+            E.checkArgument(!request.withVertex && !request.withPath && !request.withEdge,
+                            "Can't return vertex, edge or path when count only");
         }
 
         LOG.debug("Graph [{}] get customized kneighbor from source vertex " +
                   "'{}', with step '{}', limit '{}', count_only '{}', " +
-                  "with_vertex '{}' and with_path '{}'",
+                  "with_vertex '{}', with_path '{}' and with_edge '{}'",
                   graph, request.source, request.step, request.limit,
-                  request.countOnly, request.withVertex, request.withPath);
+                  request.countOnly, request.withVertex, request.withPath,
+                  request.withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         HugeGraph g = graph(manager, graph);
         Id sourceId = HugeVertex.getIdValue(request.source);
@@ -131,6 +146,8 @@ public String post(@Context GraphManager manager,
             results = traverser.customizedKneighbor(sourceId, step,
                                                     request.maxDepth,
                                                     request.limit);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
         }
 
         long size = results.size();
@@ -144,20 +161,41 @@ public String post(@Context GraphManager manager,
         if (request.withPath) {
             paths.addAll(results.paths(request.limit));
         }
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (request.withVertex && !request.countOnly) {
-            Set<Id> ids = new HashSet<>(neighbors);
-            if (request.withPath) {
-                for (HugeTraverser.Path p : paths) {
-                    ids.addAll(p.vertices());
-                }
+
+        if (request.countOnly) {
+            return manager.serializer(g, measure.measures())
+                          .writeNodesWithPath("kneighbor", neighbors, size, paths,
+                                              QueryResults.emptyIterator(),
+                                              QueryResults.emptyIterator());
+        }
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>(neighbors);
+        if (request.withPath) {
+            for (HugeTraverser.Path p : paths) {
+                vertexIds.addAll(p.vertices());
             }
-            if (!ids.isEmpty()) {
-                iter = g.vertices(ids.toArray());
+        }
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge = Collections.emptyIterator();
+        if (request.withPath) {
+            Set<Edge> edges = results.edgeResults().getEdges(paths);
+            if (request.withEdge) {
+                iterEdge = edges.iterator();
+            } else {
+                iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
             }
         }
-        return manager.serializer(g).writeNodesWithPath("kneighbor", neighbors,
-                                                        size, paths, iter);
+
+        return manager.serializer(g, measure.measures())
+                      .writeNodesWithPath("kneighbor", neighbors,
+                                          size, paths, iterVertex, iterEdge);
     }
 
     private static class Request {
@@ -176,14 +214,16 @@ private static class Request {
         public boolean withVertex = false;
         @JsonProperty("with_path")
         public boolean withPath = false;
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
 
         @Override
         public String toString() {
             return String.format("PathRequest{source=%s,step=%s,maxDepth=%s" +
                                  "limit=%s,countOnly=%s,withVertex=%s," +
-                                 "withPath=%s}", this.source, this.step,
+                                 "withPath=%s,withEdge=%s}", this.source, this.step,
                                  this.maxDepth, this.limit, this.countOnly,
-                                 this.withVertex, this.withPath);
+                                 this.withVertex, this.withPath, this.withEdge);
         }
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java
index 30282be9d6..1adf2be5eb 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java
@@ -22,6 +22,7 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -41,12 +42,13 @@
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.slf4j.Logger;
 
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.inject.Singleton;
@@ -78,6 +80,8 @@ public String get(@Context GraphManager manager,
                       @QueryParam("max_depth") int depth,
                       @QueryParam("nearest")
                       @DefaultValue("true") boolean nearest,
+                      @QueryParam("count_only")
+                      @DefaultValue("false") boolean count_only,
                       @QueryParam("max_degree")
                       @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree,
                       @QueryParam("capacity")
@@ -87,8 +91,10 @@ public String get(@Context GraphManager manager,
         LOG.debug("Graph [{}] get k-out from '{}' with " +
                   "direction '{}', edge label '{}', max depth '{}', nearest " +
                   "'{}', max degree '{}', capacity '{}' and limit '{}'",
-                  graph, source, direction, edgeLabel, depth, nearest,
-                  maxDegree, capacity, limit);
+                  graph, source, direction, edgeLabel, depth,
+                  nearest, maxDegree, capacity, limit);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -99,8 +105,15 @@ public String get(@Context GraphManager manager,
         try (KoutTraverser traverser = new KoutTraverser(g)) {
             ids = traverser.kout(sourceId, dir, edgeLabel, depth,
                                  nearest, maxDegree, capacity, limit);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
+        }
+
+        if (count_only) {
+            return manager.serializer(g, measure.measures())
+                          .writeMap(ImmutableMap.of("vertices_size", ids.size()));
         }
-        return manager.serializer(g).writeList("vertices", ids);
+        return manager.serializer(g, measure.measures()).writeList("vertices", ids);
     }
 
     @POST
@@ -116,23 +129,25 @@ public String post(@Context GraphManager manager,
         E.checkArgument(request.step != null,
                         "The steps of request can't be null");
         if (request.countOnly) {
-            E.checkArgument(!request.withVertex && !request.withPath,
-                            "Can't return vertex or path when count only");
+            E.checkArgument(!request.withVertex && !request.withPath && !request.withEdge,
+                            "Can't return vertex, edge or path when count only");
         }
 
         LOG.debug("Graph [{}] get customized kout from source vertex '{}', " +
                   "with step '{}', max_depth '{}', nearest '{}', " +
                   "count_only '{}', capacity '{}', limit '{}', " +
-                  "with_vertex '{}' and with_path '{}'",
+                  "with_vertex '{}', with_path '{}' and with_edge '{}'",
                   graph, request.source, request.step, request.maxDepth,
                   request.nearest, request.countOnly, request.capacity,
-                  request.limit, request.withVertex, request.withPath);
+                  request.limit, request.withVertex, request.withPath,
+                  request.withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         HugeGraph g = graph(manager, graph);
         Id sourceId = HugeVertex.getIdValue(request.source);
 
         EdgeStep step = step(g, request.step);
-
         KoutRecords results;
         try (KoutTraverser traverser = new KoutTraverser(g)) {
             results = traverser.customizedKout(sourceId, step,
@@ -140,8 +155,9 @@ public String post(@Context GraphManager manager,
                                                request.nearest,
                                                request.capacity,
                                                request.limit);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
         }
-
         long size = results.size();
         if (request.limit != NO_LIMIT && size > request.limit) {
             size = request.limit;
@@ -154,20 +170,40 @@ public String post(@Context GraphManager manager,
             paths.addAll(results.paths(request.limit));
         }
 
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (request.withVertex && !request.countOnly) {
-            Set<Id> ids = new HashSet<>(neighbors);
-            if (request.withPath) {
-                for (HugeTraverser.Path p : paths) {
-                    ids.addAll(p.vertices());
-                }
+        if (request.countOnly) {
+            return manager.serializer(g, measure.measures())
+                          .writeNodesWithPath("kneighbor", neighbors, size, paths,
+                                              QueryResults.emptyIterator(),
+                                              QueryResults.emptyIterator());
+        }
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>(neighbors);
+        if (request.withPath) {
+            for (HugeTraverser.Path p : results.paths(request.limit)) {
+                vertexIds.addAll(p.vertices());
             }
-            if (!ids.isEmpty()) {
-                iter = g.vertices(ids.toArray());
+        }
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge = Collections.emptyIterator();
+        if (request.withPath) {
+            Set<Edge> edges = results.edgeResults().getEdges(paths);
+            if (request.withEdge) {
+                iterEdge = edges.iterator();
+            } else {
+                iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
             }
         }
-        return manager.serializer(g).writeNodesWithPath("kout", neighbors,
-                                                        size, paths, iter);
+
+        return manager.serializer(g, measure.measures())
+                      .writeNodesWithPath("kout", neighbors, size, paths,
+                                          iterVertex, iterEdge);
     }
 
     private static class Request {
@@ -190,15 +226,18 @@ private static class Request {
         public boolean withVertex = false;
         @JsonProperty("with_path")
         public boolean withPath = false;
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
 
         @Override
         public String toString() {
             return String.format("KoutRequest{source=%s,step=%s,maxDepth=%s" +
                                  "nearest=%s,countOnly=%s,capacity=%s," +
-                                 "limit=%s,withVertex=%s,withPath=%s}",
-                                 this.source, this.step, this.maxDepth,
-                                 this.nearest, this.countOnly, this.capacity,
-                                 this.limit, this.withVertex, this.withPath);
+                                 "limit=%s,withVertex=%s,withPath=%s," +
+                                 "withEdge=%s}", this.source, this.step,
+                                 this.maxDepth, this.nearest, this.countOnly,
+                                 this.capacity, this.limit, this.withVertex,
+                                 this.withPath, this.withEdge);
         }
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java
index 81c38e65c9..588940abb7 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java
@@ -24,30 +24,30 @@
 import java.util.List;
 import java.util.Set;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.backend.query.QueryResults;
-import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.MultiNodeShortestPathTraverser;
+import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/multinodeshortestpath")
 @Singleton
 @Tag(name = "MultiNodeShortestPathAPI")
@@ -74,32 +74,48 @@ public String post(@Context GraphManager manager,
                   graph, request.vertices, request.step, request.maxDepth,
                   request.capacity, request.withVertex);
 
+        ApiMeasurer measure = new ApiMeasurer();
+
         HugeGraph g = graph(manager, graph);
         Iterator<Vertex> vertices = request.vertices.vertices(g);
 
         EdgeStep step = step(g, request.step);
 
-        List<HugeTraverser.Path> paths;
+        MultiNodeShortestPathTraverser.WrappedListPath wrappedListPath;
         try (MultiNodeShortestPathTraverser traverser =
-                                        new MultiNodeShortestPathTraverser(g)) {
-            paths = traverser.multiNodeShortestPath(vertices, step,
-                                                    request.maxDepth,
-                                                    request.capacity);
+                     new MultiNodeShortestPathTraverser(g)) {
+            wrappedListPath = traverser.multiNodeShortestPath(vertices, step,
+                                                              request.maxDepth,
+                                                              request.capacity);
+            measure.addIterCount(traverser.vertexIterCounter.get(),
+                                 traverser.edgeIterCounter.get());
         }
 
-        if (!request.withVertex) {
-            return manager.serializer(g).writePaths("paths", paths, false);
-        }
+        List<HugeTraverser.Path> paths = wrappedListPath.paths();
 
-        Set<Id> ids = new HashSet<>();
-        for (HugeTraverser.Path p : paths) {
-            ids.addAll(p.vertices());
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
+        }
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (!ids.isEmpty()) {
-            iter = g.vertices(ids.toArray());
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = wrappedListPath.edges();
+        if (request.withEdge && !edges.isEmpty()) {
+            iterEdge = wrappedListPath.edges().iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
         }
-        return manager.serializer(g).writePaths("paths", paths, false, iter);
+
+        return manager.serializer(g, measure.measures())
+                      .writePaths("paths", paths,
+                                  false, iterVertex, iterEdge);
     }
 
     private static class Request {
@@ -114,13 +130,15 @@ private static class Request {
         public long capacity = Long.parseLong(DEFAULT_CAPACITY);
         @JsonProperty("with_vertex")
         public boolean withVertex = false;
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
 
         @Override
         public String toString() {
             return String.format("Request{vertices=%s,step=%s,maxDepth=%s" +
-                                 "capacity=%s,withVertex=%s}",
+                                 "capacity=%s,withVertex=%s,withEdge=%s}",
                                  this.vertices, this.step, this.maxDepth,
-                                 this.capacity, this.withVertex);
+                                 this.capacity, this.withVertex, this.withEdge);
         }
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java
index 6e18c9a1c2..50bca7f75b 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java
@@ -27,27 +27,11 @@
 import java.util.Iterator;
 import java.util.Set;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.CollectionPathsTraverser;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.PathsTraverser;
@@ -55,9 +39,25 @@
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/paths")
 @Singleton
 @Tag(name = "PathsAPI")
@@ -87,6 +87,8 @@ public String get(@Context GraphManager manager,
                   graph, source, target, direction, edgeLabel, depth,
                   maxDegree, capacity, limit);
 
+        ApiMeasurer measure = new ApiMeasurer();
+
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Id targetId = VertexAPI.checkAndParseVertexId(target);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -97,7 +99,10 @@ public String get(@Context GraphManager manager,
                                                       dir.opposite(), edgeLabel,
                                                       depth, maxDegree, capacity,
                                                       limit);
-        return manager.serializer(g).writePaths("paths", paths, false);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+        return manager.serializer(g, measure.measures())
+                      .writePaths("paths", paths, false);
     }
 
     @POST
@@ -120,10 +125,12 @@ public String post(@Context GraphManager manager,
 
         LOG.debug("Graph [{}] get paths from source vertices '{}', target " +
                   "vertices '{}', with step '{}', max depth '{}', " +
-                  "capacity '{}', limit '{}' and with_vertex '{}'",
+                  "capacity '{}', limit '{}', with_vertex '{}' and with_edge '{}'",
                   graph, request.sources, request.targets, request.step,
                   request.depth, request.capacity, request.limit,
-                  request.withVertex);
+                  request.withVertex, request.withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         HugeGraph g = graph(manager, graph);
         Iterator<Vertex> sources = request.sources.vertices(g);
@@ -131,24 +138,38 @@ public String post(@Context GraphManager manager,
         EdgeStep step = step(g, request.step);
 
         CollectionPathsTraverser traverser = new CollectionPathsTraverser(g);
-        Collection<HugeTraverser.Path> paths;
-        paths = traverser.paths(sources, targets, step, request.depth,
-                                request.nearest, request.capacity,
-                                request.limit);
-
-        if (!request.withVertex) {
-            return manager.serializer(g).writePaths("paths", paths, false);
+        CollectionPathsTraverser.WrappedPathCollection
+                wrappedPathCollection = traverser.paths(sources, targets,
+                                                        step, request.depth,
+                                                        request.nearest, request.capacity,
+                                                        request.limit);
+        Collection<HugeTraverser.Path> paths = wrappedPathCollection.paths();
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
         }
-
-        Set<Id> ids = new HashSet<>();
-        for (HugeTraverser.Path p : paths) {
-            ids.addAll(p.vertices());
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (!ids.isEmpty()) {
-            iter = g.vertices(ids.toArray());
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = wrappedPathCollection.edges();
+        if (request.withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
         }
-        return manager.serializer(g).writePaths("paths", paths, false, iter);
+
+        return manager.serializer(g, measure.measures())
+                      .writePaths("paths", paths, false,
+                                  iterVertex, iterEdge);
     }
 
     private static class Request {
@@ -170,14 +191,17 @@ private static class Request {
         @JsonProperty("with_vertex")
         public boolean withVertex = false;
 
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
+
         @Override
         public String toString() {
             return String.format("PathRequest{sources=%s,targets=%s,step=%s," +
                                  "maxDepth=%s,nearest=%s,capacity=%s," +
-                                 "limit=%s,withVertex=%s}", this.sources,
-                                 this.targets, this.step, this.depth,
-                                 this.nearest, this.capacity,
-                                 this.limit, this.withVertex);
+                                 "limit=%s,withVertex=%s,withEdge=%s}",
+                                 this.sources, this.targets, this.step,
+                                 this.depth, this.nearest, this.capacity,
+                                 this.limit, this.withVertex, this.withEdge);
         }
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java
index c841412cae..28ded20e60 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java
@@ -21,30 +21,35 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.SubGraphTraverser;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/rays")
 @Singleton
 @Tag(name = "RaysAPI")
@@ -66,12 +71,17 @@ public String get(@Context GraphManager manager,
                       @QueryParam("capacity")
                       @DefaultValue(DEFAULT_CAPACITY) long capacity,
                       @QueryParam("limit")
-                      @DefaultValue(DEFAULT_PATHS_LIMIT) int limit) {
+                      @DefaultValue(DEFAULT_PATHS_LIMIT) int limit,
+                      @QueryParam("with_vertex")
+                      @DefaultValue("false") boolean withVertex,
+                      @QueryParam("with_edge")
+                      @DefaultValue("false") boolean withEdge) {
         LOG.debug("Graph [{}] get rays paths from '{}' with " +
                   "direction '{}', edge label '{}', max depth '{}', " +
                   "max degree '{}', capacity '{}' and limit '{}'",
                   graph, sourceV, direction, edgeLabel, depth, maxDegree,
                   capacity, limit);
+        ApiMeasurer measure = new ApiMeasurer();
 
         Id source = VertexAPI.checkAndParseVertexId(sourceV);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -80,8 +90,33 @@ public String get(@Context GraphManager manager,
 
         SubGraphTraverser traverser = new SubGraphTraverser(g);
         HugeTraverser.PathSet paths = traverser.rays(source, dir, edgeLabel,
-                                                     depth, maxDegree,
-                                                     capacity, limit);
-        return manager.serializer(g).writePaths("rays", paths, false);
+                                                     depth, maxDegree, capacity,
+                                                     limit);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
+        }
+        if (withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = paths.getEdges();
+        if (withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
+        }
+
+        return manager.serializer(g, measure.measures())
+                      .writePaths("rays", paths, false,
+                                  iterVertex, iterEdge);
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java
index 67dfe7ab72..3a44fd85a1 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java
@@ -21,30 +21,35 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.SubGraphTraverser;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/rings")
 @Singleton
 @Tag(name = "RingsAPI")
@@ -68,14 +73,19 @@ public String get(@Context GraphManager manager,
                       @QueryParam("capacity")
                       @DefaultValue(DEFAULT_CAPACITY) long capacity,
                       @QueryParam("limit")
-                      @DefaultValue(DEFAULT_PATHS_LIMIT) int limit) {
+                      @DefaultValue(DEFAULT_PATHS_LIMIT) int limit,
+                      @QueryParam("with_vertex")
+                      @DefaultValue("false") boolean withVertex,
+                      @QueryParam("with_edge")
+                      @DefaultValue("false") boolean withEdge) {
         LOG.debug("Graph [{}] get rings paths reachable from '{}' with " +
                   "direction '{}', edge label '{}', max depth '{}', " +
-                  "source in ring '{}', max degree '{}', capacity '{}' " +
-                  "and limit '{}'",
+                  "source in ring '{}', max degree '{}', capacity '{}', " +
+                  "limit '{}', with_vertex '{}' and with_edge '{}'",
                   graph, sourceV, direction, edgeLabel, depth, sourceInRing,
-                  maxDegree, capacity, limit);
+                  maxDegree, capacity, limit, withVertex, withEdge);
 
+        ApiMeasurer measure = new ApiMeasurer();
         Id source = VertexAPI.checkAndParseVertexId(sourceV);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
 
@@ -83,8 +93,34 @@ public String get(@Context GraphManager manager,
 
         SubGraphTraverser traverser = new SubGraphTraverser(g);
         HugeTraverser.PathSet paths = traverser.rings(source, dir, edgeLabel,
-                                                      depth, sourceInRing,
-                                                      maxDegree, capacity, limit);
-        return manager.serializer(g).writePaths("rings", paths, false);
+                                                      depth, sourceInRing, maxDegree,
+                                                      capacity, limit);
+
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
+        }
+        if (withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = paths.getEdges();
+        if (withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
+        }
+
+        return manager.serializer(g, measure.measures())
+                      .writePaths("rings", paths, false,
+                                  iterVertex, iterEdge);
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java
index a7a1770fd0..489ca08054 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java
@@ -20,30 +20,39 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.core.GraphManager;
+import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.traversal.algorithm.SameNeighborTraverser;
 import org.apache.hugegraph.type.define.Directions;
+import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
 
 @Path("graphs/{graph}/traversers/sameneighbors")
 @Singleton
@@ -69,6 +78,8 @@ public String get(@Context GraphManager manager,
                   "direction {}, edge label {}, max degree '{}' and limit '{}'",
                   graph, vertex, other, direction, edgeLabel, maxDegree, limit);
 
+        ApiMeasurer measure = new ApiMeasurer();
+
         Id sourceId = VertexAPI.checkAndParseVertexId(vertex);
         Id targetId = VertexAPI.checkAndParseVertexId(other);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -77,6 +88,77 @@ public String get(@Context GraphManager manager,
         SameNeighborTraverser traverser = new SameNeighborTraverser(g);
         Set<Id> neighbors = traverser.sameNeighbors(sourceId, targetId, dir,
                                                     edgeLabel, maxDegree, limit);
-        return manager.serializer(g).writeList("same_neighbors", neighbors);
+
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        return manager.serializer(g, measure.measures())
+                      .writeList("same_neighbors", neighbors);
+    }
+
+    @POST
+    @Timed
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    public String sameNeighbors(@Context GraphManager manager,
+                                @PathParam("graph") String graph,
+                                Request request) {
+        LOG.debug("Graph [{}] get same neighbors among batch, '{}'", graph, request.toString());
+
+        ApiMeasurer measure = new ApiMeasurer();
+
+        Directions dir = Directions.convert(EdgeAPI.parseDirection(request.direction));
+        HugeGraph g = graph(manager, graph);
+        SameNeighborTraverser traverser = new SameNeighborTraverser(g);
+
+        List<Object> vertexList = request.vertexList;
+        E.checkArgument(vertexList.size() >= 2, "vertex_list size can't " +
+                                                "be less than 2");
+
+        List<Id> vertexIds = new ArrayList<>();
+        for (Object obj : vertexList) {
+            vertexIds.add(HugeVertex.getIdValue(obj));
+        }
+
+        Set<Id> neighbors = traverser.sameNeighbors(vertexIds, dir, request.labels,
+                                                    request.maxDegree, request.limit);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> ids = new HashSet<>(neighbors);
+        ids.addAll(vertexIds);
+        if (request.withVertex && !ids.isEmpty()) {
+            iterVertex = g.vertices(ids.toArray());
+        } else {
+            iterVertex = ids.iterator();
+        }
+        return manager.serializer(g, measure.measures())
+                      .writeMap(ImmutableMap.of("same_neighbors", neighbors,
+                                                "vertices", iterVertex));
+    }
+
+    private static class Request {
+
+        @JsonProperty("max_degree")
+        public long maxDegree = Long.parseLong(DEFAULT_MAX_DEGREE);
+        @JsonProperty("limit")
+        public int limit = Integer.parseInt(DEFAULT_ELEMENTS_LIMIT);
+        @JsonProperty("vertex_list")
+        private List<Object> vertexList;
+        @JsonProperty("direction")
+        private String direction;
+        @JsonProperty("labels")
+        private List<String> labels;
+        @JsonProperty("with_vertex")
+        private boolean withVertex = false;
+
+        @Override
+        public String toString() {
+            return String.format("SameNeighborsBatchRequest{vertex_list=%s," +
+                                 "direction=%s,label=%s,max_degree=%d," +
+                                 "limit=%d,with_vertex=%s",
+                                 this.vertexList, this.direction, this.labels,
+                                 this.maxDegree, this.limit, this.withVertex);
+        }
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java
index 08cbdf74cb..dcc8489ae1 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java
@@ -20,32 +20,36 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY;
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 
+import java.util.Iterator;
 import java.util.List;
-
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.ShortestPathTraverser;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
 
 @Path("graphs/{graph}/traversers/shortestpath")
 @Singleton
@@ -68,13 +72,21 @@ public String get(@Context GraphManager manager,
                       @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree,
                       @QueryParam("skip_degree")
                       @DefaultValue("0") long skipDegree,
+                      @QueryParam("with_vertex")
+                      @DefaultValue("false") boolean withVertex,
+                      @QueryParam("with_edge")
+                      @DefaultValue("false") boolean withEdge,
                       @QueryParam("capacity")
                       @DefaultValue(DEFAULT_CAPACITY) long capacity) {
         LOG.debug("Graph [{}] get shortest path from '{}', to '{}' with " +
                   "direction {}, edge label {}, max depth '{}', " +
-                  "max degree '{}', skipped maxDegree '{}' and capacity '{}'",
+                  "max degree '{}', skipped maxDegree '{}', capacity '{}', " +
+                  "with_vertex '{}' and with_edge '{}'",
                   graph, source, target, direction, edgeLabel, depth,
-                  maxDegree, skipDegree, capacity);
+                  maxDegree, skipDegree, capacity, withVertex, withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
+
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Id targetId = VertexAPI.checkAndParseVertexId(target);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -89,6 +101,29 @@ public String get(@Context GraphManager manager,
                                                          dir, edgeLabels, depth,
                                                          maxDegree, skipDegree,
                                                          capacity);
-        return manager.serializer(g).writeList("path", path.vertices());
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        List<Id> vertexIds = path.vertices();
+        if (withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(path.vertices().size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = path.getEdges();
+        if (withEdge) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
+        }
+
+        return manager.serializer(g, measure.measures())
+                      .writeMap(ImmutableMap.of("path", path.vertices(),
+                                                "vertices", iterVertex,
+                                                "edges", iterEdge));
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java
index 8813399ca7..eab339d958 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java
@@ -22,33 +22,33 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT;
 
 import java.util.Iterator;
-
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.graph.EdgeAPI;
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.core.GraphManager;
+import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser;
-import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.WeightedPaths;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/singlesourceshortestpath")
 @Singleton
 @Tag(name = "SingleSourceShortestPathAPI")
@@ -69,16 +69,22 @@ public String get(@Context GraphManager manager,
                       @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree,
                       @QueryParam("skip_degree")
                       @DefaultValue("0") long skipDegree,
+                      @QueryParam("with_vertex")
+                      @DefaultValue("false") boolean withVertex,
+                      @QueryParam("with_edge")
+                      @DefaultValue("false") boolean withEdge,
                       @QueryParam("capacity")
                       @DefaultValue(DEFAULT_CAPACITY) long capacity,
                       @QueryParam("limit")
-                      @DefaultValue(DEFAULT_PATHS_LIMIT) int limit,
-                      @QueryParam("with_vertex") boolean withVertex) {
+                      @DefaultValue(DEFAULT_PATHS_LIMIT) int limit) {
         LOG.debug("Graph [{}] get single source shortest path from '{}' " +
                   "with direction {}, edge label {}, weight property {}, " +
-                  "max degree '{}', limit '{}' and with vertex '{}'",
+                  "max degree '{}', capacity '{}', limit '{}', " +
+                  "with_vertex '{}' and with_edge '{}'",
                   graph, source, direction, edgeLabel,
-                  weight, maxDegree, withVertex);
+                  weight, maxDegree, capacity, limit, withVertex, withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -86,14 +92,31 @@ public String get(@Context GraphManager manager,
         HugeGraph g = graph(manager, graph);
         SingleSourceShortestPathTraverser traverser =
                 new SingleSourceShortestPathTraverser(g);
-        WeightedPaths paths = traverser.singleSourceShortestPaths(
-                              sourceId, dir, edgeLabel, weight,
-                              maxDegree, skipDegree, capacity, limit);
-        Iterator<Vertex> iterator = QueryResults.emptyIterator();
-        assert paths != null;
-        if (!paths.isEmpty() && withVertex) {
-            iterator = g.vertices(paths.vertices().toArray());
+        SingleSourceShortestPathTraverser.WeightedPaths paths =
+                traverser.singleSourceShortestPaths(
+                        sourceId, dir, edgeLabel, weight,
+                        maxDegree, skipDegree, capacity, limit);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = paths.vertices();
+        if (withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        return manager.serializer(g).writeWeightedPaths(paths, iterator);
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = paths.getEdges();
+        if (withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
+        }
+
+        return manager.serializer(g, measure.measures())
+                      .writeWeightedPaths(paths, iterVertex, iterEdge);
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java
index d566fae90d..9b3739acb2 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java
@@ -26,30 +26,30 @@
 import java.util.List;
 import java.util.Set;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.TemplatePathsTraverser;
 import org.apache.hugegraph.traversal.algorithm.steps.RepeatEdgeStep;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/templatepaths")
 @Singleton
 @Tag(name = "TemplatePathsAPI")
@@ -57,6 +57,22 @@ public class TemplatePathsAPI extends TraverserAPI {
 
     private static final Logger LOG = Log.logger(TemplatePathsAPI.class);
 
+    private static List<RepeatEdgeStep> steps(HugeGraph g,
+                                              List<TemplatePathStep> steps) {
+        List<RepeatEdgeStep> edgeSteps = new ArrayList<>(steps.size());
+        for (TemplatePathStep step : steps) {
+            edgeSteps.add(repeatEdgeStep(g, step));
+        }
+        return edgeSteps;
+    }
+
+    private static RepeatEdgeStep repeatEdgeStep(HugeGraph graph,
+                                                 TemplatePathStep step) {
+        return new RepeatEdgeStep(graph, step.direction, step.labels,
+                                  step.properties, step.maxDegree,
+                                  step.skipDegree, step.maxTimes);
+    }
+
     @POST
     @Timed
     @Consumes(APPLICATION_JSON)
@@ -74,9 +90,11 @@ public String post(@Context GraphManager manager,
 
         LOG.debug("Graph [{}] get template paths from source vertices '{}', " +
                   "target vertices '{}', with steps '{}', " +
-                  "capacity '{}', limit '{}' and with_vertex '{}'",
+                  "capacity '{}', limit '{}', with_vertex '{}' and with_edge '{}'",
                   graph, request.sources, request.targets, request.steps,
-                  request.capacity, request.limit, request.withVertex);
+                  request.capacity, request.limit, request.withVertex, request.withEdge);
+
+        ApiMeasurer measure = new ApiMeasurer();
 
         HugeGraph g = graph(manager, graph);
         Iterator<Vertex> sources = request.sources.vertices(g);
@@ -84,40 +102,38 @@ public String post(@Context GraphManager manager,
         List<RepeatEdgeStep> steps = steps(g, request.steps);
 
         TemplatePathsTraverser traverser = new TemplatePathsTraverser(g);
-        Set<HugeTraverser.Path> paths;
-        paths = traverser.templatePaths(sources, targets, steps,
+        TemplatePathsTraverser.WrappedPathSet wrappedPathSet =
+                traverser.templatePaths(sources, targets, steps,
                                         request.withRing, request.capacity,
                                         request.limit);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
 
-        if (!request.withVertex) {
-            return manager.serializer(g).writePaths("paths", paths, false);
-        }
+        Set<HugeTraverser.Path> paths = wrappedPathSet.paths();
 
-        Set<Id> ids = new HashSet<>();
-        for (HugeTraverser.Path p : paths) {
-            ids.addAll(p.vertices());
+        Iterator<?> iterVertex;
+        Set<Id> vertexIds = new HashSet<>();
+        for (HugeTraverser.Path path : paths) {
+            vertexIds.addAll(path.vertices());
         }
-        Iterator<Vertex> iter = QueryResults.emptyIterator();
-        if (!ids.isEmpty()) {
-            iter = g.vertices(ids.toArray());
+        if (request.withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
         }
-        return manager.serializer(g).writePaths("paths", paths, false, iter);
-    }
 
-    private static List<RepeatEdgeStep> steps(HugeGraph g,
-                                              List<TemplatePathStep> steps) {
-        List<RepeatEdgeStep> edgeSteps = new ArrayList<>(steps.size());
-        for (TemplatePathStep step : steps) {
-            edgeSteps.add(repeatEdgeStep(g, step));
+        Iterator<?> iterEdge;
+        Set<Edge> edges = wrappedPathSet.edges();
+        if (request.withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
         }
-        return edgeSteps;
-    }
 
-    private static RepeatEdgeStep repeatEdgeStep(HugeGraph graph,
-                                                 TemplatePathStep step) {
-        return new RepeatEdgeStep(graph, step.direction, step.labels,
-                                  step.properties, step.maxDegree,
-                                  step.skipDegree, step.maxTimes);
+        return manager.serializer(g, measure.measures())
+                      .writePaths("paths", paths, false,
+                                  iterVertex, iterEdge);
     }
 
     private static class Request {
@@ -136,15 +152,17 @@ private static class Request {
         public int limit = Integer.parseInt(DEFAULT_PATHS_LIMIT);
         @JsonProperty("with_vertex")
         public boolean withVertex = false;
+        @JsonProperty("with_edge")
+        public boolean withEdge = false;
 
         @Override
         public String toString() {
             return String.format("TemplatePathsRequest{sources=%s,targets=%s," +
                                  "steps=%s,withRing=%s,capacity=%s,limit=%s," +
-                                 "withVertex=%s}",
+                                 "withVertex=%s,withEdge=%s}",
                                  this.sources, this.targets, this.steps,
                                  this.withRing, this.capacity, this.limit,
-                                 this.withVertex);
+                                 this.withVertex, this.withEdge);
         }
     }
 
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java
index 56c4889f81..86364a23bf 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java
@@ -22,20 +22,6 @@
 import java.util.Iterator;
 import java.util.List;
 
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.filter.CompressInterceptor.Compress;
@@ -43,11 +29,25 @@
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.backend.query.ConditionQuery;
 import org.apache.hugegraph.backend.store.Shard;
+import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.type.HugeType;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/vertices")
 @Singleton
 @Tag(name = "VerticesAPI")
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java
index b675f618bc..1c25661f15 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java
@@ -21,20 +21,8 @@
 import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE;
 
 import java.util.Iterator;
-
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.DefaultValue;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.QueryParam;
-import jakarta.ws.rs.core.Context;
-
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.hugegraph.core.GraphManager;
-import org.slf4j.Logger;
+import java.util.List;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
@@ -42,13 +30,27 @@
 import org.apache.hugegraph.api.graph.VertexAPI;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.backend.query.QueryResults;
+import org.apache.hugegraph.core.GraphManager;
+import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser;
-import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.NodeWithWeight;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.Log;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.codahale.metrics.annotation.Timed;
 
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+
 @Path("graphs/{graph}/traversers/weightedshortestpath")
 @Singleton
 @Tag(name = "WeightedShortestPathAPI")
@@ -70,16 +72,20 @@ public String get(@Context GraphManager manager,
                       @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree,
                       @QueryParam("skip_degree")
                       @DefaultValue("0") long skipDegree,
+                      @QueryParam("with_vertex")
+                      @DefaultValue("false") boolean withVertex,
+                      @QueryParam("with_edge")
+                      @DefaultValue("false") boolean withEdge,
                       @QueryParam("capacity")
-                      @DefaultValue(DEFAULT_CAPACITY) long capacity,
-                      @QueryParam("with_vertex") boolean withVertex) {
+                      @DefaultValue(DEFAULT_CAPACITY) long capacity) {
         LOG.debug("Graph [{}] get weighted shortest path between '{}' and " +
                   "'{}' with direction {}, edge label {}, weight property {}, " +
                   "max degree '{}', skip degree '{}', capacity '{}', " +
-                  "and with vertex '{}'",
+                  "with_vertex '{}' and with_edge '{}'",
                   graph, source, target, direction, edgeLabel, weight,
-                  maxDegree, skipDegree, capacity, withVertex);
+                  maxDegree, skipDegree, capacity, withVertex, withEdge);
 
+        ApiMeasurer measure = new ApiMeasurer();
         Id sourceId = VertexAPI.checkAndParseVertexId(source);
         Id targetId = VertexAPI.checkAndParseVertexId(target);
         Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@@ -89,14 +95,38 @@ public String get(@Context GraphManager manager,
         SingleSourceShortestPathTraverser traverser =
                 new SingleSourceShortestPathTraverser(g);
 
-        NodeWithWeight path = traverser.weightedShortestPath(
-                              sourceId, targetId, dir, edgeLabel, weight,
-                              maxDegree, skipDegree, capacity);
-        Iterator<Vertex> iterator = QueryResults.emptyIterator();
-        if (path != null && withVertex) {
-            assert !path.node().path().isEmpty();
-            iterator = g.vertices(path.node().path().toArray());
+        SingleSourceShortestPathTraverser.NodeWithWeight node =
+                traverser.weightedShortestPath(sourceId, targetId,
+                                               dir, edgeLabel, weight,
+                                               maxDegree, skipDegree, capacity);
+        measure.addIterCount(traverser.vertexIterCounter.get(),
+                             traverser.edgeIterCounter.get());
+
+        if (node == null) {
+            return manager.serializer(g, measure.measures())
+                          .writeWeightedPath(null,
+                                             QueryResults.emptyIterator(),
+                                             QueryResults.emptyIterator());
+        }
+
+        Iterator<?> iterVertex;
+        List<Id> vertexIds = node.node().path();
+        if (withVertex && !vertexIds.isEmpty()) {
+            iterVertex = g.vertices(vertexIds.toArray());
+            measure.addIterCount(vertexIds.size(), 0L);
+        } else {
+            iterVertex = vertexIds.iterator();
+        }
+
+        Iterator<?> iterEdge;
+        Set<Edge> edges = node.getEdges();
+        if (withEdge && !edges.isEmpty()) {
+            iterEdge = edges.iterator();
+        } else {
+            iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator();
         }
-        return manager.serializer(g).writeWeightedPath(path, iterator);
+
+        return manager.serializer(g, measure.measures())
+                      .writeWeightedPath(node, iterVertex, iterEdge);
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java b/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
index 95a53faa39..e66b593568 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
@@ -264,4 +264,4 @@ public static synchronized ServerOptions instance() {
                     disallowEmpty(),
                     true
             );
-}
+}
\ No newline at end of file
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java
index b203c10470..2c73b5ee93 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java
@@ -224,6 +224,10 @@ public Serializer serializer(Graph g) {
         return JsonSerializer.instance();
     }
 
+    public Serializer serializer(Graph g, Map<String, Object> apiMeasure) {
+        return JsonSerializer.instance(apiMeasure);
+    }
+
     public void rollbackAll() {
         for (Graph graph : this.graphs.values()) {
             if (graph.features().graph().supportsTransactions() &&
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java
index 8103602234..035499c598 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java
@@ -24,11 +24,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
-
 import org.apache.hugegraph.HugeException;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.auth.SchemaDefine.AuthElement;
@@ -47,25 +42,44 @@
 import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.WeightedPaths;
 import org.apache.hugegraph.traversal.optimize.TraversalUtil;
 import org.apache.hugegraph.util.JsonUtil;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 public class JsonSerializer implements Serializer {
 
     private static final int LBUF_SIZE = 1024;
-
-    private static JsonSerializer INSTANCE = new JsonSerializer();
+    private static final String MEASURE_KEY = "measure";
+    private static final JsonSerializer INSTANCE = new JsonSerializer();
+    private Map<String, Object> apiMeasure = null;
 
     private JsonSerializer() {
     }
 
+    private JsonSerializer(Map<String, Object> apiMeasure) {
+        this.apiMeasure = apiMeasure;
+    }
+
     public static JsonSerializer instance() {
         return INSTANCE;
     }
 
+    public static JsonSerializer instance(Map<String, Object> apiMeasure) {
+        return new JsonSerializer(apiMeasure);
+    }
+
     @Override
     public String writeMap(Map<?, ?> map) {
-        return JsonUtil.toJson(map);
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder();
+        builder.putAll(map);
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
+        }
+        return JsonUtil.toJson(builder.build());
     }
 
     @Override
@@ -73,6 +87,10 @@ public String writeList(String label, Collection<?> list) {
         try (ByteArrayOutputStream out = new ByteArrayOutputStream(LBUF_SIZE)) {
             out.write(String.format("{\"%s\": ", label).getBytes(API.CHARSET));
             out.write(JsonUtil.toJson(list).getBytes(API.CHARSET));
+            if (this.apiMeasure != null) {
+                out.write(String.format(",\"%s\": ", MEASURE_KEY).getBytes(API.CHARSET));
+                out.write(JsonUtil.toJson(this.apiMeasure).getBytes(API.CHARSET));
+            }
             out.write("}".getBytes(API.CHARSET));
             return out.toString(API.CHARSET);
         } catch (Exception e) {
@@ -122,6 +140,11 @@ private String writeIterator(String label, Iterator<?> iter,
                 out.write(page.getBytes(API.CHARSET));
             }
 
+            if (this.apiMeasure != null) {
+                out.write(String.format(",\"%s\":[", MEASURE_KEY).getBytes(API.CHARSET));
+                out.write(JsonUtil.toJson(this.apiMeasure).getBytes(API.CHARSET));
+            }
+
             out.write("}".getBytes(API.CHARSET));
             return out.toString(API.CHARSET);
         } catch (HugeException e) {
@@ -144,7 +167,7 @@ public String writePropertyKey(PropertyKey propertyKey) {
 
     @Override
     public String writeTaskWithSchema(
-                  SchemaElement.TaskWithSchema taskWithSchema) {
+            SchemaElement.TaskWithSchema taskWithSchema) {
         StringBuilder builder = new StringBuilder();
         long id = taskWithSchema.task() == null ?
                   0L : taskWithSchema.task().asLong();
@@ -162,10 +185,14 @@ public String writeTaskWithSchema(
                                     "TaskWithSchema, only support " +
                                     "[PropertyKey, IndexLabel]", schemaElement);
         }
-        return builder.append("{\"").append(type).append("\": ")
-                      .append(schema)
-                      .append(", \"task_id\": ").append(id).append("}")
-                      .toString();
+        builder.append("{\"").append(type).append("\": ")
+               .append(schema).append(", \"task_id\": ")
+               .append(id);
+        if (this.apiMeasure != null) {
+            builder.append(String.format(",\"%s\":[", MEASURE_KEY));
+            builder.append(JsonUtil.toJson(this.apiMeasure));
+        }
+        return builder.append("}").toString();
     }
 
     @Override
@@ -245,27 +272,36 @@ public <V extends AuthElement> String writeAuthElements(String label,
 
     @Override
     public String writePaths(String name, Collection<HugeTraverser.Path> paths,
-                             boolean withCrossPoint,
-                             Iterator<Vertex> vertices) {
+                             boolean withCrossPoint, Iterator<?> vertices,
+                             Iterator<?> edges) {
         List<Map<String, Object>> pathList = new ArrayList<>(paths.size());
         for (HugeTraverser.Path path : paths) {
             pathList.add(path.toMap(withCrossPoint));
         }
 
-        Map<String, Object> results;
-        if (vertices == null) {
-            results = ImmutableMap.of(name, pathList);
-        } else {
-            results = ImmutableMap.of(name, pathList, "vertices", vertices);
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder();
+        builder.put(name, pathList);
+
+        if (vertices != null) {
+            builder.put("vertices", vertices);
+        }
+
+        if (edges != null) {
+            builder.put("edges", edges);
+        }
+
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
         }
-        return JsonUtil.toJson(results);
+
+        return JsonUtil.toJson(builder.build());
     }
 
     @Override
     public String writeCrosspoints(CrosspointsPaths paths,
-                                   Iterator<Vertex> iterator,
+                                   Iterator<?> vertices,
+                                   Iterator<?> edges,
                                    boolean withPath) {
-        Map<String, Object> results;
         List<Map<String, Object>> pathList;
         if (withPath) {
             pathList = new ArrayList<>();
@@ -275,50 +311,81 @@ public String writeCrosspoints(CrosspointsPaths paths,
         } else {
             pathList = ImmutableList.of();
         }
-        results = ImmutableMap.of("crosspoints", paths.crosspoints(),
-                                  "paths", pathList,
-                                  "vertices", iterator);
-        return JsonUtil.toJson(results);
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder()
+                                                                   .put("crosspoints",
+                                                                        paths.crosspoints())
+                                                                   .put("paths", pathList)
+                                                                   .put("vertices", vertices)
+                                                                   .put("edges", edges);
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
+        }
+        return JsonUtil.toJson(builder.build());
     }
 
     @Override
     public String writeSimilars(SimilarsMap similars,
-                                Iterator<Vertex> vertices) {
-        return JsonUtil.toJson(ImmutableMap.of("similars", similars.toMap(),
-                                               "vertices", vertices));
+                                Iterator<?> vertices) {
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder()
+                                                                   .put("similars",
+                                                                        similars.toMap())
+                                                                   .put("vertices", vertices);
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
+        }
+        return JsonUtil.toJson(builder.build());
     }
 
     @Override
-    public String writeWeightedPath(NodeWithWeight path,
-                                    Iterator<Vertex> vertices) {
+    public String writeWeightedPath(NodeWithWeight path, Iterator<?> vertices,
+                                    Iterator<?> edges) {
         Map<String, Object> pathMap = path == null ?
                                       ImmutableMap.of() : path.toMap();
-        return JsonUtil.toJson(ImmutableMap.of("path", pathMap,
-                                               "vertices", vertices));
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder()
+                                                                   .put("path", pathMap)
+                                                                   .put("vertices", vertices)
+                                                                   .put("edges", edges);
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
+        }
+        return JsonUtil.toJson(builder.build());
     }
 
     @Override
-    public String writeWeightedPaths(WeightedPaths paths,
-                                     Iterator<Vertex> vertices) {
+    public String writeWeightedPaths(WeightedPaths paths, Iterator<?> vertices,
+                                     Iterator<?> edges) {
         Map<Id, Map<String, Object>> pathMap = paths == null ?
                                                ImmutableMap.of() :
                                                paths.toMap();
-        return JsonUtil.toJson(ImmutableMap.of("paths", pathMap,
-                                               "vertices", vertices));
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder()
+                                                                   .put("paths", pathMap)
+                                                                   .put("vertices", vertices)
+                                                                   .put("edges", edges);
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
+        }
+        return JsonUtil.toJson(builder.build());
     }
 
     @Override
     public String writeNodesWithPath(String name, List<Id> nodes, long size,
                                      Collection<HugeTraverser.Path> paths,
-                                     Iterator<Vertex> vertices) {
+                                     Iterator<?> vertices, Iterator<?> edges) {
         List<Map<String, Object>> pathList = new ArrayList<>();
         for (HugeTraverser.Path path : paths) {
             pathList.add(path.toMap(false));
         }
 
-        Map<String, Object> results;
-        results = ImmutableMap.of(name, nodes, "size", size,
-                                  "paths", pathList, "vertices", vertices);
-        return JsonUtil.toJson(results);
+        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder()
+                                                                   .put(name, nodes)
+                                                                   .put("size", size)
+                                                                   .put("paths", pathList)
+                                                                   .put("vertices", vertices)
+                                                                   .put("edges", edges);
+        if (this.apiMeasure != null) {
+            builder.put(MEASURE_KEY, this.apiMeasure);
+        }
+
+        return JsonUtil.toJson(builder.build());
     }
 }
diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java
index f3b0cdcace..96fa634202 100644
--- a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java
+++ b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java
@@ -22,9 +22,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-
 import org.apache.hugegraph.auth.SchemaDefine.AuthElement;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.schema.EdgeLabel;
@@ -37,6 +34,8 @@
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser;
 import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.NodeWithWeight;
 import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.WeightedPaths;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
 
 public interface Serializer {
 
@@ -77,23 +76,26 @@ public interface Serializer {
     <V extends AuthElement> String writeAuthElements(String label, List<V> users);
 
     String writePaths(String name, Collection<HugeTraverser.Path> paths,
-                      boolean withCrossPoint, Iterator<Vertex> vertices);
+                      boolean withCrossPoint, Iterator<?> vertices,
+                      Iterator<?> edges);
 
     default String writePaths(String name, Collection<HugeTraverser.Path> paths,
                               boolean withCrossPoint) {
-        return this.writePaths(name, paths, withCrossPoint, null);
+        return this.writePaths(name, paths, withCrossPoint, null, null);
     }
 
-    String writeCrosspoints(CrosspointsPaths paths, Iterator<Vertex> iterator,
-                            boolean withPath);
+    String writeCrosspoints(CrosspointsPaths paths, Iterator<?> vertices,
+                            Iterator<?> edges, boolean withPath);
 
-    String writeSimilars(SimilarsMap similars, Iterator<Vertex> vertices);
+    String writeSimilars(SimilarsMap similars, Iterator<?> vertices);
 
-    String writeWeightedPath(NodeWithWeight path, Iterator<Vertex> vertices);
+    String writeWeightedPath(NodeWithWeight path, Iterator<?> vertices,
+                             Iterator<?> edges);
 
-    String writeWeightedPaths(WeightedPaths paths, Iterator<Vertex> vertices);
+    String writeWeightedPaths(WeightedPaths paths, Iterator<?> vertices,
+                              Iterator<?> edges);
 
     String writeNodesWithPath(String name, List<Id> nodes, long size,
                               Collection<HugeTraverser.Path> paths,
-                              Iterator<Vertex> vertices);
+                              Iterator<?> vertices, Iterator<?> edges);
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java b/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java
index 2f512f7169..0fba245966 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java
@@ -26,19 +26,29 @@
 import org.apache.commons.lang.mutable.MutableLong;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.backend.id.IdGenerator;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-
 import org.apache.hugegraph.job.UserJob;
 import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.InsertionOrderUtil;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+
 import com.google.common.collect.ImmutableMap;
 
 public class TriangleCountAlgorithm extends AbstractCommAlgorithm {
 
     public static final String ALGO_NAME = "triangle_count";
 
+    protected static int workersWhenBoth(Map<String, Object> parameters) {
+        Directions direction = direction4Out(parameters);
+        int workers = workers(parameters);
+        E.checkArgument(direction == Directions.BOTH || workers <= 0,
+                        "The workers must be not set when direction!=BOTH, " +
+                        "but got workers=%s and direction=%s",
+                        workers, direction);
+        return workers;
+    }
+
     @Override
     public String name() {
         return ALGO_NAME;
@@ -60,16 +70,6 @@ public Object call(UserJob<Object> job, Map<String, Object> parameters) {
         }
     }
 
-    protected static int workersWhenBoth(Map<String, Object> parameters) {
-        Directions direction = direction4Out(parameters);
-        int workers = workers(parameters);
-        E.checkArgument(direction == Directions.BOTH || workers <= 0,
-                        "The workers must be not set when direction!=BOTH, " +
-                        "but got workers=%s and direction=%s",
-                        workers, direction);
-        return workers;
-    }
-
     protected static class Traverser extends AlgoTraverser {
 
         protected static final String KEY_TRIANGLES = "triangles";
@@ -83,8 +83,12 @@ protected Traverser(UserJob<Object> job, String name, int workers) {
             super(job, name, workers);
         }
 
+        protected static <V> Set<V> newOrderedSet() {
+            return new TreeSet<>();
+        }
+
         public Object triangleCount(Directions direction, long degree) {
-            Map<String, Long> results = triangles( direction, degree);
+            Map<String, Long> results = triangles(direction, degree);
             results = InsertionOrderUtil.newMap(results);
             results.remove(KEY_TRIADS);
             return results;
@@ -191,7 +195,7 @@ private Set<Id> adjacentVertices(Id source, long degree,
                                          MutableLong edgesCount) {
             Iterator<Id> adjVertices = this.adjacentVertices(source,
                                                              Directions.BOTH,
-                                                             null, degree);
+                                                             (Id) null, degree);
             Set<Id> set = newOrderedSet();
             while (adjVertices.hasNext()) {
                 edgesCount.increment();
@@ -206,7 +210,7 @@ protected long intersect(long degree, Set<Id> adjVertices) {
             Id empty = IdGenerator.ZERO;
             Iterator<Id> vertices;
             for (Id v : adjVertices) {
-                vertices = this.adjacentVertices(v, dir, null, degree);
+                vertices = this.adjacentVertices(v, dir, (Id) null, degree);
                 Id lastVertex = empty;
                 while (vertices.hasNext()) {
                     Id vertex = vertices.next();
@@ -231,9 +235,5 @@ protected long intersect(long degree, Set<Id> adjVertices) {
         protected long localTriads(int size) {
             return size * (size - 1L) / 2L;
         }
-
-        protected static <V> Set<V> newOrderedSet() {
-            return new TreeSet<>();
-        }
     }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java
index 76db199498..51e919dff8 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java
@@ -21,15 +21,17 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.traversal.algorithm.strategy.TraverseStrategy;
+import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 
-import org.apache.hugegraph.structure.HugeVertex;
-import org.apache.hugegraph.util.E;
 import com.google.common.collect.ImmutableList;
 
 public class CollectionPathsTraverser extends HugeTraverser {
@@ -38,10 +40,10 @@ public CollectionPathsTraverser(HugeGraph graph) {
         super(graph);
     }
 
-    public Collection<Path> paths(Iterator<Vertex> sources,
-                                  Iterator<Vertex> targets,
-                                  EdgeStep step, int depth, boolean nearest,
-                                  long capacity, long limit) {
+    public WrappedPathCollection paths(Iterator<Vertex> sources,
+                                       Iterator<Vertex> targets,
+                                       EdgeStep step, int depth, boolean nearest,
+                                       long capacity, long limit) {
         checkCapacity(capacity);
         checkLimit(limit);
 
@@ -63,31 +65,33 @@ public Collection<Path> paths(Iterator<Vertex> sources,
                      "but got: %s", MAX_VERTICES, sourceList.size());
         checkPositive(depth, "max depth");
 
+        boolean concurrent = depth >= this.concurrentDepth();
         TraverseStrategy strategy = TraverseStrategy.create(
-                                    depth >= this.concurrentDepth(),
-                                    this.graph());
+                concurrent, this.graph());
         Traverser traverser;
         if (nearest) {
             traverser = new NearestTraverser(this, strategy,
                                              sourceList, targetList, step,
-                                             depth, capacity, limit);
+                                             depth, capacity, limit, concurrent);
         } else {
             traverser = new Traverser(this, strategy,
                                       sourceList, targetList, step,
-                                      depth, capacity, limit);
+                                      depth, capacity, limit, concurrent);
         }
 
         do {
             // Forward
             traverser.forward();
             if (traverser.finished()) {
-                return traverser.paths();
+                Collection<Path> paths = traverser.paths();
+                return new WrappedPathCollection(paths, traverser.edgeResults.getEdges(paths));
             }
 
             // Backward
             traverser.backward();
             if (traverser.finished()) {
-                return traverser.paths();
+                Collection<Path> paths = traverser.paths();
+                return new WrappedPathCollection(paths, traverser.edgeResults.getEdges(paths));
             }
         } while (true);
     }
@@ -98,8 +102,9 @@ private static class Traverser extends PathTraverser {
 
         public Traverser(HugeTraverser traverser, TraverseStrategy strategy,
                          Collection<Id> sources, Collection<Id> targets,
-                         EdgeStep step, int depth, long capacity, long limit) {
-            super(traverser, strategy, sources, targets, capacity, limit);
+                         EdgeStep step, int depth, long capacity, long limit,
+                         boolean concurrent) {
+            super(traverser, strategy, sources, targets, capacity, limit, concurrent);
             this.step = step;
             this.totalSteps = depth;
         }
@@ -180,15 +185,15 @@ protected void reInitCurrentStepIfNeeded(EdgeStep step,
         }
     }
 
-    private class NearestTraverser extends Traverser {
+    private static class NearestTraverser extends Traverser {
 
         public NearestTraverser(HugeTraverser traverser,
                                 TraverseStrategy strategy,
                                 Collection<Id> sources, Collection<Id> targets,
                                 EdgeStep step, int depth, long capacity,
-                                long limit) {
+                                long limit, boolean concurrent) {
             super(traverser, strategy, sources, targets, step,
-                  depth, capacity, limit);
+                  depth, capacity, limit, concurrent);
         }
 
         @Override
@@ -274,4 +279,23 @@ protected int accessedNodes() {
             return this.sourcesAll.size() + this.targetsAll.size();
         }
     }
+
+    public static class WrappedPathCollection {
+
+        private final Collection<Path> paths;
+        private final Set<Edge> edges;
+
+        public WrappedPathCollection(Collection<Path> paths, Set<Edge> edges) {
+            this.paths = paths;
+            this.edges = edges;
+        }
+
+        public Collection<Path> paths() {
+            return paths;
+        }
+
+        public Set<Edge> edges() {
+            return edges;
+        }
+    }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java
index 3d3559b05a..28e30367d6 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java
@@ -22,25 +22,55 @@
 import java.util.List;
 import java.util.Map;
 
-import jakarta.ws.rs.core.MultivaluedMap;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-
 import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.traversal.algorithm.steps.WeightedEdgeStep;
 import org.apache.hugegraph.util.CollectionUtil;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import jakarta.ws.rs.core.MultivaluedMap;
+
 public class CustomizePathsTraverser extends HugeTraverser {
 
+    private final EdgeRecord edgeResults;
+
     public CustomizePathsTraverser(HugeGraph graph) {
         super(graph);
+        this.edgeResults = new EdgeRecord(false);
+    }
+
+    public static List<Path> topNPath(List<Path> paths,
+                                      boolean incr, long limit) {
+        paths.sort((p1, p2) -> {
+            WeightPath wp1 = (WeightPath) p1;
+            WeightPath wp2 = (WeightPath) p2;
+            int result = Double.compare(wp1.totalWeight(), wp2.totalWeight());
+            return incr ? result : -result;
+        });
+
+        if (limit == NO_LIMIT || paths.size() <= limit) {
+            return paths;
+        }
+        return paths.subList(0, (int) limit);
+    }
+
+    private static List<Node> sample(List<Node> nodes, long sample) {
+        if (nodes.size() <= sample) {
+            return nodes;
+        }
+        List<Node> result = newList((int) sample);
+        int size = nodes.size();
+        for (int random : CollectionUtil.randomSet(0, size, (int) sample)) {
+            result.add(nodes.get(random));
+        }
+        return result;
     }
 
     public List<Path> customizedPaths(Iterator<Vertex> vertices,
@@ -64,7 +94,8 @@ public List<Path> customizedPaths(Iterator<Vertex> vertices,
         int pathCount = 0;
         long access = 0;
         MultivaluedMap<Id, Node> newVertices = null;
-        root : for (WeightedEdgeStep step : steps) {
+        root:
+        for (WeightedEdgeStep step : steps) {
             stepNum--;
             newVertices = newMultivalueMap();
             Iterator<Edge> edges;
@@ -75,7 +106,11 @@ public List<Path> customizedPaths(Iterator<Vertex> vertices,
                 edges = this.edgesOfVertex(entry.getKey(), step.step());
                 while (edges.hasNext()) {
                     HugeEdge edge = (HugeEdge) edges.next();
+                    this.edgeIterCounter.addAndGet(1L);
                     Id target = edge.id().otherVertexId();
+
+                    this.edgeResults.addEdge(entry.getKey(), target, edge);
+
                     for (Node n : entry.getValue()) {
                         // If have loop, skip target
                         if (n.contains(target)) {
@@ -113,6 +148,7 @@ public List<Path> customizedPaths(Iterator<Vertex> vertices,
                     }
                 }
             }
+            this.vertexIterCounter.addAndGet(sources.size());
             // Re-init sources
             sources = newVertices;
         }
@@ -120,6 +156,9 @@ public List<Path> customizedPaths(Iterator<Vertex> vertices,
             return ImmutableList.of();
         }
         List<Path> paths = newList();
+        if (newVertices == null) {
+            return ImmutableList.of();
+        }
         for (List<Node> nodes : newVertices.values()) {
             for (Node n : nodes) {
                 if (sorted) {
@@ -133,36 +172,13 @@ public List<Path> customizedPaths(Iterator<Vertex> vertices,
         return paths;
     }
 
-    public static List<Path> topNPath(List<Path> paths,
-                                      boolean incr, long limit) {
-        paths.sort((p1, p2) -> {
-            WeightPath wp1 = (WeightPath) p1;
-            WeightPath wp2 = (WeightPath) p2;
-            int result = Double.compare(wp1.totalWeight(), wp2.totalWeight());
-            return incr ? result : -result;
-        });
-
-        if (limit == NO_LIMIT || paths.size() <= limit) {
-            return paths;
-        }
-        return paths.subList(0, (int) limit);
-    }
-
-    private static List<Node> sample(List<Node> nodes, long sample) {
-        if (nodes.size() <= sample) {
-            return nodes;
-        }
-        List<Node> result = newList((int) sample);
-        int size = nodes.size();
-        for (int random : CollectionUtil.randomSet(0, size, (int) sample)) {
-            result.add(nodes.get(random));
-        }
-        return result;
+    public EdgeRecord edgeResults() {
+        return edgeResults;
     }
 
     public static class WeightNode extends Node {
 
-        private double weight;
+        private final double weight;
 
         public WeightNode(Id id, Node parent, double weight) {
             super(id, parent);
@@ -183,7 +199,7 @@ public List<Double> weights() {
 
     public static class WeightPath extends Path {
 
-        private List<Double> weights;
+        private final List<Double> weights;
         private double totalWeight;
 
         public WeightPath(List<Id> vertices,
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java
index f097711e04..7b6bf8f76f 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java
@@ -24,26 +24,82 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import jakarta.ws.rs.core.MultivaluedMap;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeEdge;
+import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.type.define.Directions;
+import org.apache.hugegraph.util.CollectionUtil;
+import org.apache.hugegraph.util.E;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 
-import org.apache.hugegraph.structure.HugeEdge;
-import org.apache.hugegraph.structure.HugeVertex;
-import org.apache.hugegraph.util.CollectionUtil;
-import org.apache.hugegraph.util.E;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
+import jakarta.ws.rs.core.MultivaluedMap;
+
 public class CustomizedCrosspointsTraverser extends HugeTraverser {
 
+    private final EdgeRecord edgeResults;
+
     public CustomizedCrosspointsTraverser(HugeGraph graph) {
         super(graph);
+        this.edgeResults = new EdgeRecord(false);
+    }
+
+    private static CrosspointsPaths intersectionPaths(List<HugeVertex> sources,
+                                                      List<Path> paths,
+                                                      long limit) {
+        // Split paths by end vertices
+        MultivaluedMap<Id, Id> endVertices = newMultivalueMap();
+        for (Path path : paths) {
+            List<Id> vertices = path.vertices();
+            int length = vertices.size();
+            endVertices.add(vertices.get(0), vertices.get(length - 1));
+        }
+
+        Set<Id> sourceIds = sources.stream().map(HugeVertex::id)
+                                   .collect(Collectors.toSet());
+        Set<Id> ids = endVertices.keySet();
+        if (sourceIds.size() != ids.size() || !sourceIds.containsAll(ids)) {
+            return CrosspointsPaths.EMPTY;
+        }
+
+        // Get intersection of end vertices
+        Collection<Id> intersection = null;
+        for (List<Id> ends : endVertices.values()) {
+            if (intersection == null) {
+                intersection = ends;
+            } else {
+                intersection = CollectionUtil.intersect(intersection, ends);
+            }
+            if (intersection == null || intersection.isEmpty()) {
+                return CrosspointsPaths.EMPTY;
+            }
+        }
+        assert intersection != null;
+        // Limit intersection number to limit crosspoints vertices in result
+        int size = intersection.size();
+        if (limit != NO_LIMIT && size > limit) {
+            intersection = newList(intersection).subList(0, size - 1);
+        }
+
+        // Filter intersection paths
+        List<Path> results = newList();
+        for (Path path : paths) {
+            List<Id> vertices = path.vertices();
+            int length = vertices.size();
+            if (intersection.contains(vertices.get(length - 1))) {
+                results.add(path);
+            }
+        }
+        return new CrosspointsPaths(newSet(intersection), results);
+    }
+
+    public EdgeRecord edgeResults() {
+        return edgeResults;
     }
 
     public CrosspointsPaths crosspointsPaths(Iterator<Vertex> vertices,
@@ -64,6 +120,8 @@ public CrosspointsPaths crosspointsPaths(Iterator<Vertex> vertices,
             initialSources.add(vertex.id(), node);
         }
         List<Path> paths = newList();
+        long edgeCount = 0L;
+        long vertexCount = 0L;
 
         for (PathPattern pathPattern : pathPatterns) {
             MultivaluedMap<Id, Node> sources = initialSources;
@@ -79,9 +137,14 @@ public CrosspointsPaths crosspointsPaths(Iterator<Vertex> vertices,
                 for (Map.Entry<Id, List<Node>> entry : sources.entrySet()) {
                     List<Node> adjacency = newList();
                     edges = this.edgesOfVertex(entry.getKey(), step.edgeStep);
+                    vertexCount += 1;
                     while (edges.hasNext()) {
                         HugeEdge edge = (HugeEdge) edges.next();
+                        edgeCount += 1;
                         Id target = edge.id().otherVertexId();
+
+                        this.edgeResults.addEdge(entry.getKey(), target, edge);
+
                         for (Node n : entry.getValue()) {
                             // If have loop, skip target
                             if (n.contains(target)) {
@@ -104,67 +167,21 @@ public CrosspointsPaths crosspointsPaths(Iterator<Vertex> vertices,
                 sources = newVertices;
             }
             assert stepNum == 0;
+            assert newVertices != null;
             for (List<Node> nodes : newVertices.values()) {
                 for (Node n : nodes) {
                     paths.add(new Path(n.path()));
                 }
             }
         }
+        this.vertexIterCounter.addAndGet(vertexCount);
+        this.edgeIterCounter.addAndGet(edgeCount);
         return intersectionPaths(verticesList, paths, limit);
     }
 
-    private static CrosspointsPaths intersectionPaths(List<HugeVertex> sources,
-                                                      List<Path> paths,
-                                                      long limit) {
-        // Split paths by end vertices
-        MultivaluedMap<Id, Id> endVertices = newMultivalueMap();
-        for (Path path : paths) {
-            List<Id> vertices = path.vertices();
-            int length = vertices.size();
-            endVertices.add(vertices.get(0), vertices.get(length - 1));
-        }
-
-        Set<Id> sourceIds = sources.stream().map(HugeVertex::id)
-                                   .collect(Collectors.toSet());
-        Set<Id> ids = endVertices.keySet();
-        if (sourceIds.size() != ids.size() || !sourceIds.containsAll(ids)) {
-            return CrosspointsPaths.EMPTY;
-        }
-
-        // Get intersection of end vertices
-        Collection<Id> intersection = null;
-        for (List<Id> ends : endVertices.values()) {
-            if (intersection == null) {
-                intersection = ends;
-            } else {
-                intersection = CollectionUtil.intersect(intersection, ends);
-            }
-            if (intersection == null || intersection.isEmpty()) {
-                return CrosspointsPaths.EMPTY;
-            }
-        }
-        assert intersection != null;
-        // Limit intersection number to limit crosspoints vertices in result
-        int size = intersection.size();
-        if (limit != NO_LIMIT && size > limit) {
-            intersection = newList(intersection).subList(0, size - 1);
-        }
-
-        // Filter intersection paths
-        List<Path> results = newList();
-        for (Path path : paths) {
-            List<Id> vertices = path.vertices();
-            int length = vertices.size();
-            if (intersection.contains(vertices.get(length - 1))) {
-                results.add(path);
-            }
-        }
-        return new CrosspointsPaths(newSet(intersection), results);
-    }
-
     public static class PathPattern {
 
-        private List<Step> steps;
+        private final List<Step> steps;
 
         public PathPattern() {
             this.steps = newList();
@@ -201,8 +218,8 @@ public static class CrosspointsPaths {
                 ImmutableSet.of(), ImmutableList.of()
         );
 
-        private Set<Id> crosspoints;
-        private List<Path> paths;
+        private final Set<Id> crosspoints;
+        private final List<Path> paths;
 
         public CrosspointsPaths(Set<Id> crosspoints, List<Path> paths) {
             this.crosspoints = crosspoints;
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java
index b39ea009ff..b322167c28 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java
@@ -22,27 +22,27 @@
 import java.util.Map;
 import java.util.Set;
 
-import jakarta.ws.rs.core.MultivaluedHashMap;
-import jakarta.ws.rs.core.MultivaluedMap;
-
 import org.apache.commons.lang3.mutable.MutableInt;
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.schema.EdgeLabel;
+import org.apache.hugegraph.structure.HugeEdge;
+import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.type.define.Frequency;
+import org.apache.hugegraph.util.E;
+import org.apache.hugegraph.util.InsertionOrderUtil;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import org.apache.hugegraph.structure.HugeEdge;
-import org.apache.hugegraph.structure.HugeVertex;
-import org.apache.hugegraph.util.E;
-import org.apache.hugegraph.util.InsertionOrderUtil;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+
 public class FusiformSimilarityTraverser extends HugeTraverser {
 
     private long accessed = 0L;
@@ -51,6 +51,20 @@ public FusiformSimilarityTraverser(HugeGraph graph) {
         super(graph);
     }
 
+    private static void checkGroupArgs(String groupProperty, int minGroups) {
+        if (groupProperty == null) {
+            E.checkArgument(minGroups == 0,
+                            "Can't set min group count when " +
+                            "group property not set");
+        } else {
+            E.checkArgument(!groupProperty.isEmpty(),
+                            "The group property can't be empty");
+            E.checkArgument(minGroups > 0,
+                            "Must set min group count when " +
+                            "group property set");
+        }
+    }
+
     public SimilarsMap fusiformSimilarity(Iterator<Vertex> vertices,
                                           Directions direction, String label,
                                           int minNeighbors, double alpha,
@@ -69,10 +83,10 @@ public SimilarsMap fusiformSimilarity(Iterator<Vertex> vertices,
             HugeVertex vertex = (HugeVertex) vertices.next();
             // Find fusiform similarity for current vertex
             Set<Similar> result = this.fusiformSimilarityForVertex(
-                                  vertex, direction, label,
-                                  minNeighbors, alpha, minSimilars, top,
-                                  groupProperty, minGroups, degree, capacity,
-                                  withIntermediary);
+                    vertex, direction, label,
+                    minNeighbors, alpha, minSimilars, top,
+                    groupProperty, minGroups, degree, capacity,
+                    withIntermediary);
             if (result.isEmpty()) {
                 continue;
             }
@@ -87,11 +101,11 @@ public SimilarsMap fusiformSimilarity(Iterator<Vertex> vertices,
     }
 
     private Set<Similar> fusiformSimilarityForVertex(
-                         HugeVertex vertex, Directions direction,
-                         String label, int minNeighbors, double alpha,
-                         int minSimilars, int top, String groupProperty,
-                         int minGroups, long degree, long capacity,
-                         boolean withIntermediary) {
+            HugeVertex vertex, Directions direction,
+            String label, int minNeighbors, double alpha,
+            int minSimilars, int top, String groupProperty,
+            int minGroups, long degree, long capacity,
+            boolean withIntermediary) {
         boolean matched = this.matchMinNeighborCount(vertex, direction, label,
                                                      minNeighbors, degree);
         if (!matched) {
@@ -105,6 +119,7 @@ private Set<Similar> fusiformSimilarityForVertex(
         Map<Id, MutableInt> similars = newMap();
         MultivaluedMap<Id, Id> intermediaries = new MultivaluedHashMap<>();
         Set<Id> neighbors = newIdSet();
+        long vertexCount = 1L;
         while (edges.hasNext()) {
             Id target = ((HugeEdge) edges.next()).id().otherVertexId();
             if (neighbors.contains(target)) {
@@ -116,6 +131,7 @@ private Set<Similar> fusiformSimilarityForVertex(
             Directions backDir = direction.opposite();
             Iterator<Edge> backEdges = this.edgesOfVertex(target, backDir,
                                                           labelId, degree);
+            vertexCount += 1L;
             Set<Id> currentSimilars = newIdSet();
             while (backEdges.hasNext()) {
                 Id node = ((HugeEdge) backEdges.next()).id().otherVertexId();
@@ -137,6 +153,9 @@ private Set<Similar> fusiformSimilarityForVertex(
                 count.increment();
             }
         }
+        this.edgeIterCounter.addAndGet(this.accessed);
+        this.vertexIterCounter.addAndGet(vertexCount);
+
         // Delete source vertex
         assert similars.containsKey(vertex.id());
         similars.remove(vertex.id());
@@ -189,20 +208,6 @@ private Set<Similar> fusiformSimilarityForVertex(
         return result;
     }
 
-    private static void checkGroupArgs(String groupProperty, int minGroups) {
-        if (groupProperty == null) {
-            E.checkArgument(minGroups == 0,
-                            "Can't set min group count when " +
-                            "group property not set");
-        } else {
-            E.checkArgument(!groupProperty.isEmpty(),
-                            "The group property can't be empty");
-            E.checkArgument(minGroups > 0,
-                            "Must set min group count when " +
-                            "group property set");
-        }
-    }
-
     private boolean matchMinNeighborCount(HugeVertex vertex,
                                           Directions direction,
                                           String label,
@@ -249,7 +254,7 @@ public Similar(Id id, double score, List<Id> intermediaries) {
             this.id = id;
             this.score = score;
             assert newSet(intermediaries).size() == intermediaries.size() :
-                   "Invalid intermediaries";
+                    "Invalid intermediaries";
             this.intermediaries = intermediaries;
         }
 
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java
index 21344e6b2a..c0d36f31bd 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java
@@ -19,15 +19,16 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-
-import jakarta.ws.rs.core.MultivaluedHashMap;
-import jakarta.ws.rs.core.MultivaluedMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
 
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.hugegraph.HugeException;
@@ -39,39 +40,38 @@
 import org.apache.hugegraph.backend.query.QueryResults;
 import org.apache.hugegraph.backend.tx.GraphTransaction;
 import org.apache.hugegraph.config.CoreOptions;
-import org.apache.hugegraph.schema.SchemaLabel;
-import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
-import org.apache.hugegraph.type.HugeType;
-import org.apache.hugegraph.type.define.CollectionType;
-import org.apache.hugegraph.type.define.Directions;
-import org.apache.hugegraph.type.define.HugeKeys;
-import org.apache.hugegraph.util.collection.CollectionFactory;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-import org.slf4j.Logger;
-
 import org.apache.hugegraph.exception.NotFoundException;
 import org.apache.hugegraph.iterator.ExtendableIterator;
 import org.apache.hugegraph.iterator.FilterIterator;
 import org.apache.hugegraph.iterator.LimitIterator;
 import org.apache.hugegraph.iterator.MapperIterator;
 import org.apache.hugegraph.perf.PerfUtil.Watched;
+import org.apache.hugegraph.schema.SchemaLabel;
 import org.apache.hugegraph.structure.HugeEdge;
+import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.traversal.optimize.TraversalUtil;
+import org.apache.hugegraph.type.HugeType;
+import org.apache.hugegraph.type.define.CollectionType;
+import org.apache.hugegraph.type.define.Directions;
+import org.apache.hugegraph.type.define.HugeKeys;
 import org.apache.hugegraph.util.CollectionUtil;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.InsertionOrderUtil;
 import org.apache.hugegraph.util.Log;
+import org.apache.hugegraph.util.collection.CollectionFactory;
+import org.apache.hugegraph.util.collection.ObjectIntMapping;
+import org.apache.hugegraph.util.collection.ObjectIntMappingFactory;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.slf4j.Logger;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
-public class HugeTraverser {
-
-    protected static final Logger LOG = Log.logger(HugeTraverser.class);
-
-    private HugeGraph graph;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
 
-    private static CollectionFactory collectionFactory;
+public class HugeTraverser {
 
     public static final String DEFAULT_CAPACITY = "10000000";
     public static final String DEFAULT_ELEMENTS_LIMIT = "10000000";
@@ -82,13 +82,16 @@ public class HugeTraverser {
     public static final String DEFAULT_SAMPLE = "100";
     public static final String DEFAULT_WEIGHT = "0";
     public static final int DEFAULT_MAX_DEPTH = 5000;
-
-    protected static final int MAX_VERTICES = 10;
-
     // Empirical value of scan limit, with which results can be returned in 3s
     public static final String DEFAULT_PAGE_LIMIT = "100000";
-
     public static final long NO_LIMIT = -1L;
+    protected static final Logger LOG = Log.logger(HugeTraverser.class);
+    protected static final int MAX_VERTICES = 10;
+    private static CollectionFactory collectionFactory;
+    private final HugeGraph graph;
+    // for apimeasure
+    public AtomicLong edgeIterCounter = new AtomicLong(0);
+    public AtomicLong vertexIterCounter = new AtomicLong(0);
 
     public HugeTraverser(HugeGraph graph) {
         this.graph = graph;
@@ -97,6 +100,178 @@ public HugeTraverser(HugeGraph graph) {
         }
     }
 
+    public static void checkDegree(long degree) {
+        checkPositiveOrNoLimit(degree, "max degree");
+    }
+
+    public static void checkCapacity(long capacity) {
+        checkPositiveOrNoLimit(capacity, "capacity");
+    }
+
+    public static void checkLimit(long limit) {
+        checkPositiveOrNoLimit(limit, "limit");
+    }
+
+    public static void checkPositive(long value, String name) {
+        E.checkArgument(value > 0,
+                        "The %s parameter must be > 0, but got %s",
+                        name, value);
+    }
+
+    public static void checkPositiveOrNoLimit(long value, String name) {
+        E.checkArgument(value > 0L || value == NO_LIMIT,
+                        "The %s parameter must be > 0 or == %s, but got: %s",
+                        name, NO_LIMIT, value);
+    }
+
+    public static void checkNonNegative(long value, String name) {
+        E.checkArgument(value >= 0L,
+                        "The %s parameter must be >= 0, but got: %s",
+                        name, value);
+    }
+
+    public static void checkNonNegativeOrNoLimit(long value, String name) {
+        E.checkArgument(value >= 0L || value == NO_LIMIT,
+                        "The %s parameter must be >= 0 or == %s, but got: %s",
+                        name, NO_LIMIT, value);
+    }
+
+    public static void checkCapacity(long capacity, long access,
+                                     String traverse) {
+        if (capacity != NO_LIMIT && access > capacity) {
+            throw new HugeException("Exceed capacity '%s' while finding %s",
+                                    capacity, traverse);
+        }
+    }
+
+    public static void checkSkipDegree(long skipDegree, long degree,
+                                       long capacity) {
+        E.checkArgument(skipDegree >= 0L &&
+                        skipDegree <= Query.DEFAULT_CAPACITY,
+                        "The skipped degree must be in [0, %s], but got '%s'",
+                        Query.DEFAULT_CAPACITY, skipDegree);
+        if (capacity != NO_LIMIT) {
+            E.checkArgument(degree != NO_LIMIT && degree < capacity,
+                            "The max degree must be < capacity");
+            E.checkArgument(skipDegree < capacity,
+                            "The skipped degree must be < capacity");
+        }
+        if (skipDegree > 0L) {
+            E.checkArgument(degree != NO_LIMIT && skipDegree >= degree,
+                            "The skipped degree must be >= max degree, " +
+                            "but got skipped degree '%s' and max degree '%s'",
+                            skipDegree, degree);
+        }
+    }
+
+    public static <K, V extends Comparable<? super V>> Map<K, V> topN(
+            Map<K, V> map,
+            boolean sorted,
+            long limit) {
+        if (sorted) {
+            map = CollectionUtil.sortByValue(map, false);
+        }
+        if (limit == NO_LIMIT || map.size() <= limit) {
+            return map;
+        }
+        Map<K, V> results = InsertionOrderUtil.newMap();
+        long count = 0L;
+        for (Map.Entry<K, V> entry : map.entrySet()) {
+            results.put(entry.getKey(), entry.getValue());
+            if (++count >= limit) {
+                break;
+            }
+        }
+        return results;
+    }
+
+    public static Iterator<Edge> skipSuperNodeIfNeeded(Iterator<Edge> edges,
+                                                       long degree,
+                                                       long skipDegree) {
+        if (skipDegree <= 0L) {
+            return edges;
+        }
+        List<Edge> edgeList = newList();
+        for (int i = 1; edges.hasNext(); i++) {
+            Edge edge = edges.next();
+            if (i <= degree) {
+                edgeList.add(edge);
+            }
+            if (i >= skipDegree) {
+                return QueryResults.emptyIterator();
+            }
+        }
+        return edgeList.iterator();
+    }
+
+    protected static Set<Id> newIdSet() {
+        return collectionFactory.newIdSet();
+    }
+
+    protected static <V> Set<V> newSet() {
+        return newSet(false);
+    }
+
+    protected static <V> Set<V> newSet(boolean concurrent) {
+        if (concurrent) {
+            return ConcurrentHashMap.newKeySet();
+        } else {
+            return collectionFactory.newSet();
+        }
+    }
+
+    protected static <V> Set<V> newSet(int initialCapacity) {
+        return collectionFactory.newSet(initialCapacity);
+    }
+
+    protected static <V> Set<V> newSet(Collection<V> collection) {
+        return collectionFactory.newSet(collection);
+    }
+
+    protected static <V> List<V> newList() {
+        return collectionFactory.newList();
+    }
+
+    protected static <V> List<V> newList(int initialCapacity) {
+        return collectionFactory.newList(initialCapacity);
+    }
+
+    protected static <V> List<V> newList(Collection<V> collection) {
+        return collectionFactory.newList(collection);
+    }
+
+    protected static <K, V> Map<K, V> newMap() {
+        return collectionFactory.newMap();
+    }
+
+    protected static <K, V> Map<K, V> newMap(int initialCapacity) {
+        return collectionFactory.newMap(initialCapacity);
+    }
+
+    protected static <K, V> MultivaluedMap<K, V> newMultivalueMap() {
+        return new MultivaluedHashMap<>();
+    }
+
+    protected static List<Id> joinPath(Node prev, Node back, boolean ring) {
+        // Get self path
+        List<Id> path = prev.path();
+
+        // Get reversed other path
+        List<Id> backPath = back.path();
+        Collections.reverse(backPath);
+
+        if (!ring) {
+            // Avoid loop in path
+            if (CollectionUtils.containsAny(path, backPath)) {
+                return ImmutableList.of();
+            }
+        }
+
+        // Append other path behind self path
+        path.addAll(backPath);
+        return path;
+    }
+
     public HugeGraph graph() {
         return this.graph;
     }
@@ -157,6 +332,15 @@ protected Set<Id> adjacentVertices(Id source, EdgeStep step) {
         return neighbors;
     }
 
+    protected Iterator<Id> adjacentVertices(Id source, Directions dir,
+                                            List<Id> labels, long limit) {
+        Iterator<Edge> edges = this.edgesOfVertex(source, dir, labels, limit);
+        return new MapperIterator<>(edges, e -> {
+            HugeEdge edge = (HugeEdge) e;
+            return edge.id().otherVertexId();
+        });
+    }
+
     @Watched
     protected Iterator<Edge> edgesOfVertex(Id source, Directions dir,
                                            Id label, long limit) {
@@ -189,9 +373,26 @@ protected Iterator<Edge> edgesOfVertex(Id source, Directions dir,
         }
 
         long[] count = new long[1];
-        return new LimitIterator<>(results, e -> {
-            return count[0]++ >= limit;
-        });
+        return new LimitIterator<>(results, e -> count[0]++ >= limit);
+    }
+
+    protected Iterator<Edge> edgesOfVertex(Id source, Directions dir,
+                                           List<Id> labels, long limit) {
+        if (labels == null || labels.isEmpty()) {
+            return this.edgesOfVertex(source, dir, (Id) null, limit);
+        }
+        ExtendableIterator<Edge> results = new ExtendableIterator<>();
+        for (Id label : labels) {
+            E.checkNotNull(label, "edge label");
+            results.extend(this.edgesOfVertex(source, dir, label, limit));
+        }
+
+        if (limit == NO_LIMIT) {
+            return results;
+        }
+
+        long[] count = new long[1];
+        return new LimitIterator<>(results, e -> count[0]++ >= limit);
     }
 
     protected Iterator<Edge> edgesOfVertex(Id source, EdgeStep edgeStep) {
@@ -253,7 +454,7 @@ private void fillFilterBySortKeys(Query query, Id[] edgeLabels,
         if (!GraphTransaction.matchFullEdgeSortKeys(condQuery, this.graph())) {
             Id label = condQuery.condition(HugeKeys.LABEL);
             E.checkArgument(false, "The properties %s does not match " +
-                            "sort keys of edge label '%s'",
+                                   "sort keys of edge label '%s'",
                             this.graph().mapPkId2Name(properties.keySet()),
                             this.graph().edgeLabel(label).name());
         }
@@ -308,182 +509,10 @@ protected void checkVertexExist(Id vertexId, String name) {
             this.graph.vertex(vertexId);
         } catch (NotFoundException e) {
             throw new IllegalArgumentException(String.format(
-                      "The %s with id '%s' does not exist", name, vertexId), e);
-        }
-    }
-
-    public static void checkDegree(long degree) {
-        checkPositiveOrNoLimit(degree, "max degree");
-    }
-
-    public static void checkCapacity(long capacity) {
-        checkPositiveOrNoLimit(capacity, "capacity");
-    }
-
-    public static void checkLimit(long limit) {
-        checkPositiveOrNoLimit(limit, "limit");
-    }
-
-    public static void checkPositive(long value, String name) {
-        E.checkArgument(value > 0,
-                        "The %s parameter must be > 0, but got %s",
-                        name, value);
-    }
-
-    public static void checkPositiveOrNoLimit(long value, String name) {
-        E.checkArgument(value > 0L || value == NO_LIMIT,
-                        "The %s parameter must be > 0 or == %s, but got: %s",
-                        name, NO_LIMIT, value);
-    }
-
-    public static void checkNonNegative(long value, String name) {
-        E.checkArgument(value >= 0L,
-                        "The %s parameter must be >= 0, but got: %s",
-                        name, value);
-    }
-
-    public static void checkNonNegativeOrNoLimit(long value, String name) {
-        E.checkArgument(value >= 0L || value == NO_LIMIT,
-                        "The %s parameter must be >= 0 or == %s, but got: %s",
-                        name, NO_LIMIT, value);
-    }
-
-    public static void checkCapacity(long capacity, long access,
-                                     String traverse) {
-        if (capacity != NO_LIMIT && access > capacity) {
-            throw new HugeException("Exceed capacity '%s' while finding %s",
-                                    capacity, traverse);
-        }
-    }
-
-    public static void checkSkipDegree(long skipDegree, long degree,
-                                       long capacity) {
-        E.checkArgument(skipDegree >= 0L &&
-                        skipDegree <= Query.DEFAULT_CAPACITY,
-                        "The skipped degree must be in [0, %s], but got '%s'",
-                        Query.DEFAULT_CAPACITY, skipDegree);
-        if (capacity != NO_LIMIT) {
-            E.checkArgument(degree != NO_LIMIT && degree < capacity,
-                            "The max degree must be < capacity");
-            E.checkArgument(skipDegree < capacity,
-                            "The skipped degree must be < capacity");
-        }
-        if (skipDegree > 0L) {
-            E.checkArgument(degree != NO_LIMIT && skipDegree >= degree,
-                            "The skipped degree must be >= max degree, " +
-                            "but got skipped degree '%s' and max degree '%s'",
-                            skipDegree, degree);
-        }
-    }
-
-    public static <K, V extends Comparable<? super V>> Map<K, V> topN(
-                                                                 Map<K, V> map,
-                                                                 boolean sorted,
-                                                                 long limit) {
-        if (sorted) {
-            map = CollectionUtil.sortByValue(map, false);
-        }
-        if (limit == NO_LIMIT || map.size() <= limit) {
-            return map;
-        }
-        Map<K, V> results = InsertionOrderUtil.newMap();
-        long count = 0L;
-        for (Map.Entry<K, V> entry : map.entrySet()) {
-            results.put(entry.getKey(), entry.getValue());
-            if (++count >= limit) {
-                break;
-            }
-        }
-        return results;
-    }
-
-    public static Iterator<Edge> skipSuperNodeIfNeeded(Iterator<Edge> edges,
-                                                       long degree,
-                                                       long skipDegree) {
-        if (skipDegree <= 0L) {
-            return edges;
-        }
-        List<Edge> edgeList = newList();
-        for (int i = 1; edges.hasNext(); i++) {
-            Edge edge = edges.next();
-            if (i <= degree) {
-                edgeList.add(edge);
-            }
-            if (i >= skipDegree) {
-                return QueryResults.emptyIterator();
-            }
-        }
-        return edgeList.iterator();
-    }
-
-    protected static Set<Id> newIdSet() {
-        return collectionFactory.newIdSet();
-    }
-
-    protected static <V> Set<V> newSet() {
-        return newSet(false);
-    }
-
-    protected static <V> Set<V> newSet(boolean concurrent) {
-        if (concurrent) {
-            return ConcurrentHashMap.newKeySet();
-        } else {
-            return collectionFactory.newSet();
+                    "The %s with id '%s' does not exist", name, vertexId), e);
         }
     }
 
-    protected static <V> Set<V> newSet(int initialCapacity) {
-        return collectionFactory.newSet(initialCapacity);
-    }
-
-    protected static <V> Set<V> newSet(Collection<V> collection) {
-        return collectionFactory.newSet(collection);
-    }
-
-    protected static <V> List<V> newList() {
-        return collectionFactory.newList();
-    }
-
-    protected static <V> List<V> newList(int initialCapacity) {
-        return collectionFactory.newList(initialCapacity);
-    }
-
-    protected static <V> List<V> newList(Collection<V> collection) {
-        return collectionFactory.newList(collection);
-    }
-
-    protected static <K, V> Map<K, V> newMap() {
-        return collectionFactory.newMap();
-    }
-
-    protected static <K, V> Map<K, V> newMap(int initialCapacity) {
-        return collectionFactory.newMap(initialCapacity);
-    }
-
-    protected static <K, V> MultivaluedMap<K, V> newMultivalueMap() {
-        return new MultivaluedHashMap<>();
-    }
-
-    protected static List<Id> joinPath(Node prev, Node back, boolean ring) {
-        // Get self path
-        List<Id> path = prev.path();
-
-        // Get reversed other path
-        List<Id> backPath = back.path();
-        Collections.reverse(backPath);
-
-        if (!ring) {
-            // Avoid loop in path
-            if (CollectionUtils.containsAny(path, backPath)) {
-                return ImmutableList.of();
-            }
-        }
-
-        // Append other path behind self path
-        path.addAll(backPath);
-        return path;
-    }
-
     public static class Node {
 
         private final Id id;
@@ -560,6 +589,7 @@ public static class Path {
 
         private final Id crosspoint;
         private final List<Id> vertices;
+        private Set<Edge> edges = Collections.emptySet();
 
         public Path(List<Id> vertices) {
             this(null, vertices);
@@ -570,6 +600,19 @@ public Path(Id crosspoint, List<Id> vertices) {
             this.vertices = vertices;
         }
 
+        public Path(List<Id> vertices, Set<Edge> edges) {
+            this(null, vertices);
+            this.edges = edges;
+        }
+
+        public Set<Edge> getEdges() {
+            return edges;
+        }
+
+        public void setEdges(Set<Edge> edges) {
+            this.edges = edges;
+        }
+
         public Id crosspoint() {
             return this.crosspoint;
         }
@@ -615,6 +658,7 @@ public int hashCode() {
          * Compares the specified object with this path for equality.
          * Returns <tt>true</tt> if and only if both have same vertices list
          * without regard of crosspoint.
+         *
          * @param other the object to be compared for equality with this path
          * @return <tt>true</tt> if the specified object is equal to this path
          */
@@ -638,6 +682,13 @@ public static class PathSet implements Set<Path> {
 
         private final Set<Path> paths;
 
+        private Set<Edge> edges = Collections.emptySet();
+
+        public PathSet(Set<Path> paths, Set<Edge> edges) {
+            this(paths);
+            this.edges = edges;
+        }
+
         public PathSet() {
             this(newSet());
         }
@@ -646,6 +697,18 @@ private PathSet(Set<Path> paths) {
             this.paths = paths;
         }
 
+        public Set<Path> getPaths() {
+            return this.paths;
+        }
+
+        public Set<Edge> getEdges() {
+            return edges;
+        }
+
+        public void setEdges(Set<Edge> edges) {
+            this.edges = edges;
+        }
+
         @Override
         public boolean add(Path path) {
             return this.paths.add(path);
@@ -729,7 +792,7 @@ public String toString() {
         }
 
         public void append(Id current) {
-            for (Iterator<Path> iter = paths.iterator(); iter.hasNext();) {
+            for (Iterator<Path> iter = paths.iterator(); iter.hasNext(); ) {
                 Path path = iter.next();
                 if (path.vertices().contains(current)) {
                     iter.remove();
@@ -739,4 +802,80 @@ public void append(Id current) {
             }
         }
     }
+
+    public static class EdgeRecord {
+        private final Map<Long, Edge> edgeMap;
+        private final ObjectIntMapping<Id> idMapping;
+
+        public EdgeRecord(boolean concurrent) {
+            this.edgeMap = new HashMap<>();
+            this.idMapping = ObjectIntMappingFactory.newObjectIntMapping(concurrent);
+        }
+
+        private static Long makeVertexPairIndex(int source, int target) {
+            return ((long) source & 0xFFFFFFFFL) |
+                   (((long) target << 32) & 0xFFFFFFFF00000000L);
+        }
+
+        public static Set<Id> getEdgeIds(Set<Edge> edges) {
+            return edges.stream().map(edge -> ((HugeEdge) edge).id()).collect(Collectors.toSet());
+        }
+
+        private int code(Id id) {
+            if (id.number()) {
+                long l = id.asLong();
+                if (0 <= l && l <= Integer.MAX_VALUE) {
+                    return (int) l;
+                }
+            }
+            int code = this.idMapping.object2Code(id);
+            assert code > 0;
+            return -code;
+        }
+
+        public void addEdge(Id source, Id target, Edge edge) {
+            Long index = makeVertexPairIndex(this.code(source), this.code(target));
+            this.edgeMap.put(index, edge);
+        }
+
+        private Edge getEdge(Id source, Id target) {
+            Long index = makeVertexPairIndex(this.code(source), this.code(target));
+            return this.edgeMap.get(index);
+        }
+
+        public Set<Edge> getEdges(HugeTraverser.Path path) {
+            if (path == null || path.vertices().isEmpty()) {
+                return new HashSet<>();
+            }
+            Iterator<Id> vertexIter = path.vertices().iterator();
+            return getEdges(vertexIter);
+        }
+
+        public Set<Edge> getEdges(Collection<HugeTraverser.Path> paths) {
+            Set<Edge> edgeIds = new HashSet<>();
+            for (HugeTraverser.Path path : paths) {
+                edgeIds.addAll(getEdges(path));
+            }
+            return edgeIds;
+        }
+
+        public Set<Edge> getEdges(Iterator<Id> vertexIter) {
+            Set<Edge> edges = new HashSet<>();
+            Id first = vertexIter.next();
+            Id second;
+            while (vertexIter.hasNext()) {
+                second = vertexIter.next();
+                Edge edge = getEdge(first, second);
+                if (edge == null) {
+                    edge = getEdge(second, first);
+                }
+                if (edge != null) {
+                    edges.add(edge);
+                }
+                first = second;
+            }
+            return edges;
+        }
+
+    }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java
index 240792abe2..a5539cd0f6 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java
@@ -27,10 +27,10 @@
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.type.define.Directions;
-import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
-
 import org.apache.hugegraph.util.CollectionUtil;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
+
 import com.google.common.collect.ImmutableMap;
 
 public class JaccardSimilarTraverser extends OltpTraverser {
@@ -39,6 +39,12 @@ public JaccardSimilarTraverser(HugeGraph graph) {
         super(graph);
     }
 
+    private static void reachCapacity(long count, long capacity) {
+        if (capacity != NO_LIMIT && count > capacity) {
+            throw new HugeException("Reach capacity '%s'", capacity);
+        }
+    }
+
     public double jaccardSimilarity(Id vertex, Id other, Directions dir,
                                     String label, long degree) {
         E.checkNotNull(vertex, "vertex id");
@@ -51,9 +57,14 @@ public double jaccardSimilarity(Id vertex, Id other, Directions dir,
         Id labelId = this.getEdgeLabelId(label);
 
         Set<Id> sourceNeighbors = IteratorUtils.set(this.adjacentVertices(
-                                  vertex, dir, labelId, degree));
+                vertex, dir, labelId, degree));
         Set<Id> targetNeighbors = IteratorUtils.set(this.adjacentVertices(
-                                  other, dir, labelId, degree));
+                other, dir, labelId, degree));
+
+        this.vertexIterCounter.addAndGet(2L);
+        this.edgeIterCounter.addAndGet(sourceNeighbors.size());
+        this.edgeIterCounter.addAndGet(targetNeighbors.size());
+
         return jaccardSimilarity(sourceNeighbors, targetNeighbors);
     }
 
@@ -96,6 +107,10 @@ public Map<Id, Double> jaccardSimilarsConcurrent(Id source, EdgeStep step,
 
         // Query neighbors
         Set<Id> layer1s = this.adjacentVertices(source, step);
+
+        this.vertexIterCounter.addAndGet(1L);
+        this.edgeIterCounter.addAndGet(layer1s.size());
+
         reachCapacity(count.get() + layer1s.size(), capacity);
         count.addAndGet(layer1s.size());
         if (layer1s.isEmpty()) {
@@ -111,6 +126,10 @@ public Map<Id, Double> jaccardSimilarsConcurrent(Id source, EdgeStep step,
                 return;
             }
             Set<Id> layer2s = this.adjacentVertices(id, step);
+
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(layer2s.size());
+
             if (layer2s.isEmpty()) {
                 results.put(id, 0.0D);
             }
@@ -130,6 +149,10 @@ public Map<Id, Double> jaccardSimilarsConcurrent(Id source, EdgeStep step,
                 return;
             }
             Set<Id> layer3s = this.adjacentVertices(id, step);
+
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(layer3s.size());
+
             reachCapacity(count.get() + layer3s.size(), capacity);
             if (layer3s.isEmpty()) {
                 results.put(id, 0.0D);
@@ -152,6 +175,10 @@ public Map<Id, Double> jaccardSimilarsSingle(Id source, EdgeStep step,
 
         // Query neighbors
         Set<Id> layer1s = this.adjacentVertices(source, step);
+
+        this.vertexIterCounter.addAndGet(1L);
+        this.edgeIterCounter.addAndGet(layer1s.size());
+
         reachCapacity(count + layer1s.size(), capacity);
         count += layer1s.size();
         if (layer1s.isEmpty()) {
@@ -168,6 +195,10 @@ public Map<Id, Double> jaccardSimilarsSingle(Id source, EdgeStep step,
                 continue;
             }
             layer2s = this.adjacentVertices(neighbor, step);
+
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(layer2s.size());
+
             if (layer2s.isEmpty()) {
                 results.put(neighbor, 0.0D);
                 continue;
@@ -188,6 +219,10 @@ public Map<Id, Double> jaccardSimilarsSingle(Id source, EdgeStep step,
                 continue;
             }
             layer3s = this.adjacentVertices(neighbor, step);
+
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(layer3s.size());
+
             reachCapacity(count + layer3s.size(), capacity);
             if (layer3s.isEmpty()) {
                 results.put(neighbor, 0.0D);
@@ -201,10 +236,4 @@ public Map<Id, Double> jaccardSimilarsSingle(Id source, EdgeStep step,
 
         return results;
     }
-
-    private static void reachCapacity(long count, long capacity) {
-        if (capacity != NO_LIMIT && count > capacity) {
-            throw new HugeException("Reach capacity '%s'", capacity);
-        }
-    }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java
index c9381cd423..b3ae29ac8f 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java
@@ -23,13 +23,12 @@
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.traversal.algorithm.records.KneighborRecords;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.type.define.Directions;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-
-import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 
 public class KneighborTraverser extends OltpTraverser {
 
@@ -53,12 +52,15 @@ public Set<Id> kneighbor(Id sourceV, Directions dir,
         Set<Id> all = newSet();
 
         latest.add(sourceV);
+        this.vertexIterCounter.addAndGet(1L);
 
         while (depth-- > 0) {
             long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size();
             latest = this.adjacentVertices(sourceV, latest, dir, labelId,
                                            all, degree, remaining);
             all.addAll(latest);
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(latest.size());
             if (reachLimit(limit, all.size())) {
                 break;
             }
@@ -84,9 +86,15 @@ public KneighborRecords customizedKneighbor(Id source, EdgeStep step,
                 return;
             }
             Iterator<Edge> edges = edgesOfVertex(v, step);
+            this.vertexIterCounter.addAndGet(1L);
             while (!this.reachLimit(limit, records.size()) && edges.hasNext()) {
-                Id target = ((HugeEdge) edges.next()).id().otherVertexId();
+                HugeEdge edge = (HugeEdge) edges.next();
+                Id target = edge.id().otherVertexId();
                 records.addPath(v, target);
+
+                records.edgeResults().addEdge(v, target, edge);
+
+                this.edgeIterCounter.addAndGet(1L);
             }
         };
 
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java
index 2c268c94b4..9f40be8fbd 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java
@@ -24,13 +24,12 @@
 import org.apache.hugegraph.HugeException;
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.traversal.algorithm.records.KoutRecords;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.type.define.Directions;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-
-import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 
 public class KoutTraverser extends OltpTraverser {
 
@@ -66,6 +65,7 @@ public Set<Id> kout(Id sourceV, Directions dir, String label,
 
         long remaining = capacity == NO_LIMIT ?
                          NO_LIMIT : capacity - latest.size();
+        this.vertexIterCounter.addAndGet(1L);
         while (depth-- > 0) {
             // Just get limit nodes in last layer if limit < remaining capacity
             if (depth == 0 && limit != NO_LIMIT &&
@@ -80,14 +80,16 @@ public Set<Id> kout(Id sourceV, Directions dir, String label,
                 latest = this.adjacentVertices(sourceV, latest, dir, labelId,
                                                null, degree, remaining);
             }
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(latest.size());
             if (capacity != NO_LIMIT) {
                 // Update 'remaining' value to record remaining capacity
                 remaining -= latest.size();
 
                 if (remaining <= 0 && depth > 0) {
                     throw new HugeException(
-                              "Reach capacity '%s' while remaining depth '%s'",
-                              capacity, depth);
+                            "Reach capacity '%s' while remaining depth '%s'",
+                            capacity, depth);
                 }
             }
         }
@@ -114,11 +116,17 @@ public KoutRecords customizedKout(Id source, EdgeStep step,
                 return;
             }
             Iterator<Edge> edges = edgesOfVertex(v, step);
+            this.vertexIterCounter.addAndGet(1L);
             while (!this.reachLimit(limit, depth[0], records.size()) &&
                    edges.hasNext()) {
-                Id target = ((HugeEdge) edges.next()).id().otherVertexId();
+                HugeEdge edge = (HugeEdge) edges.next();
+                Id target = edge.id().otherVertexId();
                 records.addPath(v, target);
                 this.checkCapacity(capacity, records.accessed(), depth[0]);
+
+                records.edgeResults().addEdge(v, target, edge);
+
+                this.edgeIterCounter.addAndGet(1L);
             }
         };
 
@@ -136,8 +144,8 @@ private void checkCapacity(long capacity, long accessed, long depth) {
         }
         if (accessed >= capacity && depth > 0) {
             throw new HugeException(
-                      "Reach capacity '%s' while remaining depth '%s'",
-                      capacity, depth);
+                    "Reach capacity '%s' while remaining depth '%s'",
+                    capacity, depth);
         }
     }
 
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java
index 493b97286d..aa498fa956 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java
@@ -19,28 +19,55 @@
 
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.function.Consumer;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
+import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import org.apache.hugegraph.structure.HugeVertex;
-import org.apache.hugegraph.util.E;
-
 public class MultiNodeShortestPathTraverser extends OltpTraverser {
 
     public MultiNodeShortestPathTraverser(HugeGraph graph) {
         super(graph);
     }
 
-    public List<Path> multiNodeShortestPath(Iterator<Vertex> vertices,
-                                            EdgeStep step, int maxDepth,
-                                            long capacity) {
+    private static <T> void cmn(List<T> all, int m, int n, int current,
+                                List<T> result, Consumer<List<T>> consumer) {
+        assert m <= all.size();
+        assert current <= all.size();
+        if (result == null) {
+            result = newList(n);
+        }
+        if (n == 0) {
+            // All n items are selected
+            consumer.accept(result);
+            return;
+        }
+        if (m < n || current >= all.size()) {
+            return;
+        }
+
+        // Select current item, continue to select C(m-1, n-1)
+        int index = result.size();
+        result.add(all.get(current));
+        cmn(all, m - 1, n - 1, ++current, result, consumer);
+        // Not select current item, continue to select C(m-1, n)
+        result.remove(index);
+        cmn(all, m - 1, n, current, result, consumer);
+    }
+
+    public WrappedListPath multiNodeShortestPath(Iterator<Vertex> vertices,
+                                                 EdgeStep step, int maxDepth,
+                                                 long capacity) {
         List<Vertex> vertexList = IteratorUtils.list(vertices);
         int vertexCount = vertexList.size();
         E.checkState(vertexCount >= 2 && vertexCount <= MAX_VERTICES,
@@ -56,70 +83,70 @@ public List<Path> multiNodeShortestPath(Iterator<Vertex> vertices,
         });
 
         if (maxDepth >= this.concurrentDepth() && vertexCount > 10) {
-            return this.multiNodeShortestPathConcurrent(pairs, step,
-                                                        maxDepth, capacity);
+            return this.multiNodeShortestPathConcurrent(pairs, step, maxDepth, capacity);
         } else {
-            return this.multiNodeShortestPathSingle(pairs, step,
-                                                    maxDepth, capacity);
+            return this.multiNodeShortestPathSingle(pairs, step, maxDepth, capacity);
         }
     }
 
-    public List<Path> multiNodeShortestPathConcurrent(List<Pair<Id, Id>> pairs,
-                                                      EdgeStep step,
-                                                      int maxDepth,
-                                                      long capacity) {
-        List<Path> results = new CopyOnWriteArrayList<>();
+    public WrappedListPath multiNodeShortestPathConcurrent(List<Pair<Id, Id>> pairs,
+                                                           EdgeStep step, int maxDepth,
+                                                           long capacity) {
+        List<Path> paths = new CopyOnWriteArrayList<>();
+        Set<Edge> edges = new CopyOnWriteArraySet<>();
         ShortestPathTraverser traverser =
-                              new ShortestPathTraverser(this.graph());
+                new ShortestPathTraverser(this.graph());
         this.traversePairs(pairs.iterator(), pair -> {
             Path path = traverser.shortestPath(pair.getLeft(), pair.getRight(),
                                                step, maxDepth, capacity);
             if (!Path.EMPTY.equals(path)) {
-                results.add(path);
+                paths.add(path);
             }
+            edges.addAll(path.getEdges());
         });
+        this.vertexIterCounter.addAndGet(traverser.vertexIterCounter.get());
+        this.edgeIterCounter.addAndGet(traverser.edgeIterCounter.get());
 
-        return results;
+        return new WrappedListPath(paths, edges);
     }
 
-    public List<Path> multiNodeShortestPathSingle(List<Pair<Id, Id>> pairs,
-                                                  EdgeStep step, int maxDepth,
-                                                  long capacity) {
-        List<Path> results = newList();
+    public WrappedListPath multiNodeShortestPathSingle(List<Pair<Id, Id>> pairs,
+                                                       EdgeStep step, int maxDepth,
+                                                       long capacity) {
+        List<Path> paths = newList();
+        Set<Edge> edges = newSet();
         ShortestPathTraverser traverser =
-                              new ShortestPathTraverser(this.graph());
+                new ShortestPathTraverser(this.graph());
         for (Pair<Id, Id> pair : pairs) {
             Path path = traverser.shortestPath(pair.getLeft(), pair.getRight(),
                                                step, maxDepth, capacity);
             if (!Path.EMPTY.equals(path)) {
-                results.add(path);
+                paths.add(path);
             }
+            edges.addAll(path.getEdges());
         }
-        return results;
+        this.vertexIterCounter.addAndGet(traverser.vertexIterCounter.get());
+        this.edgeIterCounter.addAndGet(traverser.edgeIterCounter.get());
+
+        return new WrappedListPath(paths, edges);
     }
 
-    private static <T> void cmn(List<T> all, int m, int n, int current,
-                                List<T> result, Consumer<List<T>> consumer) {
-        assert m <= all.size();
-        assert current <= all.size();
-        if (result == null) {
-            result = newList(n);
-        }
-        if (n == 0) {
-            // All n items are selected
-            consumer.accept(result);
-            return;
+    public static class WrappedListPath {
+
+        private final List<Path> paths;
+        private final Set<Edge> edges;
+
+        public WrappedListPath(List<Path> paths, Set<Edge> edges) {
+            this.paths = paths;
+            this.edges = edges;
         }
-        if (m < n || current >= all.size()) {
-            return;
+
+        public List<Path> paths() {
+            return paths;
         }
 
-        // Select current item, continue to select C(m-1, n-1)
-        int index = result.size();
-        result.add(all.get(current));
-        cmn(all, m - 1, n - 1, ++current, result, consumer);
-        // Not select current item, continue to select C(m-1, n)
-        result.remove(index);
-        cmn(all, m - 1, n, current, result, consumer);
+        public Set<Edge> edges() {
+            return edges;
+        }
     }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java
index 7ae281640d..ac98872c41 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java
@@ -17,6 +17,8 @@
 
 package org.apache.hugegraph.traversal.algorithm;
 
+import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT;
+
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -25,21 +27,18 @@
 import java.util.function.BiConsumer;
 
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeEdge;
+import org.apache.hugegraph.traversal.algorithm.HugeTraverser.EdgeRecord;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.traversal.algorithm.strategy.TraverseStrategy;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 
-import org.apache.hugegraph.structure.HugeEdge;
-
-import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT;
-
 public abstract class PathTraverser {
 
     protected final HugeTraverser traverser;
-
-    protected int stepCount;
     protected final long capacity;
     protected final long limit;
+    protected int stepCount;
     protected int totalSteps; // TODO: delete or implement abstract method
 
     protected Map<Id, List<HugeTraverser.Node>> sources;
@@ -52,10 +51,11 @@ public abstract class PathTraverser {
     protected Set<HugeTraverser.Path> paths;
 
     protected TraverseStrategy traverseStrategy;
+    protected EdgeRecord edgeResults;
 
     public PathTraverser(HugeTraverser traverser, TraverseStrategy strategy,
                          Collection<Id> sources, Collection<Id> targets,
-                         long capacity, long limit) {
+                         long capacity, long limit, boolean concurrent) {
         this.traverser = traverser;
         this.traverseStrategy = strategy;
 
@@ -79,6 +79,8 @@ public PathTraverser(HugeTraverser traverser, TraverseStrategy strategy,
         this.targetsAll.putAll(this.targets);
 
         this.paths = this.newPathSet();
+
+        this.edgeResults = new EdgeRecord(concurrent);
     }
 
     public void forward() {
@@ -145,9 +147,13 @@ private void traverseOne(Id v, EdgeStep step, boolean forward) {
         while (edges.hasNext()) {
             HugeEdge edge = (HugeEdge) edges.next();
             Id target = edge.id().otherVertexId();
+            this.traverser.edgeIterCounter.addAndGet(1L);
+
+            this.edgeResults.addEdge(v, target, edge);
 
             this.processOne(v, target, forward);
         }
+        this.traverser.vertexIterCounter.addAndGet(1L);
     }
 
     private void processOne(Id source, Id target, boolean forward) {
@@ -205,10 +211,7 @@ protected boolean finished() {
     protected boolean reachLimit() {
         HugeTraverser.checkCapacity(this.capacity, this.accessedNodes(),
                                     "template paths");
-        if (this.limit == NO_LIMIT || this.pathCount() < this.limit) {
-            return false;
-        }
-        return true;
+        return this.limit != NO_LIMIT && this.pathCount() >= this.limit;
     }
 
     protected int accessedNodes() {
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java
index bdc9712c53..d8b256082d 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java
@@ -21,13 +21,12 @@
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.traversal.algorithm.records.PathsRecords;
-import org.apache.hugegraph.type.define.Directions;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-
 import org.apache.hugegraph.perf.PerfUtil.Watched;
 import org.apache.hugegraph.structure.HugeEdge;
+import org.apache.hugegraph.traversal.algorithm.records.PathsRecords;
+import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
 
 public class PathsTraverser extends HugeTraverser {
 
@@ -75,6 +74,8 @@ public PathSet paths(Id sourceV, Directions sourceDir,
             }
             traverser.backward(sourceV, targetDir);
         }
+        vertexIterCounter.addAndGet(traverser.vertexCounter);
+        edgeIterCounter.addAndGet(traverser.edgeCounter);
         return traverser.paths();
     }
 
@@ -88,6 +89,8 @@ private class Traverser {
         private final long limit;
 
         private final PathSet paths;
+        private long vertexCounter;
+        private long edgeCounter;
 
         public Traverser(Id sourceV, Id targetV, Id label,
                          long degree, long capacity, long limit) {
@@ -96,6 +99,8 @@ public Traverser(Id sourceV, Id targetV, Id label,
             this.degree = degree;
             this.capacity = capacity;
             this.limit = limit;
+            this.vertexCounter = 0L;
+            this.edgeCounter = 0L;
 
             this.paths = new PathSet();
         }
@@ -115,10 +120,11 @@ public void forward(Id targetV, Directions direction) {
                 }
 
                 edges = edgesOfVertex(vid, direction, this.label, this.degree);
-
+                this.vertexCounter += 1L;
                 while (edges.hasNext()) {
                     HugeEdge edge = (HugeEdge) edges.next();
                     Id target = edge.id().otherVertexId();
+                    this.edgeCounter += 1L;
 
                     PathSet results = this.record.findPath(target, null,
                                                            true, false);
@@ -148,10 +154,11 @@ public void backward(Id sourceV, Directions direction) {
                 }
 
                 edges = edgesOfVertex(vid, direction, this.label, this.degree);
-
+                this.vertexCounter += 1L;
                 while (edges.hasNext()) {
                     HugeEdge edge = (HugeEdge) edges.next();
                     Id target = edge.id().otherVertexId();
+                    this.edgeCounter += 1L;
 
                     PathSet results = this.record.findPath(target, null,
                                                            true, false);
@@ -175,5 +182,9 @@ private boolean reachLimit() {
             checkCapacity(this.capacity, this.record.accessed(), "paths");
             return this.limit != NO_LIMIT && this.paths.size() >= this.limit;
         }
+
+        public long accessed() {
+            return this.record.accessed();
+        }
     }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java
index a551eb7cc2..06c5bec275 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java
@@ -17,15 +17,17 @@
 
 package org.apache.hugegraph.traversal.algorithm;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.type.define.Directions;
-import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
-
 import org.apache.hugegraph.util.CollectionUtil;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
 public class SameNeighborTraverser extends HugeTraverser {
 
@@ -46,11 +48,56 @@ public Set<Id> sameNeighbors(Id vertex, Id other, Directions direction,
         Id labelId = this.getEdgeLabelId(label);
 
         Set<Id> sourceNeighbors = IteratorUtils.set(this.adjacentVertices(
-                                  vertex, direction, labelId, degree));
+                vertex, direction, labelId, degree));
         Set<Id> targetNeighbors = IteratorUtils.set(this.adjacentVertices(
-                                  other, direction, labelId, degree));
+                other, direction, labelId, degree));
         Set<Id> sameNeighbors = (Set<Id>) CollectionUtil.intersect(
-                                sourceNeighbors, targetNeighbors);
+                sourceNeighbors, targetNeighbors);
+
+        this.vertexIterCounter.addAndGet(2L);
+        this.edgeIterCounter.addAndGet(sourceNeighbors.size());
+        this.edgeIterCounter.addAndGet(targetNeighbors.size());
+
+        if (limit != NO_LIMIT) {
+            int end = Math.min(sameNeighbors.size(), limit);
+            sameNeighbors = CollectionUtil.subSet(sameNeighbors, 0, end);
+        }
+        return sameNeighbors;
+    }
+
+    public Set<Id> sameNeighbors(List<Id> vertexIds, Directions direction,
+                                 List<String> labels, long degree, int limit) {
+        E.checkNotNull(vertexIds, "vertex ids");
+        E.checkArgument(vertexIds.size() >= 2, "vertex_list size can't " +
+                                               "be less than 2");
+        for (Id id : vertexIds) {
+            this.checkVertexExist(id, "vertex");
+        }
+        E.checkNotNull(direction, "direction");
+        checkDegree(degree);
+        checkLimit(limit);
+
+        List<Id> labelsId = new ArrayList<>();
+        if (labels != null) {
+            for (String label : labels) {
+                labelsId.add(this.getEdgeLabelId(label));
+            }
+        }
+
+        Set<Id> sameNeighbors = new HashSet<>();
+        for (int i = 0; i < vertexIds.size(); i++) {
+            Set<Id> vertexNeighbors = IteratorUtils.set(this.adjacentVertices(
+                    vertexIds.get(i), direction, labelsId, degree));
+            if (i == 0) {
+                sameNeighbors = vertexNeighbors;
+            } else {
+                sameNeighbors = (Set<Id>) CollectionUtil.intersect(
+                        sameNeighbors, vertexNeighbors);
+            }
+            this.vertexIterCounter.addAndGet(1L);
+            this.edgeIterCounter.addAndGet(vertexNeighbors.size());
+        }
+
         if (limit != NO_LIMIT) {
             int end = Math.min(sameNeighbors.size(), limit);
             sameNeighbors = CollectionUtil.subSet(sameNeighbors, 0, end);
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java
index e9584191f0..45ccdc6345 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java
@@ -20,18 +20,19 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.perf.PerfUtil.Watched;
+import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.traversal.algorithm.records.ShortestPathRecords;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.type.define.Directions;
+import org.apache.hugegraph.util.E;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import org.apache.hugegraph.perf.PerfUtil.Watched;
-import org.apache.hugegraph.structure.HugeEdge;
-import org.apache.hugegraph.util.E;
 import com.google.common.collect.ImmutableList;
 
 public class ShortestPathTraverser extends HugeTraverser {
@@ -81,7 +82,15 @@ public Path shortestPath(Id sourceV, Id targetV, Directions dir,
             checkCapacity(traverser.capacity, traverser.accessed(),
                           "shortest path");
         }
-        return paths.isEmpty() ? Path.EMPTY : paths.iterator().next();
+
+        this.vertexIterCounter.addAndGet(traverser.vertexCount);
+        this.edgeIterCounter.addAndGet(traverser.pathResults.accessed());
+
+        Path path = paths.isEmpty() ? Path.EMPTY : paths.iterator().next();
+
+        Set<Edge> edges = traverser.edgeResults.getEdges(path);
+        path.setEdges(edges);
+        return path;
     }
 
     public Path shortestPath(Id sourceV, Id targetV, EdgeStep step,
@@ -126,31 +135,40 @@ public PathSet allShortestPaths(Id sourceV, Id targetV, Directions dir,
             checkCapacity(traverser.capacity, traverser.accessed(),
                           "shortest path");
         }
+
+        this.vertexIterCounter.addAndGet(traverser.vertexCount);
+        this.edgeIterCounter.addAndGet(traverser.pathResults.accessed());
+
+        paths.setEdges(traverser.edgeResults.getEdges(paths));
         return paths;
     }
 
     private class Traverser {
 
-        private final ShortestPathRecords record;
+        private final ShortestPathRecords pathResults;
+        private final EdgeRecord edgeResults;
         private final Directions direction;
         private final Map<Id, String> labels;
         private final long degree;
         private final long skipDegree;
         private final long capacity;
+        private long vertexCount;
 
         public Traverser(Id sourceV, Id targetV, Directions dir,
                          Map<Id, String> labels, long degree,
                          long skipDegree, long capacity) {
-            this.record = new ShortestPathRecords(sourceV, targetV);
+            this.pathResults = new ShortestPathRecords(sourceV, targetV);
+            this.edgeResults = new EdgeRecord(false);
             this.direction = dir;
             this.labels = labels;
             this.degree = degree;
             this.skipDegree = skipDegree;
             this.capacity = capacity;
+            this.vertexCount = 0L;
         }
 
         public PathSet traverse(boolean all) {
-            return this.record.sourcesLessThanTargets() ?
+            return this.pathResults.sourcesLessThanTargets() ?
                    this.forward(all) : this.backward(all);
         }
 
@@ -162,21 +180,26 @@ public PathSet forward(boolean all) {
             PathSet results = new PathSet();
             long degree = this.skipDegree > 0L ? this.skipDegree : this.degree;
 
-            this.record.startOneLayer(true);
-            while (this.record.hasNextKey()) {
-                Id source = this.record.nextKey();
+            this.pathResults.startOneLayer(true);
+            while (this.pathResults.hasNextKey()) {
+                Id source = this.pathResults.nextKey();
 
                 Iterator<Edge> edges = edgesOfVertex(source, this.direction,
                                                      this.labels, degree);
                 edges = skipSuperNodeIfNeeded(edges, this.degree,
                                               this.skipDegree);
+
+                this.vertexCount += 1L;
+
                 while (edges.hasNext()) {
                     HugeEdge edge = (HugeEdge) edges.next();
                     Id target = edge.id().otherVertexId();
 
-                    PathSet paths = this.record.findPath(target,
-                                    t -> !this.superNode(t, this.direction),
-                                    all, false);
+                    this.edgeResults.addEdge(source, target, edge);
+
+                    PathSet paths = this.pathResults.findPath(target,
+                                                         t -> !this.superNode(t, this.direction),
+                                                         all, false);
 
                     if (paths.isEmpty()) {
                         continue;
@@ -186,9 +209,10 @@ public PathSet forward(boolean all) {
                         return paths;
                     }
                 }
+
             }
 
-            this.record.finishOneLayer();
+            this.pathResults.finishOneLayer();
 
             return results;
         }
@@ -202,21 +226,26 @@ public PathSet backward(boolean all) {
             long degree = this.skipDegree > 0L ? this.skipDegree : this.degree;
             Directions opposite = this.direction.opposite();
 
-            this.record.startOneLayer(false);
-            while (this.record.hasNextKey()) {
-                Id source = this.record.nextKey();
+            this.pathResults.startOneLayer(false);
+            while (this.pathResults.hasNextKey()) {
+                Id source = this.pathResults.nextKey();
 
                 Iterator<Edge> edges = edgesOfVertex(source, opposite,
                                                      this.labels, degree);
                 edges = skipSuperNodeIfNeeded(edges, this.degree,
                                               this.skipDegree);
+
+                this.vertexCount += 1L;
+
                 while (edges.hasNext()) {
                     HugeEdge edge = (HugeEdge) edges.next();
                     Id target = edge.id().otherVertexId();
 
-                    PathSet paths = this.record.findPath(target,
-                                    t -> !this.superNode(t, opposite),
-                                    all, false);
+                    this.edgeResults.addEdge(source, target, edge);
+
+                    PathSet paths = this.pathResults.findPath(target,
+                                                         t -> !this.superNode(t, opposite),
+                                                         all, false);
 
                     if (paths.isEmpty()) {
                         continue;
@@ -229,7 +258,7 @@ public PathSet backward(boolean all) {
             }
 
             // Re-init targets
-            this.record.finishOneLayer();
+            this.pathResults.finishOneLayer();
 
             return results;
         }
@@ -244,7 +273,7 @@ private boolean superNode(Id vertex, Directions direction) {
         }
 
         private long accessed() {
-            return this.record.accessed();
+            return this.pathResults.accessed();
         }
     }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java
index 0929711402..12d90600ec 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java
@@ -17,6 +17,9 @@
 
 package org.apache.hugegraph.traversal.algorithm;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -26,14 +29,14 @@
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
 import org.apache.hugegraph.backend.query.QueryResults;
-import org.apache.hugegraph.type.define.Directions;
-import org.apache.tinkerpop.gremlin.structure.Edge;
-
 import org.apache.hugegraph.structure.HugeEdge;
+import org.apache.hugegraph.type.define.Directions;
 import org.apache.hugegraph.util.CollectionUtil;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.InsertionOrderUtil;
 import org.apache.hugegraph.util.NumericUtil;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
@@ -57,13 +60,21 @@ public WeightedPaths singleSourceShortestPaths(Id sourceV, Directions dir,
 
         Id labelId = this.getEdgeLabelId(label);
         Traverser traverser = new Traverser(sourceV, dir, labelId, weight,
-                                            degree, skipDegree, capacity,
-                                            limit);
+                                            degree, skipDegree, capacity, limit);
         while (true) {
             // Found, reach max depth or reach capacity, stop searching
             traverser.forward();
             if (traverser.done()) {
-                return traverser.shortestPaths();
+                this.vertexIterCounter.addAndGet(traverser.vertexCount);
+                this.edgeIterCounter.addAndGet(traverser.edgeCount);
+                WeightedPaths paths = traverser.shortestPaths();
+                List<List<Id>> pathList = paths.pathList();
+                Set<Edge> edges = new HashSet<>();
+                for (List<Id> path : pathList) {
+                    edges.addAll(traverser.edgeRecord.getEdges(path.iterator()));
+                }
+                paths.setEdges(edges);
+                return paths;
             }
             checkCapacity(traverser.capacity, traverser.size, "shortest path");
         }
@@ -91,18 +102,107 @@ public NodeWithWeight weightedShortestPath(Id sourceV, Id targetV,
             traverser.forward();
             Map<Id, NodeWithWeight> results = traverser.shortestPaths();
             if (results.containsKey(targetV) || traverser.done()) {
-                return results.get(targetV);
+                this.vertexIterCounter.addAndGet(traverser.vertexCount);
+                this.edgeIterCounter.addAndGet(traverser.edgeCount);
+                NodeWithWeight nodeWithWeight = results.get(targetV);
+                if (nodeWithWeight != null) {
+                    Iterator<Id> vertexIter = nodeWithWeight.node.path().iterator();
+                    Set<Edge> edges = traverser.edgeRecord.getEdges(vertexIter);
+                    nodeWithWeight.setEdges(edges);
+                }
+                return nodeWithWeight;
             }
             checkCapacity(traverser.capacity, traverser.size, "shortest path");
         }
     }
 
+    public static class NodeWithWeight implements Comparable<NodeWithWeight> {
+
+        private final double weight;
+        private final Node node;
+
+        private Set<Edge> edges = Collections.emptySet();
+
+        public NodeWithWeight(double weight, Node node) {
+            this.weight = weight;
+            this.node = node;
+        }
+
+        public NodeWithWeight(double weight, Id id, NodeWithWeight prio) {
+            this(weight, new Node(id, prio.node()));
+        }
+
+        public Set<Edge> getEdges() {
+            return edges;
+        }
+
+        public void setEdges(Set<Edge> edges) {
+            this.edges = edges;
+        }
+
+        public double weight() {
+            return weight;
+        }
+
+        public Node node() {
+            return this.node;
+        }
+
+        public Map<String, Object> toMap() {
+            return ImmutableMap.of("weight", this.weight,
+                                   "vertices", this.node().path());
+        }
+
+        @Override
+        public int compareTo(NodeWithWeight other) {
+            return Double.compare(this.weight, other.weight);
+        }
+    }
+
+    public static class WeightedPaths extends LinkedHashMap<Id, NodeWithWeight> {
+
+        private static final long serialVersionUID = -313873642177730993L;
+        private Set<Edge> edges = Collections.emptySet();
+
+        public Set<Edge> getEdges() {
+            return edges;
+        }
+
+        public void setEdges(Set<Edge> edges) {
+            this.edges = edges;
+        }
+
+        public Set<Id> vertices() {
+            Set<Id> vertices = newIdSet();
+            vertices.addAll(this.keySet());
+            for (NodeWithWeight nw : this.values()) {
+                vertices.addAll(nw.node().path());
+            }
+            return vertices;
+        }
+
+        public List<List<Id>> pathList() {
+            List<List<Id>> pathList = new ArrayList<>();
+            for (NodeWithWeight nw : this.values()) {
+                pathList.add(nw.node.path());
+            }
+            return pathList;
+        }
+
+        public Map<Id, Map<String, Object>> toMap() {
+            Map<Id, Map<String, Object>> results = newMap();
+            for (Map.Entry<Id, NodeWithWeight> entry : this.entrySet()) {
+                Id source = entry.getKey();
+                NodeWithWeight nw = entry.getValue();
+                Map<String, Object> result = nw.toMap();
+                results.put(source, result);
+            }
+            return results;
+        }
+    }
+
     private class Traverser {
 
-        private WeightedPaths findingNodes = new WeightedPaths();
-        private WeightedPaths foundNodes = new WeightedPaths();
-        private Set<NodeWithWeight> sources;
-        private Id source;
         private final Directions direction;
         private final Id label;
         private final String weight;
@@ -110,15 +210,21 @@ private class Traverser {
         private final long skipDegree;
         private final long capacity;
         private final long limit;
-        private long size;
+        private final WeightedPaths findingNodes = new WeightedPaths();
+        private final WeightedPaths foundNodes = new WeightedPaths();
+        private final EdgeRecord edgeRecord;
+        private final Id source;
+        private final long size;
+        private Set<NodeWithWeight> sources;
+        private long vertexCount;
+        private long edgeCount;
         private boolean done = false;
 
         public Traverser(Id sourceV, Directions dir, Id label, String weight,
-                         long degree, long skipDegree, long capacity,
-                         long limit) {
+                         long degree, long skipDegree, long capacity, long limit) {
             this.source = sourceV;
             this.sources = ImmutableSet.of(new NodeWithWeight(
-                           0D, new Node(sourceV, null)));
+                    0D, new Node(sourceV, null)));
             this.direction = dir;
             this.label = label;
             this.weight = weight;
@@ -127,6 +233,9 @@ public Traverser(Id sourceV, Directions dir, Id label, String weight,
             this.capacity = capacity;
             this.limit = limit;
             this.size = 0L;
+            this.vertexCount = 0L;
+            this.edgeCount = 0L;
+            this.edgeRecord = new EdgeRecord(false);
         }
 
         /**
@@ -143,12 +252,16 @@ public void forward() {
                     HugeEdge edge = (HugeEdge) edges.next();
                     Id target = edge.id().otherVertexId();
 
+                    this.edgeCount += 1L;
+
                     if (this.foundNodes.containsKey(target) ||
                         this.source.equals(target)) {
                         // Already find shortest path for target, skip
                         continue;
                     }
 
+                    this.edgeRecord.addEdge(node.node().id(), target, edge);
+
                     double currentWeight = this.edgeWeight(edge);
                     double weight = currentWeight + node.weight();
                     NodeWithWeight nw = new NodeWithWeight(weight, target, node);
@@ -164,9 +277,10 @@ public void forward() {
                     }
                 }
             }
+            this.vertexCount += sources.size();
 
             Map<Id, NodeWithWeight> sorted = CollectionUtil.sortByValue(
-                                             this.findingNodes, true);
+                    this.findingNodes, true);
             double minWeight = 0;
             Set<NodeWithWeight> newSources = InsertionOrderUtil.newSet();
             for (Map.Entry<Id, NodeWithWeight> entry : sorted.entrySet()) {
@@ -209,7 +323,7 @@ private double edgeWeight(HugeEdge edge) {
                 edgeWeight = 1.0;
             } else {
                 edgeWeight = NumericUtil.convertToNumber(
-                             edge.value(this.weight)).doubleValue();
+                        edge.value(this.weight)).doubleValue();
             }
             return edgeWeight;
         }
@@ -232,62 +346,4 @@ private Iterator<Edge> skipSuperNodeIfNeeded(Iterator<Edge> edges) {
             return edgeList.iterator();
         }
     }
-
-    public static class NodeWithWeight implements Comparable<NodeWithWeight> {
-
-        private final double weight;
-        private final Node node;
-
-        public NodeWithWeight(double weight, Node node) {
-            this.weight = weight;
-            this.node = node;
-        }
-
-        public NodeWithWeight(double weight, Id id, NodeWithWeight prio) {
-            this(weight, new Node(id, prio.node()));
-        }
-
-        public double weight() {
-            return weight;
-        }
-
-        public Node node() {
-            return this.node;
-        }
-
-        public Map<String, Object> toMap() {
-            return ImmutableMap.of("weight", this.weight,
-                                   "vertices", this.node().path());
-        }
-
-        @Override
-        public int compareTo(NodeWithWeight other) {
-            return Double.compare(this.weight, other.weight);
-        }
-    }
-
-    public static class WeightedPaths extends LinkedHashMap<Id, NodeWithWeight> {
-
-        private static final long serialVersionUID = -313873642177730993L;
-
-        public Set<Id> vertices() {
-            Set<Id> vertices = newIdSet();
-            vertices.addAll(this.keySet());
-            for (NodeWithWeight nw : this.values()) {
-                vertices.addAll(nw.node().path());
-            }
-            return vertices;
-        }
-
-        public Map<Id, Map<String, Object>> toMap() {
-            Map<Id, Map<String, Object>> results = newMap();
-            for (Map.Entry<Id, NodeWithWeight> entry : this.entrySet()) {
-                Id source = entry.getKey();
-                NodeWithWeight nw = entry.getValue();
-                Map<String, Object> result = nw.toMap();
-                results.put(source, result);
-            }
-            return results;
-        }
-    }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java
index b1e2405c28..25e03996eb 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java
@@ -22,16 +22,15 @@
 import java.util.Map;
 import java.util.Set;
 
-import jakarta.ws.rs.core.MultivaluedMap;
-
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.type.define.Directions;
+import org.apache.hugegraph.util.E;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import org.apache.hugegraph.structure.HugeEdge;
-import org.apache.hugegraph.util.E;
+import jakarta.ws.rs.core.MultivaluedMap;
 
 public class SubGraphTraverser extends HugeTraverser {
 
@@ -39,17 +38,35 @@ public SubGraphTraverser(HugeGraph graph) {
         super(graph);
     }
 
-    public PathSet rays(Id sourceV, Directions dir, String label,
-                        int depth, long degree, long capacity, long limit) {
-        return this.subGraphPaths(sourceV, dir, label, depth, degree,
-                                  capacity, limit, false, false);
+    private static boolean hasMultiEdges(List<Edge> edges, Id target) {
+        boolean hasOutEdge = false;
+        boolean hasInEdge = false;
+        for (Edge edge : edges) {
+            if (((HugeEdge) edge).id().otherVertexId().equals(target)) {
+                if (((HugeEdge) edge).direction() == Directions.OUT) {
+                    hasOutEdge = true;
+                } else {
+                    hasInEdge = true;
+                }
+                if (hasOutEdge && hasInEdge) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public PathSet rays(Id sourceV, Directions dir, String label, int depth,
+                        long degree, long capacity, long limit) {
+        return this.subGraphPaths(sourceV, dir, label, depth, degree, capacity,
+                                  limit, false, false);
     }
 
     public PathSet rings(Id sourceV, Directions dir, String label, int depth,
                          boolean sourceInRing, long degree, long capacity,
                          long limit) {
-        return this.subGraphPaths(sourceV, dir, label, depth, degree,
-                                  capacity, limit, true, sourceInRing);
+        return this.subGraphPaths(sourceV, dir, label, depth, degree, capacity,
+                                  limit, true, sourceInRing);
     }
 
     private PathSet subGraphPaths(Id sourceV, Directions dir, String label,
@@ -69,48 +86,79 @@ private PathSet subGraphPaths(Id sourceV, Directions dir, String label,
                                             capacity, limit, rings,
                                             sourceInRing);
         PathSet paths = new PathSet();
-        while (true) {
+        do {
             paths.addAll(traverser.forward(dir));
-            if (--depth <= 0 || traverser.reachLimit() ||
-                traverser.finished()) {
-                break;
-            }
-        }
+        } while (--depth > 0 && !traverser.reachLimit() &&
+                 !traverser.finished());
+        this.vertexIterCounter.addAndGet(traverser.accessedVertices.size());
+        this.edgeIterCounter.addAndGet(traverser.edgeCount);
+        paths.setEdges(traverser.edgeRecord.getEdges(paths));
         return paths;
     }
 
-    private static boolean hasMultiEdges(List<Edge> edges, Id target) {
-        boolean hasOutEdge = false;
-        boolean hasInEdge = false;
-        for (Edge edge : edges) {
-            if (((HugeEdge) edge).id().otherVertexId().equals(target)) {
-                if (((HugeEdge) edge).direction() == Directions.OUT) {
-                    hasOutEdge = true;
-                } else {
-                    hasInEdge = true;
-                }
-                if (hasOutEdge && hasInEdge) {
-                    return true;
+    private static class RingPath extends Path {
+
+        public RingPath(Id crosspoint, List<Id> vertices) {
+            super(crosspoint, vertices);
+        }
+
+        @Override
+        public int hashCode() {
+            int hashCode = 0;
+            for (Id id : this.vertices()) {
+                hashCode ^= id.hashCode();
+            }
+            return hashCode;
+        }
+
+        /**
+         * Compares the specified object with this path for equality.
+         * Returns <tt>true</tt> if other path is equal to or
+         * reversed of this path.
+         *
+         * @param other the object to be compared
+         * @return <tt>true</tt> if the specified object is equal to or
+         * reversed of this path
+         */
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RingPath)) {
+                return false;
+            }
+            List<Id> vertices = this.vertices();
+            List<Id> otherVertices = ((Path) other).vertices();
+
+            if (vertices.equals(otherVertices)) {
+                return true;
+            }
+            if (vertices.size() != otherVertices.size()) {
+                return false;
+            }
+            for (int i = 0, size = vertices.size(); i < size; i++) {
+                int j = size - i - 1;
+                if (!vertices.get(i).equals(otherVertices.get(j))) {
+                    return false;
                 }
             }
+            return true;
         }
-        return false;
     }
 
     private class Traverser {
 
         private final Id source;
-        private MultivaluedMap<Id, Node> sources = newMultivalueMap();
-        private Set<Id> accessedVertices = newIdSet();
-
         private final Id label;
-        private int depth;
         private final long degree;
         private final long capacity;
         private final long limit;
         private final boolean rings;
         private final boolean sourceInRing;
+        private final Set<Id> accessedVertices = newIdSet();
+        private final EdgeRecord edgeRecord;
+        private MultivaluedMap<Id, Node> sources = newMultivalueMap();
+        private int depth;
         private long pathCount;
+        private long edgeCount;
 
         public Traverser(Id sourceV, Id label, int depth, long degree,
                          long capacity, long limit, boolean rings,
@@ -126,6 +174,8 @@ public Traverser(Id sourceV, Id label, int depth, long degree,
             this.rings = rings;
             this.sourceInRing = sourceInRing;
             this.pathCount = 0L;
+            this.edgeCount = 0L;
+            this.edgeRecord = new EdgeRecord(false);
         }
 
         /**
@@ -140,7 +190,7 @@ public PathSet forward(Directions direction) {
                 Id vid = entry.getKey();
                 // Record edgeList to determine if multiple edges exist
                 List<Edge> edgeList = IteratorUtils.list(edgesOfVertex(
-                                      vid, direction, this.label, this.degree));
+                        vid, direction, this.label, this.degree));
                 edges = edgeList.iterator();
 
                 if (!edges.hasNext()) {
@@ -163,7 +213,11 @@ public PathSet forward(Directions direction) {
                 while (edges.hasNext()) {
                     neighborCount++;
                     HugeEdge edge = (HugeEdge) edges.next();
+                    this.edgeCount += 1L;
                     Id target = edge.id().otherVertexId();
+
+                    this.edgeRecord.addEdge(vid, target, edge);
+
                     // Avoid deduplicate path
                     if (currentNeighbors.contains(target)) {
                         continue;
@@ -241,62 +295,11 @@ public PathSet forward(Directions direction) {
         private boolean reachLimit() {
             checkCapacity(this.capacity, this.accessedVertices.size(),
                           this.rings ? "rings" : "rays");
-            if (this.limit == NO_LIMIT || this.pathCount < this.limit) {
-                return false;
-            }
-            return true;
+            return this.limit != NO_LIMIT && this.pathCount >= this.limit;
         }
 
         private boolean finished() {
             return this.sources.isEmpty();
         }
     }
-
-    private static class RingPath extends Path {
-
-        public RingPath(Id crosspoint, List<Id> vertices) {
-            super(crosspoint, vertices);
-        }
-
-        @Override
-        public int hashCode() {
-            int hashCode = 0;
-            for (Id id : this.vertices()) {
-                hashCode ^= id.hashCode();
-            }
-            return hashCode;
-        }
-
-        /**
-         * Compares the specified object with this path for equality.
-         * Returns <tt>true</tt> if other path is equal to or
-         * reversed of this path.
-         * @param other the object to be compared
-         * @return <tt>true</tt> if the specified object is equal to or
-         * reversed of this path
-         */
-        @Override
-        public boolean equals(Object other) {
-            if (!(other instanceof RingPath)) {
-                return false;
-            }
-            List<Id> vertices = this.vertices();
-            List<Id> otherVertices = ((Path) other).vertices();
-
-            if (vertices.equals(otherVertices)) {
-                return true;
-            }
-            if (vertices.size() != otherVertices.size()) {
-                return false;
-            }
-            assert vertices.size() == otherVertices.size();
-            for (int i = 0, size = vertices.size(); i < size; i++) {
-                int j = size - i - 1;
-                if (!vertices.get(i).equals(otherVertices.get(j))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java
index 85ce74651d..949014e192 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java
@@ -25,13 +25,13 @@
 
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
 import org.apache.hugegraph.traversal.algorithm.steps.RepeatEdgeStep;
 import org.apache.hugegraph.traversal.algorithm.strategy.TraverseStrategy;
-import org.apache.tinkerpop.gremlin.structure.Vertex;
-
-import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.util.E;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
 
 public class TemplatePathsTraverser extends HugeTraverser {
 
@@ -39,11 +39,11 @@ public TemplatePathsTraverser(HugeGraph graph) {
         super(graph);
     }
 
-    public Set<Path> templatePaths(Iterator<Vertex> sources,
-                                   Iterator<Vertex> targets,
-                                   List<RepeatEdgeStep> steps,
-                                   boolean withRing,
-                                   long capacity, long limit) {
+    public WrappedPathSet templatePaths(Iterator<Vertex> sources,
+                                        Iterator<Vertex> targets,
+                                        List<RepeatEdgeStep> steps,
+                                        boolean withRing, long capacity,
+                                        long limit) {
         checkCapacity(capacity);
         checkLimit(limit);
 
@@ -68,23 +68,26 @@ public Set<Path> templatePaths(Iterator<Vertex> sources,
         for (RepeatEdgeStep step : steps) {
             totalSteps += step.maxTimes();
         }
+
+        boolean concurrent = totalSteps >= this.concurrentDepth();
         TraverseStrategy strategy = TraverseStrategy.create(
-                                    totalSteps >= this.concurrentDepth(),
-                                    this.graph());
+                concurrent, this.graph());
         Traverser traverser = new Traverser(this, strategy,
                                             sourceList, targetList, steps,
-                                            withRing, capacity, limit);
+                                            withRing, capacity, limit, concurrent);
         do {
             // Forward
             traverser.forward();
             if (traverser.finished()) {
-                return traverser.paths();
+                Set<Path> paths = traverser.paths();
+                return new WrappedPathSet(paths, traverser.edgeResults.getEdges(paths));
             }
 
             // Backward
             traverser.backward();
             if (traverser.finished()) {
-                return traverser.paths();
+                Set<Path> paths = traverser.paths();
+                return new WrappedPathSet(paths, traverser.edgeResults.getEdges(paths));
             }
         } while (true);
     }
@@ -98,14 +101,14 @@ private static class Traverser extends PathTraverser {
         protected int sourceIndex;
         protected int targetIndex;
 
-        protected boolean sourceFinishOneStep = false;
-        protected boolean targetFinishOneStep = false;
+        protected boolean sourceFinishOneStep;
+        protected boolean targetFinishOneStep;
 
         public Traverser(HugeTraverser traverser, TraverseStrategy strategy,
                          Collection<Id> sources, Collection<Id> targets,
                          List<RepeatEdgeStep> steps, boolean withRing,
-                         long capacity, long limit) {
-            super(traverser, strategy, sources, targets, capacity, limit);
+                         long capacity, long limit, boolean concurrent) {
+            super(traverser, strategy, sources, targets, capacity, limit, concurrent);
 
             this.steps = steps;
             this.withRing = withRing;
@@ -135,7 +138,7 @@ public void beforeTraverse(boolean forward) {
         public void afterTraverse(EdgeStep step, boolean forward) {
 
             Map<Id, List<Node>> all = forward ? this.sourcesAll :
-                                                this.targetsAll;
+                                      this.targetsAll;
             this.addNewVerticesToAll(all);
             this.reInitCurrentStepIfNeeded(step, forward);
             this.stepCount++;
@@ -276,4 +279,23 @@ public boolean lastSuperStep() {
                    this.targetIndex == this.sourceIndex + 1;
         }
     }
+
+    public static class WrappedPathSet {
+
+        private final Set<Path> paths;
+        private final Set<Edge> edges;
+
+        public WrappedPathSet(Set<Path> paths, Set<Edge> edges) {
+            this.paths = paths;
+            this.edges = edges;
+        }
+
+        public Set<Path> paths() {
+            return paths;
+        }
+
+        public Set<Edge> edges() {
+            return edges;
+        }
+    }
 }
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java
index ce3647de2e..fe05d00824 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java
@@ -23,14 +23,14 @@
 import java.util.function.Function;
 
 import org.apache.hugegraph.backend.id.Id;
+import org.apache.hugegraph.traversal.algorithm.HugeTraverser.Path;
+import org.apache.hugegraph.traversal.algorithm.HugeTraverser.PathSet;
 import org.apache.hugegraph.traversal.algorithm.records.record.Int2IntRecord;
 import org.apache.hugegraph.traversal.algorithm.records.record.Record;
 import org.apache.hugegraph.traversal.algorithm.records.record.RecordType;
 import org.apache.hugegraph.util.collection.CollectionFactory;
 import org.apache.hugegraph.util.collection.IntMap;
 import org.apache.hugegraph.util.collection.IntSet;
-import org.apache.hugegraph.traversal.algorithm.HugeTraverser.Path;
-import org.apache.hugegraph.traversal.algorithm.HugeTraverser.PathSet;
 
 public class ShortestPathRecords extends DoubleWayMultiPathsRecords {
 
diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java
index 4e53ecc16b..d41adc92a8 100644
--- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java
+++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java
@@ -25,17 +25,18 @@
 
 import org.apache.hugegraph.HugeException;
 import org.apache.hugegraph.backend.id.Id;
-import org.apache.hugegraph.type.define.CollectionType;
-import org.apache.hugegraph.util.collection.CollectionFactory;
-import org.apache.hugegraph.util.collection.IntIterator;
-import org.apache.hugegraph.util.collection.IntMap;
-import org.apache.hugegraph.util.collection.IntSet;
 import org.apache.hugegraph.perf.PerfUtil.Watched;
+import org.apache.hugegraph.traversal.algorithm.HugeTraverser.EdgeRecord;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser.Path;
 import org.apache.hugegraph.traversal.algorithm.HugeTraverser.PathSet;
 import org.apache.hugegraph.traversal.algorithm.records.record.Int2IntRecord;
 import org.apache.hugegraph.traversal.algorithm.records.record.Record;
 import org.apache.hugegraph.traversal.algorithm.records.record.RecordType;
+import org.apache.hugegraph.type.define.CollectionType;
+import org.apache.hugegraph.util.collection.CollectionFactory;
+import org.apache.hugegraph.util.collection.IntIterator;
+import org.apache.hugegraph.util.collection.IntMap;
+import org.apache.hugegraph.util.collection.IntSet;
 
 public abstract class SingleWayMultiPathsRecords extends AbstractRecords {
 
@@ -44,7 +45,7 @@ public abstract class SingleWayMultiPathsRecords extends AbstractRecords {
     private final int sourceCode;
     private final boolean nearest;
     private final IntSet accessedVertices;
-
+    private final EdgeRecord edgeResults;
     private IntIterator parentRecordKeys;
 
     public SingleWayMultiPathsRecords(RecordType type, boolean concurrent,
@@ -58,6 +59,7 @@ public SingleWayMultiPathsRecords(RecordType type, boolean concurrent,
         firstRecord.addPath(this.sourceCode, 0);
         this.records = new Stack<>();
         this.records.push(firstRecord);
+        this.edgeResults = new EdgeRecord(concurrent);
 
         this.accessedVertices = CollectionFactory.newIntSet();
     }
@@ -176,6 +178,10 @@ protected final Stack<Record> records() {
         return this.records;
     }
 
+    public EdgeRecord edgeResults() {
+        return edgeResults;
+    }
+
     public abstract int size();
 
     public abstract List<Id> ids(long limit);
diff --git a/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java b/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java
index 1ce5f6d705..6fba538d54 100644
--- a/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java
+++ b/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java
@@ -19,14 +19,15 @@
 
 import java.util.Map;
 
-import jakarta.ws.rs.core.Response;
+import org.apache.hugegraph.api.BaseApiTest;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import org.apache.hugegraph.api.BaseApiTest;
 import com.google.common.collect.ImmutableMap;
 
+import jakarta.ws.rs.core.Response;
+
 public class JaccardSimilarityApiTest extends BaseApiTest {
 
     static final String PATH = TRAVERSERS_API + "/jaccardsimilarity";
@@ -72,9 +73,10 @@ public void testPost() {
                                        "\"top\": 3}", markoId);
         Response r = client().post(PATH, reqBody);
         String content = assertResponseStatus(200, r);
-        Double rippleJaccardSimilarity = assertJsonContains(content, rippleId);
-        Double peterJaccardSimilarity = assertJsonContains(content, peterId);
-        Double jsonJaccardSimilarity = assertJsonContains(content, jsonId);
+        Map<?, ?> jaccardSimilarity = assertJsonContains(content, "jaccard_similarity");
+        Double rippleJaccardSimilarity = assertMapContains(jaccardSimilarity, rippleId);
+        Double peterJaccardSimilarity = assertMapContains(jaccardSimilarity, peterId);
+        Double jsonJaccardSimilarity = assertMapContains(jaccardSimilarity, jsonId);
         Assert.assertEquals(0.3333, rippleJaccardSimilarity.doubleValue(),
                             0.0001);
         Assert.assertEquals(0.25, peterJaccardSimilarity.doubleValue(), 0.0001);