From f9604a6a27d259552da92f973ca3a330beb3d6cd Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Tue, 22 Apr 2025 07:39:16 +0000
Subject: [PATCH v1 1/4] Generate the WaitClassTable[]

The WaitClassTable[] array that will be used as a lookup table by the wait events
statistics (implemented in a following commit).

The array is indexed by classId (derived from the PG_WAIT_* constants), handles
gaps in the class ID numbering and provides metadata for wait events.

A new struct (WaitClassTableEntry) is used to store the wait events metadata.

This new array is generated in generate-wait_event_types.pl, so that:

- it now needs lwlocklist.h and wait_classes.h as input parameters
- WAIT_EVENT_CLASS_MASK and WAIT_EVENT_ID_MASK have been moved away from
wait_event.c

In passing it adds severals new macros that will be used by a following commit
implementing the wait events statistics.

This commit does not generate any new files but adds some code in
wait_event_types.h and pgstat_wait_event.c.
---
 src/backend/Makefile                          |   2 +-
 src/backend/utils/activity/Makefile           |   4 +-
 .../activity/generate-wait_event_types.pl     | 244 +++++++++++++++++-
 src/backend/utils/activity/wait_event.c       |   3 -
 src/include/utils/meson.build                 |   4 +-
 5 files changed, 248 insertions(+), 9 deletions(-)

diff --git a/src/backend/Makefile b/src/backend/Makefile
index 7344c8c7f5c..685d7a0a77e 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -114,7 +114,7 @@ parser/gram.h: parser/gram.y
 storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl ../include/storage/lwlocklist.h utils/activity/wait_event_names.txt
 	$(MAKE) -C storage/lmgr lwlocknames.h
 
-utils/activity/wait_event_types.h: utils/activity/generate-wait_event_types.pl utils/activity/wait_event_names.txt
+utils/activity/wait_event_types.h: utils/activity/generate-wait_event_types.pl utils/activity/wait_event_names.txt ../include/storage/lwlocklist.h ../include/utils/wait_classes.h
 	$(MAKE) -C utils/activity wait_event_types.h pgstat_wait_event.c wait_event_funcs_data.c
 
 # run this unconditionally to avoid needing to know its dependencies here:
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 9c2443e1ecd..f9849bebc98 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -45,8 +45,8 @@ wait_event.o: pgstat_wait_event.c
 pgstat_wait_event.c: wait_event_types.h
 	touch $@
 
-wait_event_types.h: $(top_srcdir)/src/backend/utils/activity/wait_event_names.txt generate-wait_event_types.pl
-	$(PERL) $(srcdir)/generate-wait_event_types.pl --code $<
+wait_event_types.h: $(top_srcdir)/src/backend/utils/activity/wait_event_names.txt $(top_srcdir)/src/include/storage/lwlocklist.h $(top_srcdir)/src/include/utils/wait_classes.h generate-wait_event_types.pl
+	$(PERL) $(srcdir)/generate-wait_event_types.pl --code $(wordlist 1,3,$^)
 
 clean:
 	rm -f wait_event_types.h pgstat_wait_event.c wait_event_funcs_data.c
diff --git a/src/backend/utils/activity/generate-wait_event_types.pl b/src/backend/utils/activity/generate-wait_event_types.pl
index 424ad9f115d..c18693aa68b 100644
--- a/src/backend/utils/activity/generate-wait_event_types.pl
+++ b/src/backend/utils/activity/generate-wait_event_types.pl
@@ -21,6 +21,12 @@ use Getopt::Long;
 my $output_path = '.';
 my $gen_docs = 0;
 my $gen_code = 0;
+my $nb_waitclass_table_entries = 0;
+my $nb_wait_events_with_null = 0;
+my $nb_wait_events_per_class = 0;
+my %waitclass_values;
+my $wait_event_class_mask = 0xFF000000;
+my $wait_event_id_mask = 0x0000FFFF;
 
 my $continue = "\n";
 my %hashwe;
@@ -38,11 +44,50 @@ die "Not possible to specify --docs and --code simultaneously"
 
 open my $wait_event_names, '<', $ARGV[0] or die;
 
+# When generating code, we need lwlocklist.h as the second argument
+my $lwlocklist_file = $ARGV[1] if $gen_code;
+
+# When generating code, we need wait_classes.h as the third argument
+my $wait_classes_file = $ARGV[2] if $gen_code;
+
 my @abi_compatibility_lines;
 my @lines;
 my $abi_compatibility = 0;
 my $section_name;
 
