Asterisk - The Open Source Telephony Project  21.4.1
test_sched.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2009, Digium, Inc.
5  *
6  * Russell Bryant <russell@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*! \file
20  *
21  * \brief ast_sched performance test module
22  *
23  * \author Russell Bryant <russell@digium.com>
24  */
25 
26 /*** MODULEINFO
27  <depend>TEST_FRAMEWORK</depend>
28  <support_level>core</support_level>
29  ***/
30 
31 #include "asterisk.h"
32 
33 #include <inttypes.h>
34 
35 #include "asterisk/module.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/sched.h"
38 #include "asterisk/test.h"
39 #include "asterisk/cli.h"
40 #include "asterisk/astobj2.h"
41 
42 static int sched_cb(const void *data)
43 {
44  return 0;
45 }
46 
47 static int order_check;
48 static int order_check_failed;
49 
50 static void sched_order_check(struct ast_test *test, int order)
51 {
52  ++order_check;
53  if (order_check != order) {
54  ast_test_status_update(test, "Unexpected execution order: expected:%d got:%d\n",
55  order, order_check);
56  order_check_failed = 1;
57  }
58 }
59 
60 static int sched_order_1_cb(const void *data)
61 {
62  sched_order_check((void *) data, 1);
63  return 0;
64 }
65 
66 static int sched_order_2_cb(const void *data)
67 {
68  sched_order_check((void *) data, 2);
69  return 0;
70 }
71 
72 static int sched_order_3_cb(const void *data)
73 {
74  sched_order_check((void *) data, 3);
75  return 0;
76 }
77 
78 static int sched_order_4_cb(const void *data)
79 {
80  sched_order_check((void *) data, 4);
81  return 0;
82 }
83 
84 static int sched_order_5_cb(const void *data)
85 {
86  sched_order_check((void *) data, 5);
87  return 0;
88 }
89 
90 static int sched_order_6_cb(const void *data)
91 {
92  sched_order_check((void *) data, 6);
93  return 0;
94 }
95 
96 static int sched_order_7_cb(const void *data)
97 {
98  sched_order_check((void *) data, 7);
99  return 0;
100 }
101 
102 static int sched_order_8_cb(const void *data)
103 {
104  sched_order_check((void *) data, 8);
105  return 0;
106 }
107 
108 AST_TEST_DEFINE(sched_test_order)
109 {
110  struct ast_sched_context *con;
111  enum ast_test_result_state res = AST_TEST_FAIL;
112  int id1, id2, id3, wait;
113 
114  switch (cmd) {
115  case TEST_INIT:
116  info->name = "sched_test_order";
117  info->category = "/main/sched/";
118  info->summary = "Test ordering of events in the scheduler API";
119  info->description =
120  "This test ensures that events are properly ordered by the "
121  "time they are scheduled to execute in the scheduler API.";
122  return AST_TEST_NOT_RUN;
123  case TEST_EXECUTE:
124  break;
125  }
126 
127  if (!(con = ast_sched_context_create())) {
128  ast_test_status_update(test,
129  "Test failed - could not create scheduler context\n");
130  return AST_TEST_FAIL;
131  }
132 
133  /* Add 3 scheduler entries, and then remove them, ensuring that the result
134  * of ast_sched_wait() looks appropriate at each step along the way. */
135 
136  if ((wait = ast_sched_wait(con)) != -1) {
137  ast_test_status_update(test,
138  "ast_sched_wait() should have returned -1, returned '%d'\n",
139  wait);
140  goto return_cleanup;
141  }
142 
143  if ((id1 = ast_sched_add(con, 100000, sched_cb, NULL)) == -1) {
144  ast_test_status_update(test, "Failed to add scheduler entry\n");
145  goto return_cleanup;
146  }
147 
148  if ((wait = ast_sched_wait(con)) > 100000) {
149  ast_test_status_update(test,
150  "ast_sched_wait() should have returned <= 100000, returned '%d'\n",
151  wait);
152  goto return_cleanup;
153  }
154 
155  if ((id2 = ast_sched_add(con, 10000, sched_cb, NULL)) == -1) {
156  ast_test_status_update(test, "Failed to add scheduler entry\n");
157  goto return_cleanup;
158  }
159 
160  if ((wait = ast_sched_wait(con)) > 10000) {
161  ast_test_status_update(test,
162  "ast_sched_wait() should have returned <= 10000, returned '%d'\n",
163  wait);
164  goto return_cleanup;
165  }
166 
167  if ((id3 = ast_sched_add(con, 1000, sched_cb, NULL)) == -1) {
168  ast_test_status_update(test, "Failed to add scheduler entry\n");
169  goto return_cleanup;
170  }
171 
172  if ((wait = ast_sched_wait(con)) > 1000) {
173  ast_test_status_update(test,
174  "ast_sched_wait() should have returned <= 1000, returned '%d'\n",
175  wait);
176  goto return_cleanup;
177  }
178 
179  if (ast_sched_del(con, id3) == -1) {
180  ast_test_status_update(test, "Failed to remove scheduler entry\n");
181  goto return_cleanup;
182  }
183 
184  if ((wait = ast_sched_wait(con)) <= 1000) {
185  ast_test_status_update(test,
186  "ast_sched_wait() should have returned > 1000, returned '%d'\n",
187  wait);
188  goto return_cleanup;
189  }
190 
191  if (ast_sched_del(con, id2) == -1) {
192  ast_test_status_update(test, "Failed to remove scheduler entry\n");
193  goto return_cleanup;
194  }
195 
196  if ((wait = ast_sched_wait(con)) <= 10000) {
197  ast_test_status_update(test,
198  "ast_sched_wait() should have returned > 10000, returned '%d'\n",
199  wait);
200  goto return_cleanup;
201  }
202 
203  if (ast_sched_del(con, id1) == -1) {
204  ast_test_status_update(test, "Failed to remove scheduler entry\n");
205  goto return_cleanup;
206  }
207 
208  if ((wait = ast_sched_wait(con)) != -1) {
209  ast_test_status_update(test,
210  "ast_sched_wait() should have returned -1, returned '%d'\n",
211  wait);
212  goto return_cleanup;
213  }
214 
215  /*
216  * Schedule immediate and delayed entries to check the order
217  * that they get executed. They must get executed at the
218  * time they expire in the order they were added.
219  */
220 #define DELAYED_SAME_EXPIRE 300 /* ms */
221  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_1_cb, test), res, return_cleanup);
222  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_1_cb, test), res, return_cleanup);
223  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_2_cb, test), res, return_cleanup);
224  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_2_cb, test), res, return_cleanup);
225  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_3_cb, test), res, return_cleanup);
226  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_3_cb, test), res, return_cleanup);
227  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_4_cb, test), res, return_cleanup);
228  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_4_cb, test), res, return_cleanup);
229  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_5_cb, test), res, return_cleanup);
230  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_5_cb, test), res, return_cleanup);
231  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_6_cb, test), res, return_cleanup);
232  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_6_cb, test), res, return_cleanup);
233  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_7_cb, test), res, return_cleanup);
234  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_7_cb, test), res, return_cleanup);
235  ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_8_cb, test), res, return_cleanup);
236 
237  /* Check order of scheduled immediate entries. */
238  order_check = 0;
239  order_check_failed = 0;
240  usleep(50 * 1000);/* Ensure that all the immediate entries are ready to expire */
241  ast_test_validate_cleanup(test, 7 == ast_sched_runq(con), res, return_cleanup);
242  ast_test_validate_cleanup(test, !order_check_failed, res, return_cleanup);
243 
244  /* Check order of scheduled entries expiring at the same time. */
245  order_check = 0;
246  order_check_failed = 0;
247  usleep((DELAYED_SAME_EXPIRE + 50) * 1000);/* Ensure that all the delayed entries are ready to expire */
248  ast_test_validate_cleanup(test, 8 == ast_sched_runq(con), res, return_cleanup);
249  ast_test_validate_cleanup(test, !order_check_failed, res, return_cleanup);
250 
251  if ((wait = ast_sched_wait(con)) != -1) {
252  ast_test_status_update(test,
253  "ast_sched_wait() should have returned -1, returned '%d'\n",
254  wait);
255  goto return_cleanup;
256  }
257 
258  res = AST_TEST_PASS;
259 
260 return_cleanup:
262 
263  return res;
264 }
265 
266 static char *handle_cli_sched_bench(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
267 {
268  struct ast_sched_context *con;
269  struct timeval start;
270  unsigned int num, i;
271  int *sched_ids = NULL;
272 
273  switch (cmd) {
274  case CLI_INIT:
275  e->command = "sched benchmark";
276  e->usage = ""
277  "Usage: sched benchmark <num>\n"
278  "";
279  return NULL;
280  case CLI_GENERATE:
281  return NULL;
282  }
283 
284  if (a->argc != e->args + 1) {
285  return CLI_SHOWUSAGE;
286  }
287 
288  if (sscanf(a->argv[e->args], "%u", &num) != 1) {
289  return CLI_SHOWUSAGE;
290  }
291 
292  if (!(con = ast_sched_context_create())) {
293  ast_cli(a->fd, "Test failed - could not create scheduler context\n");
294  return CLI_FAILURE;
295  }
296 
297  if (!(sched_ids = ast_malloc(sizeof(*sched_ids) * num))) {
298  ast_cli(a->fd, "Test failed - memory allocation failure\n");
299  goto return_cleanup;
300  }
301 
302  ast_cli(a->fd, "Testing ast_sched_add() performance - timing how long it takes "
303  "to add %u entries at random time intervals from 0 to 60 seconds\n", num);
304 
305  start = ast_tvnow();
306 
307  for (i = 0; i < num; i++) {
308  long when = labs(ast_random()) % 60000;
309  if ((sched_ids[i] = ast_sched_add(con, when, sched_cb, NULL)) == -1) {
310  ast_cli(a->fd, "Test failed - sched_add returned -1\n");
311  goto return_cleanup;
312  }
313  }
314 
315  ast_cli(a->fd, "Test complete - %" PRIi64 " us\n", ast_tvdiff_us(ast_tvnow(), start));
316 
317  ast_cli(a->fd, "Testing ast_sched_del() performance - timing how long it takes "
318  "to delete %u entries with random time intervals from 0 to 60 seconds\n", num);
319 
320  start = ast_tvnow();
321 
322  for (i = 0; i < num; i++) {
323  if (ast_sched_del(con, sched_ids[i]) == -1) {
324  ast_cli(a->fd, "Test failed - sched_del returned -1\n");
325  goto return_cleanup;
326  }
327  }
328 
329  ast_cli(a->fd, "Test complete - %" PRIi64 " us\n", ast_tvdiff_us(ast_tvnow(), start));
330 
331 return_cleanup:
333  if (sched_ids) {
334  ast_free(sched_ids);
335  }
336 
337  return CLI_SUCCESS;
338 }
339 
340 struct test_obj {
341  ast_mutex_t lock;
342  ast_cond_t cond;
343  int scheduledCBstarted;
344  int id;
345 };
346 
347 static void test_obj_cleanup(void *data)
348 {
349  struct test_obj *obj = data;
350  ast_mutex_destroy(&obj->lock);
351  ast_cond_destroy(&obj->cond);
352 }
353 
354 static int lockingcb(const void *data)
355 {
356  struct test_obj *obj = (struct test_obj *)data;
357  struct timespec delay = {3,0};
358 
359  ast_mutex_lock(&obj->lock);
360 
361  obj->scheduledCBstarted = 1;
362  ast_cond_signal(&obj->cond);
363 
364  ast_mutex_unlock(&obj->lock);
365 
366  ao2_ref(obj, -1);
367  while (nanosleep(&delay, &delay));
368  /* sleep to force this scheduled event to remain running long
369  * enough for the scheduling thread to unlock and call
370  * AST_SCHED_DEL_UNREF
371  */
372 
373  return 0;
374 }
375 
376 AST_TEST_DEFINE(sched_test_freebird)
377 {
378  struct test_obj * obj;
379  struct ast_sched_context * con;
380  enum ast_test_result_state res = AST_TEST_FAIL;
381  int refs;
382 
383  switch (cmd) {
384  case TEST_INIT:
385  info->name = "sched_test_freebird";
386  info->category = "/main/sched/";
387  info->summary = "Test deadlock avoidance and double-unref";
388  info->description =
389  "This tests a call to AST_SCHED_DEL_UNREF on a running event.";
390  return AST_TEST_NOT_RUN;
391  case TEST_EXECUTE:
392  res = AST_TEST_PASS;
393  break;
394  }
395 
396  obj = ao2_alloc(sizeof(struct test_obj), test_obj_cleanup);
397  if (!obj) {
398  ast_test_status_update(test,
399  "ao2_alloc() did not return an object\n");
400  return AST_TEST_FAIL;
401  }
402 
403  obj->scheduledCBstarted = 0;
404 
405  con = ast_sched_context_create();
406  if (!con) {
407  ast_test_status_update(test,
408  "ast_sched_context_create() did not return a context\n");
409  ao2_cleanup(obj);
410  return AST_TEST_FAIL;
411  }
412 
413  if (ast_sched_start_thread(con)) {
414  ast_test_status_update(test, "Failed to start test thread\n");
415  ao2_cleanup(obj);
417  return AST_TEST_FAIL;
418  }
419 
420  /* This double reference is to ensure that the object isn't destroyed prematurely
421  * in a case where it is unreffed an additional time.
422  */
423  ao2_ref(obj, +2);
424  if ((obj->id = ast_sched_add(con, 0, lockingcb, obj)) == -1) {
425  ast_test_status_update(test, "Failed to add scheduler entry\n");
426  ao2_ref(obj, -3);
428  return AST_TEST_FAIL;
429  }
430 
431  ast_mutex_lock(&obj->lock);
432  while(obj->scheduledCBstarted == 0) {
433  /* Wait for the scheduled thread to indicate that it has started so we can
434  * then call the AST_SCHED_DEL_UNREF macro
435  */
436  ast_cond_wait(&obj->cond,&obj->lock);
437  }
438  ast_mutex_unlock(&obj->lock);
439 
440  ast_test_status_update(test, "Received signal, calling Scedule and UNREF\n");
441  ast_test_status_update(test, "ID: %d\n", obj->id);
442  AST_SCHED_DEL_UNREF(con, obj->id, ao2_ref(obj, -1));
443 
444  refs = ao2_ref(obj, 0);
445 
446  switch(refs){
447  case 2:
448  ast_test_status_update(test, "Correct number of references '2'\n");
449  break;
450  default:
451  ast_test_status_update(test, "Incorrect number of references '%d'\n",
452  refs);
453  res = AST_TEST_FAIL;
454  break;
455  }
456 
457  /* Based on success or failure, the refcount could change
458  */
459  while(ao2_ref(obj, -1) > 1);
460 
462 
463  return res;
464 }
465 
466 static struct ast_cli_entry cli_sched[] = {
467  AST_CLI_DEFINE(handle_cli_sched_bench, "Benchmark ast_sched add/del performance"),
468 };
469 
470 static int unload_module(void)
471 {
472  AST_TEST_UNREGISTER(sched_test_order);
473  AST_TEST_UNREGISTER(sched_test_freebird);
474  ast_cli_unregister_multiple(cli_sched, ARRAY_LEN(cli_sched));
475  return 0;
476 }
477 
478 static int load_module(void)
479 {
480  AST_TEST_REGISTER(sched_test_order);
481  AST_TEST_REGISTER(sched_test_freebird);
482  ast_cli_register_multiple(cli_sched, ARRAY_LEN(cli_sched));
484 }
485 
486 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "ast_sched performance test module");
int ast_sched_start_thread(struct ast_sched_context *con)
Start a thread for processing scheduler entries.
Definition: sched.c:197
Asterisk main include file. File version handling, generic pbx functions.
int ast_sched_runq(struct ast_sched_context *con)
Runs the queue.
Definition: sched.c:786
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
descriptor for a cli entry.
Definition: cli.h:171
Test Framework API.
#define AST_SCHED_DEL_UNREF(sched, id, refcall)
schedule task to get deleted and call unref function
Definition: sched.h:82
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
Utility functions.
int args
This gets set in ast_cli_register()
Definition: cli.h:185
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
Scheduler Routines (derived from cheops)
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
struct ast_sched_context * ast_sched_context_create(void)
Create a scheduler context.
Definition: sched.c:238
char * command
Definition: cli.h:186
int ast_sched_del(struct ast_sched_context *con, int id) attribute_warn_unused_result
Deletes a scheduled event.
Definition: sched.c:614
int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) attribute_warn_unused_result
Adds a scheduled event.
Definition: sched.c:567
const char * usage
Definition: cli.h:177
Standard Command Line Interface.
int ast_sched_wait(struct ast_sched_context *con) attribute_warn_unused_result
Determines number of seconds until the next outstanding event to take place.
Definition: sched.c:433
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
int64_t ast_tvdiff_us(struct timeval end, struct timeval start)
Computes the difference (in microseconds) between two struct timeval instances.
Definition: time.h:87
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
void ast_sched_context_destroy(struct ast_sched_context *c)
destroys a schedule context
Definition: sched.c:271
Structure for mutex and tracking information.
Definition: lock.h:135