Crossfire Server, Branches 1.12  R18729
comet_perf.c
Go to the documentation of this file.
1 /*
2  * static char *rcsid_check_alchemy_c =
3  * "$Id: check_alchemy.c 4640 2006-06-07 21:44:18Z tchize $";
4  */
5 
6 /*
7  * CrossFire, A Multiplayer game for X-windows
8  *
9  * Copyright (C) 2007 Mark Wedel & Crossfire Development Team
10  * Copyright (C) 1992 Frank Tore Johansen
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25  *
26  * The authors can be reached via e-mail at crossfire-devel@real-time.com
27  */
28 
29 /*
30  * This tests the comet spell. My main motivation for writing this
31  * was to have a consistent test I could use for performance testing.
32  * But I also wanted to make sure that the results were close before and
33  * after the performance changes - make the spell use less cpu time
34  * but having drastically different results probably isn't a good thing
35  * either.
36  * To really be useful, everything needs to be compiled with profiling
37  * (-pg). This can be done like 'setenv CFLAGS -pg; ./configure;
38  * make clean; make'. The make clean is necessary because it won't
39  * recompile the objects based just on changes the the CFLAGS.
40  *
41  * Note that this test, even after performance changes are done, still
42  * isn't bad - it checks several things - map creation, spell casting,
43  * etc. It wouldn't be hard to use this as a template to test things
44  * like resistance code, etc.
45  */
46 
47 #include <stdlib.h>
48 #include <check.h>
49 
50 #include <global.h>
51 #include <sproto.h>
52 
53 #define TEST_MAP_SIZE 40
54 #define NUM_TICKS_TO_RUN 500
55 #define NUM_COMETS_TO_CAST 30
56 #define STARTING_HP 25000
57 
58 /* The percentage, either plus or minus, that the results can
59  * vary from the baseline and still be considered OK.
60  * Note a good sanity check to make sure you put in the correct
61  * values is to set this to 0.0 - in that case, checks should
62  * pass.
63  */
64 #define HP_DELTA 5
65 
66 /* The first time you set up a test, you want to dump the
67  * initial values to put into the hp_row/hp_diag arrays.
68  * If this is set, it prints those values instead of doing
69  * a comparision.
70  */
71 /*#define PRINT_DEBUG_HP */
72 
74 object *mon;
75 
76 void setup(void) {
77  object *mon1;
78  int x, i;
79 
81 
82  mon = create_archetype("orc");
83  fail_unless(mon != NULL, "Unable to find orc object");
84 
85  /* We customize the monster a bit so it is consistent -
86  * give it a bunch of HP so it can survive the attacks,
87  * set it speed to 0 so it doesn't do anything, etc.
88  */
89  for (i = 0; i < NROFATTACKS; i++)
90  mon->resist[i] = 95;
91  mon->stats.hp = STARTING_HP;
92  mon->stats.maxhp = STARTING_HP;
93  mon->speed = 0.0;
94  mon->speed_left = 0.0;
96  update_ob_speed(mon);
97 
98  /* We now make copies of this custom monster and put it into the
99  * map. We make a diagonal from one corner to another,
100  * as well as a line across the middle of the map (\ overlayed with -)
101  * We could fill most of the map with monsters, but I think the
102  * diagonal + horizontal should give a pretty representative
103  * value of creatures being hit.
104  */
105  for (x = 0; x < TEST_MAP_SIZE; x++) {
106  mon1 = get_object();
107  copy_object(mon, mon1);
108  mon1->x = x;
109  mon1->y = TEST_MAP_SIZE/2;
110  mon1->map = test_map;
111  insert_ob_in_map(mon1, mon1->map, NULL, 0);
112 
113  if (x != TEST_MAP_SIZE/2) {
114  mon1 = get_object();
115  copy_object(mon, mon1);
116  mon1->x = x;
117  mon1->y = x;
118  mon1->map = test_map;
119  insert_ob_in_map(mon1, mon1->map, NULL, 0);
120  }
121  }
122 
123 }
124 
125 void teardown(void) {
126  free_map(test_map);
127 }
128 
129 void check_hp(const char *test, int hp_row[TEST_MAP_SIZE], int hp_diag[TEST_MAP_SIZE]) {
130  object *our_mon;
131  int x, diff;
132 
133 #ifdef PRINT_DEBUG_HP
134  printf("\nDumping debug hp for test %s\n ", test);
135 #endif
136 
137  /* Dump the HP of the monsters now. We do it in 2 passes,
138  * as I think it is easier to read that way.
139  */
140  for (x = 0; x < TEST_MAP_SIZE; x++) {
141  our_mon = GET_MAP_OB(test_map, x, TEST_MAP_SIZE/2);
142  if (!our_mon) {
143  fail("Monster destroyed at %d, %d\n", x, TEST_MAP_SIZE/2);
144  continue;
145  }
146  fail_unless(mon->name == our_mon->name, "Could not find our monster on the space?");
147 
148 #ifdef PRINT_DEBUG_HP
149  printf("%d, ", our_mon->stats.hp);
150 #else
151 
152  if (our_mon->stats.hp == hp_row[x]) {
153  diff = 0;
154  } else if (our_mon->stats.hp < hp_row[x]) {
155  diff = 100-(STARTING_HP-hp_row[x])*100/((STARTING_HP-our_mon->stats.hp) ? (STARTING_HP-our_mon->stats.hp) : 1);
156  } else {
157  diff = -(100-(STARTING_HP-our_mon->stats.hp)*100/((STARTING_HP-hp_row[x]) ? (STARTING_HP-hp_row[x]) : 1));
158  }
159 
160  if (FABS(diff) > HP_DELTA) {
161  fail("Mon (%d, %d) has hp out of range (%d != %d +/- %d, diff %d)\n", our_mon->x, our_mon->y, our_mon->stats.hp, hp_row[x], HP_DELTA, diff);
162  }
163 #endif
164  }
165 
166 #ifdef PRINT_DEBUG_HP
167  printf("\n\n");
168 #endif
169 
170  for (x = 0; x < TEST_MAP_SIZE; x++) {
171  our_mon = GET_MAP_OB(test_map, x, x);
172  if (!our_mon) {
173  fprintf(stderr, "Monster destroyed at %d, %d\n", x, x);
174  continue;
175  }
176 
177  fail_unless(mon->name == our_mon->name, "Could not find our monster on the space?");
178 
179 #ifdef PRINT_DEBUG_HP
180  printf("%d, ", our_mon->stats.hp);
181 #else
182  if (our_mon->stats.hp == hp_diag[x]) {
183  diff = 0;
184  } else if (our_mon->stats.hp < hp_diag[x]) {
185  diff = 100-(STARTING_HP-hp_diag[x])*100/((STARTING_HP-our_mon->stats.hp) ? (STARTING_HP-our_mon->stats.hp) : 1);
186  } else {
187  diff = -(100-(STARTING_HP-our_mon->stats.hp)*100/((STARTING_HP-hp_diag[x]) ? (STARTING_HP-hp_diag[x]) : 1));
188  }
189 
190  if (FABS(diff) > HP_DELTA) {
191  fail("Mon (%d, %d) has hp out of range (%d != %d +/- %d, diff %d)\n", our_mon->x, our_mon->y, our_mon->stats.hp, hp_diag[x], HP_DELTA, diff);
192  }
193 #endif
194  }
195 }
196 
197 START_TEST(cast_one_comet) {
198  int hp_row[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 24895, 24890, 24885, 24880, 24875, 24870, 24865, 24860, 24855, 24850, 24845, 24840, 24827, 24840, 24845, 24850, 24855, 24860, 24865, 24870, 24875, 24880, 24885, 24890, 24895, 25000, 25000, 25000, 25000, 25000, 25000, 25000 },
199  hp_diag[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 24895, 24890, 24885, 24880, 24875, 24870, 24865, 24860, 24855, 24850, 24845, 24840, 24827, 24840, 24845, 24850, 24855, 24860, 24865, 24870, 24875, 24880, 24885, 24890, 24895, 25000, 25000, 25000, 25000, 25000, 25000, 25000 };
200  object *comet, *rod;
201  int tick;
202 
203  rod = create_archetype("rod_heavy");
204  rod->level = 100;
205  comet = create_archetype("spell_comet");
206  insert_ob_in_ob(comet, rod);
207 
208  rod->map = test_map;
209  rod->x = TEST_MAP_SIZE/2;
210  rod->y = TEST_MAP_SIZE-1;
211 
212  insert_ob_in_map(rod, rod->map, NULL, 0);
213 
214  cast_spell(rod, rod, 1, rod->inv, NULL);
215  for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
216  process_events();
217  }
218 
219  check_hp("cast_one_comet", hp_row, hp_diag);
220 }
221 END_TEST
222 
223 START_TEST(cast_random_comet) {
224  object *comet, *rod;
225  int tick, num_cast = 0;
226  int hp_row[TEST_MAP_SIZE] = {23522, 23380, 23217, 23172, 23137, 23007, 22882, 22762, 22655, 22527, 22412, 22407, 22307, 22312, 22217, 22235, 22235, 22217, 22312, 22412, 22412, 22470, 22620, 22770, 22915, 23060, 23200, 23335, 23365, 23400, 23535, 23670, 23800, 23930, 24055, 24180, 24200, 24325, 24445, 24565 },
227  hp_diag[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 23215, 23025, 22830, 22745, 22570, 22510, 22360, 22315, 22280, 22255, 22340, 22425, 22412, 22312, 22317, 22330, 22317, 22417, 22522, 22632, 22647, 22672, 22815, 22945, 23062, 23192, 23327, 23467, 23512, 23675, 23825, 23970 };
228 
229  rod = create_archetype("rod_heavy");
230  rod->level = 100;
231  comet = create_archetype("spell_comet");
232  insert_ob_in_ob(comet, rod);
233 
234  rod->map = test_map;
235  rod->x = TEST_MAP_SIZE/2;
236  rod->y = TEST_MAP_SIZE-1;
237 
238  insert_ob_in_map(rod, rod->map, NULL, 0);
239 
240  for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
241  if (num_cast < NUM_COMETS_TO_CAST && (tick%1) == 0) {
242  remove_ob(rod);
243 
244  /* The idea here on the x is to shuffle the spaces around
245  * a little, as a more typical case is comets
246  * blowing up on different spaces.
247  */
248  rod->x = (tick*59)%37;
249  rod->y = TEST_MAP_SIZE-1;
250  rod->map = test_map;
251  insert_ob_in_map(rod, rod->map, NULL, 0);
252 
253  cast_spell(rod, rod, 1, rod->inv, NULL);
254  num_cast++;
255  }
256  process_events();
257  }
258  check_hp("cast_random_comet", hp_row, hp_diag);
259 
260 }
261 END_TEST
262 
263 START_TEST(cast_bunch_comet) {
264  object *comet, *rod;
265  int tick, num_cast = 0;
266  int hp_row[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 21850, 21700, 21550, 21400, 21250, 21100, 20950, 20800, 20650, 20500, 20350, 20200, 19810, 20200, 20350, 20500, 20650, 20800, 20950, 21100, 21250, 21400, 21550, 21700, 21850, 25000, 25000, 25000, 25000, 25000, 25000, 25000 },
267  hp_diag[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 21850, 21700, 21550, 21400, 21250, 21100, 20950, 20800, 20650, 20500, 20350, 20200, 19810, 20200, 20350, 20500, 20650, 20800, 20950, 21100, 21250, 21400, 21550, 21700, 21850, 25000, 25000, 25000, 25000, 25000, 25000, 25000 };
268 
269  rod = create_archetype("rod_heavy");
270  rod->level = 100;
271  comet = create_archetype("spell_comet");
272  insert_ob_in_ob(comet, rod);
273 
274  rod->map = test_map;
275  rod->x = TEST_MAP_SIZE/2;
276  rod->y = TEST_MAP_SIZE-1;
277 
278  insert_ob_in_map(rod, rod->map, NULL, 0);
279 
280  for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
281  if (num_cast < NUM_COMETS_TO_CAST && (tick%1) == 0) {
282  cast_spell(rod, rod, 1, rod->inv, NULL);
283  num_cast++;
284  }
285  process_events();
286  }
287  check_hp("cast_bunch_comet", hp_row, hp_diag);
288 
289 }
290 END_TEST
291 
292 Suite *comet_suite(void) {
293  Suite *s = suite_create("comet");
294  TCase *tc_core = tcase_create("Core");
295 
296  /* check by defaults has a 2 second timeout - that isn't
297  * fast enough on my system - a run of 30 comets takes about
298  * 7 seconds. Setting this to 20 is enough, but on a slower
299  * system may not be.
300  */
301  tcase_set_timeout(tc_core, 20);
302 
303  /*setup and teardown will be called before each test in testcase 'tc_core' */
304  tcase_add_checked_fixture(tc_core, setup, teardown);
305 
306  suite_add_tcase(s, tc_core);
307  tcase_add_test(tc_core, cast_one_comet);
308  tcase_add_test(tc_core, cast_bunch_comet);
309  tcase_add_test(tc_core, cast_random_comet);
310 
311  return s;
312 }
313 
314 int main(void) {
315  int nf;
316 
317  Suite *s = comet_suite();
318  SRunner *sr = srunner_create(s);
319 
320  /* Don't want to fork - if we do, we lose the profile (-pg)
321  * compiled information, which is what I used to determine if
322  * things are more efficient.
323  */
324  srunner_set_fork_status(sr, CK_NOFORK);
325 
326  /* Only want to run this once, so don't put it in setup() */
327  init(0, NULL);
328 
329  srunner_set_xml(sr, LOGDIR "/unit/server/comet.xml");
330  srunner_set_log(sr, LOGDIR "/unit/server/comet.out");
331  srunner_run_all(sr, CK_ENV); /*verbosity from env variable*/
332  nf = srunner_ntests_failed(sr);
333  srunner_free(sr);
334  fprintf(stderr, "Got %"FMT64U" supressions, %"FMT64U" spell merges, %"FMT64U" full tables\n", statistics.spell_suppressions, statistics.spell_merges, statistics.spell_hash_full);
335  return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
336 }
#define NUM_TICKS_TO_RUN
Definition: comet_perf.c:54
END_TEST Suite * comet_suite(void)
Definition: comet_perf.c:292
struct Statistics statistics
Definition: init.c:119
mapstruct * get_empty_map(int sizex, int sizey)
Definition: map.c:884
#define SET_FLAG(xyz, p)
Definition: define.h:510
#define FABS(x)
Definition: define.h:61
uint64 spell_suppressions
Definition: global.h:426
#define FLAG_STAND_STILL
Definition: define.h:605
object * mon
Definition: comet_perf.c:74
int main(void)
Definition: comet_perf.c:314
START_TEST(cast_one_comet)
Definition: comet_perf.c:197
#define TEST_MAP_SIZE
Definition: comet_perf.c:53
mapstruct * test_map
Definition: comet_perf.c:73
sint16 x
Definition: object.h:179
void free_map(mapstruct *m)
Definition: map.c:1707
#define NUM_COMETS_TO_CAST
Definition: comet_perf.c:55
sint16 hp
Definition: living.h:81
void remove_ob(object *op)
Definition: object.c:1515
sint16 maxhp
Definition: living.h:82
object * create_archetype(const char *name)
Definition: arch.c:625
float speed_left
Definition: object.h:182
uint64 spell_merges
Definition: global.h:424
struct mapdef * map
Definition: object.h:155
#define STARTING_HP
Definition: comet_perf.c:56
uint64 spell_hash_full
Definition: global.h:425
const char * name
Definition: object.h:167
#define HP_DELTA
Definition: comet_perf.c:64
sint16 y
Definition: object.h:179
float speed
Definition: object.h:181
void init(int argc, char **argv)
Definition: init.c:905
void process_events(void)
Definition: server.c:1026
void teardown(void)
Definition: comet_perf.c:125
object * insert_ob_in_ob(object *op, object *where)
Definition: object.c:2510
object * get_object(void)
Definition: object.c:921
object * insert_ob_in_map(object *op, mapstruct *m, object *originator, int flag)
Definition: object.c:1992
sint16 resist[NROFATTACKS]
Definition: object.h:192
living stats
Definition: object.h:219
#define NROFATTACKS
Definition: attack.h:45
void update_ob_speed(object *op)
Definition: object.c:1008
#define GET_MAP_OB(M, X, Y)
Definition: map.h:193
struct obj * inv
Definition: object.h:148
void copy_object(object *op2, object *op)
Definition: object.c:758
Definition: map.h:346
sint16 level
Definition: object.h:202
void check_hp(const char *test, int hp_row[TEST_MAP_SIZE], int hp_diag[TEST_MAP_SIZE])
Definition: comet_perf.c:129
void setup(void)
Definition: comet_perf.c:76
int cast_spell(object *op, object *caster, int dir, object *spell_ob, char *stringarg)
Definition: spell_util.c:1308