Viewing: lproc_lmv.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2012, 2017, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*/
#define DEBUG_SUBSYSTEM S_CLASS
#include <linux/seq_file.h>
#include <linux/statfs.h>
#include <lprocfs_status.h>
#include <obd_class.h>
#include "lmv_internal.h"
static ssize_t numobd_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
return scnprintf(buf, PAGE_SIZE, "%u\n", obd->u.lmv.lmv_mdt_count);
}
LUSTRE_RO_ATTR(numobd);
static ssize_t activeobd_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
return scnprintf(buf, PAGE_SIZE, "%u\n",
obd->u.lmv.lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count);
}
LUSTRE_RO_ATTR(activeobd);
static ssize_t desc_uuid_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
return scnprintf(buf, PAGE_SIZE, "%s\n",
obd->u.lmv.lmv_mdt_descs.ltd_lmv_desc.ld_uuid.uuid);
}
LUSTRE_RO_ATTR(desc_uuid);
static ssize_t qos_maxage_show(struct kobject *kobj,
struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
return scnprintf(buf, PAGE_SIZE, "%u\n",
obd->u.lmv.lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage);
}
static ssize_t qos_maxage_store(struct kobject *kobj,
struct attribute *attr,
const char *buffer,
size_t count)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
unsigned int val;
int rc;
rc = kstrtouint(buffer, 0, &val);
if (rc)
return rc;
obd->u.lmv.lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage = val;
return count;
}
LUSTRE_RW_ATTR(qos_maxage);
static ssize_t qos_prio_free_show(struct kobject *kobj,
struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
return scnprintf(buf, PAGE_SIZE, "%u%%\n",
(obd->u.lmv.lmv_qos.lq_prio_free * 100 + 255) >> 8);
}
static ssize_t qos_prio_free_store(struct kobject *kobj,
struct attribute *attr,
const char *buffer,
size_t count)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
struct lmv_obd *lmv = &obd->u.lmv;
char buf[6], *tmp;
unsigned int val;
int rc;
/* "100%\n\0" should be largest string */
if (count >= sizeof(buf))
return -ERANGE;
strncpy(buf, buffer, sizeof(buf));
buf[sizeof(buf) - 1] = '\0';
tmp = strchr(buf, '%');
if (tmp)
*tmp = '\0';
rc = kstrtouint(buf, 0, &val);
if (rc)
return rc;
if (val > 100)
return -EINVAL;
lmv->lmv_qos.lq_prio_free = (val << 8) / 100;
set_bit(LQ_DIRTY, &lmv->lmv_qos.lq_flags);
set_bit(LQ_RESET, &lmv->lmv_qos.lq_flags);
return count;
}
LUSTRE_RW_ATTR(qos_prio_free);
static ssize_t qos_rr_index_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
struct lmv_obd *lmv = &obd->u.lmv;
return scnprintf(buf, PAGE_SIZE, "%u\n", lmv->lmv_qos_rr_index %
lmv->lmv_mdt_descs.ltd_tgts_size);
}
static ssize_t qos_rr_index_store(struct kobject *kobj, struct attribute *attr,
const char *buffer, size_t count)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
struct lmv_obd *lmv = &obd->u.lmv;
unsigned int val;
int rc;
rc = kstrtouint(buffer, 0, &val);
if (rc)
return rc;
/* It doesn't really matter what value is stored here, since it will
* always be used modulo of the current MDT count. It is actually
* better if this value is not constrained to 0..MDTCOUNT-1, because
* it will continue to work even if the number of MDTs changes.
*/
lmv->lmv_qos_rr_index = val;
return count;
}
LUSTRE_RW_ATTR(qos_rr_index);
static ssize_t qos_threshold_rr_show(struct kobject *kobj,
struct attribute *attr,
char *buf)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
return scnprintf(buf, PAGE_SIZE, "%u%%\n",
(obd->u.lmv.lmv_qos.lq_threshold_rr * 100 +
(QOS_THRESHOLD_MAX - 1)) / QOS_THRESHOLD_MAX);
}
static ssize_t qos_threshold_rr_store(struct kobject *kobj,
struct attribute *attr,
const char *buffer,
size_t count)
{
struct obd_device *obd = container_of(kobj, struct obd_device,
obd_kset.kobj);
struct lmv_obd *lmv = &obd->u.lmv;
char buf[6], *tmp;
unsigned int val;
int rc;
/* "100%\n\0" should be largest string */
if (count >= sizeof(buf))
return -ERANGE;
strncpy(buf, buffer, sizeof(buf));
buf[sizeof(buf) - 1] = '\0';
tmp = strchr(buf, '%');
if (tmp)
*tmp = '\0';
rc = kstrtouint(buf, 0, &val);
if (rc)
return rc;
if (val > 100)
return -EINVAL;
lmv->lmv_qos.lq_threshold_rr = (val * QOS_THRESHOLD_MAX) / 100;
set_bit(LQ_DIRTY, &lmv->lmv_qos.lq_flags);
return count;
}
LUSTRE_RW_ATTR(qos_threshold_rr);
static int qos_exclude_seq_show_internal(struct seq_file *m, void *v,
bool is_prefix)
{
struct obd_device *obd = m->private;
struct lmv_obd *lmv = &obd->u.lmv;
struct qos_exclude_pattern *pat;
restart:
spin_lock(&lmv->lmv_lock);
list_for_each_entry(pat, &lmv->lmv_qos_exclude_list, qep_list) {
if (is_prefix) {
size_t len = strnlen(pat->qep_name, NAME_MAX + 3);
if (len >= 2 && pat->qep_name[len - 2] == '.' &&
pat->qep_name[len - 1] == '*')
continue;
}
seq_printf(m, "%s\n", pat->qep_name);
if (seq_has_overflowed(m)) {
spin_unlock(&lmv->lmv_lock);
kvfree(m->buf);
m->count = 0;
m->buf = kvmalloc(m->size <<= 1, GFP_KERNEL_ACCOUNT);
if (!m->buf)
return -ENOMEM;
goto restart;
}
}
spin_unlock(&lmv->lmv_lock);
return 0;
}
/* directories with exclude patterns will be created on the same MDT as its
* parent directory, the patterns are set with the rule as shell environment
* PATH: ':' is used as separator for patterns. And for convenience, '+/-' is
* used to add/remove patterns.
*/
static int qos_exclude_patterns_seq_show(struct seq_file *m, void *v)
{
return qos_exclude_seq_show_internal(m, v, false);
}
static int qos_exclude_prefixes_seq_show(struct seq_file *m, void *v)
{
return qos_exclude_seq_show_internal(m, v, true);
}
static ssize_t qos_exclude_seq_write_internal(struct file *file,
const char __user *buffer,
size_t count, loff_t *off,
bool is_prefix)
{
struct obd_device *obd;
struct lmv_obd *lmv;
char *buf;
char op = 0;
char *p;
char *name;
char namebuf[NAME_MAX + 3];
struct qos_exclude_pattern *pat;
struct qos_exclude_pattern *tmp;
int len;
bool pruned = false;
bool again = false;
/* one extra char to ensure buf ends with '\0' */
OBD_ALLOC(buf, count + 1);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, buffer, count)) {
OBD_FREE(buf, count + 1);
return -EFAULT;
}
obd = ((struct seq_file *)file->private_data)->private;
lmv = &obd->u.lmv;
p = buf;
while (p) {
while (*p == ':')
p++;
if (*p == '\0')
break;
if (*p == '+' || *p == '-')
op = *p++;
name = p;
p = strchr(name, ':');
if (p)
len = p - name;
else
len = strlen(name);
if (!len)
break;
if (len > NAME_MAX) {
CERROR("%s: %s length exceeds NAME_MAX\n",
obd->obd_name, name);
OBD_FREE(buf, count + 1);
return -ERANGE;
}
strncpy(namebuf, name, len);
namebuf[len] = '\0';
again = is_prefix;
switch (op) {
default:
if (!pruned) {
spin_lock(&lmv->lmv_lock);
list_for_each_entry_safe(pat, tmp,
&lmv->lmv_qos_exclude_list,
qep_list) {
list_del(&pat->qep_list);
OBD_FREE_PTR(pat);
}
spin_unlock(&lmv->lmv_lock);
pruned = true;
}
fallthrough;
case '+':
again_plus:
OBD_ALLOC_PTR(pat);
if (!pat) {
OBD_FREE(buf, count + 1);
return -ENOMEM;
}
strncpy(pat->qep_name, namebuf, len);
spin_lock(&lmv->lmv_lock);
list_add_tail(&pat->qep_list,
&lmv->lmv_qos_exclude_list);
spin_unlock(&lmv->lmv_lock);
if (again) {
again = false;
namebuf[len++] = '.';
namebuf[len++] = '*';
namebuf[len] = '\0';
goto again_plus;
}
break;
case '-':
again_minus:
spin_lock(&lmv->lmv_lock);
list_for_each_entry_safe(pat, tmp,
&lmv->lmv_qos_exclude_list, qep_list) {
if (strcmp(pat->qep_name, namebuf) == 0) {
list_del(&pat->qep_list);
OBD_FREE_PTR(pat);
}
}
spin_unlock(&lmv->lmv_lock);
if (again) {
again = false;
namebuf[len++] = '.';
namebuf[len++] = '*';
namebuf[len] = '\0';
goto again_minus;
}
break;
}
}
OBD_FREE(buf, count + 1);
return count;
}
static ssize_t qos_exclude_patterns_seq_write(struct file *file,
const char __user *buffer,
size_t count, loff_t *off)
{
return qos_exclude_seq_write_internal(file, buffer, count, off,
false);
}
LDEBUGFS_SEQ_FOPS(qos_exclude_patterns);
static ssize_t qos_exclude_prefixes_seq_write(struct file *file,
const char __user *buffer,
size_t count, loff_t *off)
{
return qos_exclude_seq_write_internal(file, buffer, count, off,
true);
}
LDEBUGFS_SEQ_FOPS(qos_exclude_prefixes);
static struct ldebugfs_vars ldebugfs_lmv_obd_vars[] = {
{ .name = "qos_exclude_patterns",
.fops = &qos_exclude_patterns_fops },
{ .name = "qos_exclude_prefixes",
.fops = &qos_exclude_prefixes_fops },
{ NULL }
};
static void *lmv_tgt_seq_start(struct seq_file *p, loff_t *pos)
{
struct obd_device *obd = p->private;
struct lmv_obd *lmv = &obd->u.lmv;
struct lu_tgt_descs *ltd = &lmv->lmv_mdt_descs;
struct lu_tgt_desc *tgt = NULL;
mutex_lock(<d->ltd_mutex);
if (ltd->ltd_lov_desc.ld_tgt_count == 0)
goto out;
while (*pos < lmv->lmv_mdt_descs.ltd_tgts_size) {
tgt = lmv_tgt(lmv, (__u32)*pos);
if (tgt)
goto out;
++*pos;
}
out:
mutex_unlock(<d->ltd_mutex);
return tgt;
}
static void lmv_tgt_seq_stop(struct seq_file *p, void *v)
{
}
static void *lmv_tgt_seq_next(struct seq_file *p, void *v, loff_t *pos)
{
struct obd_device *obd = p->private;
struct lmv_obd *lmv = &obd->u.lmv;
struct lu_tgt_descs *ltd = &lmv->lmv_mdt_descs;
struct lu_tgt_desc *tgt = NULL;
mutex_lock(<d->ltd_mutex);
++*pos;
while (*pos < ltd->ltd_tgts_size) {
tgt = lmv_tgt(lmv, (__u32)*pos);
if (tgt)
goto out;
++*pos;
}
out:
mutex_unlock(<d->ltd_mutex);
return tgt;
}
static int lmv_tgt_seq_show(struct seq_file *p, void *v)
{
struct lmv_tgt_desc *tgt = v;
if (!tgt)
return 0;
seq_printf(p, "%u: %s %sACTIVE\n",
tgt->ltd_index, tgt->ltd_uuid.uuid,
tgt->ltd_active ? "" : "IN");
return 0;
}
static const struct seq_operations lmv_tgt_sops = {
.start = lmv_tgt_seq_start,
.stop = lmv_tgt_seq_stop,
.next = lmv_tgt_seq_next,
.show = lmv_tgt_seq_show,
};
static int lmv_target_seq_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
int rc;
rc = seq_open(file, &lmv_tgt_sops);
if (rc)
return rc;
seq = file->private_data;
seq->private = inode->i_private;
return 0;
}
static const struct file_operations lmv_debugfs_target_fops = {
.owner = THIS_MODULE,
.open = lmv_target_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static struct attribute *lmv_attrs[] = {
&lustre_attr_activeobd.attr,
&lustre_attr_desc_uuid.attr,
&lustre_attr_numobd.attr,
&lustre_attr_qos_maxage.attr,
&lustre_attr_qos_prio_free.attr,
&lustre_attr_qos_rr_index.attr,
&lustre_attr_qos_threshold_rr.attr,
NULL,
};
ATTRIBUTE_GROUPS(lmv); /* creates lmv_groups */
int lmv_tunables_init(struct obd_device *obd)
{
int rc;
obd->obd_ktype.default_groups = lmv_groups;
obd->obd_debugfs_vars = ldebugfs_lmv_obd_vars;
rc = lprocfs_obd_setup(obd, false);
if (rc)
goto out_failed;
#ifdef CONFIG_PROC_FS
rc = lprocfs_alloc_md_stats(obd, 0);
if (rc) {
lprocfs_obd_cleanup(obd);
goto out_failed;
}
#endif /* CONFIG_PROC_FS */
debugfs_create_file("target_obd", 0444, obd->obd_debugfs_entry,
obd, &lmv_debugfs_target_fops);
out_failed:
return rc;
}