Skip to content

Commit

Permalink
Move introspection server to ecs-agent
Browse files Browse the repository at this point in the history
To make the Agent Introspection Server available to the Fargate agent,
the introspection server is now implemented in the shared ecs-agent
library. The ECS agent introspection server is refactored to use this
shared implementation.
  • Loading branch information
willmyrs committed Jan 10, 2025
1 parent c46c0f4 commit 0ea156b
Show file tree
Hide file tree
Showing 33 changed files with 3,573 additions and 1,023 deletions.
112 changes: 15 additions & 97 deletions agent/handlers/introspection_server_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,118 +16,36 @@ package handlers

import (
"context"
"encoding/json"
"net/http"
"net/http/pprof"
"strconv"
"time"

"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/engine"
handlersutils "github.com/aws/amazon-ecs-agent/agent/handlers/utils"
v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1"
logginghandler "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/logging"
"github.com/aws/amazon-ecs-agent/ecs-agent/introspection"
"github.com/aws/amazon-ecs-agent/ecs-agent/metrics"
"github.com/aws/amazon-ecs-agent/ecs-agent/utils/retry"
"github.com/cihub/seelog"
)

type rootResponse struct {
AvailableCommands []string
}

const (
// With pprof we need to increase the timeout so that there is enough time to do the profiling. Since the profiling
// time window for CPU is configurable in the request, this timeout effectively means the CPU profiling will be
// capped to 5 min.
writeTimeoutForPprof = time.Minute * 5
pprofBasePath = "/debug/pprof/"
pprofCMDLinePath = pprofBasePath + "cmdline"
pprofProfilePath = pprofBasePath + "profile"
pprofSymbolPath = pprofBasePath + "symbol"
pprofTracePath = pprofBasePath + "trace"
)

var (
// Injection points for testing
pprofIndexHandler = pprof.Index
pprofCmdlineHandler = pprof.Cmdline
pprofProfileHandler = pprof.Profile
pprofSymbolHandler = pprof.Symbol
pprofTraceHandler = pprof.Trace
)

func introspectionServerSetup(containerInstanceArn *string, taskEngine handlersutils.DockerStateResolver, cfg *config.Config) *http.Server {
paths := []string{v1.AgentMetadataPath, v1.TaskContainerMetadataPath, v1.LicensePath}

if cfg.EnableRuntimeStats.Enabled() {
paths = append(paths, pprofBasePath, pprofCMDLinePath, pprofProfilePath, pprofSymbolPath, pprofTracePath)
}

availableCommands := &rootResponse{paths}
// Autogenerated list of the above serverFunctions paths
availableCommandResponse, err := json.Marshal(&availableCommands)
if err != nil {
seelog.Errorf("Error marshaling JSON in introspection server setup: %s", err)
}

defaultHandler := func(w http.ResponseWriter, r *http.Request) {
w.Write(availableCommandResponse)
}

serverMux := http.NewServeMux()
serverMux.HandleFunc("/", defaultHandler)

v1HandlersSetup(serverMux, containerInstanceArn, taskEngine, cfg)
pprofHandlerSetup(serverMux, cfg)

// Log all requests and then pass through to serverMux
loggingServeMux := http.NewServeMux()
loggingServeMux.Handle("/", logginghandler.NewLoggingHandler(serverMux))

wTimeout := writeTimeout
if cfg.EnableRuntimeStats.Enabled() {
wTimeout = writeTimeoutForPprof
}
server := &http.Server{
Addr: ":" + strconv.Itoa(config.AgentIntrospectionPort),
Handler: loggingServeMux,
ReadTimeout: readTimeout,
WriteTimeout: wTimeout,
}

return server
}

// v1HandlersSetup adds all handlers except CredentialsHandler in v1 package to the server mux.
func v1HandlersSetup(serverMux *http.ServeMux,
containerInstanceArn *string,
taskEngine handlersutils.DockerStateResolver,
cfg *config.Config) {
serverMux.HandleFunc(v1.AgentMetadataPath, v1.AgentMetadataHandler(containerInstanceArn, cfg))
serverMux.HandleFunc(v1.TaskContainerMetadataPath, v1.TaskContainerMetadataHandler(taskEngine))
serverMux.HandleFunc(v1.LicensePath, v1.LicenseHandler)
}

func pprofHandlerSetup(serverMux *http.ServeMux, cfg *config.Config) {
if !cfg.EnableRuntimeStats.Enabled() {
return
}
serverMux.HandleFunc(pprofBasePath, pprofIndexHandler)
serverMux.HandleFunc(pprofCMDLinePath, pprofCmdlineHandler)
serverMux.HandleFunc(pprofProfilePath, pprofProfileHandler)
serverMux.HandleFunc(pprofSymbolPath, pprofSymbolHandler)
serverMux.HandleFunc(pprofTracePath, pprofTraceHandler)
}

// ServeIntrospectionHTTPEndpoint serves information about this agent/containerInstance and tasks
// running on it. "V1" here indicates the hostname version of this server instead
// of the handler versions, i.e. "V1" server can include "V1" and "V2" handlers.
// ServeIntrospectionHTTPEndpoint serves information about this agent/containerInstance and tasks running on it.
func ServeIntrospectionHTTPEndpoint(ctx context.Context, containerInstanceArn *string, taskEngine engine.TaskEngine, cfg *config.Config) {
// Is this the right level to type assert, assuming we'd abstract multiple taskengines here?
// Revisit if we ever add another type..
dockerTaskEngine := taskEngine.(*engine.DockerTaskEngine)
agentState := &v1.AgentStateImpl{
ContainerInstanceArn: containerInstanceArn,
ClusterName: cfg.Cluster,
TaskEngine: dockerTaskEngine,
}

server := introspectionServerSetup(containerInstanceArn, dockerTaskEngine, cfg)
server, _ := introspection.NewServer(
agentState,
metrics.NewNopEntryFactory(),
introspection.WithReadTimeout(readTimeout),
introspection.WithWriteTimeout(writeTimeout),
introspection.WithRuntimeStats(cfg.EnableRuntimeStats.Enabled()),
)

go func() {
<-ctx.Done()
Expand Down
Loading

0 comments on commit 0ea156b

Please sign in to comment.