// rpc tapset
// Copyright (C) 2006 IBM Corp.
// Copyright (C) 2007 Bull S.A.S
// Copyright (C) 2008, 2010-2014 Red Hat
//
// This file is part of systemtap, and is free software.  You can
// redistribute it and/or modify it under the terms of the GNU General
// Public License (GPL); either version 2, or (at your option) any
// later version.

%{
// Includes everything needed for __rpc_prot_from_protocol().
#include <linux/sunrpc/clnt.h>
%}

probe sunrpc.entry =
	sunrpc.clnt.entry,
	sunrpc.svc.entry,
	sunrpc.sched.entry
{}

probe sunrpc.return = 
	sunrpc.clnt.return,
	sunrpc.svc.return,
	sunrpc.sched.return
{}

/******************************************************************
 *                Probe points on RPC client functions            *  
 ******************************************************************/

probe sunrpc.clnt.entry = 
	sunrpc.clnt.create_client,
	sunrpc.clnt.clone_client,
	sunrpc.clnt.bind_new_program,
	sunrpc.clnt.shutdown_client,
	sunrpc.clnt.call_sync,
	sunrpc.clnt.call_async,
	sunrpc.clnt.restart_call
{}

probe sunrpc.clnt.return = 
	sunrpc.clnt.create_client.return,
	sunrpc.clnt.clone_client.return,
	sunrpc.clnt.bind_new_program.return,
	sunrpc.clnt.shutdown_client.return,
	sunrpc.clnt.call_sync.return,
	sunrpc.clnt.call_async.return,
	sunrpc.clnt.restart_call.return
{}

/*
 * Fires when an RPC client is to be created
 * 
 * kernels > 2.6.18
 *
 *  struct rpc_clnt *
 *  rpc_create(struct rpc_create_args *args)
 *	The rpc_create() function is used to create an RPC client.  It
 *	calculates some arguments (such as args->servername), then
 *	calls rpc_new_client().  We need the calculated arguments, so
 *	we'll probe rpc_new_client().  But, see discussion of
 *	sunrpc.clnt.create_client.return below.
 *
 *  static struct rpc_clnt *
 *  rpc_new_client(const struct rpc_create_args *args, struct rpc_xprt *xprt)
 *
 * kernels <= 2.6.18
 *
 *  static struct rpc_clnt * 
 *  rpc_new_client(struct rpc_xprt *xprt, char *servname, 
 *	struct rpc_program *program, u32 vers, 
 *	rpc_authflavor_t flavor)
 * 
 *  struct rpc_clnt * 
 *  rpc_create_client(struct rpc_xprt *xprt, char *servname, 
 *  	struct rpc_program *info, u32 version, 
 * 	rpc_authflavor_t authflavor)
 */

/**
 * probe sunrpc.clnt.create_client - Create an RPC client
 *
 *  @servername: the server machine name
 *  @progname:   the RPC program name
 *  @prog:       the RPC program number
 *  @vers:	 the RPC program version number
 *  @prot:	 the IP protocol number
 *  @port:	 the port number
 *  @authflavor: the authentication flavor
 */
probe sunrpc.clnt.create_client =
	_sunrpc.clnt.create_client.rpc_new_client_inline !,
	_sunrpc.clnt.create_client.rpc_new_client ?,
	_sunrpc.clnt.create_client.rpc_create_client ?
{
	name = "sunrpc.clnt.create_client"
	argstr = sprintf("%s %s %d %d %d %d %d", servername, progname, 
		prog, vers, prot, port, authflavor)
}

/*
 * Newer kernels (> 2.6.18) compiled with gcc less than version
 * 4.4.3-16, tend to have debuginfo that doesn't include location
 * information for inline function arguments.  This is a problem, since
 * we need the arguments to rpc_new_client(), which is inline.  Since
 * we can't get them for those kernels, we stash (and delete) the value
 * of the 'args' parameter of rpc_create().  We then use this value
 * when probing the inline version of rpc_new_client() (if we don't
 * have the real arguments to that inline function).
 */

@__private30 global __rpc_create_args

probe kernel.function("rpc_create").call !,
	module("sunrpc").function("rpc_create").call ?
{
	__rpc_create_args[tid()] = $args
}
	
probe kernel.function("rpc_create").return !,
	module("sunrpc").function("rpc_create").return ?
{
	delete __rpc_create_args[tid()]
}


/*
 * This function could be written in script language, but we'd need
 * embedded-C functions for XPRT_TRANSPORT_{UDP,TCP,BC_TCP} and
 * IPPROTO_{UDP,TCP}.  So, do it all in embedded-C.
 */

@__private30 function __rpc_prot_from_protocol:long(protocol:long)
%{
	switch (STAP_ARG_protocol) {
#ifdef XPRT_TRANSPORT_BC
	case XPRT_TRANSPORT_UDP:
		STAP_RETVALUE = IPPROTO_UDP;
		break;
	case XPRT_TRANSPORT_TCP:
	case XPRT_TRANSPORT_BC_TCP:
		STAP_RETVALUE = IPPROTO_TCP;
		break;
#endif
	default:
		STAP_RETVALUE = -1;
		break;
	}
%}

/*
 * The probe for the inline version of "rpc_new_client" (kernels >
 * 2.6.18) and the non-inline version of "rpc_new_client" (kernels <=
 * 2.6.18) could be combined.  However, the optimizer isn't smart
 * enough to optimize away the '__rpc_create_args' global then.
 */

