You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2024/03/23 00:46:13 UTC

(skywalking-banyandb) branch test created (now 1b5ac714)

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

hanahmily pushed a change to branch test
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git


      at 1b5ac714 Add trace stress test

This branch includes the following new commits:

     new 1b5ac714 Add trace stress test

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(skywalking-banyandb) 01/01: Add trace stress test

Posted by ha...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a commit to branch test
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git

commit 1b5ac7143d56929e9ddfcfeee0ef548f6023f003
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Sat Mar 23 00:45:40 2024 +0000

    Add trace stress test
    
    Signed-off-by: Gao Hongtao <ha...@gmail.com>
---
 banyand/measure/block.go                           |  10 +-
 banyand/measure/block_metadata.go                  |   2 +-
 banyand/measure/part_iter.go                       |   9 +-
 banyand/measure/part_iter_test.go                  |   4 +-
 banyand/measure/tstable_test.go                    |   4 +-
 banyand/stream/block.go                            |   9 +-
 banyand/stream/block_metadata.go                   |   2 +-
 banyand/stream/part.go                             |   3 +
 banyand/stream/part_iter.go                        |   9 +-
 banyand/stream/part_iter_test.go                   |   4 +-
 banyand/stream/tstable.go                          |   9 +-
 banyand/stream/tstable_test.go                     |   4 +-
 go.mod                                             |  11 +-
 go.sum                                             |  38 ++++
 .../logical/stream/stream_plan_indexscan_local.go  |   5 +
 pkg/test/query/query.go                            | 211 +++++++++++++++++++++
 pkg/test/query/trace.go                            |  88 +++++++++
 test/integration/load/load_suite_test.go           |   2 +-
 test/stress/trace/Makefile                         |  25 +++
 test/stress/trace/docker-compose.yaml              |   8 +-
 test/stress/trace/log4j2.xml                       |   8 +-
 test/stress/trace/segment.tpl.json                 | 106 +++++++++++
 test/stress/trace/trace_suite_test.go              |  40 ++++
 23 files changed, 569 insertions(+), 42 deletions(-)

diff --git a/banyand/measure/block.go b/banyand/measure/block.go
index b23e8c34..cf3c2eab 100644
--- a/banyand/measure/block.go
+++ b/banyand/measure/block.go
@@ -214,10 +214,6 @@ func (b *block) unmarshalTagFamily(decoder *encoding.BytesBlockDecoder, tfIndex
 	}
 	bigValuePool.Release(bb)
 	b.tagFamilies[tfIndex].name = name
