<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Code for invalidations due to state or attribute changes.

use {Atom, LocalName, Namespace};
use context::QuirksMode;
use element_state::ElementState;
use fallible::FallibleVec;
use hashglobe::FailedAllocationError;
use selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry};
#[cfg(feature = "gecko")]
use selector_parser::Direction;
use selector_parser::SelectorImpl;
use selectors::attr::NamespaceConstraint;
use selectors::parser::{Combinator, Component};
use selectors::parser::{Selector, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;

#[cfg(feature = "gecko")]
/// Gets the element state relevant to the given `:dir` pseudo-class selector.
pub fn dir_selector_to_state(dir: &Direction) -> ElementState {
    match *dir {
        Direction::Ltr => ElementState::IN_LTR_STATE,
        Direction::Rtl => ElementState::IN_RTL_STATE,
        Direction::Other(_) => {
            // :dir(something-random) is a valid selector, but shouldn't
            // match anything.
            ElementState::empty()
        },
    }
}

/// Mapping between (partial) CompoundSelectors (and the combinator to their
/// right) and the states and attributes they depend on.
///
/// In general, for all selectors in all applicable stylesheets of the form:
///
/// |a _ b _ c _ d _ e|
///
/// Where:
///   * |b| and |d| are simple selectors that depend on state (like :hover) or
///     attributes (like [attr...], .foo, or #foo).
///   * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on
///     state or attributes.
///
/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|,
/// even though those selectors may not appear on their own in any stylesheet.
/// This allows us to quickly scan through the dependency sites of all style
/// rules and determine the maximum effect that a given state or attribute
/// change may have on the style of elements in the document.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Dependency {
    /// The dependency selector.
    #[cfg_attr(feature = "gecko",
               ignore_malloc_size_of = "CssRules have primary refs, we measure there")]
    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
    pub selector: Selector<SelectorImpl>,

    /// The offset into the selector that we should match on.
    pub selector_offset: usize,
}

/// The kind of elements down the tree this dependency may affect.
#[derive(Debug, Eq, PartialEq)]
pub enum DependencyInvalidationKind {
    /// This dependency may affect the element that changed itself.
    Element,
    /// This dependency affects the style of the element itself, and also the
    /// style of its descendants.
    ///
    /// TODO(emilio): Each time this feels more of a hack for eager pseudos...
    ElementAndDescendants,
    /// This dependency may affect descendants down the tree.
    Descendants,
    /// This dependency may affect siblings to the right of the element that
    /// changed.
    Siblings,
    /// This dependency may affect slotted elements of the element that changed.
    SlottedElements,
}

impl Dependency {
    /// Returns the combinator to the right of the partial selector this
    /// dependency represents.
    ///
    /// TODO(emilio): Consider storing inline if it helps cache locality?
    pub fn combinator(&self) -> Option<Combinator> {
        if self.selector_offset == 0 {
            return None;
        }

        Some(self.selector.combinator_at_match_order(self.selector_offset - 1))
    }

    /// The kind of invalidation that this would generate.
    pub fn invalidation_kind(&self) -> DependencyInvalidationKind {
        match self.combinator() {
            None => DependencyInvalidationKind::Element,
            Some(Combinator::Child) |
            Some(Combinator::Descendant) => DependencyInvalidationKind::Descendants,
            Some(Combinator::LaterSibling) |
            Some(Combinator::NextSibling) => DependencyInvalidationKind::Siblings,
            // TODO(emilio): We could look at the selector itself to see if it's
            // an eager pseudo, and return only Descendants here if not.
            Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
            Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
        }
    }
}

impl SelectorMapEntry for Dependency {
    fn selector(&self) -> SelectorIter<SelectorImpl> {
        self.selector.iter_from(self.selector_offset)
    }
}

/// The same, but for state selectors, which can track more exactly what state
/// do they track.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct StateDependency {
    /// The other dependency fields.
    pub dep: Dependency,
    /// The state this dependency is affected by.
    pub state: ElementState,
}

impl SelectorMapEntry for StateDependency {
    fn selector(&self) -> SelectorIter<SelectorImpl> {
        self.dep.selector()
    }
}

/// A map where we store invalidations.
///
/// This is slightly different to a SelectorMap, in the sense of that the same
/// selector may appear multiple times.
///
/// In particular, we want to lookup as few things as possible to get the fewer
/// selectors the better, so this looks up by id, class, or looks at the list of
/// state/other attribute affecting selectors.
#[derive(Debug, MallocSizeOf)]
pub struct InvalidationMap {
    /// A map from a given class name to all the selectors with that class
    /// selector.
    pub class_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>,
    /// A map from a given id to all the selectors with that ID in the
    /// stylesheets currently applying to the document.
    pub id_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>,
    /// A map of all the state dependencies.
    pub state_affecting_selectors: SelectorMap<StateDependency>,
    /// A map of other attribute affecting selectors.
    pub other_attribute_affecting_selectors: SelectorMap<Dependency>,
    /// Whether there are attribute rules of the form `[class~="foo"]` that may
    /// match. In that case, we need to look at
    /// `other_attribute_affecting_selectors` too even if only the `class` has
    /// changed.
    pub has_class_attribute_selectors: bool,
    /// Whether there are attribute rules of the form `[id|="foo"]` that may
    /// match. In that case, we need to look at
    /// `other_attribute_affecting_selectors` too even if only the `id` has
    /// changed.
    pub has_id_attribute_selectors: bool,
}

impl InvalidationMap {
    /// Creates an empty `InvalidationMap`.
    pub fn new() -> Self {
        Self {
            class_to_selector: MaybeCaseInsensitiveHashMap::new(),
            id_to_selector: MaybeCaseInsensitiveHashMap::new(),
            state_affecting_selectors: SelectorMap::new(),
            other_attribute_affecting_selectors: SelectorMap::new(),
            has_class_attribute_selectors: false,
            has_id_attribute_selectors: false,
        }
    }

    /// Returns the number of dependencies stored in the invalidation map.
    pub fn len(&self) -> usize {
        self.state_affecting_selectors.len() +
        self.other_attribute_affecting_selectors.len() +
        self.id_to_selector.iter().fold(0, |accum, (_, ref v)| {
            accum + v.len()
        }) +
        self.class_to_selector.iter().fold(0, |accum, (_, ref v)| {
            accum + v.len()
        })
    }

    /// Adds a selector to this `InvalidationMap`.  Returns Err(..) to
    /// signify OOM.
    pub fn note_selector(
        &mut self,
        selector: &Selector<SelectorImpl>,
        quirks_mode: QuirksMode
    ) -> Result<(), FailedAllocationError> {
        self.collect_invalidations_for(selector, quirks_mode)
    }

    /// Clears this map, leaving it empty.
    pub fn clear(&mut self) {
        self.class_to_selector.clear();
        self.id_to_selector.clear();
        self.state_affecting_selectors.clear();
        self.other_attribute_affecting_selectors.clear();
        self.has_id_attribute_selectors = false;
        self.has_class_attribute_selectors = false;
    }

    // Returns Err(..) to signify OOM.
    fn collect_invalidations_for(
        &mut self,
        selector: &Selector<SelectorImpl>,
        quirks_mode: QuirksMode
    ) -> Result<(), FailedAllocationError> {
        debug!("InvalidationMap::collect_invalidations_for({:?})", selector);

        let mut iter = selector.iter();
        let mut combinator;
        let mut index = 0;

        loop {
            let sequence_start = index;

            let mut compound_visitor = CompoundSelectorDependencyCollector {
                classes: SmallVec::new(),
                ids: SmallVec::new(),
                state: ElementState::empty(),
                other_attributes: false,
                has_id_attribute_selectors: false,
                has_class_attribute_selectors: false,
            };

            // Visit all the simple selectors in this sequence.
            //
            // Note that this works because we can't have combinators nested
            // inside simple selectors (i.e. in :not() or :-moz-any()).
            //
            // If we ever support that we'll need to visit nested complex
            // selectors as well, in order to mark them as affecting descendants
            // at least.
            for ss in &mut iter {
                ss.visit(&mut compound_visitor);
                index += 1; // Account for the simple selector.
            }

            self.has_id_attribute_selectors |= compound_visitor.has_id_attribute_selectors;
            self.has_class_attribute_selectors |= compound_visitor.has_class_attribute_selectors;

            for class in compound_visitor.classes {
                self.class_to_selector
                    .entry(class, quirks_mode)
                    .or_insert_with(SmallVec::new)
                    .try_push(Dependency {
                        selector: selector.clone(),
                        selector_offset: sequence_start,
                    })?;
            }

            for id in compound_visitor.ids {
                self.id_to_selector
                    .entry(id, quirks_mode)
                    .or_insert_with(SmallVec::new)
                    .try_push(Dependency {
                        selector: selector.clone(),
                        selector_offset: sequence_start,
                    })?;
            }

            if !compound_visitor.state.is_empty() {
                self.state_affecting_selectors
                    .insert(StateDependency {
                        dep: Dependency {
                            selector: selector.clone(),
                            selector_offset: sequence_start,
                        },
                        state: compound_visitor.state,
                    }, quirks_mode)?;
            }

            if compound_visitor.other_attributes {
                self.other_attribute_affecting_selectors
                    .insert(Dependency {
                        selector: selector.clone(),
                        selector_offset: sequence_start,
                    }, quirks_mode)?;
            }

            combinator = iter.next_sequence();
            if combinator.is_none() {
                break;
            }

            index += 1; // Account for the combinator.
        }

        Ok(())
    }
}

/// A struct that collects invalidations for a given compound selector.
struct CompoundSelectorDependencyCollector {
    /// The state this compound selector is affected by.
    state: ElementState,

    /// The classes this compound selector is affected by.
    ///
    /// NB: This will be often a single class, but could be multiple in
    /// presence of :not, :-moz-any, .foo.bar.baz, etc.
    classes: SmallVec<[Atom; 5]>,

    /// The IDs this compound selector is affected by.
    ///
    /// NB: This will be almost always a single id, but could be multiple in
    /// presence of :not, :-moz-any, #foo#bar, etc.
    ids: SmallVec<[Atom; 5]>,

    /// Whether it affects other attribute-dependent selectors that aren't ID or
    /// class selectors (NB: We still set this to true in presence of [class] or
    /// [id] attribute selectors).
    other_attributes: bool,

    /// Whether there were attribute selectors with the id attribute.
    has_id_attribute_selectors: bool,

    /// Whether there were attribute selectors with the class attribute.
    has_class_attribute_selectors: bool,
}

impl SelectorVisitor for CompoundSelectorDependencyCollector {
    type Impl = SelectorImpl;

    fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
        #[cfg(feature = "gecko")]
        use selector_parser::NonTSPseudoClass;

        match *s {
            Component::ID(ref id) => {
                self.ids.push(id.clone());
            }
            Component::Class(ref class) => {
                self.classes.push(class.clone());
            }
            Component::NonTSPseudoClass(ref pc) => {
                self.other_attributes |= pc.is_attr_based();
                self.state |= match *pc {
                    #[cfg(feature = "gecko")]
                    NonTSPseudoClass::Dir(ref dir) => {
                        dir_selector_to_state(dir)
                    }
                    _ => pc.state_flag(),
                };
            }
            _ => {}
        }

        true
    }

    fn visit_attribute_selector(
        &mut self,
        constraint: &NamespaceConstraint<&Namespace>,
        _local_name: &LocalName,
        local_name_lower: &LocalName,
    ) -> bool {
        self.other_attributes = true;
        let may_match_in_no_namespace = match *constraint {
            NamespaceConstraint::Any => true,
            NamespaceConstraint::Specific(ref ns) => ns.is_empty(),
        };

        if may_match_in_no_namespace {
            self.has_id_attribute_selectors |= *local_name_lower == local_name!("id");
            self.has_class_attribute_selectors |= *local_name_lower == local_name!("class");
        }

        true
    }
}
<commit_msg>style: Track document state dependencies in the invalidation maps.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Code for invalidations due to state or attribute changes.

use {Atom, LocalName, Namespace};
use context::QuirksMode;
use element_state::{DocumentState, ElementState};
use fallible::FallibleVec;
use hashglobe::FailedAllocationError;
use selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry};
#[cfg(feature = "gecko")]
use selector_parser::Direction;
use selector_parser::SelectorImpl;
use selectors::attr::NamespaceConstraint;
use selectors::parser::{Combinator, Component};
use selectors::parser::{Selector, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;

#[cfg(feature = "gecko")]
/// Gets the element state relevant to the given `:dir` pseudo-class selector.
pub fn dir_selector_to_state(dir: &Direction) -> ElementState {
    match *dir {
        Direction::Ltr => ElementState::IN_LTR_STATE,
        Direction::Rtl => ElementState::IN_RTL_STATE,
        Direction::Other(_) => {
            // :dir(something-random) is a valid selector, but shouldn't
            // match anything.
            ElementState::empty()
        },
    }
}

/// Mapping between (partial) CompoundSelectors (and the combinator to their
/// right) and the states and attributes they depend on.
///
/// In general, for all selectors in all applicable stylesheets of the form:
///
/// |a _ b _ c _ d _ e|
///
/// Where:
///   * |b| and |d| are simple selectors that depend on state (like :hover) or
///     attributes (like [attr...], .foo, or #foo).
///   * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on
///     state or attributes.
///
/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|,
/// even though those selectors may not appear on their own in any stylesheet.
/// This allows us to quickly scan through the dependency sites of all style
/// rules and determine the maximum effect that a given state or attribute
/// change may have on the style of elements in the document.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Dependency {
    /// The dependency selector.
    #[cfg_attr(feature = "gecko",
               ignore_malloc_size_of = "CssRules have primary refs, we measure there")]
    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
    pub selector: Selector<SelectorImpl>,

    /// The offset into the selector that we should match on.
    pub selector_offset: usize,
}

/// The kind of elements down the tree this dependency may affect.
#[derive(Debug, Eq, PartialEq)]
pub enum DependencyInvalidationKind {
    /// This dependency may affect the element that changed itself.
    Element,
    /// This dependency affects the style of the element itself, and also the
    /// style of its descendants.
    ///
    /// TODO(emilio): Each time this feels more of a hack for eager pseudos...
    ElementAndDescendants,
    /// This dependency may affect descendants down the tree.
    Descendants,
    /// This dependency may affect siblings to the right of the element that
    /// changed.
    Siblings,
    /// This dependency may affect slotted elements of the element that changed.
    SlottedElements,
}

impl Dependency {
    /// Returns the combinator to the right of the partial selector this
    /// dependency represents.
    ///
    /// TODO(emilio): Consider storing inline if it helps cache locality?
    pub fn combinator(&self) -> Option<Combinator> {
        if self.selector_offset == 0 {
            return None;
        }

        Some(self.selector.combinator_at_match_order(self.selector_offset - 1))
    }

    /// The kind of invalidation that this would generate.
    pub fn invalidation_kind(&self) -> DependencyInvalidationKind {
        match self.combinator() {
            None => DependencyInvalidationKind::Element,
            Some(Combinator::Child) |
            Some(Combinator::Descendant) => DependencyInvalidationKind::Descendants,
            Some(Combinator::LaterSibling) |
            Some(Combinator::NextSibling) => DependencyInvalidationKind::Siblings,
            // TODO(emilio): We could look at the selector itself to see if it's
            // an eager pseudo, and return only Descendants here if not.
            Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
            Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
        }
    }
}

impl SelectorMapEntry for Dependency {
    fn selector(&self) -> SelectorIter<SelectorImpl> {
        self.selector.iter_from(self.selector_offset)
    }
}

/// The same, but for state selectors, which can track more exactly what state
/// do they track.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct StateDependency {
    /// The other dependency fields.
    pub dep: Dependency,
    /// The state this dependency is affected by.
    pub state: ElementState,
}

impl SelectorMapEntry for StateDependency {
    fn selector(&self) -> SelectorIter<SelectorImpl> {
        self.dep.selector()
    }
}

/// The same, but for document state selectors.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct DocumentStateDependency {
    /// The selector that is affected. We don't need to track an offset, since
    /// when it changes it changes for the whole document anyway.
    #[cfg_attr(feature = "gecko",
               ignore_malloc_size_of = "CssRules have primary refs, we measure there")]
    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
    pub selector: Selector<SelectorImpl>,
    /// The state this dependency is affected by.
    pub state: DocumentState,
}

/// A map where we store invalidations.
///
/// This is slightly different to a SelectorMap, in the sense of that the same
/// selector may appear multiple times.
///
/// In particular, we want to lookup as few things as possible to get the fewer
/// selectors the better, so this looks up by id, class, or looks at the list of
/// state/other attribute affecting selectors.
#[derive(Debug, MallocSizeOf)]
pub struct InvalidationMap {
    /// A map from a given class name to all the selectors with that class
    /// selector.
    pub class_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>,
    /// A map from a given id to all the selectors with that ID in the
    /// stylesheets currently applying to the document.
    pub id_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>,
    /// A map of all the state dependencies.
    pub state_affecting_selectors: SelectorMap<StateDependency>,
    /// A list of document state dependencies in the rules we represent.
    pub document_state_selectors: Vec<DocumentStateDependency>,
    /// A map of other attribute affecting selectors.
    pub other_attribute_affecting_selectors: SelectorMap<Dependency>,
    /// Whether there are attribute rules of the form `[class~="foo"]` that may
    /// match. In that case, we need to look at
    /// `other_attribute_affecting_selectors` too even if only the `class` has
    /// changed.
    pub has_class_attribute_selectors: bool,
    /// Whether there are attribute rules of the form `[id|="foo"]` that may
    /// match. In that case, we need to look at
    /// `other_attribute_affecting_selectors` too even if only the `id` has
    /// changed.
    pub has_id_attribute_selectors: bool,
}

impl InvalidationMap {
    /// Creates an empty `InvalidationMap`.
    pub fn new() -> Self {
        Self {
            class_to_selector: MaybeCaseInsensitiveHashMap::new(),
            id_to_selector: MaybeCaseInsensitiveHashMap::new(),
            state_affecting_selectors: SelectorMap::new(),
            document_state_selectors: Vec::new(),
            other_attribute_affecting_selectors: SelectorMap::new(),
            has_class_attribute_selectors: false,
            has_id_attribute_selectors: false,
        }
    }

    /// Returns the number of dependencies stored in the invalidation map.
    pub fn len(&self) -> usize {
        self.state_affecting_selectors.len() +
        self.document_state_selectors.len() +
        self.other_attribute_affecting_selectors.len() +
        self.id_to_selector.iter().fold(0, |accum, (_, ref v)| {
            accum + v.len()
        }) +
        self.class_to_selector.iter().fold(0, |accum, (_, ref v)| {
            accum + v.len()
        })
    }

    /// Adds a selector to this `InvalidationMap`.  Returns Err(..) to
    /// signify OOM.
    pub fn note_selector(
        &mut self,
        selector: &Selector<SelectorImpl>,
        quirks_mode: QuirksMode,
    ) -> Result<(), FailedAllocationError> {
        self.collect_invalidations_for(selector, quirks_mode)
    }

    /// Clears this map, leaving it empty.
    pub fn clear(&mut self) {
        self.class_to_selector.clear();
        self.id_to_selector.clear();
        self.state_affecting_selectors.clear();
        self.document_state_selectors.clear();
        self.other_attribute_affecting_selectors.clear();
        self.has_id_attribute_selectors = false;
        self.has_class_attribute_selectors = false;
    }

    // Returns Err(..) to signify OOM.
    fn collect_invalidations_for(
        &mut self,
        selector: &Selector<SelectorImpl>,
        quirks_mode: QuirksMode
    ) -> Result<(), FailedAllocationError> {
        debug!("InvalidationMap::collect_invalidations_for({:?})", selector);

        let mut iter = selector.iter();
        let mut combinator;
        let mut index = 0;

        let mut document_state = DocumentState::empty();

        loop {
            let sequence_start = index;

            let mut compound_visitor = CompoundSelectorDependencyCollector {
                classes: SmallVec::new(),
                ids: SmallVec::new(),
                state: ElementState::empty(),
                document_state: &mut document_state,
                other_attributes: false,
                has_id_attribute_selectors: false,
                has_class_attribute_selectors: false,
            };

            // Visit all the simple selectors in this sequence.
            //
            // Note that this works because we can't have combinators nested
            // inside simple selectors (i.e. in :not() or :-moz-any()).
            //
            // If we ever support that we'll need to visit nested complex
            // selectors as well, in order to mark them as affecting descendants
            // at least.
            for ss in &mut iter {
                ss.visit(&mut compound_visitor);
                index += 1; // Account for the simple selector.
            }

            self.has_id_attribute_selectors |= compound_visitor.has_id_attribute_selectors;
            self.has_class_attribute_selectors |= compound_visitor.has_class_attribute_selectors;

            for class in compound_visitor.classes {
                self.class_to_selector
                    .entry(class, quirks_mode)
                    .or_insert_with(SmallVec::new)
                    .try_push(Dependency {
                        selector: selector.clone(),
                        selector_offset: sequence_start,
                    })?;
            }

            for id in compound_visitor.ids {
                self.id_to_selector
                    .entry(id, quirks_mode)
                    .or_insert_with(SmallVec::new)
                    .try_push(Dependency {
                        selector: selector.clone(),
                        selector_offset: sequence_start,
                    })?;
            }

            if !compound_visitor.state.is_empty() {
                self.state_affecting_selectors
                    .insert(StateDependency {
                        dep: Dependency {
                            selector: selector.clone(),
                            selector_offset: sequence_start,
                        },
                        state: compound_visitor.state,
                    }, quirks_mode)?;
            }

            if compound_visitor.other_attributes {
                self.other_attribute_affecting_selectors
                    .insert(Dependency {
                        selector: selector.clone(),
                        selector_offset: sequence_start,
                    }, quirks_mode)?;
            }

            combinator = iter.next_sequence();
            if combinator.is_none() {
                break;
            }

            index += 1; // Account for the combinator.
        }

        if !document_state.is_empty() {
            self.document_state_selectors.try_push(DocumentStateDependency {
                state: document_state,
                selector: selector.clone(),
            })?;
        }

        Ok(())
    }
}