probe _sunrpc.clnt.create_client.rpc_new_client_inline =
	kernel.function("rpc_new_client").inline !,
	module("sunrpc").function("rpc_new_client").inline
{
	if (@defined($args)) {		# kernel > 2.6.18 (with good debuginfo)
%(systemtap_v <= "1.7" %?
		args = $args
%)
		__args = $args
		servername = __rpc_format_servername($args->servername,
						     $args->address)
		progname = kernel_string($args->program->name)
		prog = $args->prognumber
		vers = vers_from_prog($args->program, $args->version)
		authflavor = $args->authflavor

		prot = $xprt->prot
		port = @choose_defined($xprt->port, port_from_xprt($xprt))
	}
	else {				# kernel > 2.6.18 (with bad debuginfo)
%(systemtap_v <= "1.7" %?
		args = __rpc_create_args[tid()]
%)
		__args = __rpc_create_args[tid()]
		servername = __rpc_format_servername(@cast(__args, "rpc_create_args", "kernel:sunrpc")->servername,
						     @cast(__args, "rpc_create_args", "kernel:sunrpc")->address)

		progname = kernel_string(@cast(__args, "rpc_create_args", "kernel:sunrpc")->program->name)
		prog = @cast(__args, "rpc_create_args", "kernel:sunrpc")->prognumber
		vers = vers_from_prog(@cast(__args, "rpc_create_args", "kernel:sunrpc")->program, @cast(__args, "rpc_create_args", "kernel:sunrpc")->version)
		authflavor = @cast(__args, "rpc_create_args", "kernel:sunrpc")->authflavor

		prot = __rpc_prot_from_protocol(@cast(__args, "rpc_create_args", "kernel:sunrpc")->protocol)
		# Since we can't find $xprt, we can't know the port
		port = -1
	}
}

probe _sunrpc.clnt.create_client.rpc_new_client =
	kernel.function("rpc_new_client").call !,
	module("sunrpc").function("rpc_new_client").call ?
{
	if (@defined($args)) {
		# kernel > 2.6.18
		servername = __rpc_format_servername($args->servername,
						     $args->address)
		progname = kernel_string($args->program->name)
		prog = $args->prognumber
		vers = vers_from_prog($args->program, $args->version)
		authflavor = $args->authflavor
	}
	else {
		# kernel <= 2.6.18
		servername = __rpc_format_servername($servname, &$xprt->addr)
		progname = kernel_string($program->name)
		prog = $program->number
		vers = vers_from_prog($program, $vers)
		authflavor = $flavor
	}

	prot = $xprt->prot
	port = @choose_defined($xprt->port, port_from_xprt($xprt))
}

probe _sunrpc.clnt.create_client.rpc_create_client =
	kernel.function("rpc_create_client") !,
	module("sunrpc").function("rpc_create_client") ?
{
	servername = __rpc_format_servername($servname, &$xprt->addr)
	if (@defined($info)) {
		progname = kernel_string($info->name)
		prog = $info->number
		vers = vers_from_prog($info, $version)
		authflavor = $authflavor
	}
	else {
		progname = kernel_string($program->name)
		prog = $program->number
		vers = vers_from_prog($program, $vers)
		authflavor = $flavor
	}

	prot = $xprt->prot
	port = @choose_defined($xprt->port, port_from_xprt($xprt))
}

/*
 * On newer kernels, we want to probe the return of rpc_new_client()
 * here.  But because of problems with return probes on inline
 * functions (PR 4413), we have to probe the return of rpc_create()
 * instead.  So, if we find the return of rpc_create() (the only
 * caller of rpc_new_client()), we're done.
 */
probe sunrpc.clnt.create_client.return =
	_sunrpc.clnt.create_client.return.rpc_create !,
	_sunrpc.clnt.create_client.return.rpc_new_client ?,
	_sunrpc.clnt.create_client.return.rpc_create_client ?
{
	name = "sunrpc.clnt.create_client.return"
	retstr = return_str(2, $return)
}

probe _sunrpc.clnt.create_client.return.rpc_create =
	kernel.function("rpc_create").return !,
	module("sunrpc").function("rpc_create").return
{
}

probe _sunrpc.clnt.create_client.return.rpc_new_client =
	kernel.function("rpc_new_client").return !,
	module("sunrpc").function("rpc_new_client").return ?
{
}

probe _sunrpc.clnt.create_client.return.rpc_create_client = 
	kernel.function("rpc_create_client").return !,
	module("sunrpc").function("rpc_create_client").return ?
{
}

/**
 * probe sunrpc.clnt.clone_client - Clone an RPC client structure
 *
 *  @servername: the server machine name
 *  @progname:   the RPC program name
 *  @prog:       the RPC program number
 *  @vers:	 the RPC program version number
 *  @prot:	 the IP protocol number
 *  @port:	 the port number
 *  @authflavor: the authentication flavor
 */
probe sunrpc.clnt.clone_client = kernel.function("rpc_clone_client") !,
      	module("sunrpc").function("rpc_clone_client")
{
	if (@defined($clnt->cl_server))
		servername = __rpc_format_servername($clnt->cl_server,
						     &$clnt->cl_xprt->addr)
	else {
		__xprt = rcu_dereference($clnt->cl_xprt)
		servername = __rpc_format_servername(@cast(__xprt, "rpc_xprt")->servername,
						     &@cast(__xprt, "rpc_xprt")->addr)
	}
	progname = kernel_string(@choose_defined($clnt->cl_program->name,
						 $clnt->cl_protname))
	prog = prog_from_clnt($clnt)
	vers = vers_from_clnt($clnt)
	prot = prot_from_clnt($clnt)
	port = port_from_clnt($clnt)
	authflavor = $clnt->cl_auth->au_flavor
	
	name = "sunrpc.clnt.clone_client"
	argstr = sprintf("%s %s %d %d %d %d %d", servername, progname, 
			prog, vers, prot, port, authflavor)
}

