Skip to content

Commit 2d31cce

Browse files
committed
fix: add telemetry
1 parent df5adf1 commit 2d31cce

File tree

4 files changed

+148
-14
lines changed

4 files changed

+148
-14
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension)
1414
project(${TARGET_NAME})
1515
include_directories(src/include)
1616

17-
set(EXTENSION_SOURCES src/quickjs_extension.cpp)
17+
set(EXTENSION_SOURCES src/quickjs_extension.cpp src/query_farm_telemetry.cpp)
1818

1919
build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES})
2020
build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
#include <string>
3+
#include "duckdb.hpp"
4+
5+
#if defined(_WIN32) || defined(_WIN64)
6+
// Windows: functions are hidden by default unless exported
7+
#define INTERNAL_FUNC
8+
#elif defined(__GNUC__) || defined(__clang__)
9+
// Linux / macOS: hide symbol using visibility attribute
10+
#define INTERNAL_FUNC __attribute__((visibility("hidden")))
11+
#else
12+
#define INTERNAL_FUNC
13+
#endif
14+
15+
namespace duckdb {
16+
void QueryFarmSendTelemetry(ExtensionLoader &loader, const string &extension_name, const string &extension_version);
17+
}

src/query_farm_telemetry.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#include "query_farm_telemetry.hpp"
2+
#include <thread>
3+
#include "duckdb.hpp"
4+
#include "duckdb/common/http_util.hpp"
5+
#include "yyjson.hpp"
6+
#include "duckdb/main/extension_helper.hpp"
7+
#include "duckdb/main/config.hpp"
8+
#include <cstdlib>
9+
#include <future>
10+
using namespace duckdb_yyjson; // NOLINT
11+
12+
namespace duckdb
13+
{
14+
15+
namespace
16+
{
17+
18+
// Function to send the actual HTTP request
19+
void sendHTTPRequest(shared_ptr<DatabaseInstance> db, char *json_body, size_t json_body_size)
20+
{
21+
const string TARGET_URL("https://duckdb-in.query-farm.services/");
22+
23+
HTTPHeaders headers;
24+
headers.Insert("Content-Type", "application/json");
25+
26+
auto &http_util = HTTPUtil::Get(*db);
27+
unique_ptr<HTTPParams> params = http_util.InitializeParameters(*db, TARGET_URL);
28+
29+
PostRequestInfo post_request(TARGET_URL, headers, *params, reinterpret_cast<const_data_ptr_t>(json_body),
30+
json_body_size);
31+
try
32+
{
33+
auto response = http_util.Request(post_request);
34+
}
35+
catch (const std::exception &e)
36+
{
37+
// ignore all errors.
38+
}
39+
40+
free(json_body);
41+
return;
42+
}
43+
44+
} // namespace
45+
46+
INTERNAL_FUNC void QueryFarmSendTelemetry(ExtensionLoader &loader, const string &extension_name,
47+
const string &extension_version)
48+
{
49+
const char *opt_out = std::getenv("QUERY_FARM_TELEMETRY_OPT_OUT");
50+
if (opt_out != nullptr)
51+
{
52+
return;
53+
}
54+
55+
auto &dbconfig = DBConfig::GetConfig(loader.GetDatabaseInstance());
56+
auto old_value = dbconfig.options.autoinstall_known_extensions;
57+
dbconfig.options.autoinstall_known_extensions = false;
58+
try
59+
{
60+
ExtensionHelper::AutoLoadExtension(loader.GetDatabaseInstance(), "httpfs");
61+
}
62+
catch (...)
63+
{
64+
dbconfig.options.autoinstall_known_extensions = old_value;
65+
return;
66+
}
67+
68+
dbconfig.options.autoinstall_known_extensions = old_value;
69+
if (!loader.GetDatabaseInstance().ExtensionIsLoaded("httpfs"))
70+
{
71+
return;
72+
}
73+
74+
// Initialize the telemetry sender
75+
auto doc = yyjson_mut_doc_new(nullptr);
76+
77+
auto result_obj = yyjson_mut_obj(doc);
78+
yyjson_mut_doc_set_root(doc, result_obj);
79+
80+
auto platform = DuckDB::Platform();
81+
82+
yyjson_mut_obj_add_str(doc, result_obj, "extension_name", extension_name.c_str());
83+
yyjson_mut_obj_add_str(doc, result_obj, "extension_version", extension_version.c_str());
84+
yyjson_mut_obj_add_str(doc, result_obj, "user_agent", "query-farm/20251011");
85+
yyjson_mut_obj_add_str(doc, result_obj, "duckdb_platform", platform.c_str());
86+
yyjson_mut_obj_add_str(doc, result_obj, "duckdb_library_version", DuckDB::LibraryVersion());
87+
yyjson_mut_obj_add_str(doc, result_obj, "duckdb_release_codename", DuckDB::ReleaseCodename());
88+
yyjson_mut_obj_add_str(doc, result_obj, "duckdb_source_id", DuckDB::SourceID());
89+
90+
size_t telemetry_len;
91+
auto telemetry_data =
92+
yyjson_mut_val_write_opts(result_obj, YYJSON_WRITE_ALLOW_INF_AND_NAN, NULL, &telemetry_len, nullptr);
93+
94+
if (telemetry_data == nullptr)
95+
{
96+
throw SerializationException("Failed to serialize telemetry data.");
97+
}
98+
99+
yyjson_mut_doc_free(doc);
100+
101+
#ifndef __EMSCRIPTEN__
102+
[[maybe_unused]] auto _ = std::async(
103+
std::launch::async, [db_ptr = loader.GetDatabaseInstance().shared_from_this(), json = telemetry_data,
104+
len = telemetry_len]() mutable
105+
{ sendHTTPRequest(std::move(db_ptr), json, len); });
106+
#else
107+
sendHTTPRequest(loader.GetDatabaseInstance().shared_from_this(), telemetry_data, telemetry_len);
108+
#endif
109+
}
110+
111+
} // namespace duckdb

