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

Fix process agent status provider #22527

Merged
merged 9 commits into from
Feb 5, 2024
126 changes: 126 additions & 0 deletions comp/process/status/statusimpl/fixtures/json_response.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
{
"core": {
"build_arch": "arm64",
"config": {
"log_level": "info"
},
"go_version": "go1.21.5",
"metadata": {
"agent-flavor": "process_agent",
"host-tags": {
"system": []
},
"install-method": {
"installer_version": null,
"tool": null,
"tool_version": "undefined"
},
"logs": {
"auto_multi_line_detection_enabled": false,
"transport": ""
},
"meta": {
"ec2-hostname": "",
"host_aliases": [],
"hostname": "COMP-VQHPF4W6GY",
"instance-id": "",
"socket-fqdn": "COMP-VQHPF4W6GY",
"socket-hostname": "COMP-VQHPF4W6GY",
"timezones": [
"CET"
]
},
"network": null,
"os": "darwin",
"otlp": {
"enabled": false
},
"proxy-info": {
"no-proxy-nonexact-match": false,
"no-proxy-nonexact-match-explicitly-set": false,
"proxy-behavior-changed": false
},
"python": "n/a",
"systemStats": {
"cpuCores": 10,
"fbsdV": [
"",
"",
""
],
"macV": [
"14.2.1",
[
"",
"",
""
],
"arm64"
],
"machine": "arm64",
"nixV": [
"",
"",
""
],
"platform": "darwin",
"processor": "Apple M1 Max",
"pythonV": "n/a",
"winV": [
"",
"",
""
]
}
},
"version": "7.51.0-rc.1+git.416.0d1edc1"
},
"date": 1706892483712089000,
"expvars": {
"connections_queue_bytes": 0,
"connections_queue_size": 0,
"container_count": 0,
"container_id": "",
"docker_socket": "/var/run/docker.sock",
"drop_check_payloads": [],
"enabled_checks": [
"process",
"rtprocess"
],
"endpoints": {
"https://process.datadoghq.eu": [
"72724"
]
},
"event_queue_bytes": 0,
"event_queue_size": 0,
"language_detection_enabled": false,
"last_collect_time": "2024-02-02 17:47:57",
"log_file": "",
"memstats": {
"alloc": 30387880
},
"pid": 72211,
"pod_queue_bytes": 0,
"pod_queue_size": 0,
"process_count": 757,
"process_queue_bytes": 0,
"process_queue_size": 0,
"proxy_url": "",
"rtprocess_queue_bytes": 0,
"rtprocess_queue_size": 0,
"system_probe_process_module_enabled": false,
"uptime": 18,
"uptime_nano": 1706892464835469000,
"version": {
"BuildDate": "",
"GitBranch": "",
"GitCommit": "",
"GoVersion": "",
"Version": ""
},
"workloadmeta_extractor_cache_size": 0,
"workloadmeta_extractor_diffs_dropped": 0,
"workloadmeta_extractor_stale_diffs": 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Status: Not running or unreachable
48 changes: 48 additions & 0 deletions comp/process/status/statusimpl/fixtures/text_response.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

Version: 7.51.0-rc.1+git.416.0d1edc1
Status date: 2024-02-02 16:48:03.712 UTC (1706892483712)
Process Agent Start: 2024-02-02 16:47:44.835 UTC (1706892464835)
Pid: 72211
Go Version: go1.21.5
Build arch: arm64
Log Level: info
Enabled Checks: [process rtprocess]
Allocated Memory: 30,387,880 bytes
Hostname: COMP-VQHPF4W6GY
System Probe Process Module Status: Not running
Process Language Detection Enabled: False

=================
Process Endpoints
=================
https://process.datadoghq.eu - API Key ending with:
- 72724

=========
Collector
=========
Last collection time: 2024-02-02 17:47:57
Docker socket: /var/run/docker.sock
Number of processes: 757
Number of containers: 0
Process Queue length: 0
RTProcess Queue length: 0
Connections Queue length: 0
Event Queue length: 0
Pod Queue length: 0
Process Bytes enqueued: 0
RTProcess Bytes enqueued: 0
Connections Bytes enqueued: 0
Event Bytes enqueued: 0
Pod Bytes enqueued: 0
Drop Check Payloads: []

==========
Extractors
==========

Workloadmeta
============
Cache size: 0
Stale diffs discarded: 0
Diffs dropped: 0
33 changes: 22 additions & 11 deletions comp/process/status/statusimpl/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ func Module() fxutil.Module {
fx.Provide(newStatus))
}

type statusProvider struct{}
type statusProvider struct {
testServerURL string
}

func newStatus() provides {
return provides{
Expand Down Expand Up @@ -84,24 +86,33 @@ func (s statusProvider) getStatusInfo() map[string]interface{} {

func (s statusProvider) populateStatus() map[string]interface{} {
status := make(map[string]interface{})
addressPort, err := config.GetProcessAPIAddressPort()
if err != nil {
status["error"] = fmt.Sprintf("%v", err.Error())
return status
}

c := client()
statusEndpoint := fmt.Sprintf("http://%s/agent/status", addressPort)
b, err := apiutil.DoGet(c, statusEndpoint, apiutil.CloseConnection)

var url string
if s.testServerURL != "" {
url = s.testServerURL
} else {
addressPort, err := config.GetProcessAPIAddressPort()
if err != nil {
status["error"] = fmt.Sprintf("%v", err.Error())
return status
}
url = fmt.Sprintf("http://%s/agent/status", addressPort)
Comment on lines +96 to +101
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 thought
Wonder if you could mock the config.GetProcessAPIAddressPort instead of setting the testServerURL. I'd rather use an interface and mock it in test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a totally valid point. I would like to do that in a separate PR, as GetProcessAPIAddressPort is used in multiple places and I don't want to change the focus of the fix in the PR 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see config is global, ok then !

}

b, err := apiutil.DoGet(c, url, apiutil.CloseConnection)

if err != nil {
status["error"] = fmt.Sprintf("%v", err.Error())
return status
}

err = json.Unmarshal(b, &s)
err = json.Unmarshal(b, &status)
if err != nil {
status["error"] = fmt.Sprintf("%v", err.Error())
return status
return map[string]interface{}{
"error": fmt.Sprintf("%v", err.Error()),
}
}

return status
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{{- with .processAgentStatus }}
{{- if .error }}

Status: Not running or unreachable
{{- else }}

Version: {{ .core.version }}
Status date: {{ formatUnixTime .date }}
Process Agent Start: {{ formatUnixTime .expvars.uptime_nano }}
Expand Down
110 changes: 105 additions & 5 deletions comp/process/status/statusimpl/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,54 @@ package statusimpl

import (
"bytes"
"embed"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestStatusOut(t *testing.T) {
provides := newStatus()
//go:embed fixtures
var fixturesTemplates embed.FS

headerProvider := provides.StatusProvider.Provider
func fakeStatusServer(t *testing.T, errCode int, response []byte) *httptest.Server {
handler := func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

if errCode != 200 {
http.NotFound(w, r)
} else {
_, err := w.Write(response)
require.NoError(t, err)
}
}

return httptest.NewServer(http.HandlerFunc(handler))
}

func TestStatus(t *testing.T) {
originalTZ := os.Getenv("TZ")
os.Setenv("TZ", "UTC")
defer func() {
os.Setenv("TZ", originalTZ)
}()

jsonBytes, err := fixturesTemplates.ReadFile("fixtures/json_response.tmpl")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion
You can directly embed the two files using

//go:embed fixtures/json_response.json
jsonResponse string // or []bytes if you prefer to have it in bytes

It saves you from an error checking and the double ReadFile

Copy link
Member Author

@GustavoCaso GustavoCaso Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 😄
Thanks for the suggestion

assert.NoError(t, err)

textResponse, err := fixturesTemplates.ReadFile("fixtures/text_response.tmpl")
assert.NoError(t, err)

server := fakeStatusServer(t, 200, jsonBytes)
defer server.Close()

headerProvider := statusProvider{
testServerURL: server.URL,
}

tests := []struct {
name string
Expand All @@ -24,16 +63,25 @@ func TestStatusOut(t *testing.T) {
{"JSON", func(t *testing.T) {
stats := make(map[string]interface{})
headerProvider.JSON(false, stats)
processStats := stats["processAgentStatus"]

val, ok := processStats.(map[string]interface{})
assert.True(t, ok)

assert.NotEmpty(t, stats)
assert.NotEmpty(t, val["core"])
assert.Empty(t, val["error"])
}},
{"Text", func(t *testing.T) {
b := new(bytes.Buffer)
err := headerProvider.Text(false, b)

assert.NoError(t, err)

assert.NotEmpty(t, b.String())
// We replace windows line break by linux so the tests pass on every OS
expected := strings.Replace(string(textResponse), "\r\n", "\n", -1)
output := strings.Replace(b.String(), "\r\n", "\n", -1)

assert.Equal(t, expected, output)
}},
{"HTML", func(t *testing.T) {
b := new(bytes.Buffer)
Expand All @@ -51,3 +99,55 @@ func TestStatusOut(t *testing.T) {
})
}
}

func TestStatusError(t *testing.T) {
server := fakeStatusServer(t, 500, []byte{})
defer server.Close()

originalTZ := os.Getenv("TZ")
os.Setenv("TZ", "UTC")
defer func() {
os.Setenv("TZ", originalTZ)
}()

errorResponse, err := fixturesTemplates.ReadFile("fixtures/text_error_response.tmpl")
assert.NoError(t, err)

headerProvider := statusProvider{
testServerURL: server.URL,
}

tests := []struct {
name string
assertFunc func(t *testing.T)
}{
{"JSON", func(t *testing.T) {
stats := make(map[string]interface{})
headerProvider.JSON(false, stats)
processStats := stats["processAgentStatus"]

val, ok := processStats.(map[string]interface{})
assert.True(t, ok)

assert.NotEmpty(t, val["error"])
}},
{"Text", func(t *testing.T) {
b := new(bytes.Buffer)
err := headerProvider.Text(false, b)

assert.NoError(t, err)

// We replace windows line break by linux so the tests pass on every OS
expected := strings.Replace(string(errorResponse), "\r\n", "\n", -1)
output := strings.Replace(b.String(), "\r\n", "\n", -1)

assert.Equal(t, expected, output)
}},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.assertFunc(t)
})
}
}
1 change: 0 additions & 1 deletion test/new-e2e/tests/process/linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ type linuxTestSuite struct {
}

func TestLinuxTestSuite(t *testing.T) {
t.Skip("PROCS-3574: consistent failures on process tests")
e2e.Run(t, &linuxTestSuite{}, e2e.WithProvisioner(awshost.Provisioner(awshost.WithAgentOptions(agentparams.WithAgentConfig(processCheckConfigStr)))))
}

Expand Down
Loading
Loading