Skip to content

Commit 59ceaa6

Browse files
committed
update readme
1 parent b2ced8e commit 59ceaa6

1 file changed

Lines changed: 250 additions & 59 deletions

File tree

‎README.md‎

Lines changed: 250 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,277 @@
1-
# Duckspan
1+
# duckspan
22

3-
This repository is based on https://github.com/duckdb/extension-template, check it out if you want to build and ship your own DuckDB extension.
3+
Query OpenTelemetry data with SQL. Attach OTLP streams as DuckDB databases, read OTLP files.
44

5-
---
5+
## Quick Start
66

7-
This extension, Duckspan, allow you to ... <extension_goal>.
7+
```sql
8+
-- Load extension
9+
LOAD duckspan;
810

11+
-- Attach live OTLP stream (starts gRPC receiver on port 4317)
12+
ATTACH 'otlp:localhost:4317' AS live (TYPE otlp);
913

10-
## Building
11-
### Managing dependencies
12-
DuckDB extensions uses VCPKG for dependency management. Enabling VCPKG is very simple: follow the [installation instructions](https://vcpkg.io/en/getting-started) or just run the following:
13-
```shell
14-
git clone https://github.com/Microsoft/vcpkg.git
15-
./vcpkg/bootstrap-vcpkg.sh
16-
export VCPKG_TOOLCHAIN_PATH=`pwd`/vcpkg/scripts/buildsystems/vcpkg.cmake
14+
-- Query accumulated telemetry
15+
SELECT * FROM live.traces;
16+
SELECT * FROM live.metrics;
17+
SELECT * FROM live.logs;
18+
19+
-- Stop receiver
20+
DETACH live;
21+
22+
-- Read OTLP files (JSON or protobuf, auto-detected)
23+
SELECT * FROM read_otlp('traces.jsonl');
24+
SELECT * FROM read_otlp('s3://bucket/traces/*.pb');
1725
```
18-
Note: VCPKG is only required for extensions that want to rely on it for dependency management. If you want to develop an extension without dependencies, or want to do your own dependency management, just skip this step. Note that the example extension uses VCPKG to build with a dependency for instructive purposes, so when skipping this step the build may not work without removing the dependency.
1926

20-
### Build steps
21-
Now to build the extension, run:
22-
```sh
23-
make
27+
## Features
28+
29+
**Live OTLP Streams**
30+
- `ATTACH` creates a gRPC receiver on specified port
31+
- Auto-creates tables: `{name}.traces`, `{name}.metrics`, `{name}.logs`
32+
- OpenTelemetry SDKs send data → DuckDB accumulates it
33+
- `DETACH` stops receiver and removes database
34+
35+
**File Reading**
36+
- `read_otlp(filepath)` reads OTLP JSON/protobuf files
37+
- Supports JSON and protobuf formats (auto-detected)
38+
- Works with local files, S3, HTTP, Azure, GCS
39+
- Same schema as attached tables
40+
41+
**Unified Schema**
42+
- All signal types use: `(timestamp TIMESTAMP, resource JSON, data JSON)`
43+
- Query with DuckDB's JSON functions
44+
- `resource` = service/host metadata
45+
- `data` = signal-specific payload
46+
47+
## Usage
48+
49+
### ATTACH/DETACH Lifecycle
50+
51+
```sql
52+
-- Start receiver on port 4317
53+
ATTACH 'otlp:localhost:4317' AS live (TYPE otlp);
54+
55+
-- Different port
56+
ATTACH 'otlp:0.0.0.0:4318' AS metrics (TYPE otlp);
57+
58+
-- Multiple simultaneous streams
59+
ATTACH 'otlp:localhost:4317' AS traces1 (TYPE otlp);
60+
ATTACH 'otlp:localhost:4318' AS traces2 (TYPE otlp);
61+
62+
-- Cleanup
63+
DETACH traces1;
64+
DETACH traces2;
2465
```
25-
The main binaries that will be built are:
26-
```sh
27-
./build/release/duckdb
28-
./build/release/test/unittest
29-
./build/release/extension/duckspan/duckspan.duckdb_extension
66+
67+
### Querying Traces
68+
69+
```sql
70+
-- Extract service name and span details
71+
SELECT
72+
json_extract(resource, '$.service.name') as service,
73+
json_extract(data, '$.name') as span_name,
74+
json_extract(data, '$.traceId') as trace_id,
75+
timestamp
76+
FROM live.traces
77+
WHERE timestamp > NOW() - INTERVAL 1 HOUR;
78+
79+
-- Find slow requests (duration > 1 second)
80+
SELECT
81+
json_extract(resource, '$.service.name') as service,
82+
json_extract(data, '$.name') as operation,
83+
(json_extract(data, '$.endTimeUnixNano')::BIGINT -
84+
json_extract(data, '$.startTimeUnixNano')::BIGINT) / 1000000 as duration_ms
85+
FROM live.traces
86+
WHERE duration_ms > 1000
87+
ORDER BY duration_ms DESC;
88+
89+
-- Error rate by service
90+
SELECT
91+
json_extract(resource, '$.service.name') as service,
92+
COUNT(*) as total_spans,
93+
SUM(CASE WHEN json_extract(data, '$.status.code') != 0 THEN 1 ELSE 0 END) as errors,
94+
(errors::FLOAT / total_spans * 100)::INT as error_pct
95+
FROM live.traces
96+
WHERE timestamp > NOW() - INTERVAL 15 MINUTES
97+
GROUP BY service;
3098
```
31-
- `duckdb` is the binary for the duckdb shell with the extension code automatically loaded.
32-
- `unittest` is the test runner of duckdb. Again, the extension is already linked into the binary.
33-
- `duckspan.duckdb_extension` is the loadable binary as it would be distributed.
3499

35-
## Running the extension
36-
To run the extension code, simply start the shell with `./build/release/duckdb`.
100+
### Reading Files
101+
102+
```sql
103+
-- Local files
104+
SELECT COUNT(*) FROM read_otlp('test/data/traces.jsonl');
105+
106+
-- S3 (with DuckDB's S3 support)
107+
SELECT * FROM read_otlp('s3://bucket/telemetry/*.jsonl');
108+
109+
-- HTTP
110+
SELECT * FROM read_otlp('https://example.com/traces.jsonl');
111+
112+
-- Protobuf (auto-detected)
113+
SELECT * FROM read_otlp('traces.pb');
37114

38-
Now we can use the features from the extension directly in DuckDB. The template contains a single scalar function `duckspan()` that takes a string arguments and returns a string:
115+
-- Verify schema
116+
SELECT
117+
typeof(timestamp) as ts_type,
118+
typeof(resource) as resource_type,
119+
typeof(data) as data_type
120+
FROM read_otlp('traces.jsonl')
121+
LIMIT 1;
39122
```
40-
D select duckspan('Jane') as result;
41-
┌───────────────┐
42-
│ result │
43-
│ varchar │
44-
├───────────────┤
45-
│ Duckspan Jane 🐥 │
46-
└───────────────┘
123+
124+
### Persisting Stream Data
125+
126+
```sql
127+
-- Attach and accumulate data
128+
ATTACH 'otlp:localhost:4317' AS live (TYPE otlp);
129+
130+
-- Let telemetry flow in for a while...
131+
132+
-- Save to permanent tables
133+
CREATE TABLE archive_traces AS SELECT * FROM live.traces;
134+
CREATE TABLE archive_metrics AS SELECT * FROM live.metrics;
135+
136+
-- Stop receiver
137+
DETACH live;
138+
139+
-- Query archived data
140+
SELECT * FROM archive_traces WHERE timestamp > NOW() - INTERVAL 1 DAY;
47141
```
48142

49-
## Running the tests
50-
Different tests can be created for DuckDB extensions. The primary way of testing DuckDB extensions should be the SQL tests in `./test/sql`. These SQL tests can be run using:
51-
```sh
52-
make test
143+
## Data Model
144+
145+
All signal types (traces, metrics, logs) use the same schema:
146+
147+
| Column | Type | Description |
148+
|--------|------|-------------|
149+
| `timestamp` | TIMESTAMP | When the event occurred |
150+
| `resource` | JSON | Service/host metadata |
151+
| `data` | JSON | Signal-specific payload |
152+
153+
**Example Row:**
154+
```json
155+
{
156+
"timestamp": "2024-01-15 10:30:00",
157+
"resource": {
158+
"service.name": "api-server",
159+
"host.name": "prod-01"
160+
},
161+
"data": {
162+
"traceId": "5B8EFFF798038103D269B633813FC60C",
163+
"spanId": "EEE19B7EC3C1B174",
164+
"name": "GET /users",
165+
"startTimeUnixNano": "1640000000000000000",
166+
"endTimeUnixNano": "1640000000100000000",
167+
"attributes": {
168+
"http.method": "GET",
169+
"http.url": "/users",
170+
"http.status_code": 200
171+
},
172+
"status": {"code": 0}
173+
}
174+
}
53175
```
54176

55-
### Installing the deployed binaries
56-
To install your extension binaries from S3, you will need to do two things. Firstly, DuckDB should be launched with the
57-
`allow_unsigned_extensions` option set to true. How to set this will depend on the client you're using. Some examples:
177+
## Building
178+
179+
### Prerequisites
58180

59-
CLI:
60-
```shell
61-
duckdb -unsigned
181+
Install VCPKG for dependency management:
182+
183+
```bash
184+
git clone https://github.com/Microsoft/vcpkg.git
185+
./vcpkg/bootstrap-vcpkg.sh
186+
export VCPKG_TOOLCHAIN_PATH=`pwd`/vcpkg/scripts/buildsystems/vcpkg.cmake
62187
```
63188

64-
Python:
65-
```python
66-
con = duckdb.connect(':memory:', config={'allow_unsigned_extensions' : 'true'})
189+
### Build
190+
191+
Use ninja for performance:
192+
193+
```bash
194+
GEN=ninja make
67195
```
68196

69-
NodeJS:
70-
```js
71-
db = new duckdb.Database(':memory:', {"allow_unsigned_extensions": "true"});
197+
Builds:
198+
- `./build/release/duckdb` - DuckDB shell with extension loaded
199+
- `./build/release/test/unittest` - Test runner
200+
- `./build/release/extension/duckspan/duckspan.duckdb_extension` - Loadable extension
201+
202+
### Testing
203+
204+
```bash
205+
# SQL tests
206+
make test
207+
208+
# OTLP integration test (requires uv)
209+
uv run test/python/test_otlp_export.py
210+
211+
# Or via make
212+
make test-otlp-export
72213
```
73214

74-
Secondly, you will need to set the repository endpoint in DuckDB to the HTTP url of your bucket + version of the extension
75-
you want to install. To do this run the following SQL query in DuckDB:
76-
```sql
77-
SET custom_extension_repository='bucket.s3.eu-west-1.amazonaws.com/<your_extension_name>/latest';
215+
## Development
216+
217+
### Pre-commit Hooks
218+
219+
Uses `uv` for Python tooling:
220+
221+
```bash
222+
# Install hooks
223+
uvx --from pre-commit pre-commit install
224+
225+
# Run manually
226+
uvx --from pre-commit pre-commit run --all-files
78227
```
79-
Note that the `/latest` path will allow you to install the latest extension version available for your current version of
80-
DuckDB. To specify a specific version, you can pass the version instead.
81228

82-
After running these steps, you can install and load your extension using the regular INSTALL/LOAD commands in DuckDB:
229+
Hooks:
230+
- clang-format (C++ formatting)
231+
- black (Python formatting)
232+
- cmake-format/cmake-lint
233+
234+
### Code Formatting
235+
236+
```bash
237+
# Check format
238+
make format-check
239+
240+
# Auto-fix
241+
make format-fix
242+
```
243+
244+
## Architecture
245+
246+
- **Extension Type**: Storage extension registered for `TYPE otlp`
247+
- **ATTACH Handler**: Parses connection string, starts gRPC server, creates tables
248+
- **gRPC Receiver**: Implements OTLP collector service endpoints (traces/metrics/logs)
249+
- **Table Functions**: `read_otlp()` streams OTLP files with format auto-detection
250+
- **Data Conversion**: Protobuf → JSON for SQL queryability
251+
252+
### Dependencies (via VCPKG)
253+
- gRPC (OTLP receiver)
254+
- Protobuf (wire format)
255+
- OpenSSL (gRPC dependency)
256+
257+
## Limitations
258+
259+
**Data Retention**: Attached streams accumulate data in memory indefinitely. Manual cleanup required:
260+
83261
```sql
84-
INSTALL duckspan
85-
LOAD duckspan
262+
-- Option 1: Periodic DELETE
263+
DELETE FROM live.traces WHERE timestamp < NOW() - INTERVAL 1 HOUR;
264+
265+
-- Option 2: Periodic persist-and-detach
266+
CREATE TABLE archive AS SELECT * FROM live.traces;
267+
DETACH live;
268+
ATTACH 'otlp:localhost:4317' AS live (TYPE otlp);
86269
```
270+
271+
Future versions may add automatic TTL or persistent backing.
272+
273+
## References
274+
275+
- [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/)
276+
- [DuckDB Extensions](https://duckdb.org/docs/extensions/overview)
277+
- [Extension Template](https://github.com/duckdb/extension-template)

0 commit comments

Comments
 (0)