From 4485e2f934727a8cb7362b8af1e4966a41890c16 Mon Sep 17 00:00:00 2001 From: Martin Yonatan Pasaribu <107392127+martinyonatann@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:21:09 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20metrics=20for=20elasticsearch?= =?UTF-8?q?,=20fiberv2=20and=20mongo=20(#278)=20(#290)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/inst-api/instrumenter/span_suppressor.go | 5 + .../elasticsearch/es_otel_instrumenter.go | 2 + .../fiberv2/fiberv2_otel_instrumenter.go | 9 +- pkg/rules/http/net_http_otel_instrumenter.go | 8 +- pkg/rules/mongo/mongo_otel_instrumenter.go | 1 + test/elasticsearch/v8.4.0/test_es_metrics.go | 68 ++++++++++++++ .../v8.5.0/test_es_typedclient_metrics.go | 67 +++++++++++++ test/elasticsearch_tests.go | 20 ++++ test/fiber_tests.go | 7 ++ test/fiberv2/v2.43.0/fiber_http_metrics.go | 94 +++++++++++++++++++ test/fiberv2/v2.43.0/go.mod | 2 +- test/mongo/v1.11.1/test_metrics_mongo.go | 55 +++++++++++ test/mongo_tests.go | 13 ++- test/test_main.go | 55 ++++++++--- test/verifier/verifier.go | 10 +- 15 files changed, 391 insertions(+), 25 deletions(-) create mode 100644 test/elasticsearch/v8.4.0/test_es_metrics.go create mode 100644 test/elasticsearch/v8.5.0/test_es_typedclient_metrics.go create mode 100644 test/fiberv2/v2.43.0/fiber_http_metrics.go create mode 100644 test/mongo/v1.11.1/test_metrics_mongo.go diff --git a/pkg/inst-api/instrumenter/span_suppressor.go b/pkg/inst-api/instrumenter/span_suppressor.go index 4098fb0b..7e9e6a4b 100644 --- a/pkg/inst-api/instrumenter/span_suppressor.go +++ b/pkg/inst-api/instrumenter/span_suppressor.go @@ -16,6 +16,7 @@ package instrumenter import ( "context" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils" "go.opentelemetry.io/otel/attribute" @@ -31,6 +32,8 @@ var scopeKey = map[string]attribute.Key{ utils.NET_HTTP_SERVER_SCOPE_NAME: utils.HTTP_SERVER_KEY, utils.HERTZ_HTTP_CLIENT_SCOPE_NAME: utils.HTTP_CLIENT_KEY, utils.HERTZ_HTTP_SERVER_SCOPE_NAME: utils.HTTP_SERVER_KEY, + utils.FIBER_V2_SERVER_SCOPE_NAME: utils.HTTP_SERVER_KEY, + utils.ELASTICSEARCH_SCOPE_NAME: utils.HTTP_CLIENT_KEY, // grpc utils.GRPC_CLIENT_SCOPE_NAME: utils.RPC_CLIENT_KEY, @@ -53,6 +56,8 @@ var kindKey = map[string]trace.SpanKind{ utils.NET_HTTP_SERVER_SCOPE_NAME: trace.SpanKindServer, utils.HERTZ_HTTP_CLIENT_SCOPE_NAME: trace.SpanKindClient, utils.HERTZ_HTTP_SERVER_SCOPE_NAME: trace.SpanKindServer, + utils.FIBER_V2_SERVER_SCOPE_NAME: trace.SpanKindServer, + utils.ELASTICSEARCH_SCOPE_NAME: trace.SpanKindClient, // grpc utils.GRPC_CLIENT_SCOPE_NAME: trace.SpanKindClient, diff --git a/pkg/rules/elasticsearch/es_otel_instrumenter.go b/pkg/rules/elasticsearch/es_otel_instrumenter.go index 2f82fc05..f408c098 100644 --- a/pkg/rules/elasticsearch/es_otel_instrumenter.go +++ b/pkg/rules/elasticsearch/es_otel_instrumenter.go @@ -16,6 +16,7 @@ package elasticsearch import ( "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/db" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/http" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/instrumenter" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/version" @@ -49,6 +50,7 @@ func BuildElasticSearchInstrumenter() instrumenter.Instrumenter[*esRequest, inte builder := instrumenter.Builder[*esRequest, any]{} getter := elasticSearchGetter{} return builder.Init().SetSpanNameExtractor(&db.DBSpanNameExtractor[*esRequest]{Getter: elasticSearchGetter{}}).SetSpanKindExtractor(&instrumenter.AlwaysClientExtractor[*esRequest]{}). + AddOperationListeners(http.HttpServerMetrics("elasticsearch.client")). SetInstrumentationScope(instrumentation.Scope{ Name: utils.ELASTICSEARCH_SCOPE_NAME, Version: version.Tag, diff --git a/pkg/rules/fiberv2/fiberv2_otel_instrumenter.go b/pkg/rules/fiberv2/fiberv2_otel_instrumenter.go index f2bdd3ad..d365c599 100644 --- a/pkg/rules/fiberv2/fiberv2_otel_instrumenter.go +++ b/pkg/rules/fiberv2/fiberv2_otel_instrumenter.go @@ -14,10 +14,11 @@ package fiberv2 import ( + "strconv" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/version" "go.opentelemetry.io/otel/sdk/instrumentation" - "strconv" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/http" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/net" @@ -73,11 +74,10 @@ func (n fiberv2ServerAttrsGetter) GetNetworkTransport(request *fiberv2Request, r return "tcp" } func (n fiberv2ServerAttrsGetter) GetNetworkProtocolName(request *fiberv2Request, response *fiberv2Response) string { - if request.isTls == false { + if !request.isTls { return "http" - } else { - return "https" } + return "https" } func (n fiberv2ServerAttrsGetter) GetNetworkProtocolVersion(request *fiberv2Request, response *fiberv2Response) string { return "" @@ -128,6 +128,7 @@ func BuildFiberV2ServerOtelInstrumenter() *instrumenter.PropagatingFromUpstreamI networkExtractor := net.NetworkAttrsExtractor[*fiberv2Request, *fiberv2Response, fiberv2ServerAttrsGetter]{Getter: serverGetter} urlExtractor := net.UrlAttrsExtractor[*fiberv2Request, *fiberv2Response, fiberv2ServerAttrsGetter]{Getter: serverGetter} return builder.Init().SetSpanStatusExtractor(http.HttpServerSpanStatusExtractor[*fiberv2Request, *fiberv2Response]{Getter: serverGetter}).SetSpanNameExtractor(&http.HttpServerSpanNameExtractor[*fiberv2Request, *fiberv2Response]{Getter: serverGetter}). + AddOperationListeners(http.HttpServerMetrics("fiberv2.server")). SetSpanKindExtractor(&instrumenter.AlwaysServerExtractor[*fiberv2Request]{}). SetInstrumentationScope(instrumentation.Scope{ Name: utils.FIBER_V2_SERVER_SCOPE_NAME, diff --git a/pkg/rules/http/net_http_otel_instrumenter.go b/pkg/rules/http/net_http_otel_instrumenter.go index 9ae48a42..f05d19d4 100644 --- a/pkg/rules/http/net_http_otel_instrumenter.go +++ b/pkg/rules/http/net_http_otel_instrumenter.go @@ -15,10 +15,11 @@ package http import ( + "strconv" + "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/version" "go.opentelemetry.io/otel/sdk/instrumentation" - "strconv" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/http" "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/net" @@ -163,11 +164,10 @@ func (n netHttpServerAttrsGetter) GetNetworkTransport(request *netHttpRequest, r } func (n netHttpServerAttrsGetter) GetNetworkProtocolName(request *netHttpRequest, response *netHttpResponse) string { - if request.isTls == false { + if !request.isTls { return "http" - } else { - return "https" } + return "https" } func (n netHttpServerAttrsGetter) GetNetworkProtocolVersion(request *netHttpRequest, response *netHttpResponse) string { diff --git a/pkg/rules/mongo/mongo_otel_instrumenter.go b/pkg/rules/mongo/mongo_otel_instrumenter.go index 8a5026de..fc9fc075 100644 --- a/pkg/rules/mongo/mongo_otel_instrumenter.go +++ b/pkg/rules/mongo/mongo_otel_instrumenter.go @@ -63,6 +63,7 @@ func (m *mongoSpanNameExtractor) Extract(request mongoRequest) string { func BuildMongoOtelInstrumenter() instrumenter.Instrumenter[mongoRequest, interface{}] { builder := instrumenter.Builder[mongoRequest, interface{}]{} return builder.Init().SetSpanNameExtractor(&mongoSpanNameExtractor{}). + AddOperationListeners(db.DbClientMetrics("nosql.mongo")). SetSpanKindExtractor(&mongoSpanKindExtractor{}). SetInstrumentationScope(instrumentation.Scope{ Name: utils.MONGO_SCOPE_NAME, diff --git a/test/elasticsearch/v8.4.0/test_es_metrics.go b/test/elasticsearch/v8.4.0/test_es_metrics.go new file mode 100644 index 00000000..3d7c68cf --- /dev/null +++ b/test/elasticsearch/v8.4.0/test_es_metrics.go @@ -0,0 +1,68 @@ +// Copyright (c) 2024 Alibaba Group Holding Ltd. +// +// Licensed 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 main + +import ( + "strconv" + "time" + + "log" + "os" + + "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier" + "github.com/elastic/go-elasticsearch/v8" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +var ( + client *elasticsearch.Client + url = "http://127.0.0.1:" + os.Getenv("OTEL_ES_PORT") +) + +func main() { + port, err := strconv.Atoi(os.Getenv("OTEL_ES_PORT")) + if err != nil { + panic(err) + } + + client, err = elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{url}, + Password: "123456", + Username: "elastic", + }) + if err != nil { + panic(err) + } + + // creating an index + _, err = client.Indices.Create("my_index") + if err != nil { + log.Printf("failed to create index %v\n", err) + } + + time.Sleep(3 * time.Second) + verifier.WaitAndAssertMetrics(map[string]func(metricdata.ResourceMetrics){ + "http.client.request.duration": func(mrs metricdata.ResourceMetrics) { + if len(mrs.ScopeMetrics) <= 0 { + panic("No http.client.request.duration metrics received!") + } + point := mrs.ScopeMetrics[0].Metrics[0].Data.(metricdata.Histogram[float64]) + if point.DataPoints[0].Count <= 0 { + panic("http.client.request.duration metrics count is not positive, actually " + strconv.Itoa(int(point.DataPoints[0].Count))) + } + verifier.VerifyHttpClientMetricsAttributes(point.DataPoints[0].Attributes.ToSlice(), "PUT", "", "", "http", "1.1", port, 200) + }, + }) +} diff --git a/test/elasticsearch/v8.5.0/test_es_typedclient_metrics.go b/test/elasticsearch/v8.5.0/test_es_typedclient_metrics.go new file mode 100644 index 00000000..a33ed3ed --- /dev/null +++ b/test/elasticsearch/v8.5.0/test_es_typedclient_metrics.go @@ -0,0 +1,67 @@ +// Copyright (c) 2024 Alibaba Group Holding Ltd. +// +// Licensed 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 main + +import ( + "context" + "log" + "os" + "strconv" + "time" + + "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier" + "github.com/elastic/go-elasticsearch/v8" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +var ( + client *elasticsearch.TypedClient + url = "http://127.0.0.1:" + os.Getenv("OTEL_ES_PORT") +) + +func main() { + var err error + client, err = elasticsearch.NewTypedClient(elasticsearch.Config{ + Addresses: []string{url}, + Password: "123456", + Username: "elastic", + }) + if err != nil { + panic(err) + } + ctx := context.Background() + // creating an index + _, err = client.Indices.Create("my_index").Do(ctx) + if err != nil { + log.Printf("failed to create index %v\n", err) + } + time.Sleep(3 * time.Second) + port, err := strconv.Atoi(os.Getenv("OTEL_ES_PORT")) + if err != nil { + panic(err) + } + verifier.WaitAndAssertMetrics(map[string]func(metricdata.ResourceMetrics){ + "http.client.request.duration": func(mrs metricdata.ResourceMetrics) { + if len(mrs.ScopeMetrics) <= 0 { + panic("No http.client.request.duration metrics received!") + } + point := mrs.ScopeMetrics[0].Metrics[0].Data.(metricdata.Histogram[float64]) + if point.DataPoints[0].Count <= 0 { + panic("http.client.request.duration metrics count is not positive, actually " + strconv.Itoa(int(point.DataPoints[0].Count))) + } + verifier.VerifyHttpClientMetricsAttributes(point.DataPoints[0].Attributes.ToSlice(), "PUT", "", "", "http", "1.1", port, 200) + }, + }) +} diff --git a/test/elasticsearch_tests.go b/test/elasticsearch_tests.go index 4659532c..ff66c1ec 100644 --- a/test/elasticsearch_tests.go +++ b/test/elasticsearch_tests.go @@ -33,7 +33,9 @@ const defaultTCPPort = "9300" func init() { TestCases = append(TestCases, NewGeneralTestCase("es-crud-test", es_v8_module_name, "v8.4.0", "", "1.18", "", TestESCrud), + NewGeneralTestCase("es-metrics-test", es_v8_module_name, "v8.4.0", "", "1.18", "", TestESMetrics), NewGeneralTestCase("es-typed-client-test", es_v8_module_name, "v8.4.0", "", "1.18", "", TestESTypedClient), + NewGeneralTestCase("es-typed-client-metrics-test", es_v8_module_name, "v8.5.0", "", "1.18", "", TestEsTypedClientMetrics), NewLatestDepthTestCase("es-crud-latestdepth-test", es_v8_dependency_name, es_v8_module_name, "v8.4.0", "v8.15.0", "1.18", "", TestESCrud), NewMuzzleTestCase("es-muzzle", es_v8_dependency_name, es_v8_module_name, "v8.4.0", "v8.4.0", "1.18", "", []string{"go", "build", "test_es_crud.go"}), NewMuzzleTestCase("es-muzzle", es_v8_dependency_name, es_v8_module_name, "v8.5.0", "", "1.18", "", []string{"go", "build", "test_es_typedclient.go"}), @@ -49,6 +51,15 @@ func TestESCrud(t *testing.T, env ...string) { RunApp(t, "test_es_crud", env...) } +func TestESMetrics(t *testing.T, env ...string) { + esC, esPort := initElasticSearchContainer() + defer testcontainers.CleanupContainer(t, esC) + UseApp("elasticsearch/v8.4.0") + RunGoBuild(t, "go", "build", "test_es_metrics.go") + env = append(env, "OTEL_ES_PORT="+esPort.Port()) + RunApp(t, "test_es_metrics", env...) +} + func TestESTypedClient(t *testing.T, env ...string) { esC, esPort := initElasticSearchContainer() defer testcontainers.CleanupContainer(t, esC) @@ -58,6 +69,15 @@ func TestESTypedClient(t *testing.T, env ...string) { RunApp(t, "test_es_typedclient", env...) } +func TestEsTypedClientMetrics(t *testing.T, env ...string) { + esC, esPort := initElasticSearchContainer() + defer testcontainers.CleanupContainer(t, esC) + UseApp("elasticsearch/v8.5.0") + RunGoBuild(t, "go", "build", "test_es_typedclient_metrics.go") + env = append(env, "OTEL_ES_PORT="+esPort.Port()) + RunApp(t, "test_es_typedclient_metrics", env...) +} + func initElasticSearchContainer() (testcontainers.Container, nat.Port) { ctx := context.Background() elasticsearchContainer, err := runElasticSearchContainer(ctx) diff --git a/test/fiber_tests.go b/test/fiber_tests.go index 53d97732..a9548fcb 100644 --- a/test/fiber_tests.go +++ b/test/fiber_tests.go @@ -23,6 +23,7 @@ func init() { TestCases = append(TestCases, NewGeneralTestCase("basic-fiberv2-test", fiberv2_module_name, "", "", "1.18", "", TestBasicFiberv2), NewGeneralTestCase("basic-fiberv2s-test", fiberv2_module_name, "", "", "1.18", "", TestBasicFiberv2Https), + NewGeneralTestCase("basic-fiberv2-metrics-test", fiberv2_module_name, "", "", "1.18", "", TestBasicFiberv2Metrics), NewLatestDepthTestCase("fiberv2-latestdepth", fiberv2_dependency_name, fiberv2_module_name, "v2.43.0", "", "1.18", "", TestBasicFiberv2), NewMuzzleTestCase("fiberv2-muzzle", fiberv2_dependency_name, fiberv2_module_name, "v2.43.0", "", "1.18", "", []string{"go", "build", "fiber_http.go"})) } @@ -38,3 +39,9 @@ func TestBasicFiberv2Https(t *testing.T, env ...string) { RunGoBuild(t, "go", "build", "fiber_https.go") RunApp(t, "fiber_https", env...) } + +func TestBasicFiberv2Metrics(t *testing.T, env ...string) { + UseApp("fiberv2/v2.43.0") + RunGoBuild(t, "go", "build", "fiber_http_metrics.go") + RunApp(t, "fiber_http_metrics", env...) +} diff --git a/test/fiberv2/v2.43.0/fiber_http_metrics.go b/test/fiberv2/v2.43.0/fiber_http_metrics.go new file mode 100644 index 00000000..56285358 --- /dev/null +++ b/test/fiberv2/v2.43.0/fiber_http_metrics.go @@ -0,0 +1,94 @@ +// Copyright (c) 2024 Alibaba Group Holding Ltd. +// +// Licensed 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 main + +import ( + "strconv" + "time" + + "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier" + fiber "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +var port int + +func requestHttpServer() { + client := &fasthttp.Client{} + + reqURL := "http://127.0.0.1:" + strconv.Itoa(port) + "/fiber" + + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer func() { + fasthttp.ReleaseRequest(req) + fasthttp.ReleaseResponse(resp) + }() + + req.SetRequestURI(reqURL) + req.Header.SetMethod(fasthttp.MethodGet) + + if err := client.Do(req, resp); err != nil { + panic(err) + } +} + +func setupFiberServer() { + app := fiber.New() + app.Get("/fiber", func(c *fiber.Ctx) error { + // Send a string response to the client + return c.Status(fiber.StatusOK).SendString("Hello, World 👋!") + }) + + var err error + port, err = verifier.GetFreePort() + if err != nil { + panic(err) + } + + if err := app.Listen("127.0.0.1:" + strconv.Itoa(port)); err != nil { + panic(err) + } +} + +func main() { + go setupFiberServer() + time.Sleep(2 * time.Second) + requestHttpServer() + time.Sleep(3 * time.Second) + verifier.WaitAndAssertMetrics(map[string]func(metricdata.ResourceMetrics){ + "http.server.request.duration": func(mrs metricdata.ResourceMetrics) { + if len(mrs.ScopeMetrics) <= 0 { + panic("No http.server.request.duration metrics received!") + } + point := mrs.ScopeMetrics[0].Metrics[0].Data.(metricdata.Histogram[float64]) + if point.DataPoints[0].Count <= 0 { + panic("http.server.request.duration metrics count is not positive, actually " + strconv.Itoa(int(point.DataPoints[0].Count))) + } + verifier.VerifyHttpServerMetricsAttributes(point.DataPoints[0].Attributes.ToSlice(), "GET", "/fiber", "", "http", "", "http", fiber.StatusOK) + }, + "http.client.request.duration": func(mrs metricdata.ResourceMetrics) { + if len(mrs.ScopeMetrics) <= 0 { + panic("No http.client.request.duration metrics received!") + } + point := mrs.ScopeMetrics[0].Metrics[0].Data.(metricdata.Histogram[float64]) + if point.DataPoints[0].Count <= 0 { + panic("http.client.request.duration metrics count is not positive, actually " + strconv.Itoa(int(point.DataPoints[0].Count))) + } + verifier.VerifyHttpClientMetricsAttributes(point.DataPoints[0].Attributes.ToSlice(), "GET", "127.0.0.1:"+strconv.Itoa(port), "", "http", "", port, fiber.StatusOK) + }, + }) +} diff --git a/test/fiberv2/v2.43.0/go.mod b/test/fiberv2/v2.43.0/go.mod index 81ef8edf..0c2ae2c5 100644 --- a/test/fiberv2/v2.43.0/go.mod +++ b/test/fiberv2/v2.43.0/go.mod @@ -9,6 +9,7 @@ require ( github.com/gofiber/fiber/v2 v2.43.0 github.com/valyala/fasthttp v1.45.0 go.opentelemetry.io/otel/sdk v1.31.0 + go.opentelemetry.io/otel/sdk/metric v1.30.0 ) @@ -31,7 +32,6 @@ require ( github.com/valyala/tcplisten v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) diff --git a/test/mongo/v1.11.1/test_metrics_mongo.go b/test/mongo/v1.11.1/test_metrics_mongo.go new file mode 100644 index 00000000..26ec2c4f --- /dev/null +++ b/test/mongo/v1.11.1/test_metrics_mongo.go @@ -0,0 +1,55 @@ +// Copyright (c) 2024 Alibaba Group Holding Ltd. +// +// Licensed 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 main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +// User model. +type User struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + Name string `bson:"name"` + Age int `bson:"age"` +} + +func main() { + client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(dsn)) + if err != nil { + panic(fmt.Sprintf("connect mongodb error %v \n", err)) + } + ctx := context.Background() + err = TestCreateCollection(ctx, client) + if err != nil { + log.Printf("failed to create collection: %v", err) + } + time.Sleep(3 * time.Second) + verifier.WaitAndAssertMetrics(map[string]func(metricdata.ResourceMetrics){ + // TODO : verifier + }) +} + +func TestCreateCollection(ctx context.Context, client *mongo.Client) error { + return client.Database(db).CreateCollection(ctx, "users") +} diff --git a/test/mongo_tests.go b/test/mongo_tests.go index 2178c2ea..5b17de18 100644 --- a/test/mongo_tests.go +++ b/test/mongo_tests.go @@ -31,6 +31,7 @@ func init() { TestCases = append(TestCases, NewGeneralTestCase("mongo-1.11.1-crud-test", mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", TestCrudMongo), NewGeneralTestCase("mongo-1.11.1-cursor-test", mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", TestCursor), NewGeneralTestCase("mongo-1.11.1-batch-test", mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", TestBatch), + NewGeneralTestCase("mongo-1.11.1-metrics-test", mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", TestMetrics), NewMuzzleTestCase("mongo-1.11.1-crud-muzzle", mongo_dependency_name, mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", []string{"go", "build", "test_crud_mongo.go", "dsn.go"}), NewMuzzleTestCase("mongo-1.11.1-cursor-muzzle", mongo_dependency_name, mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", []string{"go", "build", "test_batch.go", "dsn.go"}), NewMuzzleTestCase("mongo-1.11.1-batch-muzzle", mongo_dependency_name, mongo_module_name, "v1.11.1", "v1.15.1", "1.18", "", []string{"go", "build", "test_cursor.go", "dsn.go"}), @@ -39,7 +40,6 @@ func init() { func TestCrudMongo(t *testing.T, env ...string) { mongoC, mongoPort := initMongoContainer() - // defer clearMongoContainer(mongoC) defer testcontainers.CleanupContainer(t, mongoC) UseApp("mongo/v1.11.1") RunGoBuild(t, "go", "build", "test_crud_mongo.go", "dsn.go") @@ -49,7 +49,6 @@ func TestCrudMongo(t *testing.T, env ...string) { func TestCursor(t *testing.T, env ...string) { mongoC, mongoPort := initMongoContainer() - // defer clearMongoContainer(mongoC) defer testcontainers.CleanupContainer(t, mongoC) UseApp("mongo/v1.11.1") RunGoBuild(t, "go", "build", "test_cursor.go", "dsn.go") @@ -59,7 +58,6 @@ func TestCursor(t *testing.T, env ...string) { func TestBatch(t *testing.T, env ...string) { mongoC, mongoPort := initMongoContainer() - // defer clearMongoContainer(mongoC) defer testcontainers.CleanupContainer(t, mongoC) UseApp("mongo/v1.11.1") RunGoBuild(t, "go", "build", "test_batch.go", "dsn.go") @@ -67,6 +65,15 @@ func TestBatch(t *testing.T, env ...string) { RunApp(t, "test_batch", env...) } +func TestMetrics(t *testing.T, env ...string) { + mongoC, mongoPort := initMongoContainer() + defer testcontainers.CleanupContainer(t, mongoC) + UseApp("mongo/v1.11.1") + RunGoBuild(t, "go", "build", "test_metrics_mongo.go", "dsn.go") + env = append(env, "MONGO_PORT="+mongoPort.Port()) + RunApp(t, "test_metrics_mongo", env...) +} + func initMongoContainer() (testcontainers.Container, nat.Port) { req := testcontainers.ContainerRequest{ Image: "mongo:4.0", diff --git a/test/test_main.go b/test/test_main.go index e7ee5d5e..ed177a0a 100644 --- a/test/test_main.go +++ b/test/test_main.go @@ -23,23 +23,50 @@ import ( "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/version" ) +// TestCase represents the configuration and metadata for a test case, +// including version constraints, test logic, and specific test types. type TestCase struct { - TestName string - DependencyName string - ModuleName string - MinVersion *version.Version - MaxVersion *version.Version - MinGoVersion *version.Version - MaxGoVersion *version.Version - TestFunc func(t *testing.T, env ...string) - LatestDepthFunc func(t *testing.T, env ...string) - MuzzleClasses []string - IsMuzzleCheck bool + // TestName is the name of the test case. + TestName string + + // DependencyName is the name of the dependency being tested. + DependencyName string + + // ModuleName is the name of the module under test. + ModuleName string + + // MinVersion is the minimum supported version of the module. + MinVersion *version.Version + + // MaxVersion is the maximum supported version of the module. + MaxVersion *version.Version + + // MinGoVersion is the minimum Go version required for the test. + MinGoVersion *version.Version + + // MaxGoVersion is the maximum Go version supported for the test. + MaxGoVersion *version.Version + + // TestFunc is the function to run the general test logic. + TestFunc func(t *testing.T, env ...string) + + // LatestDepthFunc is the function to run the latest depth compatibility test. + LatestDepthFunc func(t *testing.T, env ...string) + + // MuzzleClasses contains the muzzle classes for the muzzle compatibility check. + MuzzleClasses []string + + // IsMuzzleCheck indicates if the test case is for muzzle compatibility. + IsMuzzleCheck bool + + // IsLatestDepthCheck indicates if the test case is for the latest depth check. IsLatestDepthCheck bool } var TestCases = make([]*TestCase, 0) +// NewGeneralTestCase creates a general test case to validate module compatibility +// with specified version constraints and optional test logic. func NewGeneralTestCase(testName, moduleName, minVersion, maxVersion, minGoVersion, maxGoVersion string, testFunc func(t *testing.T, env ...string)) *TestCase { minVer, err := version.NewVersion(minVersion) if minVersion != "" && err != nil { @@ -59,7 +86,7 @@ func NewGeneralTestCase(testName, moduleName, minVersion, maxVersion, minGoVersi } goVersion, _ := version.NewGoVersion(strings.ReplaceAll(runtime.Version(), "go", "")) if (minGoVer != nil && goVersion.LessThan(minGoVer)) || (maxGoVer != nil && goVersion.GreaterThan(maxGoVer)) { - log.Printf("This test does not suppport go " + goVersion.String()) + log.Printf("This test does not suppport go %s", goVersion.String()) return nil } return &TestCase{ @@ -75,6 +102,8 @@ func NewGeneralTestCase(testName, moduleName, minVersion, maxVersion, minGoVersi } } +// NewMuzzleTestCase creates a test case to validate the muzzle compatibility +// of a module with its dependencies. func NewMuzzleTestCase(testName, dependencyName, moduleName, minVersion, maxVersion, minGoVersion, maxGoVersion string, muzzleClasses []string) *TestCase { c := NewGeneralTestCase(testName, moduleName, minVersion, maxVersion, minGoVersion, maxGoVersion, nil) if c == nil { @@ -86,6 +115,8 @@ func NewMuzzleTestCase(testName, dependencyName, moduleName, minVersion, maxVers return c } +// NewLatestDepthTestCase creates a test case to validate the compatibility +// of a module's latest depth with its dependencies. func NewLatestDepthTestCase(testName, dependencyName, moduleName, minVersion, maxVersion, minGoVersion, maxGoVersion string, latestTestFunc func(t *testing.T, env ...string)) *TestCase { c := NewGeneralTestCase(testName, moduleName, minVersion, maxVersion, minGoVersion, maxGoVersion, nil) if c == nil { diff --git a/test/verifier/verifier.go b/test/verifier/verifier.go index b2b5935e..bea38a0f 100644 --- a/test/verifier/verifier.go +++ b/test/verifier/verifier.go @@ -15,9 +15,11 @@ package verifier import ( - "go.opentelemetry.io/otel/attribute" "strings" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) @@ -107,3 +109,9 @@ func verifyRpcAttributes(span tracetest.SpanStub, name, system, service, method Assert(GetAttribute(span.Attributes, "rpc.service").AsString() == service, "Except rpc service to be %s, got %s", method, GetAttribute(span.Attributes, "rpc.service").AsString()) Assert(GetAttribute(span.Attributes, "rpc.method").AsString() == method, "Except rpc method to be %s, got %s", method, GetAttribute(span.Attributes, "rpc.method").AsString()) } + +func VerifyDbMetricsAttributes(attrs []attribute.KeyValue, dbSystem, operationName, serverAddress string) { + Assert(GetAttribute(attrs, string(semconv.DBSystemKey)).AsString() == dbSystem, "Expected db.system to be %s, got %s", dbSystem, GetAttribute(attrs, string(semconv.DBSystemKey)).AsString()) + Assert(GetAttribute(attrs, string(semconv.DBOperationNameKey)).AsString() == operationName, "Expected db.operation.name to be %s, got %s", operationName, GetAttribute(attrs, string(semconv.DBOperationNameKey)).AsString()) + Assert(GetAttribute(attrs, string(semconv.ServerAddressKey)).AsString() == serverAddress, "Expected server.address to be %s, got %s", serverAddress, GetAttribute(attrs, string(semconv.ServerAddressKey)).AsString()) +}