Skip to contentSkip to conversation

Accurate CPU utilization patch

PATCH New Corey Minyard 1 participant 1 message May 23, 2002

Subsystems (8)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Accurate CPU utilization patch

The current system of measuring CPU used by a process/the system is crude (at best). Basically, if you get a timer tick when your application is running, then you get a whole timer tick's work of CPU added to your process's usage. That's actually just fine for most applications, but some applications need tight bounds and more accurate accounting. This patch addresses this concern.

This patch adds a configuration option that causes the kernel to measure the CPU used by a process at microsecond resolution. This affects general CPU usage measurement, rusage, and virtual itimers. The patch is against 2.4.17. It touches some pretty fundamental data structures and ties in to all the exception (including interrupt and system call) handlers, so it's pretty invasive. It shouldn't slow things down when the patch is turned off, but it will slow down all exceptions when it is turned on. The patch currently works on PowerPC and x86 (although it won't work on earlier x86 processors due to lack of a high-resolution clock source). It has seen extensive testing (around 9 months of heavy usage) in the lab on an older kernel.

I'm curious if this patch is interesting enough to work to include it in the mainstream kernel release. If so, I'll gladly work to get it in to shape to go into 2.5 or whatever. (I'll also gladly take comments on how to improve it :-).

-Corey

fs/proc/proc_misc.c renamed from fs/proc/proc_misc.c.highres +2 -1
@@ -101,7 +101,8 @@
 	int len;
 
 	uptime = jiffies;
-	idle = init_tasks[0]->times.tms_utime + init_tasks[0]->times.tms_stime;
+	idle = (tms_time_to_ticks(init_tasks[0]->times.tms_utime)
+		+ tms_time_to_ticks(init_tasks[0]->times.tms_stime));
 
 	/* The formula for the fraction parts really is ((t * 100) / HZ) % 100, but
 	   that would overflow about every five days at HZ == 100.
fs/proc/array.c renamed from fs/proc/array.c.highres +6 -6
@@ -358,10 +358,10 @@
 		task->cmin_flt,
 		task->maj_flt,
 		task->cmaj_flt,
-		task->times.tms_utime,
-		task->times.tms_stime,
-		task->times.tms_cutime,
-		task->times.tms_cstime,
+		tms_time_to_ticks(task->times.tms_utime),
+		tms_time_to_ticks(task->times.tms_stime),
+		tms_time_to_ticks(task->times.tms_cutime),
+		tms_time_to_ticks(task->times.tms_cstime),
 		priority,
 		nice,
 		0UL /* removed */,
@@ -682,8 +682,8 @@
 
 	len = sprintf(buffer,
 		"cpu  %lu %lu\n",
-		task->times.tms_utime,
-		task->times.tms_stime);
+		tms_time_to_ticks(task->times.tms_utime),
+		tms_time_to_ticks(task->times.tms_stime));
 		
 	for (i = 0 ; i < smp_num_cpus; i++)
 		len += sprintf(buffer + len, "cpu%d %lu %lu\n",
fs/binfmt_elf.c renamed from fs/binfmt_elf.c.highres +4 -8
@@ -1105,14 +1105,10 @@
 	psinfo.pr_ppid = prstatus.pr_ppid = current->p_pptr->pid;
 	psinfo.pr_pgrp = prstatus.pr_pgrp = current->pgrp;
 	psinfo.pr_sid = prstatus.pr_sid = current->session;
-	prstatus.pr_utime.tv_sec = CT_TO_SECS(current->times.tms_utime);
-	prstatus.pr_utime.tv_usec = CT_TO_USECS(current->times.tms_utime);
-	prstatus.pr_stime.tv_sec = CT_TO_SECS(current->times.tms_stime);
-	prstatus.pr_stime.tv_usec = CT_TO_USECS(current->times.tms_stime);
-	prstatus.pr_cutime.tv_sec = CT_TO_SECS(current->times.tms_cutime);
-	prstatus.pr_cutime.tv_usec = CT_TO_USECS(current->times.tms_cutime);
-	prstatus.pr_cstime.tv_sec = CT_TO_SECS(current->times.tms_cstime);
-	prstatus.pr_cstime.tv_usec = CT_TO_USECS(current->times.tms_cstime);
+	prstatus.pr_utime = tms_time_to_timeval(current->times.tms_utime);
+	prstatus.pr_stime = tms_time_to_timeval(current->times.tms_stime);
+	prstatus.pr_cutime = tms_time_to_timeval(current->times.tms_cutime);
+	prstatus.pr_cstime = tms_time_to_timeval(current->times.tms_cstime);
 
 	/*
 	 * This transfers the registers from regs into the standard
mm/oom_kill.c renamed from mm/oom_kill.c.highres +3 -1
@@ -72,7 +72,9 @@
 	 * very well in practice. This is not safe against jiffie wraps
 	 * but we don't care _that_ much...
 	 */
-	cpu_time = (p->times.tms_utime + p->times.tms_stime) >> (SHIFT_HZ + 3);
+	cpu_time = (tms_time_to_ticks(p->times.tms_utime)
+		    + tms_time_to_ticks(p->times.tms_stime))
+			>> (SHIFT_HZ + 3);
 	run_time = (jiffies - p->start_time) >> (SHIFT_HZ + 10);
 
 	points /= int_sqrt(cpu_time);
arch/ppc/config.in renamed from arch/ppc/config.in.highres +5 -0
@@ -237,6 +237,11 @@
   source drivers/zorro/Config.in
 fi
 
+bool 'High resolution CPU usage and virtual itimers' CONFIG_HIGHRES_CPU_USAGE
+if [ "$CONFIG_HIGHRES_CPU_USAGE" = "y" ]; then
+  bool 'Does the virtual itimer count system CPU usage?' CONFIG_VIRT_ITIMER_COUNTS_SYSTEM
+fi
+
 endmenu
 source drivers/mtd/Config.in
 source drivers/pnp/Config.in
arch/ppc/kernel/entry.S renamed from arch/ppc/kernel/entry.S.highres +4 -0
@@ -298,6 +298,10 @@
 ret_to_user_hook:
 	nop
 restore:
+#ifdef CONFIG_HIGHRES_CPU_USAGE
+	mr	r3,r2
+	bl	tms_leave_system
+#endif
 	lwz	r3,_XER(r1)
 	mtspr	XER,r3
 	REST_10GPRS(9,r1)
arch/ppc/kernel/time.c renamed from arch/ppc/kernel/time.c.highres +29 -0
@@ -243,6 +243,35 @@
 	tv->tv_usec = usec;
 }
 
+#ifdef CONFIG_HIGHRES_CPU_USAGE
+unsigned long curr_timestamp(void)
+{
+        return get_native_tbl();
+}
+
+unsigned long timestamp_usec_diff(unsigned long ts1, unsigned long ts2)
+{
+        unsigned long val;
+	unsigned long usec;
+
+	if (__USE_RTC()) {
+	        int delta = ts1 - ts2;
+		val = delta<0 ? delta + 1000000000 : delta;
+	} else {
+        	val = ts1 - ts2;
+	}
+	usec = mulhwu(tb_to_us, val);
+	if (usec > 1000000) {
+		/* We had some wierd overflow due to the timebases being
+		   changed, just ignore it. */
+		printk("tb went backwards? ts1=%lu, ts2=%ld\n", ts1, ts2);
+		usec = 0;
+	}
+
+	return usec;
+}
+#endif
+
 void do_settimeofday(struct timeval *tv)
 {
 	unsigned long flags;
arch/ppc/kernel/head.S renamed from arch/ppc/kernel/head.S.highres +45 -0

62 lines not on this page · raw message

arch/i386/config.in renamed from arch/i386/config.in.highres +3 -0
@@ -279,6 +279,9 @@
    bool '    Use real mode APM BIOS call to power off' CONFIG_APM_REAL_MODE_POWER_OFF
 fi
 
+dep_bool 'High resolution CPU usage and virtual itimers' CONFIG_HIGHRES_CPU_USAGE $CONFIG_X86_TSC
+dep_bool 'Does the virtual itimer count system CPU usage?' CONFIG_VIRT_ITIMER_COUNTS_SYSTEM $CONFIG_HIGHRES_CPU_USAGE
+
 endmenu
 
 source drivers/mtd/Config.in
arch/i386/kernel/entry.S renamed from arch/i386/kernel/entry.S.highres +27 -5
@@ -81,6 +81,27 @@
 
 ENOSYS = 38
 
+#define GET_CURRENT(reg) \
+	movl $-8192, reg; \
+	andl %esp, reg
+
+#ifdef CONFIG_HIGHRES_CPU_USAGE
+#define HIGHRES_START_CODE \
+	pushl %eax;        \
+	GET_CURRENT(%ebx); \
+	pushl %ebx;        \
+	call  SYMBOL_NAME(tms_enter_system); \
+	addl  $4,%esp;     \
+	popl %eax; 
+#define HIGHRES_END_CODE \
+	GET_CURRENT(%ebx); \
+	pushl %ebx;        \
+	call  SYMBOL_NAME(tms_leave_system); \
+	addl  $4,%esp; 
+#else
+#define HIGHRES_START_CODE
+#define HIGHRES_END_CODE
+#endif
 
 #define SAVE_ALL \
 	cld; \
@@ -95,9 +116,11 @@
 	pushl %ebx; \
 	movl $(__KERNEL_DS),%edx; \
 	movl %edx,%ds; \
-	movl %edx,%es;
+	movl %edx,%es; \
+	HIGHRES_START_CODE;
 
 #define RESTORE_ALL	\
+	HIGHRES_END_CODE; \
 	popl %ebx;	\
 	popl %ecx;	\
 	popl %edx;	\
@@ -128,10 +151,6 @@
 	.long 3b,6b;	\
 .previous
 
-#define GET_CURRENT(reg) \
-	movl $-8192, reg; \
-	andl %esp, reg
-
 ENTRY(lcall7)
 	pushfl			# We get a different stack layout with call gates,
 	pushl %eax		# which has to be cleaned up later..
@@ -286,6 +305,9 @@
 	movl $(__KERNEL_DS),%edx
 	movl %edx,%ds
 	movl %edx,%es
+	pushl %edi
+	HIGHRES_START_CODE
+	popl %edi
 	GET_CURRENT(%ebx)
 	call *%edi
 	addl $8,%esp
arch/i386/kernel/time.c renamed from arch/i386/kernel/time.c.highres +31 -0
@@ -715,3 +715,34 @@
 	setup_irq(0, &irq0);
 #endif
 }
+
+#ifdef CONFIG_HIGHRES_CPU_USAGE
+unsigned long curr_timestamp(void)
+{
+	register unsigned long eax, edx;
+	rdtsc(eax,edx);
+	return eax;
+}
+
+unsigned long timestamp_usec_diff(unsigned long ts1, unsigned long ts2)
+{
+	register unsigned long eax, rv;
+
+	/* Read the Time Stamp Counter */
+
+	eax = ts1 - ts2;
+	__asm__("mull %2"
+		:"=a" (eax), "=d" (rv)
+		:"rm" (fast_gettimeoffset_quotient),
+		 "0" (eax));
+
+	if (rv >= 1000000) {
+		/* We had some wierd overflow due to the timebases being
+		   changed, just ignore it. */
+		printk("tb went backwards? ts1=%lu, ts2=%ld\n", ts1, ts2);
+		rv = 0;
+	}
+
+	return rv;
+}
+#endif
kernel/acct.c renamed from kernel/acct.c.highres +2 -2
@@ -296,8 +296,8 @@
 
 	ac.ac_btime = CT_TO_SECS(current->start_time) + (xtime.tv_sec - (jiffies / HZ));
 	ac.ac_etime = encode_comp_t(jiffies - current->start_time);
-	ac.ac_utime = encode_comp_t(current->times.tms_utime);
-	ac.ac_stime = encode_comp_t(current->times.tms_stime);
+	ac.ac_utime = encode_comp_t(tms_time_to_ticks(current->times.tms_utime));
+	ac.ac_stime = encode_comp_t(tms_time_to_ticks(current->times.tms_stime));
 	ac.ac_uid = current->uid;
 	ac.ac_gid = current->gid;
 	ac.ac_tty = (current->tty) ? kdev_t_to_nr(current->tty->device) : 0;
kernel/timer.c renamed from kernel/timer.c.highres +144 -0

164 lines not on this page · raw message

kernel/fork.c renamed from kernel/fork.c.highres +14 -4
@@ -626,15 +626,25 @@
 	p->sigpending = 0;
 	init_sigpending(&p->pending);
 
-	p->it_real_value = p->it_virt_value = p->it_prof_value = 0;
-	p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;
+	p->it_real_value = p->it_prof_value = 0;
+	p->it_real_incr = p->it_prof_incr = 0;
+#ifndef CONFIG_HIGHRES_CPU_USAGE
+	p->it_virt_value = p->it_virt_incr = 0;
+#else
+	p->it_virt_value.tv_sec = p->it_virt_value.tv_usec = 0;
+	p->it_virt_incr.tv_sec = p->it_virt_incr.tv_usec = 0;
+	/* We start out in system code. */
+	p->system_code_nest = 1;
+#endif
 	init_timer(&p->real_timer);
 	p->real_timer.data = (unsigned long) p;
 
 	p->leader = 0;		/* session leadership doesn't inherit */
 	p->tty_old_pgrp = 0;
-	p->times.tms_utime = p->times.tms_stime = 0;
-	p->times.tms_cutime = p->times.tms_cstime = 0;
+	init_tms_time(&(p->times.tms_utime));
+	init_tms_time(&(p->times.tms_stime));
+	init_tms_time(&(p->times.tms_cutime));
+	init_tms_time(&(p->times.tms_cstime));
 #ifdef CONFIG_SMP
 	{
 		int i;
kernel/sys.c renamed from kernel/sys.c.highres +29 -14

76 lines not on this page · raw message

kernel/itimer.c renamed from kernel/itimer.c.highres +35 -4
@@ -58,20 +58,35 @@
 			if ((long) val <= 0)
 				val = 1;
 		}
+		jiffiestotv(val, &value->it_value);
+		jiffiestotv(interval, &value->it_interval);
 		break;
 	case ITIMER_VIRTUAL:
+#ifndef CONFIG_HIGHRES_CPU_USAGE
 		val = current->it_virt_value;
 		interval = current->it_virt_incr;
+		jiffiestotv(val, &value->it_value);
+		jiffiestotv(interval, &value->it_interval);
+#else
+		{
+			unsigned long flags;
+
+			read_lock_irqsave(&virt_itimer_lock, flags);
+			value->it_value = current->it_virt_value;
+			value->it_interval = current->it_virt_incr;
+			read_unlock_irqrestore(&virt_itimer_lock, flags);
+		}
+#endif
 		break;
 	case ITIMER_PROF:
 		val = current->it_prof_value;
 		interval = current->it_prof_incr;
+		jiffiestotv(val, &value->it_value);
+		jiffiestotv(interval, &value->it_interval);
 		break;
 	default:
 		return(-EINVAL);
 	}
