1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/* sockperf.c
18 * This simple network client tries to connect to an echo daemon (echod)
19 * listening on a port it supplies, then time how long it takes to
20 * reply with packets of varying sizes.
21 * It prints results once completed.
22 *
23 * To run,
24 *
25 *   ./echod &
26 *   ./sockperf
27 */
28
29#include <stdio.h>
30#include <stdlib.h>  /* for atexit() */
31
32#include "apr.h"
33#include "apr_network_io.h"
34#include "apr_strings.h"
35
36#define MAX_ITERS    10
37#define TEST_SIZE  1024
38
39struct testSet {
40    char c;
41    apr_size_t size;
42    int iters;
43} testRuns[] = {
44    { 'a', 1, 3 },
45    { 'b', 4, 3 },
46    { 'c', 16, 5 },
47    { 'd', 64, 5 },
48    { 'e', 256, 10 },
49};
50
51struct testResult {
52    int size;
53    int iters;
54    apr_time_t msecs[MAX_ITERS];
55    apr_time_t avg;
56};
57
58static apr_int16_t testPort = 4747;
59static apr_sockaddr_t *sockAddr = NULL;
60
61static void reportError(const char *msg, apr_status_t rv,
62                        apr_pool_t *pool)
63{
64    fprintf(stderr, "%s\n", msg);
65    if (rv != APR_SUCCESS)
66        fprintf(stderr, "Error: %d\n'%s'\n", rv,
67                apr_psprintf(pool, "%pm", &rv));
68
69}
70
71static void closeConnection(apr_socket_t *sock)
72{
73    apr_size_t len = 0;
74    apr_socket_send(sock, NULL, &len);
75}
76
77static apr_status_t sendRecvBuffer(apr_time_t *t, const char *buf,
78                                   apr_size_t size, apr_pool_t *pool)
79{
80    apr_socket_t *sock;
81    apr_status_t rv;
82    apr_size_t len = size, thistime = size;
83    char *recvBuf;
84    apr_time_t testStart = apr_time_now(), testEnd;
85    int i;
86
87    if (! sockAddr) {
88        rv = apr_sockaddr_info_get(&sockAddr, "127.0.0.1", APR_UNSPEC,
89                                   testPort, 0, pool);
90        if (rv != APR_SUCCESS) {
91            reportError("Unable to get socket info", rv, pool);
92            return rv;
93        }
94
95        /* make sure we can connect to daemon before we try tests */
96
97        rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, APR_PROTO_TCP,
98                           pool);
99        if (rv != APR_SUCCESS) {
100            reportError("Unable to create IPv4 stream socket", rv, pool);
101            return rv;
102        }
103
104        rv = apr_socket_connect(sock, sockAddr);
105        if (rv != APR_SUCCESS) {
106            reportError("Unable to connect to echod!", rv, pool);
107            apr_socket_close(sock);
108            return rv;
109        }
110        apr_socket_close(sock);
111
112    }
113
114    recvBuf = apr_palloc(pool, size);
115    if (! recvBuf) {
116        reportError("Unable to allocate buffer", ENOMEM, pool);
117        return ENOMEM;
118    }
119
120    *t = 0;
121
122    /* START! */
123    testStart = apr_time_now();
124    rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, APR_PROTO_TCP,
125                           pool);
126    if (rv != APR_SUCCESS) {
127        reportError("Unable to create IPv4 stream socket", rv, pool);
128        return rv;
129    }
130
131    rv = apr_socket_connect(sock, sockAddr);
132    if (rv != APR_SUCCESS) {
133        reportError("Unable to connect to echod!", rv, pool);
134        apr_socket_close(sock);
135        return rv;
136    }
137
138    for (i = 0; i < 3; i++) {
139
140        len = size;
141        thistime = size;
142
143        rv = apr_socket_send(sock, buf, &len);
144        if (rv != APR_SUCCESS || len != size) {
145            reportError(apr_psprintf(pool,
146                         "Unable to send data correctly (iteration %d of 3)",
147                         i) , rv, pool);
148            closeConnection(sock);
149            apr_socket_close(sock);
150            return rv;
151        }
152
153        do {
154            len = thistime;
155            rv = apr_socket_recv(sock, &recvBuf[size - thistime], &len);
156            if (rv != APR_SUCCESS) {
157                reportError("Error receiving from socket", rv, pool);
158                break;
159            }
160            thistime -= len;
161        } while (thistime);
162    }
163
164    closeConnection(sock);
165    apr_socket_close(sock);
166    testEnd = apr_time_now();
167    /* STOP! */
168
169    if (thistime) {
170        reportError("Received less than we sent :-(", rv, pool);
171        return rv;
172    }
173    if (strncmp(recvBuf, buf, size) != 0) {
174        reportError("Received corrupt data :-(", 0, pool);
175        printf("We sent:\n%s\nWe received:\n%s\n", buf, recvBuf);
176        return EINVAL;
177    }
178    *t = testEnd - testStart;
179    return APR_SUCCESS;
180}
181
182static apr_status_t runTest(struct testSet *ts, struct testResult *res,
183                            apr_pool_t *pool)
184{
185    char *buffer;
186    apr_status_t rv;
187    int i;
188    apr_size_t sz = ts->size * TEST_SIZE;
189
190    buffer = apr_palloc(pool, sz);
191    if (!buffer) {
192        reportError("Unable to allocate buffer", ENOMEM, pool);
193        return ENOMEM;
194    }
195    memset(buffer, ts->c, sz);
196
197    res->iters = ts->iters > MAX_ITERS ? MAX_ITERS : ts->iters;
198
199    for (i = 0; i < res->iters; i++) {
200        apr_time_t iterTime;
201        rv = sendRecvBuffer(&iterTime, buffer, sz, pool);
202        if (rv != APR_SUCCESS) {
203            res->iters = i;
204            break;
205        }
206        res->msecs[i] = iterTime;
207    }
208
209    return rv;
210}
211
212int main(int argc, char **argv)
213{
214    apr_pool_t *pool;
215    apr_status_t rv;
216    int i;
217    int nTests = sizeof(testRuns) / sizeof(testRuns[0]);
218    struct testResult *results;
219
220    printf("APR Test Application: sockperf\n");
221
222    apr_initialize();
223    atexit(apr_terminate);
224
225    apr_pool_create(&pool, NULL);
226
227    results = (struct testResult *)apr_pcalloc(pool,
228                                        sizeof(*results) * nTests);
229
230    for (i = 0; i < nTests; i++) {
231        printf("Test -> %c\n", testRuns[i].c);
232        results[i].size = testRuns[i].size * (apr_size_t)TEST_SIZE;
233        rv = runTest(&testRuns[i], &results[i], pool);
234        if (rv != APR_SUCCESS) {
235            /* error already reported */
236            exit(1);
237        }
238    }
239
240    printf("Tests Complete!\n");
241    for (i = 0; i < nTests; i++) {
242        int j;
243        apr_time_t totTime = 0;
244        printf("%10d byte block:\n", results[i].size);
245        printf("\t%2d iterations : ", results[i].iters);
246        for (j = 0; j < results[i].iters; j++) {
247            printf("%6" APR_TIME_T_FMT, results[i].msecs[j]);
248            totTime += results[i].msecs[j];
249        }
250        printf("<\n");
251        printf("\t  Average: %6" APR_TIME_T_FMT "\n",
252               totTime / results[i].iters);
253    }
254
255    return 0;
256}
257