[PATCH for-next] scsi: Implement host state statistics

Seymour, Shane M
2023-03-15T06:08:45Z

The following patch implements host state statistics via sysfs. The intent
is to allow user space to see the state changes and be able to report when
a host changes state. The files do not separate out the time spent into
each state but only into three:

$ ll /sys/class/scsi_host/host0/stats
total 0
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_first_change_time
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_last_change_time
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_other_count
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_other_ns
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_recovery_count
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_recovery_ns
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_running_count
-r--r--r--. 1 root root 4096 Mar 13 22:43 state_running_ns

They are running, recovery and other. The running state is SHOST_CREATED
and SHOST_RUNNING. The recovery state is SHOST_RECOVERY,
SHOST_CANCEL_RECOVERY, and SHOST_DEL_RECOVERY. Any other state gets
accounted for in other.

The current state is not accounted for in the information read. Because
of that you must read:

1. The last_change_time for that host
2. the current state of a host and the uptime
3. each of the above *count and *ns files
4. Re-read the last_change_time
5. Compare the two last_change_time values read and if different try again.
6. The total time read from the *ns files is subtracted from the uptime and
that time is then allocated to the current state time.

The first change time is to determine when the host was created so programs
can determine if it was newly created or not.

A (GPLv2) program called hostmond will be released in a few months that
will monitor these interfaces and report (local host only via syslog(3C))
when hosts change state.

Signed-off-by: Shane Seymour <shane.seymour@hpe.com>
---
diff --git a/Documentation/ABI/testing/sysfs-class-scsi_host b/Documentation/ABI/testing/sysfs-class-scsi_host
index 7c98d8f43c45..2ce266df812f 100644
--- a/Documentation/ABI/testing/sysfs-class-scsi_host
+++ b/Documentation/ABI/testing/sysfs-class-scsi_host
@@ -117,3 +117,75 @@ KernelVersion: v2.6.39
Contact: linux-ide@vger.kernel.org
Description:
(RO) Displays supported enclosure management message types.
+
+What: /sys/class/scsi_host/hostX/stats/state_first_change_time
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is the time since boot where this SCSI host saw it's
+ initial state change. For most SCSI hosts this should be
+ relatively close to the time the system booted up (if the
+ Low Level Driver (LLD) was loaded at boot).
+
+ Looking at this file will help you determine when the host
+ was created. The time value read from this file is the
+ number of nanoseconds since boot.
+
+What: /sys/class/scsi_host/hostX/stats/state_last_change_time
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is the last time that the state of the SCSI host
+ changed. This can be used in two ways. You can read it
+ twice - once before reading any statistics and once after.
+
+ If the value has changed between reads you might consider
+ re-reading the statistics files again as the data will
+ have changed.
+
+What: /sys/class/scsi_host/hostX/stats/state_other_count
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is a simple count of the number of times this SCSI
+ host has transitioned out of this state.
+
+What: /sys/class/scsi_host/hostX/stats/state_other_ns
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is the amount of time spent in a state of other.
+
+What: /sys/class/scsi_host/hostX/stats/state_recovery_count
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is a simple count of the number of times this SCSI
+ host has transitioned out of this state.
+
+What: /sys/class/scsi_host/hostX/stats/state_recovery_ns
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is the amount of time spent in a state of recovery.
+
+What: /sys/class/scsi_host/hostX/stats/state_running_count
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is a simple count of the number of times this SCSI
+ host has transitioned out of this state.
+
+What: /sys/class/scsi_host/hostX/stats/state_running_ns
+Date: March 2023
+Kernel Version: 6.4
+Contact: shane.seymour@hpe.com
+Description:
+ (RO) This is the amount of time spent in a state of running.
diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index f7f62e56afca..1a010488b52b 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -64,6 +64,49 @@ static struct class shost_class = {
.dev_groups = scsi_shost_groups,
};

