1 /*
2 *
3 * Copyright 2016 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include <grpc/grpc.h>
20
21 #include <stdio.h>
22 #include <string.h>
23
24 #include <grpc/byte_buffer.h>
25 #include <grpc/byte_buffer_reader.h>
26 #include <grpc/support/alloc.h>
27 #include <grpc/support/log.h>
28 #include <grpc/support/time.h>
29 #include "src/core/lib/gpr/env.h"
30 #include "src/core/lib/gpr/string.h"
31 #include "src/core/lib/gpr/useful.h"
32
33 #include "test/core/util/cmdline.h"
34 #include "test/core/util/memory_counters.h"
35 #include "test/core/util/test_config.h"
36
37 static grpc_channel* channel;
38 static grpc_completion_queue* cq;
39 static grpc_op metadata_ops[2];
40 static grpc_op status_ops[2];
41 static grpc_op snapshot_ops[6];
42 static grpc_op* op;
43
44 typedef struct {
45 grpc_call* call;
46 grpc_metadata_array initial_metadata_recv;
47 grpc_status_code status;
48 grpc_slice details;
49 grpc_metadata_array trailing_metadata_recv;
50 } fling_call;
51
52 // Statically allocate call data structs. Enough to accomodate 10000 ping-pong
53 // calls and 1 extra for the snapshot calls.
54 static fling_call calls[10001];
55
tag(intptr_t t)56 static void* tag(intptr_t t) { return (void*)t; }
57
58 // A call is intentionally divided into two steps. First step is to initiate a
59 // call (i.e send and recv metadata). A call is outstanding after we initated,
60 // so we can measure the call memory usage.
init_ping_pong_request(int call_idx)61 static void init_ping_pong_request(int call_idx) {
62 grpc_metadata_array_init(&calls[call_idx].initial_metadata_recv);
63
64 memset(metadata_ops, 0, sizeof(metadata_ops));
65 op = metadata_ops;
66 op->op = GRPC_OP_SEND_INITIAL_METADATA;
67 op->data.send_initial_metadata.count = 0;
68 op->flags = GRPC_INITIAL_METADATA_WAIT_FOR_READY;
69 op++;
70 op->op = GRPC_OP_RECV_INITIAL_METADATA;
71 op->data.recv_initial_metadata.recv_initial_metadata =
72 &calls[call_idx].initial_metadata_recv;
73 op++;
74
75 grpc_slice hostname = grpc_slice_from_static_string("localhost");
76 calls[call_idx].call = grpc_channel_create_call(
77 channel, nullptr, GRPC_PROPAGATE_DEFAULTS, cq,
78 grpc_slice_from_static_string("/Reflector/reflectUnary"), &hostname,
79 gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
80
81 GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(calls[call_idx].call,
82 metadata_ops,
83 (size_t)(op - metadata_ops),
84 tag(call_idx), nullptr));
85 grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
86 }
87
88 // Second step is to finish the call (i.e recv status) and destroy the call.
finish_ping_pong_request(int call_idx)89 static void finish_ping_pong_request(int call_idx) {
90 grpc_metadata_array_init(&calls[call_idx].trailing_metadata_recv);
91
92 memset(status_ops, 0, sizeof(status_ops));
93 op = status_ops;
94 op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
95 op->data.recv_status_on_client.trailing_metadata =
96 &calls[call_idx].trailing_metadata_recv;
97 op->data.recv_status_on_client.status = &calls[call_idx].status;
98 op->data.recv_status_on_client.status_details = &calls[call_idx].details;
99 op++;
100
101 GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(calls[call_idx].call,
102 status_ops,
103 (size_t)(op - status_ops),
104 tag(call_idx), nullptr));
105 grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
106 grpc_metadata_array_destroy(&calls[call_idx].initial_metadata_recv);
107 grpc_metadata_array_destroy(&calls[call_idx].trailing_metadata_recv);
108 grpc_slice_unref(calls[call_idx].details);
109 grpc_call_unref(calls[call_idx].call);
110 calls[call_idx].call = nullptr;
111 }
112
send_snapshot_request(int call_idx,grpc_slice call_type)113 static struct grpc_memory_counters send_snapshot_request(int call_idx,
114 grpc_slice call_type) {
115 grpc_metadata_array_init(&calls[call_idx].initial_metadata_recv);
116 grpc_metadata_array_init(&calls[call_idx].trailing_metadata_recv);
117
118 grpc_byte_buffer* response_payload_recv = nullptr;
119 memset(snapshot_ops, 0, sizeof(snapshot_ops));
120 op = snapshot_ops;
121
122 op->op = GRPC_OP_SEND_INITIAL_METADATA;
123 op->data.send_initial_metadata.count = 0;
124 op->flags = GRPC_INITIAL_METADATA_WAIT_FOR_READY;
125 op++;
126 op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
127 op++;
128 op->op = GRPC_OP_RECV_INITIAL_METADATA;
129 op->data.recv_initial_metadata.recv_initial_metadata =
130 &calls[call_idx].initial_metadata_recv;
131 op++;
132 op->op = GRPC_OP_RECV_MESSAGE;
133 op->data.recv_message.recv_message = &response_payload_recv;
134 op++;
135 op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
136 op->data.recv_status_on_client.trailing_metadata =
137 &calls[call_idx].trailing_metadata_recv;
138 op->data.recv_status_on_client.status = &calls[call_idx].status;
139 op->data.recv_status_on_client.status_details = &calls[call_idx].details;
140 op++;
141
142 grpc_slice hostname = grpc_slice_from_static_string("localhost");
143 calls[call_idx].call = grpc_channel_create_call(
144 channel, nullptr, GRPC_PROPAGATE_DEFAULTS, cq, call_type, &hostname,
145 gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
146 GPR_ASSERT(GRPC_CALL_OK == grpc_call_start_batch(calls[call_idx].call,
147 snapshot_ops,
148 (size_t)(op - snapshot_ops),
149 (void*)nullptr, nullptr));
150 grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME), nullptr);
151
152 grpc_byte_buffer_reader reader;
153 grpc_byte_buffer_reader_init(&reader, response_payload_recv);
154 grpc_slice response = grpc_byte_buffer_reader_readall(&reader);
155
156 struct grpc_memory_counters snapshot;
157 snapshot.total_size_absolute =
158 (reinterpret_cast<struct grpc_memory_counters*> GRPC_SLICE_START_PTR(
159 response))
160 ->total_size_absolute;
161 snapshot.total_allocs_absolute =
162 (reinterpret_cast<struct grpc_memory_counters*> GRPC_SLICE_START_PTR(
163 response))
164 ->total_allocs_absolute;
165 snapshot.total_size_relative =
166 (reinterpret_cast<struct grpc_memory_counters*> GRPC_SLICE_START_PTR(
167 response))
168 ->total_size_relative;
169 snapshot.total_allocs_relative =
170 (reinterpret_cast<struct grpc_memory_counters*> GRPC_SLICE_START_PTR(
171 response))
172 ->total_allocs_relative;
173
174 grpc_metadata_array_destroy(&calls[call_idx].initial_metadata_recv);
175 grpc_metadata_array_destroy(&calls[call_idx].trailing_metadata_recv);
176 grpc_slice_unref(response);
177 grpc_byte_buffer_reader_destroy(&reader);
178 grpc_byte_buffer_destroy(response_payload_recv);
179 grpc_slice_unref(calls[call_idx].details);
180 calls[call_idx].details = grpc_empty_slice();
181 grpc_call_unref(calls[call_idx].call);
182 calls[call_idx].call = nullptr;
183
184 return snapshot;
185 }
186
main(int argc,char ** argv)187 int main(int argc, char** argv) {
188 grpc_memory_counters_init();
189 grpc_slice slice = grpc_slice_from_copied_string("x");
190 char* fake_argv[1];
191
192 const char* target = "localhost:443";
193 gpr_cmdline* cl;
194 grpc_event event;
195
196 grpc_init();
197
198 GPR_ASSERT(argc >= 1);
199 fake_argv[0] = argv[0];
200 grpc_test_init(1, fake_argv);
201
202 int warmup_iterations = 100;
203 int benchmark_iterations = 1000;
204
205 cl = gpr_cmdline_create("memory profiling client");
206 gpr_cmdline_add_string(cl, "target", "Target host:port", &target);
207 gpr_cmdline_add_int(cl, "warmup", "Warmup iterations", &warmup_iterations);
208 gpr_cmdline_add_int(cl, "benchmark", "Benchmark iterations",
209 &benchmark_iterations);
210 gpr_cmdline_parse(cl, argc, argv);
211 gpr_cmdline_destroy(cl);
212
213 for (size_t k = 0; k < GPR_ARRAY_SIZE(calls); k++) {
214 calls[k].details = grpc_empty_slice();
215 }
216
217 cq = grpc_completion_queue_create_for_next(nullptr);
218
219 struct grpc_memory_counters client_channel_start =
220 grpc_memory_counters_snapshot();
221 channel = grpc_insecure_channel_create(target, nullptr, nullptr);
222
223 int call_idx = 0;
224
225 struct grpc_memory_counters before_server_create = send_snapshot_request(
226 0, grpc_slice_from_static_string("Reflector/GetBeforeSvrCreation"));
227 struct grpc_memory_counters after_server_create = send_snapshot_request(
228 0, grpc_slice_from_static_string("Reflector/GetAfterSvrCreation"));
229
230 // warmup period
231 for (int i = 0; i < warmup_iterations; i++) {
232 send_snapshot_request(
233 0, grpc_slice_from_static_string("Reflector/SimpleSnapshot"));
234 }
235
236 for (call_idx = 0; call_idx < warmup_iterations; ++call_idx) {
237 init_ping_pong_request(call_idx + 1);
238 }
239
240 struct grpc_memory_counters server_benchmark_calls_start =
241 send_snapshot_request(
242 0, grpc_slice_from_static_string("Reflector/SimpleSnapshot"));
243
244 struct grpc_memory_counters client_benchmark_calls_start =
245 grpc_memory_counters_snapshot();
246
247 // benchmark period
248 for (; call_idx < warmup_iterations + benchmark_iterations; ++call_idx) {
249 init_ping_pong_request(call_idx + 1);
250 }
251
252 struct grpc_memory_counters client_calls_inflight =
253 grpc_memory_counters_snapshot();
254
255 struct grpc_memory_counters server_calls_inflight = send_snapshot_request(
256 0, grpc_slice_from_static_string("Reflector/DestroyCalls"));
257
258 do {
259 event = grpc_completion_queue_next(
260 cq,
261 gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
262 gpr_time_from_micros(10000, GPR_TIMESPAN)),
263 nullptr);
264 } while (event.type != GRPC_QUEUE_TIMEOUT);
265
266 // second step - recv status and destroy call
267 for (call_idx = 0; call_idx < warmup_iterations + benchmark_iterations;
268 ++call_idx) {
269 finish_ping_pong_request(call_idx + 1);
270 }
271
272 struct grpc_memory_counters server_calls_end = send_snapshot_request(
273 0, grpc_slice_from_static_string("Reflector/SimpleSnapshot"));
274
275 struct grpc_memory_counters client_channel_end =
276 grpc_memory_counters_snapshot();
277
278 grpc_channel_destroy(channel);
279 grpc_completion_queue_shutdown(cq);
280
281 do {
282 event = grpc_completion_queue_next(cq, gpr_inf_future(GPR_CLOCK_REALTIME),
283 nullptr);
284 } while (event.type != GRPC_QUEUE_SHUTDOWN);
285 grpc_slice_unref(slice);
286
287 grpc_completion_queue_destroy(cq);
288 grpc_shutdown();
289
290 gpr_log(GPR_INFO, "---------client stats--------");
291 gpr_log(
292 GPR_INFO, "client call memory usage: %f bytes per call",
293 static_cast<double>(client_calls_inflight.total_size_relative -
294 client_benchmark_calls_start.total_size_relative) /
295 benchmark_iterations);
296 gpr_log(GPR_INFO, "client channel memory usage %zi bytes",
297 client_channel_end.total_size_relative -
298 client_channel_start.total_size_relative);
299
300 gpr_log(GPR_INFO, "---------server stats--------");
301 gpr_log(GPR_INFO, "server create: %zi bytes",
302 after_server_create.total_size_relative -
303 before_server_create.total_size_relative);
304 gpr_log(
305 GPR_INFO, "server call memory usage: %f bytes per call",
306 static_cast<double>(server_calls_inflight.total_size_relative -
307 server_benchmark_calls_start.total_size_relative) /
308 benchmark_iterations);
309 gpr_log(GPR_INFO, "server channel memory usage %zi bytes",
310 server_calls_end.total_size_relative -
311 after_server_create.total_size_relative);
312
313 const char* csv_file = "memory_usage.csv";
314 FILE* csv = fopen(csv_file, "w");
315 if (csv) {
316 char* env_build = gpr_getenv("BUILD_NUMBER");
317 char* env_job = gpr_getenv("JOB_NAME");
318 fprintf(
319 csv, "%f,%zi,%zi,%f,%zi,%s,%s\n",
320 static_cast<double>(client_calls_inflight.total_size_relative -
321 client_benchmark_calls_start.total_size_relative) /
322 benchmark_iterations,
323 client_channel_end.total_size_relative -
324 client_channel_start.total_size_relative,
325 after_server_create.total_size_relative -
326 before_server_create.total_size_relative,
327 static_cast<double>(server_calls_inflight.total_size_relative -
328 server_benchmark_calls_start.total_size_relative) /
329 benchmark_iterations,
330 server_calls_end.total_size_relative -
331 after_server_create.total_size_relative,
332 env_build == nullptr ? "" : env_build,
333 env_job == nullptr ? "" : env_job);
334 fclose(csv);
335 gpr_log(GPR_INFO, "Summary written to %s", csv_file);
336 }
337
338 grpc_memory_counters_destroy();
339 return 0;
340 }
341