/*
 * General Public License (GPL) v2
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, 
 * write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Copyright 2007 jjmora@arrakis.es.  All rights reserved.
 * Use is subject to license terms.
*/


/*
 * Este programa acepta como parametro el PID de un proceso
 * y utiliza las fucniones de la libreria libkvm para obtener
 * el estado de los distintos threads que lo forman.
 *
 * Uso: lwpstat  [-s|-w|-l|-a|-r] [-T seconds] [-t LWPID,LWPID,LWPID,...] -p <pid>
 *
 *           -l sort by LWPID.
 *           -w sort by WCHAN.
 *           -s sort by system call.
 *           -a show all LWP.
 *           -t LWPID LWPID ... show this LWP list.
 *           -r reverse.
 *           -T time in seconds
 *
 * IMPORTANTE: Este programa debe compilarse con la opcion de soporte para
 *             64bits, en gcc es "-m64". Tambien se debe utilizar "-lkvm" 
 *             para linkar la libreria libkvm
*/ 

/*
 * This program accept a process's ID and use the function 
 * of the libkvm library to obtain the state of all threads.
 *
 * Use: lwpstat  [-s|-w|-l|-a|-r] [-T seconds] [-t LWPID,LWPID,LWPID,...] -p <pid>
 *
 *           -i sort by LWPID.
 *           -w sort by WCHAN.
 *           -s sort by system call.
 *           -a show all LWP.
 *           -r reverse.
 *           -t LWPID LWPID ... show this LWP list.
 *           -T time in seconds
 *
 * IMPORTANT: This program must be compiled with the option to 64bits support, 
 *            in gcc compiler is "-m64". Also we must be compiled with "-lkvm" 
 *            to support the libkvm library.
 *
*/





#include <stdio.h>
#include <limits.h>
#include <sys/times.h>

#define _KERNEL

#include <kvm.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/klwp.h>
#include <sys/sobject.h> 
#include <sys/turnstile.h>
#include <sys/termios.h>

#include "sysc.h"

struct thread_info {
        int t_tid;
        clock_t   t_disp_time;
        int t_state;
        caddr_t lc_wchan;
        caddr_t lc_wchan0;
        int sobj_type;
        struct turnstile *t_ts;
        int t_ts_waiters;
        unsigned char t_sysnum;
        long lwp_arg[2];
};

void error_param();
int num_lines();
void read_thread_info(kvm_t *kvm_p,proc_t *pproc,int nthreads,struct thread_info *p_thread_info,int *nlwp_time);