/// A struct that collects invalidations for a given compound selector.
struct CompoundSelectorDependencyCollector<'a> {
    /// The state this compound selector is affected by.
    state: ElementState,

    /// The document this _complex_ selector is affected by.
    ///
    /// We don't need to track state per compound selector, since it's global
    /// state and it changes for everything.
    document_state: &'a mut DocumentState,

    /// The classes this compound selector is affected by.
    ///
    /// NB: This will be often a single class, but could be multiple in
    /// presence of :not, :-moz-any, .foo.bar.baz, etc.
    classes: SmallVec<[Atom; 5]>,

    /// The IDs this compound selector is affected by.
    ///
    /// NB: This will be almost always a single id, but could be multiple in
    /// presence of :not, :-moz-any, #foo#bar, etc.
    ids: SmallVec<[Atom; 5]>,

    /// Whether it affects other attribute-dependent selectors that aren't ID or
    /// class selectors (NB: We still set this to true in presence of [class] or
    /// [id] attribute selectors).
    other_attributes: bool,

    /// Whether there were attribute selectors with the id attribute.
    has_id_attribute_selectors: bool,

    /// Whether there were attribute selectors with the class attribute.
    has_class_attribute_selectors: bool,
}

impl<'a> SelectorVisitor for CompoundSelectorDependencyCollector<'a> {
    type Impl = SelectorImpl;

    fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
        #[cfg(feature = "gecko")]
        use selector_parser::NonTSPseudoClass;

        match *s {
            Component::ID(ref id) => {
                self.ids.push(id.clone());
            }
            Component::Class(ref class) => {
                self.classes.push(class.clone());
            }
            Component::NonTSPseudoClass(ref pc) => {
                self.other_attributes |= pc.is_attr_based();
                self.state |= match *pc {
                    #[cfg(feature = "gecko")]
                    NonTSPseudoClass::Dir(ref dir) => {
                        dir_selector_to_state(dir)
                    }
                    _ => pc.state_flag(),
                };
                *self.document_state |= pc.document_state_flag();
            }
            _ => {}
        }

        true
    }

    fn visit_attribute_selector(
        &mut self,
        constraint: &NamespaceConstraint<&Namespace>,
        _local_name: &LocalName,
        local_name_lower: &LocalName,
    ) -> bool {
        self.other_attributes = true;
        let may_match_in_no_namespace = match *constraint {
            NamespaceConstraint::Any => true,
            NamespaceConstraint::Specific(ref ns) => ns.is_empty(),
        };

        if may_match_in_no_namespace {
            self.has_id_attribute_selectors |= *local_name_lower == local_name!("id");
            self.has_class_attribute_selectors |= *local_name_lower == local_name!("class");
        }

        true
    }
}
<|endoftext|>


<commit_before>use solitaire::card::*;

use std::char;
use std::cmp;
use std::iter::range_inclusive;

#[deriving(PartialEq, Show)]
pub struct Deck {
    pub d: Vec<Card>,
}

impl Iterator<uint> for Deck {
    fn next(&mut self) -> Option<uint> {
        Some(self.gen_keystream_letter())
    }
}

impl Deck {
    pub fn new() -> Deck {
        let per_suit = 13;
        let mut deck = Deck { d: Vec::new() };
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Clubs(i));
        }
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Diamonds(i));
        }
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Hearts(i));
        }
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Spades(i));
        }

        deck.d.push(JokerA);
        deck.d.push(JokerB);
        return deck;
    }

    pub fn encrypt(&mut self, plain: &str) -> String {
        plain.chars().map(|x|
                          match char::is_alphabetic(x) {
                              true => (((x as u8 - 63 + self.gen_keystream_letter() as u8) % 26) + 63) as char,
                              false => x,
                          }).collect::<String>()
    }

    pub fn gen_keystream_letter(&mut self) -> uint {
        self.move_joker(JokerA, 1);
        self.move_joker(JokerB, 2);
        self.triple_cut();
        let i = self.d.last().unwrap().count_index();
        self.count_cut(i);
        let i = self.d[0].count_index();
        let output_card = self.d[i];
        match output_card {
            Clubs(i) => i,
            Diamonds(i) => i + 13,
            Hearts(i) => i,
            Spades(i) => i + 13,
            _ => self.gen_keystream_letter(),
        }
    }

    pub fn move_joker(&mut self, j: Card, count: int) {
        let mut i = self.d.as_slice().position_elem(&j).unwrap();
        for _ in range(0i, count) {
            if i < self.d.len()-1 {
                self.d.as_mut_slice().swap(i, i+1);
            } else {
                // if card is the last card in the deck, move below the first card of the deck
                // every card needs to be moved... maybe this should have been a linked list
                let mut new = Vec::with_capacity(self.d.len());
                new.push(self.d[0]);
                new.push(*self.d.last().unwrap());
                new.push_all(self.d.slice(1, self.d.len()-1));
                self.d = new;
            }
            i = if i != self.d.len()-1 {
                i + 1
            } else {
                1
            };
        }
    }

    pub fn triple_cut(&mut self) {
        let mut new = Vec::with_capacity(self.d.len());
        let ai = self.d.as_slice().position_elem(&JokerA).unwrap();
        let bi = self.d.as_slice().position_elem(&JokerB).unwrap();
        let first = cmp::min(ai, bi);
        let secnd = cmp::max(ai, bi);
        new.push_all(self.d.slice(secnd+1, self.d.len()));
        new.push_all(self.d.slice(first, secnd + 1));
        new.push_all(self.d.slice(0, first));
        self.d = new;
    }

    pub fn count_cut(&mut self, c: uint) {
        // cut after c
        let mut new = Vec::with_capacity(self.d.len());
        for i in range(c, self.d.len()) {
            new.push(self.d[i]);
        }

        for i in range(0, c) {
            new.push(self.d[i]);
        }
        self.d = new;
    }

}

#[test]
fn new_deck() {
    let d = Deck::new();
    assert_eq!(d, Deck { d: vec![Clubs(1), Clubs(2), Clubs(3), Clubs(4), Clubs(5), Clubs(6), Clubs(7), Clubs(8), Clubs(9), Clubs(10), Clubs(11), Clubs(12), Clubs(13), Diamonds(1), Diamonds(2), Diamonds(3), Diamonds(4), Diamonds(5), Diamonds(6), Diamonds(7), Diamonds(8), Diamonds(9), Diamonds(10), Diamonds(11), Diamonds(12), Diamonds(13), Hearts(1), Hearts(2), Hearts(3), Hearts(4), Hearts(5), Hearts(6), Hearts(7), Hearts(8), Hearts(9), Hearts(10), Hearts(11), Hearts(12), Hearts(13), Spades(1), Spades(2), Spades(3), Spades(4), Spades(5), Spades(6), Spades(7), Spades(8), Spades(9), Spades(10), Spades(11), Spades(12), Spades(13), JokerA, JokerB] });
}

#[test]
fn move_joker() {
    let mut d = Deck{ d: vec![Clubs(1), Clubs(2), JokerA] };
    d.move_joker(JokerA, 1);
    assert_eq!(d.d, vec![Clubs(1), JokerA, Clubs(2)]);

    let mut d = Deck{ d: vec![Clubs(1), Clubs(2), Hearts(3), JokerA, JokerB] };
    d.move_joker(JokerA, 1);
    d.move_joker(JokerB, 2);
    assert_eq!(d.d, vec![Clubs(1), JokerB, Clubs(2), Hearts(3), JokerA]);
}

#[test]
fn triple_cut() {
    let tests = vec![
        (vec![Clubs(1), JokerA, Clubs(2), JokerB, Clubs(3)], vec![Clubs(3), JokerA, Clubs(2), JokerB, Clubs(1)]),
        (vec![Clubs(1), Hearts(1), JokerA, Clubs(2), JokerB, Clubs(3), Hearts(3)], vec![Clubs(3), Hearts(3), JokerA, Clubs(2), JokerB, Clubs(1), Hearts(1)]),
        (vec![JokerA, JokerB], vec![JokerA, JokerB]),
        ];

    for test in tests.iter() {
        let (initial, expect) = test.clone();
        let mut got = Deck{ d: initial };
        got.triple_cut();
        assert_eq!(got.d, expect);
    }
}

#[test]
fn gen_keystream_letter() {
    let mut d = Deck::new();
    let expects = vec![4, 49, 10, 24, 8, 51, 44, 6, 4, 33];
    for e in expects.iter() {
        assert_eq!(d.gen_keystream_letter(), *e);
    }
}
<commit_msg>Move magic constant to make clearer<commit_after>use solitaire::card::*;

use std::char;
use std::cmp;
use std::iter::range_inclusive;

#[deriving(PartialEq, Show)]
pub struct Deck {
    pub d: Vec<Card>,
}

impl Iterator<uint> for Deck {
    fn next(&mut self) -> Option<uint> {
        Some(self.gen_keystream_letter())
    }
}

impl Deck {
    pub fn new() -> Deck {
        let per_suit = 13;
        let mut deck = Deck { d: Vec::new() };
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Clubs(i));
        }
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Diamonds(i));
        }
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Hearts(i));
        }
        for i in range_inclusive(1, per_suit) {
            deck.d.push(Spades(i));
        }

        deck.d.push(JokerA);
        deck.d.push(JokerB);
        return deck;
    }

    pub fn encrypt(&mut self, plain: &str) -> String {
        let a = 'A' as u8;
        plain.chars().map(|x|
                          match char::is_alphabetic(x) {
                              true => (((x as u8 - a + self.gen_keystream_letter() as u8) % 26) + a) as char,
                              false => x,
                          }).collect::<String>()
    }

    pub fn gen_keystream_letter(&mut self) -> uint {
        self.move_joker(JokerA, 1);
        self.move_joker(JokerB, 2);
        self.triple_cut();
        let i = self.d.last().unwrap().count_index();
        self.count_cut(i);
        let i = self.d[0].count_index();
        let output_card = self.d[i];
        match output_card {
            Clubs(i) => i,
            Diamonds(i) => i + 13,
            Hearts(i) => i,
            Spades(i) => i + 13,
            _ => self.gen_keystream_letter(),
        }
    }

    pub fn move_joker(&mut self, j: Card, count: int) {
        let mut i = self.d.as_slice().position_elem(&j).unwrap();
        for _ in range(0i, count) {
            if i < self.d.len()-1 {
                self.d.as_mut_slice().swap(i, i+1);
            } else {
                // if card is the last card in the deck, move below the first card of the deck
                // every card needs to be moved... maybe this should have been a linked list
                let mut new = Vec::with_capacity(self.d.len());
                new.push(self.d[0]);
                new.push(*self.d.last().unwrap());
                new.push_all(self.d.slice(1, self.d.len()-1));
                self.d = new;
            }
            i = if i != self.d.len()-1 {
                i + 1
            } else {
                1
            };
        }
    }

    pub fn triple_cut(&mut self) {
        let mut new = Vec::with_capacity(self.d.len());
        let ai = self.d.as_slice().position_elem(&JokerA).unwrap();
        let bi = self.d.as_slice().position_elem(&JokerB).unwrap();
        let first = cmp::min(ai, bi);
        let secnd = cmp::max(ai, bi);
        new.push_all(self.d.slice(secnd+1, self.d.len()));
        new.push_all(self.d.slice(first, secnd + 1));
        new.push_all(self.d.slice(0, first));
        self.d = new;
    }

    pub fn count_cut(&mut self, c: uint) {
        // cut after c
        let mut new = Vec::with_capacity(self.d.len());
        for i in range(c, self.d.len()) {
            new.push(self.d[i]);
        }

        for i in range(0, c) {
            new.push(self.d[i]);
        }
        self.d = new;
    }

}

#[test]
fn new_deck() {
    let d = Deck::new();
    assert_eq!(d, Deck { d: vec![Clubs(1), Clubs(2), Clubs(3), Clubs(4), Clubs(5), Clubs(6), Clubs(7), Clubs(8), Clubs(9), Clubs(10), Clubs(11), Clubs(12), Clubs(13), Diamonds(1), Diamonds(2), Diamonds(3), Diamonds(4), Diamonds(5), Diamonds(6), Diamonds(7), Diamonds(8), Diamonds(9), Diamonds(10), Diamonds(11), Diamonds(12), Diamonds(13), Hearts(1), Hearts(2), Hearts(3), Hearts(4), Hearts(5), Hearts(6), Hearts(7), Hearts(8), Hearts(9), Hearts(10), Hearts(11), Hearts(12), Hearts(13), Spades(1), Spades(2), Spades(3), Spades(4), Spades(5), Spades(6), Spades(7), Spades(8), Spades(9), Spades(10), Spades(11), Spades(12), Spades(13), JokerA, JokerB] });
}

#[test]
fn move_joker() {
    let mut d = Deck{ d: vec![Clubs(1), Clubs(2), JokerA] };
    d.move_joker(JokerA, 1);
    assert_eq!(d.d, vec![Clubs(1), JokerA, Clubs(2)]);

    let mut d = Deck{ d: vec![Clubs(1), Clubs(2), Hearts(3), JokerA, JokerB] };
    d.move_joker(JokerA, 1);
    d.move_joker(JokerB, 2);
    assert_eq!(d.d, vec![Clubs(1), JokerB, Clubs(2), Hearts(3), JokerA]);
}

#[test]
fn triple_cut() {
    let tests = vec![
        (vec![Clubs(1), JokerA, Clubs(2), JokerB, Clubs(3)], vec![Clubs(3), JokerA, Clubs(2), JokerB, Clubs(1)]),
        (vec![Clubs(1), Hearts(1), JokerA, Clubs(2), JokerB, Clubs(3), Hearts(3)], vec![Clubs(3), Hearts(3), JokerA, Clubs(2), JokerB, Clubs(1), Hearts(1)]),
        (vec![JokerA, JokerB], vec![JokerA, JokerB]),
        ];

    for test in tests.iter() {
        let (initial, expect) = test.clone();
        let mut got = Deck{ d: initial };
        got.triple_cut();
        assert_eq!(got.d, expect);
    }
}

#[test]
fn gen_keystream_letter() {
    let mut d = Deck::new();
    let expects = vec![4, 49, 10, 24, 8, 51, 44, 6, 4, 33];
    for e in expects.iter() {
        assert_eq!(d.gen_keystream_letter(), *e);
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 TiKV Project Authors. Licensed under Apache-2.0.

//! Storage configuration.

use std::error::Error;

use sys_info;

use tikv_util::config::{self, ReadableSize, KB};

use engine::rocks::{Cache, LRUCacheOptions, MemoryAllocator};

use libc::c_int;

pub const DEFAULT_DATA_DIR: &str = "./";
pub const DEFAULT_ROCKSDB_SUB_DIR: &str = "db";
const DEFAULT_GC_RATIO_THRESHOLD: f64 = 1.1;
const DEFAULT_MAX_KEY_SIZE: usize = 4 * 1024;
const DEFAULT_SCHED_CAPACITY: usize = 10240;
const DEFAULT_SCHED_CONCURRENCY: usize = 2048000;

// According to "Little's law", assuming you can write 100MB per
// second, and it takes about 100ms to process the write requests
// on average, in that situation the writing bytes estimated 10MB,
// here we use 100MB as default value for tolerate 1s latency.
const DEFAULT_SCHED_PENDING_WRITE_MB: u64 = 100;

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    pub data_dir: String,
    pub gc_ratio_threshold: f64,
    pub max_key_size: usize,
    pub scheduler_notify_capacity: usize,
    pub scheduler_concurrency: usize,
    pub scheduler_worker_pool_size: usize,
    pub scheduler_pending_write_threshold: ReadableSize,
    pub block_cache: BlockCacheConfig,
}

impl Default for Config {
    fn default() -> Config {
        let total_cpu = sys_info::cpu_num().unwrap();
        Config {
            data_dir: DEFAULT_DATA_DIR.to_owned(),
            gc_ratio_threshold: DEFAULT_GC_RATIO_THRESHOLD,
            max_key_size: DEFAULT_MAX_KEY_SIZE,
            scheduler_notify_capacity: DEFAULT_SCHED_CAPACITY,
            scheduler_concurrency: DEFAULT_SCHED_CONCURRENCY,
            scheduler_worker_pool_size: if total_cpu >= 16 { 8 } else { 4 },
            scheduler_pending_write_threshold: ReadableSize::mb(DEFAULT_SCHED_PENDING_WRITE_MB),
            block_cache: BlockCacheConfig::default(),
        }
    }
}

impl Config {
    pub fn validate(&mut self) -> Result<(), Box<dyn Error>> {
        if self.data_dir != DEFAULT_DATA_DIR {
            self.data_dir = config::canonicalize_path(&self.data_dir)?
        }
        Ok(())
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct BlockCacheConfig {
    pub shared: bool,
    pub capacity: Option<ReadableSize>,
    pub num_shard_bits: i32,
    pub strict_capacity_limit: bool,
    pub high_pri_pool_ratio: f64,
    pub memory_allocator: Option<String>,
}

impl Default for BlockCacheConfig {
    fn default() -> BlockCacheConfig {
        BlockCacheConfig {
            shared: true,
            capacity: None,
            num_shard_bits: 6,
            strict_capacity_limit: false,
            high_pri_pool_ratio: 0.0,
            memory_allocator: Some(String::from("nodump")),
        }
    }
}

impl BlockCacheConfig {
    pub fn build_shared_cache(&self) -> Option<Cache> {
        if !self.shared {
            return None;
        }
        let capacity = match self.capacity {
            None => {
                let total_mem = sys_info::mem_info().unwrap().total * KB;
                ((total_mem as f64) * 0.45) as usize
            }
            Some(c) => c.0 as usize,
        };
        let mut cache_opts = LRUCacheOptions::new();
        cache_opts.set_capacity(capacity);
        cache_opts.set_num_shard_bits(self.num_shard_bits as c_int);
        cache_opts.set_strict_capacity_limit(self.strict_capacity_limit);
        cache_opts.set_high_pri_pool_ratio(self.high_pri_pool_ratio);
        if let Some(allocator) = self.new_memory_allocator() {
            cache_opts.set_memory_allocator(allocator);
        }
        Some(Cache::new_lru_cache(cache_opts))
    }

    fn new_memory_allocator(&self) -> Option<MemoryAllocator> {
        if let Some(ref alloc) = self.memory_allocator {
            match alloc.as_str() {
                #[cfg(feature = "jemalloc")]
                "nodump" => match MemoryAllocator::new_jemalloc_memory_allocator() {
                    Ok(allocator) => {
                        return Some(allocator);
                    }
                    Err(e) => {
                        warn!("Create jemalloc nodump allocator for block cache failed: {}, continue with default allocator", e);
                    }
                },
                "" => {}
                other => {
                    warn!(
                        "Memory allocator {} is not supported, continue with default allocator",
                        other
                    );
                }
            }
        };
        None
    }
}
<commit_msg>Modify default value of high_pri_pool_ratio (#5576)<commit_after>// Copyright 2016 TiKV Project Authors. Licensed under Apache-2.0.

//! Storage configuration.

use std::error::Error;

use sys_info;

use tikv_util::config::{self, ReadableSize, KB};

use engine::rocks::{Cache, LRUCacheOptions, MemoryAllocator};

use libc::c_int;

pub const DEFAULT_DATA_DIR: &str = "./";
pub const DEFAULT_ROCKSDB_SUB_DIR: &str = "db";
const DEFAULT_GC_RATIO_THRESHOLD: f64 = 1.1;
const DEFAULT_MAX_KEY_SIZE: usize = 4 * 1024;
const DEFAULT_SCHED_CAPACITY: usize = 10240;
const DEFAULT_SCHED_CONCURRENCY: usize = 2048000;

// According to "Little's law", assuming you can write 100MB per
// second, and it takes about 100ms to process the write requests
// on average, in that situation the writing bytes estimated 10MB,
// here we use 100MB as default value for tolerate 1s latency.
const DEFAULT_SCHED_PENDING_WRITE_MB: u64 = 100;

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    pub data_dir: String,
    pub gc_ratio_threshold: f64,
    pub max_key_size: usize,
    pub scheduler_notify_capacity: usize,
    pub scheduler_concurrency: usize,
    pub scheduler_worker_pool_size: usize,
    pub scheduler_pending_write_threshold: ReadableSize,
    pub block_cache: BlockCacheConfig,
}

impl Default for Config {
    fn default() -> Config {
        let total_cpu = sys_info::cpu_num().unwrap();
        Config {
            data_dir: DEFAULT_DATA_DIR.to_owned(),
            gc_ratio_threshold: DEFAULT_GC_RATIO_THRESHOLD,
            max_key_size: DEFAULT_MAX_KEY_SIZE,
            scheduler_notify_capacity: DEFAULT_SCHED_CAPACITY,
            scheduler_concurrency: DEFAULT_SCHED_CONCURRENCY,
            scheduler_worker_pool_size: if total_cpu >= 16 { 8 } else { 4 },
            scheduler_pending_write_threshold: ReadableSize::mb(DEFAULT_SCHED_PENDING_WRITE_MB),
            block_cache: BlockCacheConfig::default(),
        }
    }
}

impl Config {
    pub fn validate(&mut self) -> Result<(), Box<dyn Error>> {
        if self.data_dir != DEFAULT_DATA_DIR {
            self.data_dir = config::canonicalize_path(&self.data_dir)?
        }
        Ok(())
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct BlockCacheConfig {
    pub shared: bool,
    pub capacity: Option<ReadableSize>,
    pub num_shard_bits: i32,
    pub strict_capacity_limit: bool,
    pub high_pri_pool_ratio: f64,
    pub memory_allocator: Option<String>,
}

impl Default for BlockCacheConfig {
    fn default() -> BlockCacheConfig {
        BlockCacheConfig {
            shared: true,
            capacity: None,
            num_shard_bits: 6,
            strict_capacity_limit: false,
            high_pri_pool_ratio: 0.8,
            memory_allocator: Some(String::from("nodump")),
        }
    }
}

impl BlockCacheConfig {
    pub fn build_shared_cache(&self) -> Option<Cache> {
        if !self.shared {
            return None;
        }
        let capacity = match self.capacity {
            None => {
                let total_mem = sys_info::mem_info().unwrap().total * KB;
                ((total_mem as f64) * 0.45) as usize
            }
            Some(c) => c.0 as usize,
        };
        let mut cache_opts = LRUCacheOptions::new();
        cache_opts.set_capacity(capacity);
        cache_opts.set_num_shard_bits(self.num_shard_bits as c_int);
        cache_opts.set_strict_capacity_limit(self.strict_capacity_limit);
        cache_opts.set_high_pri_pool_ratio(self.high_pri_pool_ratio);
        if let Some(allocator) = self.new_memory_allocator() {
            cache_opts.set_memory_allocator(allocator);
        }
        Some(Cache::new_lru_cache(cache_opts))
    }

    fn new_memory_allocator(&self) -> Option<MemoryAllocator> {
        if let Some(ref alloc) = self.memory_allocator {
            match alloc.as_str() {
                #[cfg(feature = "jemalloc")]
                "nodump" => match MemoryAllocator::new_jemalloc_memory_allocator() {
                    Ok(allocator) => {
                        return Some(allocator);
                    }
                    Err(e) => {
                        warn!("Create jemalloc nodump allocator for block cache failed: {}, continue with default allocator", e);
                    }
                },
                "" => {}
                other => {
                    warn!(
                        "Memory allocator {} is not supported, continue with default allocator",
                        other
                    );
                }
            }
        };
        None
    }
}
<|endoftext|>


<commit_before>use cfg_if::cfg_if;
use crate::errno::Errno;
use libc::{self, c_int};
use std::ptr;
use crate::sys::signal::Signal;
use crate::unistd::Pid;
use crate::Result;

pub type RequestType = c_int;

cfg_if! {
    if #[cfg(any(target_os = "dragonfly",
                 target_os = "freebsd",
                 target_os = "macos",
                 target_os = "openbsd"))] {
        #[doc(hidden)]
        pub type AddressType = *mut ::libc::c_char;
    } else {
        #[doc(hidden)]
        pub type AddressType = *mut ::libc::c_void;
    }
}

libc_enum! {
    #[repr(i32)]
    /// Ptrace Request enum defining the action to be taken.
    #[non_exhaustive]
    pub enum Request {
        PT_TRACE_ME,
        PT_READ_I,
        PT_READ_D,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_READ_U,
        PT_WRITE_I,
        PT_WRITE_D,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_WRITE_U,
        PT_CONTINUE,
        PT_KILL,
        #[cfg(any(any(target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "macos"),
                  all(target_os = "openbsd", target_arch = "x86_64"),
                  all(target_os = "netbsd", any(target_arch = "x86_64",
                                                target_arch = "powerpc"))))]
        PT_STEP,
        PT_ATTACH,
        PT_DETACH,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_SIGEXC,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_THUPDATE,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_ATTACHEXC
    }
}

unsafe fn ptrace_other(
    request: Request,
    pid: Pid,
    addr: AddressType,
    data: c_int,
) -> Result<c_int> {
    Errno::result(libc::ptrace(
        request as RequestType,
        libc::pid_t::from(pid),
        addr,
        data,
    )).map(|_| 0)
}

/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)`
///
/// Indicates that this process is to be traced by its parent.
/// This is the only ptrace request to be issued by the tracee.
pub fn traceme() -> Result<()> {
    unsafe { ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0).map(drop) }
}

/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)`
///
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
    unsafe { ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) }
}

/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)`
///
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
/// signal specified by `sig`.
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
    let data = match sig.into() {
        Some(s) => s as c_int,
        None => 0,
    };
    unsafe {
        ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop)
    }
}

/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
///
/// Continues the execution of the process with PID `pid`, optionally
/// delivering a signal specified by `sig`.
pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
    let data = match sig.into() {
        Some(s) => s as c_int,
        None => 0,
    };
    unsafe {
        // Ignore the useless return value
        ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(drop)
    }
}

/// Issues a kill request as with `ptrace(PT_KILL, ...)`
///
/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);`
pub fn kill(pid: Pid) -> Result<()> {
    unsafe {
        ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(drop)
    }
}

/// Move the stopped tracee process forward by a single step as with
/// `ptrace(PT_STEP, ...)`
///
/// Advances the execution of the process with PID `pid` by a single step optionally delivering a
/// signal specified by `sig`.
///
/// # Example
/// ```rust
/// use nix::sys::ptrace::step;
/// use nix::unistd::Pid;
/// use nix::sys::signal::Signal;
/// use nix::sys::wait::*;
/// // If a process changes state to the stopped state because of a SIGUSR1
/// // signal, this will step the process forward and forward the user
/// // signal to the stopped process
/// match waitpid(Pid::from_raw(-1), None) {
///     Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => {
///         let _ = step(pid, Signal::SIGUSR1);
///     }
///     _ => {},
/// }
/// ```
#[cfg(
    any(
        any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"),
        all(target_os = "openbsd", target_arch = "x86_64"),
        all(target_os = "netbsd",
            any(target_arch = "x86_64", target_arch = "powerpc")
        )
    )
)]
pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
    let data = match sig.into() {
        Some(s) => s as c_int,
        None => 0,
    };
    unsafe { ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(drop) }
}

/// Reads a word from a processes memory at the given address
pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> {
    unsafe {
        // Traditionally there was a difference between reading data or
        // instruction memory but not in modern systems.
        ptrace_other(Request::PT_READ_D, pid, addr, 0)
    }
}

/// Writes a word into the processes memory at the given address
pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> {
    unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(drop) }
}
<commit_msg>Suppress clippy::not_unsafe_ptr_arg_deref warnings in ptrace on BSD<commit_after>use cfg_if::cfg_if;
use crate::errno::Errno;
use libc::{self, c_int};
use std::ptr;
use crate::sys::signal::Signal;
use crate::unistd::Pid;
use crate::Result;

pub type RequestType = c_int;

cfg_if! {
    if #[cfg(any(target_os = "dragonfly",
                 target_os = "freebsd",
                 target_os = "macos",
                 target_os = "openbsd"))] {
        #[doc(hidden)]
        pub type AddressType = *mut ::libc::c_char;
    } else {
        #[doc(hidden)]
        pub type AddressType = *mut ::libc::c_void;
    }
}

libc_enum! {
    #[repr(i32)]
    /// Ptrace Request enum defining the action to be taken.
    #[non_exhaustive]
    pub enum Request {
        PT_TRACE_ME,
        PT_READ_I,
        PT_READ_D,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_READ_U,
        PT_WRITE_I,
        PT_WRITE_D,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_WRITE_U,
        PT_CONTINUE,
        PT_KILL,
        #[cfg(any(any(target_os = "dragonfly",
                  target_os = "freebsd",
                  target_os = "macos"),
                  all(target_os = "openbsd", target_arch = "x86_64"),
                  all(target_os = "netbsd", any(target_arch = "x86_64",
                                                target_arch = "powerpc"))))]
        PT_STEP,
        PT_ATTACH,
        PT_DETACH,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_SIGEXC,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_THUPDATE,
        #[cfg(target_os = "macos")]
        #[cfg_attr(docsrs, doc(cfg(all())))]
        PT_ATTACHEXC
    }
}

unsafe fn ptrace_other(
    request: Request,
    pid: Pid,
    addr: AddressType,
    data: c_int,
) -> Result<c_int> {
    Errno::result(libc::ptrace(
        request as RequestType,
        libc::pid_t::from(pid),
        addr,
        data,
    )).map(|_| 0)
}

/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)`
///
/// Indicates that this process is to be traced by its parent.
/// This is the only ptrace request to be issued by the tracee.
pub fn traceme() -> Result<()> {
    unsafe { ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0).map(drop) }
}

/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)`
///
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
    unsafe { ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) }
}

/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)`
///
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
/// signal specified by `sig`.
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
    let data = match sig.into() {
        Some(s) => s as c_int,
        None => 0,
    };
    unsafe {
        ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop)
    }
}

/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
///
/// Continues the execution of the process with PID `pid`, optionally
/// delivering a signal specified by `sig`.
pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
    let data = match sig.into() {
        Some(s) => s as c_int,
        None => 0,
    };
    unsafe {
        // Ignore the useless return value
        ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(drop)
    }
}

/// Issues a kill request as with `ptrace(PT_KILL, ...)`
///
/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);`
pub fn kill(pid: Pid) -> Result<()> {
    unsafe {
        ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(drop)
    }
}

/// Move the stopped tracee process forward by a single step as with
/// `ptrace(PT_STEP, ...)`
///
/// Advances the execution of the process with PID `pid` by a single step optionally delivering a
/// signal specified by `sig`.
///
/// # Example
/// ```rust
/// use nix::sys::ptrace::step;
/// use nix::unistd::Pid;
/// use nix::sys::signal::Signal;
/// use nix::sys::wait::*;
/// // If a process changes state to the stopped state because of a SIGUSR1
/// // signal, this will step the process forward and forward the user
/// // signal to the stopped process
/// match waitpid(Pid::from_raw(-1), None) {
///     Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => {
///         let _ = step(pid, Signal::SIGUSR1);
///     }
///     _ => {},
/// }
/// ```
#[cfg(
    any(
        any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"),
        all(target_os = "openbsd", target_arch = "x86_64"),
        all(target_os = "netbsd",
            any(target_arch = "x86_64", target_arch = "powerpc")
        )
    )
)]
pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
    let data = match sig.into() {
        Some(s) => s as c_int,
        None => 0,
    };
    unsafe { ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(drop) }
}

/// Reads a word from a processes memory at the given address
// Technically, ptrace doesn't dereference the pointer.  It passes it directly
// to the kernel.
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> {
    unsafe {
        // Traditionally there was a difference between reading data or
        // instruction memory but not in modern systems.
        ptrace_other(Request::PT_READ_D, pid, addr, 0)
    }
}

/// Writes a word into the processes memory at the given address
// Technically, ptrace doesn't dereference the pointer.  It passes it directly
// to the kernel.
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> {
    unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(drop) }
}
<|endoftext|>


<commit_before>// Copyright 2015 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

use {
    CommandResult,
    Host,
    Providers,
    Result,
    Cpu, Os, Telemetry,
};
use command::CommandTarget;
use error::Error;
use file::{FileTarget, FileOwner};
use package::PackageTarget;
use regex::Regex;
use service::ServiceTarget;
use std::env;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use super::{default_base as default, Target, unix_base as unix};
use telemetry::TelemetryTarget;

//
// Command
//

impl CommandTarget for Target {
    #[allow(unused_variables)]
    fn exec(host: &mut Host, cmd: &str) -> Result<CommandResult> {
        default::command_exec(cmd)
    }
}

//
// File
//

impl FileTarget for Target {
    #[allow(unused_variables)]
    fn file_is_file(host: &mut Host, path: &str) -> Result<bool> {
        default::file_is_file(path)
    }

    #[allow(unused_variables)]
    fn file_exists(host: &mut Host, path: &str) -> Result<bool> {
        default::file_exists(path)
    }

    #[allow(unused_variables)]
    fn file_delete(host: &mut Host, path: &str) -> Result<()> {
        default::file_delete(path)
    }

    #[allow(unused_variables)]
    fn file_mv(host: &mut Host, path: &str, new_path: &str) -> Result<()> {
        default::file_mv(path, new_path)
    }

    #[allow(unused_variables)]
    fn file_copy(host: &mut Host, path: &str, new_path: &str) -> Result<()> {
        default::file_copy(path, new_path)
    }

    #[allow(unused_variables)]
    fn file_get_owner(host: &mut Host, path: &str) -> Result<FileOwner> {
        unix::file_get_owner(path)
    }

    #[allow(unused_variables)]
    fn file_set_owner(host: &mut Host, path: &str, user: &str, group: &str) -> Result<()> {
        default::file_set_owner(path, user, group)
    }

    #[allow(unused_variables)]
    fn file_get_mode(host: &mut Host, path: &str) -> Result<u16> {
        unix::file_get_mode(path)
    }

    #[allow(unused_variables)]
    fn file_set_mode(host: &mut Host, path: &str, mode: u16) -> Result<()> {
        default::file_set_mode(path, mode)
    }
}

//
// Package
//

impl PackageTarget for Target {
    fn default_provider(host: &mut Host) -> Result<Providers> {
        default::default_provider(host, vec![Providers::Pkg, Providers::Ports])
    }
}

//
// Service
//

impl ServiceTarget for Target {
    #[allow(unused_variables)]
    fn service_action(host: &mut Host, name: &str, action: &str) -> Result<CommandResult> {
        let mut rc_conf = try!(OpenOptions::new().read(true).write(true).append(true).open("/etc/rc.conf"));
        let mut rc = String::new();
        try!(rc_conf.read_to_string(&mut rc));

        let match_daemon = Regex::new(&format!("(?m)^\\s*{}_enable\\s*=\\s*[\"']{{0,1}}(?:YES|yes)[\"']{{0,1}}", name)).unwrap();

        match action {
            "enable" => {
                if ! match_daemon.is_match(&rc) {
                    try!(rc_conf.write_all(&format!("{}_enable=\"YES\"", name).into_bytes()));
                }

                Ok(CommandResult{
                    exit_code: 0,
                    stdout: String::new(),
                    stderr: String::new(),
                })
            },
            "disable" => {
                if match_daemon.is_match(&rc) {
                    try!(rc_conf.write_all(&match_daemon.replace(&rc, "").into_bytes()));
                }

                Ok(CommandResult{
                    exit_code: 0,
                    stdout: String::new(),
                    stderr: String::new(),
                })
            },
            _ => default::service_action(name, action)
        }
    }
}

//
// Telemetry
//

impl TelemetryTarget for Target {
    #[allow(unused_variables)]
    fn telemetry_init(host: &mut Host) -> Result<Telemetry> {
        let cpu_vendor = try!(telemetry_cpu_vendor());
        let cpu_brand = try!(unix::get_sysctl_item("hw\\.model"));
        let hostname = try!(default::hostname());
        let os_version = try!(unix::version());

        Ok(Telemetry::new(
            Cpu::new(
                &cpu_vendor,
                &cpu_brand,
                try!(try!(unix::get_sysctl_item("hw\\.ncpu")).parse::<u32>()),
            ),
            try!(default::fs()),
            &hostname,
            try!(try!(unix::get_sysctl_item("hw\\.physmem")).parse::<u64>()),
            try!(unix::net()),
            Os::new(env::consts::ARCH, "unix", "freebsd", &os_version),
        ))
    }
}

fn telemetry_cpu_vendor() -> Result<String> {
    let mut fh = try!(File::open("/var/run/dmesg.boot"));
    let mut fc = String::new();
    fh.read_to_string(&mut fc).unwrap();

    let regex = Regex::new(r#"(?m)^CPU:.+$\n\s+Origin="([A-Za-z]+)""#).unwrap();
    if let Some(cap) = regex.captures(&fc) {
        Ok(cap.at(1).unwrap().to_string())
    } else {
        Err(Error::Generic("Could not match CPU vendor".to_string()))
    }
}

#[cfg(test)]
mod tests {
    use Host;
    use package::PackageTarget;
    use target::Target;
    use telemetry::TelemetryTarget;

    #[test]
    fn test_package_default_provider() {
        let mut host = Host::new();
        let result = Target::default_provider(&mut host);
        assert!(result.is_ok());
    }

    #[test]
    fn test_telemetry_init() {
        let mut host = Host::new();
        let result = Target::telemetry_init(&mut host);
        assert!(result.is_ok());
    }
}
<commit_msg>Fixed bugs with managing rc.conf<commit_after>// Copyright 2015 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

use {
    CommandResult,
    Host,
    Providers,
    Result,
    Cpu, Os, Telemetry,
};
use command::CommandTarget;
use error::Error;
use file::{FileTarget, FileOwner};
use package::PackageTarget;
use regex::Regex;
use service::ServiceTarget;
use std::env;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use super::{default_base as default, Target, unix_base as unix};
use telemetry::TelemetryTarget;

//
// Command
//

impl CommandTarget for Target {
    #[allow(unused_variables)]
    fn exec(host: &mut Host, cmd: &str) -> Result<CommandResult> {
        default::command_exec(cmd)
    }
}

//
// File
//

impl FileTarget for Target {
    #[allow(unused_variables)]
    fn file_is_file(host: &mut Host, path: &str) -> Result<bool> {
        default::file_is_file(path)
    }

    #[allow(unused_variables)]
    fn file_exists(host: &mut Host, path: &str) -> Result<bool> {
        default::file_exists(path)
    }

    #[allow(unused_variables)]
    fn file_delete(host: &mut Host, path: &str) -> Result<()> {
        default::file_delete(path)
    }

    #[allow(unused_variables)]
    fn file_mv(host: &mut Host, path: &str, new_path: &str) -> Result<()> {
        default::file_mv(path, new_path)
    }

    #[allow(unused_variables)]
    fn file_copy(host: &mut Host, path: &str, new_path: &str) -> Result<()> {
        default::file_copy(path, new_path)
    }

    #[allow(unused_variables)]
    fn file_get_owner(host: &mut Host, path: &str) -> Result<FileOwner> {
        unix::file_get_owner(path)
    }

    #[allow(unused_variables)]
    fn file_set_owner(host: &mut Host, path: &str, user: &str, group: &str) -> Result<()> {
        default::file_set_owner(path, user, group)
    }

    #[allow(unused_variables)]
    fn file_get_mode(host: &mut Host, path: &str) -> Result<u16> {
        unix::file_get_mode(path)
    }

    #[allow(unused_variables)]
    fn file_set_mode(host: &mut Host, path: &str, mode: u16) -> Result<()> {
        default::file_set_mode(path, mode)
    }
}

//
// Package
//

impl PackageTarget for Target {
    fn default_provider(host: &mut Host) -> Result<Providers> {
        default::default_provider(host, vec![Providers::Pkg, Providers::Ports])
    }
}

//
// Service
//

impl ServiceTarget for Target {
    #[allow(unused_variables)]
    fn service_action(host: &mut Host, name: &str, action: &str) -> Result<CommandResult> {
        let mut rc_conf = try!(OpenOptions::new().read(true).write(true).open("/etc/rc.conf"));
        let mut rc = String::new();
        try!(rc_conf.read_to_string(&mut rc));

        let match_daemon = Regex::new(&format!("(?m)^\\s*{}_enable\\s*=\\s*[\"']{{0,1}}(?:YES|yes)[\"']{{0,1}}\n?", name)).unwrap();

        match action {
            "enable" => {
                if ! match_daemon.is_match(&rc) {
                    let newline = if rc.ends_with("\n") { "" } else { "\n" };
                    try!(rc_conf.write_all(&format!("{}{}_enable=\"YES\"\n", newline, name).into_bytes()));
                    try!(rc_conf.sync_data());
                }

                Ok(CommandResult{
                    exit_code: 0,
                    stdout: String::new(),
                    stderr: String::new(),
                })
            },
            "disable" => {
                if match_daemon.is_match(&rc) {
                    let replace = match_daemon.replace(&rc, "").trim().to_string();
                    try!(rc_conf.seek(SeekFrom::Start(0)));
                    try!(rc_conf.set_len(replace.len() as u64));
                    try!(rc_conf.write_all(replace.as_bytes()));
                    try!(rc_conf.sync_data());
                }

                Ok(CommandResult{
                    exit_code: 0,
                    stdout: String::new(),
                    stderr: String::new(),
                })
            },
            "start" | "stop" | "restart" => {
                if ! match_daemon.is_match(&rc) {
                    default::service_action(name, &format!("one{}", action))
                } else {
                    default::service_action(name, action)
                }
            },
            _ => default::service_action(name, action),
        }
    }
}

//
// Telemetry
//

impl TelemetryTarget for Target {
    #[allow(unused_variables)]
    fn telemetry_init(host: &mut Host) -> Result<Telemetry> {
        let cpu_vendor = try!(telemetry_cpu_vendor());
        let cpu_brand = try!(unix::get_sysctl_item("hw\\.model"));
        let hostname = try!(default::hostname());
        let os_version = try!(unix::version());

        Ok(Telemetry::new(
            Cpu::new(
                &cpu_vendor,
                &cpu_brand,
                try!(try!(unix::get_sysctl_item("hw\\.ncpu")).parse::<u32>()),
            ),
            try!(default::fs()),
            &hostname,
            try!(try!(unix::get_sysctl_item("hw\\.physmem")).parse::<u64>()),
            try!(unix::net()),
            Os::new(env::consts::ARCH, "unix", "freebsd", &os_version),
        ))
    }
}

fn telemetry_cpu_vendor() -> Result<String> {
    let mut fh = try!(File::open("/var/run/dmesg.boot"));
    let mut fc = String::new();
    fh.read_to_string(&mut fc).unwrap();

    let regex = Regex::new(r#"(?m)^CPU:.+$\n\s+Origin="([A-Za-z]+)""#).unwrap();
    if let Some(cap) = regex.captures(&fc) {
        Ok(cap.at(1).unwrap().to_string())
    } else {
        Err(Error::Generic("Could not match CPU vendor".to_string()))
    }
}

#[cfg(test)]
mod tests {
    use Host;
    use package::PackageTarget;
    use target::Target;
    use telemetry::TelemetryTarget;

    #[test]
    fn test_package_default_provider() {
        let mut host = Host::new();
        let result = Target::default_provider(&mut host);
        assert!(result.is_ok());
    }

    #[test]
    fn test_telemetry_init() {
        let mut host = Host::new();
        let result = Target::telemetry_init(&mut host);
        assert!(result.is_ok());
    }
}
<|endoftext|>


<commit_before>#![feature(test)]

extern crate clap;
extern crate test;

use clap::{App, Arg, SubCommand};

use test::Bencher;

static M_VAL_NAMES: [&'static str; 2] = ["one", "two"];
static ARGS: &'static str = "-o --option=[opt]... 'tests options'
                             [positional] 'tests positionals'";
static OPT3_VALS: [&'static str; 2] = ["fast", "slow"];
static POS3_VALS: [&'static str; 2] = ["vi", "emacs"];

macro_rules! create_app {
    () => ({
        App::new("claptests")
                .version("0.1")
                .about("tests clap library")
                .author("Kevin K. <kbknapp@gmail.com>")
                .args_from_usage(ARGS)
                .arg(Arg::from_usage("-f --flag... 'tests flags'")
                             .global(true))
                .args(vec![
                          Arg::from_usage("[flag2] -F 'tests flags with exclusions'").conflicts_with("flag").requires("option2"),
                          Arg::from_usage("--long-option-2 [option2] 'tests long options with exclusions'").conflicts_with("option").requires("positional2"),
                          Arg::from_usage("[positional2] 'tests positionals with exclusions'"),
                          Arg::from_usage("-O --Option [option3] 'tests options with specific value sets'").possible_values(&OPT3_VALS),
                          Arg::from_usage("[positional3]... 'tests positionals with specific values'").possible_values(&POS3_VALS),
                          Arg::from_usage("--multvals [multvals] 'Tests mutliple values, not mult occs'").value_names(&M_VAL_NAMES),
                          Arg::from_usage("--multvalsmo [multvalsmo]... 'Tests mutliple values, not mult occs'").value_names(&M_VAL_NAMES),
                          Arg::from_usage("--minvals2 [minvals]... 'Tests 2 min vals'").min_values(2),
                          Arg::from_usage("--maxvals3 [maxvals]... 'Tests 3 max vals'").max_values(3)
                    ])
                .subcommand(SubCommand::with_name("subcmd")
                                        .about("tests subcommands")
                                        .version("0.1")
                                        .author("Kevin K. <kbknapp@gmail.com>")
                                        .arg_from_usage("-o --option [scoption]... 'tests options'")
                                        .arg_from_usage("[scpositional] 'tests positionals'"))
    })
}

#[bench]
fn build_app(b: &mut Bencher) {

    b.iter(|| create_app!());
}

#[bench]
fn parse_clean(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec![""]));
}

#[bench]
fn parse_flag(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "-f"]));
}

#[bench]
fn parse_option(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "-o", "option1"]));
}

#[bench]
fn parse_positional(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "arg1"]));
}

#[bench]
fn parse_sc_clean(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd"]));
}

#[bench]
fn parse_sc_flag(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "-f"]));
}

#[bench]
fn parse_sc_option(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "-o", "option1"]));
}

#[bench]
fn parse_sc_positional(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "arg1"]));
}

#[bench]
fn parse_complex1(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "three"]));
}

#[bench]
fn parse_complex2(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"]));
}

#[bench]
fn parse_sc_complex(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "-f", "-o", "option1", "arg1"]));
}
<commit_msg>tests: adds benches for builder vs from_usage<commit_after>#![feature(test)]

#[macro_use]
extern crate clap;
extern crate test;

use clap::{App, Arg, SubCommand};

use test::Bencher;

static M_VAL_NAMES: [&'static str; 2] = ["one", "two"];
static ARGS: &'static str = "-o --option=[opt]... 'tests options'
                             [positional] 'tests positionals'";
static OPT3_VALS: [&'static str; 2] = ["fast", "slow"];
static POS3_VALS: [&'static str; 2] = ["vi", "emacs"];

macro_rules! create_app {
    () => ({
        App::new("claptests")
                .version("0.1")
                .about("tests clap library")
                .author("Kevin K. <kbknapp@gmail.com>")
                .args_from_usage(ARGS)
                .arg(Arg::from_usage("-f --flag... 'tests flags'")
                             .global(true))
                .args(vec![
                          Arg::from_usage("[flag2] -F 'tests flags with exclusions'").conflicts_with("flag").requires("option2"),
                          Arg::from_usage("--long-option-2 [option2] 'tests long options with exclusions'").conflicts_with("option").requires("positional2"),
                          Arg::from_usage("[positional2] 'tests positionals with exclusions'"),
                          Arg::from_usage("-O --Option [option3] 'tests options with specific value sets'").possible_values(&OPT3_VALS),
                          Arg::from_usage("[positional3]... 'tests positionals with specific values'").possible_values(&POS3_VALS),
                          Arg::from_usage("--multvals [multvals] 'Tests mutliple values, not mult occs'").value_names(&M_VAL_NAMES),
                          Arg::from_usage("--multvalsmo [multvalsmo]... 'Tests mutliple values, not mult occs'").value_names(&M_VAL_NAMES),
                          Arg::from_usage("--minvals2 [minvals]... 'Tests 2 min vals'").min_values(2),
                          Arg::from_usage("--maxvals3 [maxvals]... 'Tests 3 max vals'").max_values(3)
                    ])
                .subcommand(SubCommand::with_name("subcmd")
                                        .about("tests subcommands")
                                        .version("0.1")
                                        .author("Kevin K. <kbknapp@gmail.com>")
                                        .arg_from_usage("-o --option [scoption]... 'tests options'")
                                        .arg_from_usage("[scpositional] 'tests positionals'"))
    })
}

#[bench]
fn create_app_from_usage(b: &mut Bencher) {

    b.iter(|| create_app!());
}

#[bench]
fn create_app_builder(b: &mut Bencher) {
    b.iter(|| {
        App::new("claptests")
                .version("0.1")
                .about("tests clap library")
                .author("Kevin K. <kbknapp@gmail.com>")
                .arg(Arg::with_name("opt")
                    .help("tests options")
                    .short("o")
                    .long("option")
                    .takes_value(true)
                    .multiple(true))
                .arg(Arg::with_name("positional")
                    .help("tests positionals")
                    .index(1))
                .arg(Arg::with_name("flag")
                     .short("f")
                     .help("tests flags")
                     .long("flag")
                     .multiple(true)
                     .global(true))
                .arg(Arg::with_name("flag2")
                    .short("F")
                    .help("tests flags with exclusions")
                    .conflicts_with("flag")
                    .requires("option2"))
                .arg(Arg::with_name("option2")
                    .help("tests long options with exclusions")
                    .conflicts_with("option")
                    .requires("positional2")
                    .takes_value(true)
                    .long("long-option-2"))
                .arg(Arg::with_name("positional2")
                    .index(3)
                    .help("tests positionals with exclusions"))
                .arg(Arg::with_name("option3")
                    .short("O")
                    .long("Option")
                    .takes_value(true)
                    .help("tests options with specific value sets")
                    .possible_values(&OPT3_VALS))
                .arg(Arg::with_name("positional3")
                    .multiple(true)
                    .help("tests positionals with specific values")
                    .index(4)
                    .possible_values(&POS3_VALS))
                .arg(Arg::with_name("multvals")
                    .long("multvals")
                    .takes_value(true)
                    .help("Tests mutliple values, not mult occs")
                    .value_names(&M_VAL_NAMES))
                .arg(Arg::with_name("multvalsmo")
                    .long("multvalsmo")
                    .takes_value(true)
                    .multiple(true)
                    .help("Tests mutliple values, not mult occs")
                    .value_names(&M_VAL_NAMES))
                .arg(Arg::with_name("minvals")
                    .long("minvals2")
                    .multiple(true)
                    .takes_value(true)
                    .help("Tests 2 min vals")
                    .min_values(2))
                .arg(Arg::with_name("maxvals")
                    .long("maxvals3")
                    .takes_value(true)
                    .multiple(true)
                    .help("Tests 3 max vals")
                    .max_values(3))
                .subcommand(SubCommand::with_name("subcmd")
                    .about("tests subcommands")
                    .version("0.1")
                    .author("Kevin K. <kbknapp@gmail.com>")
                    .arg(Arg::with_name("scoption")
                        .short("o")
                        .long("option")
                        .multiple(true)
                        .takes_value(true)
                        .help("tests options"))
                    .arg(Arg::with_name("scpositional")
                        .index(1)
                        .help("tests positionals")));
    });
}

// #[bench]
// fn create_app_macros(b: &mut Bencher) {
//     b.iter(|| {
//         clap_app!(claptests =>
//                 (version: "0.1")
//                 (about: "tests clap library")
//                 (author: "Kevin K. <kbknapp@gmail.com>")
//                 (@arg opt: -o --option +takes_value ... "tests options")
//                 (@arg positional: [positional] "tests positionals")
//                 (@arg flag: -f --flag ... +global "tests flags")
//                 (@arg flag2: -F conflicts_with[flag] requires[option2] "tests flags with exclusions")
//                 (@arg option2: --long-option-2 conflicts_with[option] requires[positional2] "tests long options with exclusions")
//                 (@arg positional2: [positional2] "tests positionals with exclusions")
//                 (@arg option3: -O --Option +takes_value possible_values[fast, slow] "tests options with specific value sets")
//                 (@arg positional3: [positional3] ... possible_values[vi, emacs] "tests positionals with specific values")
//                 (@arg multvals: --multvals +takes_value value_names[one, two] "Tests mutliple values, not mult occs")
//                 (@arg multvalsmo: --multvalsmo ... +takes_value value_names[one, two] "Tests mutliple values, not mult occs")
//                 (@arg minvals: --minvals2 min_values(1) ... +takes_value "Tests 2 min vals")
//                 (@arg maxvals: --maxvals3 ... +takes_value max_values(3) "Tests 3 max vals")
//                 (@subcommand subcmd =>
//                     (about: "tests subcommands")
//                     (version: "0.1")
//                     (author: "Kevin K. <kbknapp@gmail.com>")
//                     (@arg scoption: -o --option ... +takes_value "tests options")
//                     (@arg scpositional: [scpositional] "tests positionals"))
//         );
//     });
// }

#[bench]
fn parse_clean(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec![""]));
}

#[bench]
fn parse_flag(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "-f"]));
}

#[bench]
fn parse_option(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "-o", "option1"]));
}

#[bench]
fn parse_positional(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "arg1"]));
}

#[bench]
fn parse_sc_clean(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd"]));
}

#[bench]
fn parse_sc_flag(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "-f"]));
}

#[bench]
fn parse_sc_option(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "-o", "option1"]));
}

#[bench]
fn parse_sc_positional(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "arg1"]));
}

#[bench]
fn parse_complex1(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "three"]));
}

#[bench]
fn parse_complex2(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"]));
}

#[bench]
fn parse_sc_complex(b: &mut Bencher) {
    b.iter(|| create_app!().get_matches_from(vec!["", "subcmd", "-f", "-o", "option1", "arg1"]));
}
<|endoftext|>


<commit_before>#![feature(test)]
#![deny(warnings)]

extern crate futures;
extern crate hyper;
extern crate pretty_env_logger;
extern crate test;
extern crate tokio;

use std::net::SocketAddr;

use futures::{Future, Stream};
use tokio::runtime::current_thread::Runtime;

use hyper::{Body, Method, Request, Response, Server};
use hyper::client::HttpConnector;

#[bench]
fn http1_get(b: &mut test::Bencher) {
    opts()
        .bench(b)
}

#[bench]
fn http1_post(b: &mut test::Bencher) {
    opts()
        .method(Method::POST)
        .request_body(b"foo bar baz quux")
        .bench(b)
}

#[bench]
fn http1_body_100kb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 100];
    opts()
        .method(Method::POST)
        .request_body(body)
        .response_body(body)
        .bench(b)
}

#[bench]
fn http1_body_10mb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 1024 * 10];
    opts()
        .method(Method::POST)
        .request_body(body)
        .response_body(body)
        .bench(b)
}

#[bench]
fn http1_get_parallel(b: &mut test::Bencher) {
    opts()
        .parallel(10)
        .bench(b)
}

#[bench]
fn http2_get(b: &mut test::Bencher) {
    opts()
        .http2()
        .bench(b)
}

#[bench]
fn http2_post(b: &mut test::Bencher) {
    opts()
        .http2()
        .method(Method::POST)
        .request_body(b"foo bar baz quux")
        .bench(b)
}

#[bench]
fn http2_get_parallel(b: &mut test::Bencher) {
    opts()
        .http2()
        .parallel(10)
        .bench(b)
}

// ==== Benchmark Options =====

struct Opts {
    http2: bool,
    parallel_cnt: u32,
    request_method: Method,
    request_body: Option<&'static [u8]>,
    response_body: &'static [u8],
}

fn opts() -> Opts {
    Opts {
        http2: false,
        parallel_cnt: 1,
        request_method: Method::GET,
        request_body: None,
        response_body: b"Hello",
    }
}

impl Opts {
    fn http2(mut self) -> Self {
        self.http2 = true;
        self
    }

    fn method(mut self, m: Method) -> Self {
        self.request_method = m;
        self
    }

    fn request_body(mut self, body: &'static [u8]) -> Self {
        self.request_body = Some(body);
        self
    }

    fn response_body(mut self, body: &'static [u8]) -> Self {
        self.response_body = body;
        self
    }

    fn parallel(mut self, cnt: u32) -> Self {
        assert!(cnt > 0, "parallel count must be larger than 0");
        self.parallel_cnt = cnt;
        self
    }

    fn bench(self, b: &mut test::Bencher) {
        let _ = pretty_env_logger::try_init();
        let mut rt = Runtime::new().unwrap();

        b.bytes = self.response_body.len() as u64 + self.request_body.map(|b| b.len()).unwrap_or(0) as u64;

        let addr = spawn_hello(&mut rt, self.response_body);

        let connector = HttpConnector::new(1);
        let client = hyper::Client::builder()
            .http2_only(self.http2)
            .build::<_, Body>(connector);

        let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap();

        let make_request = || {
            let body = self
                .request_body
                .map(Body::from)
                .unwrap_or_else(|| Body::empty());
            let mut req = Request::new(body);
            *req.method_mut() = self.request_method.clone();
            req
        };

        if self.parallel_cnt == 1 {
            b.iter(move || {
                let mut req = make_request();
                *req.uri_mut() = url.clone();
                rt.block_on(client.request(req).and_then(|res| {
                    res.into_body().for_each(|_chunk| {
                        Ok(())
                    })
                })).expect("client wait");
            });
        } else {
            b.iter(|| {
                let futs = (0..self.parallel_cnt)
                    .into_iter()
                    .map(|_| {
                        let mut req = make_request();
                        *req.uri_mut() = url.clone();
                        client.request(req).and_then(|res| {
                            res.into_body().for_each(|_chunk| {
                                Ok(())
                            })
                        }).map_err(|e| panic!("client error: {}", e))
                    });
                let _ = rt.block_on(::futures::future::join_all(futs));
            });
        }
    }
}

fn spawn_hello(rt: &mut Runtime, body: &'static [u8]) -> SocketAddr {
    use hyper::service::{service_fn};
    let addr = "127.0.0.1:0".parse().unwrap();

    let srv = Server::bind(&addr)
        .serve(move || {
            service_fn(move |req: Request<Body>| {
                req
                    .into_body()
                    .for_each(|_chunk| {
                        Ok(())
                    })
                    .map(move |_| {
                        Response::new(Body::from(body))
                    })
            })
        });
    let addr = srv.local_addr();
    let fut = srv.map_err(|err| panic!("server error: {}", err));
    rt.spawn(fut);
    return addr
}
<commit_msg>chore(benches): add a few parallel + body end-to-end configurations<commit_after>#![feature(test)]
#![deny(warnings)]

extern crate futures;
extern crate hyper;
extern crate pretty_env_logger;
extern crate test;
extern crate tokio;

use std::net::SocketAddr;

use futures::{Future, Stream};
use tokio::runtime::current_thread::Runtime;

use hyper::{Body, Method, Request, Response, Server};
use hyper::client::HttpConnector;

#[bench]
fn http1_get(b: &mut test::Bencher) {
    opts()
        .bench(b)
}

#[bench]
fn http1_post(b: &mut test::Bencher) {
    opts()
        .method(Method::POST)
        .request_body(b"foo bar baz quux")
        .bench(b)
}

#[bench]
fn http1_body_both_100kb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 100];
    opts()
        .method(Method::POST)
        .request_body(body)
        .response_body(body)
        .bench(b)
}

#[bench]
fn http1_body_both_10mb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 1024 * 10];
    opts()
        .method(Method::POST)
        .request_body(body)
        .response_body(body)
        .bench(b)
}

#[bench]
fn http1_parallel_x10_empty(b: &mut test::Bencher) {
    opts()
        .parallel(10)
        .bench(b)
}

#[bench]
fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 1024 * 10];
    opts()
        .parallel(10)
        .method(Method::POST)
        .request_body(body)
        .bench(b)
}

#[bench]
fn http2_get(b: &mut test::Bencher) {
    opts()
        .http2()
        .bench(b)
}

#[bench]
fn http2_post(b: &mut test::Bencher) {
    opts()
        .http2()
        .method(Method::POST)
        .request_body(b"foo bar baz quux")
        .bench(b)
}

#[bench]
fn http2_req_100kb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 100];
    opts()
        .http2()
        .method(Method::POST)
        .request_body(body)
        .bench(b)
}

#[bench]
fn http2_parallel_x10_empty(b: &mut test::Bencher) {
    opts()
        .http2()
        .parallel(10)
        .bench(b)
}

#[bench]
fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) {
    let body = &[b'x'; 1024 * 1024 * 10];
    opts()
        .http2()
        .parallel(10)
        .method(Method::POST)
        .request_body(body)
        .http2_stream_window(std::u32::MAX >> 1)
        .http2_conn_window(std::u32::MAX >> 1)
        .bench(b)
}

// ==== Benchmark Options =====

struct Opts {
    http2: bool,
    http2_stream_window: Option<u32>,
    http2_conn_window: Option<u32>,
    parallel_cnt: u32,
    request_method: Method,
    request_body: Option<&'static [u8]>,
    response_body: &'static [u8],
}

fn opts() -> Opts {
    Opts {
        http2: false,
        http2_stream_window: None,
        http2_conn_window: None,
        parallel_cnt: 1,
        request_method: Method::GET,
        request_body: None,
        response_body: b"Hello",
    }
}

impl Opts {
    fn http2(mut self) -> Self {
        self.http2 = true;
        self
    }

    fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self {
        self.http2_stream_window = sz.into();
        self
    }

    fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self {
        self.http2_conn_window = sz.into();
        self
    }

    fn method(mut self, m: Method) -> Self {
        self.request_method = m;
        self
    }

    fn request_body(mut self, body: &'static [u8]) -> Self {
        self.request_body = Some(body);
        self
    }

    fn response_body(mut self, body: &'static [u8]) -> Self {
        self.response_body = body;
        self
    }

    fn parallel(mut self, cnt: u32) -> Self {
        assert!(cnt > 0, "parallel count must be larger than 0");
        self.parallel_cnt = cnt;
        self
    }

    fn bench(self, b: &mut test::Bencher) {
        let _ = pretty_env_logger::try_init();
        let mut rt = Runtime::new().unwrap();

        b.bytes = self.response_body.len() as u64 + self.request_body.map(|b| b.len()).unwrap_or(0) as u64;

        let addr = spawn_hello(&mut rt, &self);

        let connector = HttpConnector::new(1);
        let client = hyper::Client::builder()
            .http2_only(self.http2)
            .http2_initial_stream_window_size(self.http2_stream_window)
            .http2_initial_connection_window_size(self.http2_conn_window)
            .build::<_, Body>(connector);

        let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap();

        let make_request = || {
            let body = self
                .request_body
                .map(Body::from)
                .unwrap_or_else(|| Body::empty());
            let mut req = Request::new(body);
            *req.method_mut() = self.request_method.clone();
            req
        };

        if self.parallel_cnt == 1 {
            b.iter(move || {
                let mut req = make_request();
                *req.uri_mut() = url.clone();
                rt.block_on(client.request(req).and_then(|res| {
                    res.into_body().for_each(|_chunk| {
                        Ok(())
                    })
                })).expect("client wait");
            });
        } else {
            b.iter(|| {
                let futs = (0..self.parallel_cnt)
                    .into_iter()
                    .map(|_| {
                        let mut req = make_request();
                        *req.uri_mut() = url.clone();
                        client.request(req).and_then(|res| {
                            res.into_body().for_each(|_chunk| {
                                Ok(())
                            })
                        }).map_err(|e| panic!("client error: {}", e))
                    });
                let _ = rt.block_on(::futures::future::join_all(futs));
            });
        }
    }
}

fn spawn_hello(rt: &mut Runtime, opts: &Opts) -> SocketAddr {
    use hyper::service::{service_fn};
    let addr = "127.0.0.1:0".parse().unwrap();

    let body = opts.response_body;
    let mut builder = Server::bind(&addr)
        .http2_only(opts.http2);
    // api woopsie
    builder
        .http2_initial_stream_window_size(opts.http2_stream_window)
        .http2_initial_connection_window_size(opts.http2_conn_window);

    let srv = builder.serve(move || {
            service_fn(move |req: Request<Body>| {
                req
                    .into_body()
                    .for_each(|_chunk| {
                        Ok(())
                    })
                    .map(move |_| {
                        Response::new(Body::from(body))
                    })
            })
        });
    let addr = srv.local_addr();
    let fut = srv.map_err(|err| panic!("server error: {}", err));
    rt.spawn(fut);
    return addr
}
<|endoftext|>