// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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");