int main(int argc, char **argv,char **envp)
{
int n,j;
int nthreads;

char cad[80];
char str[50];

kvm_t *kvm_p;
struct pid pid;
proc_t *pproc;
int npid;
int pid_ok;

int nsec;
int sec_ok;

int sort_ok;
int argv_sort;
int argv_reverse;
int argv_all;

struct tms tms;
ulong clock_actual;
long seg,min,minx,hor;
int nlwp_time[6];

ulong val,val1;

int list_lwp[20];
int n_lwp;
int c;
int i;
int view_ok;
int cont_lines;

struct thread_info *p_thread_info;
struct thread_info aux_thread_info;

init_sysc(&syscalltable);


argv_sort=0;
npid=0;
pid_ok=0;
nsec=1;
sec_ok=0;
argv_reverse=0;
argv_all=0;
n_lwp=0;

for(n=1;n<argc;n++)
	{

        if (!strcmp(argv[n],"-w"))
                argv_sort=1; 
        if (!strcmp(argv[n],"-s"))
		argv_sort=2; 
        if (!strcmp(argv[n],"-l"))
                argv_sort=3; 
        if (!strcmp(argv[n],"-a"))
                argv_all=1; 
        if (!strcmp(argv[n],"-r"))
                argv_reverse=1; 
	if (!strcmp(argv[n],"-p"))
                { 
		if(n+1<argc)
			npid=atoi(argv[n+1]);pid_ok=1; 
		}

	if (!strcmp(argv[n],"-T"))
                { 
		if(n+1<argc)
			nsec=atoi(argv[n+1]);sec_ok=1; 
		}

	if (!strcmp(argv[n],"-t"))
		{
		c=0;
		n++;
		i=n;
		while (i<argc)
			{
			if (atoi(argv[i]))
				{
				list_lwp[c]=atoi(argv[i]);
				c++;
				}
				else
				{i=argc;}
			i++;
			}
		n_lwp=c;
		n=n+c;
		}
	}

if(!pid_ok)
	{error_param("The <pid> is need");return(1);}

for(;;)
	{
	kvm_p=kvm_open(NULL,NULL,NULL,O_RDONLY,cad);

	if(kvm_p==NULL)
		{printf("\n kvm_open ERROR %s\n\n",cad);return(1);}

/*
 * Se obtiene un puntero a una estructura de tipo proc_t
*/

	pproc=kvm_getproc(kvm_p,npid);

	if(pproc==NULL)
		{
		printf("El proceso no existe\n");
		return(1);
		}

	if(kvm_read(kvm_p,(uintptr_t)pproc->p_pidp,&pid,sizeof(pid))<0)
        	printf("\nkvm_read: Error leyendo p_pidp");

	nthreads=pproc->p_lwpcnt;

	printf("\n Pid: %d  \tPPid: %d   Threads: %d",pid.pid_id,pproc->p_ppid,nthreads);
	printf("\n Args: %s",pproc->p_user.u_psargs); 

	p_thread_info=malloc(sizeof(struct thread_info)*nthreads);
	if(p_thread_info==NULL)
		{printf("\n malloc(): Error no se ha podido reservar memoria \n\n");return(1);}	
        read_thread_info(kvm_p,pproc,nthreads,p_thread_info,nlwp_time);

	kvm_close(kvm_p);

/* 
 * Ordena el array p_thread_info[] 
*/

	val=0;
	val1=0;
	sort_ok=1;

	while(sort_ok)
		{
		sort_ok=0;
		for (n=0;n<nthreads-1;n++)
			{
			if (argv_sort==2)
				{val=(ulong)p_thread_info[n].t_sysnum;val1=(ulong)p_thread_info[n+1].t_sysnum;}

			if (argv_sort==1)
				{val=(ulong)p_thread_info[n].lc_wchan;val1=(ulong)p_thread_info[n+1].lc_wchan;}

			if (argv_sort==3)
				{val=(ulong)p_thread_info[n].t_tid;val1=(ulong)p_thread_info[n+1].t_tid;}

			if (argv_sort==0)
				{val=(ulong)p_thread_info[n+1].t_disp_time;val1=(ulong)p_thread_info[n].t_disp_time;}

			if(argv_reverse)
				{
				if(val<val1)
					{
					aux_thread_info=p_thread_info[n];
					p_thread_info[n]=p_thread_info[n+1];
					p_thread_info[n+1]=aux_thread_info;
					sort_ok=1;
					}	
				}
				else
				{
				if(val>val1)
                                	{
	                                aux_thread_info=p_thread_info[n];
					p_thread_info[n]=p_thread_info[n+1];
                                	p_thread_info[n+1]=aux_thread_info;
                                	sort_ok=1;
                                	}

				}
			}
		}

/*
 * Imprime el array p_thread_info[]
*/

	printf("\nt_tid   time   t_state    wchan/wchan0             sobj_type  #w     t_ts             syscall");
	printf("\n----- -------- ------- --------------------------- --------- --- ------------- --------------------------------------");
	clock_actual=times(&tms);

	cont_lines=-1;
	for(n=0;n<nthreads;n++)
	        {
	
		view_ok=0;
		for(j=0;j<n_lwp;j++)
			if (p_thread_info[n].t_tid==list_lwp[j])
				view_ok=1;

		if(n_lwp==0)
			view_ok=1;

		if (view_ok)
			{	
			cont_lines++;	
        		sprintf(str,"%5.d",p_thread_info[n].t_tid);

			seg=((clock_actual-p_thread_info[n].t_disp_time)/CLK_TCK)%60;
			minx=((clock_actual-p_thread_info[n].t_disp_time)/CLK_TCK)/60;
			min=minx%60;
			hor=minx/60;

        		printf("\n%s %0.2d:%0.2d:%0.2d",str,hor,min,seg);

        		switch(p_thread_info[n].t_state)
                		{
		                case 0:printf(" FREE   ");break;
		                case 1:printf(" SLEEP  ");break;
				case 2:printf(" RUN    ");break;
		                case 4:printf(" ONPROC ");break;
		                case 8:printf(" ZOMB   ");break;
		                case 10:printf(" STOPPED");break;
		                }

		        sprintf(str,"0x%0.11lx/0x%0.11lx",p_thread_info[n].lc_wchan,p_thread_info[n].lc_wchan0);
		        printf(" %-s ",str);

		 	switch(p_thread_info[n].sobj_type)
		                {
		                case 0:printf("NONE    ");break;
		                case 1:printf("MUTEX   ");break;
		                case 2:printf("RWLOCK  ");break;
		                case 3:printf("CV      ");break;
		                case 4:printf("SEMA    ");break;
		                case 5:printf("USER    ");break;
		                case 6:printf("USER_PI ");break;
		                case 7:printf("SHUTTLE ");break;
		                }

			printf("  %3d 0x%011lx",p_thread_info[n].t_ts_waiters,p_thread_info[n].t_ts);

			sprintf(str,"%s(0x%lx, 0x%lx, ...)",syscalltable[p_thread_info[n].t_sysnum],p_thread_info[n].lwp_arg[0],p_thread_info[n].lwp_arg[1]);
			printf(" %-35s",str);
			}
		if (! argv_all)	
			{
			if(num_lines()<(cont_lines+10))
				n=nthreads;
			}
		}


	printf("\n%d <1m, %d <5m, %d <30m, %d <1h, %d <5h, %d >5h",nlwp_time[0],nlwp_time[1],nlwp_time[2],nlwp_time[3],nlwp_time[4],nlwp_time[5]);

	for(n=6+cont_lines;n<num_lines();n++)
		printf("\n");

	free(p_thread_info);

	if(argv_all)
		return;

	sleep(nsec);
	}

return;
}

