1457c8996SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * linux/kernel/profile.c
41da177e4SLinus Torvalds * Simple profiling. Manages a direct-mapped profile hit count buffer,
51da177e4SLinus Torvalds * with configurable resolution, support for restricting the cpus on
61da177e4SLinus Torvalds * which profiling is done, and switching between cpu time and
71da177e4SLinus Torvalds * schedule() calls via kernel command line parameters passed at boot.
81da177e4SLinus Torvalds *
91da177e4SLinus Torvalds * Scheduler profiling support, Arjan van de Ven and Ingo Molnar,
101da177e4SLinus Torvalds * Red Hat, July 2004
111da177e4SLinus Torvalds * Consolidation of architecture support code for profiling,
126d49e352SNadia Yvette Chambers * Nadia Yvette Chambers, Oracle, July 2004
131da177e4SLinus Torvalds * Amortized hit count accounting via per-cpu open-addressed hashtables
146d49e352SNadia Yvette Chambers * to resolve timer interrupt livelocks, Nadia Yvette Chambers,
156d49e352SNadia Yvette Chambers * Oracle, 2004
161da177e4SLinus Torvalds */
171da177e4SLinus Torvalds
189984de1aSPaul Gortmaker #include <linux/export.h>
191da177e4SLinus Torvalds #include <linux/profile.h>
2057c8a661SMike Rapoport #include <linux/memblock.h>
211da177e4SLinus Torvalds #include <linux/notifier.h>
221da177e4SLinus Torvalds #include <linux/mm.h>
231da177e4SLinus Torvalds #include <linux/cpumask.h>
241da177e4SLinus Torvalds #include <linux/cpu.h>
251da177e4SLinus Torvalds #include <linux/highmem.h>
2697d1f15bSArjan van de Ven #include <linux/mutex.h>
2722b8ce94SDave Hansen #include <linux/slab.h>
2822b8ce94SDave Hansen #include <linux/vmalloc.h>
293905f9adSIngo Molnar #include <linux/sched/stat.h>
303905f9adSIngo Molnar
311da177e4SLinus Torvalds #include <asm/sections.h>
327d12e780SDavid Howells #include <asm/irq_regs.h>
33e8edc6e0SAlexey Dobriyan #include <asm/ptrace.h>
341da177e4SLinus Torvalds
351da177e4SLinus Torvalds struct profile_hit {
361da177e4SLinus Torvalds u32 pc, hits;
371da177e4SLinus Torvalds };
381da177e4SLinus Torvalds #define PROFILE_GRPSHIFT 3
391da177e4SLinus Torvalds #define PROFILE_GRPSZ (1 << PROFILE_GRPSHIFT)
401da177e4SLinus Torvalds #define NR_PROFILE_HIT (PAGE_SIZE/sizeof(struct profile_hit))
411da177e4SLinus Torvalds #define NR_PROFILE_GRP (NR_PROFILE_HIT/PROFILE_GRPSZ)
421da177e4SLinus Torvalds
431da177e4SLinus Torvalds static atomic_t *prof_buffer;
442d186afdSPavel Skripkin static unsigned long prof_len;
452d186afdSPavel Skripkin static unsigned short int prof_shift;
4607031e14SIngo Molnar
47ece8a684SIngo Molnar int prof_on __read_mostly;
4807031e14SIngo Molnar EXPORT_SYMBOL_GPL(prof_on);
4907031e14SIngo Molnar
profile_setup(char * str)5022b8ce94SDave Hansen int profile_setup(char *str)
511da177e4SLinus Torvalds {
52f3da64d1SFabian Frederick static const char schedstr[] = "schedule";
53f3da64d1SFabian Frederick static const char kvmstr[] = "kvm";
5435783ccbSwuchi const char *select = NULL;
551da177e4SLinus Torvalds int par;
561da177e4SLinus Torvalds
57*b88f5538STetsuo Handa if (!strncmp(str, schedstr, strlen(schedstr))) {
581da177e4SLinus Torvalds prof_on = SCHED_PROFILING;
5935783ccbSwuchi select = schedstr;
6007031e14SIngo Molnar } else if (!strncmp(str, kvmstr, strlen(kvmstr))) {
6107031e14SIngo Molnar prof_on = KVM_PROFILING;
6235783ccbSwuchi select = kvmstr;
63dfaa9c94SWilliam Lee Irwin III } else if (get_option(&str, &par)) {
642d186afdSPavel Skripkin prof_shift = clamp(par, 0, BITS_PER_LONG - 1);
651da177e4SLinus Torvalds prof_on = CPU_PROFILING;
662d186afdSPavel Skripkin pr_info("kernel profiling enabled (shift: %u)\n",
671da177e4SLinus Torvalds prof_shift);
681da177e4SLinus Torvalds }
6935783ccbSwuchi
7035783ccbSwuchi if (select) {
7135783ccbSwuchi if (str[strlen(select)] == ',')
7235783ccbSwuchi str += strlen(select) + 1;
7335783ccbSwuchi if (get_option(&str, &par))
7435783ccbSwuchi prof_shift = clamp(par, 0, BITS_PER_LONG - 1);
7535783ccbSwuchi pr_info("kernel %s profiling enabled (shift: %u)\n",
7635783ccbSwuchi select, prof_shift);
7735783ccbSwuchi }
7835783ccbSwuchi
791da177e4SLinus Torvalds return 1;
801da177e4SLinus Torvalds }
811da177e4SLinus Torvalds __setup("profile=", profile_setup);
821da177e4SLinus Torvalds
831da177e4SLinus Torvalds
profile_init(void)84ce05fcc3SPaul Mundt int __ref profile_init(void)
851da177e4SLinus Torvalds {
8622b8ce94SDave Hansen int buffer_bytes;
871da177e4SLinus Torvalds if (!prof_on)
8822b8ce94SDave Hansen return 0;
891da177e4SLinus Torvalds
901da177e4SLinus Torvalds /* only text is profiled */
911da177e4SLinus Torvalds prof_len = (_etext - _stext) >> prof_shift;
920fe6ee8fSChen Zhongjin
930fe6ee8fSChen Zhongjin if (!prof_len) {
940fe6ee8fSChen Zhongjin pr_warn("profiling shift: %u too large\n", prof_shift);
950fe6ee8fSChen Zhongjin prof_on = 0;
960fe6ee8fSChen Zhongjin return -EINVAL;
970fe6ee8fSChen Zhongjin }
980fe6ee8fSChen Zhongjin
9922b8ce94SDave Hansen buffer_bytes = prof_len*sizeof(atomic_t);
10022b8ce94SDave Hansen
101b62f495dSMel Gorman prof_buffer = kzalloc(buffer_bytes, GFP_KERNEL|__GFP_NOWARN);
10222b8ce94SDave Hansen if (prof_buffer)
10322b8ce94SDave Hansen return 0;
10422b8ce94SDave Hansen
105b62f495dSMel Gorman prof_buffer = alloc_pages_exact(buffer_bytes,
106b62f495dSMel Gorman GFP_KERNEL|__GFP_ZERO|__GFP_NOWARN);
10722b8ce94SDave Hansen if (prof_buffer)
10822b8ce94SDave Hansen return 0;
10922b8ce94SDave Hansen
110559fa6e7SJesper Juhl prof_buffer = vzalloc(buffer_bytes);
111559fa6e7SJesper Juhl if (prof_buffer)
11222b8ce94SDave Hansen return 0;
11322b8ce94SDave Hansen
11422b8ce94SDave Hansen return -ENOMEM;
1151da177e4SLinus Torvalds }
1161da177e4SLinus Torvalds
do_profile_hits(int type,void * __pc,unsigned int nr_hits)1176f7bd76fSRakib Mullick static void do_profile_hits(int type, void *__pc, unsigned int nr_hits)
1181da177e4SLinus Torvalds {
1191da177e4SLinus Torvalds unsigned long pc;
1201da177e4SLinus Torvalds pc = ((unsigned long)__pc - (unsigned long)_stext) >> prof_shift;
1212accfdb7SLinus Torvalds if (pc < prof_len)
1222accfdb7SLinus Torvalds atomic_add(nr_hits, &prof_buffer[pc]);
1231da177e4SLinus Torvalds }
1246f7bd76fSRakib Mullick
profile_hits(int type,void * __pc,unsigned int nr_hits)1256f7bd76fSRakib Mullick void profile_hits(int type, void *__pc, unsigned int nr_hits)
1266f7bd76fSRakib Mullick {
1276f7bd76fSRakib Mullick if (prof_on != type || !prof_buffer)
1286f7bd76fSRakib Mullick return;
1296f7bd76fSRakib Mullick do_profile_hits(type, __pc, nr_hits);
1306f7bd76fSRakib Mullick }
131bbe1a59bSAndrew Morton EXPORT_SYMBOL_GPL(profile_hits);
132bbe1a59bSAndrew Morton
profile_tick(int type)1337d12e780SDavid Howells void profile_tick(int type)
1341da177e4SLinus Torvalds {
1357d12e780SDavid Howells struct pt_regs *regs = get_irq_regs();
1367d12e780SDavid Howells
1377c51f7bbSTetsuo Handa /* This is the old kernel-only legacy profiling */
1387c51f7bbSTetsuo Handa if (!user_mode(regs))
1391da177e4SLinus Torvalds profile_hit(type, (void *)profile_pc(regs));
1401da177e4SLinus Torvalds }
1411da177e4SLinus Torvalds
1421da177e4SLinus Torvalds #ifdef CONFIG_PROC_FS
1431da177e4SLinus Torvalds #include <linux/proc_fs.h>
144583a22e7SAlexey Dobriyan #include <linux/seq_file.h>
1457c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
1461da177e4SLinus Torvalds
1471da177e4SLinus Torvalds /*
1481da177e4SLinus Torvalds * This function accesses profiling information. The returned data is
1491da177e4SLinus Torvalds * binary: the sampling step and the actual contents of the profile
1501da177e4SLinus Torvalds * buffer. Use of the program readprofile is recommended in order to
1511da177e4SLinus Torvalds * get meaningful info out of these data.
1521da177e4SLinus Torvalds */
1531da177e4SLinus Torvalds static ssize_t
read_profile(struct file * file,char __user * buf,size_t count,loff_t * ppos)1541da177e4SLinus Torvalds read_profile(struct file *file, char __user *buf, size_t count, loff_t *ppos)
1551da177e4SLinus Torvalds {
1561da177e4SLinus Torvalds unsigned long p = *ppos;
1571da177e4SLinus Torvalds ssize_t read;
1581da177e4SLinus Torvalds char *pnt;
1592d186afdSPavel Skripkin unsigned long sample_step = 1UL << prof_shift;
1601da177e4SLinus Torvalds
1611da177e4SLinus Torvalds if (p >= (prof_len+1)*sizeof(unsigned int))
1621da177e4SLinus Torvalds return 0;
1631da177e4SLinus Torvalds if (count > (prof_len+1)*sizeof(unsigned int) - p)
1641da177e4SLinus Torvalds count = (prof_len+1)*sizeof(unsigned int) - p;
1651da177e4SLinus Torvalds read = 0;
1661da177e4SLinus Torvalds
1671da177e4SLinus Torvalds while (p < sizeof(unsigned int) && count > 0) {
168064b022cSHeiko Carstens if (put_user(*((char *)(&sample_step)+p), buf))
169064b022cSHeiko Carstens return -EFAULT;
1701da177e4SLinus Torvalds buf++; p++; count--; read++;
1711da177e4SLinus Torvalds }
1721da177e4SLinus Torvalds pnt = (char *)prof_buffer + p - sizeof(atomic_t);
1731da177e4SLinus Torvalds if (copy_to_user(buf, (void *)pnt, count))
1741da177e4SLinus Torvalds return -EFAULT;
1751da177e4SLinus Torvalds read += count;
1761da177e4SLinus Torvalds *ppos += read;
1771da177e4SLinus Torvalds return read;
1781da177e4SLinus Torvalds }
1791da177e4SLinus Torvalds
180787dbea1SBen Dooks /* default is to not implement this call */
setup_profiling_timer(unsigned mult)181787dbea1SBen Dooks int __weak setup_profiling_timer(unsigned mult)
182787dbea1SBen Dooks {
183787dbea1SBen Dooks return -EINVAL;
184787dbea1SBen Dooks }
185787dbea1SBen Dooks
1861da177e4SLinus Torvalds /*
1871da177e4SLinus Torvalds * Writing to /proc/profile resets the counters
1881da177e4SLinus Torvalds *
1891da177e4SLinus Torvalds * Writing a 'profiling multiplier' value into it also re-sets the profiling
1901da177e4SLinus Torvalds * interrupt frequency, on architectures that support this.
1911da177e4SLinus Torvalds */
write_profile(struct file * file,const char __user * buf,size_t count,loff_t * ppos)1921da177e4SLinus Torvalds static ssize_t write_profile(struct file *file, const char __user *buf,
1931da177e4SLinus Torvalds size_t count, loff_t *ppos)
1941da177e4SLinus Torvalds {
1951da177e4SLinus Torvalds #ifdef CONFIG_SMP
1961da177e4SLinus Torvalds if (count == sizeof(int)) {
1971da177e4SLinus Torvalds unsigned int multiplier;
1981da177e4SLinus Torvalds
1991da177e4SLinus Torvalds if (copy_from_user(&multiplier, buf, sizeof(int)))
2001da177e4SLinus Torvalds return -EFAULT;
2011da177e4SLinus Torvalds
2021da177e4SLinus Torvalds if (setup_profiling_timer(multiplier))
2031da177e4SLinus Torvalds return -EINVAL;
2041da177e4SLinus Torvalds }
2051da177e4SLinus Torvalds #endif
2061da177e4SLinus Torvalds memset(prof_buffer, 0, prof_len * sizeof(atomic_t));
2071da177e4SLinus Torvalds return count;
2081da177e4SLinus Torvalds }
2091da177e4SLinus Torvalds
21097a32539SAlexey Dobriyan static const struct proc_ops profile_proc_ops = {
21197a32539SAlexey Dobriyan .proc_read = read_profile,
21297a32539SAlexey Dobriyan .proc_write = write_profile,
21397a32539SAlexey Dobriyan .proc_lseek = default_llseek,
2141da177e4SLinus Torvalds };
2151da177e4SLinus Torvalds
create_proc_profile(void)216e722d8daSSebastian Andrzej Siewior int __ref create_proc_profile(void)
2171da177e4SLinus Torvalds {
2181da177e4SLinus Torvalds struct proc_dir_entry *entry;
219c270a817SSrivatsa S. Bhat int err = 0;
2201da177e4SLinus Torvalds
2211da177e4SLinus Torvalds if (!prof_on)
2221da177e4SLinus Torvalds return 0;
223c33fff0aSDenis V. Lunev entry = proc_create("profile", S_IWUSR | S_IRUGO,
22497a32539SAlexey Dobriyan NULL, &profile_proc_ops);
2257c51f7bbSTetsuo Handa if (entry)
226271a15eaSDavid Howells proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t));
227c270a817SSrivatsa S. Bhat return err;
2281da177e4SLinus Torvalds }
229c96d6660SPaul Gortmaker subsys_initcall(create_proc_profile);
2301da177e4SLinus Torvalds #endif /* CONFIG_PROC_FS */
231