Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/civisibility: test session logical names #2904

Merged
merged 2 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions ddtrace/tracer/civisibility_payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"sync/atomic"

"github.com/tinylib/msgp/msgp"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
Expand Down Expand Up @@ -65,6 +67,26 @@ func newCiVisibilityPayload() *ciVisibilityPayload {
func (p *ciVisibilityPayload) getBuffer(config *config) (*bytes.Buffer, error) {
log.Debug("ciVisibilityPayload: .getBuffer (count: %v)", p.itemCount())

// Create a buffer to read the current payload
payloadBuf := new(bytes.Buffer)
if _, err := payloadBuf.ReadFrom(p.payload); err != nil {
return nil, err
}

// Create the visibility payload
visibilityPayload := p.writeEnvelope(config.env, payloadBuf.Bytes())

// Create a new buffer to encode the visibility payload in MessagePack format
encodedBuf := new(bytes.Buffer)
if err := msgp.Encode(encodedBuf, visibilityPayload); err != nil {
return nil, err
}

return encodedBuf, nil
}

func (p *ciVisibilityPayload) writeEnvelope(env string, events []byte) *ciTestCyclePayload {

/*
The Payload format in the CI Visibility protocol is like this:
{
Expand All @@ -85,36 +107,35 @@ func (p *ciVisibilityPayload) getBuffer(config *config) (*bytes.Buffer, error) {
The event format can be found in the `civisibility_tslv.go` file in the ciVisibilityEvent documentation
*/

// Create a buffer to read the current payload
payloadBuf := new(bytes.Buffer)
if _, err := payloadBuf.ReadFrom(p.payload); err != nil {
return nil, err
}

// Create the metadata map
allMetadata := map[string]string{
"language": "go",
"runtime-id": globalconfig.RuntimeID(),
"library_version": version.Tag,
}
if config.env != "" {
allMetadata["env"] = config.env
if env != "" {
allMetadata["env"] = env
}

// Create the visibility payload
visibilityPayload := ciTestCyclePayload{
visibilityPayload := &ciTestCyclePayload{
Version: 1,
Metadata: map[string]map[string]string{
"*": allMetadata,
},
Events: payloadBuf.Bytes(),
Events: events,
}

// Create a new buffer to encode the visibility payload in MessagePack format
encodedBuf := new(bytes.Buffer)
if err := msgp.Encode(encodedBuf, &visibilityPayload); err != nil {
return nil, err
// Check for the test session name and append the tag at the metadata level
if testSessionName, ok := utils.GetCITags()[constants.TestSessionName]; ok {
testSessionMap := map[string]string{
constants.TestSessionName: testSessionName,
}
visibilityPayload.Metadata["test_session_end"] = testSessionMap
visibilityPayload.Metadata["test_module_end"] = testSessionMap
visibilityPayload.Metadata["test_suite_end"] = testSessionMap
visibilityPayload.Metadata["test"] = testSessionMap
}

return encodedBuf, nil
return visibilityPayload
}
49 changes: 49 additions & 0 deletions ddtrace/tracer/civisibility_payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package tracer

import (
"bytes"
"encoding/json"
"io"
"strconv"
"strings"
Expand All @@ -15,6 +16,10 @@ import (

"github.com/stretchr/testify/assert"
"github.com/tinylib/msgp/msgp"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
)

func newCiVisibilityEventsList(n int) []*ciVisibilityEvent {
Expand Down Expand Up @@ -80,6 +85,50 @@ func TestCiVisibilityPayloadDecode(t *testing.T) {
}
}

func TestCiVisibilityPayloadEnvelope(t *testing.T) {
assert := assert.New(t)
p := newCiVisibilityPayload()
payload := p.writeEnvelope("none", []byte{})

// Encode the payload to message pack
encodedBuf := new(bytes.Buffer)
err := msgp.Encode(encodedBuf, payload)
assert.NoError(err)

// Convert the message pack to json
jsonBuf := new(bytes.Buffer)
_, err = msgp.CopyToJSON(jsonBuf, encodedBuf)
assert.NoError(err)

// Decode the json payload
var testCyclePayload ciTestCyclePayload
err = json.Unmarshal(jsonBuf.Bytes(), &testCyclePayload)
assert.NoError(err)

// Now let's assert the decoded envelope metadata
assert.Contains(testCyclePayload.Metadata, "*")
assert.Subset(testCyclePayload.Metadata["*"], map[string]string{
"language": "go",
"runtime-id": globalconfig.RuntimeID(),
"library_version": version.Tag,
})

testSessionName := utils.GetCITags()[constants.TestSessionName]
testSessionMap := map[string]string{constants.TestSessionName: testSessionName}

assert.Contains(testCyclePayload.Metadata, "test_session_end")
assert.Subset(testCyclePayload.Metadata["test_session_end"], testSessionMap)

assert.Contains(testCyclePayload.Metadata, "test_module_end")
assert.Subset(testCyclePayload.Metadata["test_module_end"], testSessionMap)

assert.Contains(testCyclePayload.Metadata, "test_suite_end")
assert.Subset(testCyclePayload.Metadata["test_suite_end"], testSessionMap)

assert.Contains(testCyclePayload.Metadata, "test")
assert.Subset(testCyclePayload.Metadata["test"], testSessionMap)
}

func BenchmarkCiVisibilityPayloadThroughput(b *testing.B) {
b.Run("10K", benchmarkCiVisibilityPayloadThroughput(1))
b.Run("100K", benchmarkCiVisibilityPayloadThroughput(10))
Expand Down
3 changes: 3 additions & 0 deletions internal/civisibility/constants/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ const (
// This environment variable should be set to your Datadog API key, allowing the agentless mode to authenticate and
// send data directly to the Datadog platform.
APIKeyEnvironmentVariable = "DD_API_KEY"

// CIVisibilityTestSessionNameEnvironmentVariable indicate the test session name to be used on CI Visibility payloads
CIVisibilityTestSessionNameEnvironmentVariable = "DD_TEST_SESSION_NAME"
)
4 changes: 4 additions & 0 deletions internal/civisibility/constants/test_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ const (
// TestCommandWorkingDirectory indicates the test command working directory relative to the source root.
// This constant is used to tag traces with the working directory path relative to the source root.
TestCommandWorkingDirectory = "test.working_directory"

// TestSessionName indicates the test session name
// This constant is used to tag traces with the test session name
TestSessionName = "test_session.name"
)

// Define valid test status types.
Expand Down
4 changes: 4 additions & 0 deletions internal/civisibility/integrations/manual_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ func fillCommonTags(opts []tracer.StartSpanOption) []tracer.StartSpanOption {

// Apply CI tags
for k, v := range utils.GetCITags() {
// Ignore the test session name (sent at the payload metadata level, see `civisibility_payload.go`)
if k == constants.TestSessionName {
continue
}
opts = append(opts, tracer.Tag(k, v))
}

Expand Down
16 changes: 1 addition & 15 deletions internal/civisibility/integrations/manual_api_ddtestsession.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"

Expand All @@ -37,23 +35,11 @@ type tslvTestSession struct {

// CreateTestSession initializes a new test session. It automatically determines the command and working directory.
func CreateTestSession() DdTestSession {
var cmd string
if len(os.Args) == 1 {
cmd = filepath.Base(os.Args[0])
} else {
cmd = fmt.Sprintf("%s %s ", filepath.Base(os.Args[0]), strings.Join(os.Args[1:], " "))
}

// Filter out some parameters to make the command more stable.
cmd = regexp.MustCompile(`(?si)-test.gocoverdir=(.*)\s`).ReplaceAllString(cmd, "")
cmd = regexp.MustCompile(`(?si)-test.v=(.*)\s`).ReplaceAllString(cmd, "")
cmd = regexp.MustCompile(`(?si)-test.testlogfile=(.*)\s`).ReplaceAllString(cmd, "")
cmd = strings.TrimSpace(cmd)
wd, err := os.Getwd()
if err == nil {
wd = utils.GetRelativePathFromCITagsSourceRoot(wd)
}
return CreateTestSessionWith(cmd, wd, "", time.Now())
return CreateTestSessionWith(utils.GetCITags()[constants.TestCommand], wd, "", time.Now())
}

// CreateTestSessionWith initializes a new test session with specified command, working directory, framework, and start time.
Expand Down
31 changes: 31 additions & 0 deletions internal/civisibility/utils/environmentTags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
package utils

import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"

"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants"
Expand Down Expand Up @@ -90,12 +94,39 @@ func GetRelativePathFromCITagsSourceRoot(path string) string {
// A map[string]string containing the extracted CI/CD tags.
func createCITagsMap() map[string]string {
localTags := getProviderTags()

// Populate runtime values
localTags[constants.OSPlatform] = runtime.GOOS
localTags[constants.OSVersion] = osinfo.OSVersion()
localTags[constants.OSArchitecture] = runtime.GOARCH
localTags[constants.RuntimeName] = runtime.Compiler
localTags[constants.RuntimeVersion] = runtime.Version()

// Get command line test command
var cmd string
if len(os.Args) == 1 {
cmd = filepath.Base(os.Args[0])
} else {
cmd = fmt.Sprintf("%s %s ", filepath.Base(os.Args[0]), strings.Join(os.Args[1:], " "))
}

// Filter out some parameters to make the command more stable.
cmd = regexp.MustCompile(`(?si)-test.gocoverdir=(.*)\s`).ReplaceAllString(cmd, "")
cmd = regexp.MustCompile(`(?si)-test.v=(.*)\s`).ReplaceAllString(cmd, "")
cmd = regexp.MustCompile(`(?si)-test.testlogfile=(.*)\s`).ReplaceAllString(cmd, "")
cmd = strings.TrimSpace(cmd)
localTags[constants.TestCommand] = cmd

// Populate the test session name
if testSessionName, ok := os.LookupEnv(constants.CIVisibilityTestSessionNameEnvironmentVariable); ok {
localTags[constants.TestSessionName] = testSessionName
} else if jobName, ok := localTags[constants.CIJobName]; ok {
localTags[constants.TestSessionName] = fmt.Sprintf("%s-%s", jobName, cmd)
} else {
localTags[constants.TestSessionName] = cmd
}

// Populate missing git data
gitData, _ := getLocalGitData()

// Populate Git metadata from the local Git repository if not already present in localTags
Expand Down
Loading