public inbox for [email protected]  
help / color / mirror / Atom feed
From: Junwang Zhao <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Cc: Andrey Borodin <[email protected]>
Cc: Jelte Fennema-Nio <[email protected]>
Subject: Official devcontainer config
Date: Thu, 1 Aug 2024 22:56:40 +0800
Message-ID: <CAEG8a3K_mZsBZ60smh4FM4Dk9cazAUmM72iXdFKEHgthZnaHcw@mail.gmail.com> (raw)

Stack Overflow 2024 developer survey[1] said VSCode
is the most used development environment.

In a PostgreSQL Hacker Mentoring discussion, we talked
about how to use vscode to debug and running postgres,
Andrey(ccd) has tons of tips for new developers, and
I post my daily used devcontainer config[2] , Jelte(ccd)
suggested that it might be a good idea we integrate the
config into postgres repo so that the barrier to entry for
new developers will be much lower.

**Note**

This is not intended to change the workflow of experienced
hackers, it is just hoping to make the life easier for
beginner developers.

**How to use**

Open VSCode Command Palette(cmd/ctrl + shift + p),
search devcontainer, then choose something like
`Dev containers: Rebuild and Reopen in Container`, you are
good to go.

**About the patch**

devcontainer.json:

The .devcontainer/devcontainer.json is the entry point for
VSCode to *open folder to develop in a container*, it will build
the docker image for the first time you open in container,
this will take some time.

There are some parameters(runArgs) for running the container,
we need some settings and privileges to run perf or generate
core dumps.

It has a mount point mapping the hosts $HOME/freedom
to container's /opt/freedom, I chose the name *freedom*
because the container is kind of a jail.

It also installed some vscode extensions and did some
customizations.

After diving into the container, the postCreateCommand.sh
will be automatically called, it will do some configurations
like git, perf, .vscode, core_pattern, etc. It also downloads
michaelpq's pg_plugins and FlameGraph.

Dockerfile:

It is based on debian bookworm, it installed dependencies
to build postgres, also IPC::Run to run TAP tests I guess.

It also has a .gdbinit to break elog.c:errfinish for elevel 21,
this will make the debugging easier why error is logged.

gdbpg.py is adapted from https://github.com/tvondra/gdbpg,
I think putting it here will make it evolve as time goes.

tasks.json:

This is kind of like a bookkeeping for developers, it has
the following commands:

- meson debug setup
- meson release setup
- ninja build
- regression tests
- ninja install
- init cluster
- start cluster
- stop cluster
- install pg_bsd_indent
- pgindent
- apply patch
- generate flamegraph

launch.json:

It has one configuration that makes it possible to attach
to one process(e.g. postgres backend) and debug
with vscode.

PFA and please give it a try if you are a VSCode user.

[1]: https://survey.stackoverflow.co/2024/technology#1-integrated-development-environment
[2]: https://github.com/atomicdb/devcontainer/tree/main/postgres

--
Regards
Junwang Zhao


Attachments:

  [application/octet-stream] v1-0001-official-devcontainer-config.patch (29.4K, ../CAEG8a3K_mZsBZ60smh4FM4Dk9cazAUmM72iXdFKEHgthZnaHcw@mail.gmail.com/2-v1-0001-official-devcontainer-config.patch)
  download | inline diff:
From cf7cc066ed86f36f46b2726e97c6c745eed21a51 Mon Sep 17 00:00:00 2001
From: Zhao Junwang <[email protected]>
Date: Thu, 1 Aug 2024 13:49:53 +0000
Subject: [PATCH v1] official devcontainer config

Signed-off-by: Zhao Junwang <[email protected]>
---
 .devcontainer/.gdbinit             |  32 +++
 .devcontainer/.psqlrc              |   7 +
 .devcontainer/Dockerfile           |  95 +++++++
 .devcontainer/devcontainer.json    |  46 ++++
 .devcontainer/gdbpg.py             | 386 +++++++++++++++++++++++++++++
 .devcontainer/launch.json          |  20 ++
 .devcontainer/postCreateCommand.sh |  86 +++++++
 .devcontainer/tasks.json           | 219 ++++++++++++++++
 .gitignore                         |   3 +
 9 files changed, 894 insertions(+)
 create mode 100644 .devcontainer/.gdbinit
 create mode 100644 .devcontainer/.psqlrc
 create mode 100644 .devcontainer/Dockerfile
 create mode 100644 .devcontainer/devcontainer.json
 create mode 100644 .devcontainer/gdbpg.py
 create mode 100644 .devcontainer/launch.json
 create mode 100644 .devcontainer/postCreateCommand.sh
 create mode 100644 .devcontainer/tasks.json

