You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2022/10/20 02:26:41 UTC

[skywalking-cli] branch master updated: Adapt the new record query API for sub-command `metrics sampled-record` (#167)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-cli.git


The following commit(s) were added to refs/heads/master by this push:
     new e684fae  Adapt the new record query API for sub-command `metrics sampled-record` (#167)
e684fae is described below

commit e684fae0107045fc23799146d62f04cb68bd5a3b
Author: mrproliu <74...@qq.com>
AuthorDate: Thu Oct 20 10:26:35 2022 +0800

    Adapt the new record query API for sub-command `metrics sampled-record` (#167)
---
 CHANGES.md                                         |  1 +
 README.md                                          |  1 +
 ...{SampledRecords.graphql => ReadRecords.graphql} |  6 ++-
 assets/graphqls/metrics/SampledRecords.graphql     |  2 +
 cmd/swctl/main.go                                  |  2 +
 dist/LICENSE                                       |  6 +--
 go.mod                                             |  2 +-
 go.sum                                             | 15 +++---
 .../commands/metrics/aggregation/sampled-record.go | 33 ++++++++++++-
 .../metrics/aggregation/sorted-condition.go        | 33 +++++++++++++
 internal/commands/records/list.go                  | 39 +++++++++++++++
 internal/commands/records/records.go               | 31 ++++++++++++
 pkg/graphql/metadata/metadata.go                   | 56 ++++++++++++----------
 pkg/graphql/metrics/metrics.go                     | 12 +++++
 test/cases/8.8.1/expected/trace-users-detail.yml   |  3 ++
 test/cases/9.0.0/expected/trace-users-detail.yml   |  3 ++
 16 files changed, 204 insertions(+), 41 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7b5f0bf..f3271ea 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,7 @@ Release Notes.
 * Bump up swck dependency for transitive dep upgrade by @kezhenxu94 in https://github.com/apache/skywalking-cli/pull/162
 * Add the sub-commands for query sorted metrics/records by @mrproliu in https://github.com/apache/skywalking-cli/pull/163
 * Add compatibility documentation by @mrproliu in https://github.com/apache/skywalking-cli/pull/164
+* Add the sub-command `records list` for adapt the new record query API by @mrproliu in https://github.com/apache/skywalking-cli/pull/167
 
 0.10.0
 ------------------
diff --git a/README.md b/README.md
index 2d8192c..b6e8541 100644
--- a/README.md
+++ b/README.md
@@ -161,6 +161,7 @@ SkyWalking CLI and SkyWalking OAP communicate with different query version, here
 | SkyWalking CLI | OAP Server Version |
 |----------------|---------------|
 | \> = 0.11.0    | \> = 9.2.0    |
+| \> = 0.12.0    | \> = 9.3.0    |
 
 # Contributing
 
diff --git a/assets/graphqls/metrics/SampledRecords.graphql b/assets/graphqls/metrics/ReadRecords.graphql
similarity index 84%
copy from assets/graphqls/metrics/SampledRecords.graphql
copy to assets/graphqls/metrics/ReadRecords.graphql
index 9fc1a6c..6aa68fe 100644
--- a/assets/graphqls/metrics/SampledRecords.graphql
+++ b/assets/graphqls/metrics/ReadRecords.graphql
@@ -15,9 +15,11 @@
 # specific language governing permissions and limitations
 # under the License.
 
-query ($condition:TopNCondition!, $duration: Duration!) {
-    result: readSampledRecords(condition: $condition, duration: $duration) {
+query ($condition: RecordCondition!, $duration: Duration!) {
+    result: readRecords(condition: $condition, duration: $duration) {
         name
+        id
         value
+        refId
     }
 }
\ No newline at end of file
diff --git a/assets/graphqls/metrics/SampledRecords.graphql b/assets/graphqls/metrics/SampledRecords.graphql
index 9fc1a6c..6c895b5 100644
--- a/assets/graphqls/metrics/SampledRecords.graphql
+++ b/assets/graphqls/metrics/SampledRecords.graphql
@@ -18,6 +18,8 @@
 query ($condition:TopNCondition!, $duration: Duration!) {
     result: readSampledRecords(condition: $condition, duration: $duration) {
         name
+        id
         value
+        refId
     }
 }
\ No newline at end of file
diff --git a/cmd/swctl/main.go b/cmd/swctl/main.go
index 8ea20eb..32674e4 100644
--- a/cmd/swctl/main.go
+++ b/cmd/swctl/main.go
@@ -38,6 +38,7 @@ import (
 	"github.com/apache/skywalking-cli/internal/commands/metrics"
 	"github.com/apache/skywalking-cli/internal/commands/process"
 	"github.com/apache/skywalking-cli/internal/commands/profiling"
+	"github.com/apache/skywalking-cli/internal/commands/records"
 	"github.com/apache/skywalking-cli/internal/commands/service"
 	"github.com/apache/skywalking-cli/internal/commands/trace"
 	"github.com/apache/skywalking-cli/internal/logger"
@@ -104,6 +105,7 @@ services, service instances, etc.`
 		layer.Command,
 		process.Command,
 		profiling.Command,
+		records.Command,
 	}
 
 	app.Before = interceptor.BeforeChain(
diff --git a/dist/LICENSE b/dist/LICENSE
index 5e1fdb0..c6e0bca 100644
--- a/dist/LICENSE
+++ b/dist/LICENSE
@@ -294,7 +294,7 @@ The text of each license is also included at licenses/license-[project].txt.
     sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22 Apache-2.0
     sigs.k8s.io/controller-runtime v0.10.0 Apache-2.0
     sigs.k8s.io/structured-merge-diff/v4 v4.1.2 Apache-2.0
-    skywalking.apache.org/repo/goapi v0.0.0-20220714130828-0d56d1f4c592 Apache-2.0
+    skywalking.apache.org/repo/goapi v0.0.0-20221019074310-53ebda305187 Apache-2.0
 
 ========================================================================
 BSD-2-Clause licenses
@@ -360,14 +360,14 @@ The text of each license is also included at licenses/license-[project].txt.
     golang.org/x/net v0.0.0-20220909164309-bea034e7d591 BSD-3-Clause
     golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d BSD-3-Clause
     golang.org/x/sync v0.0.0-20210220032951-036812b2e83c BSD-3-Clause
-    golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 BSD-3-Clause
+    golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab BSD-3-Clause
     golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 BSD-3-Clause
     golang.org/x/text v0.3.7 BSD-3-Clause
     golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac BSD-3-Clause
     golang.org/x/tools v0.1.3 BSD-3-Clause
     golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 BSD-3-Clause
     google.golang.org/api v0.20.0 BSD-3-Clause
-    google.golang.org/protobuf v1.28.0 BSD-3-Clause
+    google.golang.org/protobuf v1.28.1 BSD-3-Clause
     gopkg.in/errgo.v2 v2.1.0 BSD-3-Clause
     gopkg.in/fsnotify.v1 v1.4.7 BSD-3-Clause
     gopkg.in/inf.v0 v0.9.1 BSD-3-Clause
diff --git a/go.mod b/go.mod
index b023557..75f9d0c 100644
--- a/go.mod
+++ b/go.mod
@@ -26,5 +26,5 @@ require (
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/apimachinery v0.22.1
 	sigs.k8s.io/controller-runtime v0.10.0
-	skywalking.apache.org/repo/goapi v0.0.0-20220714130828-0d56d1f4c592
+	skywalking.apache.org/repo/goapi v0.0.0-20221019074310-53ebda305187
 )
diff --git a/go.sum b/go.sum
index b0f2554..7df1ade 100644
--- a/go.sum
+++ b/go.sum
@@ -572,8 +572,8 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -640,9 +640,10 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -783,8 +784,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -858,6 +859,6 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3
 sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
-skywalking.apache.org/repo/goapi v0.0.0-20220714130828-0d56d1f4c592 h1:3UbXoMUpGBoYLvuUCaKPzlHCM9Q+enaaOcQ19QbTDr8=
-skywalking.apache.org/repo/goapi v0.0.0-20220714130828-0d56d1f4c592/go.mod h1:uWwwvhcwe2MD/nJCg0c1EE/eL6KzaBosLHDfMFoEJ30=
+skywalking.apache.org/repo/goapi v0.0.0-20221019074310-53ebda305187 h1:6JgAg9aohcHd72VplZUGycZgCNo6iQrz735nmtOTCnE=
+skywalking.apache.org/repo/goapi v0.0.0-20221019074310-53ebda305187/go.mod h1:lxmYWY1uAP5SLVKNymAyDzn7KG6dhPWN+pYHmyt+0vo=
 software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=
diff --git a/internal/commands/metrics/aggregation/sampled-record.go b/internal/commands/metrics/aggregation/sampled-record.go
index 6b38463..3745de7 100644
--- a/internal/commands/metrics/aggregation/sampled-record.go
+++ b/internal/commands/metrics/aggregation/sampled-record.go
@@ -18,6 +18,8 @@
 package aggregation
 
 import (
+	"fmt"
+
 	api "skywalking.apache.org/repo/goapi/query"
 
 	"github.com/urfave/cli/v2"
@@ -28,6 +30,7 @@ import (
 	"github.com/apache/skywalking-cli/internal/model"
 	"github.com/apache/skywalking-cli/pkg/display"
 	"github.com/apache/skywalking-cli/pkg/display/displayable"
+	"github.com/apache/skywalking-cli/pkg/graphql/metadata"
 	"github.com/apache/skywalking-cli/pkg/graphql/metrics"
 )
 
@@ -44,7 +47,9 @@ $ swctl metrics sampled-record --name top_n_database_statement 5
 	Flags: flags.Flags(
 		flags.DurationFlags,
 		flags.MetricsFlags,
-		flags.ServiceFlags,
+		flags.InstanceRelationFlags,
+		flags.EndpointRelationFlags,
+		flags.ProcessRelationFlags,
 		[]cli.Flag{
 			&cli.GenericFlag{
 				Name:  "order",
@@ -59,9 +64,33 @@ $ swctl metrics sampled-record --name top_n_database_statement 5
 	),
 	Before: interceptor.BeforeChain(
 		interceptor.DurationInterceptor,
-		interceptor.ParseService(false),
+		interceptor.ParseEndpointRelation(false),
+		interceptor.ParseInstanceRelation(false),
+		interceptor.ParseProcessRelation(false),
 	),
 	Action: func(ctx *cli.Context) error {
+		// read OAP version
+		major, minor, err := metadata.BackendVersion(ctx)
+		if err != nil {
+			return fmt.Errorf("read backend version failure: %v", err)
+		}
+
+		// since 9.3.0, use new record query API
+		if major >= 9 && minor >= 3 {
+			condition, duration, err1 := buildReadRecordsCondition(ctx)
+			if err1 != nil {
+				return err1
+			}
+			logger.Log.Debugln(condition.Name, condition.TopN)
+
+			records, err1 := metrics.ReadRecords(ctx, *condition, *duration)
+			if err1 != nil {
+				return err1
+			}
+
+			return display.Display(ctx, &displayable.Displayable{Data: records})
+		}
+
 		condition, duration, err := buildSortedCondition(ctx, false)
 		if err != nil {
 			return err
diff --git a/internal/commands/metrics/aggregation/sorted-condition.go b/internal/commands/metrics/aggregation/sorted-condition.go
index 9d50624..9efeb00 100644
--- a/internal/commands/metrics/aggregation/sorted-condition.go
+++ b/internal/commands/metrics/aggregation/sorted-condition.go
@@ -71,3 +71,36 @@ func buildSortedCondition(ctx *cli.Context, parseScope bool) (*api.TopNCondition
 			Step:  step,
 		}, nil
 }
+
+func buildReadRecordsCondition(ctx *cli.Context) (*api.RecordCondition, *api.Duration, error) {
+	start := ctx.String("start")
+	end := ctx.String("end")
+	step := ctx.Generic("step").(*model.StepEnumValue).Selected
+
+	metricsName := ctx.String("name")
+	order := ctx.Generic("order").(*model.OrderEnumValue).Selected
+	topN := 5
+	entity, err := interceptor.ParseEntity(ctx)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if ctx.NArg() > 0 {
+		nn, err2 := strconv.Atoi(ctx.Args().First())
+		if err2 != nil {
+			return nil, nil, fmt.Errorf("the 1st argument must be a number: %v", err2)
+		}
+		topN = nn
+	}
+
+	return &api.RecordCondition{
+			Name:         metricsName,
+			ParentEntity: entity,
+			TopN:         topN,
+			Order:        order,
+		}, &api.Duration{
+			Start: start,
+			End:   end,
+			Step:  step,
+		}, nil
+}
diff --git a/internal/commands/records/list.go b/internal/commands/records/list.go
new file mode 100644
index 0000000..7cee28a
--- /dev/null
+++ b/internal/commands/records/list.go
@@ -0,0 +1,39 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package records
+
+import (
+	"github.com/apache/skywalking-cli/internal/commands/metrics/aggregation"
+
+	"github.com/urfave/cli/v2"
+)
+
+var ListCommand = &cli.Command{
+	Name:      "list",
+	Aliases:   []string{"ls"},
+	Usage:     "List records according to the specified options",
+	ArgsUsage: "<n>",
+	UsageText: `List the top <n> records according to the specified options.
+
+Examples:
+1. Query the top 5 database statements whose execute duration are largest:
+$ swctl records list --name top_n_database_statement 5`,
+	Flags:  aggregation.SampledRecords.Flags,
+	Before: aggregation.SampledRecords.Before,
+	Action: aggregation.SampledRecords.Action,
+}
diff --git a/internal/commands/records/records.go b/internal/commands/records/records.go
new file mode 100644
index 0000000..ef7a91b
--- /dev/null
+++ b/internal/commands/records/records.go
@@ -0,0 +1,31 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package records
+
+import (
+	"github.com/urfave/cli/v2"
+)
+
+var Command = &cli.Command{
+	Name:    "records",
+	Aliases: []string{"rec"},
+	Usage:   "Records related sub-command",
+	Subcommands: cli.Commands{
+		ListCommand,
+	},
+}
diff --git a/pkg/graphql/metadata/metadata.go b/pkg/graphql/metadata/metadata.go
index 23d8161..77e7695 100644
--- a/pkg/graphql/metadata/metadata.go
+++ b/pkg/graphql/metadata/metadata.go
@@ -19,21 +19,21 @@ package metadata
 
 import (
 	"fmt"
+	"regexp"
 	"strconv"
-	"strings"
-
-	"github.com/apache/skywalking-cli/pkg/graphql/common"
 
 	api "skywalking.apache.org/repo/goapi/query"
 
-	"github.com/apache/skywalking-cli/assets"
-
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli/v2"
 
+	"github.com/apache/skywalking-cli/assets"
 	"github.com/apache/skywalking-cli/pkg/graphql/client"
+	"github.com/apache/skywalking-cli/pkg/graphql/common"
 )
 
+var backendVersion = regexp.MustCompile(`^(?P<Major>\d+)\.(?P<Minor>\d+)`)
+
 func AllServices(cliCtx *cli.Context, duration api.Duration) ([]api.Service, error) {
 	var response map[string][]api.Service
 
@@ -52,7 +52,7 @@ func AllServices(cliCtx *cli.Context, duration api.Duration) ([]api.Service, err
 func SearchService(cliCtx *cli.Context, serviceCode string) (service api.Service, err error) {
 	var response map[string]api.Service
 
-	majorVersion, err := backendMajorVersion(cliCtx)
+	majorVersion, _, err := BackendVersion(cliCtx)
 	if err != nil {
 		return api.Service{}, err
 	}
@@ -115,7 +115,7 @@ func SearchBrowserService(cliCtx *cli.Context, serviceCode string) (service api.
 func SearchEndpoints(cliCtx *cli.Context, serviceID, keyword string, limit int) ([]api.Endpoint, error) {
 	var response map[string][]api.Endpoint
 
-	majorVersion, err := backendMajorVersion(cliCtx)
+	majorVersion, _, err := BackendVersion(cliCtx)
 	if err != nil {
 		return nil, err
 	}
@@ -244,31 +244,35 @@ func ListLayerService(cliCtx *cli.Context, layer string) ([]api.Service, error)
 	return response["result"], err
 }
 
-func protocolVersion(cliCtx *cli.Context) (string, error) {
-	if majorVersion, err := backendMajorVersion(cliCtx); err != nil {
-		return "", err
-	} else if majorVersion >= 9 {
-		return "v2", nil
-	}
-	return "v1", nil
-}
-
-func backendMajorVersion(cliCtx *cli.Context) (int, error) {
+func BackendVersion(cliCtx *cli.Context) (major, minor int, err error) {
 	version, err := common.Version(cliCtx)
 	if err != nil {
-		return 0, err
+		return 0, 0, err
 	}
 	if version == "" {
-		return 0, fmt.Errorf("failed to detect OAP version")
+		return 0, 0, fmt.Errorf("failed to detect OAP version")
 	}
-	idx := strings.Index(version, ".")
-	if idx < 0 {
-		idx = 0
+
+	versions := backendVersion.FindStringSubmatch(version)
+	if len(versions) != 3 {
+		return 0, 0, fmt.Errorf("parsing OAP version failure: %s", version)
 	}
-	majorVersion := version[:idx]
-	atoi, err := strconv.Atoi(majorVersion)
+	major, err = strconv.Atoi(versions[1])
 	if err != nil {
-		return 0, err
+		return 0, 0, fmt.Errorf("parse major failure: %s", version)
 	}
-	return atoi, nil
+	minor, err = strconv.Atoi(versions[2])
+	if err != nil {
+		return 0, 0, fmt.Errorf("parse minor failure: %s", version)
+	}
+	return major, minor, nil
+}
+
+func protocolVersion(cliCtx *cli.Context) (string, error) {
+	if majorVersion, _, err := BackendVersion(cliCtx); err != nil {
+		return "", err
+	} else if majorVersion >= 9 {
+		return "v2", nil
+	}
+	return "v1", nil
 }
diff --git a/pkg/graphql/metrics/metrics.go b/pkg/graphql/metrics/metrics.go
index 3939722..870c667 100644
--- a/pkg/graphql/metrics/metrics.go
+++ b/pkg/graphql/metrics/metrics.go
@@ -104,6 +104,18 @@ func SampledRecords(ctx *cli.Context, condition api.TopNCondition, duration api.
 	return response["result"], err
 }
 
+func ReadRecords(ctx *cli.Context, condition api.RecordCondition, duration api.Duration) ([]*api.Record, error) {
+	var response map[string][]*api.Record
+
+	request := graphql.NewRequest(assets.Read("graphqls/metrics/ReadRecords.graphql"))
+	request.Var("condition", condition)
+	request.Var("duration", duration)
+
+	err := client.ExecuteQuery(ctx, request, &response)
+
+	return response["result"], err
+}
+
 func ListMetrics(ctx *cli.Context, regex string) ([]*api.MetricDefinition, error) {
 	var response map[string][]*api.MetricDefinition
 	request := graphql.NewRequest(assets.Read("graphqls/metrics/ListMetrics.graphql"))
diff --git a/test/cases/8.8.1/expected/trace-users-detail.yml b/test/cases/8.8.1/expected/trace-users-detail.yml
index e426d27..52dc63d 100644
--- a/test/cases/8.8.1/expected/trace-users-detail.yml
+++ b/test/cases/8.8.1/expected/trace-users-detail.yml
@@ -40,6 +40,7 @@ spans:
         value: 200
       {{- end }}
     logs: [ ]
+    attachedevents: []
   - traceid: {{ notEmpty .traceid }}
     segmentid: {{ .segmentid }}
     spanid: {{ .spanid }}
@@ -65,6 +66,7 @@ spans:
         value: 200
       {{- end }}
     logs: [ ]
+    attachedevents: []
   - traceid: {{ notEmpty .traceid }}
     segmentid: {{ .segmentid }}
     spanid: {{ .spanid }}
@@ -96,4 +98,5 @@ spans:
         value: 200
       {{- end }}
     logs: [ ]
+    attachedevents: []
   {{- end }}
diff --git a/test/cases/9.0.0/expected/trace-users-detail.yml b/test/cases/9.0.0/expected/trace-users-detail.yml
index e426d27..52dc63d 100644
--- a/test/cases/9.0.0/expected/trace-users-detail.yml
+++ b/test/cases/9.0.0/expected/trace-users-detail.yml
@@ -40,6 +40,7 @@ spans:
         value: 200
       {{- end }}
     logs: [ ]
+    attachedevents: []
   - traceid: {{ notEmpty .traceid }}
     segmentid: {{ .segmentid }}
     spanid: {{ .spanid }}
@@ -65,6 +66,7 @@ spans:
         value: 200
       {{- end }}
     logs: [ ]
+    attachedevents: []
   - traceid: {{ notEmpty .traceid }}
     segmentid: {{ .segmentid }}
     spanid: {{ .spanid }}
@@ -96,4 +98,5 @@ spans:
         value: 200
       {{- end }}
     logs: [ ]
+    attachedevents: []
   {{- end }}