Crossfire Server, Branch 1.12  R12190
comet_perf.c
Go to the documentation of this file.
00001 /*
00002  * static char *rcsid_check_alchemy_c =
00003  *   "$Id: check_alchemy.c 4640 2006-06-07 21:44:18Z tchize $";
00004  */
00005 
00006 /*
00007  * CrossFire, A Multiplayer game for X-windows
00008  *
00009  * Copyright (C) 2007 Mark Wedel & Crossfire Development Team
00010  * Copyright (C) 1992 Frank Tore Johansen
00011  *
00012  * This program is free software; you can redistribute it and/or modify
00013  * it under the terms of the GNU General Public License as published by
00014  * the Free Software Foundation; either version 2 of the License, or
00015  * (at your option) any later version.
00016  *
00017  * This program is distributed in the hope that it will be useful,
00018  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020  * GNU General Public License for more details.
00021  *
00022  * You should have received a copy of the GNU General Public License
00023  * along with this program; if not, write to the Free Software
00024  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00025  *
00026  * The authors can be reached via e-mail at crossfire-devel@real-time.com
00027  */
00028 
00029 /*
00030  * This tests the comet spell.  My main motivation for writing this
00031  * was to have a consistent test I could use for performance testing.
00032  * But I also wanted to make sure that the results were close before and
00033  * after the performance changes - make the spell use less cpu time
00034  * but having drastically different results probably isn't a good thing
00035  * either.
00036  * To really be useful, everything needs to be compiled with profiling
00037  * (-pg).  This can be done like 'setenv CFLAGS -pg; ./configure;
00038  * make clean; make'.  The make clean is necessary because it won't
00039  * recompile the objects based just on changes the the CFLAGS.
00040  *
00041  * Note that this test, even after performance changes are done, still
00042  * isn't bad - it checks several things - map creation, spell casting,
00043  * etc.  It wouldn't be hard to use this as a template to test things
00044  * like resistance code, etc.
00045  */
00046 
00047 #include <stdlib.h>
00048 #include <check.h>
00049 
00050 #include <global.h>
00051 #include <sproto.h>
00052 
00053 #define TEST_MAP_SIZE    40
00054 #define NUM_TICKS_TO_RUN    500
00055 #define NUM_COMETS_TO_CAST  30
00056 #define STARTING_HP     25000
00057 
00058 /* The percentage, either plus or minus, that the results can
00059  * vary from the baseline and still be considered OK.
00060  * Note a good sanity check to make sure you put in the correct
00061  * values is to set this to 0.0 - in that case, checks should
00062  * pass.
00063  */
00064 #define HP_DELTA    5
00065 
00066 /* The first time you set up a test, you want to dump the
00067  * initial values to put into the hp_row/hp_diag arrays.
00068  * If this is set, it prints those values instead of doing
00069  * a comparision.
00070  */
00071 /*#define PRINT_DEBUG_HP */
00072 
00073 mapstruct *test_map;
00074 object *mon;
00075 
00076 void setup(void) {
00077     object *mon1;
00078     int x, i;
00079 
00080     test_map = get_empty_map(TEST_MAP_SIZE, TEST_MAP_SIZE);
00081 
00082     mon = create_archetype("orc");
00083     fail_unless(mon != NULL, "Unable to find orc object");
00084 
00085     /* We customize the monster a bit so it is consistent -
00086      * give it a bunch of HP so it can survive the attacks,
00087      * set it speed to 0 so it doesn't do anything, etc.
00088      */
00089     for (i = 0; i < NROFATTACKS; i++)
00090         mon->resist[i] = 95;
00091     mon->stats.hp = STARTING_HP;
00092     mon->stats.maxhp = STARTING_HP;
00093     mon->speed = 0.0;
00094     mon->speed_left = 0.0;
00095     SET_FLAG(mon, FLAG_STAND_STILL);
00096     update_ob_speed(mon);
00097 
00098     /* We now make copies of this custom monster and put it into the
00099      * map.  We make a diagonal from one corner to another,
00100      * as well as a line across the middle of the map (\ overlayed with -)
00101      * We could fill most of the map with monsters, but I think the
00102      * diagonal + horizontal should give a pretty representative
00103      * value of creatures being hit.
00104      */
00105     for (x = 0; x < TEST_MAP_SIZE; x++) {
00106         mon1 = get_object();
00107         copy_object(mon, mon1);
00108         mon1->x = x;
00109         mon1->y = TEST_MAP_SIZE/2;
00110         mon1->map = test_map;
00111         insert_ob_in_map(mon1, mon1->map, NULL, 0);
00112 
00113         if (x != TEST_MAP_SIZE/2) {
00114             mon1 = get_object();
00115             copy_object(mon, mon1);
00116             mon1->x = x;
00117             mon1->y = x;
00118             mon1->map = test_map;
00119             insert_ob_in_map(mon1, mon1->map, NULL, 0);
00120         }
00121     }
00122 
00123 }
00124 
00125 void teardown(void) {
00126     free_map(test_map);
00127 }
00128 
00129 void check_hp(const char *test, int hp_row[TEST_MAP_SIZE], int hp_diag[TEST_MAP_SIZE]) {
00130     object *our_mon;
00131     int x, diff;
00132 
00133 #ifdef PRINT_DEBUG_HP
00134     printf("\nDumping debug hp for test %s\n ", test);
00135 #endif
00136 
00137     /* Dump the HP of the monsters now.  We do it in 2 passes,
00138      * as I think it is easier to read that way.
00139      */
00140     for (x = 0; x < TEST_MAP_SIZE; x++) {
00141         our_mon = GET_MAP_OB(test_map, x, TEST_MAP_SIZE/2);
00142         if (!our_mon) {
00143             fail("Monster destroyed at %d, %d\n", x, TEST_MAP_SIZE/2);
00144             continue;
00145         }
00146         fail_unless(mon->name == our_mon->name, "Could not find our monster on the space?");
00147 
00148 #ifdef PRINT_DEBUG_HP
00149         printf("%d, ", our_mon->stats.hp);
00150 #else
00151 
00152         if (our_mon->stats.hp == hp_row[x]) {
00153             diff = 0;
00154         } else  if (our_mon->stats.hp < hp_row[x]) {
00155             diff = 100-(STARTING_HP-hp_row[x])*100/((STARTING_HP-our_mon->stats.hp) ? (STARTING_HP-our_mon->stats.hp) : 1);
00156         } else {
00157             diff = -(100-(STARTING_HP-our_mon->stats.hp)*100/((STARTING_HP-hp_row[x]) ? (STARTING_HP-hp_row[x]) : 1));
00158         }
00159 
00160         if (FABS(diff) > HP_DELTA) {
00161             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);
00162         }
00163 #endif
00164     }
00165 
00166 #ifdef PRINT_DEBUG_HP
00167     printf("\n\n");
00168 #endif
00169 
00170     for (x = 0; x < TEST_MAP_SIZE; x++) {
00171         our_mon = GET_MAP_OB(test_map, x, x);
00172         if (!our_mon) {
00173             fprintf(stderr, "Monster destroyed at %d, %d\n", x, x);
00174             continue;
00175         }
00176 
00177         fail_unless(mon->name == our_mon->name, "Could not find our monster on the space?");
00178 
00179 #ifdef PRINT_DEBUG_HP
00180         printf("%d, ", our_mon->stats.hp);
00181 #else
00182         if (our_mon->stats.hp == hp_diag[x]) {
00183             diff = 0;
00184         } else if (our_mon->stats.hp < hp_diag[x]) {
00185             diff = 100-(STARTING_HP-hp_diag[x])*100/((STARTING_HP-our_mon->stats.hp) ? (STARTING_HP-our_mon->stats.hp) : 1);
00186         } else {
00187             diff = -(100-(STARTING_HP-our_mon->stats.hp)*100/((STARTING_HP-hp_diag[x]) ? (STARTING_HP-hp_diag[x]) : 1));
00188         }
00189 
00190         if (FABS(diff) > HP_DELTA) {
00191             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);
00192         }
00193 #endif
00194     }
00195 }
00196 
00197 START_TEST(cast_one_comet) {
00198     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 },
00199         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 };
00200     object *comet, *rod;
00201     int tick;
00202 
00203     rod = create_archetype("rod_heavy");
00204     rod->level = 100;
00205     comet = create_archetype("spell_comet");
00206     insert_ob_in_ob(comet, rod);
00207 
00208     rod->map = test_map;
00209     rod->x = TEST_MAP_SIZE/2;
00210     rod->y = TEST_MAP_SIZE-1;
00211 
00212     insert_ob_in_map(rod, rod->map, NULL, 0);
00213 
00214     cast_spell(rod, rod, 1, rod->inv, NULL);
00215     for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
00216         process_events();
00217     }
00218 
00219     check_hp("cast_one_comet", hp_row, hp_diag);
00220 }
00221 END_TEST
00222 
00223 START_TEST(cast_random_comet) {
00224     object *comet, *rod;
00225     int tick, num_cast = 0;
00226     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 },
00227         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 };
00228 
00229     rod = create_archetype("rod_heavy");
00230     rod->level = 100;
00231     comet = create_archetype("spell_comet");
00232     insert_ob_in_ob(comet, rod);
00233 
00234     rod->map = test_map;
00235     rod->x = TEST_MAP_SIZE/2;
00236     rod->y = TEST_MAP_SIZE-1;
00237 
00238     insert_ob_in_map(rod, rod->map, NULL, 0);
00239 
00240     for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
00241         if (num_cast < NUM_COMETS_TO_CAST && (tick%1) == 0) {
00242             remove_ob(rod);
00243 
00244             /* The idea here on the x is to shuffle the spaces around
00245              * a little, as a more typical case is comets
00246              * blowing up on different spaces.
00247              */
00248             rod->x = (tick*59)%37;
00249             rod->y = TEST_MAP_SIZE-1;
00250             rod->map = test_map;
00251             insert_ob_in_map(rod, rod->map, NULL, 0);
00252 
00253             cast_spell(rod, rod, 1, rod->inv, NULL);
00254             num_cast++;
00255         }
00256         process_events();
00257     }
00258     check_hp("cast_random_comet", hp_row, hp_diag);
00259 
00260 }
00261 END_TEST
00262 
00263 START_TEST(cast_bunch_comet) {
00264     object *comet, *rod;
00265     int tick, num_cast = 0;
00266     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 },
00267         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 };
00268 
00269     rod = create_archetype("rod_heavy");
00270     rod->level = 100;
00271     comet = create_archetype("spell_comet");
00272     insert_ob_in_ob(comet, rod);
00273 
00274     rod->map = test_map;
00275     rod->x = TEST_MAP_SIZE/2;
00276     rod->y = TEST_MAP_SIZE-1;
00277 
00278     insert_ob_in_map(rod, rod->map, NULL, 0);
00279 
00280     for (tick = 0; tick < NUM_TICKS_TO_RUN; tick++) {
00281         if (num_cast < NUM_COMETS_TO_CAST && (tick%1) == 0) {
00282             cast_spell(rod, rod, 1, rod->inv, NULL);
00283             num_cast++;
00284         }
00285         process_events();
00286     }
00287     check_hp("cast_bunch_comet", hp_row, hp_diag);
00288 
00289 }
00290 END_TEST
00291 
00292 Suite *comet_suite(void) {
00293     Suite *s = suite_create("comet");
00294     TCase *tc_core = tcase_create("Core");
00295 
00296     /* check by defaults has a 2 second timeout - that isn't
00297      * fast enough on my system - a run of 30 comets takes about
00298      * 7 seconds.  Setting this to 20 is enough, but on a slower
00299      * system may not be.
00300      */
00301     tcase_set_timeout(tc_core, 20);
00302 
00303     /*setup and teardown will be called before each test in testcase 'tc_core' */
00304     tcase_add_checked_fixture(tc_core, setup, teardown);
00305 
00306     suite_add_tcase(s, tc_core);
00307     tcase_add_test(tc_core, cast_one_comet);
00308     tcase_add_test(tc_core, cast_bunch_comet);
00309     tcase_add_test(tc_core, cast_random_comet);
00310 
00311     return s;
00312 }
00313 
00314 int main(void) {
00315     int nf;
00316 
00317     Suite *s = comet_suite();
00318     SRunner *sr = srunner_create(s);
00319 
00320     /* Don't want to fork - if we do, we lose the profile (-pg)
00321      * compiled information, which is what I used to determine if
00322      * things are more efficient.
00323      */
00324     srunner_set_fork_status(sr, CK_NOFORK);
00325 
00326     /* Only want to run this once, so don't put it in setup() */
00327     init(0, NULL);
00328 
00329     srunner_set_xml(sr, LOGDIR "/unit/server/comet.xml");
00330     srunner_set_log(sr, LOGDIR "/unit/server/comet.out");
00331     srunner_run_all(sr, CK_ENV); /*verbosity from env variable*/
00332     nf = srunner_ntests_failed(sr);
00333     srunner_free(sr);
00334     fprintf(stderr, "Got %"FMT64U" supressions, %"FMT64U" spell merges, %"FMT64U" full tables\n", statistics.spell_suppressions, statistics.spell_merges, statistics.spell_hash_full);
00335     return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
00336 }