probe sunrpc.clnt.clone_client.return = 
	kernel.function("rpc_clone_client").return !,
      	module("sunrpc").function("rpc_clone_client").return
{
	name = "sunrpc.clnt.clone_client.return"
	retstr = return_str(2, $return)
}

/**
 * probe sunrpc.clnt.shutdown_client - Shutdown an RPC client
 *
 *  @servername: the server machine name
 *  @progname:   the RPC program name
 *  @prog:       the RPC program number
 *  @vers:	 the RPC program version number
 *  @prot:	 the IP protocol number
 *  @port:	 the port number
 *  @authflavor: the authentication flavor
 *  @clones:     the number of clones
 *  @tasks:      the number of references
 *  @netreconn:  the count of reconnections
 *  @rpccnt:     the count of RPC calls
 *  @om_ops:      the count of operations
 *  @om_ntrans:   the count of RPC transmissions
 *  @om_bytes_sent: the count of bytes out
 *  @om_bytes_recv: the count of bytes in
 *  @om_queue:    the jiffies queued for xmit
 *  @om_rtt:      the RPC RTT jiffies
 *  @om_execute:  the RPC execution jiffies
 */
probe sunrpc.clnt.shutdown_client = kernel.function("rpc_shutdown_client") !,
      	module("sunrpc").function("rpc_shutdown_client")
{
	if (@defined($clnt->cl_server))
		servername = __rpc_format_servername($clnt->cl_server,
						     &$clnt->cl_xprt->addr)
	else {
		__xprt = rcu_dereference($clnt->cl_xprt)
		servername = __rpc_format_servername(@cast(__xprt, "rpc_xprt")->servername,
						     &@cast(__xprt, "rpc_xprt")->addr)
	}
	progname = kernel_string(@choose_defined($clnt->cl_program->name,
						 $clnt->cl_protname))
	prog = prog_from_clnt($clnt)
	vers = vers_from_clnt($clnt)
	prot = prot_from_clnt($clnt)
	port = port_from_clnt($clnt)
	authflavor = $clnt->cl_auth->au_flavor
	clones = clones_from_clnt($clnt)
	tasks = tasks_from_clnt($clnt)

	/* per-program statistics */
	netreconn = $clnt->cl_stats->netreconn
	rpccnt = $clnt->cl_stats->rpccnt

	/* per-client statistics */
	if (@defined($clnt->cl_metrics)) {
		om_ops = $clnt->cl_metrics->om_ops
		om_ntrans = $clnt->cl_metrics->om_ntrans
		om_bytes_sent = $clnt->cl_metrics->om_bytes_sent
		om_bytes_recv = $clnt->cl_metrics->om_bytes_recv
		if (@defined($clnt->cl_metrics->om_queue->tv64)) {
  		      om_queue = $clnt->cl_metrics->om_queue->tv64
		      om_rtt = $clnt->cl_metrics->om_rtt->tv64
		      om_execute = $clnt->cl_metrics->om_execute->tv64
		}
		else
		{
  		      om_queue = $clnt->cl_metrics->om_queue
		      om_rtt = $clnt->cl_metrics->om_rtt
		      om_execute = $clnt->cl_metrics->om_execute
		}
	}
	else {
		om_ops = -1
		om_ntrans = -1
		om_bytes_sent = -1
		om_bytes_recv = -1
		om_queue = -1
		om_rtt = -1
		om_execute = -1
	}

	name = "sunrpc.clnt.shutdown_client"
	argstr = sprintf("%s %s %d %d %d %d %d %d %d", servername, progname, 
			prog, vers, prot, port, authflavor, clones, tasks)
}

probe sunrpc.clnt.shutdown_client.return = 
	kernel.function("rpc_shutdown_client").return !,
      	module("sunrpc").function("rpc_shutdown_client").return
{
	name = "sunrpc.clnt.shutdown_client.return"
	retstr = (@defined($return) ? return_str(1, $return) : "N/A")
}

/**
 * probe sunrpc.clnt.bind_new_program - Bind a new RPC program to an existing client
 *
 *  @servername:     the server machine name
 *  @old_progname:   the name of old RPC program
 *  @old_prog:       the number of old RPC program
 *  @old_vers:	     the version of old RPC program
 *  @progname:       the name of new RPC program
 *  @prog:           the number of new RPC program
 *  @vers:	     the version of new RPC program
 */
probe sunrpc.clnt.bind_new_program = 
	kernel.function("rpc_bind_new_program") !,
      	module("sunrpc").function("rpc_bind_new_program")
{
	if (@defined($old->cl_server))
		servername = __rpc_format_servername($old->cl_server,
						     &$old->cl_xprt->addr)
	else {
		__xprt = rcu_dereference($old->cl_xprt)
		servername = __rpc_format_servername(@cast(__xprt, "rpc_xprt")->servername,
						     &@cast(__xprt, "rpc_xprt")->addr)
	}
	old_progname = kernel_string(@choose_defined($old->cl_program->name,
						     $old->cl_protname))
	old_prog = prog_from_clnt($old)
	old_vers = vers_from_clnt($old)
	progname = kernel_string($program->name)
	prog = $program->number
	vers = vers_from_prog($program, $vers)

	name = "sunrpc.clnt.bind_new_program"
	argstr = sprintf("%s %s %d %s %d", servername, old_progname, 
			old_vers, progname, vers)
}

probe sunrpc.clnt.bind_new_program.return = 
	kernel.function("rpc_bind_new_program").return !,
      	module("sunrpc").function("rpc_bind_new_program").return
{
	name = "sunrpc.clnt.bind_new_program.return"
	retstr = return_str(2, $return)
}