-
-	if len(tagProjection) < 1 {
-		return
-	}
 	cc := b.tagFamilies[tfIndex].resizeColumns(len(tagProjection))
 	for j := range tagProjection {
 		for i := range cfm.columnMetadata {
@@ -413,7 +409,7 @@ type blockCursor struct {
 func (bc *blockCursor) reset() {
 	bc.idx = 0
 	bc.p = nil
-	bc.bm = blockMetadata{}
+	bc.bm.reset()
 	bc.minTimestamp = 0
 	bc.maxTimestamp = 0
 	bc.tagProjection = bc.tagProjection[:0]
@@ -429,10 +425,10 @@ func (bc *blockCursor) reset() {
 	bc.fields.reset()
 }
 
-func (bc *blockCursor) init(p *part, bm blockMetadata, queryOpts queryOptions) {
+func (bc *blockCursor) init(p *part, bm *blockMetadata, queryOpts queryOptions) {
 	bc.reset()
 	bc.p = p
-	bc.bm = bm
+	bc.bm.copyFrom(bm)
 	bc.minTimestamp = queryOpts.minTimestamp
 	bc.maxTimestamp = queryOpts.maxTimestamp
 	bc.tagProjection = queryOpts.TagProjection
diff --git a/banyand/measure/block_metadata.go b/banyand/measure/block_metadata.go
index 91584760..82e44ca1 100644
--- a/banyand/measure/block_metadata.go
+++ b/banyand/measure/block_metadata.go
@@ -186,7 +186,7 @@ func (bm *blockMetadata) unmarshal(src []byte) ([]byte, error) {
 	return src, nil
 }
 
-func (bm blockMetadata) less(other blockMetadata) bool {
+func (bm *blockMetadata) less(other *blockMetadata) bool {
 	if bm.seriesID == other.seriesID {
 		return bm.timestamps.min < other.timestamps.min
 	}
diff --git a/banyand/measure/part_iter.go b/banyand/measure/part_iter.go
index aa41f6d1..aef39977 100644
--- a/banyand/measure/part_iter.go
+++ b/banyand/measure/part_iter.go
@@ -35,19 +35,19 @@ import (
 type partIter struct {
 	err                  error
 	p                    *part
+	curBlock             *blockMetadata
 	sids                 []common.SeriesID
 	primaryBlockMetadata []primaryBlockMetadata
 	bms                  []blockMetadata
 	compressedPrimaryBuf []byte
 	primaryBuf           []byte
-	curBlock             blockMetadata
 	sidIdx               int
 	minTimestamp         int64
 	maxTimestamp         int64
 }
 
 func (pi *partIter) reset() {
-	pi.curBlock = blockMetadata{}
+	pi.curBlock = nil
 	pi.p = nil
 	pi.sids = nil
 	pi.sidIdx = 0
@@ -60,6 +60,7 @@ func (pi *partIter) reset() {
 
 func (pi *partIter) init(bma *blockMetadataArray, p *part, sids []common.SeriesID, minTimestamp, maxTimestamp int64) {
 	pi.reset()
+	pi.curBlock = &blockMetadata{}
 	pi.p = p
 
 	pi.bms = bma.arr
@@ -227,7 +228,7 @@ func (pi *partIter) findBlock() bool {
 			continue
 		}
 
-		pi.curBlock = *bm
+		pi.curBlock = bm
 
 		pi.bms = bhs[1:]
 		return true
@@ -346,7 +347,7 @@ func (pih *partMergeIterHeap) Len() int {
 
 func (pih *partMergeIterHeap) Less(i, j int) bool {
 	x := *pih
-	return x[i].block.bm.less(x[j].block.bm)
+	return x[i].block.bm.less(&x[j].block.bm)
 }
 
 func (pih *partMergeIterHeap) Swap(i, j int) {
diff --git a/banyand/measure/part_iter_test.go b/banyand/measure/part_iter_test.go
index 2451c9b0..45c18d3c 100644
--- a/banyand/measure/part_iter_test.go
+++ b/banyand/measure/part_iter_test.go
@@ -124,7 +124,9 @@ func Test_partIter_nextBlock(t *testing.T) {
 					if pi.curBlock.seriesID == 0 {
 						t.Errorf("Expected currBlock to be initialized, but it was nil")
 					}
-					got = append(got, pi.curBlock)
+					var bm blockMetadata
+					bm.copyFrom(pi.curBlock)
+					got = append(got, bm)
 				}
 
 				if !errors.Is(pi.error(), tt.wantErr) {
diff --git a/banyand/measure/tstable_test.go b/banyand/measure/tstable_test.go
index a37d1a9f..4ea3b81d 100644
--- a/banyand/measure/tstable_test.go
+++ b/banyand/measure/tstable_test.go
@@ -155,7 +155,9 @@ func Test_tstIter(t *testing.T) {
 			if ti.piHeap[0].curBlock.seriesID == 0 {
 				t.Errorf("Expected curBlock to be initialized, but it was nil")
 			}
-			got = append(got, ti.piHeap[0].curBlock)
+			var bm blockMetadata
+			bm.copyFrom(ti.piHeap[0].curBlock)
+			got = append(got, bm)
 		}
 
 		if !errors.Is(ti.Error(), tt.wantErr) {
diff --git a/banyand/stream/block.go b/banyand/stream/block.go
index ee254902..e8f63200 100644
--- a/banyand/stream/block.go
+++ b/banyand/stream/block.go
@@ -191,9 +191,6 @@ func (b *block) unmarshalTagFamily(decoder *encoding.BytesBlockDecoder, tfIndex
 	}
 	bigValuePool.Release(bb)
 	b.tagFamilies[tfIndex].name = name
-	if len(tagProjection) < 1 {
-		return
-	}
 	cc := b.tagFamilies[tfIndex].resizeTags(len(tagProjection))
 	for j := range tagProjection {
 		for i := range tfm.tagMetadata {
@@ -426,7 +423,7 @@ type blockCursor struct {
 func (bc *blockCursor) reset() {
 	bc.idx = 0
 	bc.p = nil
-	bc.bm = blockMetadata{}
+	bc.bm.reset()
 	bc.minTimestamp = 0
 	bc.maxTimestamp = 0
 	bc.tagProjection = bc.tagProjection[:0]
@@ -441,10 +438,10 @@ func (bc *blockCursor) reset() {
 	bc.tagFamilies = tff[:0]
 }
 
-func (bc *blockCursor) init(p *part, bm blockMetadata, queryOpts queryOptions) {
+func (bc *blockCursor) init(p *part, bm *blockMetadata, queryOpts queryOptions) {
 	bc.reset()
 	bc.p = p
-	bc.bm = bm
+	bc.bm.copyFrom(bm)
 	bc.minTimestamp = queryOpts.minTimestamp
 	bc.maxTimestamp = queryOpts.maxTimestamp
 	bc.tagProjection = queryOpts.TagProjection
diff --git a/banyand/stream/block_metadata.go b/banyand/stream/block_metadata.go
index 08968b7f..8c8e2771 100644
--- a/banyand/stream/block_metadata.go
+++ b/banyand/stream/block_metadata.go
@@ -190,7 +190,7 @@ func (bm *blockMetadata) unmarshal(src []byte) ([]byte, error) {
 	return src, nil
 }
 
-func (bm blockMetadata) less(other blockMetadata) bool {
+func (bm *blockMetadata) less(other *blockMetadata) bool {
 	if bm.seriesID == other.seriesID {
 		return bm.timestamps.min < other.timestamps.min
 	}
diff --git a/banyand/stream/part.go b/banyand/stream/part.go
index 59af3462..426f4f5e 100644
--- a/banyand/stream/part.go
+++ b/banyand/stream/part.go
@@ -173,6 +173,9 @@ func (p *part) getElement(seriesID common.SeriesID, timestamp common.ItemID, tag
 				break
 			}
 		}
+		if targetBlockMetadata.seriesID != seriesID {
+			continue
+		}
 
 		timestamps := make([]int64, 0)
 		timestamps = mustReadTimestampsFrom(timestamps, &targetBlockMetadata.timestamps, int(targetBlockMetadata.count), p.timestamps)
diff --git a/banyand/stream/part_iter.go b/banyand/stream/part_iter.go
index 1b53439a..e9c24b98 100644
--- a/banyand/stream/part_iter.go
+++ b/banyand/stream/part_iter.go
@@ -35,19 +35,19 @@ import (
 type partIter struct {
 	err                  error
 	p                    *part
+	curBlock             *blockMetadata
 	sids                 []common.SeriesID
 	primaryBlockMetadata []primaryBlockMetadata
 	bms                  []blockMetadata
 	compressedPrimaryBuf []byte
 	primaryBuf           []byte
-	curBlock             blockMetadata
 	sidIdx               int
 	minTimestamp         int64
 	maxTimestamp         int64
 }
 
 func (pi *partIter) reset() {
-	pi.curBlock = blockMetadata{}
+	pi.curBlock = nil
 	pi.p = nil
 	pi.sids = nil
 	pi.sidIdx = 0
@@ -60,6 +60,7 @@ func (pi *partIter) reset() {
 
 func (pi *partIter) init(bma *blockMetadataArray, p *part, sids []common.SeriesID, minTimestamp, maxTimestamp int64) {
 	pi.reset()
+	pi.curBlock = &blockMetadata{}
 	pi.p = p
 
 	pi.bms = bma.arr
@@ -227,7 +228,7 @@ func (pi *partIter) findBlock() bool {
 			continue
 		}
 
-		pi.curBlock = *bm
+		pi.curBlock = bm
 
 		pi.bms = bhs[1:]
 		return true
@@ -341,7 +342,7 @@ func (pih *partMergeIterHeap) Len() int {
 
 func (pih *partMergeIterHeap) Less(i, j int) bool {
 	x := *pih
-	return x[i].block.bm.less(x[j].block.bm)
+	return x[i].block.bm.less(&x[j].block.bm)
 }
 
 func (pih *partMergeIterHeap) Swap(i, j int) {
diff --git a/banyand/stream/part_iter_test.go b/banyand/stream/part_iter_test.go
index 8a600cc4..5a7d258a 100644
--- a/banyand/stream/part_iter_test.go
+++ b/banyand/stream/part_iter_test.go
@@ -123,7 +123,9 @@ func Test_partIter_nextBlock(t *testing.T) {
 					if pi.curBlock.seriesID == 0 {
 						t.Errorf("Expected currBlock to be initialized, but it was nil")
 					}
-					got = append(got, pi.curBlock)
+					var bm blockMetadata
+					bm.copyFrom(pi.curBlock)
+					got = append(got, bm)
 				}
 
 				if !errors.Is(pi.error(), tt.wantErr) {
diff --git a/banyand/stream/tstable.go b/banyand/stream/tstable.go
index 272c956c..db28a032 100644
--- a/banyand/stream/tstable.go
+++ b/banyand/stream/tstable.go
@@ -285,9 +285,12 @@ func (tst *tsTable) mustAddElements(es *elements) {
 }
 
 func (tst *tsTable) getElement(seriesID common.SeriesID, timestamp common.ItemID, tagProjection []pbv1.TagProjection) (*element, int, error) {
-	tst.RLock()
-	defer tst.RUnlock()
-	for _, p := range tst.currentSnapshot().parts {
+	s := tst.currentSnapshot()
+	if s == nil {
+		return nil, 0, fmt.Errorf("snapshot is absent, cannot find element with seriesID %d and timestamp %d", seriesID, timestamp)
+	}
+	defer s.decRef()
+	for _, p := range s.parts {
 		if !p.p.containTimestamp(timestamp) {
 			continue
 		}
diff --git a/banyand/stream/tstable_test.go b/banyand/stream/tstable_test.go
index de3bef3b..f5bd52b9 100644
--- a/banyand/stream/tstable_test.go
+++ b/banyand/stream/tstable_test.go
@@ -154,7 +154,9 @@ func Test_tstIter(t *testing.T) {
 			if ti.piHeap[0].curBlock.seriesID == 0 {
 				t.Errorf("Expected curBlock to be initialized, but it was nil")
 			}
-			got = append(got, ti.piHeap[0].curBlock)
+			var bm blockMetadata
+			bm.copyFrom(ti.piHeap[0].curBlock)
+			got = append(got, bm)
 		}
 
 		if !errors.Is(ti.Error(), tt.wantErr) {
diff --git a/go.mod b/go.mod
index bfaf6890..607bef95 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.21
 
 require (
 	github.com/RoaringBitmap/roaring v1.7.0
+	github.com/apache/skywalking-cli v0.0.0-20231201043336-d5f3597733aa
 	github.com/benbjohnson/clock v1.3.0
 	github.com/blugelabs/bluge v0.2.2
 	github.com/cespare/xxhash v1.1.0
@@ -20,6 +21,7 @@ require (
 	github.com/hashicorp/golang-lru v1.0.2
 	github.com/hashicorp/golang-lru/v2 v2.0.7
 	github.com/kkdai/maglev v0.2.0
+	github.com/montanaflynn/stats v0.7.1
 	github.com/oklog/run v1.1.0
 	github.com/onsi/ginkgo/v2 v2.13.2
 	github.com/onsi/gomega v1.30.0
@@ -30,6 +32,7 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.18.2
 	github.com/stretchr/testify v1.8.4
+	github.com/urfave/cli/v2 v2.3.0
 	github.com/xhit/go-str2duration/v2 v2.1.0
 	github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04
 	go.etcd.io/etcd/client/v3 v3.5.11
@@ -43,6 +46,13 @@ require (
 	google.golang.org/grpc v1.60.1
 	google.golang.org/protobuf v1.31.0
 	sigs.k8s.io/yaml v1.4.0
+	skywalking.apache.org/repo/goapi v0.0.0-20230712035303-201c1fb2d6ec
+)
+
+require (
+	github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
+	github.com/machinebox/graphql v0.2.2 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 )
 
 require (
@@ -97,7 +107,6 @@ require (
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/montanaflynn/stats v0.7.1
 	github.com/mschoch/smat v0.2.0 // indirect
 	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
diff --git a/go.sum b/go.sum
index 3cd60e04..7847d402 100644
--- a/go.sum
+++ b/go.sum
@@ -17,6 +17,8 @@ github.com/SkyAPM/clock v1.3.1-0.20220809233656-dc7607c94a97 h1:FKuhJ+6n/DHspGeL
 github.com/SkyAPM/clock v1.3.1-0.20220809233656-dc7607c94a97/go.mod h1:2xGRl9H1pllhxTbEGO1W3gDkip8P9GQaHPni/wpdR44=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apache/skywalking-cli v0.0.0-20231201043336-d5f3597733aa h1:BaY0aGFDrBZ2Pggbj3cG7WmvN5hOWoYmnKMD+FCZdoc=
+github.com/apache/skywalking-cli v0.0.0-20231201043336-d5f3597733aa/go.mod h1:p6h6J8MbHs9pAQXQaVF3CqEZzckoTrHmDrd6/ZEkYzc=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s=
 github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc h1:Keo7wQ7UODUaHcEi7ltENhbAK2VgZjfat6mLy03tQzo=
@@ -53,6 +55,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
@@ -65,6 +69,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V
 github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -88,6 +94,9 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
 github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
@@ -139,8 +148,10 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -220,9 +231,13 @@ github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy
 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
 github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
 github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
+github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo=
+github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -281,6 +296,8 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
 github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
 github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@@ -292,6 +309,7 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
 github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
 github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
 github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -325,6 +343,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -343,6 +362,8 @@ github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDgu
 github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
 github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
@@ -350,6 +371,7 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
 github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@@ -394,6 +416,7 @@ go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZ
 go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
 go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
 go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
 go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -427,8 +450,11 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@@ -446,6 +472,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -464,6 +491,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -479,6 +507,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -504,6 +533,7 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
@@ -523,8 +553,10 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
@@ -548,6 +580,7 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
 google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
 google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
 google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
@@ -562,6 +595,9 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
 google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -600,3 +636,5 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+skywalking.apache.org/repo/goapi v0.0.0-20230712035303-201c1fb2d6ec h1:s+C9qfKkom7OYFKo8sGtXlXk6jU8DjRiI6bh/CgrpD4=
+skywalking.apache.org/repo/goapi v0.0.0-20230712035303-201c1fb2d6ec/go.mod h1:onFubXaIoY/2FTRVrLMqCTlaNq4SilAEwF/2G0IcaBw=
diff --git a/pkg/query/logical/stream/stream_plan_indexscan_local.go b/pkg/query/logical/stream/stream_plan_indexscan_local.go
index a8a90110..194b3f97 100644
--- a/pkg/query/logical/stream/stream_plan_indexscan_local.go
+++ b/pkg/query/logical/stream/stream_plan_indexscan_local.go
@@ -172,6 +172,7 @@ func buildElementsFromColumnResult(r *pbv1.StreamColumnResult) (elements []*stre
 }
 
 func buildElementsFromQueryResults(results []pbv1.StreamQueryResult) (elements []*streamv1.Element) {
+	deduplication := make(map[string]struct{})
 	for _, result := range results {
 		for {
 			r := result.Pull()
@@ -179,6 +180,10 @@ func buildElementsFromQueryResults(results []pbv1.StreamQueryResult) (elements [
 				break
 			}
 			for i := range r.Timestamps {
+				if _, ok := deduplication[r.ElementIDs[i]]; ok {
+					continue
+				}
+				deduplication[r.ElementIDs[i]] = struct{}{}
 				e := &streamv1.Element{
 					Timestamp: timestamppb.New(time.Unix(0, r.Timestamps[i])),
 					ElementId: r.ElementIDs[i],
diff --git a/pkg/test/query/query.go b/pkg/test/query/query.go
new file mode 100644
index 00000000..489cb034
--- /dev/null
+++ b/pkg/test/query/query.go
@@ -0,0 +1,211 @@
+// 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 query provides functions for analyzing and collecting metrics.
+package query
+
+import (
+	"encoding/csv"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+	"time"
+
+	"github.com/montanaflynn/stats"
+
+	"github.com/apache/skywalking-banyandb/pkg/logger"
+)
+
+const (
+	metricsFile = "data.csv"
+	resultFile  = "result.csv"
+)
+
+func init() {
+	_ = logger.Init(logger.Logging{
+		Env:   "dev",
+		Level: "debug",
+	})
+}
+
+func analyze(metricNames []string, rootPath string) {
+	file, err := os.Open(filepath.Join(rootPath, metricsFile))
+	if err != nil {
+		fmt.Println("Error opening metrics file:", err)
+		return
+	}
+	defer file.Close()
+
+	reader := csv.NewReader(file)
+	records, err := reader.ReadAll()
+	if err != nil {
+		fmt.Println("Error reading metrics file:", err)
+		return
+	}
+	if reader == nil {
+		fmt.Println("No records found in metrics file.")
+		return
+	}
+
+	// Transpose the records to handle column-based data.
+	transposed := transpose(records)
+
+	// Open the results file.
+	resultsFile, err := os.OpenFile(filepath.Join(rootPath, resultFile), os.O_CREATE|os.O_WRONLY, 0o644)
+	if err != nil {
+		fmt.Println("Error opening results file:", err)
+		return
+	}
+	defer resultsFile.Close()
+	if err = resultsFile.Truncate(0); err != nil {
+		fmt.Println("Error truncating results file:", err)
+		return
+	}
+	// Write the header to the results file.
+	writeHeader(resultsFile)
+
+	for i, record := range transposed {
+		// Convert the records to a slice of floats for analysis.
+		data := make([]float64, 0, len(record))
+		for _, r := range record {
+			d := atof(r) // Convert the string to a float.
+			if d < 0 {
+				continue
+			}
+			data = append(data, d)
+		}
+
+		// Calculate the statistics.
+		min, _ := stats.Min(data)
+		max, _ := stats.Max(data)
+		mean, _ := stats.Mean(data)
+		median, _ := stats.Median(data)
+		p95, _ := stats.Percentile(data, 95)
+
+		// Write the results to another file and print them to the console.
+		writeResults(resultsFile, metricNames[i], min, max, mean, median, p95)
+	}
+}
+
+func writeHeader(file *os.File) {
+	header := "Metric Name, Min, Max, Mean, Median, P95\n"
+	_, err := file.WriteString(header)
+	if err != nil {
+		fmt.Println("Error writing to results file:", err)
+		return
+	}
+}
+
+func writeResults(file *os.File, metricName string, min, max, mean, median, p95 float64) {
+	results := fmt.Sprintf("%s, %f, %f, %f, %f, %f\n",
+		metricName, min, max, mean, median, p95)
+
+	_, err := file.WriteString(results)
+	if err != nil {
+		fmt.Println("Error writing to results file:", err)
+		return
+	}
+
+	fmt.Print(results)
+}
+
+func transpose(slice [][]string) [][]string {
+	xl := len(slice[0])
+	yl := len(slice)
+	result := make([][]string, xl)
+	for i := range result {
+		result[i] = make([]string, yl)
+	}
+	for i, row := range slice {
+		for j, col := range row {
+			result[j][i] = col
+		}
+	}
+	return result
+}
+
+func atof(s string) float64 {
+	value, err := strconv.ParseFloat(s, 64)
+	if err != nil {
+		panic(err)
+	}
+	return value
+}
+
+type scrape func() ([]float64, error)
+
+func collect(rootPath string, scrape scrape, interval time.Duration, closeCh <-chan struct{}) {
+	file, err := os.OpenFile(filepath.Join(rootPath, metricsFile), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
+	if err != nil {
+		panic(err)
+	}
+	defer file.Close()
+	if err = file.Truncate(0); err != nil {
+		panic(err)
+	}
+	lastCollectTime := time.Now()
+	for {
+		metrics, err := scrape()
+		if err != nil {
+			logger.Errorf("Error scraping metrics: %v", err)
+			select {
+			case <-closeCh:
+				return
+			case <-time.After(5 * time.Second):
+			}
+			continue
+		}
+
+		select {
+		case <-closeCh:
+			return
+		default:
+		}
+		if time.Since(lastCollectTime) < time.Minute {
+			select {
+			case <-closeCh:
+				return
+			case <-time.After(interval):
+			}
+			continue
+		}
+		lastCollectTime = time.Now()
+
+		err = writeMetrics(file, metrics)
+		if err != nil {
+			fmt.Println("Error writing metrics:", err)
+		}
+		select {
+		case <-closeCh:
+			return
+		case <-time.After(interval):
+		}
+	}
+}
+
+func writeMetrics(file *os.File, metrics []float64) error {
+	writer := csv.NewWriter(file)
+	defer writer.Flush()
+
+	record := make([]string, len(metrics))
+	for i := range metrics {
+		record[i] = fmt.Sprintf("%f", metrics[i])
+	}
+
+	return writer.Write(record)
+}
diff --git a/pkg/test/query/trace.go b/pkg/test/query/trace.go
new file mode 100644
index 00000000..3c4834f1
--- /dev/null
+++ b/pkg/test/query/trace.go
@@ -0,0 +1,88 @@
+// 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 query
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"path"
+	"time"
+
+	"github.com/apache/skywalking-cli/pkg/graphql/trace"
+	"github.com/apache/skywalking-cli/pkg/graphql/utils"
+	"github.com/urfave/cli/v2"
+	api "skywalking.apache.org/repo/goapi/query"
+)
+
+// TraceListOrderByDuration verifies the trace list order by duration.
+func TraceListOrderByDuration(basePath string, timeout time.Duration, fs *flag.FlagSet) {
+	basePath = path.Join(basePath, "trace-duration")
+	err := os.MkdirAll(basePath, 0o755)
+	if err != nil {
+		panic(err)
+	}
+	stopCh := make(chan struct{})
+	go func() {
+		time.Sleep(timeout)
+		close(stopCh)
+	}()
+	collect(basePath, func() ([]float64, error) {
+		d, err := traceList(api.QueryOrderByDuration, fs)
+		if err != nil {
+			return nil, err
+		}
+		return []float64{d.Seconds()}, nil
+	}, 500*time.Millisecond, stopCh)
+	analyze([]string{"result"}, basePath)
+}
+
+const (
+	defaultPageSize = 15
+)
+
+func traceList(order api.QueryOrder, fs *flag.FlagSet) (time.Duration, error) {
+	ctx := cli.NewContext(cli.NewApp(), fs, nil)
+	duration := api.Duration{
+		Start: time.Now().Add(-30 * time.Minute).Format(utils.StepFormats[api.StepMinute]),
+		End:   time.Now().Format(utils.StepFormats[api.StepMinute]),
+		Step:  api.StepMinute,
+	}
+	pageNum := 1
+	serviceID := ctx.String("service-id")
+	condition := &api.TraceQueryCondition{
+		ServiceID:     &serviceID,
+		QueryDuration: &duration,
+		QueryOrder:    order,
+		TraceState:    api.TraceStateAll,
+		Paging: &api.Pagination{
+			PageNum:  &pageNum,
+			PageSize: defaultPageSize,
+		},
+	}
+	start := time.Now()
+	result, err := trace.Traces(ctx, condition)
+	elapsed := time.Since(start)
+	if err != nil {
+		return 0, err
+	}
+	if len(result.Traces) < 1 {
+		return 0, fmt.Errorf("no result")
+	}
+	return elapsed, nil
+}
diff --git a/test/integration/load/load_suite_test.go b/test/integration/load/load_suite_test.go
index 65011cd4..30f07696 100644
--- a/test/integration/load/load_suite_test.go
+++ b/test/integration/load/load_suite_test.go
@@ -44,7 +44,7 @@ func TestIntegrationLoad(t *testing.T) {
 	RunSpecs(t, "Integration Load Suite", Label("integration", "slow"))
 }
 
-var _ = Describe("Test Suite", func() {
+var _ = Describe("Load Test Suit", func() {
 	var (
 		connection *grpc.ClientConn
 		now        time.Time
diff --git a/test/stress/trace/Makefile b/test/stress/trace/Makefile
index ca57b2db..f331107e 100644
--- a/test/stress/trace/Makefile
+++ b/test/stress/trace/Makefile
@@ -26,6 +26,10 @@ endif
 
 cli_env := DOCKER_BUILDKIT=1 USER_ID=$(shell id -u) GROUP_ID=$(shell id -g)
 
+SIZE ?= 2
+
+QPS ?= 10
+
 .PHONY: clean
 clean:
 	rm -rf /tmp/banyandb-stress-trace
@@ -37,3 +41,24 @@ up: clean
 .PHONY: down
 down:
 	docker compose down
+
+.PHONY: test_traffic
+test_traffic:
+	curl -XPOST 'http://localhost:12800/mock-data/segments/tasks?size=$(SIZE)' -H'Content-Type: application/json' -d "@segment.tpl.json"
+
+.PHONY: up_traffic
+up_traffic:
+	curl -XPOST 'http://localhost:12800/mock-data/segments/tasks?qps=$(QPS)' -H'Content-Type: application/json' -d "@segment.tpl.json"
+
+.PHONY: ls_traffic
+ls_traffic:
+	curl -XGET 'http://localhost:12800/mock-data/segments/tasks'
+
+.PHONY: rm_traffic
+rm_traffic:
+	curl -XDELETE 'http://localhost:12800/mock-data/segments/tasks'
+
+.PHONY: test_query
+test_query:
+	go test -v -timeout 1h -run TestQuery ./...
+	
\ No newline at end of file
diff --git a/test/stress/trace/docker-compose.yaml b/test/stress/trace/docker-compose.yaml
index 9554ddf3..7ea06551 100644
--- a/test/stress/trace/docker-compose.yaml
+++ b/test/stress/trace/docker-compose.yaml
@@ -53,15 +53,11 @@ services:
     extends:
       file: ../../docker/base-compose.yml
       service: oap
+    image: hanahmily/data-generator:latest
     environment:
       SW_STORAGE: banyandb
     ports:
-      - 12800
-    deploy:
-      resources:
-        limits:
-          cpus: "4"
-          memory: 4G
+      - 12800:12800
     volumes:
       - ./log4j2.xml:/skywalking/config/log4j2.xml
     networks:
diff --git a/test/stress/trace/log4j2.xml b/test/stress/trace/log4j2.xml
index e3174f57..1a9ceccf 100644
--- a/test/stress/trace/log4j2.xml
+++ b/test/stress/trace/log4j2.xml
@@ -24,10 +24,10 @@
         </Console>
     </Appenders>
     <Loggers>
-        <logger name="org.apache.skywalking.oap.server.storage.plugin.banyandb" level="DEBUG"/>
-        <logger name="org.apache.skywalking.oap.server.core.storage.ttl" level="DEBUG"/>
-        <logger name="org.apache.skywalking.restapi" level="DEBUG"/>
-        <Root level="INFO">
+        <logger name="org.apache.skywalking.oap.server.storage.plugin.banyandb" level="WARN"/>
+        <logger name="org.apache.skywalking.oap.server.core.storage.ttl" level="INFO"/>
+        <logger name="org.apache.skywalking.restapi" level="INFO"/>
+        <Root level="WARN">
             <AppenderRef ref="Console"/>
         </Root>
     </Loggers>
diff --git a/test/stress/trace/segment.tpl.json b/test/stress/trace/segment.tpl.json
new file mode 100644
index 00000000..3597234a
--- /dev/null
+++ b/test/stress/trace/segment.tpl.json
@@ -0,0 +1,106 @@
+{
+  "traceId": {
+    "type": "uuid",
+    "changingFrequency": "1"
+  },
+  "serviceInstanceName": {
+    "type": "randomString",
+    "length": "10",
+    "letters": true,
+    "numbers": true,
+    "domainSize": 10
+  },
+  "serviceName": {
+    "type": "fixedString",
+    "value": "service_"
+  },
+  "segments": {
+    "type": "randomList",
+    "size": 5,
+    "item": {
+      "endpointName": {
+        "type": "randomString",
+        "length": "10",
+        "prefix": "test_",
+        "letters": true,
+        "numbers": true,
+        "domainSize": 10
+      },
+      "error": {
+        "type": "randomInt",
+        "min": 1,
+        "max": 1
+      },
+      "tags": {
+        "type": "randomList",
+        "size": 5,
+        "item": {
+          "key": {
+            "type": "randomString",
+            "length": "10",
+            "prefix": "test_tag_",
+            "letters": true,
+            "numbers": true,
+            "domainSize": 5
+          },
+          "value": {
+            "type": "randomString",
+            "length": "10",
+            "prefix": "test_value_",
+            "letters": true,
+            "numbers": true,
+            "domainSize": 10
+          }
+        }
+      },
+      "spans": {
+        "type": "randomList",
+        "size": 5,
+        "item": {
+          "latency": {
+            "type": "randomInt",
+            "min": 100,
+            "max": 1000
+          },
+          "operationName": {
+            "type": "randomString",
+            "length": "10",
+            "prefix": "test_endpoint_",
+            "letters": true,
+            "numbers": true
+          },
+          "componentId": {
+            "type": "randomInt",
+            "min": "0",
+            "max": "4"
+          },
+          "error": {
+            "type": "randomBool",
+            "possibility": "0.2"
+          },
+          "tags": {
+            "type": "randomList",
+            "size": 5,
+            "item": {
+              "key": {
+                "type": "randomString",
+                "length": "10",
+                "prefix": "test_tag_key_",
+                "letters": true,
+                "numbers": true,
+                "domainSize": 10
+              },
+              "value": {
+                "type": "randomString",
+                "length": "10",
+                "prefix": "test_tag_val_",
+                "letters": true,
+                "numbers": true
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/test/stress/trace/trace_suite_test.go b/test/stress/trace/trace_suite_test.go
new file mode 100644
index 00000000..e9ae64b9
--- /dev/null
+++ b/test/stress/trace/trace_suite_test.go
@@ -0,0 +1,40 @@
+package trace_test
+
+import (
+	"flag"
+	"path"
+	"runtime"
+	"testing"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/apache/skywalking-banyandb/pkg/test/query"
+)
+
+func TestIntegrationLoad(t *testing.T) {
+	t.Skip("Skip the stress trace test")
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Stress Trace Suite")
+}
+
+var _ = Describe("Query", func() {
+	const timeout = 30 * time.Minute
+	var (
+		fs       *flag.FlagSet
+		basePath string
+	)
+
+	BeforeEach(func() {
+		fs = flag.NewFlagSet("", flag.PanicOnError)
+		fs.String("base-url", "http://localhost:12800/graphql", "")
+		fs.String("service-id", "c2VydmljZV8x.1", "")
+		_, basePath, _, _ = runtime.Caller(0)
+		basePath = path.Dir(basePath)
+	})
+
+	It("TraceListOrderByDuration", func() {
+		query.TraceListOrderByDuration(basePath, timeout, fs)
+	})
+})