Time accounting based on TSC

- as thre are still KERNEL and IDLE entries, time accounting for
  kernel and idle time works the same as for any other process

- everytime we stop accounting for the currently running process,
  kernel or idle, we read the TSC counter and increment the p_cycles
  entry.

- the process cycles inherently include some of the kernel cycles as
  we can stop accounting for the process only after we save its
  context and we start accounting just before we restore its context

- this assumes that the system does not scale the CPU frequency which
  will be true for ... long time ;-)
This commit is contained in:
Tomas Hruby 2010-02-10 15:36:54 +00:00
parent f08f2bd88c
commit 1b56fdb33c
14 changed files with 94 additions and 52 deletions

View file

@ -36,12 +36,16 @@
TEST_INT_IN_KERNEL(4, 0f) ;\
\
SAVE_PROCESS_CTX(0) ;\
push %ebp ;\
call cycles_accounting_stop ;\
add $4, %esp ;\
movl $0, %ebp /* for stack trace */ ;\
APIC_IRQ_HANDLER(irq) ;\
jmp restart ;\
\
0: \
pusha ;\
call cycles_accounting_stop_idle ;\
APIC_IRQ_HANDLER(irq) ;\
popa ;\
iret ;
@ -142,12 +146,16 @@ apic_hwint15:
TEST_INT_IN_KERNEL(4, 0f) ;\
\
SAVE_PROCESS_CTX(0) ;\
push %ebp ;\
call cycles_accounting_stop ;\
add $4, %esp ;\
movl $0, %ebp /* for stack trace */ ;\
LAPIC_INTR_HANDLER(func) ;\
jmp restart ;\
\
0: \
pusha ;\
call cycles_accounting_stop_idle ;\
LAPIC_INTR_HANDLER(func) ;\
popa ;\
iret ;

View file

@ -7,6 +7,9 @@
#include "../../kernel.h"
#include "../../clock.h"
#include "../../proc.h"
#include <minix/u64.h>
#ifdef CONFIG_APIC
#include "apic.h"
@ -22,6 +25,9 @@
#define TIMER_FREQ 1193182 /* clock frequency for timer in PC and AT */
#define TIMER_COUNT(freq) (TIMER_FREQ/(freq)) /* initial value for counter*/
/* FIXME make it cpu local! */
PRIVATE u64_t tsc_ctr_switch; /* when did we switched time accounting */
PRIVATE irq_hook_t pic_timer_hook; /* interrupt handler hook */
/*===========================================================================*
@ -119,3 +125,22 @@ PUBLIC int arch_register_local_timer_handler(irq_handler_t handler)
return 0;
}
PUBLIC void cycles_accounting_init(void)
{
read_tsc_64(&tsc_ctr_switch);
}
PUBLIC void cycles_accounting_stop(struct proc * p)
{
u64_t tsc;
read_tsc_64(&tsc);
p->p_cycles = add64(p->p_cycles, sub64(tsc, tsc_ctr_switch));
tsc_ctr_switch = tsc;
}
PUBLIC void cycles_accounting_stop_idle(void)
{
cycles_accounting_stop(proc_addr(IDLE));
}

View file

@ -253,6 +253,9 @@ csinit:
TEST_INT_IN_KERNEL(4, 0f) ;\
\
SAVE_PROCESS_CTX(0) ;\
push %ebp ;\
call cycles_accounting_stop ;\
add $4, %esp ;\
movl $0, %ebp /* for stack trace */ ;\
PIC_IRQ_HANDLER(irq) ;\
movb $END_OF_INT, %al ;\
@ -261,6 +264,7 @@ csinit:
\
0: \
pusha ;\
call cycles_accounting_stop_idle ;\
PIC_IRQ_HANDLER(irq) ;\
movb $END_OF_INT, %al ;\
outb $INT_CTL /* reenable interrupts in master pic */ ;\
@ -316,6 +320,9 @@ hwint07:
TEST_INT_IN_KERNEL(4, 0f) ;\
\
SAVE_PROCESS_CTX(0) ;\
push %ebp ;\
call cycles_accounting_stop ;\
add $4, %esp ;\
movl $0, %ebp /* for stack trace */ ;\
PIC_IRQ_HANDLER(irq) ;\
movb $END_OF_INT, %al ;\
@ -325,6 +332,7 @@ hwint07:
\
0: \
pusha ;\
call cycles_accounting_stop_idle ;\
PIC_IRQ_HANDLER(irq) ;\
movb $END_OF_INT, %al ;\
outb $INT_CTL /* reenable interrupts in master pic */ ;\
@ -395,6 +403,11 @@ ipc_entry:
push %eax
push %ecx
/* stop user process cycles */
push %ebp
call cycles_accounting_stop
add $4, %esp
/* for stack trace */
movl $0, %ebp
@ -420,9 +433,6 @@ kernel_call_entry:
/* save the pointer to the current process */
push %ebp
/* for stack trace */
movl $0, %ebp
/*
* pass the syscall arguments from userspace to the handler.
* SAVE_PROCESS_CTX() does not clobber these registers, they are still
@ -430,6 +440,14 @@ kernel_call_entry:
*/
push %eax
/* stop user process cycles */
push %ebp
call cycles_accounting_stop
add $4, %esp
/* for stack trace */
movl $0, %ebp
call kernel_call
/* restore the current process pointer and save the return value */
@ -458,6 +476,11 @@ exception_entry_from_user:
SAVE_PROCESS_CTX(8)
/* stop user process cycles */
push %ebp
call cycles_accounting_stop
add $4, %esp
/* for stack trace clear %ebp */
movl $0, %ebp
@ -604,6 +627,10 @@ copr_not_available:
jnz 0f /* jump if FPU is already initialized */
orw $MF_FPU_INITIALIZED, (%ebx)
fninit
/* stop user process cycles */
push %ebp
call cycles_accounting_stop
add $4, %esp
jmp copr_return
0: /* load FPU context for current process */
mov %ss:FP_SAVE_AREA_P(%ebp), %eax