/**
 * probe sunrpc.clnt.call_sync - Make a synchronous RPC call
 *
 *  @servername: the server machine name
 *  @progname:   the RPC program name
 *  @prog:       the RPC program number
 *  @vers:	 the RPC program version number
 *  @prot:	 the IP protocol number
 *  @port:	 the port number
 *  @xid:        current transmission id
 *  @dead:       whether this client is abandoned
 *  @procname:   the procedure name in this RPC call
 *  @proc:       the procedure number in this RPC call
 *  @flags:      flags
 */
probe sunrpc.clnt.call_sync = kernel.function("rpc_call_sync") !,
      	module("sunrpc").function("rpc_call_sync")
{
	if (@defined($clnt->cl_server))
		servername = __rpc_format_servername($clnt->cl_server,
						     &$clnt->cl_xprt->addr)
	else {
		__xprt = rcu_dereference($clnt->cl_xprt)
		servername = __rpc_format_servername(@cast(__xprt, "rpc_xprt")->servername,
						     &@cast(__xprt, "rpc_xprt")->addr)
	}
	progname = kernel_string(@choose_defined($clnt->cl_program->name,
						 $clnt->cl_protname))
	prog = prog_from_clnt($clnt)
	vers = vers_from_clnt($clnt)
	prot = prot_from_clnt($clnt)
	port = port_from_clnt($clnt)
	xid  = xid_from_clnt($clnt)
	dead = @choose_defined($clnt->cl_dead,
		atomic_read(@choose_defined(&$clnt->cl_kref->refcount,
					    &$clnt->cl_count)) == 0)

	proc = proc_from_msg($msg)
	procname = kernel_string(@choose_defined($msg->rpc_proc->p_name, 0), "NULL")
	flags = $flags
	
	name = "sunrpc.clnt.call_sync"
	argstr = sprintf("%s %d %s %d %s %d", servername, xid, progname, 
			vers, procname, flags)
}

probe sunrpc.clnt.call_sync.return = kernel.function("rpc_call_sync").return !,
      	module("sunrpc").function("rpc_call_sync").return
{
	name = "sunrpc.clnt.call_sync.return"
	retstr = return_str(1, $return)
}

/**
 * probe sunrpc.clnt.call_async - Make an asynchronous RPC call
 *
 *  @servername: the server machine name
 *  @progname:   the RPC program name
 *  @prog:       the RPC program number
 *  @vers:	 the RPC program version number
 *  @prot:	 the IP protocol number
 *  @port:	 the port number
 *  @xid:        current transmission id
 *  @dead:       whether this client is abandoned
 *  @procname:   the procedure name in this RPC call
 *  @proc:       the procedure number in this RPC call
 *  @flags:      flags
 */
probe sunrpc.clnt.call_async = kernel.function("rpc_call_async") !,
      	module("sunrpc").function("rpc_call_async")
{
	if (@defined($clnt->cl_server))
		servername = __rpc_format_servername($clnt->cl_server,
						     &$clnt->cl_xprt->addr)
	else {
		__xprt = rcu_dereference($clnt->cl_xprt)
		servername = __rpc_format_servername(@cast(__xprt, "rpc_xprt")->servername,
						     &@cast(__xprt, "rpc_xprt")->addr)
	}
	progname = kernel_string(@choose_defined($clnt->cl_program->name,
						 $clnt->cl_protname))
	prog = prog_from_clnt($clnt)
	vers = vers_from_clnt($clnt)
	prot = prot_from_clnt($clnt)
	port = port_from_clnt($clnt)
	xid  = xid_from_clnt($clnt)
	dead = @choose_defined($clnt->cl_dead,
		atomic_read(@choose_defined(&$clnt->cl_kref->refcount,
					    &$clnt->cl_count)) == 0)

	proc = proc_from_msg($msg)
	procname = kernel_string(@choose_defined($msg->rpc_proc->p_name, 0), "NULL")
	flags = $flags
	
	name = "sunrpc.clnt.call_async"
	argstr = sprintf("%s %d %s %d %s %d", servername, xid, progname, 
			vers, procname, flags)
}

probe sunrpc.clnt.call_async.return = 
	kernel.function("rpc_call_async").return !,
      	module("sunrpc").function("rpc_call_async").return
{
	name = "sunrpc.clnt.call_async.return"
	retstr = return_str(1, $return)
}

/**
 * probe sunrpc.clnt.restart_call - Restart an asynchronous RPC call
 *
 * @servername:   the server machine name
 * @prog:         the RPC program number
 * @xid:          the transmission id
 * @tk_pid:	  the debugging aid of task
 * @tk_flags:     the task flags
 * @tk_priority:  the task priority
 * @tk_runstate:  the task run status
 */
probe sunrpc.clnt.restart_call = kernel.function("rpc_restart_call") !,
      	module("sunrpc").function("rpc_restart_call")
{
	if (@defined($task->tk_client->cl_server))
		servername = __rpc_format_servername($task->tk_client->cl_server,
						     &$task->tk_client->cl_xprt->addr)
	else {
		__xprt = rcu_dereference($task->tk_client->cl_xprt)
		servername = __rpc_format_servername(@cast(__xprt, "rpc_xprt")->servername,
						     &@cast(__xprt, "rpc_xprt")->addr)
	}
	prog = prog_from_clnt($task->tk_client)
	xid = $task->tk_rqstp->rq_xid
	tk_pid = $task->tk_pid
	tk_flags = $task->tk_flags
	tk_priority = $task->tk_priority
	tk_runstate = $task->tk_runstate

	name = "sunrpc.clnt.restart_call"
	argstr = sprintf("%s %d %d %d %d %d %d", servername, prog, xid, tk_pid, 
			tk_flags, tk_priority, tk_runstate)
}

probe sunrpc.clnt.restart_call.return = 
	kernel.function("rpc_restart_call").return !,
      	module("sunrpc").function("rpc_restart_call").return
{
	name = "sunrpc.clnt.restart_call.return"
	retstr = "N/A"
}