-	jiffiestotv(val, &value->it_value);
-	jiffiestotv(interval, &value->it_interval);
 	return 0;
 }
 
@@ -110,12 +125,12 @@
 	register unsigned long i, j;
 	int k;
 
-	i = tvtojiffies(&value->it_interval);
-	j = tvtojiffies(&value->it_value);
 	if (ovalue && (k = do_getitimer(which, ovalue)) < 0)
 		return k;
 	switch (which) {
 		case ITIMER_REAL:
+			i = tvtojiffies(&value->it_interval);
+			j = tvtojiffies(&value->it_value);
 			del_timer_sync(&current->real_timer);
 			current->it_real_value = j;
 			current->it_real_incr = i;
@@ -128,12 +143,28 @@
 			add_timer(&current->real_timer);
 			break;
 		case ITIMER_VIRTUAL:
+#ifndef CONFIG_HIGHRES_CPU_USAGE
+			i = tvtojiffies(&value->it_interval);
+			j = tvtojiffies(&value->it_value);
 			if (j)
 				j++;
 			current->it_virt_value = j;
 			current->it_virt_incr = i;
+#else
+			{
+				unsigned long flags;
+
+				write_lock_irqsave(&virt_itimer_lock, flags);
+				current->it_virt_value = value->it_value;
+				current->it_virt_incr = value->it_interval;
+				write_unlock_irqrestore(&virt_itimer_lock,
+							flags);
+			}
+#endif
 			break;
 		case ITIMER_PROF:
+			i = tvtojiffies(&value->it_interval);
+			j = tvtojiffies(&value->it_value);
 			if (j)
 				j++;
 			current->it_prof_value = j;
kernel/exit.c renamed from kernel/exit.c.highres +13 -2
@@ -535,8 +535,19 @@
 				}
 				goto end_wait4;
 			case TASK_ZOMBIE:
