Skip to content

Commit

Permalink
[AWS Fargate] Add memory hard limit from container metadata and remov…
Browse files Browse the repository at this point in the history
…e usage percentage (#37194)
  • Loading branch information
lucian-ioan authored Dec 5, 2023
1 parent 76919e0 commit 16b713b
Show file tree
Hide file tree
Showing 12 changed files with 68 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
- Fix memory leak on Windows {issue}37142[37142] {pull}37171[37171]
- Fix unintended skip in metric collection on Azure Monitor {issue}37204[37204] {pull}37203[37203]
- Fix the "api-version query parameter (?api-version=) is required for all requests" error in Azure Billing. {pull}37158[37158]
- Add memory hard limit from container metadata and remove usage percentage in AWS Fargate. {pull}37194[37194]

*Osquerybeat*

Expand Down
22 changes: 10 additions & 12 deletions metricbeat/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -4986,6 +4986,16 @@ type: keyword

--

*`awsfargate.task_stats.memory_hard_limit`*::
+
--
The Hard Memory Limit for the task from Amazon ECS.


type: scaled_float

--

[float]
=== cpu

Expand Down Expand Up @@ -5501,18 +5511,6 @@ format: bytes

--

*`awsfargate.task_stats.memory.usage.pct`*::
+
--
Memory usage percentage.


type: scaled_float

format: percent

--

*`awsfargate.task_stats.memory.usage.total`*::
+
--
Expand Down
2 changes: 1 addition & 1 deletion x-pack/metricbeat/module/awsfargate/fields.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@
},
"usage": {
"max": 15294464,
"pct": 0.003136136404770672,
"total": 12349440
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
type: keyword
description: >
The known status for the task from Amazon ECS.
- name: memory_hard_limit
type: scaled_float
description: >
The Hard Memory Limit for the task from Amazon ECS.
- name: cpu
type: group
description: Runtime CPU metrics.
Expand Down Expand Up @@ -251,11 +255,6 @@
format: bytes
description: >
Max memory usage.
- name: pct
type: scaled_float
format: percent
description: >
Memory usage percentage.
- name: total
type: long
format: bytes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"Revision": "7",
"DesiredStatus": "RUNNING",
"KnownStatus": "ACTIVATING",
"Limits": {
"Memory": 7168
},
"Containers": [{
"DockerId": "1234",
"Name": "query-metadata",
Expand All @@ -14,6 +17,9 @@
"com.amazonaws.ecs.container-name": "query-metadata",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/febee046097849aba589d4435207c04a",
"com.amazonaws.ecs.task-definition-family": "query-metadata",
"com.amazonaws.ecs.task-definition-version": "7"}
"com.amazonaws.ecs.task-definition-version": "7"},
"Limits": {
"Memory": 3328
}
}]
}
}
2 changes: 2 additions & 0 deletions x-pack/metricbeat/module/awsfargate/task_stats/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type container struct {
Name string
Image string
Labels map[string]string
Limits Limits
}

func getContainerMetadata(c *container) *container {
Expand All @@ -23,6 +24,7 @@ func getContainerMetadata(c *container) *container {
Image: c.Image,
Name: helpers.ExtractContainerName([]string{c.Name}),
Labels: deDotLabels(c.Labels),
Limits: c.Limits,
}
}

Expand Down
6 changes: 5 additions & 1 deletion x-pack/metricbeat/module/awsfargate/task_stats/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func createEvent(stats *Stats) mb.Event {
_, _ = e.MetricSetFields.Put("task_known_status", taskKnownStatus)
}

memoryHardLimit := stats.Container.Limits.Memory
if memoryHardLimit != 0 {
_, _ = e.MetricSetFields.Put("memory_hard_limit", memoryHardLimit)
}