/*********************************************
 *    Probe points on RPC server interface   *
 ********************************************/
probe sunrpc.svc.entry =
	sunrpc.svc.register,
	sunrpc.svc.create,
	sunrpc.svc.destroy,
	sunrpc.svc.process,
	sunrpc.svc.authorise,
	sunrpc.svc.recv,
	sunrpc.svc.send,
	sunrpc.svc.drop
{}

probe sunrpc.svc.return =
	sunrpc.svc.register.return,
	sunrpc.svc.create.return,
	sunrpc.svc.destroy.return,
	sunrpc.svc.process.return,
	sunrpc.svc.authorise.return,
	sunrpc.svc.recv.return,
	sunrpc.svc.send.return,
	sunrpc.svc.drop.return
{}

/**
 * probe sunrpc.svc.register - Register an RPC service with the local portmapper
 *
 * @sv_name:     the service name
 * @progname:    the name of the program
 * @prog:        the number of the program
 * @prot:	 the IP protocol number
 * @port:	 the port number
 *
 * If @proto and @port are both 0, then unregister a service.
 */
probe sunrpc.svc.register = kernel.function("svc_register") !,
      	module("sunrpc").function("svc_register")
{
	sv_name = kernel_string($serv->sv_name)
	progname = kernel_string($serv->sv_program->pg_name)
	prog = $serv->sv_program->pg_prog
	prot = $proto
	port = $port

	name = "sunrpc.svc.register"
	argstr = sprintf("%s %s %d %d", sv_name, progname, prot, port)
}

probe sunrpc.svc.register.return = kernel.function("svc_register").return !,
      	module("sunrpc").function("svc_register").return
{
	name = "sunrpc.svc.register.return"
	retstr = return_str(1, $return)
}

/**
 * probe sunrpc.svc.create - Create an RPC service
 *
 * @progname:    the name of the program
 * @prog:        the number of the program
 * @pg_nvers:	 the number of supported versions
 * @bufsize:     the buffer size
 */
probe sunrpc.svc.create = kernel.function("svc_create") !,
      	module("sunrpc").function("svc_create")
{
	progname = kernel_string($prog->pg_name)
	prog = $prog->pg_prog
	pg_nvers  = $prog->pg_nvers
	bufsize = $bufsize
	
	name = "sunrpc.svc.create"
	argstr = sprintf("%s %d %d %d", progname, prog, pg_nvers, bufsize)
}

probe sunrpc.svc.create.return = kernel.function("svc_create").return !,
      	module("sunrpc").function("svc_create").return
{
	name = "sunrpc.svc.create.return"
	retstr = return_str(2, $return)
}

/**
 * probe sunrpc.svc.destroy - Destroy an RPC service
 *
 * @sv_name:     the service name
 * @sv_progname: the name of the program
 * @sv_prog:     the number of the program
 * @sv_nrthreads:the number of concurrent threads
 * @netcnt:      the count of received RPC requests
 * @nettcpconn:  the count of accepted TCP connections
 * @rpccnt:      the count of valid RPC requests
 * @rpcbadfmt:   the count of requests dropped for bad formats
 * @rpcbadauth:  the count of requests drooped for authentication failure
 */
probe sunrpc.svc.destroy = kernel.function("svc_destroy") !,
      	module("sunrpc").function("svc_destroy")
{
	sv_name = kernel_string($serv->sv_name) /* service name */
	sv_progname = kernel_string($serv->sv_program->pg_name)
	sv_prog = $serv->sv_program->pg_prog
	sv_nrthreads = $serv->sv_nrthreads

	/* RPC statistics */
	netcnt = $serv->sv_stats->netcnt
	netcpconn = $serv->sv_stats->nettcpconn
	rpccnt = $serv->sv_stats->rpccnt
	rpcbadfmt = $serv->sv_stats->rpcbadfmt
	rpcbadauth = $serv->sv_stats->rpcbadauth
	
	name = "sunrpc.svc.destroy"
	argstr = sprintf("%s %d %d", sv_name, sv_prog, sv_nrthreads)
}

probe sunrpc.svc.destroy.return = kernel.function("svc_destroy").return !,
      	module("sunrpc").function("svc_destroy").return
{
	name = "sunrpc.svc.destroy.return"
	retstr = "N/A"
}

/**
 * probe sunrpc.svc.process - Process an RPC request
 *
 * @sv_name:     the service name
 * @sv_prog:     the number of the program
 * @sv_nrthreads:the number of concurrent threads
 * @peer_ip:     the peer address where the request is from
 * @rq_xid:      the transmission id in the request
 * @rq_prog:     the program number in the request
 * @rq_vers:     the program version in the request
 * @rq_proc:     the procedure number in the request
 * @rq_prot:     the IP protocol of the reqeust
 */
probe sunrpc.svc.process = kernel.function("svc_process") !,
      	module("sunrpc").function("svc_process")
{
	if (! @defined($serv)) {
		sv_name = kernel_string($rqstp->rq_server->sv_name)
		sv_prog = $rqstp->rq_server->sv_program->pg_prog
		sv_nrthreads = $rqstp->rq_server->sv_nrthreads
	}
	else {
		sv_name = kernel_string($serv->sv_name) /* service name */
		sv_prog = $serv->sv_program->pg_prog
		sv_nrthreads = $serv->sv_nrthreads
	}
	peer_ip = addr_from_rqst($rqstp)
	rq_xid = $rqstp->rq_xid
	rq_prog = $rqstp->rq_prog
	rq_vers = $rqstp->rq_vers
	rq_proc = $rqstp->rq_proc
	rq_prot = $rqstp->rq_prot
	
	name = "sunrpc.svc.process"
	argstr = sprintf("%s %d %d %d %d %d %d", sv_name, sv_prog, peer_ip,
			rq_xid, rq_prog, rq_vers, rq_proc)
}

