Skip to content

Commit

Permalink
adds otel; improves docker build
Browse files Browse the repository at this point in the history
  • Loading branch information
dalton-oliveira committed Dec 8, 2023
1 parent e7a437a commit 3ddf7aa
Show file tree
Hide file tree
Showing 10 changed files with 1,572 additions and 421 deletions.
1,728 changes: 1,394 additions & 334 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ Multiplayer Snake game
repository = "https://github.com/dalton-oliveira/snake-rs"
readme = "./README.md"
license = "MIT"

[workspace.dependencies]
opentelemetry = { version="0.21" }
opentelemetry-http = "0.10.0"
opentelemetry-jaeger = { version="0.20.0" }
opentelemetry_sdk = { version="0.21.1" }
opentelemetry-prometheus = { version = "0.14.0" }
17 changes: 6 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
FROM messense/rust-musl-cross:x86_64-musl as chef
RUN cargo install cargo-chef wasm-bindgen-cli
RUN rustup target add wasm32-unknown-unknown
RUN apt update && apt install wget
RUN wget https://dmej8g5cpdyqd.cloudfront.net/downloads/noip-duc_3.0.0-beta.7.tar.gz && tar xf noip-duc_3.0.0-beta.7.tar.gz
# RUN ls noip-duc_3.0.0-beta.7/binaries -lah
# RUN noip-duc_3.0.0-beta.7/binaries/noip-duc_3.0.0-beta.7_x86_64-musl

WORKDIR /snake

FROM chef AS planner
# Copy source code from previous stage
COPY . .
# Generate info for caching dependencies
RUN cargo chef prepare --recipe-path recipe-wasm.json
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS builder
COPY --from=planner /snake/recipe.json recipe.json
# Build & cache dependencies
RUN cargo chef cook --release --recipe-path recipe.json
# Copy source code from previous stage
COPY --from=planner /snake/recipe-wasm.json recipe-wasm.json
RUN cargo chef cook --release --target wasm32-unknown-unknown --recipe-path recipe-wasm.json -p wasm-render
RUN cargo chef cook --release --recipe-path recipe.json -p snake-web

COPY . .
# Build application
RUN bash build.sh

# Create a new stage with a minimal image
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ cargo run -p snake-termion
## Roadmap

