Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 43b8d157 authored by Andres Oportus's avatar Andres Oportus Committed by Vincent Zvikaramba
Browse files

ANDROID: cpufreq_stat: add per task/uid/freq stats



Adds per process nodes in /proc/PID/time_in_state showing per
frequency times and adds a global /proc/uid_time_in_state
showing per frequency per uid times.

Bug: 34133340
Bug: 38320164

Tests: boot sailfish and reading /proc/uid_time_in_state and
/proc/$$/time_in_state

Signed-off-by: default avatarAndres Oportus <andresoportus@google.com>
Change-Id: Ideb22b608b9a5e7bd2200a3a6df0f110b635f96a
parent 9da6dd3a
Loading
Loading
Loading
Loading
+332 −26
Original line number Diff line number Diff line
@@ -9,26 +9,47 @@
 * published by the Free Software Foundation.
 */

#include <linux/atomic.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <asm/cputime.h>
#include <linux/hashtable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/proc_fs.h>
#include <linux/profile.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/sort.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/sched.h>
#include <asm/cputime.h>

#define UID_HASH_BITS 10

DECLARE_HASHTABLE(uid_hash_table, UID_HASH_BITS);

static spinlock_t cpufreq_stats_lock;
static DEFINE_SPINLOCK(cpufreq_stats_table_lock);

static DEFINE_RT_MUTEX(uid_lock); /* uid_hash_table */

struct uid_entry {
	uid_t uid;
	unsigned int dead_max_states;
	unsigned int alive_max_states;
	u64 *dead_time_in_state;
	u64 *alive_time_in_state;
	struct hlist_node hash;
};

struct cpufreq_stats {
	unsigned int cpu;
	unsigned int total_trans;
	unsigned long long last_time;
	unsigned int max_state;
	unsigned int state_num;
	unsigned int last_index;
	atomic_t cpu_freq_i;
	atomic_t all_freq_i;
	u64 *time_in_state;
	unsigned int *freq_table;
#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
@@ -54,6 +75,7 @@ struct all_freq_table {
};

static struct all_freq_table *all_freq_table;
static bool cpufreq_all_freq_init;

static DEFINE_PER_CPU(struct all_cpufreq_stats *, all_cpufreq_stats);
static DEFINE_PER_CPU(struct cpufreq_stats *, cpufreq_stats_table);
@@ -64,6 +86,125 @@ struct cpufreq_stats_attribute {
	ssize_t(*show) (struct cpufreq_stats *, char *);
};

/* Caller must hold uid lock */
static struct uid_entry *find_uid_entry(uid_t uid)
{
	struct uid_entry *uid_entry;

	hash_for_each_possible(uid_hash_table, uid_entry, hash, uid) {
		if (uid_entry->uid == uid)
			return uid_entry;
	}
	return NULL;
}

/* Caller must hold uid lock */
static struct uid_entry *find_or_register_uid(uid_t uid)
{
	struct uid_entry *uid_entry;

	uid_entry = find_uid_entry(uid);
	if (uid_entry)
		return uid_entry;

	uid_entry = kzalloc(sizeof(struct uid_entry), GFP_ATOMIC);
	if (!uid_entry)
		return NULL;

	uid_entry->uid = uid;

	hash_add(uid_hash_table, &uid_entry->hash, uid);