probe sunrpc.svc.process.return = kernel.function("svc_process").return !,
      	module("sunrpc").function("svc_process").return
{
	name = "sunrpc.svc.process.return"
	retstr = return_str(1, $return)
}

/* 
 * probe sunrpc.svc.authorise - An RPC request is to be authorised
 *
 * @sv_name:     the service name
 * @peer_ip:     the peer address where the request is from
 * @rq_xid:      the transmission id in the request
 * @rq_prog:     the program number in the request
 * @rq_vers:     the program version in the request
 * @rq_proc:     the procedure number in the request
 * @rq_prot:     the IP protocol of the reqeust
 */
probe sunrpc.svc.authorise = kernel.function("svc_authorise") !,
      	module("sunrpc").function("svc_authorise")
{
	sv_name = kernel_string($rqstp->rq_server->sv_name)
	peer_ip = addr_from_rqst($rqstp)
	rq_xid  = $rqstp->rq_xid
	rq_prog = $rqstp->rq_prog
	rq_vers = $rqstp->rq_vers
	rq_proc = $rqstp->rq_proc
	rq_prot = $rqstp->rq_prot

	name = "sunrpc.svc.authorise"
	argstr = sprintf("%d %d %d %d %d %d", peer_ip, rq_xid, rq_prog, 
			rq_vers, rq_proc, rq_prot)
}

probe sunrpc.svc.authorise.return = kernel.function("svc_authorise").return !,
      	module("sunrpc").function("svc_authorise").return
{
	name = "sunrpc.svc.authorise.return"
	retstr = return_str(1, $return)
}

/**
 * probe sunrpc.svc.recv - Listen for the next RPC request on any socket
 *
 * @sv_name:     the service name
 * @sv_prog:     the number of the program
 * @sv_nrthreads:the number of concurrent threads
 * @timeout:     the timeout of waiting for data
 */
probe sunrpc.svc.recv = kernel.function("svc_recv") !,
      	module("sunrpc").function("svc_recv")
{
	if (! @defined($serv)) {
	        sv_name = kernel_string($rqstp->rq_server->sv_name) 
        	sv_prog = $rqstp->rq_server->sv_program->pg_prog
	        sv_nrthreads = $rqstp->rq_server->sv_nrthreads
	}
	else {
		sv_name = kernel_string($serv->sv_name)	
		sv_prog = $serv->sv_program->pg_prog
		sv_nrthreads = $serv->sv_nrthreads
	}
	timeout = $timeout

	name = "sunrpc.svc.recv"
	argstr = sprintf("%s %d", sv_name, timeout)
}

probe sunrpc.svc.recv.return = kernel.function("svc_recv").return !,
      	module("sunrpc").function("svc_recv").return
{
	name = "sunrpc.svc.recv.return"
	retstr = return_str(1, $return)
}

/**
 * probe sunrpc.svc.send  - Return reply to RPC client
 *
 * @sv_name:     the service name
 * @peer_ip:     the peer address where the request is from
 * @rq_xid:      the transmission id in the request
 * @rq_prog:     the program number in the request
 * @rq_vers:     the program version in the request
 * @rq_proc:     the procedure number in the request
 * @rq_prot:     the IP protocol of the reqeust
 */
probe sunrpc.svc.send = kernel.function("svc_send") !,
      	module("sunrpc").function("svc_send")
{
	sv_name = kernel_string($rqstp->rq_server->sv_name)
	peer_ip = addr_from_rqst($rqstp)
	rq_xid  = $rqstp->rq_xid
	rq_prog = $rqstp->rq_prog
	rq_vers = $rqstp->rq_vers
	rq_proc = $rqstp->rq_proc
	rq_prot = $rqstp->rq_prot
	
	name = "sunrpc.svc.send"
	argstr = sprintf("%s %d %d %d %d %d %d", sv_name, peer_ip, 
			rq_xid, rq_prog, rq_vers, rq_proc, rq_prot)
}

probe sunrpc.svc.send.return = kernel.function("svc_send").return !,
      	module("sunrpc").function("svc_send").return
{
	name = "sunrpc.svc.send.return"
	retstr = return_str(1, $return)
}

/**
 * probe sunrpc.svc.drop - Drop RPC request
 *
 * @sv_name:     the service name
 * @peer_ip:     the peer address where the request is from
 * @rq_xid:      the transmission id in the request
 * @rq_prog:     the program number in the request
 * @rq_vers:     the program version in the request
 * @rq_proc:     the procedure number in the request
 * @rq_prot:     the IP protocol of the reqeust
 */
probe sunrpc.svc.drop = kernel.function("svc_drop") !,
      	module("sunrpc").function("svc_drop")
{
	sv_name = kernel_string($rqstp->rq_server->sv_name)
	peer_ip = addr_from_rqst($rqstp)
	rq_xid  = $rqstp->rq_xid
	rq_prog = $rqstp->rq_prog
	rq_vers = $rqstp->rq_vers
	rq_proc = $rqstp->rq_proc
	rq_prot = $rqstp->rq_prot
	
	name = "sunrpc.svc.drop"
	argstr = sprintf("%s %d %d %d %d %d %d", sv_name, peer_ip, 
			rq_xid, rq_prog, rq_vers, rq_proc, rq_prot)
}

probe sunrpc.svc.drop.return = kernel.function("svc_drop").return !,
      	module("sunrpc").function("svc_drop").return
{
	name = "sunrpc.svc.drop.return"
	retstr = "N/A"
}

/*******************************************************************
 *                  Probe points on RPC scheduler                  *
 ******************************************************************/
