xref: /linux-6.15/kernel/profile.c (revision b88f5538)
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