blob: da89baeb526cbb257139b6df840c16996c7ed291 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/gfp.h>
#include <linux/proc_fs.h>
#include <linux/mmzone.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/sort.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/kernel_stat.h>
#include <linux/memcontrol.h>
#include <linux/mm_inline.h>
#include <linux/sched/clock.h>
#include <linux/gfp.h>
#include <linux/mm.h>
#include <linux/jhash.h>
#include <asm/stacktrace.h>
#include <linux/amlogic/file_cache.h>
#include <linux/tick.h>
#include <trace/hooks/mm.h>
#include <linux/amlogic/pin_file.h>
static int file_cache_filter = 64; /* not print size < file_cache_filter, kb */
static struct proc_dir_entry *d_filecache;
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
static int record_fct(struct page *page, struct file_cache_trace *fct,
int *used, struct rb_root *root, int mc,
int active)
{
struct address_space *mapping;
struct file_cache_trace *tmp;
struct rb_node **link, *parent = NULL;
mapping = page_mapping(page);
if (!mapping)
return -1;
link = &root->rb_node;
while (*link) {
parent = *link;
tmp = rb_entry(parent, struct file_cache_trace, entry);
if (mapping == tmp->mapping) { /* match */
tmp->count++;
tmp->mapcnt += mc;
if (active)
tmp->active_count++;
else
tmp->inactive_count++;
return 0;
} else if (mapping < tmp->mapping) {
link = &tmp->entry.rb_left;
} else {
link = &tmp->entry.rb_right;
}
}
/* not found, get a new page summary */
if (*used >= MAX_FCT) {
pr_err("file out of range\n");
return -ERANGE;
}
tmp = &fct[*used];
*used = (*used) + 1;
tmp->mapping = mapping;
tmp->count++;
tmp->mapcnt += mc;
tmp->off = page_to_pgoff(page);
if (active)
tmp->active_count++;
else
tmp->inactive_count++;
rb_link_node(&tmp->entry, parent, link);
rb_insert_color(&tmp->entry, root);
return 0;
}
#endif
struct filecache_stat {
unsigned int total;
unsigned int nomap[3]; /* include active/inactive */
unsigned int files;
};
static struct filecache_stat fs;
static inline unsigned long vma_start_pgoff(struct vm_area_struct *v)
{
return v->vm_pgoff;
}
static inline unsigned long vma_last_pgoff(struct vm_area_struct *v)
{
return v->vm_pgoff + vma_pages(v) - 1;
}
/*
* Iterate over intervals intersecting [start;last]
*
* Note that a node's interval intersects [start;last] iff:
* Cond1: vma_start_pgoff(node) <= last
* and
* Cond2: start <= vma_last_pgoff(node)
*/
static struct vm_area_struct *
aml_vma_subtree_search(struct vm_area_struct *node, unsigned long start, unsigned long last)
{
while (true) {
/*
* Loop invariant: start <= node->shared.rb_subtree_last
* (Cond2 is satisfied by one of the subtree nodes)
*/
if (node->shared.rb.rb_left) {
struct vm_area_struct *left = rb_entry(node->shared.rb.rb_left,
struct vm_area_struct, shared.rb);
if (start <= left->shared.rb_subtree_last) {
/*
* Some nodes in left subtree satisfy Cond2.
* Iterate to find the leftmost such node N.
* If it also satisfies Cond1, that's the
* match we are looking for. Otherwise, there
* is no matching interval as nodes to the
* right of N can't satisfy Cond1 either.
*/
node = left;
continue;
}
}
if (vma_start_pgoff(node) <= last) { /* Cond1 */
if (start <= vma_last_pgoff(node)) /* Cond2 */
return node; /* node is leftmost match */
if (node->shared.rb.rb_right) {
node = rb_entry(node->shared.rb.rb_right,
struct vm_area_struct, shared.rb);
if (start <= node->shared.rb_subtree_last)
continue;
}
}
return NULL; /* No match */
}
}
static struct vm_area_struct *
aml_vma_iter_first(struct rb_root_cached *root,
unsigned long start, unsigned long last)
{
struct vm_area_struct *node, *leftmost;
if (!root->rb_root.rb_node)
return NULL;
/*
* Fastpath range intersection/overlap between A: [a0, a1] and
* B: [b0, b1] is given by:
*
* a0 <= b1 && b0 <= a1
*
* ... where A holds the lock range and B holds the smallest
* 'start' and largest 'last' in the tree. For the later, we
* rely on the root node, which by augmented interval tree
* property, holds the largest value in its last-in-subtree.
* This allows mitigating some of the tree walk overhead for
* non-intersecting ranges, maintained and consulted in O(1).
*/
node = rb_entry(root->rb_root.rb_node, struct vm_area_struct, shared.rb);
if (node->shared.rb_subtree_last < start)
return NULL;
leftmost = rb_entry(root->rb_leftmost, struct vm_area_struct, shared.rb);
if (vma_start_pgoff(leftmost) > last)
return NULL;
return aml_vma_subtree_search(node, start, last);
}
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
void get_file_cache_hook(void *data, struct lruvec *lruvec)
{
struct page *page, *next;
struct list_head *list;
struct rb_root fct_root = RB_ROOT;
unsigned int t = 0, in = 0, an = 0;
int r, mc, lru = 0, a = 0;
struct file_cache_trace *fct = (struct file_cache_trace *)data;
for_each_lru(lru) {
/* only count for filecache */
if (!is_file_lru(lru) &&
lru != LRU_UNEVICTABLE)
continue;
if (lru == LRU_ACTIVE_FILE)
a = 1;
else
a = 0;
list = &lruvec->lists[lru];
spin_lock_irq(&lruvec->lru_lock);
list_for_each_entry_safe(page, next,
list, lru) {
if (!page_is_file_lru(page))
continue;
t++;
mc = page_mapcount(page);
if (mc <= 0) {
if (a)
an++;
else
in++;
continue;
}
r = record_fct(page, fct, &fs.files,
&fct_root, mc, a);
/* some data may lost */
if (r == -ERANGE) {
spin_unlock_irq(&lruvec->lru_lock);
goto out;
}
if (r) {
if (a)
an++;
else
in++;
}
}
spin_unlock_irq(&lruvec->lru_lock);
}
out: /* update final statistics */
fs.total += t;
fs.nomap[0] += an + in;
fs.nomap[1] += in;
fs.nomap[2] += an;
}
#endif
static int fcmp(const void *x1, const void *x2)
{
struct file_cache_trace *s1, *s2;
s1 = (struct file_cache_trace *)x1;
s2 = (struct file_cache_trace *)x2;
return s2->count - s1->count;
}
static char *parse_fct_name(struct file_cache_trace *fct, char *buf)
{
struct address_space *mapping = fct->mapping;
pgoff_t pgoff;
struct vm_area_struct *vma;
struct file *file;
pgoff = fct->off;
vma = aml_vma_iter_first(&mapping->i_mmap, pgoff, pgoff);
if (!vma) {
pr_err("%s, can't find vma for mapping:%p\n",
__func__, mapping);
return NULL;
}
memset(buf, 0, 256);
file = vma->vm_file;
if (file) {
char *p = d_path(&file->f_path, buf, 256);
if (!IS_ERR(p))
mangle_path(buf, p, "\n");
else
return NULL;
}
if (mapping->flags & (1 << AS_LOCK_MAPPING))
strncat(buf, " [pin]", 255);
return buf;
}
#ifdef arch_idle_time
static u64 get_iowait_time(struct kernel_cpustat *kcs, int cpu)
{
u64 iowait;
iowait = kcs->cpustat[CPUTIME_IOWAIT];
if (cpu_online(cpu) && nr_iowait_cpu(cpu))
iowait += arch_idle_time(cpu);
return iowait;
}
#else
static u64 aml_get_idle_time(struct kernel_cpustat *kcs, int cpu)
{
u64 idle, idle_usecs = -1ULL;
if (cpu_online(cpu))
idle_usecs = get_cpu_idle_time_us(cpu, NULL);
if (idle_usecs == -1ULL)
/* !NO_HZ or cpu offline so we can rely on cpustat.idle */
idle = kcs->cpustat[CPUTIME_IDLE];
else
idle = idle_usecs * NSEC_PER_USEC;
return idle;
}
static u64 get_iowait_time(struct kernel_cpustat *kcs, int cpu)
{
u64 iowait, iowait_usecs = -1ULL;
if (cpu_online(cpu))
iowait_usecs = get_cpu_iowait_time_us(cpu, NULL);
if (iowait_usecs == -1ULL)
/* !NO_HZ or cpu offline so we can rely on cpustat.iowait */
iowait = kcs->cpustat[CPUTIME_IOWAIT];
else
iowait = iowait_usecs * NSEC_PER_USEC;
return iowait;
}
#endif
static u64 get_iow_time(u64 *cpu)
{
u64 user, nice, system, idle, iowait, irq, softirq, steal;
u64 guest, guest_nice;
int i;
user = 0;
nice = 0;
system = 0;
idle = 0;
iowait = 0;
irq = 0;
softirq = 0;
steal = 0;
guest = 0;
guest_nice = 0;
for_each_possible_cpu(i) {
/*
* When runtime, it will reallocate a
* new buffer based on the number of CPUs.
*/
/* coverity[overrun-call:SUPPRESS] */
struct kernel_cpustat *kcs = &kcpustat_cpu(i);
user += kcpustat_cpu(i).cpustat[CPUTIME_USER];
nice += kcpustat_cpu(i).cpustat[CPUTIME_NICE];
system += kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM];
idle += aml_get_idle_time(kcs, i);
iowait += get_iowait_time(kcs, i);
irq += kcpustat_cpu(i).cpustat[CPUTIME_IRQ];
softirq += kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ];
steal += kcpustat_cpu(i).cpustat[CPUTIME_STEAL];
guest += kcpustat_cpu(i).cpustat[CPUTIME_GUEST];
guest_nice += kcpustat_cpu(i).cpustat[CPUTIME_GUEST_NICE];
}
*cpu = user + nice + system + idle + iowait + irq + softirq + steal +
guest + guest_nice;
return iowait;
}
#define K(x) ((unsigned long)(x) << (PAGE_SHIFT - 10))
static int filecache_show(struct seq_file *m, void *arg)
{
int i;
unsigned long tick = 0;
unsigned long long now;
u64 iow = 0, cputime = 0;
char fname[256];
struct file_cache_trace *fct;
unsigned int small_files = 0, small_fcache = 0;
unsigned int small_active = 0, small_inactive = 0;
fct = vzalloc(sizeof(*fct) * MAX_FCT);
if (!fct)
return -ENOMEM;
tick = sched_clock();
/* update_file_cache(&fs, fct); */
memset(&fs, 0, sizeof(struct filecache_stat));
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
register_trace_android_vh_do_traversal_lruvec(get_file_cache_hook, (void *)fct);
do_traversal_all_lruvec();
unregister_trace_android_vh_do_traversal_lruvec(get_file_cache_hook, (void *)fct);
#endif
now = sched_clock();
tick = now - tick;
sort(fct, fs.files, sizeof(struct file_cache_trace), fcmp, NULL);
seq_puts(m, "------------------------------\n");
seq_puts(m, "count(KB), active, inactive, mc, lc, amc, file name\n");
for (i = 0; i < fs.files; i++) {
if (K(fct[i].count) < file_cache_filter) {
small_files++;
small_fcache += fct[i].count;
small_active += fct[i].active_count;
small_inactive += fct[i].inactive_count;
continue;
}
seq_printf(m, " %6lu, %6lu, %6lu, %6u, %4ld, %3u, %s\n",
K(fct[i].count), K(fct[i].active_count),
K(fct[i].inactive_count), fct[i].mapcnt,
K(fct[i].lock_count),
fct[i].mapcnt / fct[i].count,
parse_fct_name(&fct[i], fname));
}
iow = get_iow_time(&cputime);
seq_printf(m, "small files:%u, cache:%lu [%lu/%lu] KB, time:%ld\n",
small_files, K(small_fcache),
K(small_inactive), K(small_active), tick);
seq_printf(m, "total:%lu KB, nomap[I/A]:%lu [%lu/%lu] KB, files:%u\n",
K(fs.total), K(fs.nomap[0]),
K(fs.nomap[1]), K(fs.nomap[2]),
fs.files);
seq_printf(m, "ktime:%12lld, iow:%12lld, cputime:%12lld\n",
now, iow, cputime);
seq_puts(m, "------------------------------\n");
vfree(fct);
return 0;
}
static int filecache_open(struct inode *inode, struct file *file)
{
return single_open(file, filecache_show, NULL);
}
static const struct proc_ops filecache_ops = {
.proc_open = filecache_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int __init filecache_module_init(void)
{
d_filecache = proc_create("filecache", 0444,
NULL, &filecache_ops);
if (IS_ERR_OR_NULL(d_filecache)) {
pr_err("%s, create filecache failed\n", __func__);
return -1;
}
return 0;
}
static void __exit filecache_module_exit(void)
{
if (d_filecache)
proc_remove(d_filecache);
}
module_init(filecache_module_init);
module_exit(filecache_module_exit);
MODULE_LICENSE("GPL v2");