diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index f1f6b83fba8..1f3e8a9b1a2 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -49,6 +49,7 @@ https://github.com/elastic/beats/compare/v5.0.0-alpha5...master[Check the HEAD d *Metricbeat* - Use the new scaled_float Elasticsearch type for the percentage values. {pull}2156[2156] +- Add cgroup metrics to the system/process MetricSet. {pull}2184[2184] *Packetbeat* diff --git a/glide.yaml b/glide.yaml index ebeba1b275b..f5455cbece1 100644 --- a/glide.yaml +++ b/glide.yaml @@ -25,7 +25,7 @@ import: subpackages: - /difflib - package: github.com/elastic/gosigar - version: f720d6786aff82d4dae3d0af8c0ea5a34cd04029 + version: cabc0737dc6b82977febbc5c24865259447e1965 - package: github.com/samuel/go-parser version: ca8abbf65d0e61dedf061f98bd3850f250e27539 - package: github.com/samuel/go-thrift diff --git a/libbeat/scripts/generate_template.py b/libbeat/scripts/generate_template.py index 749c63e4415..7791ac6b69d 100644 --- a/libbeat/scripts/generate_template.py +++ b/libbeat/scripts/generate_template.py @@ -116,7 +116,7 @@ def dedot(group): else: fields.append(field) for _, field in dedotted.items(): - fields.append(field) + fields.append(dedot(field)) group["fields"] = fields return group diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index e077e4d2e4d..98d831b842e 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -2853,6 +2853,478 @@ format: bytes The shared memory the process uses. +[float] +== cgroup Fields + +Metrics and limits from the cgroup of which the task is a member. cgroup metrics are reported when the process has membership in a non-root cgroup. These metrics are only available on Linux. + + + +[float] +=== system.process.cgroup.id + +type: keyword + +The ID common to all cgroups associated with this task. If there isn't a common ID used by all cgroups this field will be absent. + + +[float] +=== system.process.cgroup.path + +type: keyword + +The path to the cgroup relative to the cgroup subsystem's mountpoint. If there isn't a common path used by all cgroups this field will be absent. + + +[float] +== cpu Fields + +The cpu subsystem schedules CPU access for tasks in the cgroup. Access can be controlled by two separate schedulers, CFS and RT. CFS stands for completely fair scheduler which proportionally divides the CPU time between cgroups based on weight. RT stands for real time scheduler which sets a maximum amount of CPU time that processes in the cgroup can consume during a given period. + + + +[float] +=== system.process.cgroup.cpu.id + +type: keyword + +ID of the cgroup. + +[float] +=== system.process.cgroup.cpu.path + +type: keyword + +Path to the cgroup relative to the cgroup subsystem's mountpoint. + + +[float] +=== system.process.cgroup.cpu.cfs.period.us + +type: long + +Period of time in microseconds for how regularly a cgroup's access to CPU resources should be reallocated. + + +[float] +=== system.process.cgroup.cpu.cfs.quota.us + +type: long + +Total amount of time in microseconds for which all tasks in a cgroup can run during one period (as defined by cfs.period.us). + + +[float] +=== system.process.cgroup.cpu.cfs.shares + +type: long + +An integer value that specifies a relative share of CPU time available to the tasks in a cgroup. The value specified in the cpu.shares file must be 2 or higher. + + +[float] +=== system.process.cgroup.cpu.rt.period.us + +type: long + +Period of time in microseconds for how regularly a cgroup's access to CPU resources is reallocated. + + +[float] +=== system.process.cgroup.cpu.rt.runtime.us + +type: long + +Period of time in microseconds for the longest continuous period in which the tasks in a cgroup have access to CPU resources. + + +[float] +=== system.process.cgroup.cpu.stats.periods + +type: long + +Number of period intervals (as specified in cpu.cfs.period.us) that have elapsed. + + +[float] +=== system.process.cgroup.cpu.stats.throttled.periods + +type: long + +Number of times tasks in a cgroup have been throttled (that is, not allowed to run because they have exhausted all of the available time as specified by their quota). + + +[float] +=== system.process.cgroup.cpu.stats.throttled.nanos + +type: long + +The total time duration (in nanoseconds) for which tasks in a cgroup have been throttled. + + +[float] +== cpuacct Fields + +CPU accounting metrics. + + +[float] +=== system.process.cgroup.cpuacct.id + +type: keyword + +ID of the cgroup. + +[float] +=== system.process.cgroup.cpuacct.path + +type: keyword + +Path to the cgroup relative to the cgroup subsystem's mountpoint. + + +[float] +=== system.process.cgroup.cpuacct.total.nanos + +type: long + +Total CPU time in nanoseconds consumed by all tasks in the cgroup. + + +[float] +=== system.process.cgroup.cpuacct.stats.user.nanos + +type: long + +CPU time consumed by tasks in user mode. + +[float] +=== system.process.cgroup.cpuacct.stats.system.nanos + +type: long + +CPU time consumed by tasks in user (kernel) mode. + +[float] +=== system.process.cgroup.cpuacct.percpu + +type: dict + +CPU time (in nanoseconds) consumed on each CPU by all tasks in this cgroup. + + +[float] +== memory Fields + +Memory limits and metrics. + + +[float] +=== system.process.cgroup.memory.id + +type: keyword + +ID of the cgroup. + +[float] +=== system.process.cgroup.memory.path + +type: keyword + +Path to the cgroup relative to the cgroup subsystem's mountpoint. + + +[float] +=== system.process.cgroup.memory.mem.usage.bytes + +type: long + +Total memory usage by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.mem.usage.max.bytes + +type: long + +The maximum memory used by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.mem.limit.bytes + +type: long + +The maximum amount of user memory in bytes (including file cache) that tasks in the cgroup are allowed to use. + + +[float] +=== system.process.cgroup.memory.mem.failures + +type: long + +The number of times that the memory limit (mem.limit.bytes) was reached. + + +[float] +=== system.process.cgroup.memory.memsw.usage.bytes + +type: long + +The sum of current memory usage plus swap space used by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.memsw.usage.max.bytes + +type: long + +The maximum amount of memory and swap space used by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.memsw.limit.bytes + +type: long + +The maximum amount for the sum of memory and swap usage that tasks in the cgroup are allowed to use. + + +[float] +=== system.process.cgroup.memory.memsw.failures + +type: long + +The number of times that the memory plus swap space limit (memsw.limit.bytes) was reached. + + +[float] +=== system.process.cgroup.memory.kmem.usage.bytes + +type: long + +Total kernel memory usage by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.kmem.usage.max.bytes + +type: long + +The maximum kernel memory used by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.kmem.limit.bytes + +type: long + +The maximum amount of kernel memory that tasks in the cgroup are allowed to use. + + +[float] +=== system.process.cgroup.memory.kmem.failures + +type: long + +The number of times that the memory limit (kmem.limit.bytes) was reached. + + +[float] +=== system.process.cgroup.memory.kmem_tcp.usage.bytes + +type: long + +Total memory usage for TCP buffers in bytes. + + +[float] +=== system.process.cgroup.memory.kmem_tcp.usage.max.bytes + +type: long + +The maximum memory used for TCP buffers by processes in the cgroup (in bytes). + + +[float] +=== system.process.cgroup.memory.kmem_tcp.limit.bytes + +type: long + +The maximum amount of memory for TCP buffers that tasks in the cgroup are allowed to use. + + +[float] +=== system.process.cgroup.memory.kmem_tcp.failures + +type: long + +The number of times that the memory limit (kmem_tcp.limit.bytes) was reached. + + +[float] +=== system.process.cgroup.memory.stats.active_anon.bytes + +type: long + +Anonymous and swap cache on active least-recently-used (LRU) list, including tmpfs (shmem), in bytes. + + +[float] +=== system.process.cgroup.memory.stats.active_file.bytes + +type: long + +File-backed memory on active LRU list, in bytes. + +[float] +=== system.process.cgroup.memory.stats.cache.bytes + +type: long + +Page cache, including tmpfs (shmem), in bytes. + +[float] +=== system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes + +type: long + +Memory limit for the hierarchy that contains the memory cgroup, in bytes. + + +[float] +=== system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes + +type: long + +Memory plus swap limit for the hierarchy that contains the memory cgroup, in bytes. + + +[float] +=== system.process.cgroup.memory.stats.inactive_anon.bytes + +type: long + +Anonymous and swap cache on inactive LRU list, including tmpfs (shmem), in bytes + + +[float] +=== system.process.cgroup.memory.stats.inactive_file.bytes + +type: long + +File-backed memory on inactive LRU list, in bytes. + + +[float] +=== system.process.cgroup.memory.stats.mapped_file.bytes + +type: long + +Size of memory-mapped mapped files, including tmpfs (shmem), in bytes. + + +[float] +=== system.process.cgroup.memory.stats.page_faults + +type: long + +Number of times that a process in the cgroup triggered a page fault. + + +[float] +=== system.process.cgroup.memory.stats.major_page_faults + +type: long + +Number of times that a process in the cgroup triggered a major fault. "Major" faults happen when the kernel actually has to read the data from disk. + + +[float] +=== system.process.cgroup.memory.stats.pages_in + +type: long + +Number of pages paged into memory. This is a counter. + + +[float] +=== system.process.cgroup.memory.stats.pages_out + +type: long + +Number of pages paged out of memory. This is a counter. + + +[float] +=== system.process.cgroup.memory.stats.rss.bytes + +type: long + +Anonymous and swap cache (includes transparent hugepages), not including tmpfs (shmem), in bytes. + + +[float] +=== system.process.cgroup.memory.stats.rss_huge.bytes + +type: long + +Number of bytes of anonymous transparent hugepages. + + +[float] +=== system.process.cgroup.memory.stats.swap.bytes + +type: long + +Swap usage, in bytes. + + +[float] +=== system.process.cgroup.memory.stats.unevictable.bytes + +type: long + +Memory that cannot be reclaimed, in bytes. + + +[float] +== blkio Fields + +Block IO metrics. + + +[float] +=== system.process.cgroup.blkio.id + +type: keyword + +ID of the cgroup. + +[float] +=== system.process.cgroup.blkio.path + +type: keyword + +Path to the cgroup relative to the cgroup subsystems mountpoint. + + +[float] +=== system.process.cgroup.blkio.total.bytes + +type: keyword + +Total number of bytes transferred to and from all block devices by processes in the cgroup. + + +[float] +=== system.process.cgroup.blkio.total.ios + +type: keyword + +Total number of I/O operations performed on all devices by processes in the cgroup as seen by the throttling policy. + + [[exported-fields-zookeeper]] == ZooKeeper Fields diff --git a/metricbeat/etc/fields.yml b/metricbeat/etc/fields.yml index 04394ddf89f..75ec8348a75 100644 --- a/metricbeat/etc/fields.yml +++ b/metricbeat/etc/fields.yml @@ -1709,6 +1709,350 @@ format: bytes description: > The shared memory the process uses. + - name: cgroup + type: group + description: > + Metrics and limits from the cgroup of which the task is a member. + cgroup metrics are reported when the process has membership in a + non-root cgroup. These metrics are only available on Linux. + fields: + - name: id + type: keyword + description: > + The ID common to all cgroups associated with this task. + If there isn't a common ID used by all cgroups this field will be + absent. + + - name: path + type: keyword + description: > + The path to the cgroup relative to the cgroup subsystem's mountpoint. + If there isn't a common path used by all cgroups this field will be + absent. + + - name: cpu + type: group + description: > + The cpu subsystem schedules CPU access for tasks in the cgroup. + Access can be controlled by two separate schedulers, CFS and RT. + CFS stands for completely fair scheduler which proportionally + divides the CPU time between cgroups based on weight. RT stands for + real time scheduler which sets a maximum amount of CPU time that + processes in the cgroup can consume during a given period. + + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystem's + mountpoint. + + - name: cfs.period.us + type: long + description: > + Period of time in microseconds for how regularly a + cgroup's access to CPU resources should be reallocated. + + - name: cfs.quota.us + type: long + description: > + Total amount of time in microseconds for which all + tasks in a cgroup can run during one period (as defined by + cfs.period.us). + + - name: cfs.shares + type: long + description: > + An integer value that specifies a relative share of CPU time + available to the tasks in a cgroup. The value specified in the + cpu.shares file must be 2 or higher. + + - name: rt.period.us + type: long + description: > + Period of time in microseconds for how regularly a cgroup's + access to CPU resources is reallocated. + + - name: rt.runtime.us + type: long + description: > + Period of time in microseconds for the longest continuous period + in which the tasks in a cgroup have access to CPU resources. + + - name: stats.periods + type: long + description: > + Number of period intervals (as specified in cpu.cfs.period.us) + that have elapsed. + + - name: stats.throttled.periods + type: long + description: > + Number of times tasks in a cgroup have been throttled (that is, + not allowed to run because they have exhausted all of the + available time as specified by their quota). + + - name: stats.throttled.nanos + type: long + description: > + The total time duration (in nanoseconds) for which tasks in a + cgroup have been throttled. + + - name: cpuacct + type: group + description: CPU accounting metrics. + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystem's + mountpoint. + + - name: total.nanos + type: long + description: > + Total CPU time in nanoseconds consumed by all tasks in the + cgroup. + + - name: stats.user.nanos + type: long + description: CPU time consumed by tasks in user mode. + + - name: stats.system.nanos + type: long + description: CPU time consumed by tasks in user (kernel) mode. + + - name: percpu + type: dict + dict-type: long + description: > + CPU time (in nanoseconds) consumed on each CPU by all tasks in + this cgroup. + + - name: memory + type: group + description: Memory limits and metrics. + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystem's mountpoint. + + - name: mem.usage.bytes + type: long + description: > + Total memory usage by processes in the cgroup (in bytes). + + - name: mem.usage.max.bytes + type: long + description: > + The maximum memory used by processes in the cgroup (in bytes). + + - name: mem.limit.bytes + type: long + description: > + The maximum amount of user memory in bytes (including file + cache) that tasks in the cgroup are allowed to use. + + - name: mem.failures + type: long + description: > + The number of times that the memory limit (mem.limit.bytes) was + reached. + + - name: memsw.usage.bytes + type: long + description: > + The sum of current memory usage plus swap space used by + processes in the cgroup (in bytes). + + - name: memsw.usage.max.bytes + type: long + description: > + The maximum amount of memory and swap space used by processes in + the cgroup (in bytes). + + - name: memsw.limit.bytes + type: long + description: > + The maximum amount for the sum of memory and swap usage + that tasks in the cgroup are allowed to use. + + - name: memsw.failures + type: long + description: > + The number of times that the memory plus swap space limit + (memsw.limit.bytes) was reached. + + - name: kmem.usage.bytes + type: long + description: > + Total kernel memory usage by processes in the cgroup (in bytes). + + - name: kmem.usage.max.bytes + type: long + description: > + The maximum kernel memory used by processes in the cgroup (in + bytes). + + - name: kmem.limit.bytes + type: long + description: > + The maximum amount of kernel memory that tasks in the cgroup are + allowed to use. + + - name: kmem.failures + type: long + description: > + The number of times that the memory limit (kmem.limit.bytes) was + reached. + + - name: kmem_tcp.usage.bytes + type: long + description: > + Total memory usage for TCP buffers in bytes. + + - name: kmem_tcp.usage.max.bytes + type: long + description: > + The maximum memory used for TCP buffers by processes in the + cgroup (in bytes). + + - name: kmem_tcp.limit.bytes + type: long + description: > + The maximum amount of memory for TCP buffers that tasks in the + cgroup are allowed to use. + + - name: kmem_tcp.failures + type: long + description: > + The number of times that the memory limit (kmem_tcp.limit.bytes) + was reached. + + - name: stats.active_anon.bytes + type: long + description: > + Anonymous and swap cache on active least-recently-used (LRU) + list, including tmpfs (shmem), in bytes. + + - name: stats.active_file.bytes + type: long + description: File-backed memory on active LRU list, in bytes. + + - name: stats.cache.bytes + type: long + description: Page cache, including tmpfs (shmem), in bytes. + + - name: stats.hierarchical_memory_limit.bytes + type: long + description: > + Memory limit for the hierarchy that contains the memory cgroup, + in bytes. + + - name: stats.hierarchical_memsw_limit.bytes + type: long + description: > + Memory plus swap limit for the hierarchy that contains the + memory cgroup, in bytes. + + - name: stats.inactive_anon.bytes + type: long + description: > + Anonymous and swap cache on inactive LRU list, including tmpfs + (shmem), in bytes + + - name: stats.inactive_file.bytes + type: long + description: > + File-backed memory on inactive LRU list, in bytes. + + - name: stats.mapped_file.bytes + type: long + description: > + Size of memory-mapped mapped files, including tmpfs (shmem), + in bytes. + + - name: stats.page_faults + type: long + description: > + Number of times that a process in the cgroup triggered a page + fault. + + - name: stats.major_page_faults + type: long + description: > + Number of times that a process in the cgroup triggered a major + fault. "Major" faults happen when the kernel actually has to + read the data from disk. + + - name: stats.pages_in + type: long + description: > + Number of pages paged into memory. This is a counter. + + - name: stats.pages_out + type: long + description: > + Number of pages paged out of memory. This is a counter. + + - name: stats.rss.bytes + type: long + description: > + Anonymous and swap cache (includes transparent hugepages), not + including tmpfs (shmem), in bytes. + + - name: stats.rss_huge.bytes + type: long + description: > + Number of bytes of anonymous transparent hugepages. + + - name: stats.swap.bytes + type: long + description: > + Swap usage, in bytes. + + - name: stats.unevictable.bytes + type: long + description: > + Memory that cannot be reclaimed, in bytes. + + - name: blkio + type: group + description: Block IO metrics. + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystems mountpoint. + + - name: total.bytes + type: keyword + description: > + Total number of bytes transferred to and from all block devices + by processes in the cgroup. + + - name: total.ios + type: keyword + description: > + Total number of I/O operations performed on all devices + by processes in the cgroup as seen by the throttling policy. - key: zookeeper title: "ZooKeeper" description: > diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index 74d4bf77f74..cf0d1d9dd52 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -1375,6 +1375,383 @@ }, "process": { "properties": { + "cgroup": { + "properties": { + "blkio": { + "properties": { + "id": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "path": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "total": { + "properties": { + "bytes": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "ios": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + } + } + } + } + }, + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "type": "long" + } + } + }, + "shares": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "path": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "rt": { + "properties": { + "period": { + "properties": { + "us": { + "type": "long" + } + } + }, + "runtime": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "stats": { + "properties": { + "periods": { + "type": "long" + }, + "throttled": { + "properties": { + "nanos": { + "type": "long" + }, + "periods": { + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "path": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "stats": { + "properties": { + "system": { + "properties": { + "nanos": { + "type": "long" + } + } + }, + "user": { + "properties": { + "nanos": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "nanos": { + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "memory": { + "properties": { + "id": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "kmem": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "kmem_tcp": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "memsw": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "path": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "stats": { + "properties": { + "active_anon": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "active_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cache": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "hierarchical_memory_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "hierarchical_memsw_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inactive_anon": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inactive_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "major_page_faults": { + "type": "long" + }, + "mapped_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "page_faults": { + "type": "long" + }, + "pages_in": { + "type": "long" + }, + "pages_out": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "rss_huge": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "swap": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "unevictable": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "path": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + } + } + }, "cmdline": { "ignore_above": 1024, "index": "not_analyzed", diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index 3adcad40e4c..e7e41d15bdb 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -1372,6 +1372,371 @@ }, "process": { "properties": { + "cgroup": { + "properties": { + "blkio": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "ios": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "type": "long" + } + } + }, + "shares": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rt": { + "properties": { + "period": { + "properties": { + "us": { + "type": "long" + } + } + }, + "runtime": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "stats": { + "properties": { + "periods": { + "type": "long" + }, + "throttled": { + "properties": { + "nanos": { + "type": "long" + }, + "periods": { + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "system": { + "properties": { + "nanos": { + "type": "long" + } + } + }, + "user": { + "properties": { + "nanos": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "nanos": { + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kmem": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "kmem_tcp": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "memsw": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "active_anon": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "active_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cache": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "hierarchical_memory_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "hierarchical_memsw_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inactive_anon": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inactive_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "major_page_faults": { + "type": "long" + }, + "mapped_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "page_faults": { + "type": "long" + }, + "pages_in": { + "type": "long" + }, + "pages_out": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "rss_huge": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "swap": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "unevictable": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "cmdline": { "ignore_above": 1024, "type": "keyword" diff --git a/metricbeat/module/system/process/_meta/fields.yml b/metricbeat/module/system/process/_meta/fields.yml index b7f67a60930..1ae3d9a2301 100644 --- a/metricbeat/module/system/process/_meta/fields.yml +++ b/metricbeat/module/system/process/_meta/fields.yml @@ -87,3 +87,347 @@ format: bytes description: > The shared memory the process uses. + - name: cgroup + type: group + description: > + Metrics and limits from the cgroup of which the task is a member. + cgroup metrics are reported when the process has membership in a + non-root cgroup. These metrics are only available on Linux. + fields: + - name: id + type: keyword + description: > + The ID common to all cgroups associated with this task. + If there isn't a common ID used by all cgroups this field will be + absent. + + - name: path + type: keyword + description: > + The path to the cgroup relative to the cgroup subsystem's mountpoint. + If there isn't a common path used by all cgroups this field will be + absent. + + - name: cpu + type: group + description: > + The cpu subsystem schedules CPU access for tasks in the cgroup. + Access can be controlled by two separate schedulers, CFS and RT. + CFS stands for completely fair scheduler which proportionally + divides the CPU time between cgroups based on weight. RT stands for + real time scheduler which sets a maximum amount of CPU time that + processes in the cgroup can consume during a given period. + + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystem's + mountpoint. + + - name: cfs.period.us + type: long + description: > + Period of time in microseconds for how regularly a + cgroup's access to CPU resources should be reallocated. + + - name: cfs.quota.us + type: long + description: > + Total amount of time in microseconds for which all + tasks in a cgroup can run during one period (as defined by + cfs.period.us). + + - name: cfs.shares + type: long + description: > + An integer value that specifies a relative share of CPU time + available to the tasks in a cgroup. The value specified in the + cpu.shares file must be 2 or higher. + + - name: rt.period.us + type: long + description: > + Period of time in microseconds for how regularly a cgroup's + access to CPU resources is reallocated. + + - name: rt.runtime.us + type: long + description: > + Period of time in microseconds for the longest continuous period + in which the tasks in a cgroup have access to CPU resources. + + - name: stats.periods + type: long + description: > + Number of period intervals (as specified in cpu.cfs.period.us) + that have elapsed. + + - name: stats.throttled.periods + type: long + description: > + Number of times tasks in a cgroup have been throttled (that is, + not allowed to run because they have exhausted all of the + available time as specified by their quota). + + - name: stats.throttled.nanos + type: long + description: > + The total time duration (in nanoseconds) for which tasks in a + cgroup have been throttled. + + - name: cpuacct + type: group + description: CPU accounting metrics. + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystem's + mountpoint. + + - name: total.nanos + type: long + description: > + Total CPU time in nanoseconds consumed by all tasks in the + cgroup. + + - name: stats.user.nanos + type: long + description: CPU time consumed by tasks in user mode. + + - name: stats.system.nanos + type: long + description: CPU time consumed by tasks in user (kernel) mode. + + - name: percpu + type: dict + dict-type: long + description: > + CPU time (in nanoseconds) consumed on each CPU by all tasks in + this cgroup. + + - name: memory + type: group + description: Memory limits and metrics. + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystem's mountpoint. + + - name: mem.usage.bytes + type: long + description: > + Total memory usage by processes in the cgroup (in bytes). + + - name: mem.usage.max.bytes + type: long + description: > + The maximum memory used by processes in the cgroup (in bytes). + + - name: mem.limit.bytes + type: long + description: > + The maximum amount of user memory in bytes (including file + cache) that tasks in the cgroup are allowed to use. + + - name: mem.failures + type: long + description: > + The number of times that the memory limit (mem.limit.bytes) was + reached. + + - name: memsw.usage.bytes + type: long + description: > + The sum of current memory usage plus swap space used by + processes in the cgroup (in bytes). + + - name: memsw.usage.max.bytes + type: long + description: > + The maximum amount of memory and swap space used by processes in + the cgroup (in bytes). + + - name: memsw.limit.bytes + type: long + description: > + The maximum amount for the sum of memory and swap usage + that tasks in the cgroup are allowed to use. + + - name: memsw.failures + type: long + description: > + The number of times that the memory plus swap space limit + (memsw.limit.bytes) was reached. + + - name: kmem.usage.bytes + type: long + description: > + Total kernel memory usage by processes in the cgroup (in bytes). + + - name: kmem.usage.max.bytes + type: long + description: > + The maximum kernel memory used by processes in the cgroup (in + bytes). + + - name: kmem.limit.bytes + type: long + description: > + The maximum amount of kernel memory that tasks in the cgroup are + allowed to use. + + - name: kmem.failures + type: long + description: > + The number of times that the memory limit (kmem.limit.bytes) was + reached. + + - name: kmem_tcp.usage.bytes + type: long + description: > + Total memory usage for TCP buffers in bytes. + + - name: kmem_tcp.usage.max.bytes + type: long + description: > + The maximum memory used for TCP buffers by processes in the + cgroup (in bytes). + + - name: kmem_tcp.limit.bytes + type: long + description: > + The maximum amount of memory for TCP buffers that tasks in the + cgroup are allowed to use. + + - name: kmem_tcp.failures + type: long + description: > + The number of times that the memory limit (kmem_tcp.limit.bytes) + was reached. + + - name: stats.active_anon.bytes + type: long + description: > + Anonymous and swap cache on active least-recently-used (LRU) + list, including tmpfs (shmem), in bytes. + + - name: stats.active_file.bytes + type: long + description: File-backed memory on active LRU list, in bytes. + + - name: stats.cache.bytes + type: long + description: Page cache, including tmpfs (shmem), in bytes. + + - name: stats.hierarchical_memory_limit.bytes + type: long + description: > + Memory limit for the hierarchy that contains the memory cgroup, + in bytes. + + - name: stats.hierarchical_memsw_limit.bytes + type: long + description: > + Memory plus swap limit for the hierarchy that contains the + memory cgroup, in bytes. + + - name: stats.inactive_anon.bytes + type: long + description: > + Anonymous and swap cache on inactive LRU list, including tmpfs + (shmem), in bytes + + - name: stats.inactive_file.bytes + type: long + description: > + File-backed memory on inactive LRU list, in bytes. + + - name: stats.mapped_file.bytes + type: long + description: > + Size of memory-mapped mapped files, including tmpfs (shmem), + in bytes. + + - name: stats.page_faults + type: long + description: > + Number of times that a process in the cgroup triggered a page + fault. + + - name: stats.major_page_faults + type: long + description: > + Number of times that a process in the cgroup triggered a major + fault. "Major" faults happen when the kernel actually has to + read the data from disk. + + - name: stats.pages_in + type: long + description: > + Number of pages paged into memory. This is a counter. + + - name: stats.pages_out + type: long + description: > + Number of pages paged out of memory. This is a counter. + + - name: stats.rss.bytes + type: long + description: > + Anonymous and swap cache (includes transparent hugepages), not + including tmpfs (shmem), in bytes. + + - name: stats.rss_huge.bytes + type: long + description: > + Number of bytes of anonymous transparent hugepages. + + - name: stats.swap.bytes + type: long + description: > + Swap usage, in bytes. + + - name: stats.unevictable.bytes + type: long + description: > + Memory that cannot be reclaimed, in bytes. + + - name: blkio + type: group + description: Block IO metrics. + fields: + - name: id + type: keyword + description: ID of the cgroup. + + - name: path + type: keyword + description: > + Path to the cgroup relative to the cgroup subsystems mountpoint. + + - name: total.bytes + type: keyword + description: > + Total number of bytes transferred to and from all block devices + by processes in the cgroup. + + - name: total.ios + type: keyword + description: > + Total number of I/O operations performed on all devices + by processes in the cgroup as seen by the throttling policy. diff --git a/metricbeat/module/system/process/process.go b/metricbeat/module/system/process/process.go index 51cf89abc14..56b37c25e90 100644 --- a/metricbeat/module/system/process/process.go +++ b/metricbeat/module/system/process/process.go @@ -3,12 +3,19 @@ package process import ( + "runtime" + "strconv" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/gosigar/cgroup" "github.com/pkg/errors" ) +var debugf = logp.MakeDebug("system-process") + func init() { if err := mb.Registry.AddMetricSet("system", "process", New); err != nil { panic(err) @@ -18,7 +25,8 @@ func init() { // MetricSet that fetches process metrics. type MetricSet struct { mb.BaseMetricSet - stats *ProcStats + stats *ProcStats + cgroup *cgroup.Reader } // New creates and returns a new MetricSet. @@ -44,6 +52,14 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { if err != nil { return nil, err } + + if runtime.GOOS == "linux" { + m.cgroup, err = cgroup.NewReader("", true) + if err != nil { + return nil, errors.Wrap(err, "error initializing cgroup reader") + } + } + return m, nil } @@ -55,5 +71,218 @@ func (m *MetricSet) Fetch() ([]common.MapStr, error) { return nil, errors.Wrap(err, "process stats") } + if m.cgroup != nil { + for _, proc := range procs { + pid, ok := proc["pid"].(int) + if !ok { + debugf("error converting pid to int for proc %+v", proc) + continue + } + stats, err := m.cgroup.GetStatsForProcess(pid) + if err != nil { + debugf("error getting cgroups stats for pid=%d, %v", pid, err) + continue + } + + if statsMap := cgroupStatsToMap(stats); statsMap != nil { + proc["cgroup"] = statsMap + } + } + } + return procs, err } + +// cgroupStatsToMap returns a MapStr containing the data from the stats object. +// If stats is nil then nil is returned. +func cgroupStatsToMap(stats *cgroup.Stats) common.MapStr { + if stats == nil { + return nil + } + + cgroup := common.MapStr{} + + // id and path are only available when all subsystems share a common path. + if stats.ID != "" { + cgroup["id"] = stats.ID + } + if stats.Path != "" { + cgroup["path"] = stats.Path + } + + if cpu := cgroupCPUToMapStr(stats.CPU); cpu != nil { + cgroup["cpu"] = cpu + } + if cpuacct := cgroupCPUAccountingToMapStr(stats.CPUAccounting); cpuacct != nil { + cgroup["cpuacct"] = cpuacct + } + if memory := cgroupMemoryToMapStr(stats.Memory); memory != nil { + cgroup["memory"] = memory + } + if blkio := cgroupBlockIOToMapStr(stats.BlockIO); blkio != nil { + cgroup["blkio"] = blkio + } + + return cgroup +} + +// cgroupCPUToMapStr returns a MapStr containing CPUSubsystem data. If the +// cpu parameter is nil then nil is returned. +func cgroupCPUToMapStr(cpu *cgroup.CPUSubsystem) common.MapStr { + if cpu == nil { + return nil + } + + return common.MapStr{ + "id": cpu.ID, + "path": cpu.Path, + "cfs": common.MapStr{ + "period": common.MapStr{ + "us": cpu.CFS.PeriodMicros, + }, + "quota": common.MapStr{ + "us": cpu.CFS.QuotaMicros, + }, + "shares": cpu.CFS.Shares, + }, + "rt": common.MapStr{ + "period": common.MapStr{ + "us": cpu.RT.PeriodMicros, + }, + "runtime": common.MapStr{ + "us": cpu.RT.RuntimeMicros, + }, + }, + "stats": common.MapStr{ + "periods": cpu.Stats.Periods, + "throttled": common.MapStr{ + "periods": cpu.Stats.ThrottledPeriods, + "nanos": cpu.Stats.ThrottledTimeNanos, + }, + }, + } +} + +// cgroupCPUAccountingToMapStr returns a MapStr containing +// CPUAccountingSubsystem data. If the cpuacct parameter is nil then nil is +// returned. +func cgroupCPUAccountingToMapStr(cpuacct *cgroup.CPUAccountingSubsystem) common.MapStr { + if cpuacct == nil { + return nil + } + + perCPUUsage := common.MapStr{} + for i, usage := range cpuacct.UsagePerCPU { + perCPUUsage[strconv.Itoa(i+1)] = usage + } + + return common.MapStr{ + "id": cpuacct.ID, + "path": cpuacct.Path, + "total": common.MapStr{ + "nanos": cpuacct.TotalNanos, + }, + "percpu": perCPUUsage, + "stats": common.MapStr{ + "system": common.MapStr{ + "nanos": cpuacct.Stats.SystemNanos, + }, + "user": common.MapStr{ + "nanos": cpuacct.Stats.UserNanos, + }, + }, + } +} + +// cgroupMemoryToMapStr returns a MapStr containing MemorySubsystem data. If the +// memory parameter is nil then nil is returned. +func cgroupMemoryToMapStr(memory *cgroup.MemorySubsystem) common.MapStr { + if memory == nil { + return nil + } + + addMemData := func(key string, m common.MapStr, data cgroup.MemoryData) { + m[key] = common.MapStr{ + "failures": memory.Mem.FailCount, + "limit": common.MapStr{ + "bytes": memory.Mem.Limit, + }, + "usage": common.MapStr{ + "bytes": memory.Mem.Usage, + "max": common.MapStr{ + "bytes": memory.Mem.MaxUsage, + }, + }, + } + } + + memMap := common.MapStr{ + "id": memory.ID, + "path": memory.Path, + } + addMemData("mem", memMap, memory.Mem) + addMemData("memsw", memMap, memory.MemSwap) + addMemData("kmem", memMap, memory.Kernel) + addMemData("kmem_tcp", memMap, memory.KernelTCP) + memMap["stats"] = common.MapStr{ + "active_anon": common.MapStr{ + "bytes": memory.Stats.ActiveAnon, + }, + "active_file": common.MapStr{ + "bytes": memory.Stats.ActiveFile, + }, + "cache": common.MapStr{ + "bytes": memory.Stats.Cache, + }, + "hierarchical_memory_limit": common.MapStr{ + "bytes": memory.Stats.HierarchicalMemoryLimit, + }, + "hierarchical_memsw_limit": common.MapStr{ + "bytes": memory.Stats.HierarchicalMemswLimit, + }, + "inactive_anon": common.MapStr{ + "bytes": memory.Stats.InactiveAnon, + }, + "inactive_file": common.MapStr{ + "bytes": memory.Stats.InactiveFile, + }, + "mapped_file": common.MapStr{ + "bytes": memory.Stats.MappedFile, + }, + "page_faults": memory.Stats.PageFaults, + "major_page_faults": memory.Stats.MajorPageFaults, + "pages_in": memory.Stats.PagesIn, + "pages_out": memory.Stats.PagesOut, + "rss": common.MapStr{ + "bytes": memory.Stats.RSS, + }, + "rss_huge": common.MapStr{ + "bytes": memory.Stats.RSSHuge, + }, + "swap": common.MapStr{ + "bytes": memory.Stats.Swap, + }, + "unevictable": common.MapStr{ + "bytes": memory.Stats.Unevictable, + }, + } + + return memMap +} + +// cgroupBlockIOToMapStr returns a MapStr containing BlockIOSubsystem data. +// If the blockIO parameter is nil then nil is returned. +func cgroupBlockIOToMapStr(blockIO *cgroup.BlockIOSubsystem) common.MapStr { + if blockIO == nil { + return nil + } + + return common.MapStr{ + "id": blockIO.ID, + "path": blockIO.Path, + "total": common.MapStr{ + "bytes": blockIO.Throttle.TotalBytes, + "ios": blockIO.Throttle.TotalIOs, + }, + } +} diff --git a/vendor/github.com/elastic/gosigar/cgroup/blkio.go b/vendor/github.com/elastic/gosigar/cgroup/blkio.go new file mode 100644 index 00000000000..289bfafad7f --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/blkio.go @@ -0,0 +1,293 @@ +package cgroup + +import ( + "bufio" + "os" + "path/filepath" + "strconv" + "strings" + "unicode" +) + +// BlockIOSubsystem contains limits and metrics from the "blkio" subsystem. The +// blkio subsystem controls and monitors access to I/O on block devices by tasks +// in a cgroup. +// +// https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt +type BlockIOSubsystem struct { + Metadata + Throttle ThrottlePolicy `json:"throttle,omitempty"` // Throttle limits for upper IO rates and metrics. + //CFQ CFQScheduler `json:"cfq,omitempty"` // Completely fair queue scheduler limits and metrics. +} + +// CFQScheduler contains limits and metrics for the proportional weight time +// based division of disk policy. It is implemented in CFQ. Hence this policy +// takes effect only on leaf nodes when CFQ is being used. +// +// https://www.kernel.org/doc/Documentation/block/cfq-iosched.txt +type CFQScheduler struct { + Weight uint64 `json:"weight"` // Default weight for all devices unless overridden. Allowed range of weights is from 10 to 1000. + Devices []CFQDevice `json:"devices,omitempty"` +} + +// CFQDevice contains CFQ limits and metrics associated with a single device. +type CFQDevice struct { + DeviceID DeviceID `json:"device_id"` // ID of the device. + + // Proportional weight for the device. 0 means a per device weight is not set and + // that the blkio.weight value is used. + Weight uint64 `json:"weight"` + + TimeMs uint64 `json:"time_ms"` // Disk time allocated to cgroup per device in milliseconds. + Sectors uint64 `json:"sectors"` // Number of sectors transferred to/from disk by the cgroup. + Bytes OperationValues `json:"io_service_bytes"` // Number of bytes transferred to/from the disk by the cgroup. + IOs OperationValues `json:"io_serviced"` // Number of IO operations issued to the disk by the cgroup. + ServiceTimeNanos OperationValues `json:"io_service_time"` // Amount of time between request dispatch and request completion for the IOs done by this cgroup. + WaitTimeNanos OperationValues `json:"io_wait_time"` // Amount of time the IOs for this cgroup spent waiting in the scheduler queues for service. + Merges OperationValues `json:"io_merged"` // Total number of bios/requests merged into requests belonging to this cgroup. +} + +// ThrottlePolicy contains the upper IO limits and metrics for devices used +// by the cgroup. +type ThrottlePolicy struct { + Devices []ThrottleDevice `json:"devices,omitempty"` // Device centric view of limits and metrics. + TotalBytes uint64 `json:"total_io_service_bytes"` // Total number of bytes serviced by all devices. + TotalIOs uint64 `json:"total_io_serviced"` // Total number of IO operations serviced by all devices. +} + +// ThrottleDevice contains throttle limits and metrics associated with a single device. +type ThrottleDevice struct { + DeviceID DeviceID `json:"device_id"` // ID of the device. + + ReadLimitBPS uint64 `json:"read_bps_device"` // Read limit in bytes per second (BPS). Zero means no limit. + WriteLimitBPS uint64 `json:"write_bps_device"` // Write limit in bytes per second (BPS). Zero mean no limit. + ReadLimitIOPS uint64 `json:"read_iops_device"` // Read limit in IOPS. Zero means no limit. + WriteLimitIOPS uint64 `json:"write_iops_device"` // Write limit in IOPS. Zero means no limit. + + Bytes OperationValues `json:"io_service_bytes"` // Number of bytes transferred to/from the disk by the cgroup. + IOs OperationValues `json:"io_serviced"` // Number of IO operations issued to the disk by the cgroup. +} + +// OperationValues contains the I/O limits or metrics associated with read, +// write, sync, and async operations. +type OperationValues struct { + Read uint64 `json:"read"` + Write uint64 `json:"write"` + Async uint64 `json:"async"` + Sync uint64 `json:"sync"` +} + +// DeviceID identifies a Linux block device. +type DeviceID struct { + Major uint64 + Minor uint64 +} + +// blkioValue holds a single blkio value associated with a device. +type blkioValue struct { + DeviceID + Operation string + Value uint64 +} + +// get reads metrics from the "blkio" subsystem. path is the filepath to the +// cgroup hierarchy to read. +func (blkio *BlockIOSubsystem) get(path string) error { + if err := blkioThrottle(path, blkio); err != nil { + return err + } + + // TODO(akroh): Implement reading for the CFQ values. + + return nil +} + +// blkioThrottle reads all of the limits and metrics associated with blkio +// throttling policy. +func blkioThrottle(path string, blkio *BlockIOSubsystem) error { + devices := map[DeviceID]*ThrottleDevice{} + + getDevice := func(id DeviceID) *ThrottleDevice { + td := devices[id] + if td == nil { + td = &ThrottleDevice{DeviceID: id} + devices[id] = td + } + return td + } + + values, err := readBlkioValues(path, "blkio.throttle.io_service_bytes") + if err != nil { + return err + } + if values != nil { + for id, opValues := range collectOpValues(values) { + getDevice(id).Bytes = *opValues + } + } + + values, err = readBlkioValues(path, "blkio.throttle.io_serviced") + if err != nil { + return err + } + if values != nil { + for id, opValues := range collectOpValues(values) { + getDevice(id).IOs = *opValues + } + } + + values, err = readBlkioValues(path, "blkio.throttle.read_bps_device") + if err != nil { + return err + } + if values != nil { + for _, bv := range values { + getDevice(bv.DeviceID).ReadLimitBPS = bv.Value + } + } + + values, err = readBlkioValues(path, "blkio.throttle.write_bps_device") + if err != nil { + return err + } + if values != nil { + for _, bv := range values { + getDevice(bv.DeviceID).WriteLimitBPS = bv.Value + } + } + + values, err = readBlkioValues(path, "blkio.throttle.read_iops_device") + if err != nil { + return err + } + if values != nil { + for _, bv := range values { + getDevice(bv.DeviceID).ReadLimitIOPS = bv.Value + } + } + + values, err = readBlkioValues(path, "blkio.throttle.write_iops_device") + if err != nil { + return err + } + if values != nil { + for _, bv := range values { + getDevice(bv.DeviceID).WriteLimitIOPS = bv.Value + } + } + + blkio.Throttle.Devices = make([]ThrottleDevice, 0, len(devices)) + for _, dev := range devices { + blkio.Throttle.Devices = append(blkio.Throttle.Devices, *dev) + blkio.Throttle.TotalBytes += dev.Bytes.Read + dev.Bytes.Write + blkio.Throttle.TotalIOs += dev.IOs.Read + dev.IOs.Write + } + + return nil +} + +// collectOpValues collects the discreet I/O values (e.g. read, write, sync, +// async) for a given device into a single OperationValues object. It returns a +// mapping of device ID to OperationValues. +func collectOpValues(values []blkioValue) map[DeviceID]*OperationValues { + opValues := map[DeviceID]*OperationValues{} + for _, bv := range values { + opValue := opValues[bv.DeviceID] + if opValue == nil { + opValue = &OperationValues{} + opValues[bv.DeviceID] = opValue + } + + switch bv.Operation { + case "read": + opValue.Read = bv.Value + case "write": + opValue.Write = bv.Value + case "async": + opValue.Async = bv.Value + case "sync": + opValue.Sync = bv.Value + } + } + + return opValues +} + +// readDeviceValues reads values from a single blkio file. +// It expects to read values like "245:1 read 18880" or "254:1 1909". It returns +// an array containing an entry for each valid line read. +func readBlkioValues(path ...string) ([]blkioValue, error) { + f, err := os.Open(filepath.Join(path...)) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + defer f.Close() + + var values []blkioValue + sc := bufio.NewScanner(f) + for sc.Scan() { + line := strings.TrimSpace(sc.Text()) + if len(line) == 0 { + continue + } + // Valid lines start with a device ID. + if !unicode.IsNumber(rune(line[0])) { + continue + } + + v, err := parseBlkioValue(sc.Text()) + if err != nil { + return nil, err + } + + values = append(values, v) + } + + return values, nil +} + +func isColonOrSpace(r rune) bool { + return unicode.IsSpace(r) || r == ':' +} + +func parseBlkioValue(line string) (blkioValue, error) { + fields := strings.FieldsFunc(line, isColonOrSpace) + if len(fields) != 3 && len(fields) != 4 { + return blkioValue{}, ErrInvalidFormat + } + + major, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return blkioValue{}, err + } + + minor, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return blkioValue{}, err + } + + var value uint64 + var operation string + if len(fields) == 3 { + value, err = parseUint([]byte(fields[2])) + if err != nil { + return blkioValue{}, err + } + } else { + operation = strings.ToLower(fields[2]) + + value, err = parseUint([]byte(fields[3])) + if err != nil { + return blkioValue{}, err + } + } + + return blkioValue{ + DeviceID: DeviceID{major, minor}, + Operation: operation, + Value: value, + }, nil +} diff --git a/vendor/github.com/elastic/gosigar/cgroup/cpu.go b/vendor/github.com/elastic/gosigar/cgroup/cpu.go new file mode 100644 index 00000000000..88681231a20 --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/cpu.go @@ -0,0 +1,139 @@ +package cgroup + +import ( + "bufio" + "os" + "path/filepath" +) + +// CPUSubsystem contains metrics and limits from the "cpu" subsystem. This +// subsystem is used to guarantee a minimum number of cpu shares to the cgroup +// when the system is busy. This subsystem does not track CPU usage, for that +// information see the "cpuacct" subsystem. +type CPUSubsystem struct { + Metadata + // Completely Fair Scheduler (CFS) settings. + CFS CFS `json:"cfs,omitempty"` + // Real-time (RT) Scheduler settings. + RT RT `json:"rt,omitempty"` + // CPU time statistics for tasks in this cgroup. + Stats ThrottleStats `json:"stats,omitempty"` +} + +// RT contains the tunable parameters for the real-time scheduler. +type RT struct { + // Period of time in microseconds for how regularly the cgroup's access to + // CPU resources should be reallocated. + PeriodMicros uint64 `json:"period_us"` + // Period of time in microseconds for the longest continuous period in which + // the tasks in the cgroup have access to CPU resources. + RuntimeMicros uint64 `json:"quota_us"` +} + +// CFS contains the tunable parameters for the completely fair scheduler. +type CFS struct { + // Period of time in microseconds for how regularly the cgroup's access to + // CPU resources should be reallocated. + PeriodMicros uint64 `json:"period_us"` + // Total amount of time in microseconds for which all tasks in the cgroup + // can run during one period. + QuotaMicros uint64 `json:"quota_us"` + // Relative share of CPU time available to tasks the cgroup. The value is + // an integer greater than or equal to 2. + Shares uint64 `json:"shares"` +} + +// ThrottleStats contains stats that indicate the extent to which this cgroup's +// CPU usage was throttled. +type ThrottleStats struct { + // Number of periods with throttling active. + Periods uint64 `json:"periods,omitempty"` + // Number of periods when the cgroup hit its throttling limit. + ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` + // Aggregate time the cgroup was throttled for in nanoseconds. + ThrottledTimeNanos uint64 `json:"throttled_nanos,omitempty"` +} + +// get reads metrics from the "cpu" subsystem. path is the filepath to the +// cgroup hierarchy to read. +func (cpu *CPUSubsystem) get(path string) error { + if err := cpuCFS(path, cpu); err != nil { + return err + } + + if err := cpuRT(path, cpu); err != nil { + return err + } + + if err := cpuStat(path, cpu); err != nil { + return err + } + + return nil +} + +func cpuStat(path string, cpu *CPUSubsystem) error { + f, err := os.Open(filepath.Join(path, "cpu.stat")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer f.Close() + + sc := bufio.NewScanner(f) + for sc.Scan() { + t, v, err := parseCgroupParamKeyValue(sc.Text()) + if err != nil { + return err + } + switch t { + case "nr_periods": + cpu.Stats.Periods = v + + case "nr_throttled": + cpu.Stats.ThrottledPeriods = v + + case "throttled_time": + cpu.Stats.ThrottledTimeNanos = v + } + } + + return nil +} + +func cpuCFS(path string, cpu *CPUSubsystem) error { + var err error + cpu.CFS.PeriodMicros, err = parseUintFromFile(path, "cpu.cfs_period_us") + if err != nil { + return err + } + + cpu.CFS.QuotaMicros, err = parseUintFromFile(path, "cpu.cfs_quota_us") + if err != nil { + return err + } + + cpu.CFS.Shares, err = parseUintFromFile(path, "cpu.shares") + if err != nil { + return err + } + + return nil +} + +func cpuRT(path string, cpu *CPUSubsystem) error { + var err error + cpu.RT.PeriodMicros, err = parseUintFromFile(path, "cpu.rt_period_us") + if err != nil { + return err + } + + cpu.RT.RuntimeMicros, err = parseUintFromFile(path, "cpu.rt_runtime_us") + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/elastic/gosigar/cgroup/cpuacct.go b/vendor/github.com/elastic/gosigar/cgroup/cpuacct.go new file mode 100644 index 00000000000..871e2f8f8fb --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/cpuacct.go @@ -0,0 +1,109 @@ +package cgroup + +import ( + "bufio" + "bytes" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/elastic/gosigar/util" +) + +var clockTicks = uint64(util.GetClockTicks()) + +// CPUAccountingSubsystem contains metrics from the "cpuacct" subsystem. +type CPUAccountingSubsystem struct { + Metadata + TotalNanos uint64 `json:"total_nanos"` + UsagePerCPU []uint64 `json:"usage_percpu_nanos"` + // CPU time statistics for tasks in this cgroup. + Stats CPUAccountingStats `json:"stats,omitempty"` +} + +// CPUAccountingStats contains the stats reported from the cpuacct subsystem. +type CPUAccountingStats struct { + UserNanos uint64 `json:"user_nanos"` + SystemNanos uint64 `json:"system_nanos"` +} + +// get reads metrics from the "cpuacct" subsystem. path is the filepath to the +// cgroup hierarchy to read. +func (cpuacct *CPUAccountingSubsystem) get(path string) error { + if err := cpuacctStat(path, cpuacct); err != nil { + return err + } + + if err := cpuacctUsage(path, cpuacct); err != nil { + return err + } + + if err := cpuacctUsagePerCPU(path, cpuacct); err != nil { + return err + } + + return nil +} + +func cpuacctStat(path string, cpuacct *CPUAccountingSubsystem) error { + f, err := os.Open(filepath.Join(path, "cpuacct.stat")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer f.Close() + + sc := bufio.NewScanner(f) + for sc.Scan() { + t, v, err := parseCgroupParamKeyValue(sc.Text()) + if err != nil { + return err + } + switch t { + case "user": + cpuacct.Stats.UserNanos = convertJiffiesToNanos(v) + case "system": + cpuacct.Stats.SystemNanos = convertJiffiesToNanos(v) + } + } + + return nil +} + +func cpuacctUsage(path string, cpuacct *CPUAccountingSubsystem) error { + var err error + cpuacct.TotalNanos, err = parseUintFromFile(path, "cpuacct.usage") + if err != nil { + return err + } + + return nil +} + +func cpuacctUsagePerCPU(path string, cpuacct *CPUAccountingSubsystem) error { + contents, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu")) + if err != nil { + return err + } + + var values []uint64 + usages := bytes.Fields(contents) + for _, usage := range usages { + value, err := parseUint(usage) + if err != nil { + return err + } + + values = append(values, value) + } + cpuacct.UsagePerCPU = values + + return nil +} + +func convertJiffiesToNanos(j uint64) uint64 { + return (j * uint64(time.Second)) / clockTicks +} diff --git a/vendor/github.com/elastic/gosigar/cgroup/doc.go b/vendor/github.com/elastic/gosigar/cgroup/doc.go new file mode 100644 index 00000000000..25a81a8dfba --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/doc.go @@ -0,0 +1,11 @@ +// Package cgroup reads metrics and other tunable parameters associated with +// control groups, a Linux kernel feature for grouping tasks to track and limit +// resource usage. +// +// Terminology +// +// A cgroup is a collection of processes that are bound to a set of limits. +// +// A subsystem is a kernel component the modifies the behavior of processes +// in a cgroup. +package cgroup diff --git a/vendor/github.com/elastic/gosigar/cgroup/memory.go b/vendor/github.com/elastic/gosigar/cgroup/memory.go new file mode 100644 index 00000000000..aa50b65efd5 --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/memory.go @@ -0,0 +1,168 @@ +package cgroup + +import ( + "bufio" + "os" + "path/filepath" +) + +// MemorySubsystem contains the metrics and limits from the "memory" subsystem. +type MemorySubsystem struct { + Metadata + Mem MemoryData `json:"mem"` // Memory usage by tasks in this cgroup. + MemSwap MemoryData `json:"memsw"` // Memory plus swap usage by tasks in this cgroup. + Kernel MemoryData `json:"kmem"` // Kernel memory used by tasks in this cgroup. + KernelTCP MemoryData `json:"kmem_tcp"` // Kernel TCP buffer memory used by tasks in this cgroup. + Stats MemoryStat `json:"stats"` // A wide range of memory statistics. +} + +// MemoryData groups related memory usage metrics and limits. +type MemoryData struct { + Usage uint64 `json:"usage"` // Usage in bytes. + MaxUsage uint64 `json:"max_usage"` // Max usage in bytes. + Limit uint64 `json:"limit"` // Limit in bytes. + FailCount uint64 `json:"failure_count"` // Number of times the memory limit has been reached. +} + +// MemoryStat contains various memory statistics and accounting information +// associated with a cgroup. +type MemoryStat struct { + // Page cache, including tmpfs (shmem), in bytes. + Cache uint64 `json:"cache"` + // Anonymous and swap cache, not including tmpfs (shmem), in bytes. + RSS uint64 `json:"rss"` + // Anonymous transparent hugepages in bytes. + RSSHuge uint64 `json:"rss_huge"` + // Size of memory-mapped mapped files, including tmpfs (shmem), in bytes. + MappedFile uint64 `json:"mapped_file"` + // Number of pages paged into memory. + PagesIn uint64 `json:"pgpgin"` + // Number of pages paged out of memory. + PagesOut uint64 `json:"pgpgout"` + // Number of times a task in the cgroup triggered a page fault. + PageFaults uint64 `json:"pgfault"` + // Number of times a task in the cgroup triggered a major page fault. + MajorPageFaults uint64 `json:"pgmajfault"` + // Swap usage in bytes. + Swap uint64 `json:"swap"` + // Anonymous and swap cache on active least-recently-used (LRU) list, including tmpfs (shmem), in bytes. + ActiveAnon uint64 `json:"active_anon"` + // Anonymous and swap cache on inactive LRU list, including tmpfs (shmem), in bytes. + InactiveAnon uint64 `json:"inactive_anon"` + // File-backed memory on active LRU list, in bytes. + ActiveFile uint64 `json:"active_file"` + // File-backed memory on inactive LRU list, in bytes. + InactiveFile uint64 `json:"inactive_file"` + // Memory that cannot be reclaimed, in bytes. + Unevictable uint64 `json:"unevictable"` + // Memory limit for the hierarchy that contains the memory cgroup, in bytes. + HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit"` + // Memory plus swap limit for the hierarchy that contains the memory cgroup, in bytes. + HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit"` +} + +// get reads metrics from the "memory" subsystem. path is the filepath to the +// cgroup hierarchy to read. +func (mem *MemorySubsystem) get(path string) error { + if err := memoryData(path, "memory", &mem.Mem); err != nil { + return err + } + + if err := memoryData(path, "memory.memsw", &mem.MemSwap); err != nil { + return err + } + + if err := memoryData(path, "memory.kmem", &mem.Kernel); err != nil { + return err + } + + if err := memoryData(path, "memory.kmem.tcp", &mem.KernelTCP); err != nil { + return err + } + + if err := memoryStats(path, mem); err != nil { + return err + } + + return nil +} + +func memoryData(path, prefix string, data *MemoryData) error { + var err error + data.Usage, err = parseUintFromFile(path, prefix+".usage_in_bytes") + if err != nil { + return err + } + + data.MaxUsage, err = parseUintFromFile(path, prefix+".max_usage_in_bytes") + if err != nil { + return err + } + + data.Limit, err = parseUintFromFile(path, prefix+".limit_in_bytes") + if err != nil { + return err + } + + data.FailCount, err = parseUintFromFile(path, prefix+".failcnt") + if err != nil { + return err + } + + return nil +} + +func memoryStats(path string, mem *MemorySubsystem) error { + f, err := os.Open(filepath.Join(path, "memory.stat")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer f.Close() + + sc := bufio.NewScanner(f) + for sc.Scan() { + t, v, err := parseCgroupParamKeyValue(sc.Text()) + if err != nil { + return err + } + switch t { + case "cache": + mem.Stats.Cache = v + case "rss": + mem.Stats.RSS = v + case "rss_huge": + mem.Stats.RSSHuge = v + case "mapped_file": + mem.Stats.MappedFile = v + case "pgpgin": + mem.Stats.PagesIn = v + case "pgpgout": + mem.Stats.PagesOut = v + case "pgfault": + mem.Stats.PageFaults = v + case "pgmajfault": + mem.Stats.MajorPageFaults = v + case "swap": + mem.Stats.Swap = v + case "active_anon": + mem.Stats.ActiveAnon = v + case "inactive_anon": + mem.Stats.InactiveAnon = v + case "active_file": + mem.Stats.ActiveFile = v + case "inactive_file": + mem.Stats.InactiveFile = v + case "unevictable": + mem.Stats.Unevictable = v + case "hierarchical_memory_limit": + mem.Stats.HierarchicalMemoryLimit = v + case "hierarchical_memsw_limit": + mem.Stats.HierarchicalMemswLimit = v + } + } + + return nil +} diff --git a/vendor/github.com/elastic/gosigar/cgroup/reader.go b/vendor/github.com/elastic/gosigar/cgroup/reader.go new file mode 100644 index 00000000000..7e50c0c2f0a --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/reader.go @@ -0,0 +1,162 @@ +package cgroup + +import ( + "path/filepath" +) + +// Stats contains metrics and limits from each of the cgroup subsystems. +type Stats struct { + Metadata + CPU *CPUSubsystem `json:"cpu"` + CPUAccounting *CPUAccountingSubsystem `json:"cpuacct"` + Memory *MemorySubsystem `json:"memory"` + BlockIO *BlockIOSubsystem `json:"blkio"` +} + +// Metadata contains metadata associated with cgroup stats. +type Metadata struct { + ID string `json:"id,omitempty"` // ID of the cgroup. + Path string `json:"path,omitempty"` // Path to the cgroup relative to the cgroup subsystem's mountpoint. +} + +type mount struct { + subsystem string // Subsystem name (e.g. cpuacct). + mountpoint string // Mountpoint of the subsystem (e.g. /cgroup/cpuacct). + path string // Relative path to the cgroup (e.g. /docker/). + id string // ID of the cgroup. + fullPath string // Absolute path to the cgroup. It's the mountpoint joined with the path. +} + +// Reader reads cgroup metrics and limits. +type Reader struct { + // Mountpoint of the root filesystem. Defaults to / if not set. This can be + // useful for example if you mount / as /rootfs inside of a container. + rootfsMountpoint string + ignoreRootCgroups bool // Ignore a cgroup when its path is "/". + cgroupMountpoints map[string]string // Mountpoints for each subsystem (e.g. cpu, cpuacct, memory, blkio). +} + +// NewReader creates and returns a new Reader. +func NewReader(rootfsMountpoint string, ignoreRootCgroups bool) (*Reader, error) { + if rootfsMountpoint == "" { + rootfsMountpoint = "/" + } + + // Determine what subsystems are supported by the kernel. + subsystems, err := SupportedSubsystems(rootfsMountpoint) + if err != nil { + return nil, err + } + + // Locate the mountpoints of those subsystems. + mountpoints, err := SubsystemMountpoints(rootfsMountpoint, subsystems) + if err != nil { + return nil, err + } + + return &Reader{ + rootfsMountpoint: rootfsMountpoint, + ignoreRootCgroups: ignoreRootCgroups, + cgroupMountpoints: mountpoints, + }, nil +} + +// GetStatsForProcess returns cgroup metrics and limits associated with a process. +func (r *Reader) GetStatsForProcess(pid int) (*Stats, error) { + // Read /proc/[pid]/cgroup to get the paths to the cgroup metrics. + paths, err := ProcessCgroupPaths(r.rootfsMountpoint, pid) + if err != nil { + return nil, err + } + + // Build the full path for the subsystems we are interested in. + mounts := map[string]mount{} + for _, interestedSubsystem := range []string{"blkio", "cpu", "cpuacct", "memory"} { + path, found := paths[interestedSubsystem] + if !found { + continue + } + + if path == "/" && r.ignoreRootCgroups { + continue + } + + subsystemMount, found := r.cgroupMountpoints[interestedSubsystem] + if !found { + continue + } + + mounts[interestedSubsystem] = mount{ + subsystem: interestedSubsystem, + mountpoint: filepath.Join(r.rootfsMountpoint, subsystemMount), + path: path, + id: filepath.Base(path), + fullPath: filepath.Join(r.rootfsMountpoint, subsystemMount, path), + } + } + + stats := Stats{Metadata: getCommonCgroupMetadata(mounts)} + + // Collect stats from each cgroup subsystem associated with the task. + if mount, found := mounts["blkio"]; found { + stats.BlockIO = &BlockIOSubsystem{} + err := stats.BlockIO.get(mount.fullPath) + if err != nil { + return nil, err + } + stats.BlockIO.Metadata.ID = mount.id + stats.BlockIO.Metadata.Path = mount.path + } + if mount, found := mounts["cpu"]; found { + stats.CPU = &CPUSubsystem{} + err := stats.CPU.get(mount.fullPath) + if err != nil { + return nil, err + } + stats.CPU.Metadata.ID = mount.id + stats.CPU.Metadata.Path = mount.path + } + if mount, found := mounts["cpuacct"]; found { + stats.CPUAccounting = &CPUAccountingSubsystem{} + err := stats.CPUAccounting.get(mount.fullPath) + if err != nil { + return nil, err + } + stats.CPUAccounting.Metadata.ID = mount.id + stats.CPUAccounting.Metadata.Path = mount.path + } + if mount, found := mounts["memory"]; found { + stats.Memory = &MemorySubsystem{} + err := stats.Memory.get(mount.fullPath) + if err != nil { + return nil, err + } + stats.Memory.Metadata.ID = mount.id + stats.Memory.Metadata.Path = mount.path + } + + // Return nil if no metrics were collected. + if stats.BlockIO == nil && stats.CPU == nil && stats.CPUAccounting == nil && stats.Memory == nil { + return nil, nil + } + + return &stats, nil +} + +// getCommonCgroupMetadata returns Metadata containing the cgroup path and ID +// iff all subsystems share a common path and ID. This is common for +// containerized processes. If there is no common path and ID then the returned +// values are empty strings. +func getCommonCgroupMetadata(mounts map[string]mount) Metadata { + var path string + for _, m := range mounts { + if path == "" { + path = m.path + } else if path != m.path { + // All paths are not the same. + return Metadata{} + } + } + + return Metadata{Path: path, ID: filepath.Base(path)} +} diff --git a/vendor/github.com/elastic/gosigar/cgroup/util.go b/vendor/github.com/elastic/gosigar/cgroup/util.go new file mode 100644 index 00000000000..3f52797c7e2 --- /dev/null +++ b/vendor/github.com/elastic/gosigar/cgroup/util.go @@ -0,0 +1,179 @@ +package cgroup + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" +) + +var ( + // ErrInvalidFormat indicates a malformed key/value pair on a line. + ErrInvalidFormat = errors.New("error invalid key/value format") +) + +// Parses a cgroup param and returns the key name and value. +func parseCgroupParamKeyValue(t string) (string, uint64, error) { + parts := strings.Fields(t) + if len(parts) != 2 { + return "", 0, ErrInvalidFormat + } + + value, err := parseUint([]byte(parts[1])) + if err != nil { + return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err) + } + + return parts[0], value, nil +} + +// parseUintFromFile reads a single uint value from a file. +func parseUintFromFile(path ...string) (uint64, error) { + value, err := ioutil.ReadFile(filepath.Join(path...)) + if err != nil { + return 0, err + } + + return parseUint(value) +} + +// parseUint reads a single uint value. It will trip any whitespace before +// attempting to parse string. If the value is negative it will return 0. +func parseUint(value []byte) (uint64, error) { + strValue := string(bytes.TrimSpace(value)) + uintValue, err := strconv.ParseUint(strValue, 10, 64) + if err != nil { + // Munge negative values to 0. + intValue, intErr := strconv.ParseInt(strValue, 10, 64) + if intErr == nil && intValue < 0 { + return 0, nil + } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 { + return 0, nil + } + + return 0, err + } + + return uintValue, nil +} + +// SupportedSubsystems returns the subsystems that are supported by the +// kernel. The returned map contains a entry for each subsystem. +func SupportedSubsystems(rootfsMountpoint string) (map[string]struct{}, error) { + if rootfsMountpoint == "" { + rootfsMountpoint = "/" + } + + cgroups, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "cgroups")) + if err != nil { + return nil, err + } + + subsystemSet := map[string]struct{}{} + sc := bufio.NewScanner(cgroups) + for sc.Scan() { + line := sc.Text() + + // Ignore the header. + if len(line) > 0 && line[0] == '#' { + continue + } + + fields := strings.Fields(line) + if len(fields) == 0 { + continue + } + + subsystem := fields[0] + subsystemSet[subsystem] = struct{}{} + } + + return subsystemSet, nil +} + +// SubsystemMountpoints returns the mountpoints for each of the given subsystems. +// The returned map contains the subsystem name as a key and the value is the +// mountpoint. +func SubsystemMountpoints(rootfsMountpoint string, subsystems map[string]struct{}) (map[string]string, error) { + if rootfsMountpoint == "" { + rootfsMountpoint = "/" + } + + mountinfo, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "self", "mountinfo")) + if err != nil { + return nil, err + } + + mounts := map[string]string{} + sc := bufio.NewScanner(mountinfo) + for sc.Scan() { + // https://www.kernel.org/doc/Documentation/filesystems/proc.txt + // Example: + // 25 21 0:20 / /cgroup/cpu rw,relatime - cgroup cgroup rw,cpu + line := sc.Text() + fields := strings.Fields(line) + if len(fields) != 10 || fields[6] != "-" { + return nil, fmt.Errorf("invalid mountinfo data") + } + + if fields[7] != "cgroup" { + continue + } + + mountpoint := fields[4] + opts := strings.Split(fields[9], ",") + for _, opt := range opts { + // XXX(akroh): May need to handle options prepended with 'name='. + // Test if option is a subsystem name. + if _, found := subsystems[opt]; found { + // Add the subsystem mount if it does not already exist. + if _, exists := mounts[opt]; !exists { + mounts[opt] = mountpoint + } + } + } + } + + return mounts, nil +} + +// ProcessCgroupPaths returns the cgroups to which a process belongs and the +// pathname of the cgroup relative to the mountpoint of the subsystem. +func ProcessCgroupPaths(rootfsMountpoint string, pid int) (map[string]string, error) { + if rootfsMountpoint == "" { + rootfsMountpoint = "/" + } + + cgroup, err := os.Open(filepath.Join(rootfsMountpoint, "proc", strconv.Itoa(pid), "cgroup")) + if err != nil { + return nil, err + } + + paths := map[string]string{} + sc := bufio.NewScanner(cgroup) + for sc.Scan() { + // http://man7.org/linux/man-pages/man7/cgroups.7.html + // Format: hierarchy-ID:subsystem-list:cgroup-path + // Example: + // 2:cpu:/docker/b29faf21b7eff959f64b4192c34d5d67a707fe8561e9eaa608cb27693fba4242 + line := sc.Text() + + fields := strings.Split(line, ":") + if len(fields) != 3 { + continue + } + + path := fields[2] + subsystems := strings.Split(fields[1], ",") + for _, subsystem := range subsystems { + paths[subsystem] = path + } + } + + return paths, nil +} diff --git a/vendor/github.com/elastic/gosigar/util/sysconf_cgo.go b/vendor/github.com/elastic/gosigar/util/sysconf_cgo.go new file mode 100644 index 00000000000..52aa84f6f0a --- /dev/null +++ b/vendor/github.com/elastic/gosigar/util/sysconf_cgo.go @@ -0,0 +1,12 @@ +// +build linux,cgo + +package util + +/* +#include +*/ +import "C" + +func GetClockTicks() int { + return int(C.sysconf(C._SC_CLK_TCK)) +} diff --git a/vendor/github.com/elastic/gosigar/util/sysconf_nocgo.go b/vendor/github.com/elastic/gosigar/util/sysconf_nocgo.go new file mode 100644 index 00000000000..b5d4d4e8f39 --- /dev/null +++ b/vendor/github.com/elastic/gosigar/util/sysconf_nocgo.go @@ -0,0 +1,7 @@ +// +build !cgo !linux + +package util + +func GetClockTicks() int { + return 100 +}