View file

@ -325,10 +325,10 @@ PRIVATE void printslot(struct proc *pp, int level)
COL
kprintf("%d: %s %d prio %d/%d time %d/%d cr3 0x%lx rts %s misc %s",
kprintf("%d: %s %d prio %d/%d time %d/%d cycles 0x%x%08x cr3 0x%lx rts %s misc %s",
proc_nr(pp), pp->p_name, pp->p_endpoint,
pp->p_priority, pp->p_max_priority, pp->p_user_time,
pp->p_sys_time, pp->p_seg.p_cr3,
pp->p_sys_time, pp->p_cycles.hi, pp->p_cycles.lo, pp->p_seg.p_cr3,
rtsflagstr(pp->p_rts_flags), miscflagstr(pp->p_misc_flags));
if(pp->p_rts_flags & RTS_SENDING) {

View file

@ -78,8 +78,6 @@ PUBLIC int bsp_timer_int_handler(void)
{
unsigned ticks;
IDLE_STOP;
if(minix_panicing)
return 0;
@ -192,8 +190,6 @@ PUBLIC int ap_timer_int_handler(void)
int expired = 0;
struct proc * p, * billp;
IDLE_STOP;
#ifdef CONFIG_WATCHDOG
/*
* we need to know whether local timer ticks are happening or whether

View file

@ -25,12 +25,6 @@
#define unset_sys_bit(map,bit) \
( MAP_CHUNK(map.chunk,bit) &= ~(1 << CHUNK_OFFSET(bit) )
#ifdef CONFIG_IDLE_TSC
#define IDLE_STOP if(idle_active) { read_tsc_64(&idle_stop); idle_active = 0; }
#else
#define IDLE_STOP
#endif
/* args to intr_init() */
#define INTS_ORIG 0 /* restore interrupts */
#define INTS_MINIX 1 /* initialize interrupts for minix */

View file

@ -58,12 +58,6 @@ EXTERN int verboseflags;
EXTERN int config_no_apic; /* optionaly turn off apic */
#endif
#ifdef CONFIG_IDLE_TSC
EXTERN u64_t idle_tsc;
EXTERN u64_t idle_stop;
EXTERN int idle_active;
#endif
EXTERN unsigned cpu_hz[CONFIG_MAX_CPUS];
#define cpu_set_freq(cpu, freq) do {cpu_hz[cpu] = freq;} while (0)

View file

@ -111,8 +111,6 @@ PUBLIC void irq_handle(int irq)
{
irq_hook_t * hook;
IDLE_STOP;
/* here we need not to get this IRQ until all the handlers had a say */
hw_intr_mask(irq);
hook = irq_handlers[irq];

View file

@ -13,8 +13,6 @@
/* We only support 1 cpu now */
#define CONFIG_MAX_CPUS 1
#define cpuid 0
/* measure cumulative idle timestamp counter ticks */
#undef CONFIG_IDLE_TSC
/* This is the master header for the kernel. It includes some other files
* and defines the principal constants.

View file

@ -218,10 +218,6 @@ PUBLIC void main()
#endif /* SPROFILE */
cprof_procs_no = 0; /* init nr of hash table slots used */
#ifdef CONFIG_IDLE_TSC
idle_tsc = cvu64(0);
#endif
vm_running = 0;
krandom.random_sources = RANDOM_SOURCES;
krandom.random_elements = RANDOM_ELEMENTS;
@ -255,6 +251,8 @@ PUBLIC void main()
FIXME("PROC check enabled");
#endif
cycles_accounting_init();
restart();
NOT_REACHABLE;
}

View file

@ -119,30 +119,21 @@ PRIVATE int QueueMess(endpoint_t ep, vir_bytes msg_lin, struct proc *dst)
/*===========================================================================*
* idle *
*===========================================================================*/
PRIVATE void idle()
PRIVATE void idle(void)
{
/* This function is called whenever there is no work to do.
* Halt the CPU, and measure how many timestamp counter ticks are
* spent not doing anything. This allows test setups to measure
* the CPU utiliziation of certain workloads with high precision.
*/
#ifdef CONFIG_IDLE_TSC
u64_t idle_start;
read_tsc_64(&idle_start);
idle_active = 1;
#endif
/* start accounting for the idle time */
cycles_accounting_stop(proc_addr(KERNEL));
halt_cpu();
#ifdef CONFIG_IDLE_TSC
if (idle_active) {
IDLE_STOP;
printf("Kernel: idle active after resuming CPU\n");
}
idle_tsc = add64(idle_tsc, sub64(idle_stop, idle_start));
#endif
/*
* end of accounting for the idle task does not happen here, the kernel
* is handling stuff for quite a while before it gets back here!
*/
}
/*===========================================================================*
@ -274,6 +265,7 @@ check_misc_flags:
#endif
proc_ptr = arch_finish_schedcheck();
cycles_accounting_stop(proc_addr(KERNEL));
NOREC_RETURN(schedch, proc_ptr);
}

View file

@ -40,6 +40,8 @@ struct proc {
clock_t p_virt_left; /* number of ticks left on virtual timer */
clock_t p_prof_left; /* number of ticks left on profile timer */
u64_t p_cycles; /* how many cycles did the process use */
struct proc *p_nextready; /* pointer to next ready process */
struct proc *p_caller_q; /* head of list of procs wishing to send */
struct proc *p_q_link; /* link to next proc wishing to send */

View file

@ -18,6 +18,18 @@ _PROTOTYPE( void set_timer, (struct timer *tp, clock_t t, tmr_func_t f) );
_PROTOTYPE( void reset_timer, (struct timer *tp) );
_PROTOTYPE( void ser_dump_proc, (void) );
_PROTOTYPE( void cycles_accounting_init, (void) );
/*
* This functions start and stop accounting for process, kernel or idle cycles.
* It inherently have to account for some kernel cycles for process too,
* therefore it should be called asap after trapping to kernel and as late as
* possible before returning to userspace. These function is architecture
* dependent
*/
_PROTOTYPE( void cycles_accounting_stop, (struct proc * p) );
/* this is a wrapper to make calling it from assembly easier */
_PROTOTYPE( void cycles_accounting_stop_idle, (void) );
/* main.c */
_PROTOTYPE( void main, (void) );
_PROTOTYPE( void prepare_shutdown, (int how) );

View file

@ -152,14 +152,12 @@ PUBLIC int do_getinfo(struct proc * caller, message * m_ptr)
break;
}
case GET_IDLETSC: {
#ifdef CONFIG_IDLE_TSC
length = sizeof(idle_tsc);
src_vir = (vir_bytes) &idle_tsc;
struct proc * idl;
idl = proc_addr(IDLE);
length = sizeof(idl->p_cycles);
src_vir = (vir_bytes) &idl->p_cycles;
break;
#else
kprintf("do_getinfo: kernel not compiled with CONFIG_IDLE_TSC\n");
return(EINVAL);
#endif
}
case GET_AOUTHEADER: {
int hdrindex, index = m_ptr->I_VAL_LEN2_E;