/* 
 * void error_param()
 *
 * Imprime por pantalla los distintos parametros que acepta el programa.
*/

void error_param(char *cad)
{
	printf("Error: %s\n\n     Usage: lwpstat [-s|-w|-l|-a|-r] [-T seconds] [-t LWPID,LWPID,LWPID,...] -p <pid>",cad);
	printf("\n     Version 0.1.1");
	printf("\n\n         -l sort by LWPID.");
	printf("\n         -w sort by WCHAN.");
	printf("\n         -s sort by system call.");
	printf("\n         -r reverse.");
	printf("\n         -a show all LWP.");
	printf("\n         -t LWPID LWPID ... show this LWP list.");
	printf("\n         -p <pid> process ID.");
	printf("\n         -T <seconds> time in seconds.");

	printf("\n\n");
	return;
}

/*
 *  int num_lines()
 *
 *  Devuelve el numero de lines que tiene el terminal.
*/

int num_lines()
{
	struct winsize w_size;
	char *env;
        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w_size) != -1)
        	{
                if (w_size.ws_row > 0)
                        return(w_size.ws_row);
		}	

        env=(char *)getenv("LINES");
        if(env)
                return(atoi(env));

return(20);
}

/*
 *
 * Leer la informacion de los threads
 *
*/

void read_thread_info(kvm_t *kvm_p,proc_t *pproc,int nthreads,struct thread_info *p_thread_info,int *nlwp_time)
{
int n;
int minx;
kthread_t pkthr_list;
klwp_t pklwp;
struct tms tms;
ulong clock_actual;
sobj_ops_t synobj;
turnstile_t ts;

if(kvm_read(kvm_p,(uintptr_t)pproc->p_tlist,&pkthr_list,sizeof(pkthr_list))<0)
	printf("\nkvm_read: Error leyendo p_tlist");

clock_actual=times(&tms);

for(n=0;n<6;n++)
	nlwp_time[n]=0;

for(n=0;n<nthreads;n++)
	{
        p_thread_info[n].t_tid=pkthr_list.t_tid;
        p_thread_info[n].t_disp_time=pkthr_list.t_disp_time;

        minx=((clock_actual-p_thread_info[n].t_disp_time)/CLK_TCK)/60;

        if(minx<1)
		nlwp_time[0]++;
        if((minx>=1)&&(minx<5))
                nlwp_time[1]++;
        if((minx>=5)&&(minx<30))
                nlwp_time[2]++;
        if((minx>=30)&&(minx<60))
                nlwp_time[3]++;
        if((minx>=60)&&(minx<300))
                nlwp_time[4]++;
        if(minx>=300)
                nlwp_time[5]++;

        p_thread_info[n].t_state=pkthr_list.t_state;
        p_thread_info[n].lc_wchan=pkthr_list.t_lwpchan.lc_wchan;
        p_thread_info[n].lc_wchan0=pkthr_list.t_lwpchan.lc_wchan0;

        if(kvm_read(kvm_p,(uintptr_t)pkthr_list.t_sobj_ops,&synobj,sizeof(synobj))<0)
                 printf("\nkvm_read: Error leyendo t_sobj_ops");

        p_thread_info[n].sobj_type=synobj.sobj_type;

        if(kvm_read(kvm_p,(uintptr_t)pkthr_list.t_ts,&ts,sizeof(ts))<0)
		printf("\nkvm_read: Error leyendo t_ts");

        p_thread_info[n].t_ts=pkthr_list.t_ts;
        p_thread_info[n].t_ts_waiters=ts.ts_waiters;

        if(kvm_read(kvm_p,(uintptr_t)pkthr_list.t_lwp,&pklwp,sizeof(pklwp))<0)
		printf("\nkvm_read: Error leyendo t_lwp");

        p_thread_info[n].t_sysnum=pkthr_list.t_sysnum;
        p_thread_info[n].lwp_arg[0]=pklwp.lwp_arg[0];
        p_thread_info[n].lwp_arg[1]=pklwp.lwp_arg[1];

        if(kvm_read(kvm_p,(uintptr_t)pkthr_list.t_forw,&pkthr_list,sizeof(pkthr_list))<0)
		printf("\nkvm_read: Error leyendo t_forw");
	}
return;
}

