-
Notifications
You must be signed in to change notification settings - Fork 8
Add Distributed Tracing support #177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
slashmo
wants to merge
10
commits into
valkey-io:main
Choose a base branch
from
slashmo:feature/distributed-tracing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
8a3a0bc
Add static name to Valkey commands
slashmo 144a40c
Add Distributed Tracing support behind new trait
slashmo 03d63f1
Add open-telemetry example showcasing Distributed Tracing support
slashmo 7a889bd
Run swift-format
slashmo 8bbf9f5
Document open-telemetry example
slashmo 5606d42
Remove left-over TODO
slashmo 0fce495
Re-format docker-compose.yml
slashmo c57a3b5
Re-format ValkeyConnectionTests.swift
slashmo 5642200
Clean up service bootstrap in open-telemetry example
slashmo d09f0f2
Remove open-telemetry example
slashmo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// swift-tools-version:6.1 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "open-telemetry", | ||
platforms: [.macOS(.v15)], | ||
products: [ | ||
.executable(name: "example", targets: ["Example"]) | ||
], | ||
dependencies: [ | ||
// TODO: Change to remote once Distributed Tracing support was merged into main and/or tagged | ||
.package(path: "../../"), | ||
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"), | ||
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.0.0"), | ||
.package(url: "https://github.com/swift-otel/swift-otel.git", exact: "1.0.0-alpha.1"), | ||
], | ||
targets: [ | ||
.executableTarget( | ||
name: "Example", | ||
dependencies: [ | ||
.product(name: "Valkey", package: "valkey-swift"), | ||
.product(name: "Hummingbird", package: "hummingbird"), | ||
.product(name: "Tracing", package: "swift-distributed-tracing"), | ||
.product(name: "OTel", package: "swift-otel"), | ||
] | ||
) | ||
], | ||
swiftLanguageModes: [.v6] | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# OpenTelemetry example | ||
|
||
An example HTTP server that uses a Valkey client, both of which emit Distributed Tracing spans | ||
via [Swift OTel](https://github.com/swift-otel/swift-otel). | ||
|
||
## Overview | ||
|
||
This example bootstraps Swift OTel to export Distributed Tracing spans to Jaeger. | ||
|
||
It then starts a Hummingbird HTTP server along with its associated middleware for instrumentation. | ||
|
||
Finally, the server uses a Valkey client in its request handler to demonstrate the spans | ||
created by executing various Valkey commands. | ||
|
||
## Testing | ||
|
||
The example uses [Docker Compose](https://docs.docker.com/compose) to run a Valkey server alongside Jaeger to collect | ||
and visualize the spans from the HTTP server and Valkey client, which is running on your local machine. | ||
|
||
### Running Valkey and Jaeger | ||
|
||
In one terminal window, run the following command: | ||
|
||
```console | ||
% docker compose up | ||
[+] Running 4/4 | ||
✔ Network open-telemetry_default Created 0.0s | ||
✔ Volume "open-telemetry_valkey_data" Created 0.0s | ||
✔ Container open-telemetry-jaeger-1 Created 0.0s | ||
✔ Container open-telemetry-valkey-1 Created 0.0s | ||
... | ||
``` | ||
|
||
### Running the server | ||
|
||
Now, in another terminal, run the server locally using the following command: | ||
|
||
```console | ||
% swift run | ||
``` | ||
|
||
### Making some requests | ||
|
||
Finally, in a third terminal, make a request to the server: | ||
|
||
```console | ||
% curl http://localhost:8080/compute/42 | ||
``` | ||
|
||
The example server fakes an expensive algorithm which is hard-coded to take a couple of seconds to complete. | ||
That's why the first request will take a decent amount of time. | ||
|
||
Now, make the same request again: | ||
|
||
```console | ||
% curl http://localhost:8080/compute/42 | ||
``` | ||
|
||
You should see that it returns instantaniously. We successfully cached the previously computed value in Valkey | ||
and can now read it from the cache instead of re-computing it each time. | ||
|
||
### Visualizing the traces using Jaeger UI | ||
|
||
Visit Jaeger UI in your browser at [localhost:16686](http://localhost:16686). | ||
|
||
Select `example` from the dropdown and click `Find Traces`. | ||
|
||
You should see a handful of traces, including: | ||
|
||
#### `/compute/{x}` with an execution time of ~ 3.2 seconds | ||
|
||
This corresponds to the first request to `/42` where we had to compute the value. Click on this trace to reveal | ||
its spans. The root span represents our entire Hummingbird request handling. Nested inside are three child spans: | ||
|
||
1. `HGET`: Shows the `HGET` Valkey command used to look up the cached value for `42`. | ||
2. `compute`: Represents our expensive algorithm. We can see that this takes up the majority of the entire trace. | ||
3. `HSET`: Shows the `HSET` Valkey command sent to store the computed value for future retrieval. | ||
|
||
#### `/compute/{x}` with an execution time of a few milliseconds | ||
|
||
This span corresponds to a subsequent request to `/42` where we could utelize our cache to avoid the | ||
expensive computation. Click on this trace to reveal its spans. Like before, the root span represents | ||
the Hummingbird request handling. We can also see a single child span: | ||
|
||
1. `HGET`: Shows the `HGET` Valkey command used to look up the cached value for `42`. | ||
|
||
### Making some more requests | ||
|
||
The example also comes with a few more API endpoints to demonstrate other Valkey commands: | ||
|
||
#### Pipelined commands | ||
|
||
Send the following request to kick off multiple pipelined commands: | ||
|
||
```console | ||
% curl http://localhost:8080/multi | ||
``` | ||
|
||
This will run three pipelined `EVAL` commands and produces a trace made up of the following spans: | ||
|
||
1. `/multi`: The Hummingbird request handling. | ||
2. `MULTI`: The Valkey client span representing the execution of the pipelined commands. | ||
|
||
Click on the `MULTI` span to reveal its attributes. New here are the following two attributes: | ||
|
||
- `db.operation.batch.size`: This is set to `3` and represents the number of pipelined commands. | ||
- `db.operation.name`: This is set to `MULTI EVAL`, showing that the pipeline consists only of `EVAL` commands. | ||
|
||
#### Failing commands | ||
|
||
Send the following request to send some gibberish to Valkey resulting in an error: | ||
|
||
```console | ||
% curl http://localhost:8080/error | ||
``` | ||
|
||
This will send an `EVAL` command with invalid script contents (`EVAL not a script`) resulting in a trace | ||
made up of the following spans: | ||
|
||
1. `/error`: The Hummingbird request handling. | ||
2. `EVAL`: The Valkey client span representing the failed `EVAL` command. | ||
|
||
Click on the `EVAL` span to reveal its attributes. New here are the following two attributes: | ||
|
||
- `db.response.status_code`: This is set to `ERR` and represents the prefix of the simple error returned | ||
by Valkey. | ||
- `error`: This is set to `true` indicating that the operation failed. In Jaeger, this is additionally displayed | ||
via a red exclamation mark next to the span name. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import Hummingbird | ||
import Logging | ||
import OTel | ||
import ServiceLifecycle | ||
import Tracing | ||
import Valkey | ||
|
||
@main | ||
struct Example { | ||
static func main() async throws { | ||
let observability = try bootstrapObservability() | ||
let logger = Logger(label: "example") | ||
|
||
let valkeyClient = ValkeyClient( | ||
.hostname("localhost"), | ||
logger: logger | ||
) | ||
|
||
let router = Router() | ||
router.add(middleware: TracingMiddleware()) | ||
router.add(middleware: LogRequestsMiddleware(.info)) | ||
|
||
router.get("/compute/:x") { _, context in | ||
let x = try context.parameters.require("x", as: Int.self) | ||
|
||
func expensiveAlgorithm(_ x: Int) async throws -> Int { | ||
try await withSpan("compute") { span in | ||
span.attributes["input"] = x | ||
try await Task.sleep(for: .seconds(3)) | ||
return x * 2 | ||
} | ||
} | ||
|
||
if let cachedResult = try await valkeyClient.hget("values", field: "\(x)") { | ||
return cachedResult | ||
} | ||
|
||
let result = try await expensiveAlgorithm(x) | ||
|
||
try await valkeyClient.hset("values", data: [.init(field: "\(x)", value: "\(result)")]) | ||
|
||
return ByteBuffer(string: "\(result)") | ||
} | ||
|
||
router.get("/multi") { _, _ in | ||
_ = await valkeyClient.execute( | ||
EVAL(script: "return '1'"), | ||
EVAL(script: "return '2'"), | ||
EVAL(script: "return '3'") | ||
) | ||
return HTTPResponse.Status.ok | ||
} | ||
|
||
router.get("/error") { _, _ in | ||
_ = try? await valkeyClient.eval(script: "not a script") | ||
return HTTPResponse.Status.ok | ||
} | ||
|
||
var app = Application(router: router) | ||
app.addServices(observability, valkeyClient) | ||
|
||
try await app.runService() | ||
} | ||
|
||
private static func bootstrapObservability() throws -> some Service { | ||
LoggingSystem.bootstrap( | ||
StreamLogHandler.standardOutput(label:metadataProvider:), | ||
metadataProvider: OTel.makeLoggingMetadataProvider() | ||
) | ||
|
||
var configuration = OTel.Configuration.default | ||
configuration.serviceName = "example" | ||
|
||
// For now, valkey-swift only supports Distributed Tracing so we disable the other signals. | ||
configuration.logs.enabled = false | ||
configuration.metrics.enabled = false | ||
|
||
return try OTel.bootstrap(configuration: configuration) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
services: | ||
valkey: | ||
image: valkey/valkey:8.0 | ||
ports: | ||
- 6379:6379 | ||
healthcheck: | ||
test: ["CMD", "valkey-cli", "--raw", "incr", "ping"] | ||
volumes: | ||
- valkey_data:/data | ||
|
||
jaeger: | ||
image: jaegertracing/all-in-one:latest | ||
ports: | ||
- 4318:4318 # OTLP/HTTP receiver | ||
- 16686:16686 # Jaeger Web UI | ||
|
||
volumes: | ||
valkey_data: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.