Repository: duckdb/duckdb (language: python)

Issue:
Various TODOs and FIXMEs
Look here: https://github.com/cwida/duckdb/search?q=TODO+OR+FIXME&unscoped_q=TODO+OR+FIXME



Discussion / hints:
Not much left here so closing

Fix patch:
diff --git a/src/common/enum_util.cpp b/src/common/enum_util.cpp
index dd223a7cbc67..0657bd50f382 100644
--- a/src/common/enum_util.cpp
+++ b/src/common/enum_util.cpp
@@ -101,7 +101,6 @@
 #include "duckdb/main/extension_helper.hpp"
 #include "duckdb/main/extension_install_info.hpp"
 #include "duckdb/main/profiling_info.hpp"
-#include "duckdb/main/profiling_node.hpp"
 #include "duckdb/main/query_result.hpp"
 #include "duckdb/main/secret/secret.hpp"
 #include "duckdb/main/settings.hpp"
@@ -4303,12 +4302,18 @@ MetaPipelineType EnumUtil::FromString<MetaPipelineType>(const char *value) {
 template<>
 const char* EnumUtil::ToChars<MetricsType>(MetricsType value) {
 	switch(value) {
+	case MetricsType::QUERY_NAME:
+		return "QUERY_NAME";
+	case MetricsType::BLOCKED_THREAD_TIME:
+		return "BLOCKED_THREAD_TIME";
 	case MetricsType::CPU_TIME:
 		return "CPU_TIME";
 	case MetricsType::EXTRA_INFO:
 		return "EXTRA_INFO";
 	case MetricsType::CUMULATIVE_CARDINALITY:
 		return "CUMULATIVE_CARDINALITY";
+	case MetricsType::OPERATOR_TYPE:
+		return "OPERATOR_TYPE";
 	case MetricsType::OPERATOR_CARDINALITY:
 		return "OPERATOR_CARDINALITY";
 	case MetricsType::CUMULATIVE_ROWS_SCANNED:
@@ -4324,6 +4329,12 @@ const char* EnumUtil::ToChars<MetricsType>(MetricsType value) {
 
 template<>
 MetricsType EnumUtil::FromString<MetricsType>(const char *value) {
+	if (StringUtil::Equals(value, "QUERY_NAME")) {
+		return MetricsType::QUERY_NAME;
+	}
+	if (StringUtil::Equals(value, "BLOCKED_THREAD_TIME")) {
+		return MetricsType::BLOCKED_THREAD_TIME;
+	}
 	if (StringUtil::Equals(value, "CPU_TIME")) {
 		return MetricsType::CPU_TIME;
 	}
@@ -4333,6 +4344,9 @@ MetricsType EnumUtil::FromString<MetricsType>(const char *value) {
 	if (StringUtil::Equals(value, "CUMULATIVE_CARDINALITY")) {
 		return MetricsType::CUMULATIVE_CARDINALITY;
 	}
+	if (StringUtil::Equals(value, "OPERATOR_TYPE")) {
+		return MetricsType::OPERATOR_TYPE;
+	}
 	if (StringUtil::Equals(value, "OPERATOR_CARDINALITY")) {
 		return MetricsType::OPERATOR_CARDINALITY;
 	}
@@ -5720,29 +5734,6 @@ ProfilerPrintFormat EnumUtil::FromString<ProfilerPrintFormat>(const char *value)
 	throw NotImplementedException(StringUtil::Format("Enum value: '%s' not implemented", value));
 }
 
-template<>
-const char* EnumUtil::ToChars<ProfilingNodeType>(ProfilingNodeType value) {
-	switch(value) {
-	case ProfilingNodeType::QUERY_ROOT:
-		return "QUERY_ROOT";
-	case ProfilingNodeType::OPERATOR:
-		return "OPERATOR";
-	default:
-		throw NotImplementedException(StringUtil::Format("Enum value: '%d' not implemented", value));
-	}
-}
-
-template<>
-ProfilingNodeType EnumUtil::FromString<ProfilingNodeType>(const char *value) {
-	if (StringUtil::Equals(value, "QUERY_ROOT")) {
-		return ProfilingNodeType::QUERY_ROOT;
-	}
-	if (StringUtil::Equals(value, "OPERATOR")) {
-		return ProfilingNodeType::OPERATOR;
-	}
-	throw NotImplementedException(StringUtil::Format("Enum value: '%s' not implemented", value));
-}
-
 template<>
 const char* EnumUtil::ToChars<QuantileSerializationType>(QuantileSerializationType value) {
 	switch(value) {
diff --git a/src/common/render_tree.cpp b/src/common/render_tree.cpp
index fc1f285b96b0..ed667f7c075c 100644
--- a/src/common/render_tree.cpp
+++ b/src/common/render_tree.cpp
@@ -116,19 +116,23 @@ static unique_ptr<RenderTreeNode> CreateNode(const PipelineRenderNode &op) {
 }
 
 static unique_ptr<RenderTreeNode> CreateNode(const ProfilingNode &op) {
+	auto &info = op.GetProfilingInfo();
 	InsertionOrderPreservingMap<string> extra_info;
-	if (op.GetProfilingInfo().Enabled(MetricsType::EXTRA_INFO)) {
+	if (info.Enabled(MetricsType::EXTRA_INFO)) {
 		extra_info = op.GetProfilingInfo().extra_info;
 	}
 
-	auto node_name = op.GetName();
+	string node_name = "QUERY";
+	if (op.depth > 0) {
+		node_name = info.GetMetricAsString(MetricsType::OPERATOR_TYPE);
+	}
+
 	auto result = make_uniq<RenderTreeNode>(node_name, extra_info);
-	if (op.GetProfilingInfo().Enabled(MetricsType::OPERATOR_CARDINALITY)) {
-		result->extra_text["Cardinality"] = op.GetProfilingInfo().GetMetricAsString(MetricsType::OPERATOR_CARDINALITY);
+	if (info.Enabled(MetricsType::OPERATOR_CARDINALITY)) {
+		result->extra_text["Cardinality"] = info.GetMetricAsString(MetricsType::OPERATOR_CARDINALITY);
 	}
-	if (op.GetProfilingInfo().Enabled(MetricsType::OPERATOR_TIMING)) {
-		string timing = StringUtil::Format(
-		    "%.2f", op.GetProfilingInfo().metrics.at(MetricsType::OPERATOR_TIMING).GetValue<double>());
+	if (info.Enabled(MetricsType::OPERATOR_TIMING)) {
+		string timing = StringUtil::Format("%.2f", info.metrics.at(MetricsType::OPERATOR_TIMING).GetValue<double>());
 		result->extra_text["Timing"] = timing + "s";
 	}
 	return result;
diff --git a/src/execution/operator/aggregate/physical_ungrouped_aggregate.cpp b/src/execution/operator/aggregate/physical_ungrouped_aggregate.cpp
index 2537ab80df77..15d082ab88af 100644
--- a/src/execution/operator/aggregate/physical_ungrouped_aggregate.cpp
+++ b/src/execution/operator/aggregate/physical_ungrouped_aggregate.cpp
@@ -385,7 +385,7 @@ SinkCombineResultType PhysicalUngroupedAggregate::Combine(ExecutionContext &cont
 	gstate.state.Combine(lstate.state);
 
 	auto &client_profiler = QueryProfiler::Get(context.client);
-	context.thread.profiler.Flush(*this, lstate.child_executor, "child_executor", 0);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
 
 	return SinkCombineResultType::FINISHED;
diff --git a/src/execution/operator/filter/physical_filter.cpp b/src/execution/operator/filter/physical_filter.cpp
index 7a90ade2d034..5caa354014ad 100644
--- a/src/execution/operator/filter/physical_filter.cpp
+++ b/src/execution/operator/filter/physical_filter.cpp
@@ -31,7 +31,7 @@ class FilterState : public CachingOperatorState {
 
 public:
 	void Finalize(const PhysicalOperator &op, ExecutionContext &context) override {
-		context.thread.profiler.Flush(op, executor, "filter", 0);
+		context.thread.profiler.Flush(op);
 	}
 };
 
diff --git a/src/execution/operator/join/physical_hash_join.cpp b/src/execution/operator/join/physical_hash_join.cpp
index ae5eb161c1e8..ebba0e70dd69 100644
--- a/src/execution/operator/join/physical_hash_join.cpp
+++ b/src/execution/operator/join/physical_hash_join.cpp
@@ -337,7 +337,7 @@ SinkCombineResultType PhysicalHashJoin::Combine(ExecutionContext &context, Opera
 	}
 
 	auto &client_profiler = QueryProfiler::Get(context.client);
-	context.thread.profiler.Flush(*this, lstate.join_key_executor, "join_key_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
 	if (filter_pushdown) {
 		filter_pushdown->Combine(*gstate.global_filter_state, *lstate.local_filter_state);
@@ -694,7 +694,7 @@ class HashJoinOperatorState : public CachingOperatorState {
 
 public:
 	void Finalize(const PhysicalOperator &op, ExecutionContext &context) override {
-		context.thread.profiler.Flush(op, probe_executor, "probe_executor", 0);
+		context.thread.profiler.Flush(op);
 	}
 };
 
diff --git a/src/execution/operator/join/physical_iejoin.cpp b/src/execution/operator/join/physical_iejoin.cpp
index f76f6bd3de87..64d09966dd5d 100644
--- a/src/execution/operator/join/physical_iejoin.cpp
+++ b/src/execution/operator/join/physical_iejoin.cpp
@@ -147,7 +147,7 @@ SinkCombineResultType PhysicalIEJoin::Combine(ExecutionContext &context, Operato
 	gstate.tables[gstate.child]->Combine(lstate.table);
 	auto &client_profiler = QueryProfiler::Get(context.client);
 
-	context.thread.profiler.Flush(*this, lstate.table.executor, gstate.child ? "rhs_executor" : "lhs_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
 
 	return SinkCombineResultType::FINISHED;
diff --git a/src/execution/operator/join/physical_nested_loop_join.cpp b/src/execution/operator/join/physical_nested_loop_join.cpp
index 60e4eb2ff18c..022337e5a3c8 100644
--- a/src/execution/operator/join/physical_nested_loop_join.cpp
+++ b/src/execution/operator/join/physical_nested_loop_join.cpp
@@ -200,12 +200,9 @@ SinkResultType PhysicalNestedLoopJoin::Sink(ExecutionContext &context, DataChunk
 
 SinkCombineResultType PhysicalNestedLoopJoin::Combine(ExecutionContext &context,
                                                       OperatorSinkCombineInput &input) const {
-	auto &state = input.local_state.Cast<NestedLoopJoinLocalState>();
 	auto &client_profiler = QueryProfiler::Get(context.client);
-
-	context.thread.profiler.Flush(*this, state.rhs_executor, "rhs_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
-
 	return SinkCombineResultType::FINISHED;
 }
 
@@ -266,7 +263,7 @@ class PhysicalNestedLoopJoinState : public CachingOperatorState {
 
 public:
 	void Finalize(const PhysicalOperator &op, ExecutionContext &context) override {
-		context.thread.profiler.Flush(op, lhs_executor, "lhs_executor", 0);
+		context.thread.profiler.Flush(op);
 	}
 };
 
diff --git a/src/execution/operator/join/physical_piecewise_merge_join.cpp b/src/execution/operator/join/physical_piecewise_merge_join.cpp
index 2500c01aef60..d7b30423e3a0 100644
--- a/src/execution/operator/join/physical_piecewise_merge_join.cpp
+++ b/src/execution/operator/join/physical_piecewise_merge_join.cpp
@@ -125,7 +125,7 @@ SinkCombineResultType PhysicalPiecewiseMergeJoin::Combine(ExecutionContext &cont
 	gstate.table->Combine(lstate.table);
 	auto &client_profiler = QueryProfiler::Get(context.client);
 
-	context.thread.profiler.Flush(*this, lstate.table.executor, "rhs_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
 
 	return SinkCombineResultType::FINISHED;
@@ -250,7 +250,7 @@ class PiecewiseMergeJoinState : public CachingOperatorState {
 
 	void Finalize(const PhysicalOperator &op, ExecutionContext &context) override {
 		if (lhs_local_table) {
-			context.thread.profiler.Flush(op, lhs_local_table->executor, "lhs_executor", 0);
+			context.thread.profiler.Flush(op);
 		}
 	}
 };
diff --git a/src/execution/operator/persistent/physical_batch_insert.cpp b/src/execution/operator/persistent/physical_batch_insert.cpp
index a65d1cca71ac..3f62e5df0504 100644
--- a/src/execution/operator/persistent/physical_batch_insert.cpp
+++ b/src/execution/operator/persistent/physical_batch_insert.cpp
@@ -518,7 +518,7 @@ SinkCombineResultType PhysicalBatchInsert::Combine(ExecutionContext &context, Op
 	auto &lstate = input.local_state.Cast<BatchInsertLocalState>();
 	auto &memory_manager = gstate.memory_manager;
 	auto &client_profiler = QueryProfiler::Get(context.client);
-	context.thread.profiler.Flush(*this, lstate.default_executor, "default_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
 
 	memory_manager.UpdateMinBatchIndex(lstate.partition_info.min_batch_index.GetIndex());
diff --git a/src/execution/operator/persistent/physical_insert.cpp b/src/execution/operator/persistent/physical_insert.cpp
index 64c706b83eb9..626f28b8222a 100644
--- a/src/execution/operator/persistent/physical_insert.cpp
+++ b/src/execution/operator/persistent/physical_insert.cpp
@@ -496,7 +496,7 @@ SinkCombineResultType PhysicalInsert::Combine(ExecutionContext &context, Operato
 	auto &gstate = input.global_state.Cast<InsertGlobalState>();
 	auto &lstate = input.local_state.Cast<InsertLocalState>();
 	auto &client_profiler = QueryProfiler::Get(context.client);
-	context.thread.profiler.Flush(*this, lstate.default_executor, "default_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
 
 	if (!parallel || !lstate.local_collection) {
diff --git a/src/execution/operator/persistent/physical_update.cpp b/src/execution/operator/persistent/physical_update.cpp
index 8dc663430642..f314eb120e52 100644
--- a/src/execution/operator/persistent/physical_update.cpp
+++ b/src/execution/operator/persistent/physical_update.cpp
@@ -166,11 +166,9 @@ unique_ptr<LocalSinkState> PhysicalUpdate::GetLocalSinkState(ExecutionContext &c
 }
 
 SinkCombineResultType PhysicalUpdate::Combine(ExecutionContext &context, OperatorSinkCombineInput &input) const {
-	auto &state = input.local_state.Cast<UpdateLocalState>();
 	auto &client_profiler = QueryProfiler::Get(context.client);
-	context.thread.profiler.Flush(*this, state.default_executor, "default_executor", 1);
+	context.thread.profiler.Flush(*this);
 	client_profiler.Flush(context.thread.profiler);
-
 	return SinkCombineResultType::FINISHED;
 }
 
diff --git a/src/execution/operator/projection/physical_projection.cpp b/src/execution/operator/projection/physical_projection.cpp
index 23aac3cd008d..3f6179aa7baa 100644
--- a/src/execution/operator/projection/physical_projection.cpp
+++ b/src/execution/operator/projection/physical_projection.cpp
@@ -15,7 +15,7 @@ class ProjectionState : public OperatorState {
 
 public:
 	void Finalize(const PhysicalOperator &op, ExecutionContext &context) override {
-		context.thread.profiler.Flush(op, executor, "projection", 0);
+		context.thread.profiler.Flush(op);
 	}
 };
 
diff --git a/src/execution/operator/scan/physical_table_scan.cpp b/src/execution/operator/scan/physical_table_scan.cpp
index 8633889e3c4a..73330f2afd78 100644
--- a/src/execution/operator/scan/physical_table_scan.cpp
+++ b/src/execution/operator/scan/physical_table_scan.cpp
@@ -136,6 +136,7 @@ string PhysicalTableScan::GetName() const {
 
 InsertionOrderPreservingMap<string> PhysicalTableScan::ParamsToString() const {
 	InsertionOrderPreservingMap<string> result;
+	result["Function"] = StringUtil::Upper(function.name);
 	if (function.to_string) {
 		result["Stringified"] = function.to_string(bind_data.get());
 	}
diff --git a/src/include/duckdb.h b/src/include/duckdb.h
index 20e65a41734c..cd74bb699526 100644
--- a/src/include/duckdb.h
+++ b/src/include/duckdb.h
@@ -3308,24 +3308,6 @@ Returns the child node at the specified index.
 */
 DUCKDB_API duckdb_profiling_info duckdb_profiling_info_get_child(duckdb_profiling_info info, idx_t index);
 
-/*!
-Returns the operator name of the current profiling info node, if the node is an Operator Node.
-
-* @param info A profiling information object.
-* @return The name of the operator of the current node. Returns nullptr, if the node is not an Operator Node. The result
-must be freed with `duckdb_free`.
-*/
-DUCKDB_API const char *duckdb_profiling_info_get_name(duckdb_profiling_info info);
-
-/*!
-Returns the query of the current profiling info node, if the node is the root.
-
-* @param info A profiling information object.
-* @return The query of the current node. Returns nullptr, if the node is not the root. The result must be freed
- * with `duckdb_free`.
-*/
-DUCKDB_API const char *duckdb_profiling_info_get_query(duckdb_profiling_info info);
-
 //===--------------------------------------------------------------------===//
 // Appender
 //===--------------------------------------------------------------------===//
diff --git a/src/include/duckdb/common/enum_util.hpp b/src/include/duckdb/common/enum_util.hpp
index 24c9ca32ae47..f6cd19c8b0ff 100644
--- a/src/include/duckdb/common/enum_util.hpp
+++ b/src/include/duckdb/common/enum_util.hpp
@@ -250,8 +250,6 @@ enum class PreparedStatementMode : uint8_t;
 
 enum class ProfilerPrintFormat : uint8_t;
 
-enum class ProfilingNodeType : uint8_t;
-
 enum class QuantileSerializationType : uint8_t;
 
 enum class QueryNodeType : uint8_t;
@@ -684,9 +682,6 @@ const char* EnumUtil::ToChars<PreparedStatementMode>(PreparedStatementMode value
 template<>
 const char* EnumUtil::ToChars<ProfilerPrintFormat>(ProfilerPrintFormat value);
 
-template<>
-const char* EnumUtil::ToChars<ProfilingNodeType>(ProfilingNodeType value);
-
 template<>
 const char* EnumUtil::ToChars<QuantileSerializationType>(QuantileSerializationType value);
 
@@ -1171,9 +1166,6 @@ PreparedStatementMode EnumUtil::FromString<PreparedStatementMode>(const char *va
 template<>
 ProfilerPrintFormat EnumUtil::FromString<ProfilerPrintFormat>(const char *value);
 
-template<>
-ProfilingNodeType EnumUtil::FromString<ProfilingNodeType>(const char *value);
-
 template<>
 QuantileSerializationType EnumUtil::FromString<QuantileSerializationType>(const char *value);
 
diff --git a/src/include/duckdb/execution/executor.hpp b/src/include/duckdb/execution/executor.hpp
index 149b04603b77..481ffd2c09d6 100644
--- a/src/include/duckdb/execution/executor.hpp
+++ b/src/include/duckdb/execution/executor.hpp
@@ -37,6 +37,9 @@ class Executor {
 	friend class PipelineTask;
 	friend class PipelineBuildState;
 
+public:
+	static constexpr idx_t WAIT_TIME = 20;
+
 public:
 	explicit Executor(ClientContext &context);
 	~Executor();
@@ -176,5 +179,8 @@ class Executor {
 
 	//! Currently alive executor tasks
 	atomic<idx_t> executor_tasks;
+
+	//! Total time blocked while waiting on tasks. In ticks. One tick corresponds to WAIT_TIME.
+	atomic<idx_t> blocked_thread_time;
 };
 } // namespace duckdb
diff --git a/src/include/duckdb/main/capi/extension_api.hpp b/src/include/duckdb/main/capi/extension_api.hpp
index a12fe6ae4ab4..0ca479ae6d46 100644
--- a/src/include/duckdb/main/capi/extension_api.hpp
+++ b/src/include/duckdb/main/capi/extension_api.hpp
@@ -268,8 +268,6 @@ typedef struct {
 	duckdb_value (*duckdb_profiling_info_get_value)(duckdb_profiling_info info, const char *key);
 	idx_t (*duckdb_profiling_info_get_child_count)(duckdb_profiling_info info);
 	duckdb_profiling_info (*duckdb_profiling_info_get_child)(duckdb_profiling_info info, idx_t index);
-	const char *(*duckdb_profiling_info_get_name)(duckdb_profiling_info info);
-	const char *(*duckdb_profiling_info_get_query)(duckdb_profiling_info info);
 	void (*duckdb_scalar_function_set_varargs)(duckdb_scalar_function scalar_function, duckdb_logical_type type);
 	void (*duckdb_scalar_function_set_special_handling)(duckdb_scalar_function scalar_function);
 	void (*duckdb_scalar_function_set_volatile)(duckdb_scalar_function scalar_function);
@@ -587,8 +585,6 @@ inline duckdb_ext_api_v0 CreateAPIv0() {
 	result.duckdb_profiling_info_get_value = duckdb_profiling_info_get_value;
 	result.duckdb_profiling_info_get_child_count = duckdb_profiling_info_get_child_count;
 	result.duckdb_profiling_info_get_child = duckdb_profiling_info_get_child;
-	result.duckdb_profiling_info_get_name = duckdb_profiling_info_get_name;
-	result.duckdb_profiling_info_get_query = duckdb_profiling_info_get_query;
 	result.duckdb_scalar_function_set_varargs = duckdb_scalar_function_set_varargs;
 	result.duckdb_scalar_function_set_special_handling = duckdb_scalar_function_set_special_handling;
 	result.duckdb_scalar_function_set_volatile = duckdb_scalar_function_set_volatile;
diff --git a/src/include/duckdb/main/capi/header_generation/apis/v0/v0.0/v0.0.2.json b/src/include/duckdb/main/capi/header_generation/apis/v0/v0.0/v0.0.2.json
index 11420d560017..c81bbf9aa46c 100644
--- a/src/include/duckdb/main/capi/header_generation/apis/v0/v0.0/v0.0.2.json
+++ b/src/include/duckdb/main/capi/header_generation/apis/v0/v0.0/v0.0.2.json
@@ -5,8 +5,6 @@
         "duckdb_profiling_info_get_value",
         "duckdb_profiling_info_get_child_count",
         "duckdb_profiling_info_get_child",
-        "duckdb_profiling_info_get_name",
-        "duckdb_profiling_info_get_query",
         "duckdb_scalar_function_set_varargs",
         "duckdb_scalar_function_set_special_handling",
         "duckdb_scalar_function_set_volatile",
diff --git a/src/include/duckdb/main/capi/header_generation/functions/profiling_info.json b/src/include/duckdb/main/capi/header_generation/functions/profiling_info.json
index 57065ce18500..ca5236a5330b 100644
--- a/src/include/duckdb/main/capi/header_generation/functions/profiling_info.json
+++ b/src/include/duckdb/main/capi/header_generation/functions/profiling_info.json
@@ -79,41 +79,6 @@
                 },
                 "return_value": "The child node at the specified index."
             }
-        },
-        {
-            "name": "duckdb_profiling_info_get_name",
-            "return_type": "const char *",
-            "params": [
-                {
-                    "type": "duckdb_profiling_info",
-                    "name": "info"
-                }
-            ],
-            "comment": {
-                "description": "Returns the operator name of the current profiling info node, if the node is an Operator Node.\n\n",
-                "param_comments": {
-                    "info": "A profiling information object."
-                },
-                "return_value": "The name of the operator of the current node. Returns nullptr, if the node is not an Operator Node. The result must be freed with `duckdb_free`."
-            }
-        },
-        {
-            "name": "duckdb_profiling_info_get_query",
-            "return_type": "const char *",
-            "params": [
-                {
-                    "type": "duckdb_profiling_info",
-                    "name": "info"
-                }
-            ],
-            "comment": {
-                "description": "Returns the query of the current profiling info node, if the node is the root.\n\n",
-                "param_comments": {
-                    "info": "A profiling information object."
-                },
-                "return_value": "The query of the current node. Returns nullptr, if the node is not the root. The result must be freed\n * with `duckdb_free`."
-            }
         }
-
     ]
 }
\ No newline at end of file
diff --git a/src/include/duckdb/main/profiling_info.hpp b/src/include/duckdb/main/profiling_info.hpp
index acdd9a30f381..52a64c1da908 100644
--- a/src/include/duckdb/main/profiling_info.hpp
+++ b/src/include/duckdb/main/profiling_info.hpp
@@ -24,9 +24,12 @@ struct yyjson_mut_val;
 namespace duckdb {
 
 enum class MetricsType : uint8_t {
+	QUERY_NAME,
+	BLOCKED_THREAD_TIME,
 	CPU_TIME,
 	EXTRA_INFO,
 	CUMULATIVE_CARDINALITY,
+	OPERATOR_TYPE,
 	OPERATOR_CARDINALITY,
 	CUMULATIVE_ROWS_SCANNED,
 	OPERATOR_ROWS_SCANNED,
@@ -44,30 +47,32 @@ typedef unordered_map<MetricsType, Value, MetricsTypeHashFunction> profiler_metr
 
 class ProfilingInfo {
 public:
-	// set of metrics with their values; only enabled metrics are present in the set
+	// Enabling a metric adds it to this set.
 	profiler_settings_t settings;
+	// Contains all enabled metrics.
 	profiler_metrics_t metrics;
+	// Additional metrics.
+	// FIXME: move to metrics.
 	InsertionOrderPreservingMap<string> extra_info;
 
 public:
 	ProfilingInfo() = default;
-	explicit ProfilingInfo(profiler_settings_t &n_settings) : settings(n_settings) {
+	explicit ProfilingInfo(profiler_settings_t &n_settings, idx_t depth = 0) : settings(n_settings) {
+		if (depth == 0) {
+			settings.insert(MetricsType::QUERY_NAME);
+		} else {
+			settings.insert(MetricsType::OPERATOR_TYPE);
+		}
 		ResetMetrics();
 	}
 	ProfilingInfo(ProfilingInfo &) = default;
 	ProfilingInfo &operator=(ProfilingInfo const &) = default;
 
 public:
-	// set the metrics set
-	void SetSettings(profiler_settings_t const &n_settings);
-	// get the metrics set
-	const profiler_settings_t &GetSettings();
 	static profiler_settings_t DefaultSettings();
 	static profiler_settings_t DefaultOperatorSettings();
 
 public:
-	// reset the metrics to default
-	void ResetSettings();
 	void ResetMetrics();
 	bool Enabled(const MetricsType setting) const;
 
@@ -77,8 +82,9 @@ class ProfilingInfo {
 
 public:
 	template <class METRIC_TYPE>
-	METRIC_TYPE &GetMetricValue(const MetricsType setting) {
-		return metrics[setting].GetValue<METRIC_TYPE>();
+	METRIC_TYPE GetMetricValue(const MetricsType setting) const {
+		auto val = metrics.at(setting);
+		return val.GetValue<METRIC_TYPE>();
 	}
 
 	template <class METRIC_TYPE>
diff --git a/src/include/duckdb/main/profiling_node.hpp b/src/include/duckdb/main/profiling_node.hpp
index e76756271d17..04d49d108625 100644
--- a/src/include/duckdb/main/profiling_node.hpp
+++ b/src/include/duckdb/main/profiling_node.hpp
@@ -24,14 +24,10 @@
 
 namespace duckdb {
 
-enum class ProfilingNodeType : uint8_t { QUERY_ROOT, OPERATOR };
-
-class QueryProfilingNode;
-
-// Recursive tree that mirrors the operator tree
+//! Recursive tree mirroring the operator tree.
 class ProfilingNode {
 public:
-	explicit ProfilingNode(ProfilingNodeType node_type) : node_type(node_type) {
+	explicit ProfilingNode() {
 	}
 	virtual ~ProfilingNode() {};
 
@@ -41,83 +37,24 @@ class ProfilingNode {
 public:
 	idx_t depth = 0;
 	vector<unique_ptr<ProfilingNode>> children;
-	ProfilingNodeType node_type = ProfilingNodeType::OPERATOR;
 
 public:
-	virtual string GetName() const = 0;
-
 	idx_t GetChildCount() {
 		return children.size();
 	}
-
 	ProfilingInfo &GetProfilingInfo() {
 		return profiling_info;
 	}
-
 	const ProfilingInfo &GetProfilingInfo() const {
 		return profiling_info;
 	}
-
 	optional_ptr<ProfilingNode> GetChild(idx_t idx) {
 		return children[idx].get();
 	}
-
 	optional_ptr<ProfilingNode> AddChild(unique_ptr<ProfilingNode> child) {
 		children.push_back(std::move(child));
 		return children.back().get();
 	}
-
-	template <class TARGET>
-	TARGET &Cast() {
-		if (node_type != TARGET::TYPE) {
-			throw InternalException("Failed to cast ProfilingNode - node type mismatch");
-		}
-		return reinterpret_cast<TARGET &>(*this);
-	}
-
-	template <class TARGET>
-	const TARGET &Cast() const {
-		if (node_type != TARGET::TYPE) {
-			throw InternalException("Failed to cast ProfilingNode - node type mismatch");
-		}
-		return reinterpret_cast<const TARGET &>(*this);
-	}
-};
-
-// Holds the top level query info
-class QueryProfilingNode : public ProfilingNode {
-public:
-	static constexpr const ProfilingNodeType TYPE = ProfilingNodeType::QUERY_ROOT;
-
-public:
-	explicit QueryProfilingNode(const string &query) : ProfilingNode(TYPE), query(query) {
-	}
-	~QueryProfilingNode() override {};
-
-public:
-	string GetName() const override {
-		return EnumUtil::ToString(node_type);
-	}
-	string query;
-};
-
-class OperatorProfilingNode : public ProfilingNode {
-public:
-	static constexpr const ProfilingNodeType TYPE = ProfilingNodeType::OPERATOR;
-
-public:
-	OperatorProfilingNode(const string &name, PhysicalOperatorType type) : ProfilingNode(TYPE), name(name), type(type) {
-	}
-	~OperatorProfilingNode() override {};
-
-public:
-	string GetName() const override {
-		return name;
-	}
-
-public:
-	string name;
-	PhysicalOperatorType type;
 };
 
 } // namespace duckdb
diff --git a/src/include/duckdb/main/query_profiler.hpp b/src/include/duckdb/main/query_profiler.hpp
index 55239635062f..3d762e8325df 100644
--- a/src/include/duckdb/main/query_profiler.hpp
+++ b/src/include/duckdb/main/query_profiler.hpp
@@ -43,11 +43,11 @@ struct OperatorInformation {
 	string name;
 
 	void AddTime(double n_time) {
-		this->time += n_time;
+		time += n_time;
 	}
 
 	void AddReturnedElements(idx_t n_elements) {
-		this->elements_returned += n_elements;
+		elements_returned += n_elements;
 	}
 };
 
@@ -62,9 +62,8 @@ class OperatorProfiler {
 	DUCKDB_API void StartOperator(optional_ptr<const PhysicalOperator> phys_op);
 	DUCKDB_API void EndOperator(optional_ptr<DataChunk> chunk);
 
-	//! Adds the timings gathered in the OperatorProfiler (tree) to the QueryProfiler (tree)
-	DUCKDB_API void Flush(const PhysicalOperator &phys_op, ExpressionExecutor &expression_executor, const string &name,
-	                      int id);
+	//! Adds the timings in the OperatorProfiler (tree) to the QueryProfiler (tree).
+	DUCKDB_API void Flush(const PhysicalOperator &phys_op);
 	DUCKDB_API OperatorInformation &GetOperatorInfo(const PhysicalOperator &phys_op);
 
 	~OperatorProfiler() {
@@ -90,6 +89,12 @@ class OperatorProfiler {
 	reference_map_t<const PhysicalOperator, OperatorInformation> timings;
 };
 
+struct QueryInfo {
+	QueryInfo() : blocked_thread_time(0) {};
+	string query_name;
+	double blocked_thread_time;
+};
+
 //! The QueryProfiler can be used to measure timings of queries
 class QueryProfiler {
 public:
@@ -121,6 +126,8 @@ class QueryProfiler {
 
 	//! Adds the timings gathered by an OperatorProfiler to this query profiler
 	DUCKDB_API void Flush(OperatorProfiler &profiler);
+	//! Adds the top level query information to the global profiler.
+	DUCKDB_API void SetInfo(const double &blocked_thread_time);
 
 	DUCKDB_API void StartPhase(string phase);
 	DUCKDB_API void EndPhase();
@@ -166,8 +173,8 @@ class QueryProfiler {
 	//! The root of the query tree
 	unique_ptr<ProfilingNode> root;
 
-	//! The query string
-	string query;
+	//! Top level query information.
+	QueryInfo query_info;
 	//! The timer used to time the execution time of the entire query
 	Profiler main_query;
 	//! A map of a Physical Operator pointer to a tree node
diff --git a/src/include/duckdb_extension.h b/src/include/duckdb_extension.h
index dc7cc513be68..15c0b421c3cc 100644
--- a/src/include/duckdb_extension.h
+++ b/src/include/duckdb_extension.h
@@ -328,8 +328,6 @@ typedef struct {
 	duckdb_value (*duckdb_profiling_info_get_value)(duckdb_profiling_info info, const char *key);
 	idx_t (*duckdb_profiling_info_get_child_count)(duckdb_profiling_info info);
 	duckdb_profiling_info (*duckdb_profiling_info_get_child)(duckdb_profiling_info info, idx_t index);
-	const char *(*duckdb_profiling_info_get_name)(duckdb_profiling_info info);
-	const char *(*duckdb_profiling_info_get_query)(duckdb_profiling_info info);
 	void (*duckdb_scalar_function_set_varargs)(duckdb_scalar_function scalar_function, duckdb_logical_type type);
 	void (*duckdb_scalar_function_set_special_handling)(duckdb_scalar_function scalar_function);
 	void (*duckdb_scalar_function_set_volatile)(duckdb_scalar_function scalar_function);
@@ -728,8 +726,6 @@ typedef struct {
 #define duckdb_profiling_info_get_value       duckdb_ext_api.duckdb_profiling_info_get_value
 #define duckdb_profiling_info_get_child_count duckdb_ext_api.duckdb_profiling_info_get_child_count
 #define duckdb_profiling_info_get_child       duckdb_ext_api.duckdb_profiling_info_get_child
-#define duckdb_profiling_info_get_name        duckdb_ext_api.duckdb_profiling_info_get_name
-#define duckdb_profiling_info_get_query       duckdb_ext_api.duckdb_profiling_info_get_query
 
 #define duckdb_table_description_create  duckdb_ext_api.duckdb_table_description_create
 #define duckdb_table_description_destroy duckdb_ext_api.duckdb_table_description_destroy
diff --git a/src/main/capi/profiling_info-c.cpp b/src/main/capi/profiling_info-c.cpp
index 8e8de02c194e..327b7c3fa5ab 100644
--- a/src/main/capi/profiling_info-c.cpp
+++ b/src/main/capi/profiling_info-c.cpp
@@ -58,27 +58,3 @@ duckdb_profiling_info duckdb_profiling_info_get_child(duckdb_profiling_info info
 	ProfilingNode *profiling_info_ptr = node.GetChild(index).get();
 	return reinterpret_cast<duckdb_profiling_info>(profiling_info_ptr);
 }
-
-const char *duckdb_profiling_info_get_name(duckdb_profiling_info info) {
-	if (!info) {
-		return nullptr;
-	}
-	auto &node = *reinterpret_cast<duckdb::ProfilingNode *>(info);
-	if (node.node_type != duckdb::ProfilingNodeType::OPERATOR) {
-		return nullptr;
-	}
-	auto &op_node = node.Cast<duckdb::OperatorProfilingNode>();
-	return strdup(op_node.name.c_str());
-}
-
-const char *duckdb_profiling_info_get_query(duckdb_profiling_info info) {
-	if (!info) {
-		return nullptr;
-	}
-	auto &node = *reinterpret_cast<duckdb::ProfilingNode *>(info);
-	if (node.node_type != duckdb::ProfilingNodeType::QUERY_ROOT) {
-		return nullptr;
-	}
-	auto &query_node = node.Cast<duckdb::QueryProfilingNode>();
-	return strdup(query_node.query.c_str());
-}
diff --git a/src/main/profiling_info.cpp b/src/main/profiling_info.cpp
index ea8a24869bf5..48d5759f0e94 100644
--- a/src/main/profiling_info.cpp
+++ b/src/main/profiling_info.cpp
@@ -9,22 +9,11 @@ using namespace duckdb_yyjson; // NOLINT
 
 namespace duckdb {
 
-void ProfilingInfo::SetSettings(profiler_settings_t const &n_settings) {
-	this->settings = n_settings;
-}
-
-const profiler_settings_t &ProfilingInfo::GetSettings() {
-	return settings;
-}
-
 profiler_settings_t ProfilingInfo::DefaultSettings() {
 	return {
-	    MetricsType::CPU_TIME,
-	    MetricsType::EXTRA_INFO,
-	    MetricsType::CUMULATIVE_CARDINALITY,
-	    MetricsType::OPERATOR_CARDINALITY,
-	    MetricsType::CUMULATIVE_ROWS_SCANNED,
-	    MetricsType::OPERATOR_ROWS_SCANNED,
+	    MetricsType::QUERY_NAME,           MetricsType::BLOCKED_THREAD_TIME,     MetricsType::CPU_TIME,
+	    MetricsType::EXTRA_INFO,           MetricsType::CUMULATIVE_CARDINALITY,  MetricsType::OPERATOR_TYPE,
+	    MetricsType::OPERATOR_CARDINALITY, MetricsType::CUMULATIVE_ROWS_SCANNED, MetricsType::OPERATOR_ROWS_SCANNED,
 	    MetricsType::OPERATOR_TIMING,
 	};
 }
@@ -37,27 +26,26 @@ profiler_settings_t ProfilingInfo::DefaultOperatorSettings() {
 	};
 }
 
-void ProfilingInfo::ResetSettings() {
-	settings.clear();
-	settings = DefaultSettings();
-}
-
 void ProfilingInfo::ResetMetrics() {
 	metrics.clear();
 
 	auto default_settings = DefaultSettings();
-
 	for (auto &metric : default_settings) {
 		if (!Enabled(metric)) {
 			continue;
 		}
 
 		switch (metric) {
+		case MetricsType::QUERY_NAME:
+		case MetricsType::BLOCKED_THREAD_TIME:
 		case MetricsType::CPU_TIME:
 		case MetricsType::OPERATOR_TIMING: {
 			metrics[metric] = Value::CreateValue(0.0);
 			break;
 		}
+		case MetricsType::OPERATOR_TYPE:
+			metrics[metric] = Value::CreateValue<uint8_t>(0);
+			break;
 		case MetricsType::CUMULATIVE_CARDINALITY:
 		case MetricsType::OPERATOR_CARDINALITY:
 		case MetricsType::CUMULATIVE_ROWS_SCANNED:
@@ -106,9 +94,14 @@ string ProfilingInfo::GetMetricAsString(MetricsType setting) const {
 		return "\"" + result + "\"";
 	}
 
-	// The metric cannot be NULL, and should have been 0 initialized.
+	// The metric cannot be NULL and must be initialized.
 	D_ASSERT(!metrics.at(setting).IsNull());
 
+	if (setting == MetricsType::OPERATOR_TYPE) {
+		auto type = PhysicalOperatorType(metrics.at(setting).GetValue<uint8_t>());
+		return EnumUtil::ToString(type);
+	}
+
 	return metrics.at(setting).ToString();
 }
 
@@ -143,11 +136,19 @@ void ProfilingInfo::WriteMetricsToJSON(yyjson_mut_doc *doc, yyjson_mut_val *dest
 		D_ASSERT(!metrics[metric].IsNull());
 
 		switch (metric) {
+		case MetricsType::QUERY_NAME:
+			yyjson_mut_obj_add_strcpy(doc, dest, key_ptr, metrics[metric].GetValue<string>().c_str());
+			break;
+		case MetricsType::BLOCKED_THREAD_TIME:
 		case MetricsType::CPU_TIME:
 		case MetricsType::OPERATOR_TIMING: {
 			yyjson_mut_obj_add_real(doc, dest, key_ptr, metrics[metric].GetValue<double>());
 			break;
 		}
+		case MetricsType::OPERATOR_TYPE: {
+			yyjson_mut_obj_add_strcpy(doc, dest, key_ptr, GetMetricAsString(metric).c_str());
+			break;
+		}
 		case MetricsType::CUMULATIVE_CARDINALITY:
 		case MetricsType::OPERATOR_CARDINALITY:
 		case MetricsType::CUMULATIVE_ROWS_SCANNED:
diff --git a/src/main/query_profiler.cpp b/src/main/query_profiler.cpp
index 6e200c88a642..ad2d566d126a 100644
--- a/src/main/query_profiler.cpp
+++ b/src/main/query_profiler.cpp
@@ -5,11 +5,9 @@
 #include "duckdb/common/numeric_utils.hpp"
 #include "duckdb/common/printer.hpp"
 #include "duckdb/common/string_util.hpp"
-#include "duckdb/common/to_string.hpp"
 #include "duckdb/common/tree_renderer/text_tree_renderer.hpp"
 #include "duckdb/execution/expression_executor.hpp"
 #include "duckdb/execution/operator/helper/physical_execute.hpp"
-#include "duckdb/execution/operator/join/physical_left_delim_join.hpp"
 #include "duckdb/execution/physical_operator.hpp"
 #include "duckdb/main/client_config.hpp"
 #include "duckdb/main/client_context.hpp"
@@ -31,11 +29,11 @@ QueryProfiler::QueryProfiler(ClientContext &context_p)
 }
 
 bool QueryProfiler::IsEnabled() const {
-	return is_explain_analyze ? true : ClientConfig::GetConfig(context).enable_profiler;
+	return is_explain_analyze || ClientConfig::GetConfig(context).enable_profiler;
 }
 
 bool QueryProfiler::IsDetailedEnabled() const {
-	return is_explain_analyze ? false : ClientConfig::GetConfig(context).enable_detailed_profiling;
+	return !is_explain_analyze && ClientConfig::GetConfig(context).enable_detailed_profiling;
 }
 
 ProfilerPrintFormat QueryProfiler::GetPrintFormat(ExplainFormat format) const {
@@ -66,8 +64,8 @@ QueryProfiler &QueryProfiler::Get(ClientContext &context) {
 	return *ClientData::Get(context).profiler;
 }
 
-void QueryProfiler::StartQuery(string query, bool is_explain_analyze, bool start_at_optimizer) {
-	if (is_explain_analyze) {
+void QueryProfiler::StartQuery(string query, bool is_explain_analyze_p, bool start_at_optimizer) {
+	if (is_explain_analyze_p) {
 		StartExplainAnalyze();
 	}
 	if (!IsEnabled()) {
@@ -82,13 +80,13 @@ void QueryProfiler::StartQuery(string query, bool is_explain_analyze, bool start
 		D_ASSERT(PrintOptimizerOutput());
 		return;
 	}
-	this->running = true;
-	this->query = std::move(query);
+
+	running = true;
+	query_info.query_name = std::move(query);
 	tree_map.clear();
 	root = nullptr;
 	phase_timings.clear();
 	phase_stack.clear();
-
 	main_query.Start();
 }
 
@@ -130,22 +128,21 @@ bool QueryProfiler::OperatorRequiresProfiling(PhysicalOperatorType op_type) {
 }
 
 void QueryProfiler::Finalize(ProfilingNode &node) {
-	auto &op_node = node.Cast<OperatorProfilingNode>();
-
-	for (idx_t i = 0; i < op_node.GetChildCount(); i++) {
-		auto child = op_node.GetChild(i);
+	for (idx_t i = 0; i < node.GetChildCount(); i++) {
+		auto child = node.GetChild(i);
 		Finalize(*child);
-		if (op_node.type == PhysicalOperatorType::UNION &&
-		    op_node.GetProfilingInfo().Enabled(MetricsType::OPERATOR_CARDINALITY)) {
-			op_node.GetProfilingInfo().AddToMetric(
-			    MetricsType::OPERATOR_CARDINALITY,
-			    child->GetProfilingInfo().metrics[MetricsType::OPERATOR_CARDINALITY].GetValue<idx_t>());
+
+		auto &info = node.GetProfilingInfo();
+		auto type = PhysicalOperatorType(info.GetMetricValue<uint8_t>(MetricsType::OPERATOR_TYPE));
+		if (type == PhysicalOperatorType::UNION && info.Enabled(MetricsType::OPERATOR_CARDINALITY)) {
+			info.AddToMetric(MetricsType::OPERATOR_CARDINALITY,
+			                 child->GetProfilingInfo().metrics[MetricsType::OPERATOR_CARDINALITY].GetValue<idx_t>());
 		}
 	}
 }
 
 void QueryProfiler::StartExplainAnalyze() {
-	this->is_explain_analyze = true;
+	is_explain_analyze = true;
 }
 
 template <class METRIC_TYPE>
@@ -167,31 +164,40 @@ void QueryProfiler::EndQuery() {
 
 	main_query.End();
 	if (root && root->GetProfilingInfo().Enabled(MetricsType::OPERATOR_CARDINALITY)) {
-		Finalize(root->GetChild(0)->Cast<OperatorProfilingNode>());
+		Finalize(*root->GetChild(0));
 	}
-	this->running = false;
-	// print or output the query profiling after termination
-	// EXPLAIN ANALYSE should not be outputted by the profiler
+	running = false;
+
+	// Print or output the query profiling after query termination.
+	// EXPLAIN ANALYZE output is not written by the profiler.
 	if (IsEnabled() && !is_explain_analyze) {
-		// Expand the query info
+		// Expand the query info.
 		if (root) {
-			auto &query_info = root->Cast<QueryProfilingNode>();
-			query_info.query = query;
-			query_info.GetProfilingInfo() = ProfilingInfo(ClientConfig::GetConfig(context).profiler_settings);
-			if (query_info.GetProfilingInfo().Enabled(MetricsType::OPERATOR_TIMING)) {
-				query_info.GetProfilingInfo().metrics[MetricsType::OPERATOR_TIMING] = main_query.Elapsed();
+			auto &info = root->GetProfilingInfo();
+			info = ProfilingInfo(ClientConfig::GetConfig(context).profiler_settings);
+			info.metrics[MetricsType::QUERY_NAME] = query_info.query_name;
+
+			if (info.Enabled(MetricsType::BLOCKED_THREAD_TIME)) {
+				info.metrics[MetricsType::BLOCKED_THREAD_TIME] = query_info.blocked_thread_time;
+			}
+			if (info.Enabled(MetricsType::OPERATOR_TIMING)) {
+				info.metrics[MetricsType::OPERATOR_TIMING] = main_query.Elapsed();
 			}
-			if (query_info.GetProfilingInfo().Enabled(MetricsType::CPU_TIME)) {
+			if (info.Enabled(MetricsType::CPU_TIME)) {
 				GetCumulativeMetric<double>(*root, MetricsType::CPU_TIME, MetricsType::OPERATOR_TIMING);
 			}
-			if (query_info.GetProfilingInfo().Enabled(MetricsType::CUMULATIVE_CARDINALITY)) {
+			if (info.Enabled(MetricsType::CUMULATIVE_CARDINALITY)) {
 				GetCumulativeMetric<idx_t>(*root, MetricsType::CUMULATIVE_CARDINALITY,
 				                           MetricsType::OPERATOR_CARDINALITY);
 			}
-			if (query_info.GetProfilingInfo().Enabled(MetricsType::CUMULATIVE_ROWS_SCANNED)) {
+			if (info.Enabled(MetricsType::CUMULATIVE_ROWS_SCANNED)) {
 				GetCumulativeMetric<idx_t>(*root, MetricsType::CUMULATIVE_ROWS_SCANNED,
 				                           MetricsType::OPERATOR_ROWS_SCANNED);
 			}
+
+			if (info.Enabled(MetricsType::OPERATOR_TYPE)) {
+				info.settings.erase(MetricsType::OPERATOR_TYPE);
+			}
 		}
 
 		string tree = ToString();
@@ -206,7 +212,8 @@ void QueryProfiler::EndQuery() {
 			WriteToFile(save_location.c_str(), tree);
 		}
 	}
-	this->is_explain_analyze = false;
+
+	is_explain_analyze = false;
 }
 
 string QueryProfiler::ToString(ExplainFormat explain_format) const {
@@ -252,7 +259,7 @@ void QueryProfiler::EndPhase() {
 	if (!IsEnabled() || !running) {
 		return;
 	}
-	D_ASSERT(phase_stack.size() > 0);
+	D_ASSERT(!phase_stack.empty());
 
 	// end the timer
 	phase_profiler.End();
@@ -352,8 +359,7 @@ OperatorInformation &OperatorProfiler::GetOperatorInfo(const PhysicalOperator &p
 	}
 }
 
-void OperatorProfiler::Flush(const PhysicalOperator &phys_op, ExpressionExecutor &expression_executor,
-                             const string &name, int id) {
+void OperatorProfiler::Flush(const PhysicalOperator &phys_op) {
 	auto entry = timings.find(phys_op);
 	if (entry == timings.end()) {
 		return;
@@ -397,6 +403,15 @@ void QueryProfiler::Flush(OperatorProfiler &profiler) {
 	profiler.timings.clear();
 }
 
+void QueryProfiler::SetInfo(const double &blocked_thread_time) {
+	lock_guard<mutex> guard(flush_lock);
+	if (!IsEnabled() || !running || !root->GetProfilingInfo().Enabled(MetricsType::BLOCKED_THREAD_TIME)) {
+		return;
+	}
+
+	query_info.blocked_thread_time = blocked_thread_time;
+}
+
 string QueryProfiler::DrawPadded(const string &str, idx_t width) {
 	if (str.size() > width) {
 		return str.substr(0, width);
@@ -451,11 +466,11 @@ void QueryProfiler::QueryTreeToStream(std::ostream &ss) const {
 	ss << "││    Query Profiling Information    ││\n";
 	ss << "│└───────────────────────────────────┘│\n";
 	ss << "└─────────────────────────────────────┘\n";
-	ss << StringUtil::Replace(query, "\n", " ") + "\n";
+	ss << StringUtil::Replace(query_info.query_name, "\n", " ") + "\n";
 
 	// checking the tree to ensure the query is really empty
 	// the query string is empty when a logical plan is deserialized
-	if (query.empty() && !root) {
+	if (query_info.query_name.empty() && !root) {
 		return;
 	}
 
@@ -549,16 +564,13 @@ string QueryProfiler::JSONSanitize(const std::string &text) {
 }
 
 static yyjson_mut_val *ToJSONRecursive(yyjson_mut_doc *doc, ProfilingNode &node) {
-	auto &op_node = node.Cast<OperatorProfilingNode>();
-
 	auto result_obj = yyjson_mut_obj(doc);
-	auto node_name = QueryProfiler::JSONSanitize(op_node.name);
-	yyjson_mut_obj_add_strcpy(doc, result_obj, "name", node_name.c_str());
-	op_node.GetProfilingInfo().WriteMetricsToJSON(doc, result_obj);
+
+	node.GetProfilingInfo().WriteMetricsToJSON(doc, result_obj);
 
 	auto children_list = yyjson_mut_arr(doc);
-	for (idx_t i = 0; i < op_node.GetChildCount(); i++) {
-		auto child = ToJSONRecursive(doc, *op_node.GetChild(i));
+	for (idx_t i = 0; i < node.GetChildCount(); i++) {
+		auto child = ToJSONRecursive(doc, *node.GetChild(i));
 		yyjson_mut_arr_add_val(children_list, child);
 	}
 	yyjson_mut_obj_add_val(doc, result_obj, "children", children_list);
@@ -587,7 +599,7 @@ string QueryProfiler::ToJSON() const {
 		yyjson_mut_obj_add_str(doc, result_obj, "result", "disabled");
 		return StringifyAndFree(doc, result_obj);
 	}
-	if (query.empty() && !root) {
+	if (query_info.query_name.empty() && !root) {
 		yyjson_mut_obj_add_str(doc, result_obj, "result", "empty");
 		return StringifyAndFree(doc, result_obj);
 	}
@@ -596,11 +608,7 @@ string QueryProfiler::ToJSON() const {
 		return StringifyAndFree(doc, result_obj);
 	}
 
-	auto &query_info = root->Cast<QueryProfilingNode>();
-	auto &settings = query_info.GetProfilingInfo();
-
-	auto query = JSONSanitize(query_info.query);
-	yyjson_mut_obj_add_strcpy(doc, result_obj, "query", query.c_str());
+	auto &settings = root->GetProfilingInfo();
 
 	settings.WriteMetricsToJSON(doc, result_obj);
 	if (settings.Enabled(MetricsType::EXTRA_INFO)) {
@@ -633,29 +641,29 @@ void QueryProfiler::WriteToFile(const char *path, string &info) const {
 	}
 }
 
-unique_ptr<ProfilingNode> QueryProfiler::CreateTree(const PhysicalOperator &root, profiler_settings_t settings,
+unique_ptr<ProfilingNode> QueryProfiler::CreateTree(const PhysicalOperator &root_p, profiler_settings_t settings,
                                                     idx_t depth) {
-	if (OperatorRequiresProfiling(root.type)) {
-		this->query_requires_profiling = true;
+	if (OperatorRequiresProfiling(root_p.type)) {
+		query_requires_profiling = true;
 	}
 
-	unique_ptr<ProfilingNode> node;
+	unique_ptr<ProfilingNode> node = make_uniq<ProfilingNode>();
+	auto &info = node->GetProfilingInfo();
+	info = ProfilingInfo(settings, depth);
+	node->depth = depth;
 
-	if (depth == 0) {
-		auto query_node = make_uniq<QueryProfilingNode>("");
-		node = std::move(query_node);
-	} else {
-		auto op_node = make_uniq<OperatorProfilingNode>(root.GetName(), root.type);
-		node = std::move(op_node);
+	if (depth != 0) {
+		info.AddToMetric<uint8_t>(MetricsType::OPERATOR_TYPE, static_cast<uint8_t>(root_p.type));
+		if (info.Enabled(MetricsType::QUERY_NAME)) {
+			info.settings.erase(MetricsType::QUERY_NAME);
+		}
 	}
-
-	node->depth = depth;
-	node->GetProfilingInfo() = ProfilingInfo(settings);
-	if (node->GetProfilingInfo().Enabled(MetricsType::EXTRA_INFO)) {
-		node->GetProfilingInfo().extra_info = root.ParamsToString();
+	if (info.Enabled(MetricsType::EXTRA_INFO)) {
+		info.extra_info = root_p.ParamsToString();
 	}
-	tree_map.insert(make_pair(reference<const PhysicalOperator>(root), reference<ProfilingNode>(*node)));
-	auto children = root.GetChildren();
+
+	tree_map.insert(make_pair(reference<const PhysicalOperator>(root_p), reference<ProfilingNode>(*node)));
+	auto children = root_p.GetChildren();
 	for (auto &child : children) {
 		auto child_node = CreateTree(child.get(), settings, depth + 1);
 		node->AddChild(std::move(child_node));
@@ -710,7 +718,7 @@ vector<QueryProfiler::PhaseTimingItem> QueryProfiler::GetOrderedPhaseTimings() c
 	return result;
 }
 
-void QueryProfiler::Propagate(QueryProfiler &qp) {
+void QueryProfiler::Propagate(QueryProfiler &) {
 }
 
 } // namespace duckdb
diff --git a/src/main/settings/settings.cpp b/src/main/settings/settings.cpp
index c9e515195352..bd250a6b31d8 100644
--- a/src/main/settings/settings.cpp
+++ b/src/main/settings/settings.cpp
@@ -741,11 +741,13 @@ void CustomProfilingSettings::SetLocal(ClientContext &context, const Value &inpu
 		                  input.ToString());
 	}
 
+	config.enable_profiler = true;
 	config.profiler_settings = FillTreeNodeSettings(json);
 }
 
 void CustomProfilingSettings::ResetLocal(ClientContext &context) {
 	auto &config = ClientConfig::GetConfig(context);
+	config.enable_profiler = ClientConfig().enable_profiler;
 	config.profiler_settings = ProfilingInfo::DefaultSettings();
 }
 
diff --git a/src/parallel/executor.cpp b/src/parallel/executor.cpp
index 0d27e05e3f74..d64ada50b884 100644
--- a/src/parallel/executor.cpp
+++ b/src/parallel/executor.cpp
@@ -22,7 +22,7 @@
 
 namespace duckdb {
 
-Executor::Executor(ClientContext &context) : context(context), executor_tasks(0) {
+Executor::Executor(ClientContext &context) : context(context), executor_tasks(0), blocked_thread_time(0) {
 }
 
 Executor::~Executor() {
@@ -466,7 +466,7 @@ void Executor::SignalTaskRescheduled(lock_guard<mutex> &) {
 }
 
 void Executor::WaitForTask() {
-	static constexpr std::chrono::milliseconds WAIT_TIME = std::chrono::milliseconds(20);
+	static constexpr std::chrono::milliseconds WAIT_TIME_MS = std::chrono::milliseconds(WAIT_TIME);
 	std::unique_lock<mutex> l(executor_lock);
 	if (to_be_rescheduled_tasks.empty()) {
 		return;
@@ -476,7 +476,8 @@ void Executor::WaitForTask() {
 		return;
 	}
 
-	task_reschedule.wait_for(l, WAIT_TIME);
+	blocked_thread_time++;
+	task_reschedule.wait_for(l, WAIT_TIME_MS);
 }
 
 void Executor::RescheduleTask(shared_ptr<Task> &task_p) {
@@ -668,10 +669,14 @@ void Executor::ThrowException() {
 	error_manager.ThrowException();
 }
 
-void Executor::Flush(ThreadContext &tcontext) {
+void Executor::Flush(ThreadContext &thread_context) {
+	static constexpr std::chrono::milliseconds WAIT_TIME_MS = std::chrono::milliseconds(WAIT_TIME);
 	auto global_profiler = profiler;
 	if (global_profiler) {
-		global_profiler->Flush(tcontext.profiler);
+		global_profiler->Flush(thread_context.profiler);
+
+		auto blocked_time = blocked_thread_time.load();
+		global_profiler->SetInfo(double(blocked_time * WAIT_TIME_MS.count()) / 1000);
 	}
 }
 


Test patch:
diff --git a/test/api/capi/test_capi_profiling.cpp b/test/api/capi/test_capi_profiling.cpp
index 79aa41d9e369..e6bdd8cb0611 100644
--- a/test/api/capi/test_capi_profiling.cpp
+++ b/test/api/capi/test_capi_profiling.cpp
@@ -25,10 +25,19 @@ void RetrieveAllMetrics(duckdb_profiling_info profiling_info, const duckdb::vect
 				duckdb_destroy_value(&value);
 				continue;
 			}
+			if (settings[i] == "QUERY_NAME" || settings[i] == "OPERATOR_NAME") {
+				auto str_value = duckdb_get_varchar(value);
+				REQUIRE(str_value != nullptr);
+				duckdb_free(str_value);
+			}
 			double result = 0;
 			try {
 				auto str_value = duckdb_get_varchar(value);
-				result = std::stod(str_value);
+				if (settings[i] == "QUERY_NAME" || settings[i] == "OPERATOR_NAME") {
+					REQUIRE(str_value != nullptr);
+				} else {
+					result = std::stod(str_value);
+				}
 				duckdb_free(str_value);
 			} catch (std::invalid_argument &e) {
 				REQUIRE(false);
@@ -51,30 +60,14 @@ void RetrieveAllMetrics(duckdb_profiling_info profiling_info, const duckdb::vect
 
 // Traverse the tree and retrieve all metrics
 void TraverseTree(duckdb_profiling_info profiling_info, const duckdb::vector<string> &settings,
-                  duckdb::map<string, double> &cumulative_counter, duckdb::map<string, double> &cumulative_result,
-                  bool is_root = true) {
-	if (is_root) {
-		// At the root, only the query name is available
-		auto query = duckdb_profiling_info_get_query(profiling_info);
-		REQUIRE(query != nullptr);
-		duckdb_free((void *)query);
-
-		REQUIRE(duckdb_profiling_info_get_name(profiling_info) == nullptr);
-	} else {
-		// At the child level, only the operator name is available
-		auto name = duckdb_profiling_info_get_name(profiling_info);
-		REQUIRE(name != nullptr);
-		duckdb_free((void *)name);
-
-		REQUIRE(duckdb_profiling_info_get_query(profiling_info) == nullptr);
-	}
-
+                  duckdb::map<string, double> &cumulative_counter, duckdb::map<string, double> &cumulative_result) {
 	RetrieveAllMetrics(profiling_info, settings, cumulative_counter, cumulative_result);
 
+	// Recurse.
 	auto child_count = duckdb_profiling_info_get_child_count(profiling_info);
 	for (idx_t i = 0; i < child_count; i++) {
 		auto child = duckdb_profiling_info_get_child(profiling_info, i);
-		TraverseTree(child, settings, cumulative_counter, cumulative_result, false);
+		TraverseTree(child, settings, cumulative_counter, cumulative_result);
 	}
 }
 
@@ -122,8 +115,8 @@ TEST_CASE("Test Profiling with All Metrics", "[capi]") {
 	REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
 
 	// test all profiling metrics
-	duckdb::vector<string> settings = {"CPU_TIME", "CUMULATIVE_CARDINALITY", "EXTRA_INFO", "OPERATOR_CARDINALITY",
-	                                   "OPERATOR_TIMING"};
+	duckdb::vector<string> settings = {"BLOCKED_THREAD_TIME",  "CPU_TIME",       "CUMULATIVE_CARDINALITY", "EXTRA_INFO",
+	                                   "OPERATOR_CARDINALITY", "OPERATOR_TIMING"};
 	REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildProfilingSettingsString(settings)));
 
 	REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
@@ -132,7 +125,6 @@ TEST_CASE("Test Profiling with All Metrics", "[capi]") {
 	REQUIRE(profiling_info != nullptr);
 
 	duckdb::map<string, double> cumulative_counter = {{"OPERATOR_TIMING", 0}, {"OPERATOR_CARDINALITY", 0}};
-
 	duckdb::map<string, double> cumulative_result {
 	    {"CPU_TIME", 0},
 	    {"CUMULATIVE_CARDINALITY", 0},
diff --git a/test/sql/explain/test_explain_analyze.test b/test/sql/explain/test_explain_analyze.test
index 1385ae54f1e9..6af71a8a7c09 100644
--- a/test/sql/explain/test_explain_analyze.test
+++ b/test/sql/explain/test_explain_analyze.test
@@ -37,7 +37,7 @@ analyzed_plan	<REGEX>:.*CROSS_PRODUCT.*
 query II
 EXPLAIN (ANALYZE, FORMAT JSON) SELECT SUM(i) FROM (SELECT * FROM integers i1, integers i2 UNION ALL SELECT * FROM integers i1, integers i2);
 ----
-analyzed_plan	<REGEX>:.*"name": "CROSS_PRODUCT".*
+analyzed_plan	<REGEX>:.*"operator_type": "CROSS_PRODUCT".*
 
 statement ok
 PRAGMA profiling_output='__TEST_DIR__/test.json'
diff --git a/test/sql/pragma/test_custom_profiling_settings.test b/test/sql/pragma/test_custom_profiling_settings.test
index 817f0ffa88bf..4d09f9c19cf0 100644
--- a/test/sql/pragma/test_custom_profiling_settings.test
+++ b/test/sql/pragma/test_custom_profiling_settings.test
@@ -17,6 +17,9 @@ PRAGMA profiling_output = 'test/sql/pragma/output.json';
 statement ok
 select unnest(['Maia', 'Thijs', 'Mark', 'Hannes', 'Tom', 'Max', 'Carlo', 'Sam', 'Tania']) as names order by random();
 
+statement ok
+PRAGMA disable_profiling;
+
 statement ok
 CREATE OR REPLACE TABLE metrics_output AS SELECT * FROM 'test/sql/pragma/output.json';
 
@@ -30,6 +33,7 @@ SELECT unnest(res) from (
 	string_split(setting, ', ') as res
 )
 ----
+"BLOCKED_THREAD_TIME": "true"
 "CPU_TIME": "true"
 "CUMULATIVE_CARDINALITY": "true"
 "CUMULATIVE_ROWS_SCANNED": "true"
@@ -37,6 +41,8 @@ SELECT unnest(res) from (
 "OPERATOR_CARDINALITY": "true"
 "OPERATOR_ROWS_SCANNED": "true"
 "OPERATOR_TIMING": "true"
+"OPERATOR_TYPE": "true"
+"QUERY_NAME": "true"
 
 # Turn off all settings
 statement ok
@@ -221,7 +227,7 @@ true
 
 # Rerun the same query but without the operator metrics to make sure the cumulative metrics are still collected
 statement ok
-PRAGMA custom_profiling_settings='{"CUMULATIVE_CARDINALITY": "true", "CUMULATIVE_ROWS_SCANNED": "true"}';
+PRAGMA custom_profiling_settings='{"CUMULATIVE_CARDINALITY": "true", "CUMULATIVE_ROWS_SCANNED": "true", "BLOCKED_THREAD_TIME": "true"}';
 
 statement ok
 PRAGMA enable_profiling = 'json';
@@ -249,6 +255,36 @@ FROM metrics_output;
 ----
 true
 
+## Create a large table to check that idle thread time is collected
+statement ok
+pragma threads=4;
+
+statement ok
+CREATE TABLE bigdata AS SELECT i AS col_a, i AS col_b FROM range(0,10000) tbl(i);
+
+statement ok
+PRAGMA custom_profiling_settings='{"BLOCKED_THREAD_TIME": "true"}';
+
+# Complicated query to make sure that idle thread time is collected
+statement ok
+SELECT (SELECT COUNT(*) FROM bigdata WHERE col_a = 1) + (SELECT COUNT(*) FROM bigdata WHERE col_b = 1);
+
+statement ok
+PRAGMA disable_profiling;
+
+statement ok
+CREATE OR REPLACE TABLE metrics_output AS SELECT * FROM 'test/sql/pragma/output.json';
+
+# Check that idle thread time is collected
+query I rowsort
+SELECT unnest(res) from (
+	select current_setting('custom_profiling_settings') as raw_setting,
+	raw_setting.trim('{}') as setting,
+	string_split(setting, ', ') as res
+)
+----
+"BLOCKED_THREAD_TIME": "true"
+
 ## ERRORS
 statement error
 SELECT operator_timing FROM metrics_output;