- Trace backend and front-end calls with [Open Telemetry](https://github.com/open-telemetry/opentelemetry-rust)
- Add unit and integration tests
- Experiment WebRTC in order to reduce latency
- Add unit and integration tests
- Run it on a embedded system with restricted memory and processing power
- Large world where the snake can navigate to stress test chosen data structures
- Other game elements such as walls and wormholes
Expand Down
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
set -e
cargo build -p wasm-render --target wasm32-unknown-unknown --release
wasm-bindgen --target web --out-dir snake-web/www/wasm --no-typescript ./target/wasm32-unknown-unknown/release/wasm_render.wasm
cargo build --release
cargo build -p snake-web --release
18 changes: 15 additions & 3 deletions snake-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ edition.workspace = true

[dependencies]
snake = { path = "../core" }
tokio = { version = "1.32.0", features = ["macros"] }
salvo = { version = "0.58.0", features = ["websocket", "serve-static"] }
tokio = { version = "1", features = ["macros"] }
salvo = { version = "0.59.0", features = ["websocket", "serve-static", "otel", "affix"] }
once_cell = "1"
futures-util = { version = "0.3", default-features = false }
rust-embed = "8.0.0"
bincode = "2.0.0-rc.3"
tracing = "0.1.39"
tracing = "0.1"
tracing-subscriber = "0.3.17"
prometheus = "0.13"
opentelemetry-prometheus.workspace = true
opentelemetry = { workspace = true, features = ["metrics"] }
opentelemetry-http = { workspace = true }
opentelemetry-jaeger = { workspace = true, features = ["rt-tokio", "collector_client", "hyper_collector_client"] }
opentelemetry_sdk = { workspace = true, features = ["rt-tokio", "metrics"] }
tracing-opentelemetry = {version = "0.22.0", features = ["metrics"]}
sentry = "0.32.0"
sentry-tracing = "0.32.0"
opentelemetry-otlp = "0.14.0"
opentelemetry-stdout = { version="0.2.0", features = ["metrics"] }
openssl = { version = "0.10.54", features = ["vendored"] }
34 changes: 12 additions & 22 deletions snake-web/src/input_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@ use salvo::websocket::WebSocket;
use snake::{game::Game, types::Direction, utils::decode};
use std::{sync::Arc, time::SystemTime};
use tokio::sync::RwLock;
use tracing::debug;
use tracing::{instrument, Span};

use crate::{websocket_game::DIRECTION, websocket_game::PING, DirectionArc};
use crate::{websocket_game::DIRECTION, websocket_game::PING};

pub fn rx_commands(
direction_arc: DirectionArc,
snake_id: u16,
mut rx: SplitStream<WebSocket>,
game: Arc<RwLock<Game>>,
) {
#[instrument(skip_all)]
pub fn rx_commands(snake_id: u16, mut rx: SplitStream<WebSocket>, game: Arc<RwLock<Game>>) {
let fut = async move {
let mut direction = direction_arc.read().await.clone();
while let Some(result) = rx.next().await {
let now = SystemTime::now();
match result {
Err(_msg) => break,
Ok(msg) => {
Expand All @@ -25,15 +21,10 @@ pub fn rx_commands(
continue;
}
match msg {
[PING, ..] => log_ping(msg, snake_id),
[PING, ..] => log_ping(msg, now, snake_id),
[DIRECTION, code] => {
if let Some(next_direction) = to_direction(*code) {
if direction == next_direction {
continue;
}
let mut d = RwLock::write(&direction_arc).await;
*d = next_direction;
direction = next_direction;
RwLock::write(&game).await.head_to(snake_id, next_direction);
}
}
_ => continue,
Expand All @@ -46,13 +37,12 @@ pub fn rx_commands(
tokio::task::spawn(fut);
}

fn log_ping(msg: &[u8], snake_id: u16) {
#[instrument(fields(ping_ms, ping_μs) skip(msg, now))]
fn log_ping(msg: &[u8], now: SystemTime, snake_id: u16) {
let (past, _size): (SystemTime, usize) = decode(&msg[1..msg.len()]).unwrap();
let μs = SystemTime::now()
.duration_since(past)
.expect("ping measure error")
.as_millis();
debug!("snake: {snake_id} ping: {:?} ms", μs);
let duration = now.duration_since(past).expect("ping measure error");
Span::current().record("ping_ms", duration.as_millis());
Span::current().record("ping_μs", duration.as_micros());
}

fn to_direction(msg: u8) -> Option<Direction> {
Expand Down
89 changes: 84 additions & 5 deletions snake-web/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,99 @@
use std::sync::Arc;

use once_cell::sync::Lazy;

use opentelemetry::trace::TracerProvider;
use opentelemetry::{global, KeyValue};
use opentelemetry_sdk::metrics::{MeterProvider, PeriodicReader};
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::Tracer};
use opentelemetry_sdk::{runtime, Resource};

use salvo::otel::{Metrics, Tracing};

use rust_embed::RustEmbed;
use salvo::prelude::*;
use salvo::serve_static::static_embed;
use salvo::websocket::WebSocketUpgrade;
use snake_web::websocket_game::WsGame;
use tracing::Level;
use tracing_subscriber;
use tracing::level_filters::LevelFilter;
use tracing::{instrument, Instrument, Level, Span};
use tracing_opentelemetry::MetricsLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{self};

#[derive(RustEmbed)]
#[folder = "www/"]
struct Assets;

static GAME: Lazy<WsGame> = Lazy::new(|| WsGame::new());
const PORT_BIND: &str = "80";
// const SENTRY_SDN: &str =
// "https://f372487ad5a8d2f18b10fc0a9331f6c9@o4506340046077952.ingest.sentry.io/4506340055842816";

fn init_meter_provider() -> MeterProvider {
// let aa = DefaultTemporalitySelector::default()
// .temporality(opentelemetry_sdk::metrics::InstrumentKind::Histogram)
// .
// .clone();
// let exporter = MetricsExporterBuilder::default()
// .with_temporality_selector(TemporalitySelector::temporality(Temporality::Delta))
// .build();
let exporter = opentelemetry_stdout::MetricsExporter::default();
let reader = PeriodicReader::builder(exporter, runtime::Tokio)
// .with_interval(Duration::from_secs(1))
.build();
MeterProvider::builder()
.with_reader(reader)
.with_resource(Resource::new(vec![KeyValue::new(
"service.snake-web",
"metrics-basic-example",
)]))
.build()
}

fn init_tracer() -> Tracer {
global::set_text_map_propagator(TraceContextPropagator::new());
let provider = opentelemetry_sdk::trace::TracerProvider::builder().build();
let tracer = provider.tracer("snake-web");
global::set_tracer_provider(provider);

let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer.clone());

let meter_provider = init_meter_provider();

let opentelemetry_metrics = MetricsLayer::new(meter_provider);

tracing_subscriber::registry()
.with(sentry_tracing::layer())
.with(LevelFilter::from_level(Level::DEBUG))
.with(opentelemetry)
.with(opentelemetry_metrics)
.init();

return tracer;
}

#[tokio::main]
async fn main() {
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
let tracer = init_tracer();

let _guard = std::env::var("SENTRY_SDN").ok().map(|sdn| {
println!("{}", sdn);
sentry::init((
sdn,
sentry::ClientOptions {
release: sentry::release_name!(),
traces_sample_rate: 1.0,
..Default::default()
},
))
});

GAME.start_game();
let router = Router::new()
.hoop(affix::inject(Arc::new(tracer.clone())))
.hoop(Metrics::new())
.hoop(Tracing::new(tracer))
.push(Router::with_path("game_data").goal(user_connected))
.push(Router::with_path("<*path>").get(static_embed::<Assets>().fallback("index.html")));

Expand All @@ -33,8 +106,14 @@ async fn main() {
}

#[handler]
async fn user_connected(req: &mut Request, res: &mut Response) -> Result<(), StatusError> {
#[instrument(skip_all)]
async fn user_connected(
req: &mut Request,
res: &mut Response,
_depot: &mut Depot,
) -> Result<(), StatusError> {
let span = Span::current();
WebSocketUpgrade::new()
.upgrade(req, res, |ws| GAME.ingress_user(ws))
.upgrade(req, res, |ws| GAME.ingress_user(ws).instrument(span))
.await
}
Loading

0 comments on commit 3ddf7aa

Please sign in to comment.