probe sunrpc.sched.entry =
	sunrpc.sched.new_task,
	sunrpc.sched.release_task ?,
	sunrpc.sched.execute,
	sunrpc.sched.delay
{}

probe sunrpc.sched.return =
	sunrpc.sched.new_task.return,
	sunrpc.sched.release_task.return ?,
	sunrpc.sched.execute.return,
	sunrpc.sched.delay.return
{}

/**
 * probe sunrpc.sched.new_task - Create new task for the specified client
 *
 * @xid:         the transmission id in the RPC call
 * @prog:        the program number in the RPC call
 * @vers:        the program version in the RPC call
 * @prot:        the IP protocol in the RPC call
 * @tk_flags:    the flags of the task
 */
probe sunrpc.sched.new_task = kernel.function("rpc_new_task") !,
      	module("sunrpc").function("rpc_new_task")
{
	if (@defined($setup_data)) {
		xid = xid_from_clnt($setup_data->rpc_client)
		prog = prog_from_clnt($setup_data->rpc_client)
		vers = vers_from_clnt($setup_data->rpc_client)
		prot = prot_from_clnt($setup_data->rpc_client)
		flags = $setup_data->flags
	}
	else {
		xid = xid_from_clnt($clnt)
		prog = prog_from_clnt($clnt)
		vers = vers_from_clnt($clnt)
		prot = prot_from_clnt($clnt)
		flags = $flags
	}

	name = "sunrpc.sched.new_task"
	argstr = sprintf("%d %d %d %d %d", xid, prog, vers, prot, flags)
}

probe sunrpc.sched.new_task.return = kernel.function("rpc_new_task").return !,
      	module("sunrpc").function("rpc_new_task").return
{
	name = "sunrpc.sched.new_task.return"
	retstr = return_str(2, $return)
}

/**
 * probe sunrpc.sched.release_task - Release all resources associated with a task
 *
 * @xid:         the transmission id in the RPC call
 * @prog:        the program number in the RPC call
 * @vers:        the program version in the RPC call
 * @prot:        the IP protocol in the RPC call
 * @tk_flags:    the flags of the task
 *
 * rpc_release_task() function might not be found for a particular kernel.
 *  So, if we can't find it, just return '-1' for everything.
 */
/*
 * The '.call' here is so that we're sure to grab the non-inline
 * version of rpc_release_task() (assuming it exists).  We can't find
 * the return of rpc_release_task() if it is inline (PR 4413).
 */
probe sunrpc.sched.release_task = kernel.function("rpc_release_task").call !,
      	module("sunrpc").function("rpc_release_task").call !,
	never
{
	if (@defined($task)) {
		xid = xid_from_clnt($task->tk_client)
		prog = prog_from_clnt($task->tk_client)
		vers = vers_from_clnt($task->tk_client)
		prot = prot_from_clnt($task->tk_client)
		tk_flags  = $task->tk_flags
	}
	else {
		xid = -1
		prog = -1
		vers = -1
		prot = -1
		tk_flags  = -1
	}
	name = "sunrpc.sched.release_task"
	argstr = sprintf("%d %d %d %d %d", xid, prog, vers, prot, tk_flags)
}

probe sunrpc.sched.release_task.return = 
	kernel.function("rpc_release_task").return !,
      	module("sunrpc").function("rpc_release_task").return ?
{
	name = "sunrpc.sched.release_task.return"
	retstr = "N/A"
}

/**
 * probe sunrpc.sched.execute - Execute the RPC `scheduler' 
 *
 * @xid:         the transmission id in the RPC call
 * @prog:        the program number in the RPC call
 * @vers:        the program version in the RPC call
 * @prot:        the IP protocol in the RPC call
 * @tk_pid:      the debugging id of the task
 * @tk_flags:    the flags of the task
 */
probe sunrpc.sched.execute = kernel.function("__rpc_execute") !,
      	module("sunrpc").function("__rpc_execute")
{
	xid = xid_from_clnt($task->tk_client)
	prog = prog_from_clnt($task->tk_client)
	vers = vers_from_clnt($task->tk_client)
	prot = prot_from_clnt($task->tk_client)
	tk_pid = $task->tk_pid
	tk_flags = $task->tk_flags

	name = "sunrpc.sched.execute"
	argstr = sprintf("%d %d %d %d %d %d", xid, prog, vers, prot, 
			tk_pid, tk_flags)
}

probe sunrpc.sched.execute.return = kernel.function("__rpc_execute").return !,
      	module("sunrpc").function("__rpc_execute").return
{
	name = "sunrpc.sched.execute.return"

	# On kernels > 2.6.20, __rpc_execute() is a void function.
	if (@defined($return)) {
		retstr = return_str(1, $return)
	}
	else {
		retstr = "N/A"
	}
}

/**
 * probe sunrpc.sched.delay - Delay an RPC task
 *
 * @xid:         the transmission id in the RPC call
 * @prog:        the program number in the RPC call
 * @vers:        the program version in the RPC call
 * @prot:        the IP protocol in the RPC call
 * @tk_pid:      the debugging id of the task
 * @tk_flags:    the flags of the task
 * @delay:       the time delayed
 */
probe sunrpc.sched.delay = kernel.function("rpc_delay") !,
      module("sunrpc").function("rpc_delay") 
{
	xid = xid_from_clnt($task->tk_client)
	prog = prog_from_clnt($task->tk_client)
	vers = vers_from_clnt($task->tk_client)
	prot = prot_from_clnt($task->tk_client)
	tk_pid = $task->tk_pid
	tk_flags = $task->tk_flags
	delay = $delay

	name = "sunrpc.sched.delay"
	argstr = sprintf("%d %d %d %d %d %d %d", xid, prog, vers, 
			prot, tk_pid, tk_flags, delay)
}

probe sunrpc.sched.delay.return = kernel.function("rpc_delay").return !,
      module("sunrpc").function("rpc_delay").return
{
	name = "sunrpc.sched.delay.return"
	retstr = "N/A"
}

/******************************************************************
 *                       Helper functions                        *
 *****************************************************************/

function xid_from_clnt:long(clnt:long)
{
	if (clnt == 0)
		return 0
	return @cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_xprt->xid
}

function prog_from_clnt:long(clnt:long)
{
	if (clnt == 0)
		return 0
	return @choose_defined(@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_prog,
		@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_pmap->pm_prog)
}

function vers_from_clnt:long(clnt:long)
{
	if (clnt == 0)
		return 0
	return @choose_defined(@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_vers,
		@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_pmap->pm_vers)
}

function prot_from_clnt:long(clnt:long)
{
	if (clnt == 0)
		return 0
	return @cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_xprt->prot
}

function port_from_xprt:long(cl_xprt:long)
{
	if (cl_xprt == 0)
		return 0

	addr = &@cast(cl_xprt, "rpc_xprt", "kernel:sunrpc")->addr
	if (addr == 0)
		return 0

	/* In reality, 'cl_xprt->addr' is of 'sockaddr_storage' type
	 * (since 2.6.19). But when used, you cast it to what is
	 * inside that buffer. The 'struct sockaddr', 'struct
	 * sockaddr_in', and 'sockaddr_in6' structures all have a
	 * family type as their 1st member. */
	if (@cast(addr, "sockaddr")->sa_family
		== @const("AF_INET")) {
		return ntohs(@cast(addr, "sockaddr_in")->sin_port)
	}
	else if (@cast(addr, "sockaddr")->sa_family
		 == @const("AF_INET6")) {
		return ntohs(@cast(addr, "sockaddr_in6")->sin6_port)
	}
	return 0
}

function port_from_clnt:long(clnt:long)
{
	if (clnt == 0)
		return 0

	cl_xprt = @cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_xprt
	return port_from_xprt(cl_xprt)
}

function clones_from_clnt:long(clnt:long)
{
    return (@defined(@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_count)
	    ? atomic_read(&@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_count)
	    : -1)
}

function tasks_from_clnt:long(clnt:long)
{
    return (@defined(@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_users)
	    ? atomic_read(&@cast(clnt, "rpc_clnt", "kernel:sunrpc")->cl_users)
	    : -1)
}

function proc_from_msg:long(msg:long)
{
	if (msg == 0)
		return 0
	return @cast(msg, "rpc_message", "kernel:sunrpc")->rpc_proc->p_proc
}

function vers_from_prog:long(program:long, vers:long)
{
    if (program
	&& vers < @cast(program, "rpc_program", "kernel:sunrpc")->nrvers)
	return @cast(program, "rpc_program", "kernel:sunrpc")->version[vers]->number
    return 0
}

function addr_from_rqst:long(rqstp:long)
{
    if (rqstp) {
	addr = &@cast(rqstp, "svc_rqst", "kernel:nfs")->rq_addr
	if (addr == 0)
		return 0

	/* In reality, 'rq_addr' is of 'sockaddr_storage' type
	 * (since 2.6.19). But when used, you cast it to what is
	 * inside that buffer. The 'struct sockaddr', 'struct
	 * sockaddr_in', and 'sockaddr_in6' structures all have a
	 * family type as their 1st member. */
	if (@cast(addr, "sockaddr")->sa_family
		== @const("AF_INET")) {
		return @cast(addr, "sockaddr_in")->sin_addr->s_addr
	}
	else if (@cast(addr, "sockaddr")->sa_family
		 == @const("AF_INET6")) {
		return &@cast(addr, "sockaddr_in6")->sin6_addr
	}
    }
    return 0
}

function addr_from_rqst_str:string(rqstp:long)
{
	if (rqstp == 0)
		return "Null"

	addr = &@cast(rqstp, "svc_rqst", "kernel:nfs")->rq_addr
	if (addr == 0)
		return "Null"

	s_addr = addr_from_rqst(rqstp)
	if (s_addr == 0)
		return "Unsupported Address Family"
	if (@cast(addr, "sockaddr_in")->sin_family
		  == @const("AF_INET")) {
		return sprintf("%s:%d",
			       format_ipaddr(s_addr,
				  @const("AF_INET")), 
				  @cast(addr, "sockaddr_in")->sin_port)
	}
	return sprintf("[%s]:%d",
		       format_ipaddr(s_addr,
			  @const("AF_INET6")),
			  @cast(addr, "sockaddr_in6")->sin6_port)
}

%{
#include <linux/un.h>
#include <linux/in.h>
#include <linux/in6.h>
%}

@__private30 function __rpc_format_servername:string(servername:long, address:long)
{
	printf("__rpc_format_servername: %p\n", servername)
	if (servername != 0)
		return kernel_string(servername)

	// If 'servername' is NULL, create a string representation of
	// the passed-in address.
	// 
	// Note that 'address' is a generic 'struct sockaddr'. When
	// you use it you have to cast it to the more specific
	// structure based on the family type.
	if (@cast(address, "sockaddr")->sa_family == @const("AF_LOCAL")) {
		return kernel_string(@cast(address, "sockaddr_un")->sun_path)
	}
	else if (@cast(address, "sockaddr")->sa_family == @const("AF_INET")) {
		return format_ipaddr(&@cast(address, "sockaddr_in")->sin_addr->s_addr, @const("AF_INET"))
	}
	else if (@cast(address, "sockaddr")->sa_family == @const("AF_INET6")) {
		return format_ipaddr(&@cast(address, "sockaddr_in6")->sin6_addr,
				     @const("AF_INET6"))
	}
	return "*unknown address family*"
}