-				current->times.tms_cutime += p->times.tms_utime + p->times.tms_cutime;
-				current->times.tms_cstime += p->times.tms_stime + p->times.tms_cstime;
+				current->times.tms_cutime
+				    = add_tms_times(current->times.tms_utime,
+						    p->times.tms_utime);
+				current->times.tms_cutime
+				    = add_tms_times(current->times.tms_cutime,
+						    p->times.tms_cutime);
+				current->times.tms_cstime
+				    = add_tms_times(current->times.tms_stime,
+						    p->times.tms_stime);
+				current->times.tms_cstime
+				    = add_tms_times(current->times.tms_cstime,
+						    p->times.tms_cstime);
+
 				read_unlock(&tasklist_lock);
 				retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
 				if (!retval && stat_addr)
kernel/signal.c renamed from kernel/signal.c.highres +2 -2
@@ -742,8 +742,8 @@
 	info.si_uid = tsk->uid;
 
 	/* FIXME: find out whether or not this is supposed to be c*time. */
-	info.si_utime = tsk->times.tms_utime;
-	info.si_stime = tsk->times.tms_stime;
+	info.si_utime = tms_time_to_ticks(tsk->times.tms_utime);
+	info.si_stime = tms_time_to_ticks(tsk->times.tms_stime);
 
 	status = tsk->exit_code & 0x7f;
 	why = SI_KERNEL;	/* shouldn't happen */
