You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by li...@apache.org on 2023/04/12 16:04:20 UTC
[skywalking-go] 01/01: Implement the Agent core
This is an automated email from the ASF dual-hosted git repository.
liuhan pushed a commit to branch agent-core
in repository https://gitbox.apache.org/repos/asf/skywalking-go.git
commit 4a5c397b67e5c708301b9dbf2729b44d29ff6bae
Author: Mrproliu <74...@qq.com>
AuthorDate: Thu Apr 13 00:04:05 2023 +0800
Implement the Agent core
---
.github/workflows/skywalking-go.yaml | 29 +-
.licenserc.yaml | 39 +++
Makefile | 76 +++++
api.go | 18 +
go.mod | 16 +
imports.go | 23 ++
plugins/core/context.go | 81 +++++
plugins/core/go.mod | 26 ++
plugins/core/intercepter.go | 38 +++
plugins/core/propagating.go | 246 ++++++++++++++
plugins/core/sampler.go | 156 +++++++++
plugins/core/sampler_test.go | 101 ++++++
plugins/core/span.go | 74 +++++
plugins/core/span_default.go | 133 ++++++++
plugins/core/span_noop.go | 83 +++++
plugins/core/span_tracing.go | 361 +++++++++++++++++++++
plugins/core/test_base.go | 61 ++++
plugins/core/tool.go | 44 +++
plugins/core/tracer.go | 42 +++
plugins/core/tracer_state.go | 41 +++
plugins/core/tracing/api.go | 252 ++++++++++++++
plugins/core/tracing/api_test.go | 330 +++++++++++++++++++
reporter/config_discovery.go | 81 +++++
reporter/doc.go | 21 ++
reporter/grpc.go | 306 +++++++++++++++++
reporter/grpc_opts.go | 22 ++
reporter/reporter.go | 94 ++++++
skylog/logger.go | 37 +++
tools/go-agent-enhance/cmd/helper.go | 40 +++
tools/go-agent-enhance/cmd/main.go | 73 +++++
tools/go-agent-enhance/go.mod | 11 +
tools/go-agent-enhance/go.sum | 21 ++
tools/go-agent-enhance/instrument/api/flags.go | 33 ++
.../go-agent-enhance/instrument/api/instrument.go | 31 ++
tools/go-agent-enhance/instrument/instrument.go | 188 +++++++++++
.../instrument/runtime/instrument.go | 136 ++++++++
tools/go-agent-enhance/tools/enhancement.go | 102 ++++++
tools/go-agent-enhance/tools/files.go | 36 ++
tools/go-agent-enhance/tools/flags.go | 102 ++++++
tools/go-agent-enhance/tools/logger.go | 36 ++
tools/go-agent-enhance/tools/templetes.go | 36 ++
41 files changed, 3675 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/skywalking-go.yaml b/.github/workflows/skywalking-go.yaml
index 134fe38..ac8bb26 100644
--- a/.github/workflows/skywalking-go.yaml
+++ b/.github/workflows/skywalking-go.yaml
@@ -20,11 +20,38 @@ on:
pull_request:
jobs:
+ build:
+ name: Build and Test
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ steps:
+ - name: Set up Go 1.18
+ uses: actions/setup-go@v2
+ with:
+ go-version: 1.18
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v2
+ with:
+ submodules: true
+ - name: Check License
+ uses: apache/skywalking-eyes@9bd5feb86b5817aa6072b008f9866a2c3bbc8587
+ - name: Get dependencies
+ run: make deps
+ - name: Test
+ run: make test
+ - name: Lint
+ run: make lint
+
required:
if: always()
name: Required
+ needs:
+ - build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Merge Requirement
- run: echo "success"
\ No newline at end of file
+ run: |
+ if [[ ${{ needs.build.result }} != 'success' ]]; then
+ exit -1
+ fi
\ No newline at end of file
diff --git a/.licenserc.yaml b/.licenserc.yaml
new file mode 100644
index 0000000..1a02b30
--- /dev/null
+++ b/.licenserc.yaml
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+header:
+ license:
+ spdx-id: Apache-2.0
+ copyright-owner: Apache Software Foundation
+
+ paths-ignore:
+ - '**/*.md'
+ - 'licenses'
+ - '**/go.mod'
+ - '**/go.sum'
+ - 'LICENSE'
+ - 'NOTICE'
+ - '.gitignore'
+ - 'dist/LICENSE.tpl'
+
+ comment: on-failure
+
+dependency:
+ files:
+ - go.mod
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..901d68f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,76 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+SH = sh
+GO = go
+GIT = git
+GO_PATH = $$($(GO) env GOPATH)
+GO_BUILD = $(GO) build
+GO_GET = $(GO) get
+GO_LINT = $(GO_PATH)/bin/golangci-lint
+
+GO_TEST = $(GO) test
+GO_TEST_LDFLAGS =
+
+REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/
+
+SHELL = /bin/bash
+
+deps:
+ $(GO_GET) -v -t -d ./...
+
+linter:
+ $(GO_LINT) version || curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GO_PATH)/bin v1.50.0
+
+.PHONY: test
+test:
+ echo "mode: atomic" > ${REPODIR}/coverage.txt;
+ @for dir in $$(find . -name go.mod -exec dirname {} \; ); do \
+ cd $$dir; \
+ go test -v -coverprofile=module_coverage.txt -covermode=atomic ./...; \
+ test_status=$$?; \
+ if [ -f module_coverage.txt ]; then \
+ tail -n +2 module_coverage.txt >> ${REPODIR}/coverage.txt; \
+ rm module_coverage.txt; \
+ fi; \
+ cd ${REPODIR}; \
+ if [ $$test_status -ne 0 ]; then \
+ echo "Error occurred during go test, exiting..."; \
+ exit $$test_status; \
+ fi; \
+ done
+
+.PHONY: lint
+lint: linter
+ @for dir in $$(find . -name go.mod -exec dirname {} \; ); do \
+ echo "Linting $$dir"; \
+ (cd $$dir && $(GO_LINT) run -v --timeout 5m ./...); \
+ if [ $$? -ne 0 ]; then \
+ exit 1; \
+ fi; \
+ done
+ $(GO_LINT) run -v --timeout 5m ./...
+
+.PHONY: check
+check:
+ $(GO) mod tidy > /dev/null
+ @if [ ! -z "`git status -s`" ]; then \
+ echo "Following files are not consistent with CI:"; \
+ git status -s; \
+ git diff; \
+ exit 1; \
+ fi
\ No newline at end of file
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..2881d22
--- /dev/null
+++ b/api.go
@@ -0,0 +1,18 @@
+// 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 main
diff --git a/go.mod b/go.mod
index 830b713..79c92f5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,19 @@
module github.com/apache/skywalking-go
go 1.18
+
+require (
+ github.com/google/uuid v1.3.0
+ github.com/pkg/errors v0.9.1
+ google.golang.org/grpc v1.54.0
+ skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a
+)
+
+require (
+ github.com/golang/protobuf v1.5.2 // indirect
+ golang.org/x/net v0.8.0 // indirect
+ golang.org/x/sys v0.6.0 // indirect
+ golang.org/x/text v0.8.0 // indirect
+ google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
+ google.golang.org/protobuf v1.29.0 // indirect
+)
diff --git a/imports.go b/imports.go
new file mode 100644
index 0000000..ae12a13
--- /dev/null
+++ b/imports.go
@@ -0,0 +1,23 @@
+// 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 main
+
+import (
+ _ "github.com/google/uuid"
+ _ "github.com/pkg/errors"
+)
diff --git a/plugins/core/context.go b/plugins/core/context.go
new file mode 100644
index 0000000..87b7f6e
--- /dev/null
+++ b/plugins/core/context.go
@@ -0,0 +1,81 @@
+// 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 core
+
+var (
+ GetGLS = func() interface{} { return nil }
+ SetGLS = func(interface{}) {}
+)
+
+type TracingContext struct {
+ ActiveSpan Span
+ Runtime *RuntimeContext
+}
+
+type RuntimeContext struct {
+ data map[string]interface{}
+}
+
+func NewTracingContext() *TracingContext {
+ return &TracingContext{
+ Runtime: &RuntimeContext{
+ data: make(map[string]interface{}),
+ },
+ }
+}
+
+func (r *RuntimeContext) clone() *RuntimeContext {
+ newData := make(map[string]interface{})
+ for k, v := range r.data {
+ newData[k] = v
+ }
+ return &RuntimeContext{
+ data: newData,
+ }
+}
+
+func GetTracingContext() *TracingContext {
+ gls := GetGLS()
+ if gls == nil {
+ return nil
+ }
+ return gls.(*TracingContext)
+}
+
+func (r *RuntimeContext) Get(key string) interface{} {
+ return r.data[key]
+}
+
+func (r *RuntimeContext) Set(key string, value interface{}) {
+ r.data[key] = value
+}
+
+func TaskTracingContextSnapshot(val interface{}) interface{} {
+ if val == nil {
+ return nil
+ }
+ context, ok := val.(*TracingContext)
+ if !ok {
+ return nil
+ }
+ snapshot := newSnapshotSpan(context.ActiveSpan)
+ return &TracingContext{
+ ActiveSpan: snapshot,
+ Runtime: context.Runtime.clone(),
+ }
+}
diff --git a/plugins/core/go.mod b/plugins/core/go.mod
new file mode 100644
index 0000000..2ade84b
--- /dev/null
+++ b/plugins/core/go.mod
@@ -0,0 +1,26 @@
+module github.com/apache/skywalking-go/plugins/core
+
+go 1.18
+
+require (
+ github.com/apache/skywalking-go v0.0.0-20230411034404-b9270e98036b
+ github.com/google/uuid v1.3.0
+ github.com/pkg/errors v0.9.1
+ github.com/stretchr/testify v1.8.2
+ skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ golang.org/x/net v0.8.0 // indirect
+ golang.org/x/sys v0.6.0 // indirect
+ golang.org/x/text v0.8.0 // indirect
+ google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
+ google.golang.org/grpc v1.54.0 // indirect
+ google.golang.org/protobuf v1.29.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace github.com/apache/skywalking-go => ../../
diff --git a/plugins/core/intercepter.go b/plugins/core/intercepter.go
new file mode 100644
index 0000000..8efa903
--- /dev/null
+++ b/plugins/core/intercepter.go
@@ -0,0 +1,38 @@
+// 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 core
+
+type Invocation struct {
+ CallerInstance interface{}
+ Args []interface{}
+
+ Continue bool
+ Return []interface{}
+
+ Context interface{}
+}
+
+type EnhancedInstance interface {
+ GetSkyWalkingDynamicField() interface{}
+ SetSkyWalkingDynamicField(interface{})
+}
+
+type Interceptor interface {
+ BeforeInvoke(invocation *Invocation) error
+ AfterInvoke(invocation *Invocation, result ...interface{}) error
+}
diff --git a/plugins/core/propagating.go b/plugins/core/propagating.go
new file mode 100644
index 0000000..64b3f30
--- /dev/null
+++ b/plugins/core/propagating.go
@@ -0,0 +1,246 @@
+// 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 core
+
+import (
+ "encoding/base64"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+const (
+ Header string = "sw8"
+ HeaderCorrelation string = "sw8-correlation"
+ headerLen int = 8
+ splitToken string = "-"
+ correlationSplitToken string = ","
+ correlationKeyValueSplitToken string = ":"
+)
+
+var (
+ errEmptyHeader = errors.New("empty header")
+ errInsufficientHeaderEntities = errors.New("insufficient header entities")
+)
+
+type SpanContext struct {
+ TraceID string `json:"trace_id"`
+ ParentSegmentID string `json:"parent_segment_id"`
+ ParentService string `json:"parent_service"`
+ ParentServiceInstance string `json:"parent_service_instance"`
+ ParentEndpoint string `json:"parent_endpoint"`
+ AddressUsedAtClient string `json:"address_used_at_client"`
+ ParentSpanID int32 `json:"parent_span_id"`
+ Sample int8 `json:"sample"`
+ Valid bool `json:"valid"`
+ CorrelationContext map[string]string `json:"correlation_context"`
+}
+
+func (s *SpanContext) GetTraceID() string {
+ return s.TraceID
+}
+
+func (s *SpanContext) GetParentSegmentID() string {
+ return s.ParentSegmentID
+}
+
+func (s *SpanContext) GetParentService() string {
+ return s.ParentService
+}
+
+func (s *SpanContext) GetParentServiceInstance() string {
+ return s.ParentServiceInstance
+}
+
+func (s *SpanContext) GetParentEndpoint() string {
+ return s.ParentEndpoint
+}
+
+func (s *SpanContext) GetAddressUsedAtClient() string {
+ return s.AddressUsedAtClient
+}
+
+func (s *SpanContext) GetParentSpanID() int32 {
+ return s.ParentSpanID
+}
+
+// Decode all SpanContext data from Extractor
+func (s *SpanContext) Decode(extractor func(headerKey string) (string, error)) error {
+ s.Valid = false
+ // sw8
+ err := s.decode(extractor, Header, s.DecodeSW8)
+ if err != nil {
+ return err
+ }
+
+ // correlation
+ err = s.decode(extractor, HeaderCorrelation, s.DecodeSW8Correlation)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Encode all SpanContext data to Injector
+func (s *SpanContext) Encode(injector func(headerKey, headerValue string) error) error {
+ // sw8
+ err := injector(Header, s.EncodeSW8())
+ if err != nil {
+ return err
+ }
+ // correlation
+ err = injector(HeaderCorrelation, s.EncodeSW8Correlation())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// DecodeSW6 converts string header to SpanContext
+func (s *SpanContext) DecodeSW8(header string) error {
+ if header == "" {
+ return errEmptyHeader
+ }
+ hh := strings.Split(header, splitToken)
+ if len(hh) < headerLen {
+ return errors.WithMessagef(errInsufficientHeaderEntities, "header string: %s", header)
+ }
+ sample, err := strconv.ParseInt(hh[0], 10, 8)
+ if err != nil {
+ return errors.Errorf("str to int8 error %s", hh[0])
+ }
+ s.Sample = int8(sample)
+ s.TraceID, err = decodeBase64(hh[1])
+ if err != nil {
+ return errors.Wrap(err, "trace id parse error")
+ }
+ s.ParentSegmentID, err = decodeBase64(hh[2])
+ if err != nil {
+ return errors.Wrap(err, "parent segment id parse error")
+ }
+ s.ParentSpanID, err = stringConvertInt32(hh[3])
+ if err != nil {
+ return errors.Wrap(err, "parent span id parse error")
+ }
+ s.ParentService, err = decodeBase64(hh[4])
+ if err != nil {
+ return errors.Wrap(err, "parent service parse error")
+ }
+ s.ParentServiceInstance, err = decodeBase64(hh[5])
+ if err != nil {
+ return errors.Wrap(err, "parent service instance parse error")
+ }
+ s.ParentEndpoint, err = decodeBase64(hh[6])
+ if err != nil {
+ return errors.Wrap(err, "parent endpoint parse error")
+ }
+ s.AddressUsedAtClient, err = decodeBase64(hh[7])
+ if err != nil {
+ return errors.Wrap(err, "network address parse error")
+ }
+ s.Valid = true
+ return nil
+}
+
+// EncodeSW6 converts SpanContext to string header
+func (s *SpanContext) EncodeSW8() string {
+ return strings.Join([]string{
+ fmt.Sprint(s.Sample),
+ encodeBase64(s.TraceID),
+ encodeBase64(s.ParentSegmentID),
+ fmt.Sprint(s.ParentSpanID),
+ encodeBase64(s.ParentService),
+ encodeBase64(s.ParentServiceInstance),
+ encodeBase64(s.ParentEndpoint),
+ encodeBase64(s.AddressUsedAtClient),
+ }, "-")
+}
+
+// DecodeSW8Correlation converts correlation string header to SpanContext
+func (s *SpanContext) DecodeSW8Correlation(header string) error {
+ s.CorrelationContext = make(map[string]string)
+ if header == "" {
+ return nil
+ }
+
+ hh := strings.Split(header, correlationSplitToken)
+ for inx := range hh {
+ keyValues := strings.Split(hh[inx], correlationKeyValueSplitToken)
+ if len(keyValues) != 2 {
+ continue
+ }
+ decodedKey, err := decodeBase64(keyValues[0])
+ if err != nil {
+ continue
+ }
+ decodedValue, err := decodeBase64(keyValues[1])
+ if err != nil {
+ continue
+ }
+
+ s.CorrelationContext[decodedKey] = decodedValue
+ }
+ return nil
+}
+
+// EncodeSW8Correlation converts correlation to string header
+func (s *SpanContext) EncodeSW8Correlation() string {
+ if len(s.CorrelationContext) == 0 {
+ return ""
+ }
+
+ content := make([]string, 0, len(s.CorrelationContext))
+ for k, v := range s.CorrelationContext {
+ content = append(content, fmt.Sprintf("%s%s%s", encodeBase64(k), correlationKeyValueSplitToken, encodeBase64(v)))
+ }
+ return strings.Join(content, correlationSplitToken)
+}
+
+func stringConvertInt32(str string) (int32, error) {
+ i, err := strconv.ParseInt(str, 0, 32)
+ return int32(i), err
+}
+
+func decodeBase64(str string) (string, error) {
+ ret, err := base64.StdEncoding.DecodeString(str)
+ if err != nil {
+ return "", err
+ }
+ return string(ret), nil
+}
+
+func encodeBase64(str string) string {
+ return base64.StdEncoding.EncodeToString([]byte(str))
+}
+
+func (s *SpanContext) decode(extractor func(headerKey string) (string, error), headerKey string, decoder func(header string) error) error {
+ val, err := extractor(headerKey)
+ if err != nil {
+ return err
+ }
+ if val == "" {
+ return nil
+ }
+ err = decoder(val)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/plugins/core/sampler.go b/plugins/core/sampler.go
new file mode 100644
index 0000000..539a05b
--- /dev/null
+++ b/plugins/core/sampler.go
@@ -0,0 +1,156 @@
+// 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 core
+
+import (
+ "fmt"
+ "math/rand"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/apache/skywalking-go/reporter"
+)
+
+type Sampler interface {
+ IsSampled(operation string) (sampled bool)
+}
+
+type ConstSampler struct {
+ decision bool
+}
+
+// NewConstSampler creates a ConstSampler.
+func NewConstSampler(sample bool) *ConstSampler {
+ s := &ConstSampler{
+ decision: sample,
+ }
+ return s
+}
+
+// IsSampled implements IsSampled() of Sampler.
+func (s *ConstSampler) IsSampled(operation string) bool {
+ return s.decision
+}
+
+// RandomSampler Use sync.Pool to implement concurrent-safe for randomizer.
+type RandomSampler struct {
+ samplingRate float64
+ threshold int
+ pool sync.Pool
+}
+
+// IsSampled implements IsSampled() of Sampler.
+func (s *RandomSampler) IsSampled(_ string) bool {
+ return s.threshold > s.generateRandomNumber()
+}
+
+func (s *RandomSampler) init() {
+ s.threshold = int(s.samplingRate * 100)
+ s.pool.New = s.newRand
+}
+
+func (s *RandomSampler) generateRandomNumber() int {
+ r := s.getRandomizer()
+ defer s.returnRandomizer(r)
+
+ return r.Intn(100)
+}
+
+func (s *RandomSampler) returnRandomizer(r *rand.Rand) {
+ s.pool.Put(r)
+}
+
+func (s *RandomSampler) getRandomizer() *rand.Rand {
+ var r *rand.Rand
+ generator := s.pool.Get()
+ if generator == nil {
+ generator = s.newRand()
+ }
+
+ r, ok := generator.(*rand.Rand)
+ if !ok {
+ r = s.newRand().(*rand.Rand) // it must be *rand.Rand
+ }
+
+ return r
+}
+
+func (s *RandomSampler) newRand() interface{} {
+ return rand.New(rand.NewSource(time.Now().UnixNano()))
+}
+
+func NewRandomSampler(samplingRate float64) *RandomSampler {
+ s := &RandomSampler{
+ samplingRate: samplingRate,
+ }
+ s.init()
+ return s
+}
+
+type DynamicSampler struct {
+ currentRate float64
+ defaultRate float64
+ sampler Sampler
+}
+
+// IsSampled implements IsSampled() of Sampler.
+func (s *DynamicSampler) IsSampled(operation string) bool {
+ return s.sampler.IsSampled(operation)
+}
+
+func (s *DynamicSampler) Key() string {
+ return "agent.sample_rate"
+}
+
+func (s *DynamicSampler) Notify(eventType reporter.AgentConfigEventType, newValue string) {
+ if eventType == reporter.DELETED {
+ newValue = fmt.Sprintf("%f", s.defaultRate)
+ }
+ samplingRate, err := strconv.ParseFloat(newValue, 64)
+ if err != nil {
+ return
+ }
+
+ // change Sampler
+ var sampler Sampler
+ if samplingRate <= 0 {
+ sampler = NewConstSampler(false)
+ } else if samplingRate >= 1.0 {
+ sampler = NewConstSampler(true)
+ } else {
+ sampler = NewRandomSampler(samplingRate)
+ }
+ s.sampler = sampler
+ s.currentRate = samplingRate
+}
+
+func (s *DynamicSampler) Value() string {
+ return fmt.Sprintf("%f", s.currentRate)
+}
+
+func NewDynamicSampler(samplingRate float64, tracer *Tracer) *DynamicSampler {
+ s := &DynamicSampler{
+ currentRate: samplingRate,
+ defaultRate: samplingRate,
+ }
+ s.Notify(reporter.MODIFY, fmt.Sprintf("%f", samplingRate))
+ // append watcher
+ tracer.cdsWatchers = append(tracer.cdsWatchers, s)
+ return s
+}
diff --git a/plugins/core/sampler_test.go b/plugins/core/sampler_test.go
new file mode 100644
index 0000000..cc93abd
--- /dev/null
+++ b/plugins/core/sampler_test.go
@@ -0,0 +1,101 @@
+// 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 core
+
+import (
+ "sync"
+ "testing"
+)
+
+var samplerOperationName = "op"
+
+func TestConstSampler_IsSampled(t *testing.T) {
+ sampler := NewConstSampler(true)
+ sampled := sampler.IsSampled(samplerOperationName)
+ if sampled != true {
+ t.Errorf("const sampler should be sampled")
+ }
+ samplerNegative := NewConstSampler(false)
+ sampledNegative := samplerNegative.IsSampled(samplerOperationName)
+ if sampledNegative != false {
+ t.Errorf("const sampler should not be sampled")
+ }
+}
+
+func TestRandomSampler_IsSampled(t *testing.T) {
+ randomSampler := NewRandomSampler(0.5)
+
+ t.Run("threshold need transform", func(t *testing.T) {
+ if randomSampler.threshold != 50 {
+ t.Errorf("threshold should be 50")
+ }
+ })
+
+ // just for test case
+ randomSampler.threshold = 100
+ sampled := randomSampler.IsSampled(samplerOperationName)
+ if sampled != true {
+ t.Errorf("random sampler should be sampled")
+ }
+
+ randomSampler.threshold = 0
+ sampled = randomSampler.IsSampled(samplerOperationName)
+ if sampled != false {
+ t.Errorf("random sampler should not be sampled")
+ }
+}
+
+func TestNewRandomSampler(t *testing.T) {
+ randomSampler := NewRandomSampler(100)
+ sampled := randomSampler.IsSampled(samplerOperationName)
+ if sampled != true {
+ t.Errorf("random sampler should be sampled")
+ }
+}
+
+func TestRandomSampler_getRandomizer(t *testing.T) {
+ t.Run("must not nil", func(t *testing.T) {
+ sampler := &RandomSampler{
+ pool: sync.Pool{},
+ }
+
+ if sampler.getRandomizer() == nil {
+ t.Errorf("randomizer should be nil")
+ }
+ })
+
+ t.Run("must not nil, if got not a *rand.Rand", func(t *testing.T) {
+ sampler := &RandomSampler{
+ pool: sync.Pool{},
+ }
+
+ sampler.pool.Put(&struct{}{})
+ if sampler.getRandomizer() == nil {
+ t.Errorf("randomizer should be nil")
+ }
+ })
+}
+
+func BenchmarkRandomPoolSampler_IsSampled(b *testing.B) {
+ sampler := NewRandomSampler(0.5)
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ sampler.IsSampled(samplerOperationName)
+ }
+ })
+}
diff --git a/plugins/core/span.go b/plugins/core/span.go
new file mode 100644
index 0000000..4d797d5
--- /dev/null
+++ b/plugins/core/span.go
@@ -0,0 +1,74 @@
+// 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 core
+
+import (
+ "time"
+
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+// SpanType is used to identify entry, exit and local
+type SpanType int32
+
+// Tag are supported by sky-walking engine.
+// As default, all Tags will be stored, but these ones have
+// particular meanings.
+type Tag string
+
+const (
+ // SpanTypeEntry is a entry span, eg http server
+ SpanTypeEntry SpanType = 0
+ // SpanTypeExit is a exit span, eg http client
+ SpanTypeExit SpanType = 1
+ // SpanTypeLocal is a local span, eg local method invoke
+ SpanTypeLocal SpanType = 2
+)
+
+const (
+ TagURL Tag = "url"
+ TagStatusCode Tag = "status_code"
+ TagHTTPMethod Tag = "http.method"
+ TagDBType Tag = "db.type"
+ TagDBInstance Tag = "db.instance"
+ TagDBStatement Tag = "db.statement"
+ TagDBSqlParameters Tag = "db.sql.parameters"
+ TagMQQueue Tag = "mq.queue"
+ TagMQBroker Tag = "mq.broker"
+ TagMQTopic Tag = "mq.topic"
+)
+
+// Span interface as commonv3 span specification
+type Span interface {
+ SetOperationName(string)
+ GetOperationName() string
+ SetPeer(string)
+ GetPeer() string
+ SetSpanLayer(agentv3.SpanLayer)
+ GetSpanLayer() agentv3.SpanLayer
+ SetComponent(int32)
+ GetComponent() int32
+ Tag(Tag, string)
+ Log(time.Time, ...string)
+ Error(time.Time, ...string)
+ End()
+ IsEntry() bool
+ IsExit() bool
+ IsValid() bool
+ ParentSpan() Span
+}
diff --git a/plugins/core/span_default.go b/plugins/core/span_default.go
new file mode 100644
index 0000000..ad9db39
--- /dev/null
+++ b/plugins/core/span_default.go
@@ -0,0 +1,133 @@
+// 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 core
+
+import (
+ "math"
+ "time"
+
+ "github.com/apache/skywalking-go/reporter"
+
+ commonv3 "skywalking.apache.org/repo/goapi/collect/common/v3"
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+type DefaultSpan struct {
+ Refs []reporter.SpanContext
+ tracer *Tracer
+ StartTime time.Time
+ EndTime time.Time
+ OperationName string
+ Peer string
+ Layer agentv3.SpanLayer
+ ComponentID int32
+ Tags []*commonv3.KeyStringValuePair
+ Logs []*agentv3.Log
+ IsError bool
+ SpanType SpanType
+ Parent Span
+}
+
+func NewDefaultSpan(tracer *Tracer, parent Span) *DefaultSpan {
+ return &DefaultSpan{
+ tracer: tracer,
+ StartTime: time.Now(),
+ SpanType: SpanTypeLocal,
+ Parent: parent,
+ }
+}
+
+// For Span
+func (ds *DefaultSpan) SetOperationName(name string) {
+ ds.OperationName = name
+}
+
+func (ds *DefaultSpan) GetOperationName() string {
+ return ds.OperationName
+}
+
+func (ds *DefaultSpan) SetPeer(peer string) {
+ ds.Peer = peer
+}
+
+func (ds *DefaultSpan) GetPeer() string {
+ return ds.Peer
+}
+
+func (ds *DefaultSpan) SetSpanLayer(layer agentv3.SpanLayer) {
+ ds.Layer = layer
+}
+
+func (ds *DefaultSpan) GetSpanLayer() agentv3.SpanLayer {
+ return ds.Layer
+}
+
+func (ds *DefaultSpan) SetComponent(componentID int32) {
+ ds.ComponentID = componentID
+}
+
+func (ds *DefaultSpan) GetComponent() int32 {
+ return ds.ComponentID
+}
+
+func (ds *DefaultSpan) Tag(key Tag, value string) {
+ ds.Tags = append(ds.Tags, &commonv3.KeyStringValuePair{Key: string(key), Value: value})
+}
+
+func (ds *DefaultSpan) Log(t time.Time, ll ...string) {
+ data := make([]*commonv3.KeyStringValuePair, 0, int32(math.Ceil(float64(len(ll))/2.0)))
+ var kvp *commonv3.KeyStringValuePair
+ for i, l := range ll {
+ if i%2 == 0 {
+ kvp = &commonv3.KeyStringValuePair{}
+ data = append(data, kvp)
+ kvp.Key = l
+ } else if kvp != nil {
+ kvp.Value = l
+ }
+ }
+ ds.Logs = append(ds.Logs, &agentv3.Log{Time: Millisecond(t), Data: data})
+}
+
+func (ds *DefaultSpan) Error(t time.Time, ll ...string) {
+ ds.IsError = true
+ ds.Log(t, ll...)
+}
+
+func (ds *DefaultSpan) End() {
+ ds.EndTime = time.Now()
+ if ctx := GetTracingContext(); ctx != nil {
+ ctx.ActiveSpan = ds.Parent
+ }
+}
+
+func (ds *DefaultSpan) IsEntry() bool {
+ return ds.SpanType == SpanTypeEntry
+}
+
+func (ds *DefaultSpan) IsExit() bool {
+ return ds.SpanType == SpanTypeExit
+}
+
+func (ds *DefaultSpan) IsValid() bool {
+ return ds.EndTime.IsZero()
+}
+
+func (ds *DefaultSpan) ParentSpan() Span {
+ return ds.Parent
+}
diff --git a/plugins/core/span_noop.go b/plugins/core/span_noop.go
new file mode 100644
index 0000000..9810228
--- /dev/null
+++ b/plugins/core/span_noop.go
@@ -0,0 +1,83 @@
+// 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 core
+
+import (
+ "time"
+
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+type NoopSpan struct {
+}
+
+func (*NoopSpan) SetOperationName(string) {
+}
+
+func (*NoopSpan) GetOperationName() string {
+ return ""
+}
+
+func (*NoopSpan) SetPeer(string) {
+}
+
+func (*NoopSpan) GetPeer() string {
+ return ""
+}
+
+func (*NoopSpan) SetSpanLayer(agentv3.SpanLayer) {
+}
+
+func (*NoopSpan) GetSpanLayer() agentv3.SpanLayer {
+ return 0
+}
+
+func (*NoopSpan) SetComponent(int32) {
+}
+
+func (*NoopSpan) GetComponent() int32 {
+ return 0
+}
+
+func (*NoopSpan) Tag(Tag, string) {
+}
+
+func (*NoopSpan) Log(time.Time, ...string) {
+}
+
+func (*NoopSpan) Error(time.Time, ...string) {
+}
+
+func (*NoopSpan) End() {
+}
+
+func (*NoopSpan) IsEntry() bool {
+ return false
+}
+
+func (*NoopSpan) IsExit() bool {
+ return false
+}
+
+func (*NoopSpan) IsValid() bool {
+ return true
+}
+
+func (n *NoopSpan) ParentSpan() Span {
+ return nil
+}
diff --git a/plugins/core/span_tracing.go b/plugins/core/span_tracing.go
new file mode 100644
index 0000000..f5152b8
--- /dev/null
+++ b/plugins/core/span_tracing.go
@@ -0,0 +1,361 @@
+// 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 core
+
+import (
+ "fmt"
+ "sync/atomic"
+ "time"
+
+ "github.com/apache/skywalking-go/reporter"
+
+ commonv3 "skywalking.apache.org/repo/goapi/collect/common/v3"
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+func NewSegmentSpan(defaultSpan *DefaultSpan, parentSpan SegmentSpan) (s SegmentSpan, err error) {
+ ssi := &SegmentSpanImpl{
+ DefaultSpan: *defaultSpan,
+ }
+ err = ssi.createSegmentContext(parentSpan)
+ if err != nil {
+ return nil, err
+ }
+ if parentSpan == nil || !parentSpan.segmentRegister() {
+ rs := newSegmentRoot(ssi)
+ err = rs.createRootSegmentContext(parentSpan)
+ if err != nil {
+ return nil, err
+ }
+ s = rs
+ } else {
+ s = ssi
+ }
+ return
+}
+
+// SegmentContext is the context in a segment
+type SegmentContext struct {
+ TraceID string
+ SegmentID string
+ SpanID int32
+ ParentSpanID int32
+ ParentSegmentID string
+ collect chan<- reporter.ReportedSpan
+ refNum *int32
+ spanIDGenerator *int32
+ FirstSpan Span `json:"-"`
+ CorrelationContext map[string]string
+}
+
+func (c *SegmentContext) GetTraceID() string {
+ return c.TraceID
+}
+
+func (c *SegmentContext) GetSegmentID() string {
+ return c.SegmentID
+}
+
+func (c *SegmentContext) GetSpanID() int32 {
+ return c.SpanID
+}
+
+func (c *SegmentContext) GetParentSpanID() int32 {
+ return c.ParentSpanID
+}
+
+func (c *SegmentContext) GetParentSegmentID() string {
+ return c.ParentSegmentID
+}
+
+type SegmentSpan interface {
+ Span
+ GetSegmentContext() SegmentContext
+ tracer() *Tracer
+ segmentRegister() bool
+}
+
+type SegmentSpanImpl struct {
+ DefaultSpan
+ SegmentContext
+}
+
+// For Span
+func (s *SegmentSpanImpl) End() {
+ if !s.IsValid() {
+ return
+ }
+ s.DefaultSpan.End()
+ go func() {
+ s.SegmentContext.collect <- s
+ }()
+}
+
+func (s *SegmentSpanImpl) GetDefaultSpan() DefaultSpan {
+ return s.DefaultSpan
+}
+
+// For Reported Span
+
+func (s *SegmentSpanImpl) Context() reporter.SegmentContext {
+ return &s.SegmentContext
+}
+
+func (s *SegmentSpanImpl) Refs() []reporter.SpanContext {
+ return s.DefaultSpan.Refs
+}
+
+func (s *SegmentSpanImpl) StartTime() int64 {
+ return Millisecond(s.DefaultSpan.StartTime)
+}
+
+func (s *SegmentSpanImpl) EndTime() int64 {
+ return Millisecond(s.DefaultSpan.EndTime)
+}
+
+func (s *SegmentSpanImpl) OperationName() string {
+ return s.DefaultSpan.OperationName
+}
+
+func (s *SegmentSpanImpl) Peer() string {
+ return s.DefaultSpan.Peer
+}
+
+func (s *SegmentSpanImpl) SpanType() agentv3.SpanType {
+ return agentv3.SpanType(s.DefaultSpan.SpanType)
+}
+
+func (s *SegmentSpanImpl) SpanLayer() agentv3.SpanLayer {
+ return s.DefaultSpan.Layer
+}
+
+func (s *SegmentSpanImpl) IsError() bool {
+ return s.DefaultSpan.IsError
+}
+
+func (s *SegmentSpanImpl) Tags() []*commonv3.KeyStringValuePair {
+ return s.DefaultSpan.Tags
+}
+
+func (s *SegmentSpanImpl) Logs() []*agentv3.Log {
+ return s.DefaultSpan.Logs
+}
+
+func (s *SegmentSpanImpl) ComponentID() int32 {
+ return s.DefaultSpan.ComponentID
+}
+
+func (s *SegmentSpanImpl) GetSegmentContext() SegmentContext {
+ return s.SegmentContext
+}
+
+func (s *SegmentSpanImpl) tracer() *Tracer {
+ return s.DefaultSpan.tracer
+}
+
+func (s *SegmentSpanImpl) segmentRegister() bool {
+ for {
+ o := atomic.LoadInt32(s.SegmentContext.refNum)
+ if o < 0 {
+ return false
+ }
+ if atomic.CompareAndSwapInt32(s.SegmentContext.refNum, o, o+1) {
+ return true
+ }
+ }
+}
+
+func (s *SegmentSpanImpl) createSegmentContext(parent SegmentSpan) (err error) {
+ if parent == nil {
+ s.SegmentContext = SegmentContext{}
+ if len(s.DefaultSpan.Refs) > 0 {
+ s.TraceID = s.DefaultSpan.Refs[0].GetTraceID()
+ s.CorrelationContext = s.DefaultSpan.Refs[0].(*SpanContext).CorrelationContext
+ } else {
+ s.TraceID, err = GenerateGlobalID()
+ if err != nil {
+ return err
+ }
+ s.CorrelationContext = make(map[string]string)
+ }
+ } else {
+ s.SegmentContext = parent.GetSegmentContext()
+ s.ParentSegmentID = s.GetSegmentID()
+ s.ParentSpanID = s.GetSpanID()
+ s.SpanID = atomic.AddInt32(s.SegmentContext.spanIDGenerator, 1)
+ s.CorrelationContext = parent.GetSegmentContext().CorrelationContext
+ }
+ if s.SegmentContext.FirstSpan == nil {
+ s.SegmentContext.FirstSpan = s
+ }
+ if s.CorrelationContext == nil {
+ s.CorrelationContext = make(map[string]string)
+ }
+ return
+}
+
+type RootSegmentSpan struct {
+ *SegmentSpanImpl
+ notify <-chan reporter.ReportedSpan
+ segment []reporter.ReportedSpan
+ doneCh chan int32
+}
+
+func (rs *RootSegmentSpan) End() {
+ if !rs.IsValid() {
+ return
+ }
+ rs.DefaultSpan.End()
+ go func() {
+ rs.doneCh <- atomic.SwapInt32(rs.SegmentContext.refNum, -1)
+ }()
+}
+
+func (rs *RootSegmentSpan) createRootSegmentContext(_ SegmentSpan) (err error) {
+ rs.SegmentID, err = GenerateGlobalID()
+ if err != nil {
+ return err
+ }
+ i := int32(0)
+ rs.spanIDGenerator = &i
+ rs.SpanID = i
+ rs.ParentSpanID = -1
+ return
+}
+
+type snapshotSpan struct {
+ DefaultSpan
+ SegmentContext
+}
+
+func (s *snapshotSpan) End() {
+ panic(fmt.Errorf("cannot End the span in other goroutine"))
+}
+
+func (s *snapshotSpan) SetOperationName(_ string) {
+ panic(fmt.Errorf("cannot update the operation name of span in other goroutine"))
+}
+
+func (s *snapshotSpan) SetSpanLayer(_ agentv3.SpanLayer) {
+ panic(fmt.Errorf("cannot update the layer of span in other goroutine"))
+}
+
+func (s *snapshotSpan) SetComponent(_ int32) {
+ panic(fmt.Errorf("cannot update the compoenent of span in other goroutine"))
+}
+
+func (s *snapshotSpan) Tag(key Tag, value string) {
+ panic(fmt.Errorf("cannot add tag of span in other goroutine"))
+}
+
+func (s *snapshotSpan) Log(_ time.Time, _ ...string) {
+ panic(fmt.Errorf("cannot add log of span in other goroutine"))
+}
+
+func (s *snapshotSpan) Error(_ time.Time, _ ...string) {
+ panic(fmt.Errorf("cannot add error of span in other goroutine"))
+}
+
+func (s *snapshotSpan) GetSegmentContext() SegmentContext {
+ return s.SegmentContext
+}
+
+func (s *snapshotSpan) tracer() *Tracer {
+ return s.DefaultSpan.tracer
+}
+
+func (s *snapshotSpan) segmentRegister() bool {
+ for {
+ o := atomic.LoadInt32(s.SegmentContext.refNum)
+ if o < 0 {
+ return false
+ }
+ if atomic.CompareAndSwapInt32(s.SegmentContext.refNum, o, o+1) {
+ return true
+ }
+ }
+}
+
+func newSegmentRoot(segmentSpan *SegmentSpanImpl) *RootSegmentSpan {
+ s := &RootSegmentSpan{
+ SegmentSpanImpl: segmentSpan,
+ }
+ var init int32
+ s.refNum = &init
+ ch := make(chan reporter.ReportedSpan)
+ s.collect = ch
+ s.notify = ch
+ s.segment = make([]reporter.ReportedSpan, 0, 10)
+ s.doneCh = make(chan int32)
+ go func() {
+ total := -1
+ defer close(ch)
+ defer close(s.doneCh)
+ for {
+ select {
+ case span := <-s.notify:
+ s.segment = append(s.segment, span)
+ case n := <-s.doneCh:
+ total = int(n)
+ }
+ if total == len(s.segment) {
+ break
+ }
+ }
+ s.tracer().Reporter.Send(append(s.segment, s))
+ }()
+ return s
+}
+
+func newSnapshotSpan(current Span) *snapshotSpan {
+ if current == nil {
+ return nil
+ }
+ segmentSpan, ok := current.(SegmentSpan)
+ if !ok {
+ return nil
+ }
+
+ segCtx := segmentSpan.GetSegmentContext()
+ copiedCorrelation := make(map[string]string)
+ for k, v := range segCtx.CorrelationContext {
+ copiedCorrelation[k] = v
+ }
+ s := &snapshotSpan{
+ DefaultSpan: DefaultSpan{
+ OperationName: segmentSpan.GetOperationName(),
+ Refs: nil,
+ tracer: segmentSpan.tracer(),
+ Peer: segmentSpan.GetPeer(),
+ },
+ SegmentContext: SegmentContext{
+ TraceID: segCtx.GetTraceID(),
+ SegmentID: segCtx.SegmentID,
+ SpanID: segCtx.SpanID,
+ ParentSpanID: segCtx.ParentSpanID,
+ ParentSegmentID: segCtx.ParentSegmentID,
+ collect: segCtx.collect,
+ refNum: segCtx.refNum,
+ spanIDGenerator: segCtx.spanIDGenerator,
+ FirstSpan: segCtx.FirstSpan,
+ CorrelationContext: copiedCorrelation,
+ },
+ }
+
+ return s
+}
diff --git a/plugins/core/test_base.go b/plugins/core/test_base.go
new file mode 100644
index 0000000..2825e6c
--- /dev/null
+++ b/plugins/core/test_base.go
@@ -0,0 +1,61 @@
+// 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 core
+
+import (
+ "github.com/apache/skywalking-go/reporter"
+)
+
+var tlsData interface{}
+
+func init() {
+ SetGLS = func(i interface{}) {
+ tlsData = i
+ }
+ GetGLS = func() interface{} {
+ return tlsData
+ }
+ ResetTracingContext()
+}
+
+func ResetTracingContext() {
+ SetGLS(nil)
+ SetGlobalTracer(&Tracer{initFlag: 1, Sampler: NewConstSampler(true), Reporter: &StoreReporter{}})
+}
+
+func GetReportedSpans() []reporter.ReportedSpan {
+ return GetGlobalTracer().Reporter.(*StoreReporter).Spans
+}
+
+type StoreReporter struct {
+ Spans []reporter.ReportedSpan
+}
+
+func NewStoreReporter() *StoreReporter {
+ return &StoreReporter{}
+}
+
+func (r *StoreReporter) Boot(entity reporter.Entity, cdsWatchers []reporter.AgentConfigChangeWatcher) {
+}
+
+func (r *StoreReporter) Send(spans []reporter.ReportedSpan) {
+ r.Spans = append(r.Spans, spans...)
+}
+
+func (r *StoreReporter) Close() {
+}
diff --git a/plugins/core/tool.go b/plugins/core/tool.go
new file mode 100644
index 0000000..1db067d
--- /dev/null
+++ b/plugins/core/tool.go
@@ -0,0 +1,44 @@
+// 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 core
+
+import (
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+// Millisecond converts time to unix millisecond
+func Millisecond(t time.Time) int64 {
+ return t.UnixNano() / int64(time.Millisecond)
+}
+
+// UUID generate UUID
+func UUID() (string, error) {
+ id, err := uuid.NewUUID()
+ if err != nil {
+ return "", err
+ }
+ return strings.ReplaceAll(id.String(), "-", ""), nil
+}
+
+// GenerateGlobalID generates global unique id
+func GenerateGlobalID() (globalID string, err error) {
+ return UUID()
+}
diff --git a/plugins/core/tracer.go b/plugins/core/tracer.go
new file mode 100644
index 0000000..9890dfc
--- /dev/null
+++ b/plugins/core/tracer.go
@@ -0,0 +1,42 @@
+// 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 core
+
+import (
+ "github.com/apache/skywalking-go/reporter"
+)
+
+type CorrelationConfig struct {
+ MaxKeyCount int
+ MaxValueSize int
+}
+
+type Tracer struct {
+ Service string
+ Instance string
+ Reporter reporter.Reporter
+ // 0 not init 1 init
+ initFlag int32
+ Sampler Sampler
+ // correlation *CorrelationConfig // temporarily disable, because haven't been implemented yet
+ cdsWatchers []reporter.AgentConfigChangeWatcher
+}
+
+func (t *Tracer) InitSuccess() bool {
+ return t.initFlag == 1
+}
diff --git a/plugins/core/tracer_state.go b/plugins/core/tracer_state.go
new file mode 100644
index 0000000..f2fd3ff
--- /dev/null
+++ b/plugins/core/tracer_state.go
@@ -0,0 +1,41 @@
+// 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 core
+
+import "sync/atomic"
+
+var (
+ globalTracer = &atomic.Value{}
+)
+
+// SetGlobalTracer registers `tracer` as the global Tracer.
+func SetGlobalTracer(tracer *Tracer) {
+ globalTracer.Store(tracer)
+}
+
+// GetGlobalTracer returns the registered global Tracer.
+// If none is registered then an instance of `nil` is returned.
+func GetGlobalTracer() *Tracer {
+ value := globalTracer.Load()
+ if value != nil {
+ if tracer, ok := value.(*Tracer); ok {
+ return tracer
+ }
+ }
+ return nil
+}
diff --git a/plugins/core/tracing/api.go b/plugins/core/tracing/api.go
new file mode 100644
index 0000000..952eaa6
--- /dev/null
+++ b/plugins/core/tracing/api.go
@@ -0,0 +1,252 @@
+// 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 tracing
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/apache/skywalking-go/plugins/core"
+ "github.com/apache/skywalking-go/reporter"
+
+ "github.com/pkg/errors"
+
+ commonv3 "skywalking.apache.org/repo/goapi/collect/common/v3"
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+var (
+ errParameter = fmt.Errorf("parameter are nil")
+)
+
+// Extractor is a tool specification which define how to
+// extract trace parent context from propagation context
+type Extractor func(headerKey string) (string, error)
+
+// Injector is a tool specification which define how to
+// inject trace context into propagation context
+type Injector func(headerKey, headerValue string) error
+
+func CreateEntrySpan(operationName string, extractor Extractor, opts ...SpanOption) (s core.Span, err error) {
+ if operationName == "" || extractor == nil {
+ return nil, errParameter
+ }
+ tracer, ctx, s, noop := createNoop()
+ if noop {
+ return s, nil
+ }
+ defer func() {
+ saveSpanToActiveIfNotError(ctx, s, err)
+ }()
+ var ref = &core.SpanContext{}
+ if err := ref.Decode(extractor); err != nil {
+ return nil, err
+ }
+ if !ref.Valid {
+ ref = nil
+ }
+
+ return createSpan0(tracer, s, append(opts, withRef(ref), withSpanType(core.SpanTypeEntry), withOperationName(operationName))...)
+}
+
+func CreateLocalSpan(operationName string, opts ...SpanOption) (s core.Span, err error) {
+ if operationName == "" {
+ return nil, errParameter
+ }
+ tracer, ctx, s, noop := createNoop()
+ if noop {
+ return s, nil
+ }
+ defer func() {
+ saveSpanToActiveIfNotError(ctx, s, err)
+ }()
+
+ return createSpan0(tracer, s, append(opts, withSpanType(core.SpanTypeLocal), withOperationName(operationName))...)
+}
+
+func CreateExitSpan(operationName, peer string, injector Injector, opts ...SpanOption) (s core.Span, err error) {
+ if operationName == "" || peer == "" || injector == nil {
+ return nil, errParameter
+ }
+ tracer, ctx, s, noop := createNoop()
+ if noop {
+ return s, nil
+ }
+ defer func() {
+ saveSpanToActiveIfNotError(ctx, s, err)
+ }()
+
+ span, err := createSpan0(tracer, s, append(opts, withSpanType(core.SpanTypeExit), withOperationName(operationName), withPeer(peer))...)
+ if err != nil {
+ return nil, err
+ }
+ spanContext := &core.SpanContext{}
+ reportedSpan, ok := span.(core.SegmentSpan)
+ if !ok {
+ return nil, errors.New("span type is wrong")
+ }
+
+ firstSpan := reportedSpan.GetSegmentContext().FirstSpan
+ spanContext.Sample = 1
+ spanContext.TraceID = reportedSpan.GetSegmentContext().TraceID
+ spanContext.ParentSegmentID = reportedSpan.GetSegmentContext().SegmentID
+ spanContext.ParentSpanID = reportedSpan.GetSegmentContext().SpanID
+ spanContext.ParentService = tracer.Service
+ spanContext.ParentServiceInstance = tracer.Instance
+ spanContext.ParentEndpoint = firstSpan.GetOperationName()
+ spanContext.AddressUsedAtClient = peer
+ spanContext.CorrelationContext = reportedSpan.GetSegmentContext().CorrelationContext
+
+ err = spanContext.Encode(injector)
+ if err != nil {
+ return nil, err
+ }
+ return span, nil
+}
+
+// SpanOption allows for functional options to adjust behavior
+// of a Span to be created by CreateLocalSpan
+type SpanOption func(s *core.DefaultSpan)
+
+func WithLayer(layer agentv3.SpanLayer) SpanOption {
+ return func(s *core.DefaultSpan) {
+ s.Layer = layer
+ }
+}
+
+func WithComponent(componentID int32) SpanOption {
+ return func(s *core.DefaultSpan) {
+ s.ComponentID = componentID
+ }
+}
+
+func WithTag(key, value string) SpanOption {
+ return func(s *core.DefaultSpan) {
+ s.Tags = append(s.Tags, &commonv3.KeyStringValuePair{Key: key, Value: value})
+ }
+}
+
+func GetRuntimeContextValue(key string) interface{} {
+ context := core.GetTracingContext()
+ if context == nil {
+ return nil
+ }
+ return context.Runtime.Get(key)
+}
+
+func SetRuntimeContextValue(key string, val interface{}) {
+ context := core.GetTracingContext()
+ if context == nil {
+ context = core.NewTracingContext()
+ core.SetGLS(context)
+ }
+ context.Runtime.Set(key, val)
+}
+
+func createNoop() (*core.Tracer, *core.TracingContext, core.Span, bool) {
+ tracer := core.GetGlobalTracer()
+ if tracer == nil || !tracer.InitSuccess() {
+ return nil, nil, &core.NoopSpan{}, true
+ }
+ ctx := getTracingContext()
+ if ctx != nil {
+ _, ok := ctx.ActiveSpan.(*core.NoopSpan)
+ return tracer, ctx, ctx.ActiveSpan, ok
+ }
+ return tracer, nil, nil, false
+}
+
+func createSpan0(tracer *core.Tracer, parent core.Span, opts ...SpanOption) (s core.Span, err error) {
+ ds := core.NewDefaultSpan(tracer, parent)
+ for _, opt := range opts {
+ opt(ds)
+ }
+ var parentSpan core.SegmentSpan
+ if parent != nil {
+ tmpSpan, ok := parent.(core.SegmentSpan)
+ if ok {
+ parentSpan = tmpSpan
+ }
+ }
+ isForceSample := len(ds.Refs) > 0
+ // Try to sample when it is not force sample
+ if parentSpan == nil && !isForceSample {
+ // Force sample
+ sampled := tracer.Sampler.IsSampled(ds.OperationName)
+ if !sampled {
+ // Filter by sample just return noop span
+ s = &core.NoopSpan{}
+ return s, nil
+ }
+ }
+ s, err = core.NewSegmentSpan(ds, parentSpan)
+ if err != nil {
+ return nil, err
+ }
+ return s, nil
+}
+
+func withSpanType(spanType core.SpanType) SpanOption {
+ return func(span *core.DefaultSpan) {
+ span.SpanType = spanType
+ }
+}
+
+func withOperationName(opName string) SpanOption {
+ return func(span *core.DefaultSpan) {
+ span.OperationName = opName
+ }
+}
+
+func withRef(sc reporter.SpanContext) SpanOption {
+ return func(span *core.DefaultSpan) {
+ if sc == nil {
+ return
+ }
+ v := reflect.ValueOf(sc)
+ if v.Interface() == reflect.Zero(v.Type()).Interface() {
+ return
+ }
+ span.Refs = append(span.Refs, sc)
+ }
+}
+
+func withPeer(peer string) SpanOption {
+ return func(span *core.DefaultSpan) {
+ span.Peer = peer
+ }
+}
+
+func getTracingContext() *core.TracingContext {
+ ctx := core.GetGLS()
+ if ctx == nil {
+ return nil
+ }
+ return ctx.(*core.TracingContext)
+}
+
+func saveSpanToActiveIfNotError(ctx *core.TracingContext, span core.Span, err error) {
+ if err != nil {
+ return
+ }
+ if ctx == nil {
+ ctx = core.NewTracingContext()
+ }
+ ctx.ActiveSpan = span
+ core.SetGLS(ctx)
+}
diff --git a/plugins/core/tracing/api_test.go b/plugins/core/tracing/api_test.go
new file mode 100644
index 0000000..068801c
--- /dev/null
+++ b/plugins/core/tracing/api_test.go
@@ -0,0 +1,330 @@
+// 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 tracing
+
+import (
+ "testing"
+ "time"
+
+ "github.com/apache/skywalking-go/plugins/core"
+
+ "github.com/stretchr/testify/assert"
+
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+const (
+ sample = 1
+ traceID = "1f2d4bf47bf711eab794acde48001122"
+ parentSegmentID = "1e7c204a7bf711eab858acde48001122"
+ parentSpanID = 0
+ parentService = "service"
+ parentServiceInstance = "instance"
+ parentEndpoint = "/foo/bar"
+ addressUsedAtClient = "foo.svc:8787"
+)
+
+var header string
+
+func init() {
+ scx := core.SpanContext{
+ Sample: sample,
+ TraceID: traceID,
+ ParentSegmentID: parentSegmentID,
+ ParentSpanID: parentSpanID,
+ ParentService: parentService,
+ ParentServiceInstance: parentServiceInstance,
+ ParentEndpoint: parentEndpoint,
+ AddressUsedAtClient: addressUsedAtClient,
+ }
+ header = scx.EncodeSW8()
+}
+
+type spanOperationTestCase struct {
+ operations []func(existingSpans []core.Span) (core.Span, error)
+ exceptedSpans []struct {
+ spanType core.SpanType
+ operationName string
+ parentSpanOpName string
+ peer string
+ }
+}
+
+func TestCreateSpanInSingleGoroutine(t *testing.T) {
+ defer core.ResetTracingContext()
+ validateSpanOperation(t, []spanOperationTestCase{
+ {
+ operations: []func(existingSpans []core.Span) (core.Span, error){
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateLocalSpan("/local1")
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateLocalSpan("/local1-1")
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateExitSpan("/local1-1-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []core.Span) (core.Span, error) { existingSpans[3].End(); return nil, nil },
+ func(existingSpans []core.Span) (core.Span, error) { existingSpans[2].End(); return nil, nil },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateExitSpan("/local1-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []core.Span) (core.Span, error) { existingSpans[4].End(); return nil, nil },
+ func(existingSpans []core.Span) (core.Span, error) { existingSpans[1].End(); return nil, nil },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateExitSpan("/entry-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []core.Span) (core.Span, error) { existingSpans[5].End(); return nil, nil },
+ func(existingSpans []core.Span) (core.Span, error) { existingSpans[0].End(); return nil, nil },
+ },
+ exceptedSpans: []struct {
+ spanType core.SpanType
+ operationName string
+ parentSpanOpName string
+ peer string
+ }{
+ {core.SpanTypeEntry, "/entry", "", ""},
+ {core.SpanTypeLocal, "/local1", "/entry", ""},
+ {core.SpanTypeLocal, "/local1-1", "/local1", ""},
+ {core.SpanTypeExit, "/local1-1-exit", "/local1-1", "localhost:8080"},
+ {core.SpanTypeExit, "/local1-exit", "/local1", "localhost:8080"},
+ {core.SpanTypeExit, "/entry-exit", "/entry", "localhost:8080"},
+ },
+ },
+ })
+}
+
+func TestCreateSpanInDifferenceGoroutine(t *testing.T) {
+ defer core.ResetTracingContext()
+ validateSpanOperation(t, []spanOperationTestCase{
+ {
+ operations: []func(existingSpans []core.Span) (core.Span, error){
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ },
+ func(existingSpans []core.Span) (core.Span, error) { // new goroutine
+ core.SetGLS(core.TaskTracingContextSnapshot(core.GetGLS()))
+ return nil, nil
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateLocalSpan("/local")
+ },
+ func(existingSpans []core.Span) (core.Span, error) { // new goroutine
+ core.SetGLS(core.TaskTracingContextSnapshot(core.GetGLS()))
+ return nil, nil
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ return CreateExitSpan("/local-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ existingSpans[2].End()
+ return nil, nil
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ existingSpans[1].End()
+ return nil, nil
+ },
+ func(existingSpans []core.Span) (core.Span, error) {
+ existingSpans[0].End()
+ return nil, nil
+ },
+ },
+ exceptedSpans: []struct {
+ spanType core.SpanType
+ operationName string
+ parentSpanOpName string
+ peer string
+ }{
+ {core.SpanTypeEntry, "/entry", "", ""},
+ {core.SpanTypeLocal, "/local", "/entry", ""},
+ {core.SpanTypeExit, "/local-exit", "/local", "localhost:8080"},
+ },
+ },
+ })
+}
+
+func TestSpanContextWriting(t *testing.T) {
+ defer core.ResetTracingContext()
+ s, err := CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ assert.NoError(t, err)
+ s.End()
+ s, err = CreateExitSpan("/exit", "localhost:8080", func(key, value string) error {
+ ctx := core.SpanContext{}
+ if key == core.Header {
+ assert.NoError(t, ctx.DecodeSW8(value))
+ }
+ if key == core.HeaderCorrelation {
+ assert.NoError(t, ctx.DecodeSW8Correlation(value))
+ }
+ return nil
+ })
+ assert.NoError(t, err)
+ s.End()
+}
+
+func TestSpanContextReading(t *testing.T) {
+ defer core.ResetTracingContext()
+ s, err := CreateEntrySpan("/entry", func(key string) (string, error) {
+ if key == core.Header {
+ return header, nil
+ }
+ return "", nil
+ })
+ assert.NoError(t, err)
+ s.End()
+ time.Sleep(time.Millisecond * 50)
+ spans := core.GetReportedSpans()
+ assert.Equal(t, 1, len(spans), "span count not correct")
+ tracingContext := spans[0].Context()
+ assert.Equal(t, traceID, tracingContext.GetTraceID(), "trace id not correct")
+ assert.Equal(t, 1, len(spans[0].Refs()), "refs not correct")
+ refCtx := spans[0].Refs()[0]
+ assert.Equal(t, traceID, refCtx.GetTraceID(), "ref trace id not correct")
+ assert.Equal(t, parentSegmentID, refCtx.GetParentSegmentID(), "ref segment id not correct")
+ assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
+ assert.Equal(t, int32(parentSpanID), refCtx.GetParentSpanID(), "ref span id not correct")
+ assert.Equal(t, parentService, refCtx.GetParentService(), "ref service not correct")
+ assert.Equal(t, parentServiceInstance, refCtx.GetParentServiceInstance(), "ref service instance not correct")
+ assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
+}
+
+func TestSpanOperation(t *testing.T) {
+ defer core.ResetTracingContext()
+ spanCreations := []func(op SpanOption) (core.Span, error){
+ func(op SpanOption) (core.Span, error) {
+ return CreateEntrySpan("test", func(headerKey string) (string, error) {
+ return "", nil
+ }, op)
+ },
+ func(op SpanOption) (core.Span, error) {
+ return CreateLocalSpan("test", op)
+ },
+ func(op SpanOption) (core.Span, error) {
+ return CreateExitSpan("test", "localhost:8080", func(headerKey, headerValue string) error {
+ return nil
+ }, op)
+ },
+ }
+
+ spanOptions := []struct {
+ op SpanOption
+ validate func(s *core.RootSegmentSpan) bool
+ }{
+ {WithLayer(agentv3.SpanLayer_Http), func(s *core.RootSegmentSpan) bool {
+ return s.DefaultSpan.Layer == agentv3.SpanLayer_Http
+ }},
+ {WithComponent(1), func(s *core.RootSegmentSpan) bool {
+ return s.DefaultSpan.ComponentID == 1
+ }},
+ {WithTag("test", "test1"), func(s *core.RootSegmentSpan) bool {
+ for _, k := range s.DefaultSpan.Tags {
+ if k.Key == "test" {
+ return k.Value == "test1"
+ }
+ }
+ return false
+ }},
+ }
+
+ for createInx, spanCreate := range spanCreations {
+ for _, op := range spanOptions {
+ create, err := spanCreate(op.op)
+ if err != nil {
+ assert.Nil(t, err, "create span error")
+ }
+ span := create.(*core.RootSegmentSpan)
+ assert.Truef(t, op.validate(span), "span validation failed, create index: %d, option index: %d", createInx, op)
+ create.End()
+ }
+ }
+}
+
+func TestRuntimeContext(t *testing.T) {
+ defer core.ResetTracingContext()
+ assert.Nilf(t, GetRuntimeContextValue("test"), "runtime context data should be nil")
+ SetRuntimeContextValue("test", "test")
+ assert.Equal(t, "test", GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
+ // switch to the new goroutine
+ oldGLS := core.GetGLS()
+ core.SetGLS(core.TaskTracingContextSnapshot(oldGLS))
+ assert.Equal(t, "test", GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
+ assert.Nilf(t, GetRuntimeContextValue("test1"), "runtime context data should be nil")
+ SetRuntimeContextValue("test1", "test1")
+ assert.Equal(t, "test1", GetRuntimeContextValue("test1"), "runtime context data should be \"test1\"")
+ // switch back to the old goroutine
+ core.SetGLS(oldGLS)
+ assert.Nilf(t, GetRuntimeContextValue("test1"), "runtime context data should be nil")
+}
+
+func validateSpanOperation(t *testing.T, cases []spanOperationTestCase) {
+ for _, tt := range cases {
+ spans := make([]core.Span, 0)
+ for i, op := range tt.operations {
+ span, err := op(spans)
+ assert.Nilf(t, err, "create span error, operation index: %d", i)
+ time.Sleep(time.Millisecond * 50)
+ if span != nil {
+ spans = append(spans, span)
+ }
+ }
+
+ time.Sleep(time.Millisecond * 100)
+ assert.Equal(t, len(tt.exceptedSpans), len(core.GetReportedSpans()), "span count not equal")
+ for i, exceptedSpan := range tt.exceptedSpans {
+ var span core.DefaultSpan
+ if i == 0 {
+ tmp, ok := core.GetReportedSpans()[len(core.GetReportedSpans())-1-i].(*core.RootSegmentSpan)
+ assert.True(t, ok, "first span is not root segment span")
+ span = tmp.DefaultSpan
+ } else {
+ found := false
+ for _, s := range core.GetReportedSpans() {
+ if s.OperationName() != exceptedSpan.operationName {
+ continue
+ }
+ tmp, ok := s.(*core.SegmentSpanImpl)
+ assert.Truef(t, ok, "span is not segment span, span index: %d", i)
+ span = tmp.DefaultSpan
+ found = true
+ break
+ }
+ assert.Truef(t, found, "span not found, span index: %d, name: %s", i, exceptedSpan.operationName)
+ }
+ assert.Equalf(t, exceptedSpan.spanType, span.SpanType, "span type not equal, span index: %d", i)
+ assert.Equalf(t, exceptedSpan.operationName, span.OperationName, "operation name not equal, span index: %d", i)
+ if exceptedSpan.parentSpanOpName != "" {
+ assert.Equalf(t, exceptedSpan.parentSpanOpName, span.Parent.GetOperationName(), "parent operation name not equal, span index: %d", i)
+ } else {
+ assert.Nilf(t, span.Parent, "parent span not nil, span index: %d", i)
+ }
+ if exceptedSpan.peer != "" {
+ assert.Equalf(t, exceptedSpan.peer, span.Peer, "span peer not equal, span index: %d", i)
+ } else {
+ assert.Truef(t, span.Peer == "", "span peer not empty, span index: %d", i)
+ }
+
+ assert.Greaterf(t, core.Millisecond(span.StartTime), int64(0), "start time not greater than 0, span index: %d", i)
+ assert.Greaterf(t, core.Millisecond(span.EndTime), core.Millisecond(span.StartTime), "end time not greater than 0, span indeX: %d", i)
+ }
+
+ core.GetGlobalTracer().Reporter = core.NewStoreReporter()
+ }
+}
diff --git a/reporter/config_discovery.go b/reporter/config_discovery.go
new file mode 100644
index 0000000..3c1e37a
--- /dev/null
+++ b/reporter/config_discovery.go
@@ -0,0 +1,81 @@
+// 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 reporter
+
+import common "skywalking.apache.org/repo/goapi/collect/common/v3"
+
+type AgentConfigEventType int32
+
+const (
+ MODIFY AgentConfigEventType = iota
+ DELETED
+)
+
+func NewConfigDiscoveryService() *ConfigDiscoveryService {
+ return &ConfigDiscoveryService{}
+}
+
+type ConfigDiscoveryService struct {
+ UUID string
+ watchers map[string]AgentConfigChangeWatcher
+}
+
+func (s *ConfigDiscoveryService) BindWatchers(watchers []AgentConfigChangeWatcher) {
+ // bind watchers
+ s.watchers = make(map[string]AgentConfigChangeWatcher)
+ for _, watcher := range watchers {
+ s.watchers[watcher.Key()] = watcher
+ }
+}
+
+func (s *ConfigDiscoveryService) HandleCommand(command *common.Command) {
+ var uuid string
+ var newConfigs = make(map[string]*common.KeyStringValuePair)
+ for _, pair := range command.GetArgs() {
+ if pair.Key == "SerialNumber" {
+ } else if pair.Key == "UUID" {
+ uuid = pair.Value
+ } else {
+ newConfigs[pair.Key] = pair
+ }
+ }
+
+ // check same uuid
+ if s.UUID == uuid {
+ return
+ }
+
+ // notify to all watchers
+ for key, watcher := range s.watchers {
+ pair := newConfigs[key]
+ if pair == nil || pair.Value == "" {
+ watcher.Notify(DELETED, "")
+ } else if pair.Value != watcher.Value() {
+ watcher.Notify(MODIFY, pair.Value)
+ }
+ }
+
+ // update uuid
+ s.UUID = uuid
+}
+
+type AgentConfigChangeWatcher interface {
+ Key() string
+ Notify(eventType AgentConfigEventType, newValue string)
+ Value() string
+}
diff --git a/reporter/doc.go b/reporter/doc.go
new file mode 100644
index 0000000..4eeeffe
--- /dev/null
+++ b/reporter/doc.go
@@ -0,0 +1,21 @@
+// 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 reporter holds reporters contain official reporter implementations.
+*/
+package reporter
diff --git a/reporter/grpc.go b/reporter/grpc.go
new file mode 100644
index 0000000..123e4bf
--- /dev/null
+++ b/reporter/grpc.go
@@ -0,0 +1,306 @@
+// 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 reporter
+
+import (
+ "context"
+ "io"
+ "time"
+
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/apache/skywalking-go/skylog"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/connectivity"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/metadata"
+
+ configuration "skywalking.apache.org/repo/goapi/collect/agent/configuration/v3"
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+ managementv3 "skywalking.apache.org/repo/goapi/collect/management/v3"
+)
+
+const (
+ maxSendQueueSize int32 = 30000
+ defaultCheckInterval = 20 * time.Second
+ defaultCDSInterval = 20 * time.Second
+)
+
+// NewGRPCReporter create a new reporter to send data to gRPC oap server. Only one backend address is allowed.
+func NewGRPCReporter(log skylog.Logger, serverAddr string, opts ...GRPCReporterOption) (Reporter, error) {
+ r := &gRPCReporter{
+ logger: log,
+ sendCh: make(chan *agentv3.SegmentObject, maxSendQueueSize),
+ checkInterval: defaultCheckInterval,
+ cdsInterval: defaultCDSInterval, // cds default on
+ }
+ for _, o := range opts {
+ o(r)
+ }
+
+ var credsDialOption grpc.DialOption
+ if r.creds != nil {
+ // use tls
+ credsDialOption = grpc.WithTransportCredentials(r.creds)
+ } else {
+ credsDialOption = grpc.WithTransportCredentials(insecure.NewCredentials())
+ }
+
+ conn, err := grpc.Dial(serverAddr, credsDialOption)
+ if err != nil {
+ return nil, err
+ }
+ r.conn = conn
+ r.traceClient = agentv3.NewTraceSegmentReportServiceClient(r.conn)
+ r.managementClient = managementv3.NewManagementServiceClient(r.conn)
+ if r.cdsInterval > 0 {
+ r.cdsClient = configuration.NewConfigurationDiscoveryServiceClient(r.conn)
+ r.cdsService = NewConfigDiscoveryService()
+ }
+ return r, nil
+}
+
+type gRPCReporter struct {
+ entity Entity
+ logger skylog.Logger
+ sendCh chan *agentv3.SegmentObject
+ conn *grpc.ClientConn
+ traceClient agentv3.TraceSegmentReportServiceClient
+ managementClient managementv3.ManagementServiceClient
+ checkInterval time.Duration
+ cdsInterval time.Duration
+ cdsService *ConfigDiscoveryService
+ cdsClient configuration.ConfigurationDiscoveryServiceClient
+
+ md metadata.MD
+ creds credentials.TransportCredentials
+
+ // bootFlag is set if Boot be executed
+ bootFlag bool
+}
+
+func (r *gRPCReporter) Boot(entity Entity, cdsWatchers []AgentConfigChangeWatcher) {
+ r.entity = entity
+ r.initSendPipeline()
+ r.check()
+ r.initCDS(cdsWatchers)
+ r.bootFlag = true
+}
+
+func (r *gRPCReporter) Send(spans []ReportedSpan) {
+ spanSize := len(spans)
+ if spanSize < 1 {
+ return
+ }
+ rootSpan := spans[spanSize-1]
+ rootCtx := rootSpan.Context()
+ segmentObject := &agentv3.SegmentObject{
+ TraceId: rootCtx.GetTraceID(),
+ TraceSegmentId: rootCtx.GetSegmentID(),
+ Spans: make([]*agentv3.SpanObject, spanSize),
+ Service: r.entity.ServiceInstanceName,
+ ServiceInstance: r.entity.ServiceInstanceName,
+ }
+ for i, s := range spans {
+ spanCtx := s.Context()
+ segmentObject.Spans[i] = &agentv3.SpanObject{
+ SpanId: spanCtx.GetSpanID(),
+ ParentSpanId: spanCtx.GetParentSpanID(),
+ StartTime: s.StartTime(),
+ EndTime: s.EndTime(),
+ OperationName: s.OperationName(),
+ Peer: s.Peer(),
+ SpanType: s.SpanType(),
+ SpanLayer: s.SpanLayer(),
+ ComponentId: s.ComponentID(),
+ IsError: s.IsError(),
+ Tags: s.Tags(),
+ Logs: s.Logs(),
+ }
+ srr := make([]*agentv3.SegmentReference, 0)
+ if i == (spanSize-1) && spanCtx.GetParentSpanID() > -1 {
+ srr = append(srr, &agentv3.SegmentReference{
+ RefType: agentv3.RefType_CrossThread,
+ TraceId: spanCtx.GetTraceID(),
+ ParentTraceSegmentId: spanCtx.GetParentSegmentID(),
+ ParentSpanId: spanCtx.GetParentSpanID(),
+ ParentService: r.entity.ServiceName,
+ ParentServiceInstance: r.entity.ServiceInstanceName,
+ })
+ }
+ if len(s.Refs()) > 0 {
+ for _, tc := range s.Refs() {
+ srr = append(srr, &agentv3.SegmentReference{
+ RefType: agentv3.RefType_CrossProcess,
+ TraceId: spanCtx.GetTraceID(),
+ ParentTraceSegmentId: tc.GetParentSegmentID(),
+ ParentSpanId: tc.GetParentSpanID(),
+ ParentService: tc.GetParentService(),
+ ParentServiceInstance: tc.GetParentServiceInstance(),
+ ParentEndpoint: tc.GetParentEndpoint(),
+ NetworkAddressUsedAtPeer: tc.GetAddressUsedAtClient(),
+ })
+ }
+ }
+ segmentObject.Spans[i].Refs = srr
+ }
+ defer func() {
+ // recover the panic caused by close sendCh
+ if err := recover(); err != nil {
+ r.logger.Errorf("reporter segment err %v", err)
+ }
+ }()
+ select {
+ case r.sendCh <- segmentObject:
+ default:
+ r.logger.Errorf("reach max send buffer")
+ }
+}
+
+func (r *gRPCReporter) Close() {
+ if r.sendCh != nil && r.bootFlag {
+ close(r.sendCh)
+ } else {
+ r.closeGRPCConn()
+ }
+}
+
+func (r *gRPCReporter) closeGRPCConn() {
+ if r.conn != nil {
+ if err := r.conn.Close(); err != nil {
+ r.logger.Error(err)
+ }
+ }
+}
+
+func (r *gRPCReporter) initSendPipeline() {
+ if r.traceClient == nil {
+ return
+ }
+ go func() {
+ StreamLoop:
+ for {
+ stream, err := r.traceClient.Collect(metadata.NewOutgoingContext(context.Background(), r.md))
+ if err != nil {
+ r.logger.Errorf("open stream error %v", err)
+ time.Sleep(5 * time.Second)
+ continue StreamLoop
+ }
+ for s := range r.sendCh {
+ err = stream.Send(s)
+ if err != nil {
+ r.logger.Errorf("send segment error %v", err)
+ r.closeStream(stream)
+ continue StreamLoop
+ }
+ }
+ r.closeStream(stream)
+ r.closeGRPCConn()
+ break
+ }
+ }()
+}
+
+func (r *gRPCReporter) initCDS(cdsWatchers []AgentConfigChangeWatcher) {
+ if r.cdsClient == nil {
+ return
+ }
+
+ // bind watchers
+ r.cdsService.BindWatchers(cdsWatchers)
+
+ // fetch config
+ go func() {
+ for {
+ if r.conn.GetState() == connectivity.Shutdown {
+ break
+ }
+
+ configurations, err := r.cdsClient.FetchConfigurations(context.Background(), &configuration.ConfigurationSyncRequest{
+ Service: r.entity.ServiceName,
+ Uuid: r.cdsService.UUID,
+ })
+
+ if err != nil {
+ r.logger.Errorf("fetch dynamic configuration error %v", err)
+ time.Sleep(r.cdsInterval)
+ continue
+ }
+
+ if len(configurations.GetCommands()) > 0 && configurations.GetCommands()[0].Command == "ConfigurationDiscoveryCommand" {
+ command := configurations.GetCommands()[0]
+ r.cdsService.HandleCommand(command)
+ }
+
+ time.Sleep(r.cdsInterval)
+ }
+ }()
+}
+
+func (r *gRPCReporter) closeStream(stream agentv3.TraceSegmentReportService_CollectClient) {
+ _, err := stream.CloseAndRecv()
+ if err != nil && err != io.EOF {
+ r.logger.Errorf("send closing error %v", err)
+ }
+}
+
+func (r *gRPCReporter) reportInstanceProperties() (err error) {
+ _, err = r.managementClient.ReportInstanceProperties(metadata.NewOutgoingContext(context.Background(), r.md), &managementv3.InstanceProperties{
+ Service: r.entity.ServiceName,
+ ServiceInstance: r.entity.ServiceInstanceName,
+ Properties: r.entity.Props,
+ Layer: r.entity.Layer,
+ })
+ return err
+}
+
+func (r *gRPCReporter) check() {
+ if r.checkInterval < 0 || r.conn == nil || r.managementClient == nil {
+ return
+ }
+ go func() {
+ instancePropertiesSubmitted := false
+ for {
+ if r.conn.GetState() == connectivity.Shutdown {
+ break
+ }
+
+ if !instancePropertiesSubmitted {
+ err := r.reportInstanceProperties()
+ if err != nil {
+ r.logger.Errorf("report serviceInstance properties error %v", err)
+ time.Sleep(r.checkInterval)
+ continue
+ }
+ instancePropertiesSubmitted = true
+ }
+
+ _, err := r.managementClient.KeepAlive(metadata.NewOutgoingContext(context.Background(), r.md), &managementv3.InstancePingPkg{
+ Service: r.entity.ServiceName,
+ ServiceInstance: r.entity.ServiceInstanceName,
+ Layer: r.entity.Layer,
+ })
+
+ if err != nil {
+ r.logger.Errorf("send keep alive signal error %v", err)
+ }
+ time.Sleep(r.checkInterval)
+ }
+ }()
+}
diff --git a/reporter/grpc_opts.go b/reporter/grpc_opts.go
new file mode 100644
index 0000000..74c9db4
--- /dev/null
+++ b/reporter/grpc_opts.go
@@ -0,0 +1,22 @@
+// 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 reporter
+
+// GRPCReporterOption allows for functional options to adjust behavior
+// of a gRPC reporter to be created by NewGRPCReporter
+type GRPCReporterOption func(r *gRPCReporter)
diff --git a/reporter/reporter.go b/reporter/reporter.go
new file mode 100644
index 0000000..679777f
--- /dev/null
+++ b/reporter/reporter.go
@@ -0,0 +1,94 @@
+// 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 reporter
+
+import (
+ "time"
+
+ commonv3 "skywalking.apache.org/repo/goapi/collect/common/v3"
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+)
+
+// Tag are supported by sky-walking engine.
+// As default, all Tags will be stored, but these ones have
+// particular meanings.
+type Tag string
+
+// Span interface as commonv3 span specification
+type Span interface {
+ SetOperationName(string)
+ GetOperationName() string
+ SetPeer(string)
+ SetSpanLayer(agentv3.SpanLayer)
+ SetComponent(int32)
+ Tag(Tag, string)
+ Log(time.Time, ...string)
+ Error(time.Time, ...string)
+ End()
+ IsEntry() bool
+ IsExit() bool
+ IsValid() bool
+}
+
+// SegmentContext is the context in a segment
+type SegmentContext interface {
+ GetTraceID() string
+ GetSegmentID() string
+ GetSpanID() int32
+ GetParentSpanID() int32
+ GetParentSegmentID() string
+}
+
+// SpanContext defines propagation specification of SkyWalking
+type SpanContext interface {
+ GetTraceID() string
+ GetParentSegmentID() string
+ GetParentService() string
+ GetParentServiceInstance() string
+ GetParentEndpoint() string
+ GetAddressUsedAtClient() string
+ GetParentSpanID() int32
+}
+
+type ReportedSpan interface {
+ Context() SegmentContext
+ Refs() []SpanContext
+ StartTime() int64
+ EndTime() int64
+ OperationName() string
+ Peer() string
+ SpanType() agentv3.SpanType
+ SpanLayer() agentv3.SpanLayer
+ IsError() bool
+ Tags() []*commonv3.KeyStringValuePair
+ Logs() []*agentv3.Log
+ ComponentID() int32
+}
+
+type Entity struct {
+ ServiceName string
+ ServiceInstanceName string
+ Props []*commonv3.KeyStringValuePair
+ Layer string
+}
+
+type Reporter interface {
+ Boot(entity Entity, cdsWatchers []AgentConfigChangeWatcher)
+ Send(spans []ReportedSpan)
+ Close()
+}
diff --git a/skylog/logger.go b/skylog/logger.go
new file mode 100644
index 0000000..80507f1
--- /dev/null
+++ b/skylog/logger.go
@@ -0,0 +1,37 @@
+// 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 skylog
+
+type Logger interface {
+ // Info logs to the INFO log.
+ Info(args ...interface{})
+ // Infof logs to the INFO log.
+ Infof(format string, args ...interface{})
+ // Warn logs to the WARNING and INFO logs.
+ Warn(args ...interface{})
+ // Warnf logs to the WARNING and INFO logs.
+ Warnf(format string, args ...interface{})
+ // Error logs to the ERROR, WARNING, and INFO logs.
+ Error(args ...interface{})
+ // Errorf logs to the ERROR, WARNING, and INFO logs.
+ Errorf(format string, args ...interface{})
+}
+
+// SetLogger for connect the agent with program
+func SetLogger(logger Logger) {
+}
diff --git a/tools/go-agent-enhance/cmd/helper.go b/tools/go-agent-enhance/cmd/helper.go
new file mode 100644
index 0000000..d97e152
--- /dev/null
+++ b/tools/go-agent-enhance/cmd/helper.go
@@ -0,0 +1,40 @@
+// 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 main
+
+import (
+ "fmt"
+ "os"
+)
+
+type EnhancementToolFlags struct {
+ Help bool `skyflag:"-h"`
+}
+
+func PrintUsageWithExit() {
+ fmt.Printf(`Usage: go {build,install} -a [-work] -toolexec "%s" PACKAGE...
+
+The Go-agent-enhance tool is designed for automatic enhancement of Golang programs,
+providing the capability for Distribute Tracing.
+
+Options:
+ -h
+ Print the usage message.
+`, os.Args[0])
+ os.Exit(1)
+}
diff --git a/tools/go-agent-enhance/cmd/main.go b/tools/go-agent-enhance/cmd/main.go
new file mode 100644
index 0000000..77b3ac1
--- /dev/null
+++ b/tools/go-agent-enhance/cmd/main.go
@@ -0,0 +1,73 @@
+// 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 main
+
+import (
+ "log"
+ "os"
+ "os/exec"
+
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument"
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/tools"
+)
+
+var toolFlags = &EnhancementToolFlags{}
+
+func main() {
+ args := os.Args[1:]
+ // Print usage
+ if err := tools.ParseFlags(toolFlags, args); err != nil || toolFlags.Help {
+ PrintUsageWithExit()
+ }
+
+ // only enhance the "compile" phase
+ cmdName := tools.ParseProxyCommandName(args)
+ if cmdName != "compile" {
+ executeDelegateCommand(args)
+ return
+ }
+
+ // parse the args
+ compileOptions := &api.CompileOptions{}
+ if err := tools.ParseFlags(compileOptions, args); err != nil {
+ executeDelegateCommand(args)
+ return
+ }
+
+ // execute the enhancement
+ args, err := instrument.Execute(compileOptions, args)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // execute the delegate command with updated args
+ executeDelegateCommand(args)
+}
+
+func executeDelegateCommand(args []string) {
+ path := args[0]
+ args = args[1:]
+ cmd := exec.Command(path, args...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if e := cmd.Run(); e != nil {
+ log.Fatal(e)
+ }
+}
diff --git a/tools/go-agent-enhance/go.mod b/tools/go-agent-enhance/go.mod
new file mode 100644
index 0000000..cd47ea4
--- /dev/null
+++ b/tools/go-agent-enhance/go.mod
@@ -0,0 +1,11 @@
+module github.com/apache/skywalking-go/tools/go-agent-enhance
+
+go 1.18
+
+require (
+ github.com/dave/dst v0.27.2 // indirect
+ github.com/sirupsen/logrus v1.9.0 // indirect
+ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
+ golang.org/x/tools v0.1.12 // indirect
+)
diff --git a/tools/go-agent-enhance/go.sum b/tools/go-agent-enhance/go.sum
new file mode 100644
index 0000000..408aba8
--- /dev/null
+++ b/tools/go-agent-enhance/go.sum
@@ -0,0 +1,21 @@
+github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc=
+github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
+github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg=
+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=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tools/go-agent-enhance/instrument/api/flags.go b/tools/go-agent-enhance/instrument/api/flags.go
new file mode 100644
index 0000000..0a39718
--- /dev/null
+++ b/tools/go-agent-enhance/instrument/api/flags.go
@@ -0,0 +1,33 @@
+// 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 api
+
+import "path/filepath"
+
+type CompileOptions struct {
+ Package string `skyflag:"-p"`
+ Output string `skyflag:"-o"`
+}
+
+func (c *CompileOptions) IsValid() bool {
+ return c.Package != "" && c.Output != ""
+}
+
+func (c *CompileOptions) CompileBaseDir() string {
+ return filepath.Dir(filepath.Dir(c.Output))
+}
diff --git a/tools/go-agent-enhance/instrument/api/instrument.go b/tools/go-agent-enhance/instrument/api/instrument.go
new file mode 100644
index 0000000..e38427c
--- /dev/null
+++ b/tools/go-agent-enhance/instrument/api/instrument.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 api
+
+import (
+ "github.com/dave/dst/dstutil"
+)
+
+type Instrument interface {
+ // CouldHandle verify current instrument can handle this request
+ CouldHandle(opts *CompileOptions) bool
+ // FilterAndEdit filter the matched data which decode by DST, and edit the data
+ FilterAndEdit(path string, cursor *dstutil.Cursor) bool
+ // WriteExtraFiles customized the extra files when there have instrumented files
+ WriteExtraFiles(dir string) ([]string, error)
+}
diff --git a/tools/go-agent-enhance/instrument/instrument.go b/tools/go-agent-enhance/instrument/instrument.go
new file mode 100644
index 0000000..ad6916c
--- /dev/null
+++ b/tools/go-agent-enhance/instrument/instrument.go
@@ -0,0 +1,188 @@
+// 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 instrument
+
+import (
+ "fmt"
+ "go/parser"
+ "go/printer"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/decorator"
+ "github.com/dave/dst/dstutil"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/runtime"
+)
+
+var instruments = []api.Instrument{
+ runtime.NewRuntimeInstrument(),
+}
+
+func Execute(opts *api.CompileOptions, args []string) ([]string, error) {
+ // if the options is invalid, just ignore
+ if !opts.IsValid() {
+ return args, nil
+ }
+
+ // init the logger for the instrument
+ loggerFile, err := initLogger(opts)
+ if err != nil {
+ return nil, err
+ }
+ defer loggerFile.Close()
+
+ return execute0(opts, args)
+}
+
+func execute0(opts *api.CompileOptions, args []string) ([]string, error) {
+ // find the instrument
+ var inst api.Instrument
+ for _, ins := range instruments {
+ if ins.CouldHandle(opts) {
+ inst = ins
+ break
+ }
+ }
+ if inst == nil {
+ return args, nil
+ }
+
+ var buildDir = filepath.Dir(opts.Output)
+
+ // instrument existing files
+ if err := instrumentFiles(buildDir, inst, args); err != nil {
+ return nil, err
+ }
+
+ // write extra files if exist
+ files, err := inst.WriteExtraFiles(buildDir)
+ if err != nil {
+ return nil, err
+ }
+ if len(files) > 0 {
+ args = append(args, files...)
+ }
+
+ return args, nil
+}
+
+func instrumentFiles(buildDir string, inst api.Instrument, args []string) error {
+ // parse files
+ parsedFiles, err := parseFilesInArgs(args)
+ if err != nil {
+ return err
+ }
+
+ // filter and edit the files
+ instrumentedFiles := make([]string, 0)
+ for path, info := range parsedFiles {
+ hasInstruted := false
+ dstutil.Apply(info.dstFile, func(cursor *dstutil.Cursor) bool {
+ if inst.FilterAndEdit(path, cursor) {
+ hasInstruted = true
+ }
+ return true
+ }, func(cursor *dstutil.Cursor) bool {
+ return true
+ })
+
+ if hasInstruted {
+ instrumentedFiles = append(instrumentedFiles, path)
+ }
+ }
+
+ // write instrumented files to the build directory
+ for _, updateFileSrc := range instrumentedFiles {
+ info := parsedFiles[updateFileSrc]
+ filename := filepath.Base(updateFileSrc)
+ dest := filepath.Join(buildDir, filename)
+ output, err := os.Create(dest)
+ if err != nil {
+ return err
+ }
+ if _, err := output.WriteString(fmt.Sprintf("//line %s:1\n", updateFileSrc)); err != nil {
+ return err
+ }
+ if err := writeFile(info.dstFile, output); err != nil {
+ return err
+ }
+ args[info.argsIndex] = dest
+ if err := output.Close(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func parseFilesInArgs(args []string) (map[string]*fileInfo, error) {
+ parsedFiles := make(map[string]*fileInfo)
+ for inx, path := range args {
+ // only process the go file
+ if !strings.HasSuffix(path, ".go") {
+ continue
+ }
+
+ // parse the file
+ file, err := decorator.ParseFile(nil, path, nil, parser.ParseComments)
+ if err != nil {
+ return nil, err
+ }
+
+ parsedFiles[path] = &fileInfo{
+ argsIndex: inx,
+ dstFile: file,
+ }
+ }
+
+ return parsedFiles, nil
+}
+
+func writeFile(file *dst.File, w io.Writer) error {
+ fset, af, err := decorator.RestoreFile(file)
+ if err != nil {
+ return err
+ }
+ return printer.Fprint(w, fset, af)
+}
+
+func initLogger(opts *api.CompileOptions) (*os.File, error) {
+ logrus.SetFormatter(&logrus.TextFormatter{
+ DisableColors: true,
+ FullTimestamp: true,
+ })
+ file, err := os.OpenFile(filepath.Join(opts.CompileBaseDir(), "instrument.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
+ if err != nil {
+ return nil, err
+ }
+ logrus.SetOutput(file)
+
+ return file, nil
+}
+
+type fileInfo struct {
+ argsIndex int
+ dstFile *dst.File
+}
diff --git a/tools/go-agent-enhance/instrument/runtime/instrument.go b/tools/go-agent-enhance/instrument/runtime/instrument.go
new file mode 100644
index 0000000..eb57458
--- /dev/null
+++ b/tools/go-agent-enhance/instrument/runtime/instrument.go
@@ -0,0 +1,136 @@
+// 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 runtime
+
+import (
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent-enhance/tools"
+)
+
+var (
+ TLSFieldName = "skywalking_tls"
+ TLSGetMethodName = "_skywalking_get_gls"
+ TLSSetMethodName = "_skywalking_set_gls"
+ TLSTakeSnapshotMethodName = "_skywalking_tls_take_snapshot"
+)
+
+type Instrument struct {
+}
+
+func NewRuntimeInstrument() *Instrument {
+ return &Instrument{}
+}
+
+func (r *Instrument) CouldHandle(opts *api.CompileOptions) bool {
+ return opts.Package == "runtime"
+}
+
+func (r *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
+ switch n := cursor.Node().(type) {
+ case *dst.TypeSpec:
+ if n.Name != nil && n.Name.Name != "g" {
+ return false
+ }
+ st, ok := n.Type.(*dst.StructType)
+ if !ok {
+ return false
+ }
+ // append the tls field
+ st.Fields.List = append(st.Fields.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent(TLSFieldName)},
+ Type: dst.NewIdent("interface{}"),
+ })
+ tools.LogWithStructEnhance("runtime", "g", TLSFieldName, "tls field")
+ return true
+ case *dst.FuncDecl:
+ if n.Name.Name != "newproc1" {
+ return false
+ }
+ if len(n.Type.Results.List) != 1 {
+ return false
+ }
+ if len(n.Type.Params.List) != 3 {
+ return false
+ }
+ parameters := tools.EnhanceParameterNames(n.Type.Params, false)
+ results := tools.EnhanceParameterNames(n.Type.Results, true)
+
+ tools.InsertStmtsBeforeBody(n.Body, `defer func() {
+ if {{(index .Parameters 1).Name}} != nil && {{(index .Results 0).Name}} != nil &&
+ {{.SnapshotMethod}} != nil && {{(index .Parameters 1).Name}}.{{.TLSField}} != nil {
+ {{(index .Results 0).Name}}.{{.TLSField}} = {{.SnapshotMethod}}({{(index .Parameters 1).Name}}.{{.TLSField}})
+ }
+}()
+`, struct {
+ Parameters []*tools.ParameterInfo
+ Results []*tools.ParameterInfo
+ SnapshotMethod string
+ TLSField string
+ }{
+ Parameters: parameters,
+ Results: results,
+ SnapshotMethod: TLSTakeSnapshotMethodName,
+ TLSField: TLSFieldName,
+ })
+ tools.LogWithMethodEnhance("runtime", "", "newproc1", "support cross goroutine context propagating")
+ return true
+ }
+ return false
+}
+
+func (r *Instrument) WriteExtraFiles(dir string) ([]string, error) {
+ return tools.WriteMultipleFile(dir, map[string]string{
+ "tls.go": tools.ExecuteTemplate(`package runtime
+
+import (
+ _ "unsafe"
+)
+
+//go:linkname {{.TLSTaskSnapshotMethod}} {{.TLSTaskSnapshotMethod}}
+var {{.TLSTaskSnapshotMethod}} func(interface{}) interface{}
+
+//go:linkname {{.TLSGetMethod}} {{.TLSGetMethod}}
+var {{.TLSGetMethod}} = _skywalking_tls_get_impl
+
+//go:linkname {{.TLSSetMethod}} {{.TLSSetMethod}}
+var {{.TLSSetMethod}} = _skywalking_tls_set_impl
+
+//go:nosplit
+func _skywalking_tls_get_impl() interface{} {
+ return getg().m.curg.{{.TLSFiledName}}
+}
+
+//go:nosplit
+func _skywalking_tls_set_impl(v interface{}) {
+ getg().m.curg.{{.TLSFiledName}} = v
+}`, struct {
+ TLSFiledName string
+ TLSGetMethod string
+ TLSSetMethod string
+ TLSTaskSnapshotMethod string
+ }{
+ TLSFiledName: TLSFieldName,
+ TLSGetMethod: TLSGetMethodName,
+ TLSSetMethod: TLSSetMethodName,
+ TLSTaskSnapshotMethod: TLSTakeSnapshotMethodName,
+ }),
+ })
+}
diff --git a/tools/go-agent-enhance/tools/enhancement.go b/tools/go-agent-enhance/tools/enhancement.go
new file mode 100644
index 0000000..d362fba
--- /dev/null
+++ b/tools/go-agent-enhance/tools/enhancement.go
@@ -0,0 +1,102 @@
+// 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 tools
+
+import (
+ "fmt"
+ "go/token"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/decorator"
+)
+
+type ParameterInfo struct {
+ Name string
+ Type dst.Expr
+ DefaultValueAsString string
+}
+
+// EnhanceParameterNames enhance the parameter names if they are missing
+func EnhanceParameterNames(fields *dst.FieldList, isResult bool) []*ParameterInfo {
+ if fields == nil {
+ return nil
+ }
+ result := make([]*ParameterInfo, 0)
+ for i, f := range fields.List {
+ defineName := fmt.Sprintf("skywalking_param_%d", i)
+ if isResult {
+ defineName = fmt.Sprintf("skywalking_result_%d", i)
+ }
+ if len(f.Names) == 0 {
+ f.Names = []*dst.Ident{{Name: defineName}}
+ result = append(result, newParameterInfo(defineName, f.Type))
+ } else {
+ for _, n := range f.Names {
+ if n.Name == "_" {
+ *n = *dst.NewIdent(defineName)
+ break
+ }
+ }
+
+ for _, n := range f.Names {
+ result = append(result, newParameterInfo(n.Name, f.Type))
+ break
+ }
+ }
+ }
+ return result
+}
+
+func GoStringToStats(goString string) []dst.Stmt {
+ parsed, err := decorator.Parse(fmt.Sprintf(`
+package main
+func main() {
+%s
+}`, goString))
+ if err != nil {
+ panic(fmt.Sprintf("parsing go failure: %v\n%s", err, goString))
+ }
+
+ return parsed.Decls[0].(*dst.FuncDecl).Body.List
+}
+
+func InsertStmtsBeforeBody(body *dst.BlockStmt, tmpl string, data interface{}) {
+ body.List = append(GoStringToStats(ExecuteTemplate(tmpl, data)), body.List...)
+}
+
+func newParameterInfo(name string, tp dst.Expr) *ParameterInfo {
+ result := &ParameterInfo{
+ Name: name,
+ Type: tp,
+ }
+ var defaultNil = "nil"
+ switch n := tp.(type) {
+ case *dst.StarExpr:
+ result.DefaultValueAsString = defaultNil
+ case *dst.UnaryExpr:
+ if n.Op == token.INT || n.Op == token.FLOAT {
+ result.DefaultValueAsString = "0"
+ } else {
+ result.DefaultValueAsString = defaultNil
+ }
+ default:
+ result.DefaultValueAsString = defaultNil
+ }
+
+ return result
+}
diff --git a/tools/go-agent-enhance/tools/files.go b/tools/go-agent-enhance/tools/files.go
new file mode 100644
index 0000000..99ba059
--- /dev/null
+++ b/tools/go-agent-enhance/tools/files.go
@@ -0,0 +1,36 @@
+// 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 tools
+
+import (
+ "os"
+ "path/filepath"
+)
+
+func WriteMultipleFile(baseDir string, nameWithData map[string]string) ([]string, error) {
+ paths := make([]string, 0)
+ for name, data := range nameWithData {
+ fileName := filepath.Join(baseDir, name)
+ if err := os.WriteFile(fileName, []byte(data), 0o600); err != nil {
+ return nil, err
+ }
+ paths = append(paths, fileName)
+ }
+
+ return paths, nil
+}
diff --git a/tools/go-agent-enhance/tools/flags.go b/tools/go-agent-enhance/tools/flags.go
new file mode 100644
index 0000000..a23c2d4
--- /dev/null
+++ b/tools/go-agent-enhance/tools/flags.go
@@ -0,0 +1,102 @@
+// 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 tools
+
+import (
+ "fmt"
+ "path/filepath"
+ "reflect"
+ "strings"
+)
+
+const flagTagKey = "skyflag"
+
+func ParseFlags(result interface{}, args []string) error {
+ if len(args) == 0 {
+ return fmt.Errorf("no args")
+ }
+ flags := parseFlagsFromStruct(result)
+ if len(flags) == 0 {
+ return nil
+ }
+
+ i := 0
+ for i < len(args)-1 {
+ i += parseFlag(flags, args[i], args[i+1])
+ }
+
+ if i < len(args) {
+ parseFlag(flags, args[i], "")
+ }
+ return nil
+}
+
+func ParseProxyCommandName(args []string) string {
+ if len(args) == 0 {
+ return ""
+ }
+
+ cmd := filepath.Base(args[0])
+ if ext := filepath.Ext(cmd); ext != "" {
+ cmd = strings.TrimSuffix(cmd, ext)
+ }
+ return cmd
+}
+
+func parseFlag(flags map[string]reflect.Value, curArg, nextArg string) int {
+ if curArg[0] != '-' {
+ return 1
+ }
+
+ kv := strings.SplitN(curArg, "=", 2)
+ option := kv[0]
+ if v, exist := flags[option]; !exist {
+ if len(kv) == 2 {
+ return 1
+ } else if nextArg == "" || (len(nextArg) > 1 && nextArg[0] != '-') {
+ return 2
+ }
+ return 1
+ } else if len(kv) == 2 {
+ v.SetString(kv[1])
+ return 1
+ } else {
+ switch v.Kind() {
+ case reflect.String:
+ v.SetString(nextArg)
+ return 2
+ case reflect.Bool:
+ v.SetBool(true)
+ return 1
+ }
+ return 1
+ }
+}
+
+func parseFlagsFromStruct(result interface{}) map[string]reflect.Value {
+ e := reflect.ValueOf(result).Elem()
+ typ := e.Type()
+ flagSetValueMap := make(map[string]reflect.Value, e.NumField())
+ for i := 0; i < typ.NumField(); i++ {
+ field := typ.Field(i)
+ if tag, ok := field.Tag.Lookup(flagTagKey); ok {
+ flagSetValueMap[tag] = e.Field(i)
+ }
+ }
+ return flagSetValueMap
+}
diff --git a/tools/go-agent-enhance/tools/logger.go b/tools/go-agent-enhance/tools/logger.go
new file mode 100644
index 0000000..e5b23b1
--- /dev/null
+++ b/tools/go-agent-enhance/tools/logger.go
@@ -0,0 +1,36 @@
+// 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 tools
+
+import "github.com/sirupsen/logrus"
+
+func LogWithMethodEnhance(pkg, receiver, method, detail string) {
+ logrus.WithField("type", "enhance_method").
+ WithField("package", pkg).
+ WithField("receiver", receiver).
+ WithField("method", method).
+ Info(detail)
+}
+
+func LogWithStructEnhance(pkg, structName, field, detail string) {
+ logrus.WithField("type", "enhance_struct").
+ WithField("package", pkg).
+ WithField("struct", structName).
+ WithField("field", field).
+ Info(detail)
+}
diff --git a/tools/go-agent-enhance/tools/templetes.go b/tools/go-agent-enhance/tools/templetes.go
new file mode 100644
index 0000000..bb51f87
--- /dev/null
+++ b/tools/go-agent-enhance/tools/templetes.go
@@ -0,0 +1,36 @@
+// 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 tools
+
+import (
+ "bytes"
+ "html/template"
+)
+
+func ExecuteTemplate(tmpl string, data interface{}) string {
+ t, err := template.New("").Parse(tmpl)
+ if err != nil {
+ panic(err)
+ }
+ var buf bytes.Buffer
+ err = t.Execute(&buf, data)
+ if err != nil {
+ panic(err)
+ }
+ return buf.String()
+}