From 181b83a91f6ec12b48276194fef8d37bc7d1b442 Mon Sep 17 00:00:00 2001 From: Michael Katsoulis Date: Mon, 17 Jan 2022 14:30:44 +0200 Subject: [PATCH] Containerd metricbeat module (#29247) * Create cpu, memory, blkio metricset of metricbeat containerd module --- CHANGELOG.next.asciidoc | 1 + metricbeat/docs/fields.asciidoc | 454 ++++++++++++++++++ metricbeat/docs/modules/containerd.asciidoc | 91 ++++ .../docs/modules/containerd/blkio.asciidoc | 25 + .../docs/modules/containerd/cpu.asciidoc | 25 + .../docs/modules/containerd/memory.asciidoc | 25 + metricbeat/docs/modules_list.asciidoc | 5 + x-pack/metricbeat/include/list.go | 4 + x-pack/metricbeat/metricbeat.reference.yml | 13 + .../module/containerd/_meta/config.yml | 11 + .../module/containerd/_meta/docs.asciidoc | 38 ++ .../module/containerd/_meta/fields.yml | 15 + .../containerd/_meta/test/containerd.v1.5.2 | 99 ++++ .../module/containerd/blkio/_meta/data.json | 35 ++ .../containerd/blkio/_meta/docs.asciidoc | 1 + .../module/containerd/blkio/_meta/fields.yml | 49 ++ .../_meta/test/containerd.v1.5.2.expected | 37 ++ .../module/containerd/blkio/blkio.go | 129 +++++ .../module/containerd/blkio/blkio_test.go | 42 ++ x-pack/metricbeat/module/containerd/config.go | 19 + .../module/containerd/containerd.go | 116 +++++ .../module/containerd/cpu/_meta/data.json | 38 ++ .../module/containerd/cpu/_meta/docs.asciidoc | 1 + .../module/containerd/cpu/_meta/fields.yml | 54 +++ .../cpu/_meta/test/containerd.v1.5.2.expected | 398 +++++++++++++++ .../containerd/cpu/_meta/testdata/config.yml | 3 + .../containerd/cpu/_meta/testdata/docs.plain | 99 ++++ .../_meta/testdata/docs.plain-expected.json | 411 ++++++++++++++++ .../metricbeat/module/containerd/cpu/cpu.go | 235 +++++++++ .../module/containerd/cpu/cpu_test.go | 31 ++ x-pack/metricbeat/module/containerd/doc.go | 6 + x-pack/metricbeat/module/containerd/fields.go | 23 + x-pack/metricbeat/module/containerd/helper.go | 25 + .../module/containerd/memory/_meta/data.json | 51 ++ .../containerd/memory/_meta/docs.asciidoc | 1 + .../module/containerd/memory/_meta/fields.yml | 109 +++++ .../_meta/test/containerd.v1.5.2.expected | 56 +++ .../module/containerd/memory/memory.go | 167 +++++++ .../module/containerd/memory/memory_test.go | 26 + .../modules.d/containerd.yml.disabled | 14 + 40 files changed, 2982 insertions(+) create mode 100644 metricbeat/docs/modules/containerd.asciidoc create mode 100644 metricbeat/docs/modules/containerd/blkio.asciidoc create mode 100644 metricbeat/docs/modules/containerd/cpu.asciidoc create mode 100644 metricbeat/docs/modules/containerd/memory.asciidoc create mode 100644 x-pack/metricbeat/module/containerd/_meta/config.yml create mode 100644 x-pack/metricbeat/module/containerd/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/containerd/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/containerd/_meta/test/containerd.v1.5.2 create mode 100644 x-pack/metricbeat/module/containerd/blkio/_meta/data.json create mode 100644 x-pack/metricbeat/module/containerd/blkio/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/containerd/blkio/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/containerd/blkio/_meta/test/containerd.v1.5.2.expected create mode 100644 x-pack/metricbeat/module/containerd/blkio/blkio.go create mode 100644 x-pack/metricbeat/module/containerd/blkio/blkio_test.go create mode 100644 x-pack/metricbeat/module/containerd/config.go create mode 100644 x-pack/metricbeat/module/containerd/containerd.go create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/data.json create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/test/containerd.v1.5.2.expected create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/testdata/config.yml create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain create mode 100644 x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain-expected.json create mode 100644 x-pack/metricbeat/module/containerd/cpu/cpu.go create mode 100644 x-pack/metricbeat/module/containerd/cpu/cpu_test.go create mode 100644 x-pack/metricbeat/module/containerd/doc.go create mode 100644 x-pack/metricbeat/module/containerd/fields.go create mode 100644 x-pack/metricbeat/module/containerd/helper.go create mode 100644 x-pack/metricbeat/module/containerd/memory/_meta/data.json create mode 100644 x-pack/metricbeat/module/containerd/memory/_meta/docs.asciidoc create mode 100644 x-pack/metricbeat/module/containerd/memory/_meta/fields.yml create mode 100644 x-pack/metricbeat/module/containerd/memory/_meta/test/containerd.v1.5.2.expected create mode 100644 x-pack/metricbeat/module/containerd/memory/memory.go create mode 100644 x-pack/metricbeat/module/containerd/memory/memory_test.go create mode 100644 x-pack/metricbeat/modules.d/containerd.yml.disabled diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 856c46b10c5..3e4267ddd65 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -174,6 +174,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Preliminary AIX support {pull}27954[27954] - Add option to skip older k8s events {pull}29396[29396] - Add `add_resource_metadata` configuration to Kubernetes module. {pull}29133[29133] +- Add `containerd` module with `cpu`, `memory`, `blkio` metricsets. {pull}29247[29247] - Add `container.id` and `container.runtime` ECS fields in container metricset. {pull}29560[29560] - Add `memory.workingset.limit.pct` field in Kubernetes container/pod metricset. {pull}29547[29547] - Add k8s metadata in state_cronjob metricset. {pull}29572[29572] diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 9f265eaafd9..d1b6ece6ffc 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -27,6 +27,7 @@ grouped in the following categories: * <> * <> * <> +* <> * <> * <> * <> @@ -10267,6 +10268,459 @@ type: long -- +[[exported-fields-containerd]] +== Containerd fields + +Containerd stats collected from containerd + + + +[float] +=== containerd + +Information and statistics about containerd's running containers. + + + +*`containerd.namespace`*:: ++ +-- +Containerd namespace + + +type: keyword + +-- + +[float] +=== blkio + +Block I/O metrics. + + + +[float] +=== read + +Accumulated reads during the life of the container + + + +*`containerd.blkio.read.ops`*:: ++ +-- +Number of reads during the life of the container + + +type: long + +-- + +*`containerd.blkio.read.bytes`*:: ++ +-- +Bytes read during the life of the container + + +type: long + +format: bytes + +-- + +[float] +=== write + +Accumulated writes during the life of the container + + + +*`containerd.blkio.write.ops`*:: ++ +-- +Number of writes during the life of the container + + +type: long + +-- + +*`containerd.blkio.write.bytes`*:: ++ +-- +Bytes written during the life of the container + + +type: long + +format: bytes + +-- + +[float] +=== summary + +Accumulated reads and writes during the life of the container + + + +*`containerd.blkio.summary.ops`*:: ++ +-- +Number of I/O operations during the life of the container + + +type: long + +-- + +*`containerd.blkio.summary.bytes`*:: ++ +-- +Bytes read and written during the life of the container + + +type: long + +format: bytes + +-- + +[float] +=== cpu + +Containerd Runtime CPU metrics. + + + +*`containerd.cpu.system.total`*:: ++ +-- +Total user and system CPU time spent in seconds. + + +type: double + +-- + + + +*`containerd.cpu.usage.kernel.ns`*:: ++ +-- +CPU Kernel usage nanoseconds + + +type: double + +-- + + +*`containerd.cpu.usage.user.ns`*:: ++ +-- +CPU User usage nanoseconds + + +type: double + +-- + + +*`containerd.cpu.usage.total.ns`*:: ++ +-- +CPU total usage nanoseconds + + +type: double + +-- + +*`containerd.cpu.usage.total.pct`*:: ++ +-- +Percentage of total CPU time normalized by the number of CPU cores + + +type: scaled_float + +format: percent + +-- + +*`containerd.cpu.usage.kernel.pct`*:: ++ +-- +Percentage of time in kernel space normalized by the number of CPU cores. + + +type: scaled_float + +format: percent + +-- + +*`containerd.cpu.usage.user.pct`*:: ++ +-- +Percentage of time in user space normalized by the number of CPU cores. + + +type: scaled_float + +format: percent + +-- + +*`containerd.cpu.usage.cpu.*.ns`*:: ++ +-- +CPU usage nanoseconds in this cpu. + + +type: object + +-- + +[float] +=== memory + +memory + + + +*`containerd.memory.workingset.pct`*:: ++ +-- +Memory working set percentage. + + +type: scaled_float + +format: percent + +-- + +*`containerd.memory.rss`*:: ++ +-- +Total memory resident set size. + + +type: long + +format: bytes + +-- + +*`containerd.memory.activeFiles`*:: ++ +-- +Total active file bytes. + + +type: long + +format: bytes + +-- + +*`containerd.memory.cache`*:: ++ +-- +Total cache bytes. + + +type: long + +format: bytes + +-- + +*`containerd.memory.inactiveFiles`*:: ++ +-- +Total inactive file bytes. + + +type: long + +format: bytes + +-- + +[float] +=== usage + +Usage memory stats. + + + +*`containerd.memory.usage.max`*:: ++ +-- +Max memory usage. + + +type: long + +format: bytes + +-- + +*`containerd.memory.usage.pct`*:: ++ +-- +Total allocated memory percentage. + + +type: scaled_float + +format: percent + +-- + +*`containerd.memory.usage.total`*:: ++ +-- +Total memory usage. + + +type: long + +format: bytes + +-- + +*`containerd.memory.usage.fail.count`*:: ++ +-- +Fail counter. + + +type: scaled_float + +-- + +*`containerd.memory.usage.limit`*:: ++ +-- +Memory usage limit. + + +type: long + +format: bytes + +-- + +[float] +=== kernel + +Kernel memory stats. + + + +*`containerd.memory.kernel.max`*:: ++ +-- +Kernel max memory usage. + + +type: long + +format: bytes + +-- + +*`containerd.memory.kernel.total`*:: ++ +-- +Kernel total memory usage. + + +type: long + +format: bytes + +-- + +*`containerd.memory.kernel.fail.count`*:: ++ +-- +Kernel fail counter. + + +type: scaled_float + +-- + +*`containerd.memory.kernel.limit`*:: ++ +-- +Kernel memory limit. + + +type: long + +format: bytes + +-- + +[float] +=== swap + +Swap memory stats. + + + +*`containerd.memory.swap.max`*:: ++ +-- +Swap max memory usage. + + +type: long + +format: bytes + +-- + +*`containerd.memory.swap.total`*:: ++ +-- +Swap total memory usage. + + +type: long + +format: bytes + +-- + +*`containerd.memory.swap.fail.count`*:: ++ +-- +Swap fail counter. + + +type: scaled_float + +-- + +*`containerd.memory.swap.limit`*:: ++ +-- +Swap memory limit. + + +type: long + +format: bytes + +-- + [[exported-fields-coredns]] == Coredns fields diff --git a/metricbeat/docs/modules/containerd.asciidoc b/metricbeat/docs/modules/containerd.asciidoc new file mode 100644 index 00000000000..41c600b202b --- /dev/null +++ b/metricbeat/docs/modules/containerd.asciidoc @@ -0,0 +1,91 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +:modulename: containerd + +[[metricbeat-module-containerd]] +[role="xpack"] +== Containerd module + +beta[] + +include::{libbeat-dir}/shared/integration-link.asciidoc[] + +:modulename!: + +Containerd module collects cpu, memory and blkio statistics about +running containers controlled by containerd runtime. + +The current metricsets are: `cpu`, `blkio` and `memory` and are enabled by default. + +[float] +=== Prerequisites +`Containerd` daemon has to be configured to provide metrics before enabling containerd module. + +In the configuration file located in `/etc/containerd/config.toml` metrics endpoint needs to +be set and containerd daemon needs to be restarted. + +``` +[metrics] + address = "127.0.0.1:1338" +``` + +[float] +=== Compatibility + +The Containerd module is tested with the following versions of Containerd: +v1.5.2 + +[float] +=== Module-specific configuration notes + +For cpu metricset if `calcpct.cpu` setting is set to true, cpu usage percentages will be calculated +and more specifically fields `containerd.cpu.usage.total.pct`, `containerd.cpu.usage.kernel.pct`, `containerd.cpu.usage.user.pct`. +Default value is true. + +For memory metricset if `calcpct.memory` setting is set to true, memory usage percentages will be calculated +and more specifically fields `containerd.memory.usage.pct` and `containerd.memory.workingset.pct`. +Default value is true. + + + +[float] +=== Example configuration + +The Containerd module supports the standard configuration options that are described +in <>. Here is an example configuration: + +[source,yaml] +---- +metricbeat.modules: +- module: containerd + metricsets: ["cpu", "memory", "blkio"] + period: 10s + # containerd metrics endpoint is configured in /etc/containerd/config.toml + # Metrics endpoint does not listen by default + # https://github.com/containerd/containerd/blob/main/docs/man/containerd-config.toml.5.md + hosts: ["localhost:1338"] + # if set to true, cpu and memory usage percentages will be calculated. Default is true + calcpct.cpu: true + calcpct.memory: true + +---- + +[float] +=== Metricsets + +The following metricsets are available: + +* <> + +* <> + +* <> + +include::containerd/blkio.asciidoc[] + +include::containerd/cpu.asciidoc[] + +include::containerd/memory.asciidoc[] + diff --git a/metricbeat/docs/modules/containerd/blkio.asciidoc b/metricbeat/docs/modules/containerd/blkio.asciidoc new file mode 100644 index 00000000000..6618e45bcd2 --- /dev/null +++ b/metricbeat/docs/modules/containerd/blkio.asciidoc @@ -0,0 +1,25 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +[[metricbeat-metricset-containerd-blkio]] +[role="xpack"] +=== Containerd blkio metricset + +beta[] + +include::../../../../x-pack/metricbeat/module/containerd/blkio/_meta/docs.asciidoc[] + +This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../../x-pack/metricbeat/module/containerd/blkio/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules/containerd/cpu.asciidoc b/metricbeat/docs/modules/containerd/cpu.asciidoc new file mode 100644 index 00000000000..0d14f45da3e --- /dev/null +++ b/metricbeat/docs/modules/containerd/cpu.asciidoc @@ -0,0 +1,25 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +[[metricbeat-metricset-containerd-cpu]] +[role="xpack"] +=== Containerd cpu metricset + +beta[] + +include::../../../../x-pack/metricbeat/module/containerd/cpu/_meta/docs.asciidoc[] + +This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../../x-pack/metricbeat/module/containerd/cpu/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules/containerd/memory.asciidoc b/metricbeat/docs/modules/containerd/memory.asciidoc new file mode 100644 index 00000000000..a09ff8fd762 --- /dev/null +++ b/metricbeat/docs/modules/containerd/memory.asciidoc @@ -0,0 +1,25 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +[[metricbeat-metricset-containerd-memory]] +[role="xpack"] +=== Containerd memory metricset + +beta[] + +include::../../../../x-pack/metricbeat/module/containerd/memory/_meta/docs.asciidoc[] + +This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../../x-pack/metricbeat/module/containerd/memory/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index ad5d929514f..30177e13624 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -72,6 +72,10 @@ This file is generated! See scripts/mage/docs_collector.go .1+| .1+| |<> beta[] |<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | .1+| .1+| |<> beta[] +|<> beta[] |image:./images/icon-no.png[No prebuilt dashboards] | +.3+| .3+| |<> beta[] +|<> beta[] +|<> beta[] |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | .1+| .1+| |<> |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | @@ -322,6 +326,7 @@ include::modules/ceph.asciidoc[] include::modules/cloudfoundry.asciidoc[] include::modules/cockroachdb.asciidoc[] include::modules/consul.asciidoc[] +include::modules/containerd.asciidoc[] include::modules/coredns.asciidoc[] include::modules/couchbase.asciidoc[] include::modules/couchdb.asciidoc[] diff --git a/x-pack/metricbeat/include/list.go b/x-pack/metricbeat/include/list.go index f06eed2652a..3fdd6556b58 100644 --- a/x-pack/metricbeat/include/list.go +++ b/x-pack/metricbeat/include/list.go @@ -25,6 +25,10 @@ import ( _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/cloudfoundry/counter" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/cloudfoundry/value" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/cockroachdb" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd/blkio" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd/cpu" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd/memory" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/coredns" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/coredns/stats" _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/enterprisesearch" diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index f974c4e77f7..39d4937afab 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -427,6 +427,19 @@ metricbeat.modules: hosts: ["localhost:8500"] +#------------------------------ Containerd Module ------------------------------ +- module: containerd + metricsets: ["cpu", "memory", "blkio"] + period: 10s + # containerd metrics endpoint is configured in /etc/containerd/config.toml + # Metrics endpoint does not listen by default + # https://github.com/containerd/containerd/blob/main/docs/man/containerd-config.toml.5.md + hosts: ["localhost:1338"] + # if set to true, cpu and memory usage percentages will be calculated. Default is true + calcpct.cpu: true + calcpct.memory: true + + #------------------------------- Coredns Module ------------------------------- - module: coredns metricsets: ["stats"] diff --git a/x-pack/metricbeat/module/containerd/_meta/config.yml b/x-pack/metricbeat/module/containerd/_meta/config.yml new file mode 100644 index 00000000000..6197801bfcb --- /dev/null +++ b/x-pack/metricbeat/module/containerd/_meta/config.yml @@ -0,0 +1,11 @@ +- module: containerd + metricsets: ["cpu", "memory", "blkio"] + period: 10s + # containerd metrics endpoint is configured in /etc/containerd/config.toml + # Metrics endpoint does not listen by default + # https://github.com/containerd/containerd/blob/main/docs/man/containerd-config.toml.5.md + hosts: ["localhost:1338"] + # if set to true, cpu and memory usage percentages will be calculated. Default is true + calcpct.cpu: true + calcpct.memory: true + diff --git a/x-pack/metricbeat/module/containerd/_meta/docs.asciidoc b/x-pack/metricbeat/module/containerd/_meta/docs.asciidoc new file mode 100644 index 00000000000..3ee37e6722c --- /dev/null +++ b/x-pack/metricbeat/module/containerd/_meta/docs.asciidoc @@ -0,0 +1,38 @@ +include::{libbeat-dir}/shared/integration-link.asciidoc[] + +:modulename!: + +Containerd module collects cpu, memory and blkio statistics about +running containers controlled by containerd runtime. + +The current metricsets are: `cpu`, `blkio` and `memory` and are enabled by default. + +[float] +=== Prerequisites +`Containerd` daemon has to be configured to provide metrics before enabling containerd module. + +In the configuration file located in `/etc/containerd/config.toml` metrics endpoint needs to +be set and containerd daemon needs to be restarted. + +``` +[metrics] + address = "127.0.0.1:1338" +``` + +[float] +=== Compatibility + +The Containerd module is tested with the following versions of Containerd: +v1.5.2 + +[float] +=== Module-specific configuration notes + +For cpu metricset if `calcpct.cpu` setting is set to true, cpu usage percentages will be calculated +and more specifically fields `containerd.cpu.usage.total.pct`, `containerd.cpu.usage.kernel.pct`, `containerd.cpu.usage.user.pct`. +Default value is true. + +For memory metricset if `calcpct.memory` setting is set to true, memory usage percentages will be calculated +and more specifically fields `containerd.memory.usage.pct` and `containerd.memory.workingset.pct`. +Default value is true. + diff --git a/x-pack/metricbeat/module/containerd/_meta/fields.yml b/x-pack/metricbeat/module/containerd/_meta/fields.yml new file mode 100644 index 00000000000..9d5a0ccc29d --- /dev/null +++ b/x-pack/metricbeat/module/containerd/_meta/fields.yml @@ -0,0 +1,15 @@ +- key: containerd + title: "Containerd" + release: beta + description: > + Containerd stats collected from containerd + fields: + - name: containerd + type: group + description: > + Information and statistics about containerd's running containers. + fields: + - name: namespace + type: keyword + description: > + Containerd namespace diff --git a/x-pack/metricbeat/module/containerd/_meta/test/containerd.v1.5.2 b/x-pack/metricbeat/module/containerd/_meta/test/containerd.v1.5.2 new file mode 100644 index 00000000000..f82e4d3c1d9 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/_meta/test/containerd.v1.5.2 @@ -0,0 +1,99 @@ +# TYPE container_blkio_io_service_bytes_recursive_bytes gauge +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Async"} 0 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Discard"} 0 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Read"} 6.9246976e+07 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Sync"} 6.9271552e+07 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Total"} 6.9271552e+07 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Write"} 24576 +# TYPE container_blkio_io_serviced_recursive_total gauge +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Async"} 0 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Discard"} 0 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Read"} 830 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Sync"} 832 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Total"} 832 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Write"} 2 +# TYPE container_cpu_kernel_nanoseconds gauge +container_cpu_kernel_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 5.3218e+11 +# TYPE container_cpu_throttle_periods_total gauge +container_cpu_throttle_periods_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_cpu_throttled_periods_total gauge +container_cpu_throttled_periods_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_cpu_throttled_time_nanoseconds gauge +container_cpu_throttled_time_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_cpu_total_nanoseconds gauge +container_cpu_total_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.236339003984e+12 +# TYPE container_cpu_user_nanoseconds gauge +container_cpu_user_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 5.2547e+11 +# TYPE container_memory_active_anon_bytes gauge +container_memory_active_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_active_file_bytes gauge +container_memory_active_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.216512e+06 +# TYPE container_memory_cache_bytes gauge +container_memory_cache_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.40980224e+08 +# TYPE container_memory_dirty_bytes gauge +container_memory_dirty_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_inactive_anon_bytes gauge +container_memory_inactive_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.4048384e+07 +# TYPE container_memory_inactive_file_bytes gauge +container_memory_inactive_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.3928448e+08 +# TYPE container_memory_kernel_failcnt_total gauge +container_memory_kernel_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_kernel_limit_bytes gauge +container_memory_kernel_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 9.223372036854772e+18 +# TYPE container_memory_kernel_max_bytes gauge +container_memory_kernel_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 6.496256e+06 +# TYPE container_memory_kernel_usage_bytes gauge +container_memory_kernel_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 6.459392e+06 +# TYPE container_memory_kerneltcp_failcnt_total gauge +container_memory_kerneltcp_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_kerneltcp_limit_bytes gauge +container_memory_kerneltcp_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 9.223372036854772e+18 +# TYPE container_memory_kerneltcp_max_bytes gauge +container_memory_kerneltcp_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_kerneltcp_usage_bytes gauge +container_memory_kerneltcp_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_rss_bytes gauge +container_memory_rss_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.3794432e+07 +# TYPE container_memory_rss_huge_bytes gauge +container_memory_rss_huge_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_swap_failcnt_total gauge +container_memory_swap_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_swap_limit_bytes gauge +container_memory_swap_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 9.223372036854772e+18 +# TYPE container_memory_swap_max_bytes gauge +container_memory_swap_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 2.09727488e+08 +# TYPE container_memory_swap_usage_bytes gauge +container_memory_swap_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.9195904e+08 +# TYPE container_memory_total_active_anon_bytes gauge +container_memory_total_active_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_total_active_file_bytes gauge +container_memory_total_active_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.216512e+06 +# TYPE container_memory_total_cache_bytes gauge +container_memory_total_cache_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.40980224e+08 +# TYPE container_memory_total_inactive_anon_bytes gauge +container_memory_total_inactive_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.4048384e+07 +# TYPE container_memory_total_inactive_file_bytes gauge +container_memory_total_inactive_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.3928448e+08 +# TYPE container_memory_total_rss_bytes gauge +container_memory_total_rss_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.3794432e+07 +# TYPE container_memory_usage_failcnt_total gauge +container_memory_usage_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_usage_limit_bytes gauge +container_memory_usage_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 2.097152e+08 +# TYPE container_memory_usage_max_bytes gauge +container_memory_usage_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 2.09608704e+08 +# TYPE container_memory_usage_usage_bytes gauge +container_memory_usage_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.91848448e+08 +# TYPE container_per_cpu_nanoseconds gauge +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="0",namespace="k8s.io"} 9.913781757e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="1",namespace="k8s.io"} 1.16475261138e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="10",namespace="k8s.io"} 1.0670990577e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="11",namespace="k8s.io"} 1.0487838037e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="2",namespace="k8s.io"} 1.05305653633e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="3",namespace="k8s.io"} 1.01195506344e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="4",namespace="k8s.io"} 1.05731762224e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="5",namespace="k8s.io"} 9.8155683224e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="6",namespace="k8s.io"} 9.5075348914e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="7",namespace="k8s.io"} 9.713478277e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="8",namespace="k8s.io"} 1.04266711568e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="9",namespace="k8s.io"} 1.02272190459e+11 diff --git a/x-pack/metricbeat/module/containerd/blkio/_meta/data.json b/x-pack/metricbeat/module/containerd/blkio/_meta/data.json new file mode 100644 index 00000000000..d1f4774a706 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/blkio/_meta/data.json @@ -0,0 +1,35 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"containerd", + "name":"blkio", + "rtt":44269 + }, + "containerd": { + "blkio": { + "read": { + "ops": 168, + "bytes": 4952064 + }, + "summary": { + "ops": 168, + "bytes": 4952064 + }, + "write": { + "ops": 20, + "bytes": 123134 + }, + "device": "/dev/vda" + }, + "namespace": "k8s.io" + }, + "container": { + "id": "b4d9e874a2de96e4512a32a49df09641fa792a99bebcc6d353723850a50db831" + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/containerd/blkio/_meta/docs.asciidoc b/x-pack/metricbeat/module/containerd/blkio/_meta/docs.asciidoc new file mode 100644 index 00000000000..4faa70acb6d --- /dev/null +++ b/x-pack/metricbeat/module/containerd/blkio/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the blkio metricset of the module containerd. diff --git a/x-pack/metricbeat/module/containerd/blkio/_meta/fields.yml b/x-pack/metricbeat/module/containerd/blkio/_meta/fields.yml new file mode 100644 index 00000000000..adcd4228888 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/blkio/_meta/fields.yml @@ -0,0 +1,49 @@ +- name: blkio + type: group + release: beta + description: > + Block I/O metrics. + fields: + - name: read + type: group + description: > + Accumulated reads during the life of the container + fields: + - name: ops + type: long + description: > + Number of reads during the life of the container + - name: bytes + type: long + format: bytes + description: > + Bytes read during the life of the container + - name: write + type: group + description: > + Accumulated writes during the life of the container + fields: + - name: ops + type: long + description: > + Number of writes during the life of the container + - name: bytes + type: long + format: bytes + description: > + Bytes written during the life of the container + - name: summary + type: group + description: > + Accumulated reads and writes during the life of the container + fields: + - name: ops + type: long + description: > + Number of I/O operations during the life of the container + - name: bytes + type: long + format: bytes + description: > + Bytes read and written during the life of the container + diff --git a/x-pack/metricbeat/module/containerd/blkio/_meta/test/containerd.v1.5.2.expected b/x-pack/metricbeat/module/containerd/blkio/_meta/test/containerd.v1.5.2.expected new file mode 100644 index 00000000000..35b67461d90 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/blkio/_meta/test/containerd.v1.5.2.expected @@ -0,0 +1,37 @@ +[ + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "device": "/dev/vda", + "read": { + "bytes": 69246976, + "ops": 830 + }, + "summary": { + "bytes": 69271552, + "ops": 832 + }, + "write": { + "bytes": 24576, + "ops": 2 + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.blkio", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + } +] \ No newline at end of file diff --git a/x-pack/metricbeat/module/containerd/blkio/blkio.go b/x-pack/metricbeat/module/containerd/blkio/blkio.go new file mode 100644 index 00000000000..7ffb30e4aec --- /dev/null +++ b/x-pack/metricbeat/module/containerd/blkio/blkio.go @@ -0,0 +1,129 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package blkio + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common" + + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + + "github.com/elastic/beats/v7/metricbeat/helper/prometheus" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/v1/metrics" +) + +var ( + // HostParser validates Prometheus URLs + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() + + // Mapping of state metrics + mapping = &prometheus.MetricsMapping{ + Metrics: map[string]prometheus.MetricMap{ + "container_blkio_io_serviced_recursive_total": prometheus.Metric("", prometheus.OpFilterMap( + "op", map[string]string{ + "Read": "read.ops", + "Write": "write.ops", + "Total": "summary.ops", + }, + )), + "container_blkio_io_service_bytes_recursive_bytes": prometheus.Metric("", prometheus.OpFilterMap( + "op", map[string]string{ + "Read": "read.bytes", + "Write": "write.bytes", + "Total": "summary.bytes", + }, + )), + }, + Labels: map[string]prometheus.LabelMap{ + "container_id": prometheus.KeyLabel("id"), + "device": prometheus.KeyLabel("device"), + "namespace": prometheus.KeyLabel("namespace"), + }, + } +) + +// Metricset for containerd blkio is a prometheus based metricset +type metricset struct { + mb.BaseMetricSet + prometheusClient prometheus.Prometheus + mod containerd.Module +} + +// init registers the MetricSet with the central registry. +// The New method will be called after the setup of the module and before starting to fetch data +func init() { + mb.Registry.MustAddMetricSet("containerd", "blkio", New, + mb.WithHostParser(hostParser), + mb.DefaultMetricSet(), + ) +} + +// 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) { + cfgwarn.Beta("The containerd blkio metricset is beta.") + + pc, err := prometheus.NewPrometheusClient(base) + if err != nil { + return nil, err + } + + mod, ok := base.Module().(containerd.Module) + if !ok { + return nil, fmt.Errorf("must be child of kubernetes module") + } + return &metricset{ + BaseMetricSet: base, + prometheusClient: pc, + mod: mod, + }, nil +} + +// Fetch gathers information from the containerd and reports events with this information. +func (m *metricset) Fetch(reporter mb.ReporterV2) error { + families, _, err := m.mod.GetContainerdMetricsFamilies(m.prometheusClient) + if err != nil { + return errors.Wrap(err, "error getting families") + } + events, err := m.prometheusClient.ProcessMetrics(families, mapping) + if err != nil { + return errors.Wrap(err, "error getting events") + } + for _, event := range events { + // setting ECS container.id and module field containerd.namespace + containerFields := common.MapStr{} + moduleFields := common.MapStr{} + rootFields := common.MapStr{} + + cID := containerd.GetAndDeleteCid(event) + namespace := containerd.GetAndDeleteNamespace(event) + + containerFields.Put("id", cID) + rootFields.Put("container", containerFields) + moduleFields.Put("namespace", namespace) + + reporter.Event(mb.Event{ + RootFields: rootFields, + ModuleFields: moduleFields, + MetricSetFields: event, + Namespace: "containerd.blkio", + }) + } + return nil +} diff --git a/x-pack/metricbeat/module/containerd/blkio/blkio_test.go b/x-pack/metricbeat/module/containerd/blkio/blkio_test.go new file mode 100644 index 00000000000..25eed0d2887 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/blkio/blkio_test.go @@ -0,0 +1,42 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License 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. + +//go:build !integration +// +build !integration + +package blkio + +import ( + "testing" + + "github.com/elastic/beats/v7/metricbeat/helper/prometheus/ptest" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" +) + +func TestEventMapping(t *testing.T) { + ptest.TestMetricSet(t, "containerd", "blkio", + ptest.TestCases{ + { + MetricsFile: "../_meta/test/containerd.v1.5.2", + ExpectedFile: "./_meta/test/containerd.v1.5.2.expected", + }, + }, + ) +} diff --git a/x-pack/metricbeat/module/containerd/config.go b/x-pack/metricbeat/module/containerd/config.go new file mode 100644 index 00000000000..3c8910acba7 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/config.go @@ -0,0 +1,19 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package containerd + +// Config contains the config needed for containerd +type Config struct { + CalculateCpuPct bool `config:"calcpct.cpu"` + CalculateMemPct bool `config:"calcpct.memory"` +} + +// DefaultConfig returns default module config +func DefaultConfig() Config { + return Config{ + CalculateCpuPct: true, + CalculateMemPct: true, + } +} diff --git a/x-pack/metricbeat/module/containerd/containerd.go b/x-pack/metricbeat/module/containerd/containerd.go new file mode 100644 index 00000000000..2b0c1fd7e09 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/containerd.go @@ -0,0 +1,116 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package containerd + +import ( + "sync" + "time" + + "github.com/mitchellh/hashstructure" + "github.com/pkg/errors" + dto "github.com/prometheus/client_model/go" + + p "github.com/elastic/beats/v7/metricbeat/helper/prometheus" + "github.com/elastic/beats/v7/metricbeat/mb" +) + +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License 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. + +func init() { + // Register the ModuleFactory function for the "containerd" module. + if err := mb.Registry.AddModule("containerd", ModuleBuilder()); err != nil { + panic(err) + } +} + +type Module interface { + mb.Module + GetContainerdMetricsFamilies(prometheus p.Prometheus) ([]*dto.MetricFamily, time.Time, error) +} + +type familiesCache struct { + sharedFamilies []*dto.MetricFamily + lastFetchErr error + lastFetchTimestamp time.Time +} + +type containerdMetricsCache struct { + cacheMap map[uint64]*familiesCache + lock sync.Mutex +} + +func (c *containerdMetricsCache) getCacheMapEntry(hash uint64) *familiesCache { + if _, ok := c.cacheMap[hash]; !ok { + c.cacheMap[hash] = &familiesCache{} + } + return c.cacheMap[hash] +} + +type module struct { + mb.BaseModule + + containerdMetricsCache *containerdMetricsCache + cacheHash uint64 +} + +func ModuleBuilder() func(base mb.BaseModule) (mb.Module, error) { + containerdMetricsCache := &containerdMetricsCache{ + cacheMap: make(map[uint64]*familiesCache), + } + + return func(base mb.BaseModule) (mb.Module, error) { + hash, err := generateCacheHash(base.Config().Hosts) + if err != nil { + return nil, errors.Wrap(err, "error generating cache hash for containerdMetricsCache") + } + m := module{ + BaseModule: base, + containerdMetricsCache: containerdMetricsCache, + cacheHash: hash, + } + return &m, nil + } +} + +func (m *module) GetContainerdMetricsFamilies(prometheus p.Prometheus) ([]*dto.MetricFamily, time.Time, error) { + m.containerdMetricsCache.lock.Lock() + defer m.containerdMetricsCache.lock.Unlock() + + now := time.Now() + // NOTE: These entries will be never removed, this can be a leak if + // metricbeat is used to monitor clusters dynamically created. + // (https://github.com/elastic/beats/pull/25640#discussion_r633395213) + familiesCache := m.containerdMetricsCache.getCacheMapEntry(m.cacheHash) + + if familiesCache.lastFetchTimestamp.IsZero() || now.Sub(familiesCache.lastFetchTimestamp) > m.Config().Period { + familiesCache.sharedFamilies, familiesCache.lastFetchErr = prometheus.GetFamilies() + familiesCache.lastFetchTimestamp = now + } + + return familiesCache.sharedFamilies, familiesCache.lastFetchTimestamp, familiesCache.lastFetchErr +} + +func generateCacheHash(host []string) (uint64, error) { + id, err := hashstructure.Hash(host, nil) + if err != nil { + return 0, err + } + return id, nil +} diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/data.json b/x-pack/metricbeat/module/containerd/cpu/_meta/data.json new file mode 100644 index 00000000000..1dc40fe4cb7 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/data.json @@ -0,0 +1,38 @@ +{ + "@timestamp": "2019-03-01T08:05:34.853Z", + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "namespace": "k8s.io", + "cpu": { + "usage": { + "user": { + "ns": 22526620000000, + "pct": 0.2496932948025663 + }, + "total": { + "pct": 0.24969398913655017, + "ns": 22554111128186 + }, + "kernel": { + "ns": 16740000000, + "pct": 0 + } + } + } + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } +} diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/docs.asciidoc b/x-pack/metricbeat/module/containerd/cpu/_meta/docs.asciidoc new file mode 100644 index 00000000000..6cf8db33d3d --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the cpu metricset of the module containerd. diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/fields.yml b/x-pack/metricbeat/module/containerd/cpu/_meta/fields.yml new file mode 100644 index 00000000000..e2a99f3d432 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/fields.yml @@ -0,0 +1,54 @@ +- name: cpu + type: group + description: > + Containerd Runtime CPU metrics. + release: beta + fields: + - name: system.total + type: double + description: > + Total user and system CPU time spent in seconds. + - name: usage + type: group + fields: + - name: kernel + type: group + fields: + - name: ns + type: double + description: > + CPU Kernel usage nanoseconds + - name: user + type: group + fields: + - name: ns + type: double + description: > + CPU User usage nanoseconds + - name: total + type: group + fields: + - name: ns + type: double + description: > + CPU total usage nanoseconds + - name: total.pct + type: scaled_float + format: percent + description: > + Percentage of total CPU time normalized by the number of CPU cores + - name: kernel.pct + type: scaled_float + format: percent + description: > + Percentage of time in kernel space normalized by the number of CPU cores. + - name: user.pct + type: scaled_float + format: percent + description: > + Percentage of time in user space normalized by the number of CPU cores. + - name: cpu.*.ns + type: object + object_type: double + description: > + CPU usage nanoseconds in this cpu. diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/test/containerd.v1.5.2.expected b/x-pack/metricbeat/module/containerd/cpu/_meta/test/containerd.v1.5.2.expected new file mode 100644 index 00000000000..e048515f8dd --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/test/containerd.v1.5.2.expected @@ -0,0 +1,398 @@ +[ + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "7": { + "ns": 97134782770 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "4": { + "ns": 105731762224 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "9": { + "ns": 102272190459 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "8": { + "ns": 104266711568 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "10": { + "ns": 106709905770 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "5": { + "ns": 98155683224 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "2": { + "ns": 105305653633 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "3": { + "ns": 101195506344 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "0": { + "ns": 99137817570 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "1": { + "ns": 116475261138 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "6": { + "ns": 95075348914 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "cpu": { + "11": { + "ns": 104878380370 + } + }, + "percpu": {} + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + }, + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "usage": { + "kernel": { + "ns": 532180000000, + "pct": 0 + }, + "total": { + "ns": 1236339003984, + "pct": 0 + }, + "user": { + "ns": 525470000000, + "pct": 0 + } + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.cpu", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + } +] \ No newline at end of file diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/config.yml b/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/config.yml new file mode 100644 index 00000000000..e19c22ddc90 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/config.yml @@ -0,0 +1,3 @@ +type: http +url: "/v1/metrics" +suffix: plain diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain b/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain new file mode 100644 index 00000000000..f82e4d3c1d9 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain @@ -0,0 +1,99 @@ +# TYPE container_blkio_io_service_bytes_recursive_bytes gauge +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Async"} 0 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Discard"} 0 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Read"} 6.9246976e+07 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Sync"} 6.9271552e+07 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Total"} 6.9271552e+07 +container_blkio_io_service_bytes_recursive_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Write"} 24576 +# TYPE container_blkio_io_serviced_recursive_total gauge +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Async"} 0 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Discard"} 0 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Read"} 830 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Sync"} 832 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Total"} 832 +container_blkio_io_serviced_recursive_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",device="/dev/vda",major="254",minor="0",namespace="k8s.io",op="Write"} 2 +# TYPE container_cpu_kernel_nanoseconds gauge +container_cpu_kernel_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 5.3218e+11 +# TYPE container_cpu_throttle_periods_total gauge +container_cpu_throttle_periods_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_cpu_throttled_periods_total gauge +container_cpu_throttled_periods_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_cpu_throttled_time_nanoseconds gauge +container_cpu_throttled_time_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_cpu_total_nanoseconds gauge +container_cpu_total_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.236339003984e+12 +# TYPE container_cpu_user_nanoseconds gauge +container_cpu_user_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 5.2547e+11 +# TYPE container_memory_active_anon_bytes gauge +container_memory_active_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_active_file_bytes gauge +container_memory_active_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.216512e+06 +# TYPE container_memory_cache_bytes gauge +container_memory_cache_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.40980224e+08 +# TYPE container_memory_dirty_bytes gauge +container_memory_dirty_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_inactive_anon_bytes gauge +container_memory_inactive_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.4048384e+07 +# TYPE container_memory_inactive_file_bytes gauge +container_memory_inactive_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.3928448e+08 +# TYPE container_memory_kernel_failcnt_total gauge +container_memory_kernel_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_kernel_limit_bytes gauge +container_memory_kernel_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 9.223372036854772e+18 +# TYPE container_memory_kernel_max_bytes gauge +container_memory_kernel_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 6.496256e+06 +# TYPE container_memory_kernel_usage_bytes gauge +container_memory_kernel_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 6.459392e+06 +# TYPE container_memory_kerneltcp_failcnt_total gauge +container_memory_kerneltcp_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_kerneltcp_limit_bytes gauge +container_memory_kerneltcp_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 9.223372036854772e+18 +# TYPE container_memory_kerneltcp_max_bytes gauge +container_memory_kerneltcp_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_kerneltcp_usage_bytes gauge +container_memory_kerneltcp_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_rss_bytes gauge +container_memory_rss_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.3794432e+07 +# TYPE container_memory_rss_huge_bytes gauge +container_memory_rss_huge_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_swap_failcnt_total gauge +container_memory_swap_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_swap_limit_bytes gauge +container_memory_swap_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 9.223372036854772e+18 +# TYPE container_memory_swap_max_bytes gauge +container_memory_swap_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 2.09727488e+08 +# TYPE container_memory_swap_usage_bytes gauge +container_memory_swap_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.9195904e+08 +# TYPE container_memory_total_active_anon_bytes gauge +container_memory_total_active_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_total_active_file_bytes gauge +container_memory_total_active_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.216512e+06 +# TYPE container_memory_total_cache_bytes gauge +container_memory_total_cache_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.40980224e+08 +# TYPE container_memory_total_inactive_anon_bytes gauge +container_memory_total_inactive_anon_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.4048384e+07 +# TYPE container_memory_total_inactive_file_bytes gauge +container_memory_total_inactive_file_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.3928448e+08 +# TYPE container_memory_total_rss_bytes gauge +container_memory_total_rss_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 4.3794432e+07 +# TYPE container_memory_usage_failcnt_total gauge +container_memory_usage_failcnt_total{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 0 +# TYPE container_memory_usage_limit_bytes gauge +container_memory_usage_limit_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 2.097152e+08 +# TYPE container_memory_usage_max_bytes gauge +container_memory_usage_max_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 2.09608704e+08 +# TYPE container_memory_usage_usage_bytes gauge +container_memory_usage_usage_bytes{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",namespace="k8s.io"} 1.91848448e+08 +# TYPE container_per_cpu_nanoseconds gauge +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="0",namespace="k8s.io"} 9.913781757e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="1",namespace="k8s.io"} 1.16475261138e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="10",namespace="k8s.io"} 1.0670990577e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="11",namespace="k8s.io"} 1.0487838037e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="2",namespace="k8s.io"} 1.05305653633e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="3",namespace="k8s.io"} 1.01195506344e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="4",namespace="k8s.io"} 1.05731762224e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="5",namespace="k8s.io"} 9.8155683224e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="6",namespace="k8s.io"} 9.5075348914e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="7",namespace="k8s.io"} 9.713478277e+10 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="8",namespace="k8s.io"} 1.04266711568e+11 +container_per_cpu_nanoseconds{container_id="7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d",cpu="9",namespace="k8s.io"} 1.02272190459e+11 diff --git a/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain-expected.json b/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain-expected.json new file mode 100644 index 00000000000..61afe14be02 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/_meta/testdata/docs.plain-expected.json @@ -0,0 +1,411 @@ +[ + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "4": { + "ns": 105731762224 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "3": { + "ns": 101195506344 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "0": { + "ns": 99137817570 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "9": { + "ns": 102272190459 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "2": { + "ns": 105305653633 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "8": { + "ns": 104266711568 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "7": { + "ns": 97134782770 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "5": { + "ns": 98155683224 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "1": { + "ns": 116475261138 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "10": { + "ns": 106709905770 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "kernel": { + "ns": 532180000000, + "pct": 0 + }, + "total": { + "ns": 1236339003984, + "pct": 0 + }, + "user": { + "ns": 525470000000, + "pct": 0 + } + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "11": { + "ns": 104878380370 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + }, + { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + }, + "containerd": { + "cpu": { + "usage": { + "cpu": { + "6": { + "ns": 95075348914 + } + }, + "percpu": {} + } + }, + "namespace": "k8s.io" + }, + "event": { + "dataset": "containerd.cpu", + "duration": 115000, + "module": "containerd" + }, + "metricset": { + "name": "cpu", + "period": 10000 + }, + "service": { + "address": "127.0.0.1:55555", + "type": "containerd" + } + } +] \ No newline at end of file diff --git a/x-pack/metricbeat/module/containerd/cpu/cpu.go b/x-pack/metricbeat/module/containerd/cpu/cpu.go new file mode 100644 index 00000000000..135092f8184 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/cpu.go @@ -0,0 +1,235 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package cpu + +import ( + "fmt" + "time" + + "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/metricbeat/helper/prometheus" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/metricbeat/mb/parse" +) + +const ( + defaultScheme = "http" + defaultPath = "/v1/metrics" +) + +var ( + // HostParser validates Prometheus URLs + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() + + // Mapping of state metrics + mapping = &prometheus.MetricsMapping{ + Metrics: map[string]prometheus.MetricMap{ + "container_cpu_total_nanoseconds": prometheus.Metric("usage.total.ns"), + "container_cpu_user_nanoseconds": prometheus.Metric("usage.user.ns"), + "container_cpu_kernel_nanoseconds": prometheus.Metric("usage.kernel.ns"), + "container_per_cpu_nanoseconds": prometheus.Metric("usage.percpu.ns"), + "process_cpu_seconds_total": prometheus.Metric("system.total"), + }, + Labels: map[string]prometheus.LabelMap{ + "container_id": prometheus.KeyLabel("id"), + "namespace": prometheus.KeyLabel("namespace"), + "cpu": prometheus.KeyLabel("cpu"), + }, + } +) + +// Metricset for containerd is a prometheus based metricset +type metricset struct { + mb.BaseMetricSet + prometheusClient prometheus.Prometheus + mod containerd.Module + calcPct bool + preTimestamp time.Time + preContainerCpuTotalUsage map[string]float64 + preContainerCpuKernelUsage map[string]float64 + preContainerCpuUserUsage map[string]float64 +} + +// init registers the MetricSet with the central registry. +// The New method will be called after the setup of the module and before starting to fetch data +func init() { + mb.Registry.MustAddMetricSet("containerd", "cpu", New, + mb.WithHostParser(hostParser), + mb.DefaultMetricSet(), + ) +} + +// 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) { + cfgwarn.Beta("The containerd cpu metricset is beta.") + + pc, err := prometheus.NewPrometheusClient(base) + if err != nil { + return nil, err + } + config := containerd.DefaultConfig() + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + mod, ok := base.Module().(containerd.Module) + if !ok { + return nil, fmt.Errorf("must be child of kubernetes module") + } + return &metricset{ + BaseMetricSet: base, + prometheusClient: pc, + mod: mod, + calcPct: config.CalculateCpuPct, + preTimestamp: time.Time{}, + preContainerCpuTotalUsage: map[string]float64{}, + preContainerCpuKernelUsage: map[string]float64{}, + preContainerCpuUserUsage: map[string]float64{}, + }, nil +} + +// Fetch gathers information from the containerd and reports events with this information. +func (m *metricset) Fetch(reporter mb.ReporterV2) error { + families, timestamp, err := m.mod.GetContainerdMetricsFamilies(m.prometheusClient) + if err != nil { + return errors.Wrap(err, "error getting families") + } + + events, err := m.prometheusClient.ProcessMetrics(families, mapping) + if err != nil { + return errors.Wrap(err, "error getting events") + } + + perContainerCpus := make(map[string]int) + if m.calcPct { + for _, event := range events { + if _, err = event.GetValue("cpu"); err == nil { + // calculate cpus used by each container + setContCpus(event, perContainerCpus) + } + } + } + + for _, event := range events { + // setting ECS container.id and module field containerd.namespace + containerFields := common.MapStr{} + moduleFields := common.MapStr{} + rootFields := common.MapStr{} + + cID := containerd.GetAndDeleteCid(event) + namespace := containerd.GetAndDeleteNamespace(event) + + containerFields.Put("id", cID) + rootFields.Put("container", containerFields) + moduleFields.Put("namespace", namespace) + + if m.calcPct { + contCpus, ok := perContainerCpus[cID] + if !ok { + contCpus = 1 + } + // calculate timestamp delta + timestampDelta := int64(0) + if !m.preTimestamp.IsZero() { + timestampDelta = timestamp.UnixNano() - m.preTimestamp.UnixNano() + } + // Calculate cpu total usage percentage + cpuUsageTotal, err := event.GetValue("usage.total.ns") + if err == nil { + cpuUsageTotalPct := calcUsagePct(timestampDelta, cpuUsageTotal.(float64), + float64(contCpus), cID, m.preContainerCpuTotalUsage) + m.Logger().Debugf("cpuUsageTotalPct for %+v is %+v", cID, cpuUsageTotalPct) + event.Put("usage.total.pct", cpuUsageTotalPct) + // Update values + m.preContainerCpuTotalUsage[cID] = cpuUsageTotal.(float64) + } + + // Calculate cpu kernel usage percentage + // If event does not contain usage.kernel.ns skip the calculation (event has only system.total) + cpuUsageKernel, err := event.GetValue("usage.kernel.ns") + if err == nil { + cpuUsageKernelPct := calcUsagePct(timestampDelta, cpuUsageKernel.(float64), + float64(contCpus), cID, m.preContainerCpuKernelUsage) + m.Logger().Debugf("cpuUsageKernelPct for %+v is %+v", cID, cpuUsageKernelPct) + event.Put("usage.kernel.pct", cpuUsageKernelPct) + // Update values + m.preContainerCpuKernelUsage[cID] = cpuUsageKernel.(float64) + } + + // Calculate cpu user usage percentage + cpuUsageUser, err := event.GetValue("usage.user.ns") + if err == nil { + cpuUsageUserPct := calcUsagePct(timestampDelta, cpuUsageUser.(float64), + float64(contCpus), cID, m.preContainerCpuUserUsage) + m.Logger().Debugf("cpuUsageUserPct for %+v is %+v", cID, cpuUsageUserPct) + event.Put("usage.user.pct", cpuUsageUserPct) + // Update values + m.preContainerCpuUserUsage[cID] = cpuUsageUser.(float64) + } + } + if cpuId, err := event.GetValue("cpu"); err == nil { + perCpuNs, err := event.GetValue("usage.percpu.ns") + if err == nil { + key := fmt.Sprintf("usage.cpu.%s.ns", cpuId) + event.Put(key, perCpuNs) + event.Delete("cpu") + event.Delete("usage.percpu.ns") + } + } + + reporter.Event(mb.Event{ + RootFields: rootFields, + ModuleFields: moduleFields, + MetricSetFields: event, + Namespace: "containerd.cpu", + }) + } + // set Timestamp of previous event + m.preTimestamp = timestamp + return nil +} + +func setContCpus(event common.MapStr, perContainerCpus map[string]int) { + val, err := event.GetValue("id") + if err != nil { + return + } + cid := val.(string) + _, err = event.GetValue("usage.percpu.ns") + if err != nil { + return + } + if _, ok := perContainerCpus[cid]; ok { + perContainerCpus[cid] += 1 + } else { + perContainerCpus[cid] = 1 + } +} + +func calcUsagePct(timestampDelta int64, newValue, contCpus float64, + cid string, oldValuesMap map[string]float64) float64 { + var usageDelta, usagePct float64 + if oldValue, ok := oldValuesMap[cid]; ok { + usageDelta = newValue - oldValue + } else { + usageDelta = newValue + } + if usageDelta == 0.0 || float64(timestampDelta) == 0.0 { + usagePct = 0.0 + } else { + // normalize percentage with cpus used per container + usagePct = (usageDelta / float64(timestampDelta)) / contCpus + } + return usagePct +} diff --git a/x-pack/metricbeat/module/containerd/cpu/cpu_test.go b/x-pack/metricbeat/module/containerd/cpu/cpu_test.go new file mode 100644 index 00000000000..e7b67c45406 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/cpu/cpu_test.go @@ -0,0 +1,31 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build !integration +// +build !integration + +package cpu + +import ( + "testing" + + "github.com/elastic/beats/v7/metricbeat/helper/prometheus/ptest" + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" +) + +func TestEventMapping(t *testing.T) { + ptest.TestMetricSet(t, "containerd", "cpu", + ptest.TestCases{ + { + MetricsFile: "../_meta/test/containerd.v1.5.2", + ExpectedFile: "./_meta/test/containerd.v1.5.2.expected", + }, + }, + ) +} + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "containerd", "cpu") +} diff --git a/x-pack/metricbeat/module/containerd/doc.go b/x-pack/metricbeat/module/containerd/doc.go new file mode 100644 index 00000000000..27f4fcf9471 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/doc.go @@ -0,0 +1,6 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Package containerd is a Metricbeat module that contains MetricSets. +package containerd diff --git a/x-pack/metricbeat/module/containerd/fields.go b/x-pack/metricbeat/module/containerd/fields.go new file mode 100644 index 00000000000..134bc54e6d0 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/fields.go @@ -0,0 +1,23 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Code generated by beats/dev-tools/cmd/asset/asset.go - DO NOT EDIT. + +package containerd + +import ( + "github.com/elastic/beats/v7/libbeat/asset" +) + +func init() { + if err := asset.SetFields("metricbeat", "containerd", asset.ModuleFieldsPri, AssetContainerd); err != nil { + panic(err) + } +} + +// AssetContainerd returns asset data. +// This is the base64 encoded zlib format compressed contents of module/containerd. +func AssetContainerd() string { + return "eJzUmM1u2zAMx+95CqKXAQOa3XMYsBYoUAzdim09F4pMp1pkydDH0vTpB8p26tjyR4IGcXQYBjsmf6T+JKVewxq3C+BaOSYUmmQG4ISTuICr293DqxmAQYnM4gKW6NgMIEHLjcid0GoBX2cAAO8fgHXMWeBaSuQOE0iNzva9pAJlYhfhw2tQLMMGBi23zXEBK6N9Xj6JuKV1r1JtMkaPganCv7BOcAtsqb2rmf5kwXilhFq9P7Tz0lKdqk5G/9qccdy9qeDWuN3oHXAPYiNDbYuVr6VcC93yU08CreZ+jPB+IzVfw/2Xn5ChM4Lvoo5FXicyyJK9F11QAwC0vnHuMy8ZqYLsWki8oc1wLwhSpAg6Df/fbU7DRAy0Dqtz23pX8UqtVpGXA8i0fvhsiYbYjoKu7e7W4cGAhbi7Px4RwA19GuAPY6+4N0Y4PIUIguGLU8Fx1FORAdE7VMcpwfosY2Z7uoZADfwyVUG9VedowiC6WHWEJlFtwkEyac0ynvvBSTZuXv7yyokM4fbxKTa+usZh31izW+swmzvtmIyqOdF+KZtNbyCNf8gaeIumOIgEH4E64NsclQOhwCLXKtmL4Z3MW7Ya32yHVL9Go7AZYb/JPrN10yomNOhPYLVG6JEWpe57iKBICyimdJm9zpAp/5cc8BPpZ3y4MQlfVLyurJqDAp7n3HUGbTmTmDynUrPYj6pmmaPhqGK/GEH/WHxM0NQSQwy7SlfkQYo3TGC5Df1S7eYE/YhrE2nT+zU7tQgpLqFKOAjXl3FxznsrdaJhhib+IUHy3M8/z6O1VASpl38xmoPixfNAtY0IkShb9UVRuhdhA2BreGeY6b1z3ofdRFuW+8b0Rpu1UCuLLqKTQY3062MgcQ+BsyIAi66yw1YYH93GNne589DWd2Abdcoo0ggGrUjoWEF8Vrx1kDHuxD+8E7Ll7HSEhU9IhcTCSByNM/4SP++cAip468MR6hy5qrwOZuuw0+GA76fQE0olhT/bNdvY0PEyY69nuKo8sNeKOuSju/mee7iUlSCl5uGSW1J3NRIYfa47XXL32stAelMm5JxrH03PqCyPALpjQkJwgqYbRYpMdFOcUIu1RBUQ8cKNXsSOrtzyUnR5pVuBj67gc5VBCeqmVg0lVzrpotiXZ09V2A1rKv/omvi9YfkFVkSBPfl6CJiTq4ZANe1aqMuyUQmz/wEAAP//Ep8/EQ==" +} diff --git a/x-pack/metricbeat/module/containerd/helper.go b/x-pack/metricbeat/module/containerd/helper.go new file mode 100644 index 00000000000..b4fac5f72fd --- /dev/null +++ b/x-pack/metricbeat/module/containerd/helper.go @@ -0,0 +1,25 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package containerd + +import "github.com/elastic/beats/v7/libbeat/common" + +// GetAndDeleteCid deletes and returns container id from an event +func GetAndDeleteCid(event common.MapStr) (cID string) { + if containerID, ok := event["id"]; ok { + cID = (containerID).(string) + event.Delete("id") + } + return +} + +// GetAndDeleteNamespace deletes and returns namespace from an event +func GetAndDeleteNamespace(event common.MapStr) (namespace string) { + if ns, ok := event["namespace"]; ok { + namespace = (ns).(string) + event.Delete("namespace") + } + return +} diff --git a/x-pack/metricbeat/module/containerd/memory/_meta/data.json b/x-pack/metricbeat/module/containerd/memory/_meta/data.json new file mode 100644 index 00000000000..67eceb1ad10 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/memory/_meta/data.json @@ -0,0 +1,51 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"containerd", + "name":"memory", + "rtt":44269 + }, + "containerd": { + "namespace": "k8s.io", + "memory": { + "activeFiles": 0, + "workingset": { + "pct": 0.07224264705882352 + }, + "swap": { + "limit": 9223372036854772000, + "total": 49373184, + "max": 49848320, + "fail": { + "count": 0 + } + }, + "kernel": { + "limit": 9223372036854772000, + "fail": { + "count": 0 + }, + "max": 843776, + "total": 827392 + }, + "rss": 13651968, + "cache": 33792000, + "usage": { + "fail": { + "count": 0 + }, + "limit": 178257920, + "total": 49373184, + "pct": 0.27697610294117647, + "max": 49848320 + }, + "inactiveFiles": 36495360 + } + }, + "type":"metricsets" +} diff --git a/x-pack/metricbeat/module/containerd/memory/_meta/docs.asciidoc b/x-pack/metricbeat/module/containerd/memory/_meta/docs.asciidoc new file mode 100644 index 00000000000..64ce9bf9023 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/memory/_meta/docs.asciidoc @@ -0,0 +1 @@ +This is the memory metricset of the module containerd. diff --git a/x-pack/metricbeat/module/containerd/memory/_meta/fields.yml b/x-pack/metricbeat/module/containerd/memory/_meta/fields.yml new file mode 100644 index 00000000000..e637302364d --- /dev/null +++ b/x-pack/metricbeat/module/containerd/memory/_meta/fields.yml @@ -0,0 +1,109 @@ +- name: memory + type: group + release: beta + description: > + memory + fields: + - name: workingset.pct + type: scaled_float + format: percent + description: > + Memory working set percentage. + - name: rss + type: long + format: bytes + description: > + Total memory resident set size. + - name: activeFiles + type: long + format: bytes + description: > + Total active file bytes. + - name: cache + type: long + format: bytes + description: > + Total cache bytes. + - name: inactiveFiles + type: long + format: bytes + description: > + Total inactive file bytes. + - name: usage + type: group + description: > + Usage memory stats. + fields: + - name: max + type: long + format: bytes + description: > + Max memory usage. + - name: pct + type: scaled_float + format: percent + description: > + Total allocated memory percentage. + - name: total + type: long + format: bytes + description: > + Total memory usage. + - name: fail.count + type: scaled_float + description: > + Fail counter. + - name: limit + type: long + format: bytes + description: > + Memory usage limit. + - name: kernel + type: group + description: > + Kernel memory stats. + fields: + - name: max + type: long + format: bytes + description: > + Kernel max memory usage. + - name: total + type: long + format: bytes + description: > + Kernel total memory usage. + - name: fail.count + type: scaled_float + description: > + Kernel fail counter. + - name: limit + type: long + format: bytes + description: > + Kernel memory limit. + - name: swap + type: group + description: > + Swap memory stats. + fields: + - name: max + type: long + format: bytes + description: > + Swap max memory usage. + - name: total + type: long + format: bytes + description: > + Swap total memory usage. + - name: fail.count + type: scaled_float + description: > + Swap fail counter. + - name: limit + type: long + format: bytes + description: > + Swap memory limit. + diff --git a/x-pack/metricbeat/module/containerd/memory/_meta/test/containerd.v1.5.2.expected b/x-pack/metricbeat/module/containerd/memory/_meta/test/containerd.v1.5.2.expected new file mode 100644 index 00000000000..016b938235b --- /dev/null +++ b/x-pack/metricbeat/module/containerd/memory/_meta/test/containerd.v1.5.2.expected @@ -0,0 +1,56 @@ +[ + { + "RootFields": { + "container": { + "id": "7434687dbe3684407afa899582f2909203b9dc5537632b512f76798db5c0787d" + } + }, + "ModuleFields": { + "namespace": "k8s.io" + }, + "MetricSetFields": { + "activeFiles": 1216512, + "cache": 140980224, + "inactiveFiles": 139284480, + "kernel": { + "fail": { + "count": 0 + }, + "limit": 9223372036854772000, + "max": 6496256, + "total": 6459392 + }, + "rss": 43794432, + "swap": { + "fail": { + "count": 0 + }, + "limit": 9223372036854772000, + "max": 209727488, + "total": 191959040 + }, + "usage": { + "fail": { + "count": 0 + }, + "limit": 209715200, + "max": 209608704, + "pct": 0.9148046875, + "total": 191848448 + }, + "workingset": { + "pct": 0.25064453125 + } + }, + "Index": "", + "ID": "", + "Namespace": "containerd.memory", + "Timestamp": "0001-01-01T00:00:00Z", + "Error": null, + "Host": "", + "Service": "", + "Took": 0, + "Period": 0, + "DisableTimeSeries": false + } +] \ No newline at end of file diff --git a/x-pack/metricbeat/module/containerd/memory/memory.go b/x-pack/metricbeat/module/containerd/memory/memory.go new file mode 100644 index 00000000000..0bdc961a0e4 --- /dev/null +++ b/x-pack/metricbeat/module/containerd/memory/memory.go @@ -0,0 +1,167 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package memory + +import ( + "fmt" + + "github.com/elastic/beats/v7/libbeat/common" + + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + + "github.com/elastic/beats/v7/metricbeat/helper/prometheus" + "github.com/elastic/beats/v7/metricbeat/mb" + "github.com/elastic/beats/v7/metricbeat/mb/parse" + "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" +) + +const ( + defaultScheme = "http" + defaultPath = "/v1/metrics" +) + +var ( + // HostParser validates Prometheus URLs + hostParser = parse.URLHostParserBuilder{ + DefaultScheme: defaultScheme, + DefaultPath: defaultPath, + }.Build() + + // Mapping of state metrics + mapping = &prometheus.MetricsMapping{ + Metrics: map[string]prometheus.MetricMap{ + "container_memory_usage_max_bytes": prometheus.Metric("usage.max"), + "container_memory_usage_usage_bytes": prometheus.Metric("usage.total"), + "container_memory_usage_limit_bytes": prometheus.Metric("usage.limit"), + "container_memory_usage_failcnt_total": prometheus.Metric("usage.fail.count"), + "container_memory_kernel_max_bytes": prometheus.Metric("kernel.max"), + "container_memory_kernel_usage_bytes": prometheus.Metric("kernel.total"), + "container_memory_kernel_limit_bytes": prometheus.Metric("kernel.limit"), + "container_memory_kernel_failcnt_total": prometheus.Metric("kernel.fail.count"), + "container_memory_swap_max_bytes": prometheus.Metric("swap.max"), + "container_memory_swap_usage_bytes": prometheus.Metric("swap.total"), + "container_memory_swap_limit_bytes": prometheus.Metric("swap.limit"), + "container_memory_swap_failcnt_total": prometheus.Metric("swap.fail.count"), + "container_memory_total_inactive_file_bytes": prometheus.Metric("inactiveFiles"), + "container_memory_total_active_file_bytes": prometheus.Metric("activeFiles"), + "container_memory_total_cache_bytes": prometheus.Metric("cache"), + "container_memory_total_rss_bytes": prometheus.Metric("rss"), + }, + Labels: map[string]prometheus.LabelMap{ + "container_id": prometheus.KeyLabel("id"), + "namespace": prometheus.KeyLabel("namespace"), + }, + } +) + +// Metricset for containerd memory is a prometheus based metricset +type metricset struct { + mb.BaseMetricSet + prometheusClient prometheus.Prometheus + mod containerd.Module + calcPct bool +} + +// init registers the MetricSet with the central registry. +// The New method will be called after the setup of the module and before starting to fetch data +func init() { + mb.Registry.MustAddMetricSet("containerd", "memory", New, + mb.WithHostParser(hostParser), + mb.DefaultMetricSet(), + ) +} + +// 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) { + cfgwarn.Beta("The containerd cpu metricset is beta.") + + pc, err := prometheus.NewPrometheusClient(base) + if err != nil { + return nil, err + } + config := containerd.DefaultConfig() + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + + mod, ok := base.Module().(containerd.Module) + if !ok { + return nil, fmt.Errorf("must be child of kubernetes module") + } + return &metricset{ + BaseMetricSet: base, + prometheusClient: pc, + mod: mod, + calcPct: config.CalculateMemPct, + }, nil +} + +// Fetch gathers information from the containerd and reports events with this information. +func (m *metricset) Fetch(reporter mb.ReporterV2) error { + families, _, err := m.mod.GetContainerdMetricsFamilies(m.prometheusClient) + if err != nil { + return errors.Wrap(err, "error getting families") + } + events, err := m.prometheusClient.ProcessMetrics(families, mapping) + if err != nil { + return errors.Wrap(err, "error getting events") + } + + for _, event := range events { + + // setting ECS container.id and module field containerd.namespace + containerFields := common.MapStr{} + moduleFields := common.MapStr{} + rootFields := common.MapStr{} + + cID := containerd.GetAndDeleteCid(event) + namespace := containerd.GetAndDeleteNamespace(event) + + containerFields.Put("id", cID) + rootFields.Put("container", containerFields) + moduleFields.Put("namespace", namespace) + + // Calculate memory total usage percentage + if m.calcPct { + inactiveFiles, err := event.GetValue("inactiveFiles") + if err != nil { + m.Logger().Debugf("memoryUsagePct calculation skipped. inactiveFiles not present in the event: %w", err) + continue + } + usageTotal, err := event.GetValue("usage.total") + if err != nil { + m.Logger().Debugf("memoryUsagePct calculation skipped. usage.total not present in the event: %w", err) + continue + } + memoryLimit, err := event.GetValue("usage.limit") + if err != nil { + m.Logger().Debugf("memoryUsagePct calculation skipped. usage.limit not present in the event: %w", err) + continue + } + mLfloat, ok := memoryLimit.(float64) + if ok && mLfloat != 0.0 { + // calculate working set memory usage + workingSetUsage := usageTotal.(float64) - inactiveFiles.(float64) + workingSetUsagePct := workingSetUsage / mLfloat + event.Put("workingset.pct", workingSetUsagePct) + + memoryUsagePct := usageTotal.(float64) / mLfloat + event.Put("usage.pct", memoryUsagePct) + m.Logger().Debugf("memoryUsagePct for %+v is %+v", cID, memoryUsagePct) + } + } + + reporter.Event(mb.Event{ + RootFields: rootFields, + ModuleFields: moduleFields, + MetricSetFields: event, + Namespace: "containerd.memory", + }) + } + return nil +} diff --git a/x-pack/metricbeat/module/containerd/memory/memory_test.go b/x-pack/metricbeat/module/containerd/memory/memory_test.go new file mode 100644 index 00000000000..027ea9b598f --- /dev/null +++ b/x-pack/metricbeat/module/containerd/memory/memory_test.go @@ -0,0 +1,26 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build !integration +// +build !integration + +package memory + +import ( + "testing" + + "github.com/elastic/beats/v7/metricbeat/helper/prometheus/ptest" + _ "github.com/elastic/beats/v7/x-pack/metricbeat/module/containerd" +) + +func TestEventMapping(t *testing.T) { + ptest.TestMetricSet(t, "containerd", "memory", + ptest.TestCases{ + { + MetricsFile: "../_meta/test/containerd.v1.5.2", + ExpectedFile: "./_meta/test/containerd.v1.5.2.expected", + }, + }, + ) +} diff --git a/x-pack/metricbeat/modules.d/containerd.yml.disabled b/x-pack/metricbeat/modules.d/containerd.yml.disabled new file mode 100644 index 00000000000..cc735a455c7 --- /dev/null +++ b/x-pack/metricbeat/modules.d/containerd.yml.disabled @@ -0,0 +1,14 @@ +# Module: containerd +# Docs: https://www.elastic.co/guide/en/beats/metricbeat/master/metricbeat-module-containerd.html + +- module: containerd + metricsets: ["cpu", "memory", "blkio"] + period: 10s + # containerd metrics endpoint is configured in /etc/containerd/config.toml + # Metrics endpoint does not listen by default + # https://github.com/containerd/containerd/blob/main/docs/man/containerd-config.toml.5.md + hosts: ["localhost:1338"] + # if set to true, cpu and memory usage percentages will be calculated. Default is true + calcpct.cpu: true + calcpct.memory: true +