1
0
mirror of https://github.com/fafhrd91/actix-net synced 2024-11-27 16:52:58 +01:00

add zero cost profiling to router

This commit is contained in:
Rob Ede 2021-07-17 01:07:07 +01:00
parent 5687e81d9f
commit 95cba659ff
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
5 changed files with 262 additions and 20 deletions

View File

@ -21,15 +21,17 @@ default = ["http"]
[dependencies]
bytestring = ">=0.1.5, <2"
firestorm = "0.4"
http = { version = "0.2.3", optional = true }
log = "0.4"
regex = "1.5"
serde = "1"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
firestorm = { version = "0.4", features = ["enable_system_time"] }
http = "0.2.3"
serde = { version = "1", features = ["derive"] }
criterion = { version = "0.3", features = ["html_reports"] }
[[bench]]
name = "router"

View File

@ -0,0 +1,172 @@
macro_rules! register {
(brackets) => {{
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
}};
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
let arr = [
concat!("/authorizations"),
concat!("/authorizations/", $p1),
concat!("/applications/", $p1, "/tokens/", $p2),
concat!("/events"),
concat!("/repos/", $p1, "/", $p2, "/events"),
concat!("/networks/", $p1, "/", $p2, "/events"),
concat!("/orgs/", $p1, "/events"),
concat!("/users/", $p1, "/received_events"),
concat!("/users/", $p1, "/received_events/public"),
concat!("/users/", $p1, "/events"),
concat!("/users/", $p1, "/events/public"),
concat!("/users/", $p1, "/events/orgs/", $p2),
concat!("/feeds"),
concat!("/notifications"),
concat!("/repos/", $p1, "/", $p2, "/notifications"),
concat!("/notifications/threads/", $p1),
concat!("/notifications/threads/", $p1, "/subscription"),
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
concat!("/users/", $p1, "/starred"),
concat!("/user/starred"),
concat!("/user/starred/", $p1, "/", $p2),
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
concat!("/users/", $p1, "/subscriptions"),
concat!("/user/subscriptions"),
concat!("/repos/", $p1, "/", $p2, "/subscription"),
concat!("/user/subscriptions/", $p1, "/", $p2),
concat!("/users/", $p1, "/gists"),
concat!("/gists"),
concat!("/gists/", $p1),
concat!("/gists/", $p1, "/star"),
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
concat!("/issues"),
concat!("/user/issues"),
concat!("/orgs/", $p1, "/issues"),
concat!("/repos/", $p1, "/", $p2, "/issues"),
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
concat!("/repos/", $p1, "/", $p2, "/assignees"),
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
concat!("/repos/", $p1, "/", $p2, "/labels"),
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
concat!("/emojis"),
concat!("/gitignore/templates"),
concat!("/gitignore/templates/", $p1),
concat!("/meta"),
concat!("/rate_limit"),
concat!("/users/", $p1, "/orgs"),
concat!("/user/orgs"),
concat!("/orgs/", $p1),
concat!("/orgs/", $p1, "/members"),
concat!("/orgs/", $p1, "/members", $p2),
concat!("/orgs/", $p1, "/public_members"),
concat!("/orgs/", $p1, "/public_members/", $p2),
concat!("/orgs/", $p1, "/teams"),
concat!("/teams/", $p1),
concat!("/teams/", $p1, "/members"),
concat!("/teams/", $p1, "/members", $p2),
concat!("/teams/", $p1, "/repos"),
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
concat!("/user/teams"),
concat!("/repos/", $p1, "/", $p2, "/pulls"),
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
concat!("/user/repos"),
concat!("/users/", $p1, "/repos"),
concat!("/orgs/", $p1, "/repos"),
concat!("/repositories"),
concat!("/repos/", $p1, "/", $p2),
concat!("/repos/", $p1, "/", $p2, "/contributors"),
concat!("/repos/", $p1, "/", $p2, "/languages"),
concat!("/repos/", $p1, "/", $p2, "/teams"),
concat!("/repos/", $p1, "/", $p2, "/tags"),
concat!("/repos/", $p1, "/", $p2, "/branches"),
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
concat!("/repos/", $p1, "/", $p2, "/comments"),
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
concat!("/repos/", $p1, "/", $p2, "/commits"),
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
concat!("/repos/", $p1, "/", $p2, "/readme"),
concat!("/repos/", $p1, "/", $p2, "/keys"),
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
concat!("/repos/", $p1, "/", $p2, "/downloads"),
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
concat!("/repos/", $p1, "/", $p2, "/forks"),
concat!("/repos/", $p1, "/", $p2, "/hooks"),
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
concat!("/repos/", $p1, "/", $p2, "/releases"),
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
concat!("/search/repositories"),
concat!("/search/code"),
concat!("/search/issues"),
concat!("/search/users"),
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
concat!("/legacy/repos/search/", $p1),
concat!("/legacy/user/search/", $p1),
concat!("/legacy/user/email/", $p1),
concat!("/users/", $p1),
concat!("/user"),
concat!("/users"),
concat!("/user/emails"),
concat!("/users/", $p1, "/followers"),
concat!("/user/followers"),
concat!("/users/", $p1, "/following"),
concat!("/user/following"),
concat!("/user/following/", $p1),
concat!("/users/", $p1, "/following", $p2),
concat!("/users/", $p1, "/keys"),
concat!("/user/keys"),
concat!("/user/keys/", $p1),
];
std::array::IntoIter::new(arr)
}};
}
fn call() -> impl Iterator<Item = &'static str> {
let arr = [
"/authorizations",
"/user/repos",
"/repos/rust-lang/rust/stargazers",
"/orgs/rust-lang/public_members/nikomatsakis",
"/repos/rust-lang/rust/releases/1.51.0",
];
std::array::IntoIter::new(arr)
}
fn main() {
let mut router = actix_router::Router::<bool>::build();
for route in register!(brackets) {
router.path(route, true);
}
let actix = router.finish();
if firestorm::enabled() {
firestorm::bench("target", || {
for route in call() {
let mut path = actix_router::Path::new(route);
actix.recognize(&mut path).unwrap();
}
})
.unwrap();
}
}

View File

@ -1,6 +1,7 @@
use std::borrow::Cow;
use std::ops::Index;
use firestorm::profile_method;
use serde::de;
use crate::de::PathDeserializer;
@ -37,21 +38,23 @@ impl<T: ResourcePath> Path<T> {
}
}
/// Get reference to inner path instance
/// Get reference to inner path instance.
#[inline]
pub fn get_ref(&self) -> &T {
&self.path
}
/// Get mutable reference to inner path instance
/// Get mutable reference to inner path instance.
#[inline]
pub fn get_mut(&mut self) -> &mut T {
&mut self.path
}
/// Path
/// Path.
#[inline]
pub fn path(&self) -> &str {
profile_method!(path);
let skip = self.skip as usize;
let path = self.path.path();
if skip <= path.len() {
@ -61,7 +64,7 @@ impl<T: ResourcePath> Path<T> {
}
}
/// Set new path
/// Set new path.
#[inline]
pub fn set(&mut self, path: T) {
self.skip = 0;
@ -69,20 +72,22 @@ impl<T: ResourcePath> Path<T> {
self.segments.clear();
}
/// Reset state
/// Reset state.
#[inline]
pub fn reset(&mut self) {
self.skip = 0;
self.segments.clear();
}
/// Skip first `n` chars in path
/// Skip first `n` chars in path.
#[inline]
pub fn skip(&mut self, n: u16) {
self.skip += n;
}
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
profile_method!(add);
match value {
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
PathItem::Segment(begin, end) => self.segments.push((
@ -116,6 +121,8 @@ impl<T: ResourcePath> Path<T> {
/// Get matched parameter by name without type conversion
pub fn get(&self, key: &str) -> Option<&str> {
profile_method!(get);
for item in self.segments.iter() {
if key == item.0 {
return match item.1 {
@ -140,9 +147,10 @@ impl<T: ResourcePath> Path<T> {
/// Get matched parameter by name.
///
/// If keyed parameter is not available empty string is used as default
/// value.
/// If keyed parameter is not available empty string is used as default value.
pub fn query(&self, key: &str) -> &str {
profile_method!(query);
if let Some(s) = self.get(key) {
s
} else {
@ -150,7 +158,7 @@ impl<T: ResourcePath> Path<T> {
}
}
/// Return iterator to items in parameter container
/// Return iterator to items in parameter container.
pub fn iter(&self) -> PathIter<'_, T> {
PathIter {
idx: 0,
@ -160,6 +168,7 @@ impl<T: ResourcePath> Path<T> {
/// Try to deserialize matching parameters to a specified type `U`
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
profile_method!(load);
de::Deserialize::deserialize(PathDeserializer::new(self))
}
}

View File

@ -6,6 +6,7 @@ use std::{
mem,
};
use firestorm::{profile_fn, profile_method, profile_section};
use regex::{escape, Regex, RegexSet};
use crate::{
@ -78,6 +79,8 @@ impl ResourceDef {
///
/// Panics if path pattern is malformed.
pub fn new<T: IntoPatterns>(path: T) -> Self {
profile_method!(new);
match path.patterns() {
Patterns::Single(pattern) => ResourceDef::from_single_pattern(&pattern, false),
@ -124,6 +127,7 @@ impl ResourceDef {
///
/// Panics if path regex pattern is malformed.
pub fn prefix(path: &str) -> Self {
profile_method!(prefix);
ResourceDef::from_single_pattern(path, true)
}
@ -134,6 +138,7 @@ impl ResourceDef {
///
/// Panics if path regex pattern is malformed.
pub fn root_prefix(path: &str) -> Self {
profile_method!(root_prefix);
ResourceDef::from_single_pattern(&insert_slash(path), true)
}
@ -149,6 +154,8 @@ impl ResourceDef {
/// Parse path pattern and create a new instance
fn from_single_pattern(pattern: &str, for_prefix: bool) -> Self {
profile_method!(from_single_pattern);
let pattern = pattern.to_owned();
let (pat_type, elements) = ResourceDef::parse(&pattern, for_prefix, false);
@ -179,6 +186,8 @@ impl ResourceDef {
/// Check if path matches this pattern.
#[inline]
pub fn is_match(&self, path: &str) -> bool {
profile_method!(is_match);
match self.pat_type {
PatternType::Static(ref s) => s == path,
PatternType::Prefix(ref s) => path.starts_with(s),
@ -189,6 +198,8 @@ impl ResourceDef {
/// Is prefix path a match against this resource.
pub fn is_prefix_match(&self, path: &str) -> Option<usize> {
profile_method!(is_prefix_match);
let path_len = path.len();
let path = if path.is_empty() { "/" } else { path };
@ -245,6 +256,7 @@ impl ResourceDef {
/// Is the given path and parameters a match against this pattern.
pub fn match_path<T: ResourcePath>(&self, path: &mut Path<T>) -> bool {
profile_method!(match_path);
self.match_path_checked(path, &|_, _| true, &Some(()))
}
@ -260,11 +272,15 @@ impl ResourceDef {
R: Resource<T>,
F: Fn(&R, &Option<U>) -> bool,
{
profile_method!(match_path_checked);
let mut segments: [PathItem; MAX_DYNAMIC_SEGMENTS] = Default::default();
let path = res.resource_path();
let (matched_len, matched_vars) = match self.pat_type {
PatternType::Static(ref segment) => {
profile_section!(pattern_static);
if segment != path.path() {
return false;
}
@ -273,6 +289,8 @@ impl ResourceDef {
}
PatternType::Prefix(ref prefix) => {
profile_section!(pattern_dynamic);
let path_str = path.path();
let path_len = path_str.len();
@ -300,24 +318,39 @@ impl ResourceDef {
}
PatternType::Dynamic(ref re, ref names) => {
let captures = match re.captures(path.path()) {
Some(captures) => captures,
_ => return false,
profile_section!(pattern_dynamic);
let captures = {
profile_section!(pattern_dynamic_regex_exec);
match re.captures(path.path()) {
Some(captures) => captures,
_ => return false,
}
};
for (no, name) in names.iter().enumerate() {
if let Some(m) = captures.name(&name) {
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
} else {
log::error!("Dynamic path match but not all segments found: {}", name);
return false;
{
profile_section!(pattern_dynamic_extract_captures);
for (no, name) in names.iter().enumerate() {
if let Some(m) = captures.name(&name) {
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
} else {
log::error!(
"Dynamic path match but not all segments found: {}",
name
);
return false;
}
}
}
};
(captures[0].len(), Some(names))
}
PatternType::DynamicSet(ref re, ref params) => {
profile_section!(pattern_dynamic_set);
let path = path.path();
let (pattern, names) = match re.matches(path).into_iter().next() {
Some(idx) => &params[idx],
@ -394,6 +427,7 @@ impl ResourceDef {
U: Iterator<Item = I>,
I: AsRef<str>,
{
profile_method!(resource_path_from_iter);
self.build_resource_path(path, |_| elements.next())
}
@ -404,6 +438,7 @@ impl ResourceDef {
U: Iterator<Item = I>,
I: AsRef<str>,
{
profile_method!(build_resource_path);
self.resource_path_from_iter(path, elements)
}
@ -420,6 +455,7 @@ impl ResourceDef {
V: AsRef<str>,
S: BuildHasher,
{
profile_method!(resource_path_from_map);
self.build_resource_path(path, |name| {
name.and_then(|name| elements.get(name).map(AsRef::<str>::as_ref))
})
@ -458,6 +494,7 @@ impl ResourceDef {
S: BuildHasher,
T: AsRef<str>,
{
profile_method!(resource_path_from_map_with_tail);
self.build_resource_path(path, |name| match name {
Some(name) => elements.get(name).map(AsRef::<str>::as_ref),
None => Some(tail.as_ref()),
@ -465,6 +502,8 @@ impl ResourceDef {
}
fn parse_param(pattern: &str) -> (PatternElement, String, &str) {
profile_method!(parse_param);
const DEFAULT_PATTERN: &str = "[^/]+";
const DEFAULT_PATTERN_TAIL: &str = ".*";
@ -526,6 +565,8 @@ impl ResourceDef {
for_prefix: bool,
force_dynamic: bool,
) -> (PatternType, Vec<PatternElement>) {
profile_method!(parse);
let mut unprocessed = pattern;
if !force_dynamic && unprocessed.find('{').is_none() && !unprocessed.ends_with('*') {
@ -637,6 +678,8 @@ impl From<String> for ResourceDef {
}
pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
profile_fn!(insert_slash);
if !path.is_empty() && !path.starts_with('/') {
let mut new_path = String::with_capacity(path.len() + 1);
new_path.push('/');

View File

@ -1,3 +1,5 @@
use firestorm::profile_method;
use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
#[derive(Debug, Copy, Clone, PartialEq)]
@ -24,6 +26,8 @@ impl<T, U> Router<T, U> {
R: Resource<P>,
P: ResourcePath,
{
profile_method!(recognize);
for item in self.0.iter() {
if item.0.match_path(resource.resource_path()) {
return Some((&item.1, ResourceId(item.0.id())));
@ -37,6 +41,8 @@ impl<T, U> Router<T, U> {
R: Resource<P>,
P: ResourcePath,
{
profile_method!(recognize_mut);
for item in self.0.iter_mut() {
if item.0.match_path(resource.resource_path()) {
return Some((&mut item.1, ResourceId(item.0.id())));
@ -55,6 +61,8 @@ impl<T, U> Router<T, U> {
R: Resource<P>,
P: ResourcePath,
{
profile_method!(recognize_checked);
for item in self.0.iter() {
if item.0.match_path_checked(resource, &check, &item.2) {
return Some((&item.1, ResourceId(item.0.id())));
@ -73,6 +81,8 @@ impl<T, U> Router<T, U> {
R: Resource<P>,
P: ResourcePath,
{
profile_method!(recognize_mut_checked);
for item in self.0.iter_mut() {
if item.0.match_path_checked(resource, &check, &item.2) {
return Some((&mut item.1, ResourceId(item.0.id())));
@ -93,6 +103,8 @@ impl<T, U> RouterBuilder<T, U> {
path: P,
resource: T,
) -> &mut (ResourceDef, T, Option<U>) {
profile_method!(path);
self.resources
.push((ResourceDef::new(path), resource, None));
self.resources.last_mut().unwrap()
@ -100,6 +112,8 @@ impl<T, U> RouterBuilder<T, U> {
/// Register resource for specified path prefix.
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
profile_method!(prefix);
self.resources
.push((ResourceDef::prefix(prefix), resource, None));
self.resources.last_mut().unwrap()
@ -107,6 +121,8 @@ impl<T, U> RouterBuilder<T, U> {
/// Register resource for ResourceDef
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
profile_method!(rdef);
self.resources.push((rdef, resource, None));
self.resources.last_mut().unwrap()
}