Add optional GZIP compression to proxy /metrics endpoint (#665)

Closes #598.

According to the Prometheus documentation, metrics export endpoints should support serving metrics compressed using GZIP. I've modified the proxy's `/metrics` endpoint to serve metrics compressed with GZIP when an `Accept-Encoding: gzip` request header is sent.

I've also added a new unit test that attempts to get the proxy's metrics endpoint as GZIP, and asserts that the metrics are decompressed successfully.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
Eliza Weisman 2018-04-24 17:42:50 -07:00 committed by GitHub
parent 3389f39d63
commit 514074129e
6 changed files with 217 additions and 17 deletions

94
Cargo.lock generated
View File

@ -9,6 +9,11 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "adler32"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "aho-corasick"
version = "0.6.4"
@ -62,6 +67,11 @@ name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "build_const"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.2.1"
@ -112,8 +122,10 @@ dependencies = [
"conduit-proxy-controller-grpc 0.3.0",
"conduit-proxy-router 0.3.0",
"convert 0.3.0",
"deflate 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)",
"domain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-mpsc-lossy 0.3.0",
"futures-watch 0.1.0 (git+https://github.com/carllerche/better-future.git)",
@ -177,6 +189,24 @@ dependencies = [
name = "convert"
version = "0.3.0"
[[package]]
name = "crc"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "deflate"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gzip-header 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "domain"
version = "0.2.3"
@ -193,6 +223,14 @@ name = "either"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "enum_primitive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "env_logger"
version = "0.5.3"
@ -217,6 +255,15 @@ name = "fixedbitset"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "flate2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fnv"
version = "1.0.6"
@ -266,6 +313,15 @@ dependencies = [
"futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gzip-header"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "h2"
version = "0.1.5"
@ -409,6 +465,26 @@ dependencies = [
"unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "miniz_oxide"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "miniz_oxide_c_api"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mio"
version = "0.6.14"
@ -502,6 +578,14 @@ dependencies = [
"num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.0"
@ -1015,29 +1099,36 @@ dependencies = [
[metadata]
"checksum abstract-ns 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8964f1b6e32687dfe6377ca7dd801b2646a01dfcdb44a25b521d4af29faaa38c"
"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
"checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9"
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
"checksum bytes 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b7db437d718977f6dc9b2e3fd6fc343c02ac6b899b73fdd2179163447bd9ce9"
"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9"
"checksum codegen 0.1.0 (git+https://github.com/carllerche/codegen)" = "<none>"
"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7"
"checksum deflate 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)" = "32c8120d981901a9970a3a1c97cf8b630e0fa8c3ca31e75b6fd6fd5f9f427b31"
"checksum domain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ed223144c5eaf3fc1406b4ccc7f360564218df8eec11cbfa03282d405a751f64"
"checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3"
"checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
"checksum env_logger 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f15f0b172cb4f52ed5dbf47f774a387cd2315d1bf7894ab5af9b083ae27efa5a"
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
"checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478"
"checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0bab5b5e94f5c31fc764ba5dd9ad16568aae5d4825538c01d6bca680c9bf94a7"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum futures-watch 0.1.0 (git+https://github.com/carllerche/better-future.git)" = "<none>"
"checksum gzip-header 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0a9fcfe1c9ee125342355b2467bc29b9dfcb2124fcae27edb9cee6f4cc5ecd40"
"checksum h2 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "065fb096fc65bbfb9c765d48c9f3f1a21cdb25ba0d3f82105b38f30ddffa2f7e"
"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82"
"checksum http 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "75df369fd52c60635208a4d3e694777c099569b3dcf4844df8f652dc004644ab"
@ -1055,6 +1146,8 @@ dependencies = [
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd"
"checksum miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aaa2d3ad070f428fffbd7d3ca2ea20bb0d8cffe9024405c44e1840bc1418b398"
"checksum miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "92d98fdbd6145645828069b37ea92ca3de225e000d80702da25c20d3584b38a5"
"checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe"
"checksum mio-uds 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1731a873077147b626d89cc6c2a0db6288d607496c5d10c0cfcf3adc697ec673"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
@ -1064,6 +1157,7 @@ dependencies = [
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
"checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7de20f146db9d920c45ee8ed8f71681fd9ade71909b48c3acbd766aa504cf10"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"

View File

@ -17,6 +17,7 @@ conduit-proxy-router = { path = "./router" }
bytes = "0.4"
domain = "0.2.3"
deflate = {version = "0.7.18", features = ["gzip"] }
env_logger = { version = "0.5", default-features = false }
futures = "0.1"
futures-watch = { git = "https://github.com/carllerche/better-future.git" }
@ -59,3 +60,4 @@ libc = "0.2"
quickcheck = { version = "0.6", default-features = false }
conduit-proxy-controller-grpc = { path = "./controller-grpc" , features = ["arbitrary"] }
regex = "0.2"
flate2 = { version = "1.0.1", default-features = false, features = ["rust_backend"] }

View File

@ -8,6 +8,7 @@ extern crate conduit_proxy_controller_grpc;
extern crate convert;
extern crate domain;
extern crate env_logger;
extern crate deflate;
#[macro_use]
extern crate futures;
extern crate futures_mpsc_lossy;

View File

@ -27,19 +27,22 @@
//! to worry about missing commas, double commas, or trailing commas at the
//! end of the label set (all of which will make Prometheus angry).
use std::default::Default;
use std::{fmt, ops, time};
use std::{fmt, time};
use std::hash::Hash;
use std::num::Wrapping;
use std::sync::{Arc, Mutex};
use std::io::Write;
use std::ops;
use deflate::CompressionOptions;
use deflate::write::GzEncoder;
use futures::future::{self, FutureResult};
use hyper;
use hyper::header::{ContentLength, ContentType};
use hyper::StatusCode;
use hyper::{self, Body, StatusCode};
use hyper::header::{AcceptEncoding, ContentEncoding, ContentType, Encoding, QualityItem};
use hyper::server::{
Service as HyperService,
Response as HyperResponse,
Request as HyperRequest,
Response as HyperResponse
Service as HyperService,
};
use indexmap::{IndexMap};
@ -631,9 +634,23 @@ impl Aggregate {
impl Serve {
fn new(metrics: &Arc<Mutex<Metrics>>) -> Self {
Serve { metrics: metrics.clone() }
Serve {
metrics: metrics.clone(),
}
}
}
fn is_gzip(req: &HyperRequest) -> bool {
if let Some(accept_encodings) = req
.headers()
.get::<AcceptEncoding>()
{
return accept_encodings
.iter()
.any(|&QualityItem { ref item, .. }| item == &Encoding::Gzip)
}
false
}
impl HyperService for Serve {
type Request = HyperRequest;
@ -647,14 +664,30 @@ impl HyperService for Serve {
.with_status(StatusCode::NotFound));
}
let body = {
let metrics = self.metrics.lock()
.expect("metrics lock poisoned");
format!("{}", *metrics)
};
future::ok(HyperResponse::new()
.with_header(ContentLength(body.len() as u64))
let resp = if is_gzip(&req) {
trace!("gzipping metrics");
let mut writer = GzEncoder::new(Vec::<u8>::new(), CompressionOptions::fast());
write!(&mut writer, "{}", *metrics)
.and_then(|_| writer.finish())
.map(|body| {
HyperResponse::new()
.with_header(ContentEncoding(vec![Encoding::Gzip]))
.with_header(ContentType::plaintext())
.with_body(body))
.with_body(Body::from(body))
})
} else {
let mut writer = Vec::<u8>::new();
write!(&mut writer, "{}", *metrics)
.map(|_| {
HyperResponse::new()
.with_header(ContentType::plaintext())
.with_body(Body::from(writer))
})
};
future::result(resp.map_err(hyper::Error::Io))
}
}

View File

@ -5,7 +5,7 @@
// Note, lints like `unused_variable` should not be ignored.
#![allow(dead_code)]
extern crate bytes;
pub extern crate bytes;
pub extern crate conduit_proxy_controller_grpc;
extern crate conduit_proxy;
pub extern crate convert;

View File

@ -2,10 +2,13 @@
#[macro_use]
extern crate log;
extern crate regex;
extern crate flate2;
#[macro_use]
mod support;
use self::support::*;
use support::bytes::IntoBuf;
use std::io::Read;
macro_rules! assert_contains {
($scrape:expr, $contains:expr) => {
@ -1089,3 +1092,70 @@ mod transport {
"tcp_connections_open{direction=\"outbound\"} 0");
}
}
// https://github.com/runconduit/conduit/issues/613
#[test]
#[cfg_attr(not(feature = "flaky_tests"), ignore)]
fn metrics_compression() {
let _ = env_logger::try_init();
let Fixture { client, metrics, proxy: _proxy } = Fixture::inbound();
let do_scrape = |encoding: &str| {
let resp = metrics.request(
metrics.request_builder("/metrics")
.method("GET")
.header("Accept-Encoding", encoding)
);
{
// create a new scope so we can release our borrow on `resp` before
// getting the body
let content_encoding = resp.headers()
.get("content-encoding")
.as_ref()
.map(|val| val
.to_str()
.expect("content-encoding value should be ascii")
);
assert_eq!(content_encoding, Some("gzip"),
"unexpected Content-Encoding {:?} (requested Accept-Encoding: {})", content_encoding, encoding);
}
let body = resp.into_body()
.concat2()
.wait()
.expect("response body concat");
let mut decoder = flate2::read::GzDecoder::new(body.into_buf());
let mut scrape = String::new();
decoder.read_to_string(&mut scrape)
.expect(&format!(
"decode gzip (requested Accept-Encoding: {})",
encoding
));
scrape
};
let encodings = &[
"gzip",
"deflate, gzip",
"gzip,deflate",
"brotli,gzip,deflate"
];
info!("client.get(/)");
assert_eq!(client.get("/"), "hello");
for &encoding in encodings {
assert_contains!(do_scrape(encoding),
"request_duration_ms_count{authority=\"tele.test.svc.cluster.local\",direction=\"inbound\"} 1");
}
info!("client.get(/)");
assert_eq!(client.get("/"), "hello");
for &encoding in encodings {
assert_contains!(do_scrape(encoding),
"request_duration_ms_count{authority=\"tele.test.svc.cluster.local\",direction=\"inbound\"} 2");
}
}