LCOV - code coverage report
Current view: top level - src/bin/pg_test_timing - pg_test_timing.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 85.9 % 185 159
Test Date: 2026-05-27 20:16:27 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  *  pg_test_timing.c
       3              :  *      tests overhead of timing calls and their monotonicity:  that
       4              :  *      they always move forward
       5              :  */
       6              : 
       7              : #include "postgres_fe.h"
       8              : 
       9              : #include <limits.h>
      10              : #include <math.h>
      11              : 
      12              : #include "getopt_long.h"
      13              : #include "port/pg_bitutils.h"
      14              : #include "portability/instr_time.h"
      15              : 
      16              : static const char *progname;
      17              : 
      18              : static unsigned int test_duration = 3;
      19              : static double max_rprct = 99.99;
      20              : 
      21              : /* record duration in powers of 2 nanoseconds */
      22              : static long long int histogram[64];
      23              : 
      24              : /* record counts of first 10K durations directly */
      25              : #define NUM_DIRECT 10000
      26              : static long long int direct_histogram[NUM_DIRECT];
      27              : 
      28              : /* separately record highest observed duration */
      29              : static int64 largest_diff;
      30              : static long long int largest_diff_count;
      31              : 
      32              : 
      33              : static void handle_args(int argc, char *argv[]);
      34              : static void test_system_timing(void);
      35              : #if PG_INSTR_TSC_CLOCK
      36              : static void test_tsc_timing(void);
      37              : #endif
      38              : static uint64 test_timing(unsigned int duration, TimingClockSourceType source, bool fast_timing);
      39              : static void output(uint64 loop_count);
      40              : 
      41              : int
      42            7 : main(int argc, char *argv[])
      43              : {
      44            7 :     set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_test_timing"));
      45            7 :     progname = get_progname(argv[0]);
      46              : 
      47            7 :     handle_args(argc, argv);
      48              : 
      49              :     /* initialize timing infrastructure (required for INSTR_* calls) */
      50            1 :     pg_initialize_timing();
      51              : 
      52            1 :     test_system_timing();
      53              : 
      54              : #if PG_INSTR_TSC_CLOCK
      55            1 :     test_tsc_timing();
      56              : #endif
      57              : 
      58            1 :     return 0;
      59              : }
      60              : 
      61              : static void
      62            7 : handle_args(int argc, char *argv[])
      63              : {
      64              :     static struct option long_options[] = {
      65              :         {"duration", required_argument, NULL, 'd'},
      66              :         {"cutoff", required_argument, NULL, 'c'},
      67              :         {NULL, 0, NULL, 0}
      68              :     };
      69              : 
      70              :     int         option;         /* Command line option */
      71            7 :     int         optindex = 0;   /* used by getopt_long */
      72              :     unsigned long optval;       /* used for option parsing */
      73              :     char       *endptr;
      74              : 
      75            7 :     if (argc > 1)
      76              :     {
      77            7 :         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
      78              :         {
      79            1 :             printf(_("Usage: %s [-d DURATION] [-c CUTOFF]\n"), progname);
      80            1 :             exit(0);
      81              :         }
      82            6 :         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
      83              :         {
      84            1 :             puts("pg_test_timing (PostgreSQL) " PG_VERSION);
      85            1 :             exit(0);
      86              :         }
      87              :     }
      88              : 
      89            6 :     while ((option = getopt_long(argc, argv, "d:c:",
      90            6 :                                  long_options, &optindex)) != -1)
      91              :     {
      92            5 :         switch (option)
      93              :         {
      94            3 :             case 'd':
      95            3 :                 errno = 0;
      96            3 :                 optval = strtoul(optarg, &endptr, 10);
      97              : 
      98            3 :                 if (endptr == optarg || *endptr != '\0' ||
      99            2 :                     errno != 0 || optval != (unsigned int) optval)
     100              :                 {
     101            1 :                     fprintf(stderr, _("%s: invalid argument for option %s\n"),
     102              :                             progname, "--duration");
     103            1 :                     fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
     104            1 :                     exit(1);
     105              :                 }
     106              : 
     107            2 :                 test_duration = (unsigned int) optval;
     108            2 :                 if (test_duration == 0)
     109              :                 {
     110            1 :                     fprintf(stderr, _("%s: %s must be in range %u..%u\n"),
     111              :                             progname, "--duration", 1, UINT_MAX);
     112            1 :                     exit(1);
     113              :                 }
     114            1 :                 break;
     115              : 
     116            1 :             case 'c':
     117            1 :                 errno = 0;
     118            1 :                 max_rprct = strtod(optarg, &endptr);
     119              : 
     120            1 :                 if (endptr == optarg || *endptr != '\0' || errno != 0)
     121              :                 {
     122            0 :                     fprintf(stderr, _("%s: invalid argument for option %s\n"),
     123              :                             progname, "--cutoff");
     124            0 :                     fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
     125            0 :                     exit(1);
     126              :                 }
     127              : 
     128            1 :                 if (max_rprct < 0 || max_rprct > 100)
     129              :                 {
     130            1 :                     fprintf(stderr, _("%s: %s must be in range %u..%u\n"),
     131              :                             progname, "--cutoff", 0, 100);
     132            1 :                     exit(1);
     133              :                 }
     134            0 :                 break;
     135              : 
     136            1 :             default:
     137            1 :                 fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
     138              :                         progname);
     139            1 :                 exit(1);
     140              :                 break;
     141              :         }
     142              :     }
     143              : 
     144            1 :     if (argc > optind)
     145              :     {
     146            0 :         fprintf(stderr,
     147            0 :                 _("%s: too many command-line arguments (first is \"%s\")\n"),
     148            0 :                 progname, argv[optind]);
     149            0 :         fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
     150              :                 progname);
     151            0 :         exit(1);
     152              :     }
     153              : 
     154            1 :     printf(ngettext("Testing timing overhead for %u second.\n\n",
     155              :                     "Testing timing overhead for %u seconds.\n\n",
     156              :                     test_duration),
     157              :            test_duration);
     158            1 : }
     159              : 
     160              : /*
     161              :  * This tests default (non-fast) timing code. A clock source for that is
     162              :  * always available. Hence, we can unconditionally output the result.
     163              :  */
     164              : static void
     165            1 : test_system_timing(void)
     166              : {
     167              :     uint64      loop_count;
     168              : 
     169            1 :     loop_count = test_timing(test_duration, TIMING_CLOCK_SOURCE_SYSTEM, false);
     170            1 :     output(loop_count);
     171            1 : }
     172              : 
     173              : /*
     174              :  * If on a supported architecture, test the TSC clock source. This clock
     175              :  * source is not always available. In that case we print an informational
     176              :  * message indicating as such.
     177              :  *
     178              :  * We first emit "slow" timings (RDTSCP on x86), which are used for higher
     179              :  * precision measurements when the TSC clock source is enabled. We emit
     180              :  * "fast" timings second (RDTSC on x86), which is used for faster timing
     181              :  * measurements with lower precision.
     182              :  */
     183              : #if PG_INSTR_TSC_CLOCK
     184              : static void
     185            1 : test_tsc_timing(void)
     186              : {
     187              :     uint64      loop_count;
     188              :     const TscClockSourceInfo *info;
     189              : 
     190            1 :     printf("\n");
     191            1 :     loop_count = test_timing(test_duration, TIMING_CLOCK_SOURCE_TSC, false);
     192            1 :     if (loop_count > 0)
     193              :     {
     194            1 :         output(loop_count);
     195            1 :         printf("\n");
     196              : 
     197              :         /* Now, emit fast timing measurements */
     198            1 :         loop_count = test_timing(test_duration, TIMING_CLOCK_SOURCE_TSC, true);
     199            1 :         output(loop_count);
     200            1 :         printf("\n");
     201              :     }
     202              : 
     203              :     /*
     204              :      * Report TSC information regardless of whether it was usable, makes
     205              :      * debugging a lot easier.
     206              :      */
     207            1 :     info = pg_timing_tsc_clock_source_info();
     208            1 :     if (info->frequency_source[0] != '\0')
     209            1 :         printf(_("TSC frequency source: %s\n"), info->frequency_source);
     210            1 :     printf(_("TSC frequency in use: %d kHz\n"), info->frequency_khz);
     211              : 
     212            1 :     if (info->calibrated_frequency_khz > 0)
     213              :     {
     214              :         double      diff_pct;
     215              : 
     216            1 :         printf(_("TSC frequency from calibration: %d kHz\n"), info->calibrated_frequency_khz);
     217              : 
     218            1 :         diff_pct = fabs((double) info->calibrated_frequency_khz - info->frequency_khz) /
     219            1 :             info->frequency_khz * 100.0;
     220              : 
     221            1 :         if (diff_pct > 10.0)
     222              :         {
     223            0 :             printf(_("WARNING: Calibrated TSC frequency differs by %.1f%% from the TSC frequency in use\n"),
     224              :                    diff_pct);
     225            0 :             printf(_("HINT: Consider setting timing_clock_source to 'system'. Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
     226            0 :             exit(1);
     227              :         }
     228              :     }
     229              :     else
     230            0 :         printf(_("TSC calibration did not converge\n"));
     231              : 
     232              :     /*
     233              :      * Report whether TSC was usable and, if so, whether it will be used
     234              :      * automatically.
     235              :      */
     236            1 :     if (loop_count > 0)
     237              :     {
     238            1 :         pg_set_timing_clock_source(TIMING_CLOCK_SOURCE_AUTO);
     239            1 :         if (pg_current_timing_clock_source() == TIMING_CLOCK_SOURCE_TSC)
     240            1 :             printf(_("\nTSC clock source will be used by default, unless timing_clock_source is set to 'system'.\n"));
     241              :         else
     242            0 :             printf(_("\nTSC clock source will not be used by default, unless timing_clock_source is set to 'tsc'.\n"));
     243              :     }
     244              :     else
     245            0 :         printf(_("\nTSC clock source is not usable. Likely unable to determine TSC frequency. Are you running in an unsupported virtualized environment?\n"));
     246            1 : }
     247              : #endif
     248              : 
     249              : static uint64
     250            3 : test_timing(unsigned int duration, TimingClockSourceType source, bool fast_timing)
     251              : {
     252            3 :     uint64      loop_count = 0;
     253              :     instr_time  start_time,
     254              :                 end_time,
     255              :                 prev,
     256              :                 cur;
     257            3 :     const char *time_source = NULL;
     258              : 
     259            3 :     if (!pg_set_timing_clock_source(source))
     260            0 :         return 0;
     261              : 
     262            3 :     time_source = PG_INSTR_SYSTEM_CLOCK_NAME;
     263              : 
     264              : #if PG_INSTR_TSC_CLOCK
     265            3 :     if (pg_current_timing_clock_source() == TIMING_CLOCK_SOURCE_TSC)
     266            2 :         time_source = fast_timing ? PG_INSTR_TSC_CLOCK_NAME_FAST : PG_INSTR_TSC_CLOCK_NAME;
     267              : #endif
     268              : 
     269            3 :     if (fast_timing)
     270            1 :         printf(_("Fast clock source: %s\n"), time_source);
     271            2 :     else if (source == TIMING_CLOCK_SOURCE_SYSTEM)
     272            1 :         printf(_("System clock source: %s\n"), time_source);
     273              :     else
     274            1 :         printf(_("Clock source: %s\n"), time_source);
     275              : 
     276              :     /*
     277              :      * Pre-zero the statistics data structures.  They're already zero by
     278              :      * default, but this helps bring them into processor cache and avoid
     279              :      * possible timing glitches due to COW behavior.
     280              :      */
     281            3 :     memset(direct_histogram, 0, sizeof(direct_histogram));
     282            3 :     memset(histogram, 0, sizeof(histogram));
     283            3 :     largest_diff = 0;
     284            3 :     largest_diff_count = 0;
     285              : 
     286            3 :     INSTR_TIME_SET_CURRENT(start_time);
     287            3 :     cur = start_time;
     288              : 
     289            3 :     end_time = start_time;
     290            3 :     INSTR_TIME_ADD_NANOSEC(end_time, duration * NS_PER_S);
     291              : 
     292     11378833 :     while (INSTR_TIME_GT(end_time, cur))
     293              :     {
     294              :         int64       diff;
     295              :         int32       bits;
     296              :         instr_time  diff_time;
     297              : 
     298     11378830 :         prev = cur;
     299              : 
     300     11378830 :         if (fast_timing)
     301      4019633 :             INSTR_TIME_SET_CURRENT_FAST(cur);
     302              :         else
     303      7359197 :             INSTR_TIME_SET_CURRENT(cur);
     304              : 
     305     11378830 :         diff_time = cur;
     306     11378830 :         INSTR_TIME_SUBTRACT(diff_time, prev);
     307     11378830 :         diff = INSTR_TIME_GET_NANOSEC(diff_time);
     308              : 
     309              :         /* Did time go backwards? */
     310     11378830 :         if (unlikely(diff < 0))
     311              :         {
     312            0 :             fprintf(stderr, _("Detected clock going backwards in time.\n"));
     313            0 :             fprintf(stderr, _("Time warp: %" PRId64 " ns\n"), diff);
     314            0 :             exit(1);
     315              :         }
     316              : 
     317              :         /* What is the highest bit in the time diff? */
     318     11378830 :         if (diff > 0)
     319              :         {
     320     11378830 :             bits = pg_leftmost_one_pos64(diff) + 1;
     321              :             /* histogram should be defined large enough */
     322              :             Assert(bits < lengthof(histogram));
     323              :         }
     324              :         else
     325            0 :             bits = 0;
     326              : 
     327              :         /* Update appropriate duration bucket */
     328     11378830 :         histogram[bits]++;
     329              : 
     330              :         /* Update direct histogram of time diffs */
     331     11378830 :         if (diff < NUM_DIRECT)
     332     11378187 :             direct_histogram[diff]++;
     333              : 
     334              :         /* Also track the largest observed duration, even if >= NUM_DIRECT */
     335     11378830 :         if (diff > largest_diff)
     336              :         {
     337           26 :             largest_diff = diff;
     338           26 :             largest_diff_count = 1;
     339              :         }
     340     11378804 :         else if (diff == largest_diff)
     341            0 :             largest_diff_count++;
     342              : 
     343     11378830 :         loop_count++;
     344              :     }
     345              : 
     346              :     /* Refresh end time to be the actual time spent (vs the target end time) */
     347            3 :     INSTR_TIME_SET_CURRENT(end_time);
     348              : 
     349            3 :     INSTR_TIME_SUBTRACT(end_time, start_time);
     350              : 
     351            3 :     printf(_("Average loop time including overhead: %0.2f ns\n"),
     352              :            INSTR_TIME_GET_DOUBLE(end_time) * NS_PER_S / loop_count);
     353              : 
     354            3 :     return loop_count;
     355              : }
     356              : 
     357              : static void
     358            3 : output(uint64 loop_count)
     359              : {
     360            3 :     int         max_bit = lengthof(histogram) - 1;
     361            3 :     const char *header1 = _("<= ns");
     362            3 :     const char *header1b = _("ns");
     363            3 :     const char *header2 = /* xgettext:no-c-format */ _("% of total");
     364            3 :     const char *header3 = /* xgettext:no-c-format */ _("running %");
     365            3 :     const char *header4 = _("count");
     366            3 :     int         len1 = strlen(header1);
     367            3 :     int         len2 = strlen(header2);
     368            3 :     int         len3 = strlen(header3);
     369            3 :     int         len4 = strlen(header4);
     370              :     double      rprct;
     371            3 :     bool        stopped = false;
     372              : 
     373            3 :     if (loop_count == 0)
     374              :     {
     375            0 :         printf(_("WARNING: No timing measurements collected. Report this as a bug to <%s>.\n"), PACKAGE_BUGREPORT);
     376            0 :         return;
     377              :     }
     378              : 
     379              :     /* find highest bit value */
     380          112 :     while (max_bit > 0 && histogram[max_bit] == 0)
     381          109 :         max_bit--;
     382              : 
     383              :     /* set minimum column widths */
     384            3 :     len1 = Max(19, len1);
     385            3 :     len2 = Max(10, len2);
     386            3 :     len3 = Max(10, len3);
     387            3 :     len4 = Max(10, len4);
     388              : 
     389            3 :     printf(_("Histogram of timing durations:\n"));
     390            3 :     printf("%*s   %*s %*s %*s\n",
     391              :            len1, header1,
     392              :            len2, header2,
     393              :            len3, header3,
     394              :            len4, header4);
     395              : 
     396            3 :     rprct = 0;
     397           86 :     for (int i = 0; i <= max_bit; i++)
     398              :     {
     399           83 :         double      prct = (double) histogram[i] * 100 / loop_count;
     400              : 
     401           83 :         rprct += prct;
     402           83 :         printf("%*llu   %*.4f %*.4f %*lld\n",
     403              :                len1, (1ULL << i) - 1,
     404              :                len2, prct,
     405              :                len3, rprct,
     406              :                len4, histogram[i]);
     407              :     }
     408              : 
     409            3 :     printf(_("\nObserved timing durations up to %.4f%%:\n"), max_rprct);
     410            3 :     printf("%*s   %*s %*s %*s\n",
     411              :            len1, header1b,
     412              :            len2, header2,
     413              :            len3, header3,
     414              :            len4, header4);
     415              : 
     416            3 :     rprct = 0;
     417        30003 :     for (int i = 0; i < NUM_DIRECT; i++)
     418              :     {
     419        30000 :         if (direct_histogram[i])
     420              :         {
     421         1342 :             double      prct = (double) direct_histogram[i] * 100 / loop_count;
     422         1342 :             bool        print_it = !stopped;
     423              : 
     424         1342 :             rprct += prct;
     425              : 
     426              :             /* if largest diff is < NUM_DIRECT, be sure we print it */
     427         1342 :             if (i == largest_diff)
     428              :             {
     429            0 :                 if (stopped)
     430            0 :                     printf("...\n");
     431            0 :                 print_it = true;
     432              :             }
     433              : 
     434         1342 :             if (print_it)
     435          933 :                 printf("%*d   %*.4f %*.4f %*lld\n",
     436              :                        len1, i,
     437              :                        len2, prct,
     438              :                        len3, rprct,
     439              :                        len4, direct_histogram[i]);
     440         1342 :             if (rprct >= max_rprct)
     441          412 :                 stopped = true;
     442              :         }
     443              :     }
     444              : 
     445              :     /* print largest diff when it's outside the array range */
     446            3 :     if (largest_diff >= NUM_DIRECT)
     447              :     {
     448            3 :         double      prct = (double) largest_diff_count * 100 / loop_count;
     449              : 
     450            3 :         printf("...\n");
     451            3 :         printf("%*lld   %*.4f %*.4f %*lld\n",
     452              :                len1, (long long) largest_diff,
     453              :                len2, prct,
     454              :                len3, 100.0,
     455              :                len4, largest_diff_count);
     456              :     }
     457              : }
        

Generated by: LCOV version 2.0-1