diff --git a/cmd/process-agent/api/status.go b/cmd/process-agent/api/status.go index 7c4585944c7c30..2f7854bc8a02f4 100644 --- a/cmd/process-agent/api/status.go +++ b/cmd/process-agent/api/status.go @@ -7,23 +7,49 @@ package api import ( "encoding/json" + "fmt" "net/http" - "github.com/DataDog/datadog-agent/pkg/status" + ddconfig "github.com/DataDog/datadog-agent/pkg/config" + "github.com/DataDog/datadog-agent/pkg/process/util" "github.com/DataDog/datadog-agent/pkg/util/log" ) +func writeError(err error, code int, w http.ResponseWriter) { + body, _ := json.Marshal(map[string]string{"error": err.Error()}) + http.Error(w, string(body), code) +} + func statusHandler(w http.ResponseWriter, _ *http.Request) { - log.Trace("Received status request from process-agent") + log.Info("Got a request for the status. Making status.") + + // Get expVar server address + ipcAddr, err := ddconfig.GetIPCAddress() + if err != nil { + writeError(err, http.StatusInternalServerError, w) + _ = log.Warn("config error:", err) + return + } + + port := ddconfig.Datadog.GetInt("process_config.expvar_port") + if port <= 0 { + _ = log.Warnf("Invalid process_config.expvar_port -- %d, using default port %d\n", port, ddconfig.DefaultProcessExpVarPort) + port = ddconfig.DefaultProcessExpVarPort + } + expvarEndpoint := fmt.Sprintf("http://%s:%d/debug/vars", ipcAddr, port) - agentStatus, err := status.GetStatus() + agentStatus, err := util.GetStatus(expvarEndpoint) if err != nil { - _ = log.Warn("failed to get status from agent:", agentStatus) + _ = log.Warn("failed to get status from agent:", err) + writeError(err, http.StatusInternalServerError, w) + return } b, err := json.Marshal(agentStatus) if err != nil { _ = log.Warn("failed to serialize status response from agent:", err) + writeError(err, http.StatusInternalServerError, w) + return } _, err = w.Write(b) diff --git a/cmd/process-agent/app/status.go b/cmd/process-agent/app/status.go index fceabe94930d63..7b87c803f63f7e 100644 --- a/cmd/process-agent/app/status.go +++ b/cmd/process-agent/app/status.go @@ -11,69 +11,21 @@ import ( "io" "os" "text/template" - "time" "github.com/spf13/cobra" "github.com/DataDog/datadog-agent/cmd/process-agent/api" - "github.com/DataDog/datadog-agent/pkg/api/util" + apiutil "github.com/DataDog/datadog-agent/pkg/api/util" ddconfig "github.com/DataDog/datadog-agent/pkg/config" - "github.com/DataDog/datadog-agent/pkg/metadata/host" "github.com/DataDog/datadog-agent/pkg/process/config" + "github.com/DataDog/datadog-agent/pkg/process/util" ddstatus "github.com/DataDog/datadog-agent/pkg/status" "github.com/DataDog/datadog-agent/pkg/util/log" ) -var httpClient = util.GetClient(false) +var httpClient = apiutil.GetClient(false) const ( - statusTemplate = ` -============================== -Process Agent ({{ .Core.AgentVersion }}) -============================== - - Status date: {{ formatUnixTime .Date }} - Process Agent Start: {{ formatUnixTime .Expvars.UptimeNano }} - Pid: {{ .Expvars.Pid }} - Go Version: {{ .Core.GoVersion }} - Python Version: {{ .Core.PythonVersion }} - Build arch: {{ .Core.Arch }} - Log Level: {{ .Core.Config.LogLevel }} - Enabled Checks: {{ .Expvars.EnabledChecks }} - Allocated Memory: {{ .Expvars.MemStats.Alloc }} bytes - Hostname: {{ .Core.Metadata.Meta.Hostname }} - -================= -Process Endpoints -================= -{{- with .Expvars.Endpoints}} - {{- range $key, $value := .}} - {{$key}} - API Key{{ if gt (len $value) 1}}s{{end}} ending with: - {{- range $idx, $apikey := $value }} - - {{$apikey}} - {{- end}} - {{- end}} -{{- else }} - - No endpoints information. The agent may be misconfigured. -{{- end }} - -========= -Collector -========= - - Last collection time: {{.Expvars.LastCollectTime}} - Docker socket: {{.Expvars.DockerSocket}} - Number of processes: {{.Expvars.ProcessCount}} - Number of containers: {{.Expvars.ContainerCount}} - Process Queue length: {{.Expvars.ProcessQueueSize}} - RTProcess Queue length: {{.Expvars.RTProcessQueueSize}} - Pod Queue length: {{.Expvars.PodQueueSize}} - Process Bytes enqueued: {{.Expvars.ProcessQueueBytes}} - RTProcess Bytes enqueued: {{.Expvars.RTProcessQueueBytes}} - Pod Bytes enqueued: {{.Expvars.PodQueueBytes}} - -` notRunning = ` ============= Process Agent @@ -93,122 +45,6 @@ Error ` ) -type coreStatus struct { - AgentVersion string `json:"version"` - GoVersion string `json:"go_version"` - PythonVersion string `json:"python_version"` - Arch string `json:"build_arch"` - Config struct { - LogLevel string `json:"log_level"` - } `json:"config"` - Metadata host.Payload `json:"metadata"` -} - -type infoVersion struct { - Version string - GitCommit string - GitBranch string - BuildDate string - GoVersion string -} - -type processExpvars struct { - Pid int `json:"pid"` - Uptime int `json:"uptime"` - UptimeNano float64 `json:"uptime_nano"` - MemStats struct{ Alloc uint64 } `json:"memstats"` - Version infoVersion `json:"version"` - DockerSocket string `json:"docker_socket"` - LastCollectTime string `json:"last_collect_time"` - ProcessCount int `json:"process_count"` - ContainerCount int `json:"container_count"` - ProcessQueueSize int `json:"process_queue_size"` - RTProcessQueueSize int `json:"rtprocess_queue_size"` - PodQueueSize int `json:"pod_queue_size"` - ProcessQueueBytes int `json:"process_queue_bytes"` - RTProcessQueueBytes int `json:"rtprocess_queue_bytes"` - PodQueueBytes int `json:"pod_queue_bytes"` - ContainerID string `json:"container_id"` - ProxyURL string `json:"proxy_url"` - LogFile string `json:"log_file"` - EnabledChecks []string `json:"enabled_checks"` - Endpoints map[string][]string `json:"endpoints"` -} - -type status struct { - Date float64 - Core coreStatus // Contains the status from the core agent - Expvars processExpvars // Contains the expvars retrieved from the process agent -} - -type statusOption func(s *status) - -type connectionError struct { - error -} - -func overrideTime(t time.Time) statusOption { - return func(s *status) { - s.Date = float64(t.UnixNano()) - } -} - -func getCoreStatus() (s coreStatus, err error) { - addressPort, err := api.GetAPIAddressPort() - if err != nil { - return coreStatus{}, fmt.Errorf("config error: %s", err.Error()) - } - - statusEndpoint := fmt.Sprintf("http://%s/agent/status", addressPort) - b, err := util.DoGet(httpClient, statusEndpoint) - if err != nil { - return s, connectionError{err} - } - - err = json.Unmarshal(b, &s) - return -} - -func getExpvars() (s processExpvars, err error) { - ipcAddr, err := ddconfig.GetIPCAddress() - if err != nil { - return processExpvars{}, fmt.Errorf("config error: %s", err.Error()) - } - - port := ddconfig.Datadog.GetInt("process_config.expvar_port") - if port <= 0 { - _ = log.Warnf("Invalid process_config.expvar_port -- %d, using default port %d\n", port, ddconfig.DefaultProcessExpVarPort) - port = ddconfig.DefaultProcessExpVarPort - } - expvarEndpoint := fmt.Sprintf("http://%s:%d/debug/vars", ipcAddr, port) - b, err := util.DoGet(httpClient, expvarEndpoint) - if err != nil { - return s, connectionError{err} - } - - err = json.Unmarshal(b, &s) - return -} - -func getStatus() (status, error) { - coreStatus, err := getCoreStatus() - if err != nil { - return status{}, err - } - - processStatus, err := getExpvars() - if err != nil { - return status{}, err - } - - s := status{ - Date: float64(time.Now().UnixNano()), - Core: coreStatus, - Expvars: processStatus, - } - return s, nil -} - func writeNotRunning(w io.Writer) { _, err := fmt.Fprint(w, notRunning) if err != nil { @@ -227,34 +63,68 @@ func writeError(w io.Writer, e error) { _ = log.Error(err) } } +func fetchStatus(statusURL string) ([]byte, error) { + body, err := apiutil.DoGet(httpClient, statusURL) + if err != nil { + return nil, util.NewConnectionError(err) + } + + return body, nil +} // getAndWriteStatus calls the status server and writes it to `w` -func getAndWriteStatus(w io.Writer, options ...statusOption) { - status, err := getStatus() +func getAndWriteStatus(statusURL string, w io.Writer, options ...util.StatusOption) { + body, err := fetchStatus(statusURL) if err != nil { switch err.(type) { - case connectionError: + case util.ConnectionError: writeNotRunning(w) default: writeError(w, err) } return } - for _, option := range options { - option(&status) + + // If options to override the status are provided, we need to deserialize and serialize it again + if len(options) > 0 { + var s util.Status + err = json.Unmarshal(body, &s) + if err != nil { + writeError(w, err) + return + } + + for _, option := range options { + option(&s) + } + + body, err = json.Marshal(s) + if err != nil { + writeError(w, err) + return + } } - tpl, err := template.New("").Funcs(ddstatus.Textfmap()).Parse(statusTemplate) + stats, err := ddstatus.FormatProcessAgentStatus(body) if err != nil { - _ = log.Error(err) + writeError(w, err) + return } - err = tpl.Execute(w, status) + _, err = w.Write([]byte(stats)) if err != nil { _ = log.Error(err) } } +func getStatusURL() (string, error) { + addressPort, err := api.GetAPIAddressPort() + if err != nil { + return "", fmt.Errorf("config error: %s", err.Error()) + } + return fmt.Sprintf("http://%s/agent/status", addressPort), nil +} + // StatusCmd returns a cobra command that prints the current status func StatusCmd() *cobra.Command { return &cobra.Command{ @@ -269,6 +139,7 @@ func runStatus(cmd *cobra.Command, _ []string) error { err := config.LoadConfigIfExists(cmd.Flag("config").Value.String()) if err != nil { writeError(os.Stdout, err) + return err } err = ddconfig.SetupLogger( @@ -282,8 +153,15 @@ func runStatus(cmd *cobra.Command, _ []string) error { ) if err != nil { writeError(os.Stdout, err) + return err + } + + statusURL, err := getStatusURL() + if err != nil { + writeError(os.Stdout, err) + return err } - getAndWriteStatus(os.Stdout) + getAndWriteStatus(statusURL, os.Stdout) return nil } diff --git a/cmd/process-agent/app/status_test.go b/cmd/process-agent/app/status_test.go index cd6b9dfd8b1dcc..2d83e0087393d1 100644 --- a/cmd/process-agent/app/status_test.go +++ b/cmd/process-agent/app/status_test.go @@ -1,13 +1,11 @@ package app import ( - "context" "encoding/json" "fmt" - "net" "net/http" + "net/http/httptest" "strings" - "sync" "testing" "text/template" "time" @@ -15,115 +13,65 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/DataDog/datadog-agent/cmd/process-agent/api" "github.com/DataDog/datadog-agent/pkg/config" "github.com/DataDog/datadog-agent/pkg/metadata/host" + "github.com/DataDog/datadog-agent/pkg/process/util" ddstatus "github.com/DataDog/datadog-agent/pkg/status" ) -type statusServer struct { - shutdownWg *sync.WaitGroup - coreStatusServer, expvarsServer *http.Server -} - -func (s *statusServer) stop() error { - err := s.coreStatusServer.Shutdown(context.Background()) - if err != nil { - return err - } - - err = s.expvarsServer.Shutdown(context.Background()) - if err != nil { - return err - } - - s.shutdownWg.Wait() - return nil -} - -func startTestServer(t *testing.T, cfg config.Config, expectedStatus status) statusServer { - var serverWg sync.WaitGroup - serverWg.Add(2) - - statusMux := http.NewServeMux() - statusMux.HandleFunc("/agent/status", func(w http.ResponseWriter, _ *http.Request) { - b, err := json.Marshal(expectedStatus.Core) - require.NoError(t, err) - - _, err = w.Write(b) - require.NoError(t, err) - }) - statusEndpoint := fmt.Sprintf("localhost:%d", cfg.GetInt("process_config.cmd_port")) - coreStatusServer := http.Server{Addr: statusEndpoint, Handler: statusMux} - statusListener, err := net.Listen("tcp", statusEndpoint) - require.NoError(t, err) - go func() { - _ = coreStatusServer.Serve(statusListener) - serverWg.Done() - }() - - expvarMux := http.NewServeMux() - expvarMux.HandleFunc("/debug/vars", func(w http.ResponseWriter, _ *http.Request) { - b, err := json.Marshal(expectedStatus.Expvars) +func fakeStatusServer(t *testing.T, stats util.Status) *httptest.Server { + handler := func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + b, err := json.Marshal(stats) require.NoError(t, err) _, err = w.Write(b) require.NoError(t, err) - }) - expvarEndpoint := fmt.Sprintf("localhost:%d", cfg.GetInt("process_config.expvar_port")) - expvarsServer := http.Server{Addr: expvarEndpoint, Handler: expvarMux} - expvarsListener, err := net.Listen("tcp", expvarEndpoint) - require.NoError(t, err) - go func() { - _ = expvarsServer.Serve(expvarsListener) - serverWg.Done() - }() + } - return statusServer{coreStatusServer: &coreStatusServer, expvarsServer: &expvarsServer, shutdownWg: &serverWg} + return httptest.NewServer(http.HandlerFunc(handler)) } func TestStatus(t *testing.T) { testTime := time.Now() - expectedStatus := status{ + expectedStatus := util.Status{ Date: float64(testTime.UnixNano()), - Core: coreStatus{ + Core: util.CoreStatus{ Metadata: host.Payload{ Meta: &host.Meta{}, }, }, - Expvars: processExpvars{}, + Expvars: util.ProcessExpvars{}, } - // Use different ports in case the host is running a real agent - cfg := config.Mock() - cfg.Set("process_config.expvar_port", 8081) - cfg.Set("process_config.cmd_port", 8082) - server := startTestServer(t, cfg, expectedStatus) - - var statusBuilder, expectedStatusBuilder strings.Builder + server := fakeStatusServer(t, expectedStatus) + defer server.Close() // Build what the expected status should be - tpl, err := template.New("").Funcs(ddstatus.Textfmap()).Parse(statusTemplate) + j, err := json.Marshal(expectedStatus) require.NoError(t, err) - err = tpl.Execute(&expectedStatusBuilder, expectedStatus) + expectedOutput, err := ddstatus.FormatProcessAgentStatus(j) require.NoError(t, err) // Build the actual status - getAndWriteStatus(&statusBuilder, overrideTime(testTime)) - - assert.Equal(t, expectedStatusBuilder.String(), statusBuilder.String()) + var statusBuilder strings.Builder + getAndWriteStatus(server.URL, &statusBuilder, util.OverrideTime(testTime)) - err = server.stop() - require.NoError(t, err) + assert.Equal(t, expectedOutput, statusBuilder.String()) } func TestNotRunning(t *testing.T) { // Use different ports in case the host is running a real agent cfg := config.Mock() - cfg.Set("process_config.expvar_port", 8081) cfg.Set("process_config.cmd_port", 8082) + addressPort, err := api.GetAPIAddressPort() + require.NoError(t, err) + statusURL := fmt.Sprintf("http://%s/agent/status", addressPort) + var b strings.Builder - getAndWriteStatus(&b) + getAndWriteStatus(statusURL, &b) assert.Equal(t, notRunning, b.String()) } @@ -136,7 +84,9 @@ func TestError(t *testing.T) { _, ipcError := config.GetIPCAddress() var errText, expectedErrText strings.Builder - getAndWriteStatus(&errText) + url, err := getStatusURL() + assert.Equal(t, "", url) + writeError(&errText, err) tpl, err := template.New("").Parse(errorMessage) require.NoError(t, err) diff --git a/pkg/process/util/status.go b/pkg/process/util/status.go new file mode 100644 index 00000000000000..3a62d145e9cceb --- /dev/null +++ b/pkg/process/util/status.go @@ -0,0 +1,141 @@ +package util + +import ( + "context" + "encoding/json" + "runtime" + "time" + + apiutil "github.com/DataDog/datadog-agent/pkg/api/util" + ddconfig "github.com/DataDog/datadog-agent/pkg/config" + "github.com/DataDog/datadog-agent/pkg/metadata/host" + "github.com/DataDog/datadog-agent/pkg/util" + "github.com/DataDog/datadog-agent/pkg/util/log" + "github.com/DataDog/datadog-agent/pkg/version" +) + +// CoreStatus holds core info about the process-agent +type CoreStatus struct { + AgentVersion string `json:"version"` + GoVersion string `json:"go_version"` + Arch string `json:"build_arch"` + Config ConfigStatus `json:"config"` + Metadata host.Payload `json:"metadata"` +} + +// ConfigStatus holds config settings from process-agent +type ConfigStatus struct { + LogLevel string `json:"log_level"` +} + +// InfoVersion holds information about process-agent version +type InfoVersion struct { + Version string + GitCommit string + GitBranch string + BuildDate string + GoVersion string +} + +// MemInfo holds information about memory usage from process-agent +type MemInfo struct { + Alloc uint64 `json:"alloc"` +} + +// ProcessExpvars holds values fetched from the exp var server +type ProcessExpvars struct { + Pid int `json:"pid"` + Uptime int `json:"uptime"` + UptimeNano float64 `json:"uptime_nano"` + MemStats MemInfo `json:"memstats"` + Version InfoVersion `json:"version"` + DockerSocket string `json:"docker_socket"` + LastCollectTime string `json:"last_collect_time"` + ProcessCount int `json:"process_count"` + ContainerCount int `json:"container_count"` + ProcessQueueSize int `json:"process_queue_size"` + RTProcessQueueSize int `json:"rtprocess_queue_size"` + PodQueueSize int `json:"pod_queue_size"` + ProcessQueueBytes int `json:"process_queue_bytes"` + RTProcessQueueBytes int `json:"rtprocess_queue_bytes"` + PodQueueBytes int `json:"pod_queue_bytes"` + ContainerID string `json:"container_id"` + ProxyURL string `json:"proxy_url"` + LogFile string `json:"log_file"` + EnabledChecks []string `json:"enabled_checks"` + Endpoints map[string][]string `json:"endpoints"` +} + +// Status holds runtime information from process-agent +type Status struct { + Date float64 `json:"date"` + Core CoreStatus `json:"core"` // Contains fields that are collected similarly to the core agent in pkg/status + Expvars ProcessExpvars `json:"expvars"` // Contains the expvars retrieved from the process agent +} + +// StatusOption is a function that acts on a Status object +type StatusOption func(s *Status) + +// ConnectionError represents an error to connect to an HTTP server +type ConnectionError struct { + error +} + +// NewConnectionError returns a new ConnectionError +func NewConnectionError(err error) ConnectionError { + return ConnectionError{err} +} + +// OverrideTime overrides the Date from a Status object +func OverrideTime(t time.Time) StatusOption { + return func(s *Status) { + s.Date = float64(t.UnixNano()) + } +} + +func getCoreStatus() (s CoreStatus) { + hostnameData, err := util.GetHostnameData(context.Background()) + var metadata *host.Payload + if err != nil { + log.Errorf("Error grabbing hostname for status: %v", err) + metadata = host.GetPayloadFromCache(context.Background(), util.HostnameData{Hostname: "unknown", Provider: "unknown"}) + } else { + metadata = host.GetPayloadFromCache(context.Background(), hostnameData) + } + + return CoreStatus{ + AgentVersion: version.AgentVersion, + GoVersion: runtime.Version(), + Arch: runtime.GOARCH, + Config: ConfigStatus{ + LogLevel: ddconfig.Datadog.GetString("log_level"), + }, + Metadata: *metadata, + } +} + +func getExpvars(expVarURL string) (s ProcessExpvars, err error) { + httpClient := apiutil.GetClient(false) + b, err := apiutil.DoGet(httpClient, expVarURL) + if err != nil { + return s, ConnectionError{err} + } + + err = json.Unmarshal(b, &s) + return +} + +// GetStatus returns a Status object with runtime information about process-agent +func GetStatus(expVarURL string) (*Status, error) { + coreStatus := getCoreStatus() + processExpVars, err := getExpvars(expVarURL) + if err != nil { + return nil, err + } + + return &Status{ + Date: float64(time.Now().UnixNano()), + Core: coreStatus, + Expvars: processExpVars, + }, nil +} diff --git a/pkg/process/util/status_test.go b/pkg/process/util/status_test.go new file mode 100644 index 00000000000000..1f425f303ef9d8 --- /dev/null +++ b/pkg/process/util/status_test.go @@ -0,0 +1,96 @@ +package util + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ddconfig "github.com/DataDog/datadog-agent/pkg/config" + "github.com/DataDog/datadog-agent/pkg/metadata/host" + "github.com/DataDog/datadog-agent/pkg/util" + "github.com/DataDog/datadog-agent/pkg/version" +) + +func fakeExpVarServer(t *testing.T, expVars ProcessExpvars) *httptest.Server { + handler := func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + b, err := json.Marshal(expVars) + require.NoError(t, err) + + _, err = w.Write(b) + require.NoError(t, err) + } + + return httptest.NewServer(http.HandlerFunc(handler)) +} + +func TestGetStatus(t *testing.T) { + testTime := time.Now() + + expectedExpVars := ProcessExpvars{ + Pid: 1, + Uptime: time.Now().Add(-time.Hour).Nanosecond(), + EnabledChecks: []string{"process", "rtprocess"}, + MemStats: MemInfo{ + Alloc: 1234, + }, + Endpoints: map[string][]string{ + "https://process.datadoghq.com": { + "fakeAPIKey", + }, + }, + LastCollectTime: "2022-02-011 10:10:00", + DockerSocket: "/var/run/docker.sock", + ProcessCount: 30, + ContainerCount: 2, + ProcessQueueSize: 1, + RTProcessQueueSize: 3, + PodQueueSize: 4, + ProcessQueueBytes: 2 * 1024, + RTProcessQueueBytes: 512, + PodQueueBytes: 4 * 1024, + } + + // Feature detection needs to run before host methods are called. During runtime, feature detection happens + // when the datadog.yaml file is loaded + ddconfig.Mock() + ddconfig.DetectFeatures() + + hostnameData, err := util.GetHostnameData(context.Background()) + var metadata *host.Payload + if err != nil { + metadata = host.GetPayloadFromCache(context.Background(), util.HostnameData{Hostname: "unknown", Provider: "unknown"}) + } else { + metadata = host.GetPayloadFromCache(context.Background(), hostnameData) + } + + expectedStatus := &Status{ + Date: float64(testTime.UnixNano()), + Core: CoreStatus{ + AgentVersion: version.AgentVersion, + GoVersion: runtime.Version(), + Arch: runtime.GOARCH, + Config: ConfigStatus{ + LogLevel: ddconfig.Datadog.GetString("log_level"), + }, + Metadata: *metadata, + }, + Expvars: expectedExpVars, + } + + expVarSrv := fakeExpVarServer(t, expectedExpVars) + defer expVarSrv.Close() + + stats, err := GetStatus(expVarSrv.URL) + require.NoError(t, err) + + OverrideTime(testTime)(stats) + assert.Equal(t, expectedStatus, stats) +} diff --git a/pkg/status/render.go b/pkg/status/render.go index 7c43925c64d984..c81fc71b67f665 100644 --- a/pkg/status/render.go +++ b/pkg/status/render.go @@ -52,6 +52,7 @@ func FormatStatus(data []byte) (string, error) { endpointsInfos := stats["endpointsInfos"] inventoriesStats := stats["inventories"] systemProbeStats := stats["systemProbeStats"] + processAgentStatus := stats["processAgentStatus"] snmpTrapsStats := stats["snmpTrapsStats"] title := fmt.Sprintf("Agent (v%s)", stats["version"]) stats["title"] = title @@ -64,6 +65,7 @@ func FormatStatus(data []byte) (string, error) { if config.Datadog.GetBool("system_probe_config.enabled") { renderStatusTemplate(b, "/systemprobe.tmpl", systemProbeStats) } + renderStatusTemplate(b, "/process-agent.tmpl", processAgentStatus) renderStatusTemplate(b, "/trace-agent.tmpl", stats["apmStats"]) renderStatusTemplate(b, "/aggregator.tmpl", aggregatorStats) renderStatusTemplate(b, "/dogstatsd.tmpl", dogstatsdStats) @@ -137,6 +139,17 @@ func FormatSecurityAgentStatus(data []byte) (string, error) { return b.String(), nil } +// FormatProcessAgentStatus takes a json bytestring and prints out the formatted status for process-agent +func FormatProcessAgentStatus(data []byte) (string, error) { + var b = new(bytes.Buffer) + + stats := make(map[string]interface{}) + json.Unmarshal(data, &stats) //nolint:errcheck + renderStatusTemplate(b, "/process-agent.tmpl", stats) + + return b.String(), nil +} + // FormatMetadataMapCLI builds the rendering in the metadataMapper template. func FormatMetadataMapCLI(data []byte) (string, error) { var b = new(bytes.Buffer) diff --git a/pkg/status/status.go b/pkg/status/status.go index 278d785c8a144d..103fd7c8f67725 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -76,6 +76,8 @@ func GetStatus() (map[string]interface{}, error) { stats["systemProbeStats"] = GetSystemProbeStats(config.Datadog.GetString("system_probe_config.sysprobe_socket")) } + stats["processAgentStatus"] = GetProcessAgentStatus() + if !config.Datadog.GetBool("no_proxy_nonexact_match") { httputils.NoProxyMapMutex.Lock() stats["TransportWarnings"] = len(httputils.NoProxyIgnoredWarningMap)+len(httputils.NoProxyUsedInFuture)+len(httputils.NoProxyChanged) > 0 diff --git a/pkg/status/status_process_agent.go b/pkg/status/status_process_agent.go new file mode 100644 index 00000000000000..23e41b3ca4247d --- /dev/null +++ b/pkg/status/status_process_agent.go @@ -0,0 +1,41 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package status + +import ( + "encoding/json" + "fmt" + + "github.com/DataDog/datadog-agent/cmd/process-agent/api" + apiutil "github.com/DataDog/datadog-agent/pkg/api/util" +) + +// GetProcessAgentStatus fetches the process-agent status from the process-agent API server +func GetProcessAgentStatus() map[string]interface{} { + httpClient := apiutil.GetClient(false) + + s := make(map[string]interface{}) + addressPort, err := api.GetAPIAddressPort() + if err != nil { + s["error"] = fmt.Sprintf("%v", err.Error()) + return s + } + + statusEndpoint := fmt.Sprintf("http://%s/agent/status", addressPort) + b, err := apiutil.DoGet(httpClient, statusEndpoint) + if err != nil { + s["error"] = fmt.Sprintf("%v", err.Error()) + return s + } + + err = json.Unmarshal(b, &s) + if err != nil { + s["error"] = fmt.Sprintf("%v", err.Error()) + return s + } + + return s +} diff --git a/pkg/status/templates/process-agent.tmpl b/pkg/status/templates/process-agent.tmpl new file mode 100644 index 00000000000000..14ac0d3a684d77 --- /dev/null +++ b/pkg/status/templates/process-agent.tmpl @@ -0,0 +1,49 @@ +============= +Process Agent +============= +{{- if .error }} + + Status: Not running or unreachable +{{- else }} + + Version: {{ .core.version }} + Status date: {{ formatUnixTime .date }} + Process Agent Start: {{ formatUnixTime .expvars.uptime_nano }} + Pid: {{ .expvars.pid }} + Go Version: {{ .core.go_version }} + Build arch: {{ .core.build_arch }} + Log Level: {{ .core.config.log_level }} + Enabled Checks: {{ .expvars.enabled_checks }} + Allocated Memory: {{ .expvars.memstats.alloc }} bytes + Hostname: {{ .core.metadata.meta.hostname }} + + ================= + Process Endpoints + ================= + {{- with .expvars.endpoints}} + {{- range $key, $value := .}} + {{$key}} - API Key{{ if gt (len $value) 1}}s{{end}} ending with: + {{- range $idx, $apikey := $value }} + - {{$apikey}} + {{- end}} + {{- end}} + {{- else }} + + No endpoints information. The agent may be misconfigured. + {{- end }} + + ========= + Collector + ========= + Last collection time: {{.expvars.last_collect_time}} + Docker socket: {{.expvars.docker_socket}} + Number of processes: {{.expvars.process_count}} + Number of containers: {{.expvars.container_count}} + Process Queue length: {{.expvars.process_queue_size}} + RTProcess Queue length: {{.expvars.rtprocess_queue_size}} + Pod Queue length: {{.expvars.pod_queue_size}} + Process Bytes enqueued: {{.expvars.process_queue_bytes}} + RTProcess Bytes enqueued: {{.expvars.rtprocess_queue_bytes}} + Pod Bytes enqueued: {{.expvars.pod_queue_bytes}} +{{- end }} + diff --git a/releasenotes/notes/process-agent-add-status-to-core-agent-38d1cd109ab5bb0e.yaml b/releasenotes/notes/process-agent-add-status-to-core-agent-38d1cd109ab5bb0e.yaml new file mode 100644 index 00000000000000..9dc3600b9a083d --- /dev/null +++ b/releasenotes/notes/process-agent-add-status-to-core-agent-38d1cd109ab5bb0e.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add `process-agent status` output to the core Agent status command.