From: Samuel Just Date: Fri, 20 Jan 2017 20:39:36 +0000 (-0800) Subject: osd_types: generalize PastIntervals to support multiple representations X-Git-Tag: v12.0.3~129^2~37 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f52313c461059fe16272dd7e9c02f5984f292d4c;p=ceph.git osd_types: generalize PastIntervals to support multiple representations Signed-off-by: Samuel Just --- diff --git a/src/osd/OSD.cc b/src/osd/OSD.cc index 8e06a9b609db..42a346aa103b 100644 --- a/src/osd/OSD.cc +++ b/src/osd/OSD.cc @@ -7954,7 +7954,9 @@ void OSD::handle_pg_create(OpRequestRef op) bool mapped = osdmap->get_primary_shard(on, &pgid); assert(mapped); - PastIntervals pi; + PastIntervals pi( + osdmap->get_pools().at(pgid.pool()).ec_pool(), + *osdmap); pg_history_t history; history.epoch_created = created; history.last_scrub_stamp = ci->second; @@ -8554,7 +8556,9 @@ void OSD::handle_pg_query(OpRequestRef op) it->second.epoch_sent, osdmap->get_epoch(), empty), - PastIntervals())); + PastIntervals( + osdmap->get_pools().at(pgid.pool()).ec_pool(), + *osdmap))); } } do_notifies(notify_list, osdmap); diff --git a/src/osd/PG.cc b/src/osd/PG.cc index 2a64601a00c9..051dc34c9e31 100644 --- a/src/osd/PG.cc +++ b/src/osd/PG.cc @@ -235,6 +235,9 @@ PG::PG(OSDService *o, OSDMapRef curmap, coll(p), pg_log(cct), pgmeta_oid(p.make_pgmeta_oid()), missing_loc(this), + past_intervals( + curmap->get_pools().at(p.pgid.pool()).ec_pool(), + *curmap), stat_queue_item(this), scrub_queued(false), recovery_queued(false), @@ -762,21 +765,6 @@ void PG::check_past_interval_bounds() const } } -/* - * Trim past_intervals. - * - * This gets rid of all the past_intervals that happened before last_epoch_clean. - */ -void PG::trim_past_intervals() -{ - past_intervals.trim( - info.history.last_epoch_clean, - [&](const PastIntervals::pg_interval_t &trimmed) { - dout(10) << __func__ << ": trimming " << trimmed << dendl; - dirty_big_info = true; - }); -} - bool PG::adjust_need_up_thru(const OSDMapRef osdmap) { epoch_t up_thru = osdmap->get_up_thru(osd->whoami); @@ -857,7 +845,6 @@ PastIntervals::PriorSet PG::build_prior() pool.info.ec_pool(), get_pgbackend()->get_is_recoverable_predicate(), *get_osdmap(), - past_intervals, up, acting, info, @@ -1966,7 +1953,7 @@ void PG::mark_clean() // strays yet. info.history.last_epoch_clean = get_osdmap()->get_epoch(); - trim_past_intervals(); + past_intervals.clear(); if (is_active()) { /* The check is needed because if we are below min_size we're not @@ -5705,6 +5692,7 @@ void PG::handle_advance_map( << dendl; update_osdmap_ref(osdmap); pool.update(osdmap); + past_intervals.update_type_from_map(pool.info.ec_pool(), *osdmap); if (cct->_conf->osd_debug_verify_cached_snaps) { interval_set actual_removed_snaps; const pg_pool_t *pi = osdmap->get_pg_pool(info.pgid.pool()); diff --git a/src/osd/PG.h b/src/osd/PG.h index 372ec4f5dbed..c61574b4e6bb 100644 --- a/src/osd/PG.h +++ b/src/osd/PG.h @@ -923,8 +923,7 @@ public: } } void check_past_interval_bounds() const; - void trim_past_intervals(); - PastIntervals::PriorSet build_prior(); + PastIntervals::PriorSet build_prior(); void remove_down_peer_info(const OSDMapRef osdmap); diff --git a/src/osd/osd_types.cc b/src/osd/osd_types.cc index df19f9927f9d..9cec5d633055 100644 --- a/src/osd/osd_types.cc +++ b/src/osd/osd_types.cc @@ -2858,152 +2858,168 @@ void PastIntervals::pg_interval_t::generate_test_instances(list& } WRITE_CLASS_ENCODER(PastIntervals::pg_interval_t) -void PastIntervals::encode(bufferlist &bl) const -{ - ::encode(past_intervals, bl); -} -void PastIntervals::decode(bufferlist::iterator &bl) -{ - ::decode(past_intervals, bl); -} +class pi_simple_rep : public PastIntervals::interval_rep { + map interval_map; +public: + size_t size() const override { return interval_map.size(); } + bool empty() const override { return interval_map.empty(); } + void clear() override { interval_map.clear(); } + pair, epoch_t> get_bounds() const override { + auto iter = interval_map.begin(); + if (iter != interval_map.end()) { + auto riter = interval_map.rbegin(); + return make_pair( + make_pair(iter->second.first, iter->second.last + 1), + riter->second.last + 1); + } else { + assert(0 == "get_bounds only valid if !empty()"); + return make_pair(make_pair(0, 0), 0); + } + } + set get_might_have_unfound( + pg_shard_t pg_whoami, + bool ec_pool) const override { + set might_have_unfound; + + // We need to decide who might have unfound objects that we need + auto p = interval_map.rbegin(); + auto end = interval_map.rend(); + for (; p != end; ++p) { + const PastIntervals::pg_interval_t &interval(p->second); + // If nothing changed, we don't care about this interval. + if (!interval.maybe_went_rw) + continue; -void PastIntervals::dump(Formatter *f) const -{ - f->open_array_section("PastIntervals"); - for (auto &&i: past_intervals) { - f->open_object_section("pg_interval_t"); - f->dump_int("epoch", i.first); - f->open_object_section("interval"); - i.second.dump(f); - f->close_section(); + int i = 0; + std::vector::const_iterator a = interval.acting.begin(); + std::vector::const_iterator a_end = interval.acting.end(); + for (; a != a_end; ++a, ++i) { + pg_shard_t shard(*a, ec_pool ? shard_id_t(i) : shard_id_t::NO_SHARD); + if (*a != CRUSH_ITEM_NONE && shard != pg_whoami) + might_have_unfound.insert(shard); + } + } + return might_have_unfound; + } + void add_interval( + bool ec_pool, + const PastIntervals::pg_interval_t &interval) override { + interval_map[interval.first] = interval; + } + unique_ptr clone() const override { + return unique_ptr(new pi_simple_rep(*this)); + } + ostream &print(ostream &out) const override { + return out << interval_map; + } + void encode(bufferlist &bl) const override { + ::encode(interval_map, bl); + } + void decode(bufferlist::iterator &bl) override { + ::decode(interval_map, bl); + } + void dump(Formatter *f) const override { + f->open_array_section("PastIntervals::compat_rep"); + for (auto &&i: interval_map) { + f->open_object_section("pg_interval_t"); + f->dump_int("epoch", i.first); + f->open_object_section("interval"); + i.second.dump(f); + f->close_section(); + f->close_section(); + } f->close_section(); } - f->close_section(); -} + bool is_classic() const override { + return true; + } + static void generate_test_instances(list &o) { + /* todo */ + return; + } + void iterate_mayberw_back_to( + bool ec_pool, + epoch_t les, + std::function &)> &&f) const override { + for (auto i = interval_map.rbegin(); i != interval_map.rend(); ++i) { + if (i->second.last < les) + break; + set actingset; + for (unsigned j = 0; j < i->second.acting.size(); ++j) { + if (i->second.acting[j] == CRUSH_ITEM_NONE) + continue; + actingset.insert( + pg_shard_t( + i->second.acting[j], + ec_pool ? shard_id_t(j) : shard_id_t::NO_SHARD)); + } + f(i->second.first, actingset); + } + } + virtual ~pi_simple_rep() override {} +}; -void PastIntervals::generate_test_instances(list &o) + +PastIntervals::PastIntervals(const PastIntervals &rhs) + : past_intervals(rhs.past_intervals ? + rhs.past_intervals->clone() : + nullptr) {} + +PastIntervals &PastIntervals::operator=(const PastIntervals &rhs) { - /* todo */ - return; + PastIntervals other(rhs); + ::swap(other, *this); + return *this; } ostream& operator<<(ostream& out, const PastIntervals &i) { - return out << i.past_intervals; + assert(i.past_intervals); + return i.past_intervals->print(out); } - -set PastIntervals::get_might_have_unfound( - pg_shard_t pg_whoami, - bool ec_pool) const +void PastIntervals::decode(bufferlist::iterator &bl) { - set might_have_unfound; - - // We need to decide who might have unfound objects that we need - PastIntervals::const_reverse_iterator p = past_intervals.rbegin(); - PastIntervals::const_reverse_iterator end = past_intervals.rend(); - for (; p != end; ++p) { - - const PastIntervals::pg_interval_t &interval(p->second); - // If nothing changed, we don't care about this interval. - if (!interval.maybe_went_rw) - continue; - - int i = 0; - std::vector::const_iterator a = interval.acting.begin(); - std::vector::const_iterator a_end = interval.acting.end(); - for (; a != a_end; ++a, ++i) { - pg_shard_t shard(*a, ec_pool ? shard_id_t(i) : shard_id_t::NO_SHARD); - if (*a != CRUSH_ITEM_NONE && shard != pg_whoami) - might_have_unfound.insert(shard); - } + DECODE_START(1, bl); + __u8 classic = 0; + ::decode(classic, bl); + if (classic) { + past_intervals.reset(new pi_simple_rep); + past_intervals->decode(bl); + } else { + assert(0 == "not implemented"); } - return might_have_unfound; + DECODE_FINISH(bl); } -bool PastIntervals::has_crashed_interval_since( - epoch_t since, - const vector &acting, - const vector &up, - const OSDMap &osdmap, - const DoutPrefixProvider *dpp) const { - bool crashed = false; - - for (PastIntervals::const_reverse_iterator p = past_intervals.rbegin(); - p != past_intervals.rend(); - ++p) { - const PastIntervals::pg_interval_t &interval = p->second; - ldpp_dout(dpp,10) << "may_need_replay " << interval << dendl; - - if (interval.last < since) - break; // we don't care - - if (interval.acting.empty()) - continue; - - if (!interval.maybe_went_rw) - continue; - - // look at whether any of the osds during this interval survived - // past the end of the interval (i.e., didn't crash and - // potentially fail to COMMIT a write that it ACKed). - bool any_survived_interval = false; +void PastIntervals::decode_classic(bufferlist::iterator &bl) +{ + past_intervals.reset(new pi_simple_rep); + past_intervals->decode(bl); +} - // consider ACTING osds - for (unsigned i=0; i &o) +{ + /* todo */ + return; +} - const osd_info_t *pinfo = 0; - if (osdmap.exists(o)) - pinfo = &osdmap.get_info(o); - - // does this osd appear to have survived through the end of the - // interval? - if (pinfo) { - if (pinfo->up_from <= interval.first && pinfo->up_thru > interval.last) { - ldpp_dout(dpp, 10) << "may_need_replay osd." << o - << " up_from " << pinfo->up_from - << " up_thru " << pinfo->up_thru - << " survived the interval" << dendl; - any_survived_interval = true; - } - else if (pinfo->up_from <= interval.first && - (std::find(acting.begin(), acting.end(), o) != acting.end() || - std::find(up.begin(), up.end(), o) != up.end())) { - ldpp_dout(dpp, 10) << "may_need_replay osd." << o - << " up_from " << pinfo->up_from - << " and is in acting|up," - << " assumed to have survived the interval" - << dendl; - // (if it hasn't, we will rebuild PriorSet) - any_survived_interval = true; - } - else if (pinfo->up_from > interval.last && - pinfo->last_clean_begin <= interval.first && - pinfo->last_clean_end > interval.last) { - ldpp_dout(dpp, 10) << "may_need_replay prior osd." << o - << " up_from " << pinfo->up_from - << " and last clean interval [" - << pinfo->last_clean_begin << "," - << pinfo->last_clean_end - << ") survived the interval" << dendl; - any_survived_interval = true; - } - } +void PastIntervals::update_type_from_map(bool ec_pool, const OSDMap &osdmap) +{ + if (!osdmap.test_flag(CEPH_OSDMAP_REQUIRE_LUMINOUS)) { + if (!past_intervals) { + past_intervals.reset(new pi_simple_rep); + } else { + assert(is_classic()); } - - if (!any_survived_interval) { - ldpp_dout(dpp, 3) << "may_need_replay no known survivors of interval " - << interval.first << "-" << interval.last - << ", may need replay" << dendl; - crashed = true; - break; + } else { + if (!past_intervals) { + // inialize new type + } else if (!is_classic()) { + // upgrade } } - return crashed; } bool PastIntervals::is_new_interval( @@ -3129,6 +3145,8 @@ bool PastIntervals::check_new_interval( // NOTE: a change in the up set primary triggers an interval // change, even though the interval members in the pg_interval_t // do not change. + assert(past_intervals); + assert(past_intervals->past_intervals); if (is_new_interval( old_acting_primary, new_acting_primary, @@ -3141,7 +3159,7 @@ bool PastIntervals::check_new_interval( osdmap, lastmap, pgid)) { - pg_interval_t& i = past_intervals->past_intervals[same_interval_since]; + pg_interval_t i; i.first = same_interval_since; i.last = osdmap->get_epoch() - 1; assert(i.first <= i.last); @@ -3210,6 +3228,7 @@ bool PastIntervals::check_new_interval( if (out) *out << __func__ << " " << i << " : acting set is too small" << std::endl; } + past_intervals->past_intervals->add_interval(old_pg_pool.ec_pool(), i); return true; } else { return false; @@ -3217,10 +3236,10 @@ bool PastIntervals::check_new_interval( } PastIntervals::PriorSet::PriorSet( + const PastIntervals &past_intervals, bool ec_pool, IsPGRecoverablePredicate *c, const OSDMap &osdmap, - const PastIntervals &past_intervals, const vector &up, const vector &acting, const pg_info_t &info, @@ -3285,77 +3304,66 @@ PastIntervals::PriorSet::PriorSet( probe.insert(pg_shard_t(up[i], ec_pool ? shard_id_t(i) : shard_id_t::NO_SHARD)); } - for (PastIntervals::const_reverse_iterator p = past_intervals.rbegin(); - p != past_intervals.rend(); - ++p) { - const PastIntervals::pg_interval_t &interval = p->second; - ldpp_dout(dpp, 10) << "build_prior " << interval << dendl; - - if (interval.last < info.history.last_epoch_started) - break; // we don't care - - if (interval.acting.empty()) - continue; - - if (!interval.maybe_went_rw) - continue; - - // look at candidate osds during this interval. each falls into - // one of three categories: up, down (but potentially - // interesting), or lost (down, but we won't wait for it). - set up_now; - bool any_down_now = false; // any candidates down now (that might have useful data) - - // consider ACTING osds - for (unsigned i = 0; i < interval.acting.size(); i++) { - int o = interval.acting[i]; - if (o == CRUSH_ITEM_NONE) - continue; - pg_shard_t so(o, ec_pool ? shard_id_t(i) : shard_id_t::NO_SHARD); - - const osd_info_t *pinfo = 0; - if (osdmap.exists(o)) - pinfo = &osdmap.get_info(o); - - if (osdmap.is_up(o)) { - // include past acting osds if they are up. - probe.insert(so); - up_now.insert(so); - } else if (!pinfo) { - ldpp_dout(dpp, 10) << "build_prior prior osd." << o << " no longer exists" << dendl; - down.insert(o); - } else if (pinfo->lost_at > interval.first) { - ldpp_dout(dpp, 10) << "build_prior prior osd." << o << " is down, but lost_at " << pinfo->lost_at << dendl; - up_now.insert(so); - down.insert(o); - } else { - ldpp_dout(dpp, 10) << "build_prior prior osd." << o << " is down" << dendl; - down.insert(o); - any_down_now = true; + past_intervals.iterate_mayberw_back_to( + ec_pool, + info.history.last_epoch_started, + [&](epoch_t start, const set &acting) { + ldpp_dout(dpp, 10) << "build_prior maybe_rw interval:" << start + << ", acting: " << acting << dendl; + + // look at candidate osds during this interval. each falls into + // one of three categories: up, down (but potentially + // interesting), or lost (down, but we won't wait for it). + set up_now; + // any candidates down now (that might have useful data) + bool any_down_now = false; + + // consider ACTING osds + for (auto &&so: acting) { + const osd_info_t *pinfo = 0; + if (osdmap.exists(so.osd)) + pinfo = &osdmap.get_info(so.osd); + + if (osdmap.is_up(so.osd)) { + // include past acting osds if they are up. + probe.insert(so); + up_now.insert(so); + } else if (!pinfo) { + ldpp_dout(dpp, 10) << "build_prior prior osd." << so.osd + << " no longer exists" << dendl; + down.insert(so.osd); + } else if (pinfo->lost_at > start) { + ldpp_dout(dpp, 10) << "build_prior prior osd." << so.osd + << " is down, but lost_at " << pinfo->lost_at << dendl; + up_now.insert(so); + down.insert(so.osd); + } else { + ldpp_dout(dpp, 10) << "build_prior prior osd." << so.osd + << " is down" << dendl; + down.insert(so.osd); + any_down_now = true; + } } - } - // if not enough osds survived this interval, and we may have gone rw, - // then we need to wait for one of those osds to recover to - // ensure that we haven't lost any information. - if (!(*pcontdec)(up_now) && any_down_now) { - // fixme: how do we identify a "clean" shutdown anyway? - ldpp_dout(dpp, 10) << "build_prior possibly went active+rw, insufficient up;" - << " including down osds" << dendl; - for (vector::const_iterator i = interval.acting.begin(); - i != interval.acting.end(); - ++i) { - if (osdmap.exists(*i) && // if it doesn't exist, we already consider it lost. - osdmap.is_down(*i)) { - pg_down = true; - - // make note of when any down osd in the cur set was lost, so that - // we can notice changes in prior_set_affected. - blocked_by[*i] = osdmap.get_info(*i).lost_at; + // if not enough osds survived this interval, and we may have gone rw, + // then we need to wait for one of those osds to recover to + // ensure that we haven't lost any information. + if (!(*pcontdec)(up_now) && any_down_now) { + // fixme: how do we identify a "clean" shutdown anyway? + ldpp_dout(dpp, 10) << "build_prior possibly went active+rw, insufficient up;" + << " including down osds" << dendl; + for (auto &&so: acting) { + if (osdmap.exists(so.osd) && // if it doesn't exist, we already consider it lost. + osdmap.is_down(so.osd)) { + pg_down = true; + + // make note of when any down osd in the cur set was lost, so that + // we can notice changes in prior_set_affected. + blocked_by[so.osd] = osdmap.get_info(so.osd).lost_at; + } } } - } - } + }); ldpp_dout(dpp, 10) << "build_prior final: probe " << probe << " down " << down diff --git a/src/osd/osd_types.h b/src/osd/osd_types.h index c0ee2f6bd475..938e9c075196 100644 --- a/src/osd/osd_types.h +++ b/src/osd/osd_types.h @@ -2497,13 +2497,75 @@ public: void dump(Formatter *f) const; static void generate_test_instances(list& o); }; + + PastIntervals() = default; + PastIntervals(bool ec_pool, const OSDMap &osdmap) : PastIntervals() { + update_type_from_map(ec_pool, osdmap); + } + PastIntervals(bool ec_pool, bool compact) : PastIntervals() { + update_type(ec_pool, compact); + } + PastIntervals(PastIntervals &&rhs) = default; + PastIntervals &operator=(PastIntervals &&rhs) = default; + + PastIntervals(const PastIntervals &rhs); + PastIntervals &operator=(const PastIntervals &rhs); + + class interval_rep { + public: + virtual size_t size() const = 0; + virtual bool empty() const = 0; + virtual void clear() = 0; + virtual pair, epoch_t> get_bounds() const = 0; + virtual set get_might_have_unfound( + pg_shard_t pg_whoami, + bool ec_pool) const = 0; + virtual void add_interval(bool ec_pool, const pg_interval_t &interval) = 0; + virtual unique_ptr clone() const = 0; + virtual ostream &print(ostream &out) const = 0; + virtual void encode(bufferlist &bl) const = 0; + virtual void decode(bufferlist::iterator &bl) = 0; + virtual void dump(Formatter *f) const = 0; + virtual bool is_classic() const = 0; + virtual void iterate_mayberw_back_to( + bool ec_pool, + epoch_t les, + std::function &)> &&f) const = 0; + virtual ~interval_rep() {} + }; + friend class pi_simple_rep; + friend class pi_compact_rep; private: - map past_intervals; + + unique_ptr past_intervals; public: - void encode(bufferlist &bl) const; + bool is_classic() const { + assert(past_intervals); + return past_intervals->is_classic(); + } + + void encode(bufferlist &bl) const { + assert(past_intervals); + ENCODE_START(1, 1, bl); + __u8 classic = is_classic(); + ::encode(classic, bl); + past_intervals->encode(bl); + ENCODE_FINISH(bl); + } + void encode_classic(bufferlist &bl) const { + assert(past_intervals); + assert(past_intervals->is_classic()); + past_intervals->encode(bl); + } + void decode(bufferlist::iterator &bl); - void dump(Formatter *f) const; + void decode_classic(bufferlist::iterator &bl); + + void dump(Formatter *f) const { + assert(past_intervals); + past_intervals->dump(f); + } static void generate_test_instances(list & o); /** @@ -2571,25 +2633,31 @@ public: friend ostream& operator<<(ostream& out, const PastIntervals &i); template - void trim(epoch_t bound, F &&f) { - PastIntervals::iterator pif = past_intervals.begin(); - PastIntervals::iterator end = past_intervals.end(); - while (pif != end) { - if (pif->second.last >= bound) - return; - f(pif->second); - past_intervals.erase(pif++); - } + void iterate_mayberw_back_to( + bool ec_pool, + epoch_t les, + F &&f) const { + assert(past_intervals); + past_intervals->iterate_mayberw_back_to(ec_pool, les, std::forward(f)); + } + void clear() { + assert(past_intervals); + past_intervals->clear(); } - void clear() { past_intervals.clear(); } /** * Should return a value which gives an indication of the amount * of state contained */ - size_t size() const { return past_intervals.size(); } + size_t size() const { + assert(past_intervals); + return past_intervals->size(); + } - bool empty() const { return past_intervals.empty(); } + bool empty() const { + assert(past_intervals); + return past_intervals->empty(); + } void swap(PastIntervals &other) { ::swap(other.past_intervals, past_intervals); @@ -2601,14 +2669,10 @@ public: */ set get_might_have_unfound( pg_shard_t pg_whoami, - bool ec_pool) const; - - bool has_crashed_interval_since( - epoch_t since, - const vector &acting, - const vector &up, - const OSDMap& osdmap, - const DoutPrefixProvider *dpp) const; + bool ec_pool) const { + assert(past_intervals); + return past_intervals->get_might_have_unfound(pg_whoami, ec_pool); + } /* Return the set of epochs * [(start_interval_start, start_interval_end), end) represented by the @@ -2617,16 +2681,8 @@ public: * the first interval so a user can verify that last_epoch_started falls * within it */ pair, epoch_t> get_bounds() const { - auto iter = past_intervals.begin(); - if (iter != past_intervals.end()) { - auto riter = past_intervals.rbegin(); - return make_pair( - make_pair(iter->second.first, iter->second.last + 1), - riter->second.last + 1); - } else { - assert(0 == "get_bounds only valid if !empty()"); - return make_pair(make_pair(0, 0), 0); - } + assert(past_intervals); + return past_intervals->get_bounds(); } struct PriorSet { @@ -2651,10 +2707,10 @@ public: private: PriorSet( + const PastIntervals &past_intervals, bool ec_pool, IsPGRecoverablePredicate *c, const OSDMap &osdmap, - const PastIntervals &past_intervals, const vector &up, const vector &acting, const pg_info_t &info, @@ -2663,25 +2719,12 @@ public: friend class PastIntervals; }; + void update_type_from_map(const OSDMap &osdmap); + template PriorSet get_prior_set(Args&&... args) const { - return PriorSet(std::forward(args)...); + return PriorSet(*this, std::forward(args)...); } - -private: - using iterator = map::iterator; - using const_iterator = map::const_iterator; - using reverse_iterator = map::reverse_iterator; - using const_reverse_iterator = - map::const_reverse_iterator; - iterator begin() { return past_intervals.begin(); } - iterator end() { return past_intervals.end(); } - const_iterator begin() const { return past_intervals.begin(); } - const_iterator end() const { return past_intervals.end(); } - reverse_iterator rbegin() { return past_intervals.rbegin(); } - reverse_iterator rend() { return past_intervals.rend(); } - const_reverse_iterator rbegin() const { return past_intervals.rbegin(); } - const_reverse_iterator rend() const { return past_intervals.rend(); } }; WRITE_CLASS_ENCODER(PastIntervals)