+# Function to parse wait_classes.h and extract wait class definitions
+sub parse_wait_classes_header
+{
+
+	open my $wait_classes_header, '<', $wait_classes_file
+	  or die "Could not open $wait_classes_file: $!";
+
+	while (<$wait_classes_header>)
+	{
+		chomp;
+		if (/^\s*#define\s+(PG_WAIT_\w+)\s+(0x[0-9A-Fa-f]+)U?\s*$/)
+		{
+			my ($macro_name, $value) = ($1, $2);
+
+			$waitclass_values{$macro_name} = $value;
+		}
+	}
+
+	close $wait_classes_header;
+}
+
+# Function to get the macro from the wait class name
+sub waitclass_to_macro
+{
+
+	my $waitclass = shift;
+	my $last = $waitclass;
+	$last =~ s/^WaitEvent//;
+	my $lastuc = uc $last;
+
+	return "PG_WAIT_" . $lastuc;
+}
+
 # Remove comments and empty lines and add waitclassname based on the section
 while (<$wait_event_names>)
 {
@@ -84,8 +129,39 @@ while (<$wait_event_names>)
 
 # Sort the lines based on the second column.
 # uc() is being used to force the comparison to be case-insensitive.
-my @lines_sorted =
-  sort { uc((split(/\t/, $a))[1]) cmp uc((split(/\t/, $b))[1]) } @lines;
+
+my @lines_sorted;
+if ($gen_code)
+{
+	my @lwlock_lines;
+
+	# Separate LWLock lines from others
+	foreach my $line (@lines)
+	{
+		if ($line =~ /^WaitEventLWLock\t/)
+		{
+			push(@lwlock_lines, $line);
+		}
+		else
+		{
+			push(@lines_sorted, $line);
+		}
+	}
+
+	# Sort only non-LWLock lines
+	@lines_sorted =
+	  sort { uc((split(/\t/, $a))[1]) cmp uc((split(/\t/, $b))[1]) }
+	  @lines_sorted;
+
+	# Add LWLock lines back in their original order
+	push(@lines_sorted, @lwlock_lines);
+}
+else
+{
+	# For docs, use original alphabetical sorting for all
+	@lines_sorted =
+	  sort { uc((split(/\t/, $a))[1]) cmp uc((split(/\t/, $b))[1]) } @lines;
+}
 
 # If we are generating code, concat @lines_sorted and then
 # @abi_compatibility_lines.
@@ -168,6 +244,10 @@ if ($gen_code)
 	printf $h $header_comment, 'wait_event_types.h';
 	printf $h "#ifndef WAIT_EVENT_TYPES_H\n";
 	printf $h "#define WAIT_EVENT_TYPES_H\n\n";
+	printf $h "#define WAIT_EVENT_CLASS_MASK   0x%08X\n",
+	  $wait_event_class_mask;
+	printf $h "#define WAIT_EVENT_ID_MASK      0x%08X\n\n",
+	  $wait_event_id_mask;
 	printf $h "#include \"utils/wait_classes.h\"\n\n";
 
 	printf $c $header_comment, 'pgstat_wait_event.c';
@@ -269,6 +349,166 @@ if ($gen_code)
 		}
 	}
 
