ecs-agent/introspection/server.go (122 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package introspection
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/pprof"
"strconv"
"time"
v1 "github.com/aws/amazon-ecs-agent/ecs-agent/introspection/v1"
"github.com/aws/amazon-ecs-agent/ecs-agent/introspection/v1/handlers"
"github.com/aws/amazon-ecs-agent/ecs-agent/metrics"
"github.com/aws/amazon-ecs-agent/ecs-agent/tmds/logging"
)
const (
Port = 51678
// 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
)
type rootResponse struct {
AvailableCommands []string
}
// Configuration for Introspection Server
type Config struct {
readTimeout time.Duration // http server read timeout
writeTimeout time.Duration // http server write timeout
enableRuntimeStats bool // enable profiling handlers
hideAgentVersion bool // if true, do not show Version in metadata
}
// Function type for updating Introspection Server config
type ConfigOpt func(*Config)
// Set if Introspection Server should accept profiling requests
func WithRuntimeStats(enableRuntimeStats bool) ConfigOpt {
return func(c *Config) {
c.enableRuntimeStats = enableRuntimeStats
}
}
// Set Introspection Server read timeout
func WithReadTimeout(readTimeout time.Duration) ConfigOpt {
return func(c *Config) {
c.readTimeout = readTimeout
}
}
// Set Introspection Server write timeout
func WithWriteTimeout(writeTimeout time.Duration) ConfigOpt {
return func(c *Config) {
c.writeTimeout = writeTimeout
}
}
// Set flag to hide the agent version. If true, the Version
// will be omitted from Agent Metadata responses.
func HideAgentVersion(isHidden bool) ConfigOpt {
return func(c *Config) {
c.hideAgentVersion = isHidden
}
}
// Create a new HTTP Introspection Server
func NewServer(agentState v1.AgentState, metricsFactory metrics.EntryFactory, options ...ConfigOpt) (*http.Server, error) {
config := new(Config)
for _, opt := range options {
opt(config)
}
return setup(agentState, metricsFactory, config)
}
func v1HandlersSetup(serverMux *http.ServeMux,
agentState v1.AgentState,
metricsFactory metrics.EntryFactory,
hideAgentVersion bool) {
serverMux.HandleFunc(handlers.V1AgentMetadataPath, handlers.AgentMetadataHandler(agentState, metricsFactory, hideAgentVersion))
serverMux.HandleFunc(handlers.V1TasksMetadataPath, handlers.TasksMetadataHandler(agentState, metricsFactory))
serverMux.HandleFunc(licensePath, licenseHandler(agentState, metricsFactory))
}
func pprofHandlerSetup(serverMux *http.ServeMux) {
serverMux.HandleFunc(pprofBasePath, pprofIndexHandler)
serverMux.HandleFunc(pprofCMDLinePath, pprofCmdlineHandler)
serverMux.HandleFunc(pprofProfilePath, pprofProfileHandler)
serverMux.HandleFunc(pprofSymbolPath, pprofSymbolHandler)
serverMux.HandleFunc(pprofTracePath, pprofTraceHandler)
}
func setup(
agentState v1.AgentState,
metricsFactory metrics.EntryFactory,
config *Config,
) (*http.Server, error) {
if agentState == nil {
return nil, errors.New("state cannot be nil")
}
if metricsFactory == nil {
return nil, errors.New("metrics factory cannot be nil")
}
paths := []string{handlers.V1AgentMetadataPath, handlers.V1TasksMetadataPath, licensePath}
if config.enableRuntimeStats {
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 {
return nil, fmt.Errorf("error marshaling JSON in introspection server setup: %s", err)
}
defaultHandler := func(w http.ResponseWriter, r *http.Request) {
w.Write(availableCommandResponse)
}
serveMux := http.NewServeMux()
serveMux.HandleFunc("/", defaultHandler)
v1HandlersSetup(serveMux, agentState, metricsFactory, config.hideAgentVersion)
wTimeout := config.writeTimeout
if config.enableRuntimeStats {
pprofHandlerSetup(serveMux)
wTimeout = writeTimeoutForPprof
}
loggingServeMux := http.NewServeMux()
loggingServeMux.Handle("/", logging.NewLoggingHandler(serveMux))
return &http.Server{
Addr: ":" + strconv.Itoa(Port),
Handler: panicHandler(loggingServeMux, metricsFactory),
ReadTimeout: config.readTimeout,
WriteTimeout: wTimeout,
}, nil
}