	return uid_entry;
}


static int uid_time_in_state_show(struct seq_file *m, void *v)
{
	struct uid_entry *uid_entry;
	struct task_struct *task, *temp;
	unsigned long bkt;
	int i;

	if (!all_freq_table || !cpufreq_all_freq_init)
		return 0;

	seq_puts(m, "uid:");
	for (i = 0; i < all_freq_table->table_size; ++i)
		seq_printf(m, " %d", all_freq_table->freq_table[i]);
	seq_putc(m, '\n');

	rt_mutex_lock(&uid_lock);

	rcu_read_lock();
	do_each_thread(temp, task) {
		/* if this task has exited, we have already accounted for all
		 * time in state
		 */
		if (!task->time_in_state)
			continue;

		uid_entry = find_or_register_uid(from_kuid_munged(
			current_user_ns(), task_uid(task)));
		if (!uid_entry)
			continue;

		if (uid_entry->alive_max_states < task->max_states) {
			uid_entry->alive_time_in_state = krealloc(
				uid_entry->alive_time_in_state,
				task->max_states *
				sizeof(uid_entry->alive_time_in_state[0]),
				GFP_ATOMIC);
			memset(uid_entry->alive_time_in_state +
				uid_entry->alive_max_states,
				0, (task->max_states -
				uid_entry->alive_max_states) *
				sizeof(uid_entry->alive_time_in_state[0]));
			uid_entry->alive_max_states = task->max_states;
		}

		for (i = 0; i < task->max_states; ++i) {
			uid_entry->alive_time_in_state[i] +=
				atomic_read(&task->time_in_state[i]);
		}
	} while_each_thread(temp, task);
	rcu_read_unlock();

	hash_for_each(uid_hash_table, bkt, uid_entry, hash) {
		int max_states = uid_entry->dead_max_states;

		if (uid_entry->alive_max_states > max_states)
			max_states = uid_entry->alive_max_states;
		if (max_states)
			seq_printf(m, "%d:", uid_entry->uid);
		for (i = 0; i < max_states; ++i) {
			u64 total_time_in_state = 0;

			if (uid_entry->dead_time_in_state &&
				i < uid_entry->dead_max_states) {
				total_time_in_state =
					uid_entry->dead_time_in_state[i];
			}
			if (uid_entry->alive_time_in_state &&
				i < uid_entry->alive_max_states) {
				total_time_in_state +=
					uid_entry->alive_time_in_state[i];
			}
			seq_printf(m, " %lu", (unsigned long)
				cputime_to_clock_t(total_time_in_state));
		}
		if (max_states)
			seq_putc(m, '\n');

		kfree(uid_entry->alive_time_in_state);
		uid_entry->alive_time_in_state = NULL;
		uid_entry->alive_max_states = 0;
	}

	rt_mutex_unlock(&uid_lock);
	return 0;
}

static int cpufreq_stats_update(unsigned int cpu)
{
	struct cpufreq_stats *stat;
@@ -79,10 +220,11 @@ static int cpufreq_stats_update(unsigned int cpu)
		return 0;
	}
	if (stat->time_in_state) {
		stat->time_in_state[stat->last_index] +=
			cur_time - stat->last_time;
		int cpu_freq_i = atomic_read(&stat->cpu_freq_i);

		stat->time_in_state[cpu_freq_i] += cur_time - stat->last_time;
		if (all_stat)
			all_stat->time_in_state[stat->last_index] +=
			all_stat->time_in_state[cpu_freq_i] +=
				cur_time - stat->last_time;
	}
	stat->last_time = cur_time;
@@ -90,6 +232,54 @@ static int cpufreq_stats_update(unsigned int cpu)
	return 0;
}

void cpufreq_task_stats_init(struct task_struct *p)
{
	size_t alloc_size;

	WRITE_ONCE(p->time_in_state, NULL);
	WRITE_ONCE(p->max_states, 0);

	if (!all_freq_table || !cpufreq_all_freq_init)
		return;

	WRITE_ONCE(p->max_states, all_freq_table->table_size);

	/* Create all_freq_table for clockticks in all possible freqs in all
	 * cpus
	 */
	alloc_size = p->max_states * sizeof(p->time_in_state[0]);

	WRITE_ONCE(p->time_in_state, kzalloc(alloc_size, GFP_KERNEL));
}

void cpufreq_task_stats_exit(struct task_struct *p)
{
	void *temp = p->time_in_state;

	WRITE_ONCE(p->time_in_state, NULL);
	mb(); /* p->time_in_state */
	kfree(temp);
}

int proc_time_in_state_show(struct seq_file *m, struct pid_namespace *ns,
			    struct pid *pid, struct task_struct *p)
{
	int i;

	if (!all_freq_table || !cpufreq_all_freq_init || !p->time_in_state)
		return 0;

	spin_lock(&cpufreq_stats_lock);
	for (i = 0; i < p->max_states; ++i) {
		seq_printf(m, "%d %lu\n", all_freq_table->freq_table[i],
			(unsigned long)cputime_to_clock_t(
				atomic_read(&p->time_in_state[i])));
	}
	spin_unlock(&cpufreq_stats_lock);