+/**
+ *
+ * scsi_host_do_state_stats - Maintains statistics for presentation
+ * via sysfs of host state changes per host.
+ * @shost - scsi host to track state change stats for.
+ * @oldstate - the previous state that the host was in.
+ *
+ **/
+static void scsi_host_do_state_stats(struct Scsi_Host *shost,
+ enum scsi_host_state oldstate)
+{
+ ktime_t now, last;
+
+ now = ktime_get();
+ last = shost->stats->state_last_change_time;
+
+ if (last == 0) {
+ shost->stats->state_first_change_time = now;
+ shost->stats->state_last_change_time = now;
+ last = now;
+ }
+
+ switch (oldstate) {
+
+ case SHOST_CREATED:
+ case SHOST_RUNNING:
+ shost->stats->state_running_ns += ktime_sub(now, last);
+ shost->stats->state_running_count++;
+ break;
+ case SHOST_RECOVERY:
+ case SHOST_CANCEL_RECOVERY:
+ case SHOST_DEL_RECOVERY:
+ shost->stats->state_recovery_ns += ktime_sub(now, last);
+ shost->stats->state_recovery_count++;
+ break;
+ default:
+ shost->stats->state_other_ns += ktime_sub(now, last);
+ shost->stats->state_other_count++;
+ break;
+ }
+ shost->stats->state_last_change_time = now;
+}
+
/**
* scsi_host_set_state - Take the given host through the host state model.
* @shost: scsi host to change the state of.
@@ -146,6 +189,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
break;
}
shost->shost_state = state;
+ scsi_host_do_state_stats(shost, oldstate);
return 0;

illegal:
@@ -369,6 +413,8 @@ static void scsi_host_dev_release(struct device *dev)

ida_free(&host_index_ida, shost->host_no);

+ kfree(shost->stats);
+
if (shost->shost_state != SHOST_CREATED)
put_device(parent);
kfree(shost);
@@ -401,6 +447,12 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
if (!shost)
return NULL;

+ shost->stats = kzalloc(sizeof(struct scsi_host_stats), GFP_KERNEL);
+ if (!shost->stats) {
+ kfree(shost);
+ return NULL;
+ }
+
shost->host_lock = &shost->default_lock;
spin_lock_init(shost->host_lock);
shost->shost_state = SHOST_CREATED;
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index ee28f73af4d4..00d41c8820aa 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -428,8 +428,110 @@ static const struct attribute_group scsi_shost_attr_group = {
.attrs = scsi_sysfs_shost_attrs,
};

+/*
+ * sysfs functions for shost state statistics.
+ */
+
+static ssize_t state_first_change_time_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%lld",
+ ktime_to_ns(shost->stats->state_first_change_time));
+}
+static DEVICE_ATTR_RO(state_first_change_time);
+
+static ssize_t state_last_change_time_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%lld",
+ ktime_to_ns(shost->stats->state_last_change_time));
+}
+static DEVICE_ATTR_RO(state_last_change_time);
+
+static ssize_t state_running_ns_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%lld",
+ ktime_to_ns(shost->stats->state_running_ns));
+}
+static DEVICE_ATTR_RO(state_running_ns);
+
+static ssize_t state_recovery_ns_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%lld",
+ ktime_to_ns(shost->stats->state_recovery_ns));
+}
+static DEVICE_ATTR_RO(state_recovery_ns);
+
+static ssize_t state_other_ns_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%lld",
+ ktime_to_ns(shost->stats->state_other_ns));
+}
+static DEVICE_ATTR_RO(state_other_ns);
+
+static ssize_t state_running_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d",
+ shost->stats->state_running_count);
+}
+static DEVICE_ATTR_RO(state_running_count);
+
+static ssize_t state_recovery_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d",
+ shost->stats->state_recovery_count);
+}
+static DEVICE_ATTR_RO(state_recovery_count);
+
+static ssize_t state_other_count_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d",
+ shost->stats->state_other_count);
+}
+static DEVICE_ATTR_RO(state_other_count);
+
+static struct attribute *scsi_shost_stats[] = {
+ &dev_attr_state_last_change_time.attr,
+ &dev_attr_state_first_change_time.attr,
+ &dev_attr_state_running_ns.attr,
+ &dev_attr_state_recovery_ns.attr,
+ &dev_attr_state_other_ns.attr,
+ &dev_attr_state_running_count.attr,
+ &dev_attr_state_recovery_count.attr,
+ &dev_attr_state_other_count.attr,
+ NULL
+};
+
+static struct attribute_group scsi_shost_stats_group = {
+ .name = "stats",
+ .attrs = scsi_shost_stats,
+};
+
const struct attribute_group *scsi_shost_groups[] = {
&scsi_shost_attr_group,
+ &scsi_shost_stats_group,
NULL
};

diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index 587cc767bb67..e6229567a295 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -513,11 +513,26 @@ struct scsi_host_template {
return rc; \
}

+/*
+ * host statistics
+ */
+
+struct scsi_host_stats {
+ ktime_t state_running_ns;
+ ktime_t state_recovery_ns;
+ ktime_t state_other_ns;
+ ktime_t state_first_change_time;
+ ktime_t state_last_change_time;
+ uint32_t state_running_count;
+ uint32_t state_recovery_count;
+ uint32_t state_other_count;
+};

/*
* shost state: If you alter this, you also need to alter scsi_sysfs.c
* (for the ascii descriptions) and the state model enforcer:
- * scsi_host_set_state()
+ * scsi_host_set_state() and if host state statistics are accounted
+ * for correctly in scsi_host_do_state_stats()
*/
enum scsi_host_state {
SHOST_CREATED = 1,
@@ -704,6 +719,11 @@ struct Scsi_Host {
*/
struct device *dma_dev;

+ /*
+ * Host statistics
+ */
+ struct scsi_host_stats *stats;
+
/*
* We should ensure that this is aligned, both for better performance
* and also because some compilers (m68k) don't automatically force