src/quickjs_extension.cpp

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include "duckdb/common/types/value.hpp"
1010
#include "duckdb/function/table_function.hpp"
1111
#include "duckdb/parser/parsed_data/create_table_function_info.hpp"
12-
12+
#include "query_farm_telemetry.hpp"
1313

1414
#include "quickjs.h"
1515

@@ -194,7 +194,7 @@ class QuickJSTableBindData : public TableFunctionData {
194194
public:
195195
QuickJSTableBindData() {
196196
}
197-
197+
198198
std::string js_code;
199199
vector<Value> parameters;
200200
};
@@ -258,7 +258,8 @@ static void QuickJSEval(DataChunk &args, ExpressionState &state, Vector &result)
258258

259259
// Convert to std::string to ensure proper null-termination and avoid string_t inline storage issues
260260
std::string script_str = script_data[i].GetString();
261-
QuickJSValue func(ctx, JS_Eval(ctx, script_str.c_str(), script_str.length(), "<eval>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT));
261+
QuickJSValue func(ctx, JS_Eval(ctx, script_str.c_str(), script_str.length(), "<eval>",
262+
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT));
262263

263264
if (func.IsException()) {
264265
ThrowJSException(ctx);
@@ -312,7 +313,8 @@ static void QuickJSExecute(DataChunk &args, ExpressionState &state, Vector &resu
312313

313314
// Convert to std::string to ensure proper null-termination and avoid string_t inline storage issues
314315
std::string script_str = script.GetString();
315-
QuickJSValue val(ctx, JS_Eval(ctx, script_str.c_str(), script_str.length(), "<eval>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT));
316+
QuickJSValue val(ctx, JS_Eval(ctx, script_str.c_str(), script_str.length(), "<eval>",
317+
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT));
316318

317319
if (val.IsException()) {
318320
ThrowJSException(ctx);
@@ -361,7 +363,7 @@ static unique_ptr<FunctionData> QuickJSTableBind(ClientContext &context, TableFu
361363
names.push_back("result");
362364

363365
auto bind_data = make_uniq<QuickJSTableBindData>();
364-
366+
365367
// Extract the JavaScript code from the first argument
366368
if (!input.inputs[0].IsNull()) {
367369
bind_data->js_code = input.inputs[0].GetValue<string>();
@@ -392,7 +394,8 @@ static unique_ptr<GlobalTableFunctionState> QuickJSTableInit(ClientContext &cont
392394
// Create a function that takes the parameters and returns the result
393395
std::string js_function_code = "(function(";
394396
for (idx_t i = 0; i < bind_data.parameters.size(); i++) {
395-
if (i > 0) js_function_code += ", ";
397+
if (i > 0)
398+
js_function_code += ", ";
396399
js_function_code += "arg" + std::to_string(i);
397400
}
398401
js_function_code += ") { ";
@@ -405,7 +408,8 @@ static unique_ptr<GlobalTableFunctionState> QuickJSTableInit(ClientContext &cont
405408
js_function_code += "return " + bind_data.js_code + "; })";
406409

407410
// Compile the function
408-
QuickJSValue func_val(ctx, JS_Eval(ctx, js_function_code.c_str(), js_function_code.length(), "<eval>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT));
411+
QuickJSValue func_val(ctx, JS_Eval(ctx, js_function_code.c_str(), js_function_code.length(), "<eval>",
412+
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT));
409413
if (func_val.IsException()) {
410414
ThrowJSException(ctx, "Failed to compile JavaScript function");
411415
}
@@ -465,8 +469,7 @@ static void LoadInternal(ExtensionLoader &loader) {
465469
//===--------------------------------------------------------------------===//
466470
ScalarFunctionSet quickjs_set("quickjs");
467471

468-
auto quickjs_scalar_function =
469-
ScalarFunction({LogicalType::VARCHAR}, LogicalType::VARCHAR, QuickJSExecute);
472+
auto quickjs_scalar_function = ScalarFunction({LogicalType::VARCHAR}, LogicalType::VARCHAR, QuickJSExecute);
470473
quickjs_scalar_function.stability = FunctionStability::VOLATILE;
471474
quickjs_set.AddFunction(quickjs_scalar_function);
472475

@@ -486,15 +489,15 @@ static void LoadInternal(ExtensionLoader &loader) {
486489
//===--------------------------------------------------------------------===//
487490
ScalarFunctionSet quickjs_eval_set("quickjs_eval");
488491

489-
auto quickjs_eval_function =
490-
ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), QuickJSEval);
492+
auto quickjs_eval_function = ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), QuickJSEval);
491493
quickjs_eval_function.varargs = LogicalType::ANY;
492494
quickjs_eval_function.stability = FunctionStability::VOLATILE;
493495
quickjs_eval_set.AddFunction(quickjs_eval_function);
494496

495497
CreateScalarFunctionInfo quickjs_eval_info(quickjs_eval_set);
496498
FunctionDescription quickjs_eval_desc;
497-
quickjs_eval_desc.description = "Execute a JavaScript function with the provided arguments and return the result as JSON";
499+
quickjs_eval_desc.description =
500+
"Execute a JavaScript function with the provided arguments and return the result as JSON";
498501
quickjs_eval_desc.parameter_types = {LogicalType::VARCHAR};
499502
quickjs_eval_desc.parameter_names = {"function"};
500503
quickjs_eval_desc.examples = {"quickjs_eval('(a, b) => a + b', 1, 2)", "quickjs_eval('(x) => x * 2', 21)"};
@@ -508,7 +511,8 @@ static void LoadInternal(ExtensionLoader &loader) {
508511
//===--------------------------------------------------------------------===//
509512
TableFunctionSet quickjs_table_set("quickjs");
510513

511-
auto quickjs_table_function = TableFunction({LogicalType::VARCHAR}, QuickJSTableFunction, QuickJSTableBind, QuickJSTableInit);
514+
auto quickjs_table_function =
515+
TableFunction({LogicalType::VARCHAR}, QuickJSTableFunction, QuickJSTableBind, QuickJSTableInit);
512516
quickjs_table_function.varargs = LogicalType::ANY;
513517
quickjs_table_set.AddFunction(quickjs_table_function);
514518

@@ -522,6 +526,8 @@ static void LoadInternal(ExtensionLoader &loader) {
522526
quickjs_table_info.descriptions.push_back(quickjs_table_desc);
523527

524528
loader.RegisterFunction(quickjs_table_info);
529+
530+
QueryFarmSendTelemetry(loader, "quickjs", "2025120401");
525531
}
526532

527533
void QuickjsExtension::Load(ExtensionLoader &loader) {

0 commit comments

Comments
 (0)