Crossfire Server, Trunk  R20513
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 10
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 static 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;
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 = object_new();
107  object_copy(mon, mon1);
108  object_insert_in_map_at(mon1, test_map, NULL, 0, x, TEST_MAP_SIZE/2);
109 
110  if (x != TEST_MAP_SIZE/2) {
111  mon1 = object_new();
112  object_copy(mon, mon1);
113  object_insert_in_map_at(mon1, test_map, NULL, 0, x, x);
114  }
115  }
116 
117 }
118 
119 static void teardown(void) {
120  free_map(test_map);
121 }
122 
123 static void check_hp(const char *test, int hp_row[TEST_MAP_SIZE], int hp_diag[TEST_MAP_SIZE]) {
124  object *our_mon;
125  int x, diff;
126 
127 #ifdef PRINT_DEBUG_HP
128  printf("\nDumping debug hp for test %s\n ", test);
129 #endif
130 
131  /* Dump the HP of the monsters now. We do it in 2 passes,
132  * as I think it is easier to read that way.
133  */
134  for (x = 0; x < TEST_MAP_SIZE; x++) {
135  our_mon = GET_MAP_OB(test_map, x, TEST_MAP_SIZE/2);
136  if (!our_mon) {
137  fail("Monster destroyed at %d, %d\n", x, TEST_MAP_SIZE/2);
138  continue;
139  }
140  fail_unless(mon->name == our_mon->name, "Could not find our monster on the space?");
141 
142 #ifdef PRINT_DEBUG_HP
143  printf("%d, ", our_mon->stats.hp);
144 #else
145 
146  if (our_mon->stats.hp == hp_row[x]) {
147  diff = 0;
148  } else if (our_mon->stats.hp < hp_row[x]) {
149  diff = 100-(STARTING_HP-hp_row[x])*100/((STARTING_HP-our_mon->stats.hp) ? (STARTING_HP-our_mon->stats.hp) : 1);
150  } else {
151  diff = -(100-(STARTING_HP-our_mon->stats.hp)*100/((STARTING_HP-hp_row[x]) ? (STARTING_HP-hp_row[x]) : 1));
152  }
153 
154  if (FABS(diff) > HP_DELTA) {
155  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);
156  }
157 #endif
158  }
159 
160 #ifdef PRINT_DEBUG_HP
161  printf("\n\n");
162 #endif
163 
164  for (x = 0; x < TEST_MAP_SIZE; x++) {
165  our_mon = GET_MAP_OB(test_map, x, x);
166  if (!our_mon) {
167  fprintf(stderr, "Monster destroyed at %d, %d\n", x, x);
168  continue;
169  }
170 
171  fail_unless(mon->name == our_mon->name, "Could not find our monster on the space?");
172 
173 #ifdef PRINT_DEBUG_HP
174  printf("%d, ", our_mon->stats.hp);
175 #else
176  if (our_mon->stats.hp == hp_diag[x]) {
177  diff = 0;
178  } else if (our_mon->stats.hp < hp_diag[x]) {
179  diff = 100-(STARTING_HP-hp_diag[x])*100/((STARTING_HP-our_mon->stats.hp) ? (STARTING_HP-our_mon->stats.hp) : 1);
180  } else {
181  diff = -(100-(STARTING_HP-our_mon->stats.hp)*100/((STARTING_HP-hp_diag[x]) ? (STARTING_HP-hp_diag[x]) : 1));
182  }
183 
184  if (FABS(diff) > HP_DELTA) {
185  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);
186  }
187 #endif
188  }
189 }
190 
191 START_TEST(cast_one_comet) {
192  int hp_row[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 24924, 24920, 24916, 24912, 24908, 24904, 24900, 24896, 24892, 24888, 24884, 24880, 24869, 24880, 24884, 24888, 24892, 24896, 24900, 24904, 24908, 24912, 24916, 24920, 24924, 25000, 25000, 25000, 25000, 25000, 25000, 25000 },
193  hp_diag[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 24924, 24920, 24916, 24912, 24908, 24904, 24900, 24896, 24892, 24888, 24884, 24880, 24869, 24880, 24884, 24888, 24892, 24896, 24900, 24904, 24908, 24912, 24916, 24920, 24924, 25000, 25000, 25000, 25000, 25000, 25000, 25000 };
194  object *comet, *rod;
195  int tick;
196 
197  rod = create_archetype("rod_heavy");
198  rod->level = 100;
199  comet = create_archetype("spell_comet");
200  object_insert_in_ob(comet, rod);
201 
202  object_insert_in_map_at(rod, test_map, NULL, 0, TEST_MAP_SIZE/2, TEST_MAP_SIZE-1);
203 
204  cast_spell(rod, rod, 1, rod->inv, NULL);
205  for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
206  process_events();
207  }
208 
209  check_hp("cast_one_comet", hp_row, hp_diag);
210 }
211 END_TEST
212 
213 START_TEST(cast_random_comet) {
214  object *comet, *rod;
215  int tick, num_cast = 0;
216  int hp_row[TEST_MAP_SIZE] = {23756, 23617, 23516, 23428, 23397, 23291, 23203, 23097, 23014, 22875, 22801, 22782, 22706, 22707, 22620, 22645, 22646, 22595, 22705, 22773, 22809, 22835, 22975, 23098, 23239, 23346, 23462, 23597, 23627, 23675, 23786, 23888, 24001, 24119, 24206, 24306, 24336, 24455, 24565, 24649 },
217  hp_diag[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 23515, 23351, 23177, 23097, 22946, 22931, 22763, 22706, 22678, 22658, 22728, 22812, 22809, 22712, 22728, 22741, 22726, 22833, 22862, 22967, 23014, 23009, 23167, 23267, 23367, 23459, 23596, 23713, 23750, 23879, 24026, 24160 };
218 
219  rod = create_archetype("rod_heavy");
220  rod->level = 100;
221  comet = create_archetype("spell_comet");
222  object_insert_in_ob(comet, rod);
223 
224  object_insert_in_map_at(rod, test_map, NULL, 0, TEST_MAP_SIZE/2, TEST_MAP_SIZE-1);
225 
226  for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
227  if (num_cast < NUM_COMETS_TO_CAST && (tick%1) == 0) {
228  object_remove(rod);
229 
230  /* The idea here on the x is to shuffle the spaces around
231  * a little, as a more typical case is comets
232  * blowing up on different spaces.
233  */
234  object_insert_in_map_at(rod, test_map, NULL, 0, (tick*59)%37, TEST_MAP_SIZE-1);
235 
236  cast_spell(rod, rod, 1, rod->inv, NULL);
237  num_cast++;
238  }
239  process_events();
240  }
241  check_hp("cast_random_comet", hp_row, hp_diag);
242 
243 }
244 END_TEST
245 
246 START_TEST(cast_bunch_comet) {
247  object *comet, *rod;
248  int tick, num_cast = 0;
249  int hp_row[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 22355, 22262, 22115, 21966, 21837, 21684, 21554, 21424, 21268, 21137, 21006, 20875, 20534, 20875, 21006, 21137, 21268, 21424, 21554, 21684, 21837, 21966, 22115, 22262, 22355, 25000, 25000, 25000, 25000, 25000, 25000, 25000 },
250  hp_diag[TEST_MAP_SIZE] = {25000, 25000, 25000, 25000, 25000, 25000, 25000, 25000, 22355, 22262, 22115, 21966, 21837, 21684, 21554, 21424, 21268, 21137, 21006, 20875, 20534, 20875, 21006, 21137, 21268, 21424, 21554, 21684, 21837, 21966, 22115, 22262, 22355, 25000, 25000, 25000, 25000, 25000, 25000, 25000 };
251 
252  rod = create_archetype("rod_heavy");
253  rod->level = 100;
254  comet = create_archetype("spell_comet");
255  object_insert_in_ob(comet, rod);
256 
257  object_insert_in_map_at(rod, test_map, NULL, 0, TEST_MAP_SIZE/2, TEST_MAP_SIZE-1);
258 
259  for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
260  if (num_cast < NUM_COMETS_TO_CAST && (tick%1) == 0) {
261  cast_spell(rod, rod, 1, rod->inv, NULL);
262  num_cast++;
263  }
264  process_events();
265  }
266  check_hp("cast_bunch_comet", hp_row, hp_diag);
267 
268 }
269 END_TEST
270 
271 static Suite *comet_suite(void) {
272  Suite *s = suite_create("comet");
273  TCase *tc_core = tcase_create("Core");
274 
275  /* check by defaults has a 2 second timeout - that isn't
276  * fast enough on my system - a run of 30 comets takes about
277  * 7 seconds. Setting this to 20 is enough, but on a slower
278  * system may not be.
279  */
280  tcase_set_timeout(tc_core, 20);
281 
282  /*setup and teardown will be called before each test in testcase 'tc_core' */
283  tcase_add_checked_fixture(tc_core, setup, teardown);
284 
285  suite_add_tcase(s, tc_core);
286  tcase_add_test(tc_core, cast_one_comet);
287  tcase_add_test(tc_core, cast_bunch_comet);
288  tcase_add_test(tc_core, cast_random_comet);
289 
290  return s;
291 }
292 
293 int main(void) {
294  int nf;
295 
296  Suite *s = comet_suite();
297  SRunner *sr = srunner_create(s);
298 
299  /* Don't want to fork - if we do, we lose the profile (-pg)
300  * compiled information, which is what I used to determine if
301  * things are more efficient.
302  */
303  srunner_set_fork_status(sr, CK_NOFORK);
304 
305  /* Only want to run this once, so don't put it in setup() */
306  init(0, NULL);
307 
308  srunner_set_xml(sr, LOGDIR "/unit/server/comet.xml");
309  srunner_set_log(sr, LOGDIR "/unit/server/comet.out");
310  srunner_run_all(sr, CK_ENV); /*verbosity from env variable*/
311  nf = srunner_ntests_failed(sr);
312  srunner_free(sr);
313  fprintf(stderr, "Got %"FMT64U" supressions, %"FMT64U" spell merges, %"FMT64U" full tables\n", statistics.spell_suppressions, statistics.spell_merges, statistics.spell_hash_full);
314  return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
315 }
#define NUM_TICKS_TO_RUN
Definition: comet_perf.c:54
struct Statistics statistics
Merged spell statistics.
Definition: init.c:113
mapstruct * get_empty_map(int sizex, int sizey)
Creates and returns a map of the specific size.
Definition: map.c:874
#define SET_FLAG(xyz, p)
Definition: define.h:223
#define FABS(x)
Decstations have trouble with fabs()...
Definition: define.h:22
#define FLAG_STAND_STILL
NPC will not (ever) move.
Definition: define.h:309
uint64_t spell_merges
Number of spell merges done.
Definition: global.h:346
object * mon
Definition: comet_perf.c:74
int main(void)
Definition: comet_perf.c:293
#define FMT64U
Definition: compat.h:13
START_TEST(cast_one_comet)
Definition: comet_perf.c:191
#define TEST_MAP_SIZE
Definition: comet_perf.c:53
mapstruct * test_map
Definition: comet_perf.c:73
Global type definitions and header inclusions.
static void setup(void)
Definition: comet_perf.c:76
void free_map(mapstruct *m)
Frees everything allocated by the given mapstructure.
Definition: map.c:1694
uint64_t spell_hash_full
Number of times spell hash was full.
Definition: global.h:347
#define NUM_COMETS_TO_CAST
Definition: comet_perf.c:55
int16_t hp
Hit Points.
Definition: living.h:39
int16_t y
Position in the map for this object.
Definition: object.h:326
object * object_insert_in_map_at(object *op, mapstruct *m, object *originator, int flag, int x, int y)
Same as object_insert_in_map() except it handle separate coordinates and do a clean job preparing mul...
Definition: object.c:1921
int16_t maxhp
Max hit points.
Definition: living.h:40
object * object_new(void)
Grabs an object from the list of unused objects, makes sure it is initialised, and returns it...
Definition: object.c:1037
object * create_archetype(const char *name)
Finds which archetype matches the given name, and returns a new object containing a copy of the arche...
Definition: arch.c:620
object * object_insert_in_ob(object *op, object *where)
This function inserts the object op in the linked list inside the object environment.
Definition: object.c:2690
float speed_left
How much speed is left to spend this round.
Definition: object.h:329
#define STARTING_HP
Definition: comet_perf.c:56
const char * name
The name of the object, obviously...
Definition: object.h:311
#define HP_DELTA
Definition: comet_perf.c:64
float speed
The overall speed of this object.
Definition: object.h:328
void init(int argc, char **argv)
This is the main server initialization function.
Definition: init.c:978
void process_events(void)
Process all active objects.
Definition: server.c:967
int16_t x
Definition: object.h:326
static void check_hp(const char *test, int hp_row[TEST_MAP_SIZE], int hp_diag[TEST_MAP_SIZE])
Definition: comet_perf.c:123
int16_t resist[NROFATTACKS]
Resistance adjustments for attacks.
Definition: object.h:341
static END_TEST Suite * comet_suite(void)
Definition: comet_perf.c:271
living stats
Str, Con, Dex, etc.
Definition: object.h:368
uint64_t spell_suppressions
Number of times ok_to_put_more() returned FALSE.
Definition: global.h:348
#define NROFATTACKS
Definition: attack.h:17
#define GET_MAP_OB(M, X, Y)
Gets the bottom object on a map.
Definition: map.h:172
void object_copy(const object *src_ob, object *dest_ob)
Copy object first frees everything allocated by the second object, and then copies the contents of th...
Definition: object.c:838
struct obj * inv
Pointer to the first object in the inventory.
Definition: object.h:290
static void teardown(void)
Definition: comet_perf.c:119
This is a game-map.
Definition: map.h:325
int16_t level
Level of creature or object.
Definition: object.h:351
void object_update_speed(object *op)
Updates the speed of an object.
Definition: object.c:1129
int cast_spell(object *op, object *caster, int dir, object *spell_ob, char *stringarg)
Main dispatch when someone casts a spell.
Definition: spell_util.c:1471
void object_remove(object *op)
This function removes the object op from the linked list of objects which it is currently tied to...
Definition: object.c:1654