|
@@ -471,6 +471,47 @@ static void st_release_request(struct st_request *streq)
|
|
|
kfree(streq);
|
|
|
}
|
|
|
|
|
|
+static void st_do_stats(struct scsi_tape *STp, struct request *req)
|
|
|
+{
|
|
|
+ ktime_t now;
|
|
|
+
|
|
|
+ now = ktime_get();
|
|
|
+ if (req->cmd[0] == WRITE_6) {
|
|
|
+ now = ktime_sub(now, STp->stats->write_time);
|
|
|
+ atomic64_add(ktime_to_ns(now), &STp->stats->tot_write_time);
|
|
|
+ atomic64_add(ktime_to_ns(now), &STp->stats->tot_io_time);
|
|
|
+ atomic64_inc(&STp->stats->write_cnt);
|
|
|
+ if (req->errors) {
|
|
|
+ atomic64_add(atomic_read(&STp->stats->last_write_size)
|
|
|
+ - STp->buffer->cmdstat.residual,
|
|
|
+ &STp->stats->write_byte_cnt);
|
|
|
+ if (STp->buffer->cmdstat.residual > 0)
|
|
|
+ atomic64_inc(&STp->stats->resid_cnt);
|
|
|
+ } else
|
|
|
+ atomic64_add(atomic_read(&STp->stats->last_write_size),
|
|
|
+ &STp->stats->write_byte_cnt);
|
|
|
+ } else if (req->cmd[0] == READ_6) {
|
|
|
+ now = ktime_sub(now, STp->stats->read_time);
|
|
|
+ atomic64_add(ktime_to_ns(now), &STp->stats->tot_read_time);
|
|
|
+ atomic64_add(ktime_to_ns(now), &STp->stats->tot_io_time);
|
|
|
+ atomic64_inc(&STp->stats->read_cnt);
|
|
|
+ if (req->errors) {
|
|
|
+ atomic64_add(atomic_read(&STp->stats->last_read_size)
|
|
|
+ - STp->buffer->cmdstat.residual,
|
|
|
+ &STp->stats->read_byte_cnt);
|
|
|
+ if (STp->buffer->cmdstat.residual > 0)
|
|
|
+ atomic64_inc(&STp->stats->resid_cnt);
|
|
|
+ } else
|
|
|
+ atomic64_add(atomic_read(&STp->stats->last_read_size),
|
|
|
+ &STp->stats->read_byte_cnt);
|
|
|
+ } else {
|
|
|
+ now = ktime_sub(now, STp->stats->other_time);
|
|
|
+ atomic64_add(ktime_to_ns(now), &STp->stats->tot_io_time);
|
|
|
+ atomic64_inc(&STp->stats->other_cnt);
|
|
|
+ }
|
|
|
+ atomic64_dec(&STp->stats->in_flight);
|
|
|
+}
|
|
|
+
|
|
|
static void st_scsi_execute_end(struct request *req, int uptodate)
|
|
|
{
|
|
|
struct st_request *SRpnt = req->end_io_data;
|
|
@@ -480,6 +521,8 @@ static void st_scsi_execute_end(struct request *req, int uptodate)
|
|
|
STp->buffer->cmdstat.midlevel_result = SRpnt->result = req->errors;
|
|
|
STp->buffer->cmdstat.residual = req->resid_len;
|
|
|
|
|
|
+ st_do_stats(STp, req);
|
|
|
+
|
|
|
tmp = SRpnt->bio;
|
|
|
if (SRpnt->waiting)
|
|
|
complete(SRpnt->waiting);
|
|
@@ -496,6 +539,7 @@ static int st_scsi_execute(struct st_request *SRpnt, const unsigned char *cmd,
|
|
|
struct rq_map_data *mdata = &SRpnt->stp->buffer->map_data;
|
|
|
int err = 0;
|
|
|
int write = (data_direction == DMA_TO_DEVICE);
|
|
|
+ struct scsi_tape *STp = SRpnt->stp;
|
|
|
|
|
|
req = blk_get_request(SRpnt->stp->device->request_queue, write,
|
|
|
GFP_KERNEL);
|
|
@@ -516,6 +560,17 @@ static int st_scsi_execute(struct st_request *SRpnt, const unsigned char *cmd,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ atomic64_inc(&STp->stats->in_flight);
|
|
|
+ if (cmd[0] == WRITE_6) {
|
|
|
+ atomic_set(&STp->stats->last_write_size, bufflen);
|
|
|
+ STp->stats->write_time = ktime_get();
|
|
|
+ } else if (cmd[0] == READ_6) {
|
|
|
+ atomic_set(&STp->stats->last_read_size, bufflen);
|
|
|
+ STp->stats->read_time = ktime_get();
|
|
|
+ } else {
|
|
|
+ STp->stats->other_time = ktime_get();
|
|
|
+ }
|
|
|
+
|
|
|
SRpnt->bio = req->bio;
|
|
|
req->cmd_len = COMMAND_SIZE(cmd[0]);
|
|
|
memset(req->cmd, 0, BLK_MAX_CDB);
|
|
@@ -4222,6 +4277,12 @@ static int st_probe(struct device *dev)
|
|
|
}
|
|
|
tpnt->index = error;
|
|
|
sprintf(disk->disk_name, "st%d", tpnt->index);
|
|
|
+ tpnt->stats = kzalloc(sizeof(struct scsi_tape_stats), GFP_KERNEL);
|
|
|
+ if (tpnt->stats == NULL) {
|
|
|
+ sdev_printk(KERN_ERR, SDp,
|
|
|
+ "st: Can't allocate statistics.\n");
|
|
|
+ goto out_idr_remove;
|
|
|
+ }
|
|
|
|
|
|
dev_set_drvdata(dev, tpnt);
|
|
|
|
|
@@ -4241,6 +4302,8 @@ static int st_probe(struct device *dev)
|
|
|
|
|
|
out_remove_devs:
|
|
|
remove_cdevs(tpnt);
|
|
|
+ kfree(tpnt->stats);
|
|
|
+out_idr_remove:
|
|
|
spin_lock(&st_index_lock);
|
|
|
idr_remove(&st_index_idr, tpnt->index);
|
|
|
spin_unlock(&st_index_lock);
|
|
@@ -4298,6 +4361,7 @@ static void scsi_tape_release(struct kref *kref)
|
|
|
|
|
|
disk->private_data = NULL;
|
|
|
put_disk(disk);
|
|
|
+ kfree(tpnt->stats);
|
|
|
kfree(tpnt);
|
|
|
return;
|
|
|
}
|
|
@@ -4513,6 +4577,184 @@ options_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
|
}
|
|
|
static DEVICE_ATTR_RO(options);
|
|
|
|
|
|
+/* Support for tape stats */
|
|
|
+
|
|
|
+/**
|
|
|
+ * read_cnt_show - return read count - count of reads made from tape drive
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t read_cnt_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->read_cnt));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(read_cnt);
|
|
|
+
|
|
|
+/**
|
|
|
+ * read_byte_cnt_show - return read byte count - tape drives
|
|
|
+ * may use blocks less than 512 bytes this gives the raw byte count of
|
|
|
+ * of data read from the tape drive.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t read_byte_cnt_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->read_byte_cnt));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(read_byte_cnt);
|
|
|
+
|
|
|
+/**
|
|
|
+ * read_us_show - return read us - overall time spent waiting on reads in ns.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t read_ns_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->tot_read_time));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(read_ns);
|
|
|
+
|
|
|
+/**
|
|
|
+ * write_cnt_show - write count - number of user calls
|
|
|
+ * to write(2) that have written data to tape.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t write_cnt_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->write_cnt));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(write_cnt);
|
|
|
+
|
|
|
+/**
|
|
|
+ * write_byte_cnt_show - write byte count - raw count of
|
|
|
+ * bytes written to tape.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t write_byte_cnt_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->write_byte_cnt));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(write_byte_cnt);
|
|
|
+
|
|
|
+/**
|
|
|
+ * write_ns_show - write ns - number of nanoseconds waiting on write
|
|
|
+ * requests to complete.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t write_ns_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->tot_write_time));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(write_ns);
|
|
|
+
|
|
|
+/**
|
|
|
+ * in_flight_show - number of I/Os currently in flight -
|
|
|
+ * in most cases this will be either 0 or 1. It may be higher if someone
|
|
|
+ * has also issued other SCSI commands such as via an ioctl.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t in_flight_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->in_flight));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(in_flight);
|
|
|
+
|
|
|
+/**
|
|
|
+ * io_ns_show - io wait ns - this is the number of ns spent
|
|
|
+ * waiting on all I/O to complete. This includes tape movement commands
|
|
|
+ * such as rewinding, seeking to end of file or tape, it also includes
|
|
|
+ * read and write. To determine the time spent on tape movement
|
|
|
+ * subtract the read and write ns from this value.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t io_ns_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->tot_io_time));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(io_ns);
|
|
|
+
|
|
|
+/**
|
|
|
+ * other_cnt_show - other io count - this is the number of
|
|
|
+ * I/O requests other than read and write requests.
|
|
|
+ * Typically these are tape movement requests but will include driver
|
|
|
+ * tape movement. This includes only requests issued by the st driver.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t other_cnt_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->other_cnt));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(other_cnt);
|
|
|
+
|
|
|
+/**
|
|
|
+ * resid_cnt_show - A count of the number of times we get a residual
|
|
|
+ * count - this should indicate someone issuing reads larger than the
|
|
|
+ * block size on tape.
|
|
|
+ * @dev: struct device
|
|
|
+ * @attr: attribute structure
|
|
|
+ * @buf: buffer to return formatted data in
|
|
|
+ */
|
|
|
+static ssize_t resid_cnt_show(struct device *dev,
|
|
|
+ struct device_attribute *attr, char *buf)
|
|
|
+{
|
|
|
+ struct st_modedef *STm = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ return sprintf(buf, "%lld",
|
|
|
+ (long long)atomic64_read(&STm->tape->stats->resid_cnt));
|
|
|
+}
|
|
|
+static DEVICE_ATTR_RO(resid_cnt);
|
|
|
+
|
|
|
static struct attribute *st_dev_attrs[] = {
|
|
|
&dev_attr_defined.attr,
|
|
|
&dev_attr_default_blksize.attr,
|
|
@@ -4521,7 +4763,35 @@ static struct attribute *st_dev_attrs[] = {
|
|
|
&dev_attr_options.attr,
|
|
|
NULL,
|
|
|
};
|
|
|
-ATTRIBUTE_GROUPS(st_dev);
|
|
|
+
|
|
|
+static struct attribute *st_stats_attrs[] = {
|
|
|
+ &dev_attr_read_cnt.attr,
|
|
|
+ &dev_attr_read_byte_cnt.attr,
|
|
|
+ &dev_attr_read_ns.attr,
|
|
|
+ &dev_attr_write_cnt.attr,
|
|
|
+ &dev_attr_write_byte_cnt.attr,
|
|
|
+ &dev_attr_write_ns.attr,
|
|
|
+ &dev_attr_in_flight.attr,
|
|
|
+ &dev_attr_io_ns.attr,
|
|
|
+ &dev_attr_other_cnt.attr,
|
|
|
+ &dev_attr_resid_cnt.attr,
|
|
|
+ NULL,
|
|
|
+};
|
|
|
+
|
|
|
+static struct attribute_group stats_group = {
|
|
|
+ .name = "stats",
|
|
|
+ .attrs = st_stats_attrs,
|
|
|
+};
|
|
|
+
|
|
|
+static struct attribute_group st_group = {
|
|
|
+ .attrs = st_dev_attrs,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct attribute_group *st_dev_groups[] = {
|
|
|
+ &st_group,
|
|
|
+ &stats_group,
|
|
|
+ NULL,
|
|
|
+};
|
|
|
|
|
|
/* The following functions may be useful for a larger audience. */
|
|
|
static int sgl_map_user_pages(struct st_buffer *STbp,
|