diff options
author | Hridya Valsaraju <hridya@google.com> | 2021-06-03 14:47:51 -0700 |
---|---|---|
committer | Daniel Vetter <daniel.vetter@ffwll.ch> | 2021-06-15 11:50:24 +0200 |
commit | bdb8d06dfefd666d5981d884b535b04105869fcc (patch) | |
tree | 7e1e7d7099de0471456ef4f57cd5f0107181a2f8 /drivers/dma-buf/dma-buf-sysfs-stats.c | |
parent | aae74ff9caa8de9a45ae2e46068c417817392a26 (diff) | |
download | linux-bdb8d06dfefd666d5981d884b535b04105869fcc.tar.gz linux-bdb8d06dfefd666d5981d884b535b04105869fcc.tar.bz2 linux-bdb8d06dfefd666d5981d884b535b04105869fcc.zip |
dmabuf: Add the capability to expose DMA-BUF stats in sysfs
Overview
========
The patch adds DMA-BUF statistics to /sys/kernel/dmabuf/buffers. It
allows statistics to be enabled for each DMA-BUF in sysfs by enabling
the config CONFIG_DMABUF_SYSFS_STATS.
The following stats will be exposed by the interface:
/sys/kernel/dmabuf/buffers/<inode_number>/exporter_name
/sys/kernel/dmabuf/buffers/<inode_number>/size
/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attach_uid>/device
/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attach_uid>/map_counter
The inode_number is unique for each DMA-BUF and was added earlier [1]
in order to allow userspace to track DMA-BUF usage across different
processes.
Use Cases
=========
The interface provides a way to gather DMA-BUF per-buffer statistics
from production devices. These statistics will be used to derive DMA-BUF
per-exporter stats and per-device usage stats for Android Bug reports.
The corresponding userspace changes can be found at [2].
Telemetry tools will also capture this information(along with other
memory metrics) periodically as well as on important events like a
foreground app kill (which might have been triggered by Low Memory
Killer). It will also contribute to provide a snapshot of the system
memory usage on other events such as OOM kills and Application Not
Responding events.
Background
==========
Currently, there are two existing interfaces that provide information
about DMA-BUFs.
1) /sys/kernel/debug/dma_buf/bufinfo
debugfs is however unsuitable to be mounted in production systems and
cannot be considered as an alternative to the sysfs interface being
proposed.
2) proc/<pid>/fdinfo/<fd>
The proc/<pid>/fdinfo/<fd> files expose information about DMA-BUF fds.
However, the existing procfs interfaces can only provide information
about the buffers for which processes hold fds or have the buffers
mmapped into their address space. Since the procfs interfaces alone
cannot provide a full picture of all DMA-BUFs in the system, there is
the need for an alternate interface to provide this information on
production systems.
The patch contains the following major improvements over v1:
1) Each attachment is represented by its own directory to allow creating
a symlink to the importing device and to also provide room for future
expansion.
2) The number of distinct mappings of each attachment is exposed in a
separate file.
3) The per-buffer statistics are now in /sys/kernel/dmabuf/buffers
inorder to make the interface expandable in future.
All of the improvements above are based on suggestions/feedback from
Daniel Vetter and Christian König.
A shell script that can be run on a classic Linux environment to read
out the DMA-BUF statistics can be found at [3](suggested by John
Stultz).
[1]: https://lore.kernel.org/patchwork/patch/1088791/
[2]: https://android-review.googlesource.com/q/topic:%22dmabuf-sysfs%22+(status:open%20OR%20status:merged)
[3]: https://android-review.googlesource.com/c/platform/system/memory/libmeminfo/+/1549734
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Hridya Valsaraju <hridya@google.com>
Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/20210603214758.2955251-1-hridya@google.com
Diffstat (limited to 'drivers/dma-buf/dma-buf-sysfs-stats.c')
-rw-r--r-- | drivers/dma-buf/dma-buf-sysfs-stats.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/drivers/dma-buf/dma-buf-sysfs-stats.c b/drivers/dma-buf/dma-buf-sysfs-stats.c new file mode 100644 index 000000000000..a2638e84199c --- /dev/null +++ b/drivers/dma-buf/dma-buf-sysfs-stats.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DMA-BUF sysfs statistics. + * + * Copyright (C) 2021 Google LLC. + */ + +#include <linux/dma-buf.h> +#include <linux/dma-resv.h> +#include <linux/kobject.h> +#include <linux/printk.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include "dma-buf-sysfs-stats.h" + +#define to_dma_buf_entry_from_kobj(x) container_of(x, struct dma_buf_sysfs_entry, kobj) + +/** + * DOC: overview + * + * ``/sys/kernel/debug/dma_buf/bufinfo`` provides an overview of every DMA-BUF + * in the system. However, since debugfs is not safe to be mounted in + * production, procfs and sysfs can be used to gather DMA-BUF statistics on + * production systems. + * + * The ``/proc/<pid>/fdinfo/<fd>`` files in procfs can be used to gather + * information about DMA-BUF fds. Detailed documentation about the interface + * is present in Documentation/filesystems/proc.rst. + * + * Unfortunately, the existing procfs interfaces can only provide information + * about the DMA-BUFs for which processes hold fds or have the buffers mmapped + * into their address space. This necessitated the creation of the DMA-BUF sysfs + * statistics interface to provide per-buffer information on production systems. + * + * The interface at ``/sys/kernel/dma-buf/buffers`` exposes information about + * every DMA-BUF when ``CONFIG_DMABUF_SYSFS_STATS`` is enabled. + * + * The following stats are exposed by the interface: + * + * * ``/sys/kernel/dmabuf/buffers/<inode_number>/exporter_name`` + * * ``/sys/kernel/dmabuf/buffers/<inode_number>/size`` + * * ``/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attach_uid>/device`` + * * ``/sys/kernel/dmabuf/buffers/<inode_number>/attachments/<attach_uid>/map_counter`` + * + * The information in the interface can also be used to derive per-exporter and + * per-device usage statistics. The data from the interface can be gathered + * on error conditions or other important events to provide a snapshot of + * DMA-BUF usage. It can also be collected periodically by telemetry to monitor + * various metrics. + * + * Detailed documentation about the interface is present in + * Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers. + */ + +struct dma_buf_stats_attribute { + struct attribute attr; + ssize_t (*show)(struct dma_buf *dmabuf, + struct dma_buf_stats_attribute *attr, char *buf); +}; +#define to_dma_buf_stats_attr(x) container_of(x, struct dma_buf_stats_attribute, attr) + +static ssize_t dma_buf_stats_attribute_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct dma_buf_stats_attribute *attribute; + struct dma_buf_sysfs_entry *sysfs_entry; + struct dma_buf *dmabuf; + + attribute = to_dma_buf_stats_attr(attr); + sysfs_entry = to_dma_buf_entry_from_kobj(kobj); + dmabuf = sysfs_entry->dmabuf; + + if (!dmabuf || !attribute->show) + return -EIO; + + return attribute->show(dmabuf, attribute, buf); +} + +static const struct sysfs_ops dma_buf_stats_sysfs_ops = { + .show = dma_buf_stats_attribute_show, +}; + +static ssize_t exporter_name_show(struct dma_buf *dmabuf, + struct dma_buf_stats_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", dmabuf->exp_name); +} + +static ssize_t size_show(struct dma_buf *dmabuf, + struct dma_buf_stats_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%zu\n", dmabuf->size); +} + +static struct dma_buf_stats_attribute exporter_name_attribute = + __ATTR_RO(exporter_name); +static struct dma_buf_stats_attribute size_attribute = __ATTR_RO(size); + +static struct attribute *dma_buf_stats_default_attrs[] = { + &exporter_name_attribute.attr, + &size_attribute.attr, + NULL, +}; +ATTRIBUTE_GROUPS(dma_buf_stats_default); + +static void dma_buf_sysfs_release(struct kobject *kobj) +{ + struct dma_buf_sysfs_entry *sysfs_entry; + + sysfs_entry = to_dma_buf_entry_from_kobj(kobj); + kfree(sysfs_entry); +} + +static struct kobj_type dma_buf_ktype = { + .sysfs_ops = &dma_buf_stats_sysfs_ops, + .release = dma_buf_sysfs_release, + .default_groups = dma_buf_stats_default_groups, +}; + +#define to_dma_buf_attach_entry_from_kobj(x) container_of(x, struct dma_buf_attach_sysfs_entry, kobj) + +struct dma_buf_attach_stats_attribute { + struct attribute attr; + ssize_t (*show)(struct dma_buf_attach_sysfs_entry *sysfs_entry, + struct dma_buf_attach_stats_attribute *attr, char *buf); +}; +#define to_dma_buf_attach_stats_attr(x) container_of(x, struct dma_buf_attach_stats_attribute, attr) + +static ssize_t dma_buf_attach_stats_attribute_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct dma_buf_attach_stats_attribute *attribute; + struct dma_buf_attach_sysfs_entry *sysfs_entry; + + attribute = to_dma_buf_attach_stats_attr(attr); + sysfs_entry = to_dma_buf_attach_entry_from_kobj(kobj); + + if (!attribute->show) + return -EIO; + + return attribute->show(sysfs_entry, attribute, buf); +} + +static const struct sysfs_ops dma_buf_attach_stats_sysfs_ops = { + .show = dma_buf_attach_stats_attribute_show, +}; + +static ssize_t map_counter_show(struct dma_buf_attach_sysfs_entry *sysfs_entry, + struct dma_buf_attach_stats_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%u\n", sysfs_entry->map_counter); +} + +static struct dma_buf_attach_stats_attribute map_counter_attribute = + __ATTR_RO(map_counter); + +static struct attribute *dma_buf_attach_stats_default_attrs[] = { + &map_counter_attribute.attr, + NULL, +}; +ATTRIBUTE_GROUPS(dma_buf_attach_stats_default); + +static void dma_buf_attach_sysfs_release(struct kobject *kobj) +{ + struct dma_buf_attach_sysfs_entry *sysfs_entry; + + sysfs_entry = to_dma_buf_attach_entry_from_kobj(kobj); + kfree(sysfs_entry); +} + +static struct kobj_type dma_buf_attach_ktype = { + .sysfs_ops = &dma_buf_attach_stats_sysfs_ops, + .release = dma_buf_attach_sysfs_release, + .default_groups = dma_buf_attach_stats_default_groups, +}; + +void dma_buf_attach_stats_teardown(struct dma_buf_attachment *attach) +{ + struct dma_buf_attach_sysfs_entry *sysfs_entry; + + sysfs_entry = attach->sysfs_entry; + if (!sysfs_entry) + return; + + sysfs_delete_link(&sysfs_entry->kobj, &attach->dev->kobj, "device"); + + kobject_del(&sysfs_entry->kobj); + kobject_put(&sysfs_entry->kobj); +} + +int dma_buf_attach_stats_setup(struct dma_buf_attachment *attach, + unsigned int uid) +{ + struct dma_buf_attach_sysfs_entry *sysfs_entry; + int ret; + struct dma_buf *dmabuf; + + if (!attach) + return -EINVAL; + + dmabuf = attach->dmabuf; + + sysfs_entry = kzalloc(sizeof(struct dma_buf_attach_sysfs_entry), + GFP_KERNEL); + if (!sysfs_entry) + return -ENOMEM; + + sysfs_entry->kobj.kset = dmabuf->sysfs_entry->attach_stats_kset; + + attach->sysfs_entry = sysfs_entry; + + ret = kobject_init_and_add(&sysfs_entry->kobj, &dma_buf_attach_ktype, + NULL, "%u", uid); + if (ret) + goto kobj_err; + + ret = sysfs_create_link(&sysfs_entry->kobj, &attach->dev->kobj, + "device"); + if (ret) + goto link_err; + + return 0; + +link_err: + kobject_del(&sysfs_entry->kobj); +kobj_err: + kobject_put(&sysfs_entry->kobj); + attach->sysfs_entry = NULL; + + return ret; +} +void dma_buf_stats_teardown(struct dma_buf *dmabuf) +{ + struct dma_buf_sysfs_entry *sysfs_entry; + + sysfs_entry = dmabuf->sysfs_entry; + if (!sysfs_entry) + return; + + kset_unregister(sysfs_entry->attach_stats_kset); + kobject_del(&sysfs_entry->kobj); + kobject_put(&sysfs_entry->kobj); +} + + +/* Statistics files do not need to send uevents. */ +static int dmabuf_sysfs_uevent_filter(struct kset *kset, struct kobject *kobj) +{ + return 0; +} + +static const struct kset_uevent_ops dmabuf_sysfs_no_uevent_ops = { + .filter = dmabuf_sysfs_uevent_filter, +}; + +static struct kset *dma_buf_stats_kset; +static struct kset *dma_buf_per_buffer_stats_kset; +int dma_buf_init_sysfs_statistics(void) +{ + dma_buf_stats_kset = kset_create_and_add("dmabuf", + &dmabuf_sysfs_no_uevent_ops, + kernel_kobj); + if (!dma_buf_stats_kset) + return -ENOMEM; + + dma_buf_per_buffer_stats_kset = kset_create_and_add("buffers", + &dmabuf_sysfs_no_uevent_ops, + &dma_buf_stats_kset->kobj); + if (!dma_buf_per_buffer_stats_kset) { + kset_unregister(dma_buf_stats_kset); + return -ENOMEM; + } + + return 0; +} + +void dma_buf_uninit_sysfs_statistics(void) +{ + kset_unregister(dma_buf_per_buffer_stats_kset); + kset_unregister(dma_buf_stats_kset); +} + +int dma_buf_stats_setup(struct dma_buf *dmabuf) +{ + struct dma_buf_sysfs_entry *sysfs_entry; + int ret; + struct kset *attach_stats_kset; + + if (!dmabuf || !dmabuf->file) + return -EINVAL; + + if (!dmabuf->exp_name) { + pr_err("exporter name must not be empty if stats needed\n"); + return -EINVAL; + } + + sysfs_entry = kzalloc(sizeof(struct dma_buf_sysfs_entry), GFP_KERNEL); + if (!sysfs_entry) + return -ENOMEM; + + sysfs_entry->kobj.kset = dma_buf_per_buffer_stats_kset; + sysfs_entry->dmabuf = dmabuf; + + dmabuf->sysfs_entry = sysfs_entry; + + /* create the directory for buffer stats */ + ret = kobject_init_and_add(&sysfs_entry->kobj, &dma_buf_ktype, NULL, + "%lu", file_inode(dmabuf->file)->i_ino); + if (ret) + goto err_sysfs_dmabuf; + + /* create the directory for attachment stats */ + attach_stats_kset = kset_create_and_add("attachments", + &dmabuf_sysfs_no_uevent_ops, + &sysfs_entry->kobj); + if (!attach_stats_kset) { + ret = -ENOMEM; + goto err_sysfs_attach; + } + + sysfs_entry->attach_stats_kset = attach_stats_kset; + + return 0; + +err_sysfs_attach: + kobject_del(&sysfs_entry->kobj); +err_sysfs_dmabuf: + kobject_put(&sysfs_entry->kobj); + dmabuf->sysfs_entry = NULL; + return ret; +} |