Skip to content

Commit

Permalink
internal/civisibility: test session logical names
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyredondo committed Oct 3, 2024
1 parent ef90025 commit 15620a7
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 24 deletions.
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
24 changes: 15 additions & 9 deletions internal/civisibility/integrations/manual_api_ddtestsession.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ 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:], " "))
var ok bool
tags := utils.GetCITags()
if cmd, ok = tags[constants.TestCommand]; !ok || cmd == "" {
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)
tags[constants.TestCommand] = cmd
}

// 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)
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

0 comments on commit 15620a7

Please sign in to comment.