+	printf $h "
+
+/* To represent wait_event_info as integers */
+typedef struct DecodedWaitInfo
+{
+        int classId;
+        int eventId;
+} DecodedWaitInfo;
+
+/* To extract classId and eventId as integers from wait_event_info */
+#define WAIT_EVENT_INFO_DECODE(d, i) \\
+    d.classId = ((i) & WAIT_EVENT_CLASS_MASK) / (WAIT_EVENT_CLASS_MASK & (-WAIT_EVENT_CLASS_MASK)), \\
+    d.eventId = (i) & WAIT_EVENT_ID_MASK
+
+/* To map wait event classes into the WaitClassTable */
+typedef struct
+{
+	const int classId;
+	const int numberOfEvents;
+	const int offSet;
+	const char *className;
+	const char *const *eventNames;
+} WaitClassTableEntry;
+
+extern WaitClassTableEntry WaitClassTable[];\n\n";
+
+	printf $c "
+/*
+ * Lookup table that is used by the wait events statistics.
+ * Indexed by classId (derived from the PG_WAIT_* constants), handle gaps
+ * in the class ID numbering and provide metadata for wait events.
+ */
+WaitClassTableEntry WaitClassTable[] = {\n";
+
+	parse_wait_classes_header();
+	my $next_index = 0;
+	my $class_divisor = $wait_event_class_mask & (-$wait_event_class_mask);
+
+	foreach my $waitclass (
+		sort {
+			my $macro_a = waitclass_to_macro($a);
+			my $macro_b = waitclass_to_macro($b);
+			hex($waitclass_values{$macro_a}) <=>
+			  hex($waitclass_values{$macro_b})
+		} keys %hashwe)
+	{
+		my $event_names_array;
+		my $array_size;
+		my $last = $waitclass;
+		$last =~ s/^WaitEvent//;
+
+		$nb_waitclass_table_entries++;
+
+		# The LWLocks need to be handled differently than the other classes when
+		# building the WaitClassTable. We need to take care of the prefedined
+		# LWLocks as well as the additional ones.
+		if ($waitclass eq 'WaitEventLWLock')
+		{
+			# Parse lwlocklist.h to get LWLock definitions
+			open my $lwlocklist, '<', $lwlocklist_file
+			  or die "Could not open $lwlocklist_file: $!";
+
+			my %predefined_lwlock_indices;
+			my $max_lwlock_index = -1;
+
+			while (<$lwlocklist>)
+			{
+				if (/^PG_LWLOCK\((\d+),\s+(\w+)\)$/)
+				{
+					my ($lockidx, $lockname) = ($1, $2);
+					$predefined_lwlock_indices{$lockname} = $lockidx;
+					$max_lwlock_index = $lockidx
+					  if $lockidx > $max_lwlock_index;
+				}
+			}
+
+			close $lwlocklist;
+
+			# Iterates through wait_event_names.txt order
+			my @event_names_sparse;
+			my $next_additional_index = $max_lwlock_index + 1;
+
+			foreach my $wev (@{ $hashwe{$waitclass} })
+			{
+				my $lockname = $wev->[1];
+
+				if (exists $predefined_lwlock_indices{$lockname})
+				{
+					# This is a predefined one, place it at its specific index
+					my $index = $predefined_lwlock_indices{$lockname};
+					$event_names_sparse[$index] = "\"$lockname\"";
+				}
+				else
+				{
+					# This is an additional one, append it after predefined ones
+					$event_names_sparse[$next_additional_index] =
+					  "\"$lockname\"";
+					$next_additional_index++;
+				}
+			}
+
+			# Fill gaps with NULL for missing predefined locks
+			for my $i (0 .. $max_lwlock_index)
+			{
+				$event_names_sparse[$i] = "NULL"
+				  unless defined $event_names_sparse[$i];
+			}
+
+			# Build the array literal
+			$event_names_array = "(const char *const []){"
+			  . join(", ", @event_names_sparse) . "}";
+			$array_size = scalar(@event_names_sparse);
+		}
+		else
+		{
+			# Construct a simple string array literal for this class
+			$event_names_array = "(const char *const []){";
+
+			# For each wait event in this class, add its name to the array
+			foreach my $wev (@{ $hashwe{$waitclass} })
+			{
+				$event_names_array .= "\"$wev->[1]\", ";
+			}
+
+			$event_names_array .= "}";
+			$array_size = scalar(@{ $hashwe{$waitclass} });
+		}
+
+		my $lastuc = uc $last;
+		my $pg_wait_class = "PG_WAIT_" . $lastuc;
+
+		my $index = hex($waitclass_values{$pg_wait_class}) / $class_divisor;
+
+		# Fill any holes with {0, 0, 0, NULL, NULL}
+		while ($next_index < $index)
+		{
+			printf $c "{0, 0, 0, NULL, NULL},\n";
+			$next_index++;
+			$nb_waitclass_table_entries++;
+		}
+
+		my $offset = $nb_wait_events_with_null;
+		$nb_wait_events_with_null += $array_size;
+
+		# Generate the entry
+		printf $c "{$pg_wait_class, $array_size, $offset, \"%s\", %s},\n",
+		  $last, $event_names_array;
+
+		$next_index = $index + 1;
+	}
+
+	printf $c "};\n\n";
+
+	printf $h "#define NB_WAITCLASSTABLE_SIZE $nb_wait_events_with_null\n";
+	printf $h
+	  "#define NB_WAITCLASSTABLE_ENTRIES $nb_waitclass_table_entries\n\n";
+	printf $h
+	  "StaticAssertDecl(NB_WAITCLASSTABLE_SIZE > 0, \"Wait class table must have entries\");\n";
+	printf $h
+	  "StaticAssertDecl(NB_WAITCLASSTABLE_ENTRIES > 0, \"Must have at least one wait class\");\n";
 	printf $h "#endif                          /* WAIT_EVENT_TYPES_H */\n";
 	close $h;
 	close $c;
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index d9b8f34a355..eba7d338c1f 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -39,9 +39,6 @@ static const char *pgstat_get_wait_io(WaitEventIO w);
 static uint32 local_my_wait_event_info;
 uint32	   *my_wait_event_info = &local_my_wait_event_info;
 
-#define WAIT_EVENT_CLASS_MASK	0xFF000000
-#define WAIT_EVENT_ID_MASK		0x0000FFFF
-
 /*
  * Hash tables for storing custom wait event ids and their names in
  * shared memory.
diff --git a/src/include/utils/meson.build b/src/include/utils/meson.build
index 78c6b9b0a23..ff519242de7 100644
--- a/src/include/utils/meson.build
+++ b/src/include/utils/meson.build
@@ -2,7 +2,9 @@
 
 wait_event_output = ['wait_event_types.h', 'pgstat_wait_event.c', 'wait_event_funcs_data.c']
 wait_event_target = custom_target('wait_event_names',
-  input: files('../../backend/utils/activity/wait_event_names.txt'),
+  input: files('../../backend/utils/activity/wait_event_names.txt',
+               '../../include/storage/lwlocklist.h',
+               '../../include/utils/wait_classes.h'),
   output: wait_event_output,
   command: [
     perl, files('../../backend/utils/activity/generate-wait_event_types.pl'),
-- 
2.34.1