kernel/sched.c renamed from kernel/sched.c.highres +2 -0
@@ -694,6 +694,8 @@
 	 * This just switches the register state and the
 	 * stack.
 	 */
+	tms_task_stopped_running(prev);
+	tms_task_started_running(next);
 	switch_to(prev, next, prev);
 	__schedule_tail(prev);
 
Documentation/Configure.help renamed from Documentation/Configure.help.highres +13 -0
@@ -19794,6 +19794,19 @@
 
   "Area6" will work for most boards. For ADX, select "Area5".
 
+High resolution CPU usage and virtual itimers
+CONFIG_HIGHRES_CPU_USAGE
+  If you say Y here, the cpu usage tracked for programs and virtual itimers
+  will be much higher precision.  The downside of this is that the system
+  call overhead will go up some (every entry into and out of the kernel must
+  be tracked).  Also, the architecture must support high-res timers of
+  some kind and the support must be added to the architecture code.
+
+Does the virtual itimer count system CPU usage?
+CONFIG_VIRT_ITIMER_COUNTS_SYSTEM
+  If highres CPU is counted, this sets whether virtual itimers count system
+  CPU used towards the amount of CPU until the virtual timer goes off.
+
 #
 # m68k-specific kernel options
 # Documented by Chris Lawrence <mailto:[email protected]> et al.
