Hi all, Here is a updated patches to implement VIRT_CPU_ACCOUNTING for ia64, which enable us to use more accurate cpu time accounting.
If you want to know the detail, please refer: [PATCH 0/9] ia64: VIRT_CPU_ACCOUNTING (accurate cpu time accounting) http://www.gelato.unsw.edu.au/archives/linux-ia64/0710/21262.html This 1 of 2 is a patch bundled previous 9 patches plus some updates. Followings are feedback of comments: - cputime.h: Better header comment description - cputime.h: Bracket (NSEC_PER_SEC / HZ) not to overflow fast - cputime.h: Retrieve "const" of timespec_to_cputime - minstate.h, ivt.S: Read ITC earlier - time.c: Follow recent change of update_process_times And additional notable change is: - head.S: Define cycle_to_cputime properly since previous macro version easily overflows at cycles in ~18 sec. Signed-off-by: Hidetoshi Seto <[EMAIL PROTECTED]> --- arch/ia64/Kconfig | 11 ++++ arch/ia64/ia32/elfcore32.h | 14 +++++ arch/ia64/kernel/asm-offsets.c | 5 + arch/ia64/kernel/entry.S | 22 ++++++++ arch/ia64/kernel/fsys.S | 20 +++++++ arch/ia64/kernel/head.S | 20 +++++++ arch/ia64/kernel/ivt.S | 57 ++++++++++++++++++++++ arch/ia64/kernel/minstate.h | 14 +++++ arch/ia64/kernel/time.c | 78 ++++++++++++++++++++++++++++++ include/asm-ia64/cputime.h | 104 +++++++++++++++++++++++++++++++++++++++++ include/asm-ia64/system.h | 12 ++++ include/asm-ia64/thread_info.h | 13 +++++ 12 files changed, 369 insertions(+), 1 deletion(-) Index: linux-2.6.24-rc5/arch/ia64/Kconfig =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/Kconfig +++ linux-2.6.24-rc5/arch/ia64/Kconfig @@ -262,6 +262,17 @@ default "17" if HUGETLB_PAGE default "11" +config VIRT_CPU_ACCOUNTING + bool "Deterministic task and CPU time accounting" + default y + help + Select this option to enable more accurate task and CPU time + accounting. This is done by reading a CPU counter on each + kernel entry and exit and on transitions within the kernel + between system, softirq and hardirq state, so there is a + small performance impact. + If in doubt, say Y here. + config SMP bool "Symmetric multi-processing support" help Index: linux-2.6.24-rc5/arch/ia64/kernel/time.c =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/time.c +++ linux-2.6.24-rc5/arch/ia64/kernel/time.c @@ -59,6 +59,84 @@ }; static struct clocksource *itc_clocksource; +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + +#include <linux/kernel_stat.h> + +extern cputime_t cycle_to_cputime(u64 cyc); + +/* + * Called from the context switch with interrupts disabled, to charge all + * accumulated times to the current process, and to prepare accounting on + * the next process. + */ +void ia64_account_on_switch(struct task_struct *prev, struct task_struct *next) +{ + struct thread_info *pi = task_thread_info(prev); + struct thread_info *ni = task_thread_info(next); + cputime_t delta_stime, delta_utime; + __u64 now; + + now = ia64_get_itc(); + + delta_stime = cycle_to_cputime(pi->ac_stime + (now - pi->ac_stamp)); + account_system_time(prev, 0, delta_stime); + account_system_time_scaled(prev, delta_stime); + + if (pi->ac_utime) { + delta_utime = cycle_to_cputime(pi->ac_utime); + account_user_time(prev, delta_utime); + account_user_time_scaled(prev, delta_utime); + } + + pi->ac_stamp = ni->ac_stamp = now; + ni->ac_stime = ni->ac_utime = 0; +} + +/* + * Account time for a transition between system, hard irq or soft irq state. + * Note that this function is called with interrupts enabled. + */ +void account_system_vtime(struct task_struct *tsk) +{ + struct thread_info *ti = task_thread_info(tsk); + unsigned long flags; + cputime_t delta_stime; + __u64 now; + + local_irq_save(flags); + + now = ia64_get_itc(); + + delta_stime = cycle_to_cputime(ti->ac_stime + (now - ti->ac_stamp)); + account_system_time(tsk, 0, delta_stime); + account_system_time_scaled(tsk, delta_stime); + ti->ac_stime = 0; + + ti->ac_stamp = now; + + local_irq_restore(flags); +} + +/* + * Called from the timer interrupt handler to charge accumulated user time + * to the current process. Must be called with interrupts disabled. + */ +void account_process_tick(struct task_struct *p, int user_tick) +{ + struct thread_info *ti = task_thread_info(p); + cputime_t delta_utime; + + if (ti->ac_utime) { + delta_utime = cycle_to_cputime(ti->ac_utime); + account_user_time(p, delta_utime); + account_user_time_scaled(p, delta_utime); + ti->ac_utime = 0; + } +} + +#endif /* CONFIG_VIRT_CPU_ACCOUNTING */ + static irqreturn_t timer_interrupt (int irq, void *dev_id) { Index: linux-2.6.24-rc5/include/asm-ia64/system.h =================================================================== --- linux-2.6.24-rc5.orig/include/asm-ia64/system.h +++ linux-2.6.24-rc5/include/asm-ia64/system.h @@ -210,6 +210,13 @@ extern void ia64_save_extra (struct task_struct *task); extern void ia64_load_extra (struct task_struct *task); +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +extern void ia64_account_on_switch (struct task_struct *prev, struct task_struct *next); +# define IA64_ACCOUNT_ON_SWITCH(p,n) ia64_account_on_switch(p,n) +#else +# define IA64_ACCOUNT_ON_SWITCH(p,n) +#endif + #ifdef CONFIG_PERFMON DECLARE_PER_CPU(unsigned long, pfm_syst_info); # define PERFMON_IS_SYSWIDE() (__get_cpu_var(pfm_syst_info) & 0x1) @@ -222,6 +229,7 @@ || IS_IA32_PROCESS(task_pt_regs(t)) || PERFMON_IS_SYSWIDE()) #define __switch_to(prev,next,last) do { \ + IA64_ACCOUNT_ON_SWITCH(prev, next); \ if (IA64_HAS_EXTRA_STATE(prev)) \ ia64_save_extra(prev); \ if (IA64_HAS_EXTRA_STATE(next)) \ @@ -266,6 +274,10 @@ void default_idle(void); +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +extern void account_system_vtime(struct task_struct *); +#endif + #endif /* __KERNEL__ */ #endif /* __ASSEMBLY__ */ Index: linux-2.6.24-rc5/include/asm-ia64/cputime.h =================================================================== --- linux-2.6.24-rc5.orig/include/asm-ia64/cputime.h +++ linux-2.6.24-rc5/include/asm-ia64/cputime.h @@ -1,6 +1,110 @@ +/* + * include/asm-ia64/cputime.h: + * Definitions for measuring cputime on ia64 machines. + * + * Based on <asm-powerpc/cputime.h>. + * + * Copyright (C) 2007 FUJITSU LIMITED + * Copyright (C) 2007 Hidetoshi Seto <[EMAIL PROTECTED]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * If we have CONFIG_VIRT_CPU_ACCOUNTING, we measure cpu time in nsec. + * Otherwise we measure cpu time in jiffies using the generic definitions. + */ + #ifndef __IA64_CPUTIME_H #define __IA64_CPUTIME_H +#ifndef CONFIG_VIRT_CPU_ACCOUNTING #include <asm-generic/cputime.h> +#else + +#include <linux/time.h> +#include <linux/jiffies.h> +#include <asm/processor.h> + +typedef u64 cputime_t; +typedef u64 cputime64_t; + +#define cputime_zero ((cputime_t)0) +#define cputime_max ((~((cputime_t)0) >> 1) - 1) +#define cputime_add(__a, __b) ((__a) + (__b)) +#define cputime_sub(__a, __b) ((__a) - (__b)) +#define cputime_div(__a, __n) ((__a) / (__n)) +#define cputime_halve(__a) ((__a) >> 1) +#define cputime_eq(__a, __b) ((__a) == (__b)) +#define cputime_gt(__a, __b) ((__a) > (__b)) +#define cputime_ge(__a, __b) ((__a) >= (__b)) +#define cputime_lt(__a, __b) ((__a) < (__b)) +#define cputime_le(__a, __b) ((__a) <= (__b)) + +#define cputime64_zero ((cputime64_t)0) +#define cputime64_add(__a, __b) ((__a) + (__b)) +#define cputime64_sub(__a, __b) ((__a) - (__b)) +#define cputime_to_cputime64(__ct) (__ct) + +/* + * Convert cputime <-> jiffies (HZ) + */ +#define cputime_to_jiffies(__ct) ((__ct) / (NSEC_PER_SEC / HZ)) +#define jiffies_to_cputime(__jif) ((__jif) * (NSEC_PER_SEC / HZ)) +#define cputime64_to_jiffies64(__ct) ((__ct) / (NSEC_PER_SEC / HZ)) +#define jiffies64_to_cputime64(__jif) ((__jif) * (NSEC_PER_SEC / HZ)) + +/* + * Convert cputime <-> milliseconds + */ +#define cputime_to_msecs(__ct) ((__ct) / NSEC_PER_MSEC) +#define msecs_to_cputime(__msecs) ((__msecs) * NSEC_PER_MSEC) + +/* + * Convert cputime <-> seconds + */ +#define cputime_to_secs(__ct) ((__ct) / NSEC_PER_SEC) +#define secs_to_cputime(__secs) ((__secs) * NSEC_PER_SEC) + +/* + * Convert cputime <-> timespec (nsec) + */ +static inline cputime_t timespec_to_cputime(const struct timespec *val) +{ + cputime_t ret = val->tv_sec * NSEC_PER_SEC; + return (ret + val->tv_nsec); +} +static inline void cputime_to_timespec(const cputime_t ct, struct timespec *val) +{ + val->tv_sec = ct / NSEC_PER_SEC; + val->tv_nsec = ct % NSEC_PER_SEC; +} + +/* + * Convert cputime <-> timeval (msec) + */ +static inline cputime_t timeval_to_cputime(struct timeval *val) +{ + cputime_t ret = val->tv_sec * NSEC_PER_SEC; + return (ret + val->tv_usec * NSEC_PER_USEC); +} +static inline void cputime_to_timeval(const cputime_t ct, struct timeval *val) +{ + val->tv_sec = ct / NSEC_PER_SEC; + val->tv_usec = (ct % NSEC_PER_SEC) / NSEC_PER_USEC; +} + +/* + * Convert cputime <-> clock (USER_HZ) + */ +#define cputime_to_clock_t(__ct) ((__ct) / (NSEC_PER_SEC / USER_HZ)) +#define clock_t_to_cputime(__x) ((__x) * (NSEC_PER_SEC / USER_HZ)) + +/* + * Convert cputime64 to clock. + */ +#define cputime64_to_clock_t(__ct) cputime_to_clock_t((cputime_t)__ct) +#endif /* CONFIG_VIRT_CPU_ACCOUNTING */ #endif /* __IA64_CPUTIME_H */ Index: linux-2.6.24-rc5/arch/ia64/ia32/elfcore32.h =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/ia32/elfcore32.h +++ linux-2.6.24-rc5/arch/ia64/ia32/elfcore32.h @@ -30,7 +30,19 @@ int si_errno; /* errno */ }; -#define jiffies_to_timeval(a,b) do { (b)->tv_usec = 0; (b)->tv_sec = (a)/HZ; }while(0) +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +/* + * Hacks are here since types between compat_timeval (= pair of s32) and + * ia64-native timeval (= pair of s64) are not compatible, at least a file + * arch/ia64/ia32/../../../fs/binfmt_elf.c will get warnings from compiler on + * use of cputime_to_timeval(), which usually an alias of jiffies_to_timeval(). + */ +#define cputime_to_timeval(a,b) \ + do { (b)->tv_usec = 0; (b)->tv_sec = (a)/NSEC_PER_SEC; } while(0) +#else +#define jiffies_to_timeval(a,b) \ + do { (b)->tv_usec = 0; (b)->tv_sec = (a)/HZ; } while(0) +#endif struct elf_prstatus { Index: linux-2.6.24-rc5/arch/ia64/kernel/asm-offsets.c =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/asm-offsets.c +++ linux-2.6.24-rc5/arch/ia64/kernel/asm-offsets.c @@ -39,6 +39,11 @@ DEFINE(TI_FLAGS, offsetof(struct thread_info, flags)); DEFINE(TI_CPU, offsetof(struct thread_info, cpu)); DEFINE(TI_PRE_COUNT, offsetof(struct thread_info, preempt_count)); +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + DEFINE(TI_AC_STAMP, offsetof(struct thread_info, ac_stamp)); + DEFINE(TI_AC_STIME, offsetof(struct thread_info, ac_stime)); + DEFINE(TI_AC_UTIME, offsetof(struct thread_info, ac_utime)); +#endif BLANK(); Index: linux-2.6.24-rc5/include/asm-ia64/thread_info.h =================================================================== --- linux-2.6.24-rc5.orig/include/asm-ia64/thread_info.h +++ linux-2.6.24-rc5/include/asm-ia64/thread_info.h @@ -31,6 +31,11 @@ mm_segment_t addr_limit; /* user-level address space limit */ int preempt_count; /* 0=premptable, <0=BUG; will also serve as bh-counter */ struct restart_block restart_block; +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + __u64 ac_stamp; + __u64 ac_stime; + __u64 ac_utime; +#endif }; #define THREAD_SIZE KERNEL_STACK_SIZE @@ -62,9 +67,17 @@ #define task_stack_page(tsk) ((void *)(tsk)) #define __HAVE_THREAD_FUNCTIONS +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +#define setup_thread_stack(p, org) \ + *task_thread_info(p) = *task_thread_info(org); \ + task_thread_info(p)->ac_stime = 0; \ + task_thread_info(p)->ac_utime = 0; \ + task_thread_info(p)->task = (p); +#else #define setup_thread_stack(p, org) \ *task_thread_info(p) = *task_thread_info(org); \ task_thread_info(p)->task = (p); +#endif #define end_of_stack(p) (unsigned long *)((void *)(p) + IA64_RBS_OFFSET) #define __HAVE_ARCH_TASK_STRUCT_ALLOCATOR Index: linux-2.6.24-rc5/arch/ia64/kernel/minstate.h =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/minstate.h +++ linux-2.6.24-rc5/arch/ia64/kernel/minstate.h @@ -3,6 +3,18 @@ #include "entry.h" +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +/* read ar.itc in advance, and use it before leaving bank 0 */ +#define ACCOUNT_GET_STAMP \ +(pUStk) mov.m r20=ar.itc; +#define ACCOUNT_SYS_ENTER \ +(pUStk) br.call.spnt rp=account_sys_enter \ + ;; +#else +#define ACCOUNT_GET_STAMP +#define ACCOUNT_SYS_ENTER +#endif + /* * DO_SAVE_MIN switches to the kernel stacks (if necessary) and saves * the minimum state necessary that allows us to turn psr.ic back @@ -122,11 +134,13 @@ ;; \ .mem.offset 0,0; st8.spill [r16]=r2,16; \ .mem.offset 8,0; st8.spill [r17]=r3,16; \ + ACCOUNT_GET_STAMP \ adds r2=IA64_PT_REGS_R16_OFFSET,r1; \ ;; \ EXTRA; \ movl r1=__gp; /* establish kernel global pointer */ \ ;; \ + ACCOUNT_SYS_ENTER \ bsw.1; /* switch back to bank 1 (must be last in insn group) */ \ ;; Index: linux-2.6.24-rc5/arch/ia64/kernel/ivt.S =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/ivt.S +++ linux-2.6.24-rc5/arch/ia64/kernel/ivt.S @@ -805,8 +805,13 @@ (p8) adds r28=16,r28 // A switch cr.iip to next bundle (p9) adds r8=1,r8 // A increment ei to next slot +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + ;; + mov b6=r30 // I0 setup syscall handler branch reg early +#else nop.i 0 ;; +#endif mov.m r25=ar.unat // M2 (5 cyc) dep r29=r8,r29,41,2 // I0 insert new ei into cr.ipsr @@ -817,7 +822,11 @@ // /////////////////////////////////////////////////////////////////////// st1 [r16]=r0 // M2|3 clear current->thread.on_ustack flag +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + mov.m r30=ar.itc // M get cycle for accounting +#else mov b6=r30 // I0 setup syscall handler branch reg early +#endif cmp.eq pKStk,pUStk=r0,r17 // A were we on kernel stacks already? and r9=_TIF_SYSCALL_TRACEAUDIT,r9 // A mask trace or audit @@ -829,6 +838,24 @@ cmp.eq p14,p0=r9,r0 // A are syscalls being traced/audited? br.call.sptk.many b7=ia64_syscall_setup // B 1: +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + // mov.m r30=ar.itc is called in advance, and r13 is current + add r16=TI_AC_STAMP+IA64_TASK_SIZE,r13 // A + add r17=TI_AC_UTIME+IA64_TASK_SIZE,r13 // A +(pKStk) br.cond.spnt .skip_accounting // B unlikely skip + ;; + ld8 r18=[r16] // M get last stamp + ld8 r19=[r17] // M cumulated utime + ;; + sub r18=r30,r18 // A elapsed time + ;; + add r19=r19,r18 // A sum + ;; + st8 [r16]=r30 // M update stamp + st8 [r17]=r19 // M update utime + ;; +.skip_accounting: +#endif mov ar.rsc=0x3 // M2 set eager mode, pl 0, LE, loadrs=0 nop 0 bsw.1 // B (6 cyc) regs are saved, switch to bank 1 @@ -928,6 +955,7 @@ * - r27: saved ar.rsc * - r28: saved cr.iip * - r29: saved cr.ipsr + * - r30: ar.itc for accounting (don't touch) * - r31: saved pr * - b0: original contents (to be saved) * On exit: @@ -1090,6 +1118,35 @@ DBG_FAULT(16) FAULT(16) +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + /* + * There is no particular reason for this code to be here, other than + * that there happens to be space here that would go unused otherwise. + * If this fault ever gets "unreserved", simply moved the following + * code to a more suitable spot... + * + * account_sys_enter is called from SAVE_MIN* macros if accounting is + * enabled and if the macro is entered from user mode. + */ +ENTRY(account_sys_enter) + // mov.m r20=ar.itc is called in advance, and r13 is current + add r16=TI_AC_STAMP+IA64_TASK_SIZE,r13 + add r17=TI_AC_UTIME+IA64_TASK_SIZE,r13 + ;; + ld8 r18=[r16] // get last stamp + ld8 r19=[r17] // cumulated utime + ;; + sub r18=r20,r18 // elapsed time + ;; + add r19=r19,r18 // sum + ;; + st8 [r16]=r20 // update stamp + st8 [r17]=r19 // update utime + ;; + br.ret.sptk.many rp +END(account_sys_enter) +#endif + .org ia64_ivt+0x4400 ///////////////////////////////////////////////////////////////////////////////////////// // 0x4400 Entry 17 (size 64 bundles) Reserved Index: linux-2.6.24-rc5/arch/ia64/kernel/fsys.S =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/fsys.S +++ linux-2.6.24-rc5/arch/ia64/kernel/fsys.S @@ -660,7 +660,11 @@ nop.i 0 ;; mov ar.rsc=0 // M2 set enforced lazy mode, pl 0, LE, loadrs=0 +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + mov.m r30=ar.itc // M get cycle for accounting +#else nop.m 0 +#endif nop.i 0 ;; mov r23=ar.bspstore // M2 (12 cyc) save ar.bspstore @@ -682,6 +686,22 @@ cmp.ne pKStk,pUStk=r0,r0 // A set pKStk <- 0, pUStk <- 1 br.call.sptk.many b7=ia64_syscall_setup // B ;; +#ifdef CONFIG_VIRT_CPU_ACCOUNTING + // mov.m r30=ar.itc is called in advance + add r16=TI_AC_STAMP+IA64_TASK_SIZE,r2 + add r17=TI_AC_UTIME+IA64_TASK_SIZE,r2 + ;; + ld8 r18=[r16] // get last stamp + ld8 r19=[r17] // cumulated utime + ;; + sub r18=r30,r18 // elapsed time + ;; + add r19=r19,r18 // sum + ;; + st8 [r16]=r30 // update stamp + st8 [r17]=r19 // update utime + ;; +#endif mov ar.rsc=0x3 // M2 set eager mode, pl 0, LE, loadrs=0 mov rp=r14 // I0 set the real return addr and r3=_TIF_SYSCALL_TRACEAUDIT,r3 // A Index: linux-2.6.24-rc5/arch/ia64/kernel/entry.S =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/entry.S +++ linux-2.6.24-rc5/arch/ia64/kernel/entry.S @@ -995,6 +995,28 @@ shladd in0=loc1,3,r17 mov in1=0 ;; +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +account_sys_leave: + // The size of current frame + // (in * 2 + loc * N (N = 8 or 12) + out * 2) + // is enough to work, so just take care to keep in0,in1 + mov loc0=ar.itc + mov loc1=IA64_KR(CURRENT) // M2 (12 cycle read latency) + ;; + add loc2=TI_AC_STAMP+IA64_TASK_SIZE,loc1 + add loc3=TI_AC_STIME+IA64_TASK_SIZE,loc1 + ;; + ld8 loc4=[loc2] // get last stamp + ld8 loc5=[loc3] // cumulated stime + ;; + sub loc4=loc0,loc4 // elapsed time + ;; + add loc5=loc5,loc4 // sum + ;; + st8 [loc2]=loc0 // update stamp + st8 [loc3]=loc5 // update stime + ;; +#endif TEXT_ALIGN(32) rse_clear_invalid: #ifdef CONFIG_ITANIUM Index: linux-2.6.24-rc5/arch/ia64/kernel/head.S =================================================================== --- linux-2.6.24-rc5.orig/arch/ia64/kernel/head.S +++ linux-2.6.24-rc5/arch/ia64/kernel/head.S @@ -1002,6 +1002,26 @@ br.ret.sptk.many rp END(sched_clock) +#ifdef CONFIG_VIRT_CPU_ACCOUNTING +GLOBAL_ENTRY(cycle_to_cputime) + alloc r16=ar.pfs,1,0,0,0 + addl r8=THIS_CPU(cpu_info) + IA64_CPUINFO_NSEC_PER_CYC_OFFSET,r0 + ;; + ldf8 f8=[r8] + ;; + setf.sig f9=r32 + ;; + xmpy.lu f10=f9,f8 // calculate low 64 bits of 128-bit product (4 cyc) + xmpy.hu f11=f9,f8 // calculate high 64 bits of 128-bit product + ;; + getf.sig r8=f10 // (5 cyc) + getf.sig r9=f11 + ;; + shrp r8=r9,r8,IA64_NSEC_PER_CYC_SHIFT + br.ret.sptk.many rp +END(cycle_to_cputime) +#endif /* CONFIG_VIRT_CPU_ACCOUNTING */ + GLOBAL_ENTRY(start_kernel_thread) .prologue .save rp, r0 // this is the end of the call-chain - To unsubscribe from this list: send the line "unsubscribe linux-ia64" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html