Skip to content

Commit

Permalink
emit container cpu/memory/disk metrics periodically
Browse files Browse the repository at this point in the history
[#76563380]

Signed-off-by: Ashvin Agrawal <ashvin@pivotal.io>
  • Loading branch information
vito authored and Ashvin Agrawal committed Feb 5, 2015
1 parent d7a0142 commit 3c09c53
Show file tree
Hide file tree
Showing 7 changed files with 552 additions and 38 deletions.
11 changes: 9 additions & 2 deletions apiresources.go
Expand Up @@ -41,7 +41,9 @@ type Container struct {
RootFSPath string `json:"rootfs"`
ExternalIP string `json:"external_ip"`
Ports []PortMapping `json:"ports"`
Log LogConfig `json:"log"`

Log LogConfig `json:"log"`
MetricsConfig MetricsConfig `json:"metrics_config"`

StartTimeout uint `json:"start_timeout"`
Setup models.Action `json:"setup"`
Expand Down Expand Up @@ -157,10 +159,15 @@ type EnvironmentVariable struct {
Value string `json:"value"`
}

type MetricsConfig struct {
Guid string `json:"guid"`
Index *int `json:"index"`
}

type LogConfig struct {
Guid string `json:"guid"`
SourceName string `json:"source_name"`
Index *int `json:"index"`
SourceName string `json:"source_name"`
}

type PortMapping struct {
Expand Down
21 changes: 15 additions & 6 deletions cmd/executor/main.go
Expand Up @@ -25,6 +25,7 @@ import (

cf_debug_server "github.com/cloudfoundry-incubator/cf-debug-server"
"github.com/cloudfoundry-incubator/executor/cmd/executor/configuration"
"github.com/cloudfoundry-incubator/executor/containermetrics"

"github.com/cloudfoundry-incubator/executor/depot/transformer"
"github.com/cloudfoundry-incubator/executor/depot/uploader"
Expand Down Expand Up @@ -139,12 +140,13 @@ var exportNetworkEnvVars = flag.Bool(
)

const (
containerOwnerName = "executor"
dropsondeDestination = "localhost:3457"
dropsondeOrigin = "executor"
maxConcurrentDownloads = 5
maxConcurrentUploads = 5
metricsReportInterval = 1 * time.Minute
containerOwnerName = "executor"
dropsondeDestination = "localhost:3457"
dropsondeOrigin = "executor"
maxConcurrentDownloads = 5
maxConcurrentUploads = 5
metricsReportInterval = 1 * time.Minute
containerMetricsReportInterval = 30 * time.Second
)

func main() {
Expand Down Expand Up @@ -211,6 +213,7 @@ func main() {
)

metricsLogger := logger.Session("metrics-reporter")
containerMetricsLogger := logger.Session("container-metrics-reporter")

members := grouper.Members{
{"api-server", &server.Server{
Expand All @@ -225,6 +228,12 @@ func main() {
}},
{"hub-closer", closeHub(hub)},
{"registry-pruner", allocationStore.RegistryPruner(logger, *registryPruningInterval)},
{"container-metrics-reporter", containermetrics.NewStatsReporter(
containerMetricsLogger,
containerMetricsReportInterval,
clock,
depotClientProvider.WithLogger(containerMetricsLogger),
)},
}

if dbgAddr := cf_debug_server.DebugAddress(flag.CommandLine); dbgAddr != "" {
Expand Down
13 changes: 13 additions & 0 deletions containermetrics/metrics_suite_test.go
@@ -0,0 +1,13 @@
package containermetrics_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestContainerMetrics(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "ContainerMetrics Suite")
}
124 changes: 124 additions & 0 deletions containermetrics/stats_reporter.go
@@ -0,0 +1,124 @@
package containermetrics

import (
"os"
"time"

"github.com/cloudfoundry-incubator/executor"
"github.com/cloudfoundry/dropsonde/metrics"
"github.com/pivotal-golang/clock"
"github.com/pivotal-golang/lager"
)

type StatsReporter struct {
logger lager.Logger

interval time.Duration
clock clock.Clock
executorClient executor.Client

cpuInfos map[string]cpuInfo
}

type cpuInfo struct {
timeSpentInCPU time.Duration
timeOfSample time.Time
}

func NewStatsReporter(logger lager.Logger, interval time.Duration, clock clock.Clock, executorClient executor.Client) *StatsReporter {
return &StatsReporter{
logger: logger,

interval: interval,
clock: clock,
executorClient: executorClient,

cpuInfos: make(map[string]cpuInfo),
}
}

func (reporter *StatsReporter) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
close(ready)

ticker := reporter.clock.NewTicker(reporter.interval)

for {
select {
case <-signals:
return nil

case <-ticker.C():
reporter.emitContainerMetrics(reporter.logger.Session("tick"))
}
}

return nil
}

func (reporter *StatsReporter) emitContainerMetrics(logger lager.Logger) {
startTime := reporter.clock.Now()

logger.Info("started")
defer func() {
logger.Info("done", lager.Data{"took": reporter.clock.Now().Sub(startTime)})
}()

containers, err := reporter.executorClient.ListContainers(nil)
if err != nil {
logger.Error("failed-to-list-containers", err)
return
}

logger.Info("emitting", lager.Data{
"total-containers": len(containers),
"listing-containers-took": reporter.clock.Now().Sub(startTime),
})

for _, container := range containers {
if container.MetricsConfig.Guid == "" {
continue
}

currentInfo := cpuInfo{
timeSpentInCPU: container.TimeSpentInCPU,
timeOfSample: reporter.clock.Now(),
}

previousInfo, found := reporter.cpuInfos[container.Guid]

reporter.cpuInfos[container.Guid] = currentInfo

var cpuPercent float64
if !found {
cpuPercent = 0.0
} else {
cpuPercent = computeCPUPercent(
previousInfo.timeSpentInCPU,
currentInfo.timeSpentInCPU,
previousInfo.timeOfSample,
currentInfo.timeOfSample,
)
}

var index int32
if container.MetricsConfig.Index != nil {
index = int32(*container.MetricsConfig.Index)
} else {
index = -1
}

err = metrics.SendContainerMetric(container.MetricsConfig.Guid, index, cpuPercent, container.MemoryUsageInBytes, container.DiskUsageInBytes)
if err != nil {
logger.Error("failed-to-send-container-metrics", err)
}
}
}

// scale from 0 - 100
func computeCPUPercent(timeSpentA, timeSpentB time.Duration, sampleTimeA, sampleTimeB time.Time) float64 {
// divide change in time spent in CPU over time between samples.
// result is out of 100.
//
// don't worry about overflowing int64. it's like, 30 years.
return float64((timeSpentB-timeSpentA)*100) / float64(sampleTimeB.UnixNano()-sampleTimeA.UnixNano())
}

0 comments on commit 3c09c53

Please sign in to comment.