diff --git a/.devcontainer/.gdbinit b/.devcontainer/.gdbinit
new file mode 100644
index 0000000000..00ac2119d9
--- /dev/null
+++ b/.devcontainer/.gdbinit
@@ -0,0 +1,32 @@
+# gdbpg.py contains scripts to nicely print the postgres datastructures
+# while in a gdb session. Since the vscode debugger is based on gdb this
+# actually also works when debugging with vscode.
+source /root/gdbpg.py
+
+# when debugging postgres it is convenient to _always_ have a breakpoint
+# trigger when an error is logged. Because .gdbinit is sourced before gdb
+# is fully attached and has the sources loaded. To make sure the breakpoint
+# is added when the library is loaded we temporary set the breakpoint pending
+# to on. After we have added out breakpoint we revert back to the default
+# configuration for breakpoint pending.
+# The breakpoint is hard to read, but at entry of the function we don't have
+# the level loaded in elevel. Instead we hardcode the location where the
+# level of the current error is stored. Also gdb doesn't understand the
+# ERROR symbol so we hardcode this to the value of ERROR. It is very unlikely
+# this value will ever change in postgres, but if it does we might need to
+# find a way to conditionally load the correct breakpoint.
+set breakpoint pending on
+break elog.c:errfinish if errordata[errordata_stack_depth].elevel == 21
+set breakpoint pending auto
+
+echo \n
+echo ----------------------------------------------------------------------------------\n
+echo when attaching to a postgres backend a breakpoint will be set on elog.c:errfinish \n
+echo it will only break on errors being raised in postgres \n
+echo \n
+echo to disable this breakpoint from vscode run `-exec disable 1` in the debug console \n
+echo this assumes it's the first breakpoint loaded as it is loaded from .gdbinit \n
+echo this can be verified with `-exec info break`, enabling can be done with \n
+echo `-exec enable 1` \n
+echo ----------------------------------------------------------------------------------\n
+echo \n
diff --git a/.devcontainer/.psqlrc b/.devcontainer/.psqlrc
new file mode 100644
index 0000000000..7642a97149
--- /dev/null
+++ b/.devcontainer/.psqlrc
@@ -0,0 +1,7 @@
+\timing on
+\pset linestyle unicode
+\pset border 2
+\setenv PAGER 'pspg --no-mouse -bX --no-commandbar --no-topbar'
+\set HISTSIZE 100000
+\set PROMPT1 '\n%[%033[1m%]%M %n@%/:%>-%p%R%[%033[0m%]%# '
+\set PROMPT2 '  '
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000000..3a09855faf
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,95 @@
+FROM debian:bookworm AS base
+
+ENV TZ=UTC
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+# install build tools(alphabetic order)
+RUN apt update && apt install -y \
+    bash-completion \
+    bison \
+    bzip2 \
+    ccache \
+    cpanminus \
+    cron \
+    curl \
+    flex \
+    fswatch \
+    gcc g++ \
+    gdb \
+    git \
+    htop \
+    iproute2 \
+    libcurl4-gnutls-dev \
+    libgdal-dev \
+    libgeos-dev \
+    libicu-dev \
+    libkrb5-dev \
+    liblz4-dev \
+    libpam0g-dev \
+    libperl-dev \
+    libproj-dev \
+    libprotobuf-c-dev \
+    libreadline-dev \
+    libselinux1-dev \
+    libssl-dev \
+    libxml2-dev \
+    libxslt-dev \
+    libzstd-dev \
+    linux-perf \
+    locales \
+    lsb-release \
+    lsof \
+    make \
+    man \
+    meson \
+    neovim \
+    ninja-build \
+    perl \
+    pkg-config \
+    procps \
+    protobuf-c-compiler \
+    pspg \
+    python3 python3-pip \
+    strace \
+    sudo \
+    tmux \
+    tree \
+    unzip \
+    uuid-dev \
+    valgrind \
+    wget \
+    zlib1g-dev \
+ && apt remove libpq-dev -y \
+ && apt autoremove -y \
+ && apt clean
+
+RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+RUN cpanm install IPC::Run
+RUN cpanm https://cpan.metacpan.org/authors/id/S/SH/SHANCOCK/Perl-Tidy-20230309.tar.gz
+
+# Since gdb will run in the context of the root user when debugging, we need add
+# both .gdbinit and gdbpg.py script into root home
+# gdbpg.py is adapted from https://github.com/tvondra/gdbpg
+COPY --chown=root:root .gdbinit /root/
+COPY --chown=root:root gdbpg.py /root/
+
+# add the postgres user to sudoers and allow all sudoers to login without a password prompt
+RUN useradd -ms /bin/bash postgres \
+ && usermod -aG sudo postgres \
+ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+
+WORKDIR /home/postgres
+USER postgres
+
+COPY --chown=postgres:postgres .psqlrc .
+
+RUN sudo mkdir -p /opt/freedom \
+ && sudo chown -R postgres:postgres /opt/freedom
+
+ENV PATH="/home/postgres/pgsql/bin:$PATH"
+ENV PGPORT=5432
+EXPOSE 5432
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..f92d116bcf
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,46 @@
+{
+    "build": { 
+        "dockerfile": "Dockerfile"
+    },
+    "runArgs": [
+        "--ulimit=core=-1",
+        "--cap-add=SYS_ADMIN",
+        "--cap-add=SYS_PTRACE",
+        "--security-opt",
+        "seccomp=unconfined",
+        "--privileged"
+    ],
+    "mounts": [
+        // You have to make sure source directory is avaliable on your host file system
+        "source=${localEnv:HOME}/freedom,target=/opt/freedom,type=bind,consistency=cached"
+    ],
+    "workspaceMount": "source=${localWorkspaceFolder},target=/home/postgres/postgres,type=bind,consistency=cached",
+    "workspaceFolder": "/home/postgres/postgres",
+    "forwardPorts": [
+        5432
+    ],
+    "customizations": {
+        "vscode": {
+            "extensions": [
+                "eamodio.gitlens",
+                "github.vscode-github-actions",
+                "GitHub.copilot",
+                "ms-vscode.cpptools-extension-pack",
+                "ms-vscode.hexeditor",
+                "ms-vsliveshare.vsliveshare",
+                "rioj7.command-variable",
+                "74th.scrollkey"
+            ],
+            "settings": {
+                "files.exclude": {
+                    "**/*.o": true,
+                    "**/.deps/": true
+                },
+                "C_Cpp.default.cStandard": "c99",
+                "C_Cpp.default.cppStandard": "c++17",
+                "C_Cpp.default.browse.databaseFilename": "${workspaceFolder}/.vscode/.browse.c_cpp.db"
+            }
+        }
+    },
+    "postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh"
+}
diff --git a/.devcontainer/gdbpg.py b/.devcontainer/gdbpg.py
new file mode 100644
index 0000000000..776570b2ce
--- /dev/null
+++ b/.devcontainer/gdbpg.py
@@ -0,0 +1,386 @@
+import gdb
+
+def format_plan_tree(tree, indent=0):
+	'formats a plan (sub)tree, with custom indentation'
+
+	# if the pointer is NULL, just return (null) string
+	if (str(tree) == '0x0'):
+		return '-> (NULL)'
+
+	# format all the important fields (similarly to EXPLAIN)
+	retval = '''
+-> %(type)s (cost=%(startup).3f...%(total).3f rows=%(rows)s width=%(width)s)
+\ttarget list:
+%(target)s
+\t%(left)s
+\t%(right)s''' % {
+			'type' : format_type(tree['type']),			# type of the Node
+			'startup' : float(tree['startup_cost']),	# startup cost
+			'total' : float(tree['total_cost']),		# total cost
+			'rows' : str(tree['plan_rows']),			# number of rows
+			'width' : str(tree['plan_width']),			# tuple width (no header)
+
+			# format target list
+			'target' : format_node_list(tree['targetlist'], 2, True),
+
+			# left subtree
+			'left' : format_plan_tree(tree['lefttree'], 0),
+
+			# right subtree
+			'right' : format_plan_tree(tree['righttree'], 0)
+		}
+
+	return add_indent(retval, indent+1)
+
+
+def format_type(t, indent=0):
+	'strip the leading T_ from the node type tag'
+
+	t = str(t)
+
+	if t.startswith('T_'):
+		t = t[2:]
+
+	return add_indent(t, indent)
+
+
+def format_int_list(lst, indent=0):
+	'format list containing integer values directly (not warapped in Node)'
+
+	# handle NULL pointer (for List we return NIL
+	if (str(lst) == '0x0'):
+		return '(NIL)'
+
+	# we'll collect the formatted items into a Python list
+	tlist = []
+	item = lst['head']
+
+	# walk the list until we reach the last item
+	while str(item) != '0x0':
+
+		# get item from the list and just grab 'int_value as int'
+		tlist.append(int(item['data']['int_value']))
+
+		# next item
+		item = item['next']
+
+	return add_indent(str(tlist), indent)
+
+
+def format_oid_list(lst, indent=0):
+	'format list containing Oid values directly (not warapped in Node)'
+
+	# handle NULL pointer (for List we return NIL)
+	if (str(lst) == '0x0'):
+		return '(NIL)'
+
+	# we'll collect the formatted items into a Python list
+	tlist = []
+	item = lst['head']
+
+	# walk the list until we reach the last item
+	while str(item) != '0x0':
+
+		# get item from the list and just grab 'oid_value as int'
+		tlist.append(int(item['data']['oid_value']))
+
+		# next item
+		item = item['next']
+
+	return add_indent(str(tlist), indent)
+
+
+def format_node_list(lst, indent=0, newline=False):
+	'format list containing Node values'
+
+	# handle NULL pointer (for List we return NIL)
+	if (str(lst) == '0x0'):
+		return '(NIL)'
+
+	# we'll collect the formatted items into a Python list
+	tlist = []
+	item = lst['head']
+
+	# walk the list until we reach the last item
+	while str(item) != '0x0':
+
+		# we assume the list contains Node instances, so grab a reference
+		# and cast it to (Node*)
+		node = cast(item['data']['ptr_value'], 'Node')
+
+		# append the formatted Node to the result list
+		tlist.append(format_node(node))
+
+		# next item
+		item = item['next']
+
+	retval = str(tlist)
+	if newline:
+		retval = "\n".join([str(t) for t in tlist])
+
+	return add_indent(retval, indent)
+
+
+def format_char(value):
+	'''convert the 'value' into a single-character string (ugly, maybe there's a better way'''
+
+	str_val = str(value.cast(gdb.lookup_type('char')))
+
+	# remove the quotes (start/end)
+	return str_val.split(' ')[1][1:-1]
+
+
+def format_relids(relids):
+	return '(not implemented)'
+
+
+def format_node_array(array, start_idx, length, indent=0):
+
+	items = []
+	for i in range(start_idx,start_idx + length - 1):
+		items.append(str(i) + " => " + format_node(array[i]))
+
+	return add_indent(("\n".join(items)), indent)
+
+
+def format_node(node, indent=0):
+	'format a single Node instance (only selected Node types supported)'
+
+	if str(node) == '0x0':
+		return add_indent('(NULL)', indent)
+
+	retval = '';
+	type_str = str(node['type'])
+
+	if is_a(node, 'TargetEntry'):
+
+		# we assume the list contains Node instances (probably safe for Plan fields)
+		node = cast(node, 'TargetEntry')
+
+		name_ptr = node['resname'].cast(gdb.lookup_type('char').pointer())
+		name = "(NULL)"
+		if str(name_ptr) != '0x0':
+			name = '"' + (name_ptr.string()) + '"'
+
+		retval = 'TargetEntry (resno=%(resno)s resname=%(name)s origtbl=%(tbl)s origcol=%(col)s junk=%(junk)s expr=[%(expr)s])' % {
+				'resno' : node['resno'],
+				'name' : name,
+				'tbl' : node['resorigtbl'],
+				'col' : node['resorigcol'],
+				'junk' : (int(node['resjunk']) == 1),
+				'expr' : format_node(node['expr'])
+			}
+
+	elif is_a(node, 'Var'):
+
+		# we assume the list contains Node instances (probably safe for Plan fields)
+		node = cast(node, 'Var')
+
+		retval = 'Var (varno=%(no)s varattno=%(attno)s levelsup=%(levelsup)s)' % {
+				'no' : node['varno'],
+				'attno' : node['varattno'],
+				'levelsup' : node['varlevelsup']
+			}
+
+	elif is_a(node, 'RangeTblRef'):
+
+		node = cast(node, 'RangeTblRef')
+
+		retval = 'RangeTblRef (rtindex=%d)' % (int(node['rtindex']),)
+
+	elif is_a(node, 'RelOptInfo'):
+
+		node = cast(node, 'RelOptInfo')
+
+		retval = 'RelOptInfo (kind=%(kind)s relids=%(relids)s rtekind=%(rtekind)s relid=%(relid)s rows=%(rows)s width=%(width)s fk=%(fk)s)' % {
+				'kind' : node['reloptkind'],
+				'rows' : node['rows'],
+				'width' : node['width'],
+				'relid' : node['relid'],
+				'relids' : format_relids(node['relids']),
+				'rtekind' : node['rtekind'],
+				'fk' : (int(node['has_fk_join']) == 1)
+			}
+
+	elif is_a(node, 'RangeTblEntry'):
+
+		node = cast(node, 'RangeTblEntry')
+
+		retval = 'RangeTblEntry (kind=%(rtekind)s relid=%(relid)s relkind=%(relkind)s)' % {
+				'relid' : node['relid'],
+				'rtekind' : node['rtekind'],
+				'relkind' : format_char(node['relkind'])
+			}
+
+	elif is_a(node, 'PlannerInfo'):
+
+		retval = format_planner_info(node)
+
+	elif is_a(node, 'PlannedStmt'):
+
+		retval = format_planned_stmt(node)
+
+	elif is_a(node, 'List'):
+
+		retval = format_node_list(node, 0, True)
+
+	elif is_a(node, 'Plan'):
+
+		retval = format_plan_tree(node)
+
+	elif is_a(node, 'RestrictInfo'):
+
+		node = cast(node, 'RestrictInfo')
+
+		retval = '''RestrictInfo (pushed_down=%(push_down)s can_join=%(can_join)s delayed=%(delayed)s)
+%(clause)s
+%(orclause)s''' % {
+			'clause' : format_node(node['clause'], 1),
+			'orclause' : format_node(node['orclause'], 1),
+			'push_down' : (int(node['is_pushed_down']) == 1),
+			'can_join' : (int(node['can_join']) == 1),
+			'delayed' : (int(node['outerjoin_delayed']) == 1)
+		}
+
+	elif is_a(node, 'OpExpr'):
+
+		node = cast(node, 'OpExpr')
+
+		retval = format_op_expr(node)
+
+	elif is_a(node, 'BoolExpr'):
+
+		node = cast(node, 'BoolExpr')
+
+		print node
+
+		retval = format_bool_expr(node)
+
+	else:
+		# default - just print the type name
+		retval = format_type(type_str)
+
+	return add_indent(str(retval), indent)
+
+
+def format_planner_info(info, indent=0):
+
+	# Query *parse;			/* the Query being planned */
+	# *glob;				/* global info for current planner run */
+	# Index	query_level;	/* 1 at the outermost Query */
+	# struct PlannerInfo *parent_root;	/* NULL at outermost Query */
+	# List	   *plan_params;	/* list of PlannerParamItems, see below */
+
+	retval = '''rel:
+%(rel)s
+rte:
+%(rte)s
+''' % {'rel' : format_node_array(info['simple_rel_array'], 1, int(info['simple_rel_array_size'])),
+	   'rte' : format_node_array(info['simple_rte_array'], 1, int(info['simple_rel_array_size']))}
+
+	return add_indent(retval, indent)
+
+
+def format_planned_stmt(plan, indent=0):
+
+	retval = '''          type: %(type)s
+      query ID: %(qid)s
+    param exec: %(nparam)s
+     returning: %(has_returning)s
+ modifying CTE: %(has_modify_cte)s
+   can set tag: %(can_set_tag)s
+     transient: %(transient)s
+  row security: %(row_security)s
+               
+     plan tree: %(tree)s
+   range table:
+%(rtable)s
+ relation OIDs: %(relation_oids)s
+   result rels: %(result_rels)s
+  utility stmt: %(util_stmt)s
+      subplans: %(subplans)s''' % {
+			'type' : plan['commandType'],
+			'qid' : plan['queryId'],
+			'nparam' : plan['nParamExec'],
+			'has_returning' : (int(plan['hasReturning']) == 1),
+			'has_modify_cte' : (int(plan['hasModifyingCTE']) == 1),
+			'can_set_tag' : (int(plan['canSetTag']) == 1),
+			'transient' : (int(plan['transientPlan']) == 1),
+			'row_security' : (int(plan['hasRowSecurity']) == 1),
+			'tree' : format_plan_tree(plan['planTree']),
+			'rtable' : format_node_list(plan['rtable'], 1, True),
+			'relation_oids' : format_oid_list(plan['relationOids']),
+			'result_rels' : format_int_list(plan['resultRelations']),
+			'util_stmt' : format_node(plan['utilityStmt']),
+			'subplans' : format_node_list(plan['subplans'], 1, True)
+		  }
+
+	return add_indent(retval, indent)
+
+def format_op_expr(node, indent=0):
+
+	return """OpExpr [opno=%(opno)s]
+%(clauses)s""" % {	'opno' : node['opno'],
+					'clauses' : format_node_list(node['args'], 1, True)}
+
+def format_bool_expr(node, indent=0):
+
+	return """BoolExpr [op=%(op)s]
+%(clauses)s""" % {	'op' : node['boolop'],
+					'clauses' : format_node_list(node['args'], 1, True)}
+
+def is_a(n, t):
+	'''checks that the node has type 't' (just like IsA() macro)'''
+
+	if not is_node(n):
+		return False
+
+	return (str(n['type']) == ('T_' + t))
+
+
+def is_node(l):
+	'''return True if the value looks like a Node (has 'type' field)'''
+
+	try:
+		x = l['type']
+		return True
+	except:
+		return False
+
+def cast(node, type_name):
+	'''wrap the gdb cast to proper node type'''
+
+	# lookup the type with name 'type_name' and cast the node to it
+	t = gdb.lookup_type(type_name)
+	return node.cast(t.pointer())
+
+
+def add_indent(val, indent):
+
+	return "\n".join([(("\t"*indent) + l) for l in val.split("\n")])
+
+
+class PgPrintCommand(gdb.Command):
+	"print PostgreSQL structures"
+
+	def __init__ (self):
+		super (PgPrintCommand, self).__init__ ("pgprint",
+					gdb.COMMAND_SUPPORT,
+					gdb.COMPLETE_NONE, False)
+
+	def invoke (self, arg, from_tty):
+
+		arg_list = gdb.string_to_argv(arg)
+		if len(arg_list) != 1:
+			print "usage: pgprint var"
+			return
+
+		l = gdb.parse_and_eval(arg_list[0])
+
+		if not is_node(l):
+			print "not a node type"
+
+		print format_node(l)
+
+
+PgPrintCommand()
diff --git a/.devcontainer/launch.json b/.devcontainer/launch.json
new file mode 100644
index 0000000000..a3c0ae98fc
--- /dev/null
+++ b/.devcontainer/launch.json
@@ -0,0 +1,20 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "(gdb) Attach",
+            "type": "cppdbg",
+            "request": "attach",
+            "processId": "${command:pickProcess}",
+            "program": "/home/postgres/pgsql/bin/postgres",
+            "additionalSOLibSearchPath": "/home/postgres/pgsql/lib",
+            "setupCommands": [
+                {
+                    "text": "handle SIGUSR1 noprint nostop pass",
+                    "description": "let gdb not stop when SIGUSR1 is sent to process",
+                    "ignoreFailures": true
+                }
+            ]
+        }
+    ]
+}
diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh
new file mode 100644
index 0000000000..ef84027fc4
--- /dev/null
+++ b/.devcontainer/postCreateCommand.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+function configure_coredumps {
+  sudo sh -c "echo 'core.%p.sig%s.%ts' > /proc/sys/kernel/core_pattern"
+}
+
+function configure_git {
+  git config --global user.email [email protected]
+  git config --global user.name "Your Name"
+  git config --global core.editor vim
+
+  # alias
+  git config --global alias.br "branch -vv"
+  git config --global alias.ci "commit -s"
+  git config --global alias.co checkout
+  git config --global alias.cob "checkout -b"
+  git config --global alias.cp cherry-pick
+  git config --global alias.dh "diff --no-prefix HEAD"
+  git config --global alias.ps push
+  git config --global alias.st status
+  git config --global alias.lg "log --graph --format='%C(auto)%h%C(reset) %C(dim white)%an%C(reset) %C(green)%ai%C(reset) %C(auto)%d%C(reset)%n   %s'"
+  git config --global alias.lg10 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -10"
+  git config --global alias.lg20 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -20"
+  git config --global alias.lg30 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -30"
+  git config --global alias.fp "format-patch --stdout --no-prefix"
+
+  git config --global --add safe.directory /home/postgres/postgres
+}
+
+function configure_vscode {
+  mkdir -p .vscode
+  cat <<EOL > ".vscode/c_cpp_properties.json"
+{
+    "configurations": [
+        {
+            "name": "Postgres Development Configuration",
+            "includePath": [
+                "\${workspaceFolder}/**",
+                "\${workspaceFolder}/src/include/",
+                "\${workspaceFolder}/../build/src/include/"
+            ],
+            "cStandard": "c99",
+            "configurationProvider": "ms-vscode.makefile-tools"
+        }
+    ],
+    "version": 4
+}
+EOL
+
+  # Copy the launch.json and tasks.json files if they don't exist
+  if [ ! -f .vscode/launch.json ]; then
+    cp .devcontainer/launch.json .vscode/
+  fi
+  if [ ! -f .vscode/tasks.json ]; then
+    cp .devcontainer/tasks.json .vscode/
+  fi
+}
+
+function configure_perf {
+  sudo sh -c "echo 0 > /proc/sys/kernel/kptr_restrict"
+  sudo su -c "echo -1 > /proc/sys/kernel/perf_event_paranoid"
+
+  if [ ! -d /opt/freedom/tools/FlameGraph ]; then
+    git clone https://github.com/brendangregg/FlameGraph.git /opt/freedom/tools/FlameGraph
+  fi
+}
+
+function configure_pg_plugins {
+  if [ ! -d /opt/freedom/extensions/pg_plugins ]; then
+    git clone https://github.com/michaelpq/pg_plugins.git /opt/freedom/extensions/pg_plugins
+  fi
+}
+
+function main {
+  mkdir -p /opt/freedom/extensions
+  mkdir -p /opt/freedom/patches
+  mkdir -p /opt/freedom/tools
+
+  configure_coredumps
+  configure_git
+  configure_perf
+  configure_pg_plugins
+  configure_vscode
+}
+
+main $@
diff --git a/.devcontainer/tasks.json b/.devcontainer/tasks.json
new file mode 100644
index 0000000000..9759b8ee4b
--- /dev/null
+++ b/.devcontainer/tasks.json
@@ -0,0 +1,219 @@
+{
+    // See https://go.microsoft.com/fwlink/?LinkId=733558
+    // for the documentation about the tasks.json format
+    // See PostgreSQL meson doc link https://wiki.postgresql.org/wiki/Meson
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "meson setup",
+            "type": "shell",
+            "command": "meson",
+            "args": [
+                "setup",
+                "../build",             // out of source tree
+                "--prefix=/home/postgres/pgsql",
+                "--buildtype=debug",    // default is debugoptimized
+                "-Dc_args=-fno-inline-functions -fno-omit-frame-pointer -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS",
+                "-Dcassert=true",
+                "-Dtap_tests=enabled",
+                "--werror"
+            ],
+            "problemMatcher": [],
+            "detail": "meson setup debug configuration"
+        },
+        {
+            "label": "meson setup release",
+            "type": "shell",
+            "command": "meson",
+            "args": [
+                "setup",
+                "../build",
+                "--prefix=/home/postgres/pgsql",
+                "--buildtype=release"
+            ],
+            "problemMatcher": [],
+            "detail": "meson setup release configuration"
+        },
+        {
+            "label": "clear build directory",
+            "type": "shell",
+            "command": "rm -rf ../build",
+            "problemMatcher": [],
+            "detail": "clear build directory"
+        },
+        {
+            "label": "ninja build",
+            "type": "shell",
+            "command": "ninja",
+            "args":[
+                "-C",
+                "../build",
+                "-j",
+                "4"
+            ],
+            "problemMatcher": [],
+            "detail": "build postgres"
+        },
+        {
+            "label": "regression tests",
+            "type": "shell",
+            "command": "meson test -C ../build -q --print-errorlogs --suite setup --suite regress",
+            "problemMatcher": [],
+            "detail": "run main regression tests"
+        },
+        {
+            "label": "check-world",
+            "type": "shell",
+            "command": "meson test -C ../build -q --print-errorlogs",
+            "problemMatcher": [],
+            "detail": "run all tests"
+        },
+        {
+            "label": "ninja install",
+            "type": "shell",
+            "command": "ninja install -C ../build",
+            "problemMatcher": [],
+            "detail": "install pgsql"
+        },
+        {
+            "label": "init cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/initdb",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata"
+            ],
+            "problemMatcher": [],
+            "detail": "init cluster using initdb"
+        },
+        {
+            "label": "start cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/pg_ctl",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata",
+                "-l",
+                "/home/postgres/pgdata/logfile",
+                "start"
+            ],
+            "problemMatcher": [],
+            "detail": "start db cluster"
+        },
+        {
+            "label": "restart cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/pg_ctl",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata",
+                "-l",
+                "/home/postgres/pgdata/logfile",
+                "restart"
+            ],
+            "problemMatcher": [],
+            "detail": "restart db cluster"
+        },
+        {
+            "label": "stop cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/pg_ctl",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata",
+                "stop"
+            ],
+            "problemMatcher": [],
+            "detail": "stop db cluster"
+        },
+        {
+            "label": "clear pgdata",
+            "type": "shell",
+            "command": "rm -rf /home/postgres/pgdata",
+            "problemMatcher": [],
+            "detail": "clear pgdata directory"
+        },
+        {
+            "label": "install pg_bsd_indent",
+            "type": "shell",
+            "command": "sudo cp ../build/src/tools/pg_bsd_indent/pg_bsd_indent /usr/local/bin",
+            "problemMatcher": [],
+            "detail": "install pg_bsd_indent to /usr/local/bin"
+        },
+        {
+            "label": "pgindent",
+            "type": "shell",
+            "command": "src/tools/pgindent/pgindent ${input:selectDir}",
+            "problemMatcher": [],
+            "detail": "run pgindent on selected directory"
+        },
+        {
+            "label": "format patch",
+            "type": "shell",
+            "command": "git format-patch -o /opt/freedom/patches -${input:formatPatchNumber} -v ${input:formatPatchVersion}",
+            "problemMatcher": [],
+            "detail": "generate patches from the topmost <n> commits"
+        },
+        {
+            "label": "apply patch",
+            "type": "shell",
+            "command": "git am /opt/freedom/patches/${input:patchFile}",
+            "problemMatcher": [],
+            "detail": "apply patch"
+        },
+        {
+            "label": "generate flamegraph",
+            "type": "shell",
+            "command": "perf record -o ../perf_${input:processId}.data -g -F 99 -p ${input:processId} -- sleep 60 && perf script -i ../perf_${input:processId}.data | /opt/freedom/tools/FlameGraph/stackcollapse-perf.pl | /opt/freedom/tools/FlameGraph/flamegraph.pl > /opt/freedom/perf_${input:processId}.svg",
+            "problemMatcher": [],
+            "detail": "generate flamegraph"
+        }
+    ],
+    "inputs": [
+        {
+            "id": "selectDir",
+            "type": "command",
+            "command": "extension.commandvariable.pickStringRemember",
+            "args": {
+                "description": "Which directory to run for pgindent?",
+                "options": [
+                    ["Use previous directory", "${remember:srcSubDir}"],
+                    ["Pick directory", "${pickFile:srcSubDir}"]
+                ],
+                "default": null,
+                "pickFile": {
+                    "srcSubDir": {
+                        "description": "Which directory?",
+                        "include": "src/**",
+                        "showDirs": true,
+                        "keyRemember": "srcSubDir"
+                    }
+                }
+            }
+        },
+        {
+            "id": "formatPatchNumber",
+            "description": "patches from the topmost <n> commits",
+            "type": "promptString",
+            "default": "1"
+        },
+        {
+            "id": "formatPatchVersion",
+            "description": "<n>-th iteration of the topic",
+            "type": "promptString",
+            "default": "1"
+        },
+        {
+            "id": "patchFile",
+            "description": "Which patch file to apply?",
+            "type": "promptString",
+            "default": "*"
+        },
+        {
+            "id": "processId",
+            "description": "Which process to perf?",
+            "type": "promptString",
+            "default": ""
+        }
+    ]
+}
diff --git a/.gitignore b/.gitignore
index 4e911395fe..952e4c93ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ win32ver.rc
 *.exe
 lib*dll.def
 lib*.pc
+.DS_Store
 
 # Local excludes in root directory
 /GNUmakefile
@@ -43,3 +44,5 @@ lib*.pc
 /Release/
 /tmp_install/
 /portlock/
+/.devcontainer/
+/.vscode/
-- 
2.39.2



view thread (7+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected]
  Subject: Re: Official devcontainer config
  In-Reply-To: <CAEG8a3K_mZsBZ60smh4FM4Dk9cazAUmM72iXdFKEHgthZnaHcw@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox