Skip to content

Commit 2429b73

Browse files
authored
Merge pull request #890 from adwinwhite/ws_metrics
2 parents 578f5d2 + 94cd35b commit 2429b73

File tree

8 files changed

+183
-5
lines changed

8 files changed

+183
-5
lines changed

tests/spec/features/tools_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def code_with_undefined_behavior
6161

6262
within(:output, :stdout) do
6363
# First-party
64-
expect(page).to have_content('core::fmt::Arguments::new_v1')
64+
expect(page).to have_content('::std::io::_print')
6565

6666
# Third-party procedural macro
6767
expect(page).to have_content('block_on(body)')

ui/Cargo.lock

Lines changed: 104 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fork-bomb-prevention = []
1010

1111
[dependencies]
1212
async-trait = "0.1.52"
13-
axum = { version = "0.5", features = ["headers"] }
13+
axum = { version = "0.5", features = ["headers", "ws"] }
1414
dotenv = "0.15.0"
1515
env_logger = "0.9.0"
1616
futures = "0.3.21"

ui/frontend/declarations.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ interface Window {
2020
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;
2121
rustPlayground: {
2222
setCode(code: string): void;
23+
webSocket: WebSocket | null;
2324
};
2425
}

ui/frontend/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import PageSwitcher from './PageSwitcher';
2424
import playgroundApp from './reducers';
2525
import Router from './Router';
2626
import configureStore from './configureStore';
27+
import openWebSocket from './websocket';
28+
29+
const params = new URLSearchParams(window.location.search);
30+
// openWebSocket() may return null.
31+
const socket = params.has('websocket') ? openWebSocket(window.location) : null;
2732

2833
const store = configureStore(window);
2934

@@ -49,6 +54,9 @@ window.rustPlayground = {
4954
setCode: code => {
5055
store.dispatch(editCode(code));
5156
},
57+
// Temporarily storing this as a global to prevent it from being
58+
// garbage collected (at least by Safari).
59+
webSocket: socket,
5260
};
5361

5462
const container = document.getElementById('playground');

ui/frontend/websocket.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default function openWebSocket(currentLocation: Location) {
2+
try {
3+
const wsProtocol = currentLocation.protocol === 'https:' ? 'wss://' : 'ws://';
4+
const wsUri = [wsProtocol, currentLocation.host, '/websocket'].join('');
5+
return new WebSocket(wsUri);
6+
} catch {
7+
// WebSocket URL error or WebSocket is not supported by browser.
8+
// Assume it's the second case since URL error is easy to notice.
9+
(async () => {
10+
try {
11+
await fetch('/nowebsocket', {
12+
method: 'post',
13+
headers: {
14+
'Content-Length': '0',
15+
},
16+
});
17+
} catch (e) {
18+
console.log('Error:', e);
19+
}
20+
})();
21+
return null;
22+
}
23+
}

ui/src/metrics.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use futures::future::BoxFuture;
22
use lazy_static::lazy_static;
3-
use prometheus::{self, register_histogram_vec, HistogramVec};
3+
use prometheus::{
4+
self, register_histogram, register_histogram_vec, register_int_counter, register_int_gauge,
5+
Histogram, HistogramVec, IntCounter, IntGauge,
6+
};
47
use regex::Regex;
58
use std::{future::Future, time::Instant};
69

@@ -14,6 +17,22 @@ lazy_static! {
1417
vec![0.1, 1.0, 2.5, 5.0, 10.0, 15.0]
1518
)
1619
.unwrap();
20+
pub(crate) static ref LIVE_WS: IntGauge = register_int_gauge!(
21+
"active_websocket_connections_count",
22+
"Number of active WebSocket connections"
23+
)
24+
.unwrap();
25+
pub(crate) static ref DURATION_WS: Histogram = register_histogram!(
26+
"websocket_duration_seconds",
27+
"WebSocket connection length",
28+
vec![15.0, 60.0, 300.0, 600.0, 1800.0, 3600.0, 7200.0]
29+
)
30+
.unwrap();
31+
pub(crate) static ref UNAVAILABLE_WS: IntCounter = register_int_counter!(
32+
"websocket_unavailability_count",
33+
"Number of failed WebSocket connections"
34+
)
35+
.unwrap();
1736
}
1837

1938
#[derive(Debug, Copy, Clone, strum::IntoStaticStr)]

ui/src/server_axum.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
gist,
33
metrics::{
44
track_metric_async, track_metric_force_endpoint_async, track_metric_no_request_async,
5-
Endpoint, GenerateLabels, SuccessDetails,
5+
Endpoint, GenerateLabels, SuccessDetails, DURATION_WS, LIVE_WS, UNAVAILABLE_WS,
66
},
77
sandbox::{self, Channel, Sandbox},
88
CachingSnafu, ClippyRequest, ClippyResponse, CompilationSnafu, CompileRequest, CompileResponse,
@@ -15,7 +15,11 @@ use crate::{
1515
};
1616
use async_trait::async_trait;
1717
use axum::{
18-
extract::{self, Extension, Path, TypedHeader},
18+
extract::{
19+
self,
20+
ws::{WebSocket, WebSocketUpgrade},
21+
Extension, Path, TypedHeader,
22+
},
1923
handler::Handler,
2024
headers::{authorization::Bearer, Authorization, CacheControl, ETag, IfNoneMatch},
2125
http::{header, uri::PathAndQuery, HeaderValue, Method, Request, StatusCode, Uri},
@@ -78,6 +82,8 @@ pub(crate) async fn serve(config: Config) {
7882
.route("/meta/gist", post(meta_gist_create))
7983
.route("/meta/gist/:id", get(meta_gist_get))
8084
.route("/metrics", get(metrics))
85+
.route("/websocket", get(websocket))
86+
.route("/nowebsocket", post(nowebsocket))
8187
.layer(Extension(Arc::new(SandboxCache::default())))
8288
.layer(Extension(config.github_token()));
8389

@@ -386,6 +392,23 @@ async fn metrics(_: MetricsAuthorization) -> Result<Vec<u8>, StatusCode> {
386392
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
387393
}
388394

395+
async fn websocket(ws: WebSocketUpgrade) -> impl IntoResponse {
396+
ws.on_upgrade(handle_socket)
397+
}
398+
399+
async fn handle_socket(mut socket: WebSocket) {
400+
LIVE_WS.inc();
401+
let start = Instant::now();
402+
while let Some(Ok(_msg)) = socket.recv().await {}
403+
LIVE_WS.dec();
404+
let elapsed = start.elapsed();
405+
DURATION_WS.observe(elapsed.as_secs_f64());
406+
}
407+
408+
async fn nowebsocket() {
409+
UNAVAILABLE_WS.inc();
410+
}
411+
389412
#[derive(Debug)]
390413
struct MetricsAuthorization;
391414

0 commit comments

Comments
 (0)