	return 0;
}

static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf)
{
	struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, policy->cpu);
@@ -128,28 +318,51 @@ static int get_index_all_cpufreq_stat(struct all_cpufreq_stats *all_stat,
	return -1;
}

/* Called without cpufreq_stats_lock held */
void acct_update_power(struct task_struct *task, cputime_t cputime) {
	struct cpufreq_power_stats *powerstats;
	struct cpufreq_stats *stats;
	unsigned int cpu_num, curr;
	unsigned long flags;
	int cpu_freq_i;
	int all_freq_i;
	u64 last_cputime;
	atomic64_t *time_in_state;

	if (!task)
		return;
	cpu_num = task_cpu(task);
	powerstats = per_cpu(cpufreq_power_stats, cpu_num);

	spin_lock_irqsave(&cpufreq_stats_table_lock, flags);
	cpu_num = task_cpu(task);
	stats = per_cpu(cpufreq_stats_table, cpu_num);
	if (!powerstats || !stats) {
		spin_unlock_irqrestore(&cpufreq_stats_table_lock, flags);
	if (!stats)
		return;

	all_freq_i = atomic_read(&stats->all_freq_i);
	time_in_state = READ_ONCE(task->time_in_state);

	/* This function is called from a different context
	 * Interruptions in between reads/assignements are ok
	 */
	if (all_freq_table && cpufreq_all_freq_init && time_in_state &&
		!(task->flags & PF_EXITING) &&
		all_freq_i != -1 && all_freq_i < READ_ONCE(task->max_states)) {
		last_cputime =
			atomic_read(&time_in_state[all_freq_i]);
		atomic_set(&time_in_state[all_freq_i],
			last_cputime + cputime);
	}

	curr = powerstats->curr[stats->last_index];
	powerstats = per_cpu(cpufreq_power_stats, cpu_num);
	if (!powerstats)
		return;

	cpu_freq_i = atomic_read(&stats->cpu_freq_i);
	if (cpu_freq_i == -1)
 		return;

	curr = powerstats->curr[cpu_freq_i];
	if (task->cpu_power != ULLONG_MAX)
		task->cpu_power += curr * cputime_to_usecs(cputime);
	spin_unlock_irqrestore(&cpufreq_stats_table_lock, flags);
}
EXPORT_SYMBOL_GPL(acct_update_power);

@@ -200,7 +413,7 @@ static ssize_t show_all_time_in_state(struct kobject *kobj,
			index = get_index_all_cpufreq_stat(all_stat, freq);
			if (index != -1) {
				len += scnprintf(buf + len, PAGE_SIZE - len,
					"%llu\t\t", (unsigned long long)
					"%lu\t\t", (unsigned long)
					cputime64_to_clock_t(all_stat->time_in_state[index]));
			} else {
				len += scnprintf(buf + len, PAGE_SIZE - len,
@@ -416,7 +629,7 @@ static int __cpufreq_stats_create_table(struct cpufreq_policy *policy,
	stat->state_num = j;
	spin_lock(&cpufreq_stats_lock);
	stat->last_time = get_jiffies_64();
	stat->last_index = freq_table_get_index(stat, policy->cur);
	atomic_set(&stat->cpu_freq_i, freq_table_get_index(stat, policy->cur));
	spin_unlock(&cpufreq_stats_lock);
	return 0;
error_alloc:
@@ -544,7 +757,8 @@ static void free_all_freq_table(void)
static void add_all_freq_table(unsigned int freq)
{
	unsigned int size;
	size = sizeof(unsigned int) * (all_freq_table->table_size + 1);
	size = sizeof(all_freq_table->freq_table[0]) *
		(all_freq_table->table_size + 1);
	all_freq_table->freq_table = krealloc(all_freq_table->freq_table,
			size, GFP_ATOMIC);
	if (IS_ERR(all_freq_table->freq_table)) {
@@ -679,9 +893,11 @@ static void cpufreq_stats_create_table(unsigned int cpu)
static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
		unsigned long val, void *data)
{
	int i;
	struct cpufreq_freqs *freq = data;
	struct cpufreq_stats *stat;
	int old_index, new_index;
	int cpu_freq_old_i, cpu_freq_new_i;
	int all_freq_old_i, all_freq_new_i;

	if (val != CPUFREQ_POSTCHANGE)
		return 0;
@@ -690,28 +906,101 @@ static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
	if (!stat)
		return 0;

	old_index = stat->last_index;
	new_index = freq_table_get_index(stat, freq->new);
	cpu_freq_old_i = atomic_read(&stat->cpu_freq_i);
	cpu_freq_new_i = freq_table_get_index(stat, freq->new);

	all_freq_old_i = atomic_read(&stat->all_freq_i);
	for (i = 0; i < all_freq_table->table_size; ++i) {
		if (all_freq_table->freq_table[i] == freq->new)
			break;
	}
	if (i != all_freq_table->table_size)
		all_freq_new_i = i;
	else
		all_freq_new_i = -1;

	/* We can't do stat->time_in_state[-1]= .. */
	if (old_index == -1 || new_index == -1)
	if (cpu_freq_old_i == -1 || cpu_freq_new_i == -1)
		return 0;

	if (all_freq_old_i == -1 || all_freq_new_i == -1)
		return 0;

	cpufreq_stats_update(freq->cpu);

	if (old_index == new_index)
	if (cpu_freq_old_i == cpu_freq_new_i)
		return 0;

	if (all_freq_old_i == all_freq_new_i)
		return 0;

	spin_lock(&cpufreq_stats_lock);
	stat->last_index = new_index;
	atomic_set(&stat->cpu_freq_i, cpu_freq_new_i);
	atomic_set(&stat->all_freq_i, all_freq_new_i);
#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
	stat->trans_table[old_index * stat->max_state + new_index]++;
	stat->trans_table[cpu_freq_old_i * stat->max_state + cpu_freq_new_i]++;
#endif
	stat->total_trans++;
	spin_unlock(&cpufreq_stats_lock);
	return 0;
}


static int process_notifier(struct notifier_block *self,
			unsigned long cmd, void *v)
{
	struct task_struct *task = v;
	struct uid_entry *uid_entry;
	uid_t uid;
	int i;

	if (!task)
		return NOTIFY_OK;

	rt_mutex_lock(&uid_lock);

	uid = from_kuid_munged(current_user_ns(), task_uid(task));
	uid_entry = find_or_register_uid(uid);
	if (!uid_entry) {
		rt_mutex_unlock(&uid_lock);
		pr_err("%s: failed to find uid %d\n", __func__, uid);
		return NOTIFY_OK;
	}

	if (uid_entry->dead_max_states < task->max_states) {
		uid_entry->dead_time_in_state = krealloc(
			uid_entry->dead_time_in_state,
			task->max_states *
			sizeof(uid_entry->dead_time_in_state[0]),
			GFP_ATOMIC);
		memset(uid_entry->dead_time_in_state +
			uid_entry->dead_max_states,
			0, (task->max_states - uid_entry->dead_max_states) *
			sizeof(uid_entry->dead_time_in_state[0]));
		uid_entry->dead_max_states = task->max_states;
	}

	for (i = 0; i < task->max_states; ++i) {
		uid_entry->dead_time_in_state[i] +=
			atomic_read(&task->time_in_state[i]);
	}

	rt_mutex_unlock(&uid_lock);
	return NOTIFY_OK;
}

static int uid_time_in_state_open(struct inode *inode, struct file *file)
{
	return single_open(file, uid_time_in_state_show, PDE_DATA(inode));
}

static const struct file_operations uid_time_in_state_fops = {
	.open		= uid_time_in_state_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
};

static struct notifier_block notifier_policy_block = {
	.notifier_call = cpufreq_stat_notifier_policy
};
@@ -720,6 +1009,10 @@ static struct notifier_block notifier_trans_block = {
	.notifier_call = cpufreq_stat_notifier_trans
};

static struct notifier_block process_notifier_block = {
	.notifier_call	= process_notifier,
};

static int __init cpufreq_stats_init(void)
{
	int ret;
@@ -733,9 +1026,16 @@ static int __init cpufreq_stats_init(void)

	create_all_freq_table();

	get_online_cpus();
	for_each_online_cpu(cpu)
		cpufreq_stats_create_table(cpu);

	put_online_cpus();
	/* XXX TODO task support for time_in_state doesn't update freq
	 * info for tasks already initialized, so tasks initialized early
	 * (before cpufreq_stat_init is done) do not get time_in_state data
	 * and CPUFREQ_TRANSITION_NOTIFIER does not update freq info for
	 * tasks already created
	 */
	ret = cpufreq_register_notifier(&notifier_trans_block,
				CPUFREQ_TRANSITION_NOTIFIER);
	if (ret) {
@@ -755,6 +1055,12 @@ static int __init cpufreq_stats_init(void)
	if (ret)
		pr_warn("Cannot create sysfs file for cpufreq current stats\n");

	proc_create_data("uid_time_in_state", 0444, NULL,
		&uid_time_in_state_fops, NULL);

	profile_event_register(PROFILE_TASK_EXIT, &process_notifier_block);

	cpufreq_all_freq_init = true;
	return 0;
}
static void __exit cpufreq_stats_exit(void)
+7 −0
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@
#include <linux/printk.h>
#include <linux/cgroup.h>
#include <linux/cpuset.h>
#include <linux/cpufreq.h>
#include <linux/audit.h>
#include <linux/poll.h>
#include <linux/nsproxy.h>
@@ -2792,6 +2793,9 @@ static const struct pid_entry tgid_base_stuff[] = {
#ifdef CONFIG_CHECKPOINT_RESTORE
	REG("timers",	  S_IRUGO, proc_timers_operations),
#endif
#ifdef CONFIG_CPU_FREQ_STAT
	ONE("time_in_state", 0444, proc_time_in_state_show),
#endif
};

static int proc_tgid_base_readdir(struct file * filp,
@@ -3183,6 +3187,9 @@ static const struct pid_entry tid_base_stuff[] = {
	REG("projid_map", S_IRUGO|S_IWUSR, proc_projid_map_operations),
	REG("setgroups",  S_IRUGO|S_IWUSR, proc_setgroups_operations),
#endif
#ifdef CONFIG_CPU_FREQ_STAT
	ONE("time_in_state", 0444, proc_time_in_state_show),
#endif
};

static int proc_tid_base_readdir(struct file * filp,
+7 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#ifndef _LINUX_CPUFREQ_H
#define _LINUX_CPUFREQ_H

#include <linux/pid_namespace.h>
#include <linux/cpumask.h>
#include <linux/completion.h>
#include <linux/kobject.h>
@@ -498,4 +499,10 @@ static inline void acct_update_power(struct task_struct *p, cputime_t cputime) {
#endif
#define MIN_FINGER_LIMIT 1344000

void cpufreq_task_stats_init(struct task_struct *p);
void cpufreq_task_stats_exit(struct task_struct *p);
int  proc_time_in_state_show(struct seq_file *m, struct pid_namespace *ns,
			     struct pid *pid, struct task_struct *p);


#endif /* _LINUX_CPUFREQ_H */
+2 −0
Original line number Diff line number Diff line
@@ -1335,6 +1335,8 @@ struct task_struct {

	cputime_t utime, stime, utimescaled, stimescaled;
	cputime_t gtime;
	atomic64_t *time_in_state;
	unsigned int max_states;
	unsigned long long cpu_power;
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
	struct cputime prev_cputime;
+4 −0
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@
#include <linux/oom.h>
#include <linux/writeback.h>
#include <linux/shm.h>
#include <linux/cpufreq.h>

#include <asm/uaccess.h>
#include <asm/unistd.h>
@@ -172,6 +173,9 @@ void release_task(struct task_struct * p)
{
	struct task_struct *leader;
	int zap_leader;
#ifdef CONFIG_CPU_FREQ_STAT
	cpufreq_task_stats_exit(p);
#endif
repeat:
	/* don't need to get the RCU readlock here - the process is dead and
	 * can't be modifying its own credentials. But shut RCU-lockdep up */
Loading