include/linux/sched.h renamed from include/linux/sched.h.highres +140 -3

168 lines not on this page · raw message

include/asm-i386/hw_irq.h renamed from include/asm-i386/hw_irq.h.highres +18 -5
@@ -95,6 +95,22 @@
 #define __STR(x) #x
 #define STR(x) __STR(x)
 
+#define GET_CURRENT \
+	"movl %esp, %ebx\n\t" \
+	"andl $-8192, %ebx\n\t"
+
+#ifdef CONFIG_HIGHRES_CPU_USAGE
+#define HIGHRES_START_CODE \
+	"pushl %eax\n\t"		          \
+	GET_CURRENT				  \
+	"pushl %ebx\n\t"			  \
+	"call  " SYMBOL_NAME_STR(tms_enter_system) "\n\t" \
+	"addl  $4,%esp\n\t"			  \
+	"popl %eax\n\t" 
+#else
+#define HIGHRES_START_CODE
+#endif
+
 #define SAVE_ALL \
 	"cld\n\t" \
 	"pushl %es\n\t" \
@@ -108,14 +124,11 @@
 	"pushl %ebx\n\t" \
 	"movl $" STR(__KERNEL_DS) ",%edx\n\t" \
 	"movl %edx,%ds\n\t" \
-	"movl %edx,%es\n\t"
+	"movl %edx,%es\n\t" \
+        HIGHRES_START_CODE
 
 #define IRQ_NAME2(nr) nr##_interrupt(void)
 #define IRQ_NAME(nr) IRQ_NAME2(IRQ##nr)
-
-#define GET_CURRENT \
-	"movl %esp, %ebx\n\t" \
-	"andl $-8192, %ebx\n\t"
 
 /*
  *	SMP has a few special interrupts for IPI messages