|
@@ -22,6 +22,8 @@
|
|
|
#include <linux/hdreg.h>
|
|
|
#include <linux/async.h>
|
|
|
#include <linux/mutex.h>
|
|
|
+#include <linux/debugfs.h>
|
|
|
+#include <linux/seq_file.h>
|
|
|
|
|
|
#include <asm/ccwdev.h>
|
|
|
#include <asm/ebcdic.h>
|
|
@@ -45,6 +47,7 @@
|
|
|
* SECTION: exported variables of dasd.c
|
|
|
*/
|
|
|
debug_info_t *dasd_debug_area;
|
|
|
+static struct dentry *dasd_debugfs_root_entry;
|
|
|
struct dasd_discipline *dasd_diag_discipline_pointer;
|
|
|
void dasd_int_handler(struct ccw_device *, unsigned long, struct irb *);
|
|
|
|
|
@@ -71,6 +74,8 @@ static void dasd_return_cqr_cb(struct dasd_ccw_req *, void *);
|
|
|
static void dasd_device_timeout(unsigned long);
|
|
|
static void dasd_block_timeout(unsigned long);
|
|
|
static void __dasd_process_erp(struct dasd_device *, struct dasd_ccw_req *);
|
|
|
+static void dasd_profile_init(struct dasd_profile *, struct dentry *);
|
|
|
+static void dasd_profile_exit(struct dasd_profile *);
|
|
|
|
|
|
/*
|
|
|
* SECTION: Operations on the device structure.
|
|
@@ -121,7 +126,7 @@ struct dasd_device *dasd_alloc_device(void)
|
|
|
device->state = DASD_STATE_NEW;
|
|
|
device->target = DASD_STATE_NEW;
|
|
|
mutex_init(&device->state_mutex);
|
|
|
-
|
|
|
+ spin_lock_init(&device->profile.lock);
|
|
|
return device;
|
|
|
}
|
|
|
|
|
@@ -159,6 +164,7 @@ struct dasd_block *dasd_alloc_block(void)
|
|
|
init_timer(&block->timer);
|
|
|
block->timer.function = dasd_block_timeout;
|
|
|
block->timer.data = (unsigned long) block;
|
|
|
+ spin_lock_init(&block->profile.lock);
|
|
|
|
|
|
return block;
|
|
|
}
|
|
@@ -222,19 +228,44 @@ static int dasd_state_known_to_new(struct dasd_device *device)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static struct dentry *dasd_debugfs_setup(const char *name,
|
|
|
+ struct dentry *base_dentry)
|
|
|
+{
|
|
|
+ struct dentry *pde;
|
|
|
+
|
|
|
+ if (!base_dentry)
|
|
|
+ return NULL;
|
|
|
+ pde = debugfs_create_dir(name, base_dentry);
|
|
|
+ if (!pde || IS_ERR(pde))
|
|
|
+ return NULL;
|
|
|
+ return pde;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Request the irq line for the device.
|
|
|
*/
|
|
|
static int dasd_state_known_to_basic(struct dasd_device *device)
|
|
|
{
|
|
|
+ struct dasd_block *block = device->block;
|
|
|
int rc;
|
|
|
|
|
|
/* Allocate and register gendisk structure. */
|
|
|
- if (device->block) {
|
|
|
- rc = dasd_gendisk_alloc(device->block);
|
|
|
+ if (block) {
|
|
|
+ rc = dasd_gendisk_alloc(block);
|
|
|
if (rc)
|
|
|
return rc;
|
|
|
- }
|
|
|
+ block->debugfs_dentry =
|
|
|
+ dasd_debugfs_setup(block->gdp->disk_name,
|
|
|
+ dasd_debugfs_root_entry);
|
|
|
+ dasd_profile_init(&block->profile, block->debugfs_dentry);
|
|
|
+ if (dasd_global_profile_level == DASD_PROFILE_ON)
|
|
|
+ dasd_profile_on(&device->block->profile);
|
|
|
+ }
|
|
|
+ device->debugfs_dentry =
|
|
|
+ dasd_debugfs_setup(dev_name(&device->cdev->dev),
|
|
|
+ dasd_debugfs_root_entry);
|
|
|
+ dasd_profile_init(&device->profile, device->debugfs_dentry);
|
|
|
+
|
|
|
/* register 'device' debug area, used for all DBF_DEV_XXX calls */
|
|
|
device->debug_area = debug_register(dev_name(&device->cdev->dev), 4, 1,
|
|
|
8 * sizeof(long));
|
|
@@ -253,6 +284,9 @@ static int dasd_state_basic_to_known(struct dasd_device *device)
|
|
|
{
|
|
|
int rc;
|
|
|
if (device->block) {
|
|
|
+ dasd_profile_exit(&device->block->profile);
|
|
|
+ if (device->block->debugfs_dentry)
|
|
|
+ debugfs_remove(device->block->debugfs_dentry);
|
|
|
dasd_gendisk_free(device->block);
|
|
|
dasd_block_clear_timer(device->block);
|
|
|
}
|
|
@@ -260,6 +294,9 @@ static int dasd_state_basic_to_known(struct dasd_device *device)
|
|
|
if (rc)
|
|
|
return rc;
|
|
|
dasd_device_clear_timer(device);
|
|
|
+ dasd_profile_exit(&device->profile);
|
|
|
+ if (device->debugfs_dentry)
|
|
|
+ debugfs_remove(device->debugfs_dentry);
|
|
|
|
|
|
DBF_DEV_EVENT(DBF_EMERG, device, "%p debug area deleted", device);
|
|
|
if (device->debug_area != NULL) {
|
|
@@ -609,21 +646,13 @@ void dasd_enable_device(struct dasd_device *device)
|
|
|
/*
|
|
|
* SECTION: device operation (interrupt handler, start i/o, term i/o ...)
|
|
|
*/
|
|
|
-#ifdef CONFIG_DASD_PROFILE
|
|
|
|
|
|
-struct dasd_profile_info_t dasd_global_profile;
|
|
|
-unsigned int dasd_profile_level = DASD_PROFILE_OFF;
|
|
|
+unsigned int dasd_global_profile_level = DASD_PROFILE_OFF;
|
|
|
|
|
|
-/*
|
|
|
- * Increments counter in global and local profiling structures.
|
|
|
- */
|
|
|
-#define dasd_profile_counter(value, counter, block) \
|
|
|
-{ \
|
|
|
- int index; \
|
|
|
- for (index = 0; index < 31 && value >> (2+index); index++); \
|
|
|
- dasd_global_profile.counter[index]++; \
|
|
|
- block->profile.counter[index]++; \
|
|
|
-}
|
|
|
+#ifdef CONFIG_DASD_PROFILE
|
|
|
+struct dasd_profile_info dasd_global_profile_data;
|
|
|
+static struct dentry *dasd_global_profile_dentry;
|
|
|
+static struct dentry *dasd_debugfs_global_entry;
|
|
|
|
|
|
/*
|
|
|
* Add profiling information for cqr before execution.
|
|
@@ -634,30 +663,121 @@ static void dasd_profile_start(struct dasd_block *block,
|
|
|
{
|
|
|
struct list_head *l;
|
|
|
unsigned int counter;
|
|
|
-
|
|
|
- if (dasd_profile_level != DASD_PROFILE_ON)
|
|
|
- return;
|
|
|
+ struct dasd_device *device;
|
|
|
|
|
|
/* count the length of the chanq for statistics */
|
|
|
counter = 0;
|
|
|
- list_for_each(l, &block->ccw_queue)
|
|
|
- if (++counter >= 31)
|
|
|
- break;
|
|
|
- dasd_global_profile.dasd_io_nr_req[counter]++;
|
|
|
- block->profile.dasd_io_nr_req[counter]++;
|
|
|
+ if (dasd_global_profile_level || block->profile.data)
|
|
|
+ list_for_each(l, &block->ccw_queue)
|
|
|
+ if (++counter >= 31)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (dasd_global_profile_level) {
|
|
|
+ dasd_global_profile_data.dasd_io_nr_req[counter]++;
|
|
|
+ if (rq_data_dir(req) == READ)
|
|
|
+ dasd_global_profile_data.dasd_read_nr_req[counter]++;
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock(&block->profile.lock);
|
|
|
+ if (block->profile.data)
|
|
|
+ block->profile.data->dasd_io_nr_req[counter]++;
|
|
|
+ if (rq_data_dir(req) == READ)
|
|
|
+ block->profile.data->dasd_read_nr_req[counter]++;
|
|
|
+ spin_unlock(&block->profile.lock);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We count the request for the start device, even though it may run on
|
|
|
+ * some other device due to error recovery. This way we make sure that
|
|
|
+ * we count each request only once.
|
|
|
+ */
|
|
|
+ device = cqr->startdev;
|
|
|
+ if (device->profile.data) {
|
|
|
+ counter = 1; /* request is not yet queued on the start device */
|
|
|
+ list_for_each(l, &device->ccw_queue)
|
|
|
+ if (++counter >= 31)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ spin_lock(&device->profile.lock);
|
|
|
+ if (device->profile.data) {
|
|
|
+ device->profile.data->dasd_io_nr_req[counter]++;
|
|
|
+ if (rq_data_dir(req) == READ)
|
|
|
+ device->profile.data->dasd_read_nr_req[counter]++;
|
|
|
+ }
|
|
|
+ spin_unlock(&device->profile.lock);
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* Add profiling information for cqr after execution.
|
|
|
*/
|
|
|
+
|
|
|
+#define dasd_profile_counter(value, index) \
|
|
|
+{ \
|
|
|
+ for (index = 0; index < 31 && value >> (2+index); index++) \
|
|
|
+ ; \
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_profile_end_add_data(struct dasd_profile_info *data,
|
|
|
+ int is_alias,
|
|
|
+ int is_tpm,
|
|
|
+ int is_read,
|
|
|
+ long sectors,
|
|
|
+ int sectors_ind,
|
|
|
+ int tottime_ind,
|
|
|
+ int tottimeps_ind,
|
|
|
+ int strtime_ind,
|
|
|
+ int irqtime_ind,
|
|
|
+ int irqtimeps_ind,
|
|
|
+ int endtime_ind)
|
|
|
+{
|
|
|
+ /* in case of an overflow, reset the whole profile */
|
|
|
+ if (data->dasd_io_reqs == UINT_MAX) {
|
|
|
+ memset(data, 0, sizeof(*data));
|
|
|
+ getnstimeofday(&data->starttod);
|
|
|
+ }
|
|
|
+ data->dasd_io_reqs++;
|
|
|
+ data->dasd_io_sects += sectors;
|
|
|
+ if (is_alias)
|
|
|
+ data->dasd_io_alias++;
|
|
|
+ if (is_tpm)
|
|
|
+ data->dasd_io_tpm++;
|
|
|
+
|
|
|
+ data->dasd_io_secs[sectors_ind]++;
|
|
|
+ data->dasd_io_times[tottime_ind]++;
|
|
|
+ data->dasd_io_timps[tottimeps_ind]++;
|
|
|
+ data->dasd_io_time1[strtime_ind]++;
|
|
|
+ data->dasd_io_time2[irqtime_ind]++;
|
|
|
+ data->dasd_io_time2ps[irqtimeps_ind]++;
|
|
|
+ data->dasd_io_time3[endtime_ind]++;
|
|
|
+
|
|
|
+ if (is_read) {
|
|
|
+ data->dasd_read_reqs++;
|
|
|
+ data->dasd_read_sects += sectors;
|
|
|
+ if (is_alias)
|
|
|
+ data->dasd_read_alias++;
|
|
|
+ if (is_tpm)
|
|
|
+ data->dasd_read_tpm++;
|
|
|
+ data->dasd_read_secs[sectors_ind]++;
|
|
|
+ data->dasd_read_times[tottime_ind]++;
|
|
|
+ data->dasd_read_time1[strtime_ind]++;
|
|
|
+ data->dasd_read_time2[irqtime_ind]++;
|
|
|
+ data->dasd_read_time3[endtime_ind]++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static void dasd_profile_end(struct dasd_block *block,
|
|
|
struct dasd_ccw_req *cqr,
|
|
|
struct request *req)
|
|
|
{
|
|
|
long strtime, irqtime, endtime, tottime; /* in microseconds */
|
|
|
long tottimeps, sectors;
|
|
|
+ struct dasd_device *device;
|
|
|
+ int sectors_ind, tottime_ind, tottimeps_ind, strtime_ind;
|
|
|
+ int irqtime_ind, irqtimeps_ind, endtime_ind;
|
|
|
|
|
|
- if (dasd_profile_level != DASD_PROFILE_ON)
|
|
|
+ device = cqr->startdev;
|
|
|
+ if (!(dasd_global_profile_level ||
|
|
|
+ block->profile.data ||
|
|
|
+ device->profile.data))
|
|
|
return;
|
|
|
|
|
|
sectors = blk_rq_sectors(req);
|
|
@@ -672,29 +792,392 @@ static void dasd_profile_end(struct dasd_block *block,
|
|
|
tottime = ((cqr->endclk - cqr->buildclk) >> 12);
|
|
|
tottimeps = tottime / sectors;
|
|
|
|
|
|
- if (!dasd_global_profile.dasd_io_reqs)
|
|
|
- memset(&dasd_global_profile, 0,
|
|
|
- sizeof(struct dasd_profile_info_t));
|
|
|
- dasd_global_profile.dasd_io_reqs++;
|
|
|
- dasd_global_profile.dasd_io_sects += sectors;
|
|
|
-
|
|
|
- if (!block->profile.dasd_io_reqs)
|
|
|
- memset(&block->profile, 0,
|
|
|
- sizeof(struct dasd_profile_info_t));
|
|
|
- block->profile.dasd_io_reqs++;
|
|
|
- block->profile.dasd_io_sects += sectors;
|
|
|
-
|
|
|
- dasd_profile_counter(sectors, dasd_io_secs, block);
|
|
|
- dasd_profile_counter(tottime, dasd_io_times, block);
|
|
|
- dasd_profile_counter(tottimeps, dasd_io_timps, block);
|
|
|
- dasd_profile_counter(strtime, dasd_io_time1, block);
|
|
|
- dasd_profile_counter(irqtime, dasd_io_time2, block);
|
|
|
- dasd_profile_counter(irqtime / sectors, dasd_io_time2ps, block);
|
|
|
- dasd_profile_counter(endtime, dasd_io_time3, block);
|
|
|
+ dasd_profile_counter(sectors, sectors_ind);
|
|
|
+ dasd_profile_counter(tottime, tottime_ind);
|
|
|
+ dasd_profile_counter(tottimeps, tottimeps_ind);
|
|
|
+ dasd_profile_counter(strtime, strtime_ind);
|
|
|
+ dasd_profile_counter(irqtime, irqtime_ind);
|
|
|
+ dasd_profile_counter(irqtime / sectors, irqtimeps_ind);
|
|
|
+ dasd_profile_counter(endtime, endtime_ind);
|
|
|
+
|
|
|
+ if (dasd_global_profile_level) {
|
|
|
+ dasd_profile_end_add_data(&dasd_global_profile_data,
|
|
|
+ cqr->startdev != block->base,
|
|
|
+ cqr->cpmode == 1,
|
|
|
+ rq_data_dir(req) == READ,
|
|
|
+ sectors, sectors_ind, tottime_ind,
|
|
|
+ tottimeps_ind, strtime_ind,
|
|
|
+ irqtime_ind, irqtimeps_ind,
|
|
|
+ endtime_ind);
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_lock(&block->profile.lock);
|
|
|
+ if (block->profile.data)
|
|
|
+ dasd_profile_end_add_data(block->profile.data,
|
|
|
+ cqr->startdev != block->base,
|
|
|
+ cqr->cpmode == 1,
|
|
|
+ rq_data_dir(req) == READ,
|
|
|
+ sectors, sectors_ind, tottime_ind,
|
|
|
+ tottimeps_ind, strtime_ind,
|
|
|
+ irqtime_ind, irqtimeps_ind,
|
|
|
+ endtime_ind);
|
|
|
+ spin_unlock(&block->profile.lock);
|
|
|
+
|
|
|
+ spin_lock(&device->profile.lock);
|
|
|
+ if (device->profile.data)
|
|
|
+ dasd_profile_end_add_data(device->profile.data,
|
|
|
+ cqr->startdev != block->base,
|
|
|
+ cqr->cpmode == 1,
|
|
|
+ rq_data_dir(req) == READ,
|
|
|
+ sectors, sectors_ind, tottime_ind,
|
|
|
+ tottimeps_ind, strtime_ind,
|
|
|
+ irqtime_ind, irqtimeps_ind,
|
|
|
+ endtime_ind);
|
|
|
+ spin_unlock(&device->profile.lock);
|
|
|
+}
|
|
|
+
|
|
|
+void dasd_profile_reset(struct dasd_profile *profile)
|
|
|
+{
|
|
|
+ struct dasd_profile_info *data;
|
|
|
+
|
|
|
+ spin_lock_bh(&profile->lock);
|
|
|
+ data = profile->data;
|
|
|
+ if (!data) {
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ memset(data, 0, sizeof(*data));
|
|
|
+ getnstimeofday(&data->starttod);
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+}
|
|
|
+
|
|
|
+void dasd_global_profile_reset(void)
|
|
|
+{
|
|
|
+ memset(&dasd_global_profile_data, 0, sizeof(dasd_global_profile_data));
|
|
|
+ getnstimeofday(&dasd_global_profile_data.starttod);
|
|
|
+}
|
|
|
+
|
|
|
+int dasd_profile_on(struct dasd_profile *profile)
|
|
|
+{
|
|
|
+ struct dasd_profile_info *data;
|
|
|
+
|
|
|
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
|
+ if (!data)
|
|
|
+ return -ENOMEM;
|
|
|
+ spin_lock_bh(&profile->lock);
|
|
|
+ if (profile->data) {
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+ kfree(data);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ getnstimeofday(&data->starttod);
|
|
|
+ profile->data = data;
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void dasd_profile_off(struct dasd_profile *profile)
|
|
|
+{
|
|
|
+ spin_lock_bh(&profile->lock);
|
|
|
+ kfree(profile->data);
|
|
|
+ profile->data = NULL;
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+}
|
|
|
+
|
|
|
+char *dasd_get_user_string(const char __user *user_buf, size_t user_len)
|
|
|
+{
|
|
|
+ char *buffer;
|
|
|
+
|
|
|
+ buffer = kmalloc(user_len + 1, GFP_KERNEL);
|
|
|
+ if (buffer == NULL)
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
+ if (copy_from_user(buffer, user_buf, user_len) != 0) {
|
|
|
+ kfree(buffer);
|
|
|
+ return ERR_PTR(-EFAULT);
|
|
|
+ }
|
|
|
+ /* got the string, now strip linefeed. */
|
|
|
+ if (buffer[user_len - 1] == '\n')
|
|
|
+ buffer[user_len - 1] = 0;
|
|
|
+ else
|
|
|
+ buffer[user_len] = 0;
|
|
|
+ return buffer;
|
|
|
}
|
|
|
+
|
|
|
+static ssize_t dasd_stats_write(struct file *file,
|
|
|
+ const char __user *user_buf,
|
|
|
+ size_t user_len, loff_t *pos)
|
|
|
+{
|
|
|
+ char *buffer, *str;
|
|
|
+ int rc;
|
|
|
+ struct seq_file *m = (struct seq_file *)file->private_data;
|
|
|
+ struct dasd_profile *prof = m->private;
|
|
|
+
|
|
|
+ if (user_len > 65536)
|
|
|
+ user_len = 65536;
|
|
|
+ buffer = dasd_get_user_string(user_buf, user_len);
|
|
|
+ if (IS_ERR(buffer))
|
|
|
+ return PTR_ERR(buffer);
|
|
|
+
|
|
|
+ str = skip_spaces(buffer);
|
|
|
+ rc = user_len;
|
|
|
+ if (strncmp(str, "reset", 5) == 0) {
|
|
|
+ dasd_profile_reset(prof);
|
|
|
+ } else if (strncmp(str, "on", 2) == 0) {
|
|
|
+ rc = dasd_profile_on(prof);
|
|
|
+ if (!rc)
|
|
|
+ rc = user_len;
|
|
|
+ } else if (strncmp(str, "off", 3) == 0) {
|
|
|
+ dasd_profile_off(prof);
|
|
|
+ } else
|
|
|
+ rc = -EINVAL;
|
|
|
+ kfree(buffer);
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_stats_array(struct seq_file *m, unsigned int *array)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < 32; i++)
|
|
|
+ seq_printf(m, "%u ", array[i]);
|
|
|
+ seq_putc(m, '\n');
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_stats_seq_print(struct seq_file *m,
|
|
|
+ struct dasd_profile_info *data)
|
|
|
+{
|
|
|
+ seq_printf(m, "start_time %ld.%09ld\n",
|
|
|
+ data->starttod.tv_sec, data->starttod.tv_nsec);
|
|
|
+ seq_printf(m, "total_requests %u\n", data->dasd_io_reqs);
|
|
|
+ seq_printf(m, "total_sectors %u\n", data->dasd_io_sects);
|
|
|
+ seq_printf(m, "total_pav %u\n", data->dasd_io_alias);
|
|
|
+ seq_printf(m, "total_hpf %u\n", data->dasd_io_tpm);
|
|
|
+ seq_printf(m, "histogram_sectors ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_secs);
|
|
|
+ seq_printf(m, "histogram_io_times ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_times);
|
|
|
+ seq_printf(m, "histogram_io_times_weighted ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_timps);
|
|
|
+ seq_printf(m, "histogram_time_build_to_ssch ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_time1);
|
|
|
+ seq_printf(m, "histogram_time_ssch_to_irq ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_time2);
|
|
|
+ seq_printf(m, "histogram_time_ssch_to_irq_weighted ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_time2ps);
|
|
|
+ seq_printf(m, "histogram_time_irq_to_end ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_time3);
|
|
|
+ seq_printf(m, "histogram_ccw_queue_length ");
|
|
|
+ dasd_stats_array(m, data->dasd_io_nr_req);
|
|
|
+ seq_printf(m, "total_read_requests %u\n", data->dasd_read_reqs);
|
|
|
+ seq_printf(m, "total_read_sectors %u\n", data->dasd_read_sects);
|
|
|
+ seq_printf(m, "total_read_pav %u\n", data->dasd_read_alias);
|
|
|
+ seq_printf(m, "total_read_hpf %u\n", data->dasd_read_tpm);
|
|
|
+ seq_printf(m, "histogram_read_sectors ");
|
|
|
+ dasd_stats_array(m, data->dasd_read_secs);
|
|
|
+ seq_printf(m, "histogram_read_times ");
|
|
|
+ dasd_stats_array(m, data->dasd_read_times);
|
|
|
+ seq_printf(m, "histogram_read_time_build_to_ssch ");
|
|
|
+ dasd_stats_array(m, data->dasd_read_time1);
|
|
|
+ seq_printf(m, "histogram_read_time_ssch_to_irq ");
|
|
|
+ dasd_stats_array(m, data->dasd_read_time2);
|
|
|
+ seq_printf(m, "histogram_read_time_irq_to_end ");
|
|
|
+ dasd_stats_array(m, data->dasd_read_time3);
|
|
|
+ seq_printf(m, "histogram_read_ccw_queue_length ");
|
|
|
+ dasd_stats_array(m, data->dasd_read_nr_req);
|
|
|
+}
|
|
|
+
|
|
|
+static int dasd_stats_show(struct seq_file *m, void *v)
|
|
|
+{
|
|
|
+ struct dasd_profile *profile;
|
|
|
+ struct dasd_profile_info *data;
|
|
|
+
|
|
|
+ profile = m->private;
|
|
|
+ spin_lock_bh(&profile->lock);
|
|
|
+ data = profile->data;
|
|
|
+ if (!data) {
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+ seq_printf(m, "disabled\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ dasd_stats_seq_print(m, data);
|
|
|
+ spin_unlock_bh(&profile->lock);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int dasd_stats_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ struct dasd_profile *profile = inode->i_private;
|
|
|
+ return single_open(file, dasd_stats_show, profile);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct file_operations dasd_stats_raw_fops = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .open = dasd_stats_open,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = single_release,
|
|
|
+ .write = dasd_stats_write,
|
|
|
+};
|
|
|
+
|
|
|
+static ssize_t dasd_stats_global_write(struct file *file,
|
|
|
+ const char __user *user_buf,
|
|
|
+ size_t user_len, loff_t *pos)
|
|
|
+{
|
|
|
+ char *buffer, *str;
|
|
|
+ ssize_t rc;
|
|
|
+
|
|
|
+ if (user_len > 65536)
|
|
|
+ user_len = 65536;
|
|
|
+ buffer = dasd_get_user_string(user_buf, user_len);
|
|
|
+ if (IS_ERR(buffer))
|
|
|
+ return PTR_ERR(buffer);
|
|
|
+ str = skip_spaces(buffer);
|
|
|
+ rc = user_len;
|
|
|
+ if (strncmp(str, "reset", 5) == 0) {
|
|
|
+ dasd_global_profile_reset();
|
|
|
+ } else if (strncmp(str, "on", 2) == 0) {
|
|
|
+ dasd_global_profile_reset();
|
|
|
+ dasd_global_profile_level = DASD_PROFILE_GLOBAL_ONLY;
|
|
|
+ } else if (strncmp(str, "off", 3) == 0) {
|
|
|
+ dasd_global_profile_level = DASD_PROFILE_OFF;
|
|
|
+ } else
|
|
|
+ rc = -EINVAL;
|
|
|
+ kfree(buffer);
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int dasd_stats_global_show(struct seq_file *m, void *v)
|
|
|
+{
|
|
|
+ if (!dasd_global_profile_level) {
|
|
|
+ seq_printf(m, "disabled\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ dasd_stats_seq_print(m, &dasd_global_profile_data);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int dasd_stats_global_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ return single_open(file, dasd_stats_global_show, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct file_operations dasd_stats_global_fops = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .open = dasd_stats_global_open,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = single_release,
|
|
|
+ .write = dasd_stats_global_write,
|
|
|
+};
|
|
|
+
|
|
|
+static void dasd_profile_init(struct dasd_profile *profile,
|
|
|
+ struct dentry *base_dentry)
|
|
|
+{
|
|
|
+ mode_t mode;
|
|
|
+ struct dentry *pde;
|
|
|
+
|
|
|
+ if (!base_dentry)
|
|
|
+ return;
|
|
|
+ profile->dentry = NULL;
|
|
|
+ profile->data = NULL;
|
|
|
+ mode = (S_IRUSR | S_IWUSR | S_IFREG);
|
|
|
+ pde = debugfs_create_file("statistics", mode, base_dentry,
|
|
|
+ profile, &dasd_stats_raw_fops);
|
|
|
+ if (pde && !IS_ERR(pde))
|
|
|
+ profile->dentry = pde;
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_profile_exit(struct dasd_profile *profile)
|
|
|
+{
|
|
|
+ dasd_profile_off(profile);
|
|
|
+ if (profile->dentry) {
|
|
|
+ debugfs_remove(profile->dentry);
|
|
|
+ profile->dentry = NULL;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_statistics_removeroot(void)
|
|
|
+{
|
|
|
+ dasd_global_profile_level = DASD_PROFILE_OFF;
|
|
|
+ if (dasd_global_profile_dentry) {
|
|
|
+ debugfs_remove(dasd_global_profile_dentry);
|
|
|
+ dasd_global_profile_dentry = NULL;
|
|
|
+ }
|
|
|
+ if (dasd_debugfs_global_entry)
|
|
|
+ debugfs_remove(dasd_debugfs_global_entry);
|
|
|
+ if (dasd_debugfs_root_entry)
|
|
|
+ debugfs_remove(dasd_debugfs_root_entry);
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_statistics_createroot(void)
|
|
|
+{
|
|
|
+ mode_t mode;
|
|
|
+ struct dentry *pde;
|
|
|
+
|
|
|
+ dasd_debugfs_root_entry = NULL;
|
|
|
+ dasd_debugfs_global_entry = NULL;
|
|
|
+ dasd_global_profile_dentry = NULL;
|
|
|
+ pde = debugfs_create_dir("dasd", NULL);
|
|
|
+ if (!pde || IS_ERR(pde))
|
|
|
+ goto error;
|
|
|
+ dasd_debugfs_root_entry = pde;
|
|
|
+ pde = debugfs_create_dir("global", dasd_debugfs_root_entry);
|
|
|
+ if (!pde || IS_ERR(pde))
|
|
|
+ goto error;
|
|
|
+ dasd_debugfs_global_entry = pde;
|
|
|
+
|
|
|
+ mode = (S_IRUSR | S_IWUSR | S_IFREG);
|
|
|
+ pde = debugfs_create_file("statistics", mode, dasd_debugfs_global_entry,
|
|
|
+ NULL, &dasd_stats_global_fops);
|
|
|
+ if (!pde || IS_ERR(pde))
|
|
|
+ goto error;
|
|
|
+ dasd_global_profile_dentry = pde;
|
|
|
+ return;
|
|
|
+
|
|
|
+error:
|
|
|
+ DBF_EVENT(DBF_ERR, "%s",
|
|
|
+ "Creation of the dasd debugfs interface failed");
|
|
|
+ dasd_statistics_removeroot();
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
#else
|
|
|
#define dasd_profile_start(block, cqr, req) do {} while (0)
|
|
|
#define dasd_profile_end(block, cqr, req) do {} while (0)
|
|
|
+
|
|
|
+static void dasd_statistics_createroot(void)
|
|
|
+{
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_statistics_removeroot(void)
|
|
|
+{
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+int dasd_stats_generic_show(struct seq_file *m, void *v)
|
|
|
+{
|
|
|
+ seq_printf(m, "Statistics are not activated in this kernel\n");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_profile_init(struct dasd_profile *profile,
|
|
|
+ struct dentry *base_dentry)
|
|
|
+{
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static void dasd_profile_exit(struct dasd_profile *profile)
|
|
|
+{
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+int dasd_profile_on(struct dasd_profile *profile)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
#endif /* CONFIG_DASD_PROFILE */
|
|
|
|
|
|
/*
|
|
@@ -2441,6 +2924,7 @@ dasd_exit(void)
|
|
|
debug_unregister(dasd_debug_area);
|
|
|
dasd_debug_area = NULL;
|
|
|
}
|
|
|
+ dasd_statistics_removeroot();
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -2992,6 +3476,8 @@ static int __init dasd_init(void)
|
|
|
|
|
|
dasd_diag_discipline_pointer = NULL;
|
|
|
|
|
|
+ dasd_statistics_createroot();
|
|
|
+
|
|
|
rc = dasd_devmap_init();
|
|
|
if (rc)
|
|
|
goto failed;
|