aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--notes/rust_libraries.txt22
-rw-r--r--rust/env.example3
-rw-r--r--rust/src/bin/fatcatd.rs29
-rw-r--r--rust/src/endpoints.rs55
-rw-r--r--rust/src/server.rs12
5 files changed, 101 insertions, 20 deletions
diff --git a/notes/rust_libraries.txt b/notes/rust_libraries.txt
index 7e6f33eb..d5c8c18a 100644
--- a/notes/rust_libraries.txt
+++ b/notes/rust_libraries.txt
@@ -8,6 +8,28 @@ libs:
- cadence (emits statsd)
- frank_jwt and JWT for (simple?) auth
+metrics:
+- best would be something with a configurable back-end, like 'log' for logging,
+ but supporing tags/labels. the prometheus model probably makes most sense by
+ default (really nice to be able to grab metrics with 'curl'/browser for
+ individual instances), but statsd seems to be what we run in production. not
+ spewing out lots of UDP by default seems like a good idea.
+- dipstick: has all the good features, and popular, but code quality has smells
+ ("a32dlkjhw"-style commit messages), and API doesn't seem very clean. Also
+ prometheus stuff not actually implemented
+- cadence: seems stable, somewhat popular, clean API. statsd-only for now, but
+ has custom backends that could be hooked on to. *super* few dependencies,
+ nice.
+- tic: many deps; doesn't seem stable or under development
+- rust-prometheus: developed by pingcap (large company). has push and pull
+ features. medum-sized deps; has feature flags
+
+A nice feature of a statsd solution is that collectd is usually running
+locally (on linux dev, or in production), and metrics can be sent there by
+default, like journald for logging.
+
+Seems like a decision between cadence (statsd) and rust-prometheus.
+
similar:
- https://github.com/DavidBM/templic-backend
- https://github.com/alexanderbanks/rust-api
diff --git a/rust/env.example b/rust/env.example
index 482f2400..bee653c2 100644
--- a/rust/env.example
+++ b/rust/env.example
@@ -5,3 +5,6 @@ AUTH_KEY_IDENT="20190101-dev-dummy-key"
AUTH_SECRET_KEY="5555555555555555555555555555555555555555xms="
AUTH_ALT_KEYS="20181220-dev:6666666666666666666666666666666666666666xms=,20181210-dev:7777777777777777777777777777777777777777xms="
#SENTRY_DSN=
+# should be localhost IP, not 'localhost'
+#FATCAT_STATSD_HOST="127.0.0.1"
+#FATCAT_STATSD_PORT="8125"
diff --git a/rust/src/bin/fatcatd.rs b/rust/src/bin/fatcatd.rs
index 388c6e61..df5c390e 100644
--- a/rust/src/bin/fatcatd.rs
+++ b/rust/src/bin/fatcatd.rs
@@ -15,6 +15,9 @@ use iron_slog::{DefaultLogFormatter, LoggerMiddleware};
use sentry::integrations::panic;
use slog::{Drain, Logger};
use std::env;
+use cadence::{StatsdClient, QueuingMetricSink, BufferedUdpMetricSink};
+use cadence::prelude::*;
+use std::net::UdpSocket;
// HTTP header middleware
header! { (XClacksOverhead, "X-Clacks-Overhead") => [String] }
@@ -29,6 +32,7 @@ impl AfterMiddleware for XClacksOverheadMiddleware {
}
}
+
/// Create custom server, wire it to the autogenerated router,
/// and pass it to the web server.
fn main() -> Result<()> {
@@ -60,7 +64,30 @@ fn main() -> Result<()> {
None
};
- let server = create_server()?;
+ let mut server = create_server()?;
+
+ // metrics reporting
+ match env::var("FATCAT_STATSD_HOST") {
+ Err(_) => {
+ info!(logger, "no metrics recipient configured");
+ },
+ Ok(host) => {
+ let port: u16 = match env::var("FATCAT_STATSD_PORT") {
+ Err(_) => cadence::DEFAULT_PORT,
+ Ok(var) => var.parse::<u16>()?, // "expect FATCAT_STATSD_PORT to be null or an integer
+ };
+ let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
+ socket.set_nonblocking(true).unwrap();
+ let udp_sink = BufferedUdpMetricSink::from((host.as_ref(), port), socket).unwrap();
+ let queuing_sink = QueuingMetricSink::from(udp_sink);
+ info!(logger, "sending statsd metrics via UDP to: {}:{}", host, port);
+ server.metrics = StatsdClient::from_sink("fatcat.api", queuing_sink);
+ //server.metrics = StatsdClient::from_udp_host("fatcat.api", (host.as_ref(), port))?;
+ server.metrics.incr("restart").unwrap();
+ }
+ };
+ info!(logger, "{:#?}", server.metrics);
+
info!(
logger,
"using primary auth key: {}", server.auth_confectionary.identifier,
diff --git a/rust/src/endpoints.rs b/rust/src/endpoints.rs
index 1bed3c56..027e25bc 100644
--- a/rust/src/endpoints.rs
+++ b/rust/src/endpoints.rs
@@ -22,6 +22,7 @@ use futures::{self, Future};
use sentry::integrations::failure::capture_fail;
use std::str::FromStr;
use uuid::{self, Uuid};
+use cadence::prelude::*;
// This makes response matching below *much* more terse
use crate::errors::FatcatError::*;
@@ -121,8 +122,10 @@ macro_rules! wrap_entity_handlers {
edit_context.check(&conn)?;
entity.db_create(&conn, &edit_context)?.into_model()
}).map_err(|e| FatcatError::from(e)) {
- Ok(edit) =>
- $post_resp::CreatedEntity(edit),
+ Ok(edit) => {
+ self.metrics.incr("entities.created").ok();
+ $post_resp::CreatedEntity(edit)
+ },
Err(fe) => generic_auth_err_responses!(fe, $post_resp),
};
Box::new(futures::done(Ok(ret)))
@@ -146,8 +149,14 @@ macro_rules! wrap_entity_handlers {
} else { None };
self.$post_batch_handler(&conn, entity_list, autoaccept.unwrap_or(false), auth_context.editor_id, editgroup_id)
}).map_err(|e| FatcatError::from(e)) {
- Ok(edit) =>
- $post_batch_resp::CreatedEntities(edit),
+ Ok(edits) => {
+ self.metrics.count("entities.created", edits.len() as i64).ok();
+ if let Some(true) = autoaccept {
+ self.metrics.incr("editgroup.created").ok();
+ self.metrics.incr("editgroup.accepted").ok();
+ };
+ $post_batch_resp::CreatedEntities(edits)
+ },
Err(fe) => generic_auth_err_responses!(fe, $post_batch_resp),
};
Box::new(futures::done(Ok(ret)))
@@ -171,8 +180,10 @@ macro_rules! wrap_entity_handlers {
edit_context.check(&conn)?;
entity.db_update(&conn, &edit_context, entity_id)?.into_model()
}).map_err(|e| FatcatError::from(e)) {
- Ok(edit) =>
- $update_resp::UpdatedEntity(edit),
+ Ok(edit) => {
+ self.metrics.incr("entities.updated").ok();
+ $update_resp::UpdatedEntity(edit)
+ },
Err(fe) => generic_auth_err_responses!(fe, $update_resp),
};
Box::new(futures::done(Ok(ret)))
@@ -195,8 +206,10 @@ macro_rules! wrap_entity_handlers {
edit_context.check(&conn)?;
$model::db_delete(&conn, &edit_context, entity_id)?.into_model()
}).map_err(|e| FatcatError::from(e)) {
- Ok(edit) =>
- $delete_resp::DeletedEntity(edit),
+ Ok(edit) => {
+ self.metrics.incr("entities.deleted").ok();
+ $delete_resp::DeletedEntity(edit)
+ },
Err(fe) => generic_auth_err_responses!(fe, $delete_resp),
};
Box::new(futures::done(Ok(ret)))
@@ -745,10 +758,13 @@ impl Api for Server {
})
.map_err(|e| FatcatError::from(e))
{
- Ok(()) => AcceptEditgroupResponse::MergedSuccessfully(Success {
- success: true,
- message: "horray!".to_string(),
- }),
+ Ok(()) => {
+ self.metrics.incr("editgroup.accepted").ok();
+ AcceptEditgroupResponse::MergedSuccessfully(Success {
+ success: true,
+ message: "horray!".to_string(),
+ })
+ },
Err(fe) => generic_auth_err_responses!(fe, AcceptEditgroupResponse),
};
Box::new(futures::done(Ok(ret)))
@@ -804,7 +820,10 @@ impl Api for Server {
})
.map_err(|e| FatcatError::from(e))
{
- Ok(eg) => CreateEditgroupResponse::SuccessfullyCreated(eg),
+ Ok(eg) => {
+ self.metrics.incr("editgroup.created").ok();
+ CreateEditgroupResponse::SuccessfullyCreated(eg)
+ },
Err(fe) => match fe {
NotFound(_, _) => CreateEditgroupResponse::NotFound(fe.into()),
DatabaseError(_) | InternalError(_) => {
@@ -884,8 +903,14 @@ impl Api for Server {
})
.map_err(|e: Error| FatcatError::from(e))
{
- Ok((result, true)) => AuthOidcResponse::Created(result),
- Ok((result, false)) => AuthOidcResponse::Found(result),
+ Ok((result, true)) => {
+ self.metrics.incr("account.signup").ok();
+ AuthOidcResponse::Created(result)
+ },
+ Ok((result, false)) => {
+ self.metrics.incr("account.login").ok();
+ AuthOidcResponse::Found(result)
+ },
Err(fe) => match fe {
DatabaseError(_) | InternalError(_) => {
error!("{}", fe);
diff --git a/rust/src/server.rs b/rust/src/server.rs
index 6b389a97..66b215fc 100644
--- a/rust/src/server.rs
+++ b/rust/src/server.rs
@@ -8,6 +8,7 @@ use diesel::pg::PgConnection;
use diesel::r2d2::ConnectionManager;
use dotenv::dotenv;
use std::env;
+use cadence::{StatsdClient, NopMetricSink};
#[cfg(feature = "postgres")]
embed_migrations!("../migrations/");
@@ -32,6 +33,7 @@ pub fn database_worker_pool() -> Result<ConnectionPool> {
pub struct Server {
pub db_pool: ConnectionPool,
pub auth_confectionary: AuthConfectionary,
+ pub metrics: StatsdClient,
}
/// Instantiate a new API server with a pooled database connection
@@ -39,13 +41,15 @@ pub fn create_server() -> Result<Server> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let manager = ConnectionManager::<PgConnection>::new(database_url);
- let pool = diesel::r2d2::Pool::builder()
+ let db_pool = diesel::r2d2::Pool::builder()
.build(manager)
.expect("Failed to create database pool.");
- let confectionary = env_confectionary()?;
+ let auth_confectionary = env_confectionary()?;
+ let metrics = StatsdClient::from_sink("blackhole", NopMetricSink);
Ok(Server {
- db_pool: pool,
- auth_confectionary: confectionary,
+ db_pool,
+ auth_confectionary,
+ metrics,
})
}