_, _ = e.MetricSetFields.Put("identifier", generateIdentifier(stats.Container.Name, stats.Container.DockerId))
return e
}
Expand Down Expand Up @@ -163,7 +168,6 @@ func createMemoryFields(stats *Stats) mapstr.M {
},
"usage": mapstr.M{
"total": stats.memoryStats.Usage,
"pct": stats.memoryStats.UsageP,
"max": stats.memoryStats.MaxUsage,
},
}
Expand Down
2 changes: 0 additions & 2 deletions x-pack/metricbeat/module/awsfargate/task_stats/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type memoryStats struct {
TotalRss uint64
TotalRssP float64
Usage uint64
UsageP float64
//Raw stats from the cgroup subsystem
Stats map[string]uint64
//Windows-only memory stats
Expand All @@ -30,7 +29,6 @@ func getMemoryStats(taskStats types.StatsJSON) memoryStats {
MaxUsage: taskStats.Stats.MemoryStats.MaxUsage,
TotalRssP: float64(totalRSS) / float64(taskStats.Stats.MemoryStats.Limit),
Usage: taskStats.Stats.MemoryStats.Usage,
UsageP: float64(taskStats.Stats.MemoryStats.Usage) / float64(taskStats.Stats.MemoryStats.Limit),
Stats: taskStats.Stats.MemoryStats.Stats,
//Windows memory statistics
Commit: taskStats.Stats.MemoryStats.Commit,
Expand Down
10 changes: 10 additions & 0 deletions x-pack/metricbeat/module/awsfargate/task_stats/task_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,15 @@ type TaskMetadata struct {
Revision string `json:"Revision"`
DesiredStatus string `json:"DesiredStatus"`
KnownStatus string `json:"KnownStatus"`
Limit Limits `json:"Limits"`
Containers []*container `json:"Containers"`
}

// Limits is a struct that represents the memory limit from ${ECS_CONTAINER_METADATA_URI_V4}/task, which is the Hard Memory Limit set in AWS ECS
type Limits struct {
Memory uint64 `json:"Memory"`
}

// New creates a new instance of the MetricSet. New is responsible for unpacking
// any MetricSet specific configuration options if there are any.
func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
Expand Down Expand Up @@ -131,6 +137,8 @@ func (m *MetricSet) queryTaskMetadataEndpoints() ([]Stats, error) {
if err != nil {
return nil, fmt.Errorf("http.Get failed: %w", err)
}
defer taskStatsResp.Body.Close()

taskStatsOutput, err := getTaskStats(taskStatsResp)
if err != nil {
return nil, fmt.Errorf("getTaskStats failed: %w", err)
Expand All @@ -145,6 +153,8 @@ func (m *MetricSet) queryTaskMetadataEndpoints() ([]Stats, error) {
if err != nil {
return nil, fmt.Errorf("http.Get failed: %w", err)
}
defer taskResp.Body.Close()

taskOutput, err := getTask(taskResp)
if err != nil {
return nil, fmt.Errorf("getTask failed: %w", err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func TestFetch(t *testing.T) {
// The goal here is to make sure every element inside the
// event has a matching field ("no field left behind").
mbtest.TestMetricsetFieldsDocumented(t, metricSet, []mb.Event{event})

t.Cleanup(func() {
taskStatsResp.Body.Close()
byteTaskResp.Body.Close()
})
}

func TestData(t *testing.T) {
Expand Down Expand Up @@ -85,6 +90,11 @@ func TestData(t *testing.T) {
standardizeEvent := m.StandardizeEvent(event)

mbtest.WriteEventToDataJSON(t, standardizeEvent, "")

t.Cleanup(func() {
taskStatsResp.Body.Close()
byteTaskResp.Body.Close()
})
}

// buildResponse is a test helper that loads the content of `filename` and returns
Expand Down
26 changes: 17 additions & 9 deletions x-pack/metricbeat/module/awsfargate/task_stats/task_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package task_stats

import (
"bytes"
"io/ioutil"
"io"
"net/http"
"testing"

Expand Down Expand Up @@ -48,6 +48,9 @@ var (
"Revision": "7",
"DesiredStatus": "RUNNING",
"KnownStatus": "ACTIVATING",
"Limits": {
"Memory": 7168
},
"Containers": [{
"DockerId": "query-metadata-1",
"Name": "query-metadata",
Expand All @@ -57,14 +60,17 @@ var (
"com.amazonaws.ecs.container-name": "query-metadata",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/febee046097849aba589d4435207c04a",
"com.amazonaws.ecs.task-definition-family": "query-metadata",
"com.amazonaws.ecs.task-definition-version": "7"}
}]
}`
"com.amazonaws.ecs.task-definition-version": "7"},
"Limits": {
"Memory": 3328
}
}]
}`
)

func TestGetTaskStats(t *testing.T) {
taskStatsResp := &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte(taskStatsJson))),
Body: io.NopCloser(bytes.NewReader([]byte(taskStatsJson))),
}

taskStatsOutput, err := getTaskStats(taskStatsResp)
Expand All @@ -74,7 +80,7 @@ func TestGetTaskStats(t *testing.T) {

func TestGetTask(t *testing.T) {
taskResp := &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte(taskRespJson))),
Body: io.NopCloser(bytes.NewReader([]byte(taskRespJson))),
}

taskOutput, err := getTask(taskResp)
Expand All @@ -87,23 +93,25 @@ func TestGetTask(t *testing.T) {
assert.Equal(t, "RUNNING", taskOutput.DesiredStatus)
assert.Equal(t, "ACTIVATING", taskOutput.KnownStatus)

assert.Equal(t, uint64(7168), taskOutput.Limit.Memory)
assert.Equal(t, 1, len(taskOutput.Containers))
assert.Equal(t, "query-metadata-1", taskOutput.Containers[0].DockerId)
assert.Equal(t, "query-metadata", taskOutput.Containers[0].Name)
assert.Equal(t, "mreferre/eksutils", taskOutput.Containers[0].Image)
assert.Equal(t, 5, len(taskOutput.Containers[0].Labels))
assert.Equal(t, uint64(3328), taskOutput.Containers[0].Limits.Memory)
}

func TestGetStatsList(t *testing.T) {
taskStatsResp := &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte(taskStatsJson))),
Body: io.NopCloser(bytes.NewReader([]byte(taskStatsJson))),
}

taskStatsOutput, err := getTaskStats(taskStatsResp)
assert.NoError(t, err)

taskResp := &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte(taskRespJson))),
Body: io.NopCloser(bytes.NewReader([]byte(taskRespJson))),
}

taskOutput, err := getTask(taskResp)
Expand All @@ -115,7 +123,7 @@ func TestGetStatsList(t *testing.T) {

func TestGetCPUStats(t *testing.T) {
taskStatsResp := &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte(taskStatsJson))),
Body: io.NopCloser(bytes.NewReader([]byte(taskStatsJson))),
}

taskStatsOutput, err := getTaskStats(taskStatsResp)
Expand Down

0 comments on commit 16b713b

Please sign in to comment.