1    	/*
2    	 * Copyright (c) 2012-2017 DESY Hamburg DMG-Division.
3    	 * Copyright (c) 2018 Red Hat, Inc.
4    	 *
5    	 * AUTHOR: Tigran Mkrtchayn (tigran.mkrtchyan@desy.de)
6    	 * AUTHOR: William Allen Simpson <wsimpson@redhat.com>
7    	 * AUTHOR: Matt Benjamin <mbenjamin@redhat.com>
8    	 *
9    	 * This code is released into the "public domain" by its author(s).
10   	 * Anybody may use, alter, and distribute the code without restriction.
11   	 * The author(s) make no guarantees, and take no liability of any kind
12   	 * for use of this code.
13   	 */
14   	
15   	/**
16   	 * @file rpcping.c
17   	 * @author William Allen Simpson <wsimpson@redhat.com>
18   	 * @brief RPC ping
19   	 *
20   	 * @section DESCRIPTION
21   	 *
22   	 * Simple RPC ping test.
23   	 *
24   	 */
25   	#include "config.h"
26   	#include <stdio.h>
27   	#include <unistd.h>
28   	#include <sys/times.h>
29   	#include <sys/types.h>
30   	#include <sys/socket.h>
31   	#include <netdb.h>
32   	#include <pthread.h>
33   	#include <getopt.h>
34   	#include <rpc/rpc.h>
35   	#include <rpc/svc_auth.h>
36   	#ifdef USE_LTTNG_NTIRPC
37   	#include "lttng/rpcping.h"
38   	#endif
39   	
40   	static pthread_mutex_t rpcping_mutex = PTHREAD_MUTEX_INITIALIZER;
41   	static pthread_cond_t rpcping_cond = PTHREAD_COND_INITIALIZER;
42   	static uint32_t rpcping_threads;
43   	
44   	static struct timespec to = {30, 0};
45   	
46   	struct state {
47   		CLIENT *handle;
48   		pthread_cond_t s_cond;
49   		pthread_mutex_t s_mutex;
50   		struct timespec starting;
51   		struct timespec stopping;
52   		int count;
53   		int proc;
54   		int id;
55   		uint32_t failures;
56   		uint32_t responses;
57   		uint32_t timeouts;
58   	};
59   	
60   	static uint64_t timespec_elapsed(const struct timespec *starting,
61   					 const struct timespec *stopping)
62   	{
63   		time_t elapsed = stopping->tv_sec - starting->tv_sec;
64   		long nsec = stopping->tv_nsec - starting->tv_nsec;
65   	
66   		return (elapsed * 1000000000L) + nsec;
67   	}
68   	
69   	static int
70   	get_conn_fd(const char *host, int hbport)
71   	{
72   		struct addrinfo *res, *fr;
73   		int r, fd = 0;
74   	
75   		r = getaddrinfo(host, NULL, NULL, &res);
76   		if (r) {
77   			return 0;
78   		}
79   		fr = res;
80   	
81   		while (res) {
82   			fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
83   			if (fd <= 0)
84   				goto next;
85   	
86   			switch (res->ai_family) {
87   			case AF_INET:
88   			{
89   				struct sockaddr_in *sin = (struct sockaddr_in *)
90   								res->ai_addr;
91   	
92   				sin->sin_port = htons(hbport);
93   				r = connect(fd, (struct sockaddr *) sin,
94   					    sizeof(struct sockaddr));
95   				if (!r) {
96   					goto done;
97   				}
98   				close(fd);
99   				break;
100  			}
101  			case AF_INET6:
102  			{
103  				struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)
104  								res->ai_addr;
105  	
106  				sin6->sin6_port = htons(hbport);
107  				r = connect(fd, (struct sockaddr *) sin6,
108  					    sizeof(struct sockaddr));
109  				if (!r) {
110  					goto done;
111  				}
112  				close(fd);
113  				break;
114  			}
115  			default:
116  				break;
117  			};
118  		next:
119  			res = res->ai_next;
120  		}
121  	
122  	done:
123  		freeaddrinfo(fr);
124  		return fd;
125  	}
126  	
127  	static void
128  	worker_cb(struct clnt_req *cc)
129  	{
130  		CLIENT *clnt = cc->cc_clnt;
131  		struct state *s = clnt->cl_u1;
132  	
133  		if (cc->cc_error.re_status != RPC_SUCCESS) {
134  			if (cc->cc_error.re_status == RPC_TIMEDOUT) {
135  				atomic_inc_uint32_t(&s->timeouts);
136  			} else {
137  				atomic_inc_uint32_t(&s->failures);
138  			}
139  		}
140  	
141  		clnt_req_release(cc);
142  		if (atomic_inc_uint32_t(&s->responses) < s->count) {
143  			return;
144  		}
145  	
146  		pthread_cond_broadcast(&s->s_cond);
147  	}
148  	
149  	static void *
150  	worker(void *arg)
151  	{
152  		struct state *s = arg;
153  		struct clnt_req *cc;
154  		int i;
155  	
156  		pthread_cond_init(&s->s_cond, NULL);
157  		pthread_mutex_init(&s->s_mutex, NULL);
158  	
159  		clock_gettime(CLOCK_MONOTONIC, &s->starting);
(1) Event cond_true: Condition "i < s->count", taking true branch.
(7) Event loop_begin: Jumped back to beginning of loop.
(8) Event cond_true: Condition "i < s->count", taking true branch.
(14) Event loop_begin: Jumped back to beginning of loop.
(15) Event cond_true: Condition "i < s->count", taking true branch.
160  		for (i = 0; i < s->count; i++) {
(16) Event returned_null: "calloc" returns "NULL" (checked 153 out of 155 times).
(27) Event var_assigned: Assigning: "cc" = "NULL" return value from "calloc".
Also see events: [example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][dereference]
161  			cc = calloc(1, sizeof(*cc));
(28) Event dereference: Dereferencing a pointer that might be "NULL" "cc" when calling "clnt_req_fill". [details]
Also see events: [returned_null][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][var_assigned]
162  			clnt_req_fill(cc, s->handle, authnone_ncreate(), s->proc,
163  				      (xdrproc_t) xdr_void, NULL,
164  				      (xdrproc_t) xdr_void, NULL);
165  	
(2) Event cond_false: Condition "clnt_req_setup(cc, to) != RPC_SUCCESS", taking false branch.
(9) Event cond_false: Condition "clnt_req_setup(cc, to) != RPC_SUCCESS", taking false branch.
166  			if (clnt_req_setup(cc, to) != RPC_SUCCESS) {
167  				rpc_perror(&cc->cc_error, "clnt_req_setup failed");
168  				s->count = i;
169  				clnt_req_release(cc);
170  				break;
(3) Event if_end: End of if statement.
(10) Event if_end: End of if statement.
171  			}
172  			cc->cc_refreshes = 1;
173  			cc->cc_process_cb = worker_cb;
174  	
175  			cc->cc_error.re_status = CLNT_CALL_BACK(cc);
(4) Event cond_false: Condition "cc->cc_error.re_status != RPC_SUCCESS", taking false branch.
(11) Event cond_false: Condition "cc->cc_error.re_status != RPC_SUCCESS", taking false branch.
176  			if (cc->cc_error.re_status != RPC_SUCCESS) {
177  				rpc_perror(&cc->cc_error, "CLNT_CALL_BACK failed");
178  				s->count = i;
179  				clnt_req_release(cc);
180  				break;
(5) Event if_end: End of if statement.
(12) Event if_end: End of if statement.
181  			}
(6) Event loop: Jumping back to the beginning of the loop.
(13) Event loop: Jumping back to the beginning of the loop.
182  		}
183  	
184  		pthread_mutex_lock(&s->s_mutex);
185  		pthread_cond_wait(&s->s_cond, &s->s_mutex);
186  		pthread_mutex_unlock(&s->s_mutex);
187  		clock_gettime(CLOCK_MONOTONIC, &s->stopping);
188  	
189  		if (atomic_dec_uint32_t(&rpcping_threads) > 0) {
190  			return NULL;
191  		}
192  	
193  		pthread_cond_broadcast(&rpcping_cond);
194  		return NULL;
195  	}
196  	
197  	static struct svc_req *
198  	alloc_request(SVCXPRT *xprt, XDR *xdrs)
199  	{
200  		struct svc_req *req = calloc(1, sizeof(*req));
201  	
202  		SVC_REF(xprt, SVC_REF_FLAG_NONE);
203  		req->rq_xprt = xprt;
204  		req->rq_xdrs = xdrs;
205  		req->rq_refcnt = 1;
206  	
207  		return req;
208  	}
209  	
210  	static void
211  	free_request(struct svc_req *req, enum xprt_stat stat)
212  	{
213  		free(req);
214  	}
215  	
216  	static void usage()
217  	{
218  		printf("Usage: rpcping <raw|rdma|tcp|udp> <host> [--rpcbind] [--count=<n>] [--threads=<n>] [--workers=<n>] [--port=<n>] [--program=<n>] [--version=<n>] [--procedure=<n>]\n");
219  	}
220  	
221  	static struct option long_options[] =
222  	{
223  		{"count", required_argument, NULL, 'c'},
224  		{"threads", required_argument, NULL, 't'},
225  		{"workers", required_argument, NULL, 'w'},
226  		{"port", required_argument, NULL, 'p'},
227  		{"program", required_argument, NULL, 'm'},
228  		{"version", required_argument, NULL, 'v'},
229  		{"procedure", required_argument, NULL, 'x'},
230  		{"rpcbind", no_argument, NULL, 'b'},
231  		{NULL, 0, NULL, 0}
232  	};
233  	
234  	int main(int argc, char *argv[])
235  	{
236  		svc_init_params svc_params;
237  		CLIENT *clnt;
238  		struct state *s;
239  		struct state *states;
240  		char *proto;
241  		char *host;
242  		double total;
243  		double elapsed_ns;
244  		int i;
245  		int opt;
246  		int count = 500; /* minimal concurrent requests */
247  		int nthreads = 1;
248  		int nworkers = 5;
249  		int port = 2049;
250  		int prog = 100003; /* nfs */
251  		int vers = 3; /* allow raw, rdma, tcp, udp by default */
252  		int proc = 0;
253  		int send_sz = 8192;
254  		int recv_sz = 8192;
255  		unsigned int failures = 0;
256  		unsigned int timeouts = 0;
257  		bool rpcbind = false;
258  	
259  	#ifdef USE_LTTNG_NTIRPC
260  		tracepoint(rpcping, test,
261  			   __FILE__, __func__, __LINE__, "Boo");
262  	#endif
263  		/* protocol and host/dest positional */
264  		if (argc < 3) {
265  			usage();
266  			exit(1);
267  		}
268  	
269  		proto = argv[1];
270  		host = argv[2];
271  	
272  		optind = 3;
273  		while ((opt = getopt_long(argc, argv, "bc:m:p:t:v:w:x:",
274  					  long_options, NULL)) != -1) {
275  			switch (opt)
276  			{
277  			case 'c':
278  				count = atoi(optarg);
279  				break;
280  			case 't':
281  				nthreads = atoi(optarg);
282  				break;
283  			case 'w':
284  				nworkers = atoi(optarg);
285  				break;
286  			case 'p':
287  				port = atoi(optarg);
288  				break;
289  			case 'm':
290  				prog = atoi(optarg);
291  				break;
292  			case 'v':
293  				vers = atoi(optarg);
294  				break;
295  			case 'x':
296  				proc = atoi(optarg);
297  				break;
298  			case 'b':
299  				rpcbind = true;
300  				break;
301  			default:
302  				usage();
303  				exit(1);
304  				break;
305  			};
306  		}
307  	
308  		states = calloc(nthreads, sizeof(struct state));
309  		if (!states) {
310  			perror("calloc failed");
311  			exit(1);
312  		}
313  	
314  		memset(&svc_params, 0, sizeof(svc_params));
315  		svc_params.alloc_cb = alloc_request;
316  		svc_params.free_cb = free_request;
317  		svc_params.flags = SVC_INIT_EPOLL | SVC_INIT_NOREG_XPRTS;
318  		svc_params.max_events = 512;
319  		svc_params.ioq_thrd_max = nworkers;
320  	
321  		if (!svc_init(&svc_params)) {
322  			perror("svc_init failed");
323  			exit(1);
324  		}
325  	
326  		rpcping_threads = nthreads;
327  		for (i = 0; i < nthreads; i++) {
328  			pthread_t t;
329  	
330  			if (rpcbind) {
331  				clnt = clnt_ncreate(host, prog, vers, proto);
332  				if (CLNT_FAILURE(clnt)) {
333  					rpc_perror(&clnt->cl_error,
334  						   "clnt_ncreate failed");
335  					exit(2);
336  				}
337  			} else {
338  				/* connect to host:port */
339  				struct sockaddr_storage ss;
340  				struct netbuf raddr = {
341  					.buf = &ss,
342  					.len = sizeof(ss)
343  				};
344  				int fd = get_conn_fd(host, port);
345  	
346  				if (fd <= 0) {
347  					perror("get_conn_fd failed");
348  					exit(3);
349  				}
350  				clnt = clnt_vc_ncreatef(fd, &raddr, prog, vers,
351  							send_sz,
352  							recv_sz,
353  							CLNT_CREATE_FLAG_CLOSE);
354  				if (CLNT_FAILURE(clnt)) {
355  					rpc_perror(&clnt->cl_error,
356  						   "clnt_ncreate failed");
357  					exit(4);
358  				}
359  			}
360  			s = &states[i];
361  			clnt->cl_u1 = s;
362  	
363  			s->handle = clnt;
364  			s->id = i;
365  			s->count = count;
366  			s->proc = proc;
367  			pthread_create(&t, NULL, worker, s);
368  		}
369  	
370  		pthread_mutex_lock(&rpcping_mutex);
371  		pthread_cond_wait(&rpcping_cond, &rpcping_mutex);
372  		pthread_mutex_unlock(&rpcping_mutex);
373  	
374  		total = 0.0;
375  		elapsed_ns = 0.0;
376  		for (i = 0; i < nthreads; i++) {
377  			s = &states[i];
378  			failures += s->failures;
379  			timeouts += s->timeouts;
380  			total += s->responses;
381  			elapsed_ns += timespec_elapsed(&s->starting, &s->stopping);
382  			CLNT_DESTROY(s->handle);
383  		}
384  		total *= 1000000000.0;
385  		total /= elapsed_ns;
386  	
387  		fprintf(stdout, "rpcping %s %s count=%d threads=%d workers=%d (port=%d program=%d version=%d procedure=%d): failures %u timeouts %u mean %2.4lf, total %2.4lf\n",
388  			proto, host, count, nthreads, nworkers, port, prog, vers, proc,
389  			failures, timeouts, total / nthreads, total);
390  		fflush(stdout);
391  	
392  		(void)svc_shutdown(SVC_SHUTDOWN_FLAG_NONE);
393  		return (0);
394  	}
395