| /* | |
| * Copyright (C) 2024 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #ifndef SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_ | |
| #define SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_ | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <memory> | |
| #include <utility> | |
| #include <vector> | |
| #include "perfetto/base/logging.h" | |
| #include "perfetto/ext/base/small_vector.h" | |
| namespace perfetto::trace_processor { | |
| using Ts = uint64_t; | |
| using Id = uint32_t; | |
| struct Interval { | |
| Ts start; | |
| Ts end; | |
| Id id; | |
| }; | |
| inline bool IsOverlapping(bool query_is_instant, | |
| Ts s, | |
| Ts e, | |
| const Interval& i) { | |
| bool interval_is_instant = (i.start == i.end); | |
| if (query_is_instant && interval_is_instant) { | |
| return s == i.start; | |
| } | |
| if (query_is_instant) { | |
| return i.start <= s && s < i.end; | |
| } | |
| if (interval_is_instant) { | |
| return s <= i.start && i.start < e; | |
| } | |
| return e > i.start && s < i.end; | |
| } | |
| inline bool IsOverlapping(const Interval& a, const Interval& b) { | |
| return IsOverlapping(a.start == a.end, a.start, a.end, b); | |
| } | |
| // An implementation of a centered interval tree data structure, designed to | |
| // efficiently find all overlap queries on a set of intervals. Centered interval | |
| // tree has a build complexity of O(N*logN) and a query time of O(logN + k), | |
| // where k is the number of overlaps in the dataset. | |
| class IntervalTree { | |
| public: | |
| // Creates an interval tree from the vector of intervals if needed. Otherwise | |
| // copies the vector of intervals. | |
| explicit IntervalTree(const std::vector<Interval>& sorted_intervals) | |
| : IntervalTree(sorted_intervals.data(), sorted_intervals.size()) {} | |
| IntervalTree(const Interval* sorted_intervals, size_t size) { | |
| PERFETTO_CHECK(size > 0); | |
| nodes_.reserve(size); | |
| Node root_node(sorted_intervals, size, nodes_); | |
| nodes_.emplace_back(std::move(root_node)); | |
| root_ = nodes_.size() - 1; | |
| } | |
| // Modifies |res| to contain Interval::Id of all intervals that overlap | |
| // interval (s, e). Has a complexity of O(log(size of tree) + (number of | |
| // overlaps)). | |
| void FindOverlaps(uint64_t s, uint64_t e, std::vector<Id>& res) const { | |
| std::vector<const Node*> stack{nodes_.data() + root_}; | |
| bool query_is_instant = (s == e); | |
| while (!stack.empty()) { | |
| const Node* n = stack.back(); | |
| stack.pop_back(); | |
| for (const Interval& i : n->intervals_) { | |
| // As we know that each interval overlaps the center, if the interval | |
| // starts after the |end| we know [start,end] can't intersect the | |
| // center. | |
| if (i.start > e) { | |
| break; | |
| } | |
| if (IsOverlapping(query_is_instant, s, e, i)) { | |
| res.push_back(i.id); | |
| } | |
| } | |
| if (e > n->center_ && | |
| n->right_node_ != std::numeric_limits<size_t>::max()) { | |
| stack.push_back(&nodes_[n->right_node_]); | |
| } | |
| if (s < n->center_ && | |
| n->left_node_ != std::numeric_limits<size_t>::max()) { | |
| stack.push_back(&nodes_[n->left_node_]); | |
| } | |
| } | |
| } | |
| // Modifies |res| to contain all overlaps (as Intervals) that overlap | |
| // interval (s, e). Has a complexity of O(log(size of tree) + (number of | |
| // overlaps)). | |
| void FindOverlaps(Ts s, Ts e, std::vector<Interval>& res) const { | |
| std::vector<const Node*> stack{nodes_.data() + root_}; | |
| bool query_is_instant = (s == e); | |
| while (!stack.empty()) { | |
| const Node* n = stack.back(); | |
| stack.pop_back(); | |
| for (const Interval& i : n->intervals_) { | |
| // As we know that each interval overlaps the center, if the interval | |
| // starts after the |end| we know [start,end] can't intersect the | |
| // center. | |
| if (i.start > e) { | |
| break; | |
| } | |
| if (IsOverlapping(query_is_instant, s, e, i)) { | |
| Interval new_int; | |
| new_int.id = i.id; | |
| if (query_is_instant) { | |
| new_int.start = s; | |
| new_int.end = s; | |
| } else if (i.start == i.end) { | |
| new_int.start = i.start; | |
| new_int.end = i.start; | |
| } else { | |
| new_int.start = std::max(s, i.start); | |
| new_int.end = std::min(e, i.end); | |
| } | |
| res.push_back(new_int); | |
| } | |
| } | |
| if (e > n->center_ && | |
| n->right_node_ != std::numeric_limits<size_t>::max()) { | |
| stack.push_back(&nodes_[n->right_node_]); | |
| } | |
| if (s < n->center_ && | |
| n->left_node_ != std::numeric_limits<size_t>::max()) { | |
| stack.push_back(&nodes_[n->left_node_]); | |
| } | |
| } | |
| } | |
| private: | |
| struct Node { | |
| base::SmallVector<Interval, 2> intervals_; | |
| uint64_t center_ = 0; | |
| size_t left_node_ = std::numeric_limits<size_t>::max(); | |
| size_t right_node_ = std::numeric_limits<size_t>::max(); | |
| explicit Node(const Interval* intervals, | |
| size_t intervals_size, | |
| std::vector<Node>& nodes) { | |
| const Interval& mid_interval = intervals[intervals_size / 2]; | |
| center_ = (mid_interval.start + mid_interval.end) / 2; | |
| // Find intervals that overlap the center_ and intervals that belong to | |
| // the left node (finish before the center_). If an interval starts | |
| // after the center break and assign all remaining intervals to the | |
| // right node. We can do this as the provided intervals are in sorted | |
| // order. | |
| std::vector<Interval> left; | |
| for (uint32_t i = 0; i < intervals_size; i++) { | |
| const Interval& inter = intervals[i]; | |
| // Starts after the center. As intervals are sorted on timestamp we | |
| // know the rest of intervals will go to the right node. | |
| if (inter.start > center_) { | |
| Node n(intervals + i, intervals_size - i, nodes); | |
| nodes.emplace_back(std::move(n)); | |
| right_node_ = nodes.size() - 1; | |
| break; | |
| } | |
| // Finishes before the center. | |
| if (inter.end < center_) { | |
| left.push_back(intervals[i]); | |
| } else { | |
| // Overlaps the center. | |
| intervals_.emplace_back(intervals[i]); | |
| } | |
| } | |
| if (!left.empty()) { | |
| Node n(left.data(), left.size(), nodes); | |
| nodes.emplace_back(std::move(n)); | |
| left_node_ = nodes.size() - 1; | |
| } | |
| } | |
| Node(const Node&) = delete; | |
| Node& operator=(const Node&) = delete; | |
| Node(Node&&) = default; | |
| Node& operator=(Node&&) = default; | |
| }; | |
| size_t root_; | |
| std::vector<Node> nodes_; | |
| }; | |
| } // namespace perfetto::trace_processor | |
| #endif // SRC_TRACE_PROCESSOR_CONTAINERS_INTERVAL_TREE_H_ |