diff mbox

Devfreq: Add debugfs node for representing frequency transition information

Message ID 1351846587-16376-1-git-send-email-jonghwa3.lee@samsung.com (mailing list archive)
State Accepted, archived
Headers show

Commit Message

Jonghwa Lee Nov. 2, 2012, 8:56 a.m. UTC
This patch adds debugfs node to measure transition of frequency on runtime.
It will be creted under '/sys/kernel/debugfs/devfreq/'dev name'/' as the name
of 'trans_table'. It contains number of transition of each frequency level,
time spent on each level, and also total transition count.
It inspired by CPUFREQ's cpufreq_stats method.

<Example of table>
diff mbox

Patch

=================================================================================
		  <freq level #1>   <freq level #2>  <freq level #3>  time spent
* <freq level #1>                         n12             n13             t1
  <freq level #2>       n21                               n23             t2
  <freq level #3>       n31               n32                             t3

Total transition :	N (= n12 + n13 + n21 + n23 + n31 + n32)
==================================================================================
(n12 : Number of transition from level 1 to level 2)
('*' : The last changed frequency when you inspect the node.)

This patch also includes documentation.

Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
---
 Documentation/devfreq/transition_status_table |  265 +++++++++++++++++++++++++
 drivers/devfreq/devfreq.c                     |  123 ++++++++++++
 include/linux/devfreq.h                       |   23 +++
 3 files changed, 411 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devfreq/transition_status_table

diff --git a/Documentation/devfreq/transition_status_table b/Documentation/devfreq/transition_status_table
new file mode 100644
index 0000000..8b92391
--- /dev/null
+++ b/Documentation/devfreq/transition_status_table
@@ -0,0 +1,265 @@ 
+DEVFREQ Transition status table
+=========================================
+
+Copyright (C) 2012 Samsung Electronics
+
+Written by Jonghwa Lee <jonghwa3.lee@samsung.com>
+
+1. Description
+----------------
+
+DEVFREQ Transition status table represents all of transition information up to now. Transition information
+includes number of transition which has been occured and time spent on the each of frequencies.
+This implementation is ispired by CPUFREQ's cpufreq_stats method.
+
+<Example of table>
+============================================================================================================
+			<freq level #1>		<freq level #2>		<freq level #3>		time spent
+
+* <freq level #1>				Number of transition	Number of transition	   t1
+						from freq1 to freq2	from freq1 to freq3
+
+  <freq level #2>	Number of transition				Number of transition	   t2
+			from freq2 to freq1				from freq2 to freq3
+
+  <freq level #3>	Number of transition	Number of transition				   t3
+			from freq3 to freq1	from freq3 to freq2
+
+Total transition :	N
+=============================================================================================================
+('*' : The last changed frequency when you inspect the node.)
+
+It may be used for measuring performance or debug use.
+
+
+2. Requirement to use
+-----------------------
+
+There is no kernel configuration option for creating this node at this moment. Only CONFIG_DEBUG_FS is required.
+It will be created automatically when devfreq device created successfully.
+(under /sys/kernel/debug/devfreq/'each devfreq device name'/ by the name of 'trans_table'.)
+
+By the way, it's going to show nothing unless you go through the below steps.
+To show appropriate information about frequency transition, each devfreq device must hand over their own list of
+available frequency and total count of frequency levels to the framework. Because at the devfreq core side,
+system doesn't know how many freqeuncy levels are available and what value of each level exactly is.
+In devfreq_dev_profile structure, freq_list and freq_levs will play the role carrying the those requirement data.
+
+To guarantee trans_table working, following two steps are required.
+(1) Initialize the freq_list of devfreq_dev_profile with all available freqeuncy data
+(2) Initialize the freq_levs of devfreq_dev_profile with total number of available freqeuncy level.
+
+3. Guage performance
+----------------------
+
+This program is user space program that measures the frequency transition during the certain interval you set.
+When the program starts, it parses the trans_table contents and stores it for later comparison at the end.
+And when you stop the gauging, the program will calculate transition information happened during the interval given.
+The result is also formed like transition status table. However it can be used for more specific purpose and usefully
+than trans_table itself. By analyzing its data, we can get an indicator of performance of various situations or
+performance of governor and others also.
+
+HOW TO USE
+-----------
+1. Set the device name correctly in the code to the variable 'dev_name[]' before the compile.
+   This will be used to get the path of debugfs node. (Default is 'exynos4412-busfreq').
+2. Run the program, and you'll see comments of '### Press any key to start, 'q' to exit >>' or error massage.
+3. Input any key then it starts measurement with comments 'Gauging start...' and '### Press any key....'.
+4. Press any key again. It shows the result of measurement directly.
+5. Program will repeat step 2.
+
+<guage_devfreq.c>
+------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <termios.h>
+
+#define EXTRA_LINE	3
+#define SEC_TO_MSEC	1000
+
+/* FIXME :
+ * Before compile this program,
+ * you shoud fix dev_name[] with device name
+ * which will be tested.
+ */
+/* for exynos4412 */
+char dev_name[] = "exynos4412-busfreq";
+
+struct trans_info {
+	unsigned int max_state;
+	unsigned int *freq;
+	unsigned int *trans_table;
+	unsigned long *time_in_state;
+};
+
+int parse_status(char *fpath, struct trans_info *info)
+{
+	int i, j;
+	char buf[256];
+	unsigned int ntrans;
+	unsigned long time;
+	int max_state = info->max_state;
+	FILE *fp;
+
+	fp = fopen(fpath, "r");
+	if (fp == NULL) {
+		printf("ERR: Coudn't open the file. \n");
+		return -EINVAL;
+	}
+	if(!info->freq[0]) {
+		/* Init freq array with presented frequency */
+		fgets(buf, 256, fp);
+		fgets(buf, 11, fp);
+
+		for (i = 0; i < max_state; i++)
+			fscanf(fp, "%8d",&info->freq[i]);
+
+		fgets(buf, 13, fp);
+	} else {
+		/* Already initialized freq array. Ignore header */
+		fgets(buf, 256, fp);
+		fgets(buf, 256, fp);
+	}
+	for (i = 0; i < max_state; i++) {
+			fgets(buf, 11, fp);
+		for(j = 0; j < max_state; j++) {
+			fscanf(fp, "%8u",&ntrans);
+			info->trans_table[(max_state * i) + j]  =
+				ntrans - info->trans_table[(max_state * i) + j];
+		}
+			fscanf(fp, "%10lu", &time);
+			info->time_in_state[i] = time - info->time_in_state[i];
+			fgetc(fp);
+	}
+
+	return 0;
+}
+
+void show_table(struct trans_info *info)
+{
+	int i, j;
+	unsigned long total_period = 0;
+	int max_state = info->max_state;
+
+	printf("\n\n");
+	for (i = 0; i < (info->max_state / 2) + 1; i++)
+		printf("\t");
+	printf("<Guaging result>\n");
+
+	/* Print table bar */
+	printf("   From  :   To\n");
+	printf("         :");
+	for (i = 0; i < max_state; i++)
+		printf("%8u", info->freq[i]);
+
+	printf("   time(ms)\n");
+
+	/* Print table entry */
+	for (i = 0; i < max_state; i++) {
+
+		printf("%8u:", info->freq[i]);
+
+		for (j = 0; j < max_state; j++)
+			printf("%8u", info->trans_table[(i *
+					max_state) + j]);
+
+		printf("%10lu\n", info->time_in_state[i]);
+		total_period += info->time_in_state[i];
+	}
+
+
+	printf("Total period : %lu\n", total_period / SEC_TO_MSEC);
+	return;
+}
+
+struct trans_info *trans_info_init(char *fpath)
+{
+	struct trans_info *info;
+	FILE *fp;
+	char buf[256];
+	int cnt = 0;
+
+	fp = fopen(fpath, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "ERROR: Coudn't open the file. \n");
+		exit(errno);
+	}
+
+	/* Count number of lines in table */
+	while (fgets(buf, 256, fp)) {
+		cnt++;
+	}
+
+	info = malloc(sizeof(struct trans_info));
+	info->max_state = cnt - EXTRA_LINE;
+	info->freq = malloc(sizeof(unsigned int) * info->max_state);
+	info->trans_table = malloc(sizeof(unsigned int) * info->max_state
+							* info->max_state);
+	info->time_in_state = malloc(sizeof(unsigned long) * info->max_state);
+
+	fclose(fp);
+
+	return info;
+}
+
+int getch()
+{
+	struct termios oldt, newt;
+	int ch;
+
+	tcgetattr(STDIN_FILENO, &oldt);
+	newt = oldt;
+	newt.c_lflag &= ~(ICANON | ECHO);
+	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
+	ch = getchar();
+	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
+	return ch;
+}
+
+int main(int argc, char **argv) {
+
+	FILE *fp;
+	struct trans_info *info;
+	char ch, fpath[256];
+	int res;
+
+	strcpy(fpath, "/sys/class/devfreq/");
+	strcat(fpath, dev_name);
+	strcat(fpath, "/trans_stat");
+
+	info = trans_info_init(fpath);
+	if (info == NULL) {
+		fprintf(stderr, "ERROR: Initialization failed\n");
+		exit(errno);
+	}
+
+	printf("### Press any key to start, 'q' to exit >>");
+	ch = getch();
+	if (ch == 'q') {
+		printf("\n\nFinish the guaging....\n");
+		return 0;
+	} else {
+		printf("\nGuaging start...\n");
+		printf("### Press any key to stop guaging >>");
+
+		/* Parse trans_stat */
+		res = parse_status(fpath, info);
+		if (res)
+			return res;
+
+		ch = getch();
+		/* Re-Parse trans_stat after duration */
+		res = parse_status(fpath, info);
+		if (res)
+			return res;
+
+		show_table(info);
+	}
+
+	return 0;
+}
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index b146d76..6012b09 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -27,6 +27,8 @@ 
 #include <linux/hrtimer.h>
 #include "governor.h"
 
+#include <linux/debugfs.h>
+
 struct class *devfreq_class;
 
 /*
@@ -46,6 +48,8 @@  static struct devfreq *wait_remove_device;
 static LIST_HEAD(devfreq_list);
 static DEFINE_MUTEX(devfreq_list_lock);
 
+static struct dentry *devfreq_debugfs;
+
 /**
  * find_device_devfreq() - find devfreq struct using device pointer
  * @dev:	device pointer used to lookup device devfreq.
@@ -72,6 +76,40 @@  static struct devfreq *find_device_devfreq(struct device *dev)
 	return ERR_PTR(-ENODEV);
 }
 
+int devfreq_get_freq_level(struct devfreq *devfreq, unsigned long freq)
+{
+	int lev;
+
+	for (lev = 0; lev < devfreq->profile->freq_levs; lev++)
+		if (freq == devfreq->profile->freq_list[lev])
+			return lev;
+
+	return -EINVAL;
+}
+
+int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
+{
+	int lev, prev_lev;
+	unsigned long cur_time;
+
+	prev_lev = devfreq_get_freq_level(devfreq, devfreq->previous_freq);
+	if (prev_lev < 0)
+		return lev;
+
+	cur_time = jiffies;
+	devfreq->time_in_state[prev_lev] +=
+			 cur_time - devfreq->last_trinfo_updated;
+	if (freq != devfreq->previous_freq) {
+		lev = devfreq_get_freq_level(devfreq, freq);
+		devfreq->trans_table[(prev_lev *
+				devfreq->profile->freq_levs) + lev]++;
+		devfreq->total_trans++;
+	}
+	devfreq->last_trinfo_updated = cur_time;
+
+	return 0;
+}
+
 /**
  * update_devfreq() - Reevaluate the device and configure frequency.
  * @devfreq:	the devfreq instance.
@@ -116,6 +154,11 @@  int update_devfreq(struct devfreq *devfreq)
 	if (err)
 		return err;
 
+	if (devfreq->profile->freq_list)
+		if (devfreq_update_status(devfreq, freq))
+			dev_err(&devfreq->dev,
+				"Couldn't update frequency transition information.\n");
+
 	devfreq->previous_freq = freq;
 	return err;
 }
@@ -179,6 +222,8 @@  static void _remove_devfreq(struct devfreq *devfreq, bool skip)
 
 	devfreq->being_removed = true;
 
+	debugfs_remove_recursive(devfreq->debugfs);
+
 	if (devfreq->profile->exit)
 		devfreq->profile->exit(devfreq->dev.parent);
 
@@ -336,6 +381,59 @@  static void devfreq_monitor(struct work_struct *work)
 	}
 }
 
+static ssize_t show_trans_table(struct file *file, char __user *user_buf,
+				size_t count, loff_t *ppos)
+{
+	char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	struct devfreq *devfreq = file->private_data;
+	ssize_t len = 0;
+	int i, j, err;
+	unsigned int freq_levs = devfreq->profile->freq_levs;
+
+	err = devfreq_update_status(devfreq, devfreq->previous_freq);
+	if (err)
+		return 0;
+
+	len = snprintf(buf, PAGE_SIZE, "   From  :   To\n");
+	len += snprintf(buf + len, PAGE_SIZE - len, "         :");
+	for (i = 0; i < freq_levs; i++)
+		len += snprintf(buf + len, PAGE_SIZE - len, "%8u",
+				devfreq->profile->freq_list[i]);
+
+	len += snprintf(buf + len, PAGE_SIZE - len, "   time(ms)\n");
+
+	for (i = 0; i < freq_levs; i++) {
+		if (devfreq->profile->freq_list[i]
+					== devfreq->previous_freq) {
+			len += snprintf(buf + len, PAGE_SIZE - len, "*");
+		} else {
+			len += snprintf(buf + len, PAGE_SIZE - len, " ");
+		}
+		len += snprintf(buf + len, PAGE_SIZE - len, "%8u:",
+				devfreq->profile->freq_list[i]);
+		for (j = 0; j < freq_levs; j++)
+			len += snprintf(buf + len, PAGE_SIZE - len, "%8u",
+				devfreq->trans_table[(i * freq_levs) + j]);
+		len += snprintf(buf + len, PAGE_SIZE - len, "%10u\n",
+			jiffies_to_msecs(devfreq->time_in_state[i]));
+	}
+
+	len += snprintf(buf + len, PAGE_SIZE - len, "Total transition : %u\n",
+					devfreq->total_trans);
+
+	len = simple_read_from_buffer(user_buf, count, ppos, buf, len);
+
+	kfree(buf);
+
+	return len;
+}
+
+static const struct file_operations devfreq_trans_table_fops = {
+	.open		= simple_open,
+	.read		= show_trans_table,
+	.llseek		= default_llseek,
+};
+
 /**
  * devfreq_add_device() - Add devfreq feature to the device
  * @dev:	the device to add devfreq feature.
@@ -390,6 +488,15 @@  struct devfreq *devfreq_add_device(struct device *dev,
 			      = msecs_to_jiffies(devfreq->profile->polling_ms);
 	devfreq->nb.notifier_call = devfreq_notifier_call;
 
+	devfreq->trans_table =	devm_kzalloc(dev, sizeof(unsigned int) *
+						devfreq->profile->freq_levs *
+						devfreq->profile->freq_levs,
+						GFP_KERNEL);
+	devfreq->time_in_state = devm_kzalloc(dev, sizeof(unsigned int) *
+						devfreq->profile->freq_levs,
+						GFP_KERNEL);
+	devfreq->last_trinfo_updated = jiffies;
+
 	dev_set_name(&devfreq->dev, dev_name(dev));
 	err = device_register(&devfreq->dev);
 	if (err) {
@@ -417,6 +524,16 @@  struct devfreq *devfreq_add_device(struct device *dev,
 				   devfreq->next_polling);
 	}
 	mutex_unlock(&devfreq_list_lock);
+
+	mutex_lock(&devfreq->lock);
+	if (!IS_ERR(devfreq_debugfs)) {
+		devfreq->debugfs = debugfs_create_dir(dev_name(dev),
+							devfreq_debugfs);
+		debugfs_create_file("trans_table", S_IRUGO, devfreq->debugfs,
+					devfreq, &devfreq_trans_table_fops);
+	}
+	mutex_unlock(&devfreq->lock);
+
 out:
 	return devfreq;
 
@@ -623,12 +740,18 @@  static int __init devfreq_init(void)
 		return PTR_ERR(devfreq_class);
 	}
 	devfreq_class->dev_attrs = devfreq_attrs;
+
+	devfreq_debugfs = debugfs_create_dir("devfreq", NULL);
+	if (IS_ERR(devfreq_debugfs))
+		pr_err("%s: couldn't create debugfs\n", __FILE__);
+
 	return 0;
 }
 subsys_initcall(devfreq_init);
 
 static void __exit devfreq_exit(void)
 {
+	debugfs_remove(devfreq_debugfs);
 	class_destroy(devfreq_class);
 }
 module_exit(devfreq_exit);
diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h
index 281c72a..d9cce45 100644
--- a/include/linux/devfreq.h
+++ b/include/linux/devfreq.h
@@ -57,6 +57,10 @@  struct devfreq_dev_status {
  * @initial_freq	The operating frequency when devfreq_add_device() is
  *			called.
  * @polling_ms		The polling interval in ms. 0 disables polling.
+ *
+ * @freq_list		List for all availble frequencies in this DEVFREQ.
+ * @freq_levs		Total number of frequncy levels.
+ *
  * @target		The device should set its operating frequency at
  *			freq or lowest-upper-than-freq value. If freq is
  *			higher than any operable frequency, set maximum.
@@ -71,11 +75,16 @@  struct devfreq_dev_status {
  *			from devfreq_remove_device() call. If the user
  *			has registered devfreq->nb at a notifier-head,
  *			this is the time to unregister it.
+ *
+ * @
  */
 struct devfreq_dev_profile {
 	unsigned long initial_freq;
 	unsigned int polling_ms;
 
+	unsigned int *freq_list;
+	unsigned int freq_levs;
+
 	int (*target)(struct device *dev, unsigned long *freq, u32 flags);
 	int (*get_dev_status)(struct device *dev,
 			      struct devfreq_dev_status *stat);
@@ -137,6 +146,12 @@  struct devfreq_governor {
  * @min_freq	Limit minimum frequency requested by user (0: none)
  * @max_freq	Limit maximum frequency requested by user (0: none)
  *
+ * @total_trans			Total trasition number.
+ * @trans_table			Transition count of each frequency level.
+ * @time_in_state		Spent time of each freqeuncy level.
+ * @last_trinfo_updated		Time mark of last transition information updating.
+ *
+ * @debgufs
  * This structure stores the devfreq information for a give device.
  *
  * Note that when a governor accesses entries in struct devfreq in its
@@ -164,6 +179,14 @@  struct devfreq {
 
 	unsigned long min_freq;
 	unsigned long max_freq;
+
+	/* information for device freqeuncy transition */
+	unsigned int total_trans;
+	unsigned int *trans_table;
+	unsigned long *time_in_state;
+	unsigned long last_trinfo_updated;
+
+	struct dentry *debugfs;
 };
 
 #if defined(CONFIG_PM_DEVFREQ)