OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #ifndef BASE_TRACKED_OBJECTS_H_ | 5 #ifndef BASE_TRACKED_OBJECTS_H_ |
6 #define BASE_TRACKED_OBJECTS_H_ | 6 #define BASE_TRACKED_OBJECTS_H_ |
7 #pragma once | 7 #pragma once |
8 | 8 |
9 #include <map> | 9 #include <map> |
10 #include <stack> | 10 #include <stack> |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
130 // purposes, we need to construct Snapshot instances for each combination of | 130 // purposes, we need to construct Snapshot instances for each combination of |
131 // birth thread, death thread, and location, along with the count of such | 131 // birth thread, death thread, and location, along with the count of such |
132 // lifetimes. We gather such data into a Snapshot instances, so that such | 132 // lifetimes. We gather such data into a Snapshot instances, so that such |
133 // instances can be sorted and aggregated (and remain frozen during our | 133 // instances can be sorted and aggregated (and remain frozen during our |
134 // processing). Snapshot instances use pointers to constant portions of the | 134 // processing). Snapshot instances use pointers to constant portions of the |
135 // birth and death datastructures, but have local (frozen) copies of the actual | 135 // birth and death datastructures, but have local (frozen) copies of the actual |
136 // statistics (birth count, durations, etc. etc.). | 136 // statistics (birth count, durations, etc. etc.). |
137 // | 137 // |
138 // A DataCollector is a container object that holds a set of Snapshots. The | 138 // A DataCollector is a container object that holds a set of Snapshots. The |
139 // statistics in a snapshot are gathered asynhcronously relative to their | 139 // statistics in a snapshot are gathered asynhcronously relative to their |
140 // ongoing updates. It is possible, though highly unlikely, that stats such | 140 // ongoing updates. It is possible, though highly unlikely, that stats could be |
141 // as a 64bit counter could be incorrectly recorded by this process. The | 141 // incorrectly recorded by this process (all data is held in 32 bit ints, but we |
| 142 // are not atomically collecting all data, so we could have count that does not, |
| 143 // for example, match with the number of durations we accumulated). The |
142 // advantage to having fast (non-atomic) updates of the data outweighs the | 144 // advantage to having fast (non-atomic) updates of the data outweighs the |
143 // minimal risk of a singular corrupt statistic snapshot (only the snapshot | 145 // minimal risk of a singular corrupt statistic snapshot (only the snapshot |
144 // could be corrupt, not the underlying and ongoing statistic). In constrast, | 146 // could be corrupt, not the underlying and ongoing statistic). In constrast, |
145 // pointer data that is accessed during snapshotting is completely invariant, | 147 // pointer data that is accessed during snapshotting is completely invariant, |
146 // and hence is perfectly acquired (i.e., no potential corruption, and no risk | 148 // and hence is perfectly acquired (i.e., no potential corruption, and no risk |
147 // of a bad memory reference). | 149 // of a bad memory reference). |
148 // | 150 // |
149 // After an array of Snapshots instances are collected into a DataCollector, | 151 // After an array of Snapshots instances are collected into a DataCollector, |
150 // they need to be prepared for displaying our output. We currently implement a | 152 // they need to be prepared for displaying our output. We currently implement a |
151 // direct rendering to HTML, but we will soon have a JSON serialization as well. | 153 // serialization into a Value hierarchy, which is automatically translated to |
152 | 154 // JSON when supplied to rendering Java Scirpt. |
153 // For direct HTML display, the data must be sorted, and possibly aggregated | |
154 // (example: how many threads are in a specific consecutive set of Snapshots? | |
155 // What was the total birth count for that set? etc.). Aggregation instances | |
156 // collect running sums of any set of snapshot instances, and are used to print | |
157 // sub-totals in an about:profiler page. | |
158 // | 155 // |
159 // TODO(jar): I need to store DataCollections, and provide facilities for taking | 156 // TODO(jar): We can implement a Snapshot system that *tries* to grab the |
160 // the difference between two gathered DataCollections. For now, I'm just | 157 // snapshots on the source threads *when* they have MessageLoops available |
161 // adding a hack that Reset()s to zero all counts and stats. This is also | 158 // (worker threads don't have message loops generally, and hence gathering from |
| 159 // them will continue to be asynchronous). We had an implementation of this in |
| 160 // the past, but the difficulty is dealing with message loops being terminated. |
| 161 // We can *try* to spam the available threads via some message loop proxy to |
| 162 // achieve this feat, and it *might* be valuable when we are colecting data for |
| 163 // upload via UMA (where correctness of data may be more significant than for a |
| 164 // single screen of about:profiler). |
| 165 // |
| 166 // TODO(jar): We need to save a single sample in each DeathData instance of the |
| 167 // times recorded. This sample should be selected in a uniformly random way. |
| 168 // |
| 169 // TODO(jar): We should support (optionally) the recording of parent-child |
| 170 // relationships for tasks. This should be done by detecting what tasks are |
| 171 // Born during the running of a parent task. The resulting data can be used by |
| 172 // a smarter profiler to aggregate the cost of a series of child tasks into |
| 173 // the ancestor task. It can also be used to illuminate what child or parent is |
| 174 // related to each task. |
| 175 // |
| 176 // TODO(jar): We need to store DataCollections, and provide facilities for |
| 177 // taking the difference between two gathered DataCollections. For now, we're |
| 178 // just adding a hack that Reset()s to zero all counts and stats. This is also |
162 // done in a slighly thread-unsafe fashion, as the resetting is done | 179 // done in a slighly thread-unsafe fashion, as the resetting is done |
163 // asynchronously relative to ongoing updates (but all data is 32 bit in size). | 180 // asynchronously relative to ongoing updates (but all data is 32 bit in size). |
164 // For basic profiling, this will work "most of the time," and should be | 181 // For basic profiling, this will work "most of the time," and should be |
165 // sufficient... but storing away DataCollections is the "right way" to do this. | 182 // sufficient... but storing away DataCollections is the "right way" to do this. |
166 // We'll accomplish this via JavaScript storage of snapshots, and then we'll | 183 // We'll accomplish this via JavaScript storage of snapshots, and then we'll |
167 // remove the Reset() methods. | 184 // remove the Reset() methods. We may also need a short-term-max value in |
| 185 // DeathData that is reset (as synchronously as possible) during each snapshot. |
| 186 // This will facilitate displaying a max value for each snapshot period. |
168 | 187 |
169 class MessageLoop; | 188 class MessageLoop; |
170 | 189 |
171 namespace tracked_objects { | 190 namespace tracked_objects { |
172 | 191 |
173 //------------------------------------------------------------------------------ | 192 //------------------------------------------------------------------------------ |
174 // For a specific thread, and a specific birth place, the collection of all | 193 // For a specific thread, and a specific birth place, the collection of all |
175 // death info (with tallies for each death thread, to prevent access conflicts). | 194 // death info (with tallies for each death thread, to prevent access conflicts). |
176 class ThreadData; | 195 class ThreadData; |
177 class BASE_EXPORT BirthOnThread { | 196 class BASE_EXPORT BirthOnThread { |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
247 DurationInt AverageMsRunDuration() const; | 266 DurationInt AverageMsRunDuration() const; |
248 DurationInt run_duration_max() const { return run_time_.max(); } | 267 DurationInt run_duration_max() const { return run_time_.max(); } |
249 DurationInt queue_duration() const { return queue_time_.duration(); } | 268 DurationInt queue_duration() const { return queue_time_.duration(); } |
250 DurationInt AverageMsQueueDuration() const; | 269 DurationInt AverageMsQueueDuration() const; |
251 DurationInt queue_duration_max() const { return queue_time_.max(); } | 270 DurationInt queue_duration_max() const { return queue_time_.max(); } |
252 | 271 |
253 // Accumulate metrics from other into this. This method is never used on | 272 // Accumulate metrics from other into this. This method is never used on |
254 // realtime statistics, and only used in snapshots and aggregatinos. | 273 // realtime statistics, and only used in snapshots and aggregatinos. |
255 void AddDeathData(const DeathData& other); | 274 void AddDeathData(const DeathData& other); |
256 | 275 |
257 // Simple print of internal state for use in line of HTML. | |
258 void WriteHTML(std::string* output) const; | |
259 | |
260 // Construct a DictionaryValue instance containing all our stats. The caller | 276 // Construct a DictionaryValue instance containing all our stats. The caller |
261 // assumes ownership of the returned instance. | 277 // assumes ownership of the returned instance. |
262 base::DictionaryValue* ToValue() const; | 278 base::DictionaryValue* ToValue() const; |
263 | 279 |
264 // Reset all tallies to zero. This is used as a hack on realtime data. | 280 // Reset all tallies to zero. This is used as a hack on realtime data. |
265 void Clear(); | 281 void Clear(); |
266 | 282 |
267 private: | 283 private: |
268 // DeathData::Data is a helper class, useful when different metrics need to be | 284 // DeathData::Data is a helper class, useful when different metrics need to be |
269 // aggregated, such as queueing times, or run times. | 285 // aggregated, such as queueing times, or run times. |
270 class Data { | 286 class Data { |
271 public: | 287 public: |
272 Data() : duration_(0), max_(0) {} | 288 Data() : duration_(0), max_(0) {} |
273 ~Data() {} | 289 ~Data() {} |
274 | 290 |
275 DurationInt duration() const { return duration_; } | 291 DurationInt duration() const { return duration_; } |
276 DurationInt max() const { return max_; } | 292 DurationInt max() const { return max_; } |
277 | 293 |
278 // Emits HTML formated description of members, assuming |count| instances | |
279 // when calculating averages. | |
280 void WriteHTML(int count, std::string* output) const; | |
281 | |
282 // Agggegate data into our state. | 294 // Agggegate data into our state. |
283 void AddData(const Data& other); | 295 void AddData(const Data& other); |
284 void AddDuration(DurationInt duration); | 296 void AddDuration(DurationInt duration); |
285 | 297 |
286 // Central helper function for calculating averages (correctly, in only one | 298 // Central helper function for calculating averages (correctly, in only one |
287 // place). | 299 // place). |
288 DurationInt AverageMsDuration(int count) const; | 300 DurationInt AverageMsDuration(int count) const; |
289 | 301 |
290 // Resets all members to zero. | 302 // Resets all members to zero. |
291 void Clear(); | 303 void Clear(); |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
479 // The following functions should all be private, and are only public because | 491 // The following functions should all be private, and are only public because |
480 // the collection is done externally. We need to relocate that code from the | 492 // the collection is done externally. We need to relocate that code from the |
481 // collection class into this class, and then all these methods can be made | 493 // collection class into this class, and then all these methods can be made |
482 // private. | 494 // private. |
483 // (Thread safe) Get start of list of all ThreadData instances. | 495 // (Thread safe) Get start of list of all ThreadData instances. |
484 static ThreadData* first(); | 496 static ThreadData* first(); |
485 // Iterate through the null terminated list of ThreadData instances. | 497 // Iterate through the null terminated list of ThreadData instances. |
486 ThreadData* next() const { return next_; } | 498 ThreadData* next() const { return next_; } |
487 // Using our lock, make a copy of the specified maps. These calls may arrive | 499 // Using our lock, make a copy of the specified maps. These calls may arrive |
488 // from non-local threads, and are used to quickly scan data from all threads | 500 // from non-local threads, and are used to quickly scan data from all threads |
489 // in order to build an HTML page for about:profiler. | 501 // in order to build JSON for about:profiler. |
490 void SnapshotBirthMap(BirthMap *output) const; | 502 void SnapshotBirthMap(BirthMap *output) const; |
491 void SnapshotDeathMap(DeathMap *output) const; | 503 void SnapshotDeathMap(DeathMap *output) const; |
492 // -------- end of should be private methods. | 504 // -------- end of should be private methods. |
493 | 505 |
494 // Hack: asynchronously clear all birth counts and death tallies data values | 506 // Hack: asynchronously clear all birth counts and death tallies data values |
495 // in all ThreadData instances. The numerical (zeroing) part is done without | 507 // in all ThreadData instances. The numerical (zeroing) part is done without |
496 // use of a locks or atomics exchanges, and may (for int64 values) produce | 508 // use of a locks or atomics exchanges, and may (for int64 values) produce |
497 // bogus counts VERY rarely. | 509 // bogus counts VERY rarely. |
498 static void ResetAllThreadData(); | 510 static void ResetAllThreadData(); |
499 | 511 |
(...skipping 18 matching lines...) Expand all Loading... |
518 // the profiler enabled. It will generally be optimized away when it is | 530 // the profiler enabled. It will generally be optimized away when it is |
519 // ifdef'ed to be small enough (allowing the profiler to be "compiled out" of | 531 // ifdef'ed to be small enough (allowing the profiler to be "compiled out" of |
520 // the code). | 532 // the code). |
521 static TrackedTime Now(); | 533 static TrackedTime Now(); |
522 | 534 |
523 private: | 535 private: |
524 // Allow only tests to call ShutdownSingleThreadedCleanup. We NEVER call it | 536 // Allow only tests to call ShutdownSingleThreadedCleanup. We NEVER call it |
525 // in production code. | 537 // in production code. |
526 friend class TrackedObjectsTest; | 538 friend class TrackedObjectsTest; |
527 | 539 |
528 // Implment a stack that avoids allocations during a push() by having enough | |
529 // space ahead of time. | |
530 class ThreadDataPool { | |
531 public: | |
532 ThreadDataPool(); | |
533 ~ThreadDataPool(); | |
534 | |
535 // Make sure the stack is large enough to support the indicated number of | |
536 // elements. | |
537 void reserve(size_t largest_worker_pool_number); | |
538 | |
539 bool empty() const; | |
540 const ThreadData* top() const; | |
541 void push(const ThreadData* thread_data); | |
542 void pop(); | |
543 | |
544 private: | |
545 std::vector<const ThreadData*> stack_; | |
546 size_t empty_slot_; | |
547 DISALLOW_COPY_AND_ASSIGN(ThreadDataPool); | |
548 }; | |
549 | |
550 // Worker thread construction creates a name since there is none. | 540 // Worker thread construction creates a name since there is none. |
551 explicit ThreadData(size_t thread_number); | 541 explicit ThreadData(int thread_number); |
552 | 542 |
553 // Message loop based construction should provide a name. | 543 // Message loop based construction should provide a name. |
554 explicit ThreadData(const std::string& suggested_name); | 544 explicit ThreadData(const std::string& suggested_name); |
555 | 545 |
556 ~ThreadData(); | 546 ~ThreadData(); |
557 | 547 |
558 // Push this instance to the head of all_thread_data_list_head_, linking it to | 548 // Push this instance to the head of all_thread_data_list_head_, linking it to |
559 // the previous head. This is performed after each construction, and leaves | 549 // the previous head. This is performed after each construction, and leaves |
560 // the instance permanently on that list. | 550 // the instance permanently on that list. |
561 void PushToHeadOfList(); | 551 void PushToHeadOfList(); |
562 | 552 |
563 // In this thread's data, record a new birth. | 553 // In this thread's data, record a new birth. |
564 Births* TallyABirth(const Location& location); | 554 Births* TallyABirth(const Location& location); |
565 | 555 |
566 // Find a place to record a death on this thread. | 556 // Find a place to record a death on this thread. |
567 void TallyADeath(const Births& birth, | 557 void TallyADeath(const Births& birth, |
568 DurationInt queue_duration, | 558 DurationInt queue_duration, |
569 DurationInt duration); | 559 DurationInt duration); |
570 | 560 |
571 // Using our lock to protect the iteration, Clear all birth and death data. | 561 // Using our lock to protect the iteration, Clear all birth and death data. |
572 void Reset(); | 562 void Reset(); |
573 | 563 |
574 // This method is called by the TLS system when a thread terminates. | 564 // This method is called by the TLS system when a thread terminates. |
575 // The argument may be NULL if this thread has never tracked a birth or death. | 565 // The argument may be NULL if this thread has never tracked a birth or death. |
576 static void OnThreadTermination(void* thread_data); | 566 static void OnThreadTermination(void* thread_data); |
577 | 567 |
578 // This method should be called when a worker thread terminates, so that we | 568 // This method should be called when a worker thread terminates, so that we |
579 // can save all the thread data into a cache of reusable ThreadData instances. | 569 // can save all the thread data into a cache of reusable ThreadData instances. |
580 void OnThreadTerminationCleanup() const; | 570 void OnThreadTerminationCleanup(); |
581 | 571 |
582 // Cleans up data structures, and returns statics to near pristine (mostly | 572 // Cleans up data structures, and returns statics to near pristine (mostly |
583 // uninitialized) state. If there is any chance that other threads are still | 573 // uninitialized) state. If there is any chance that other threads are still |
584 // using the data structures, then the |leak| argument should be passed in as | 574 // using the data structures, then the |leak| argument should be passed in as |
585 // true, and the data structures (birth maps, death maps, ThreadData | 575 // true, and the data structures (birth maps, death maps, ThreadData |
586 // insntances, etc.) will be leaked and not deleted. If you have joined all | 576 // insntances, etc.) will be leaked and not deleted. If you have joined all |
587 // threads since the time that InitializeAndSetTrackingStatus() was called, | 577 // threads since the time that InitializeAndSetTrackingStatus() was called, |
588 // then you can pass in a |leak| value of false, and this function will | 578 // then you can pass in a |leak| value of false, and this function will |
589 // delete recursively all data structures, starting with the list of | 579 // delete recursively all data structures, starting with the list of |
590 // ThreadData instances. | 580 // ThreadData instances. |
591 static void ShutdownSingleThreadedCleanup(bool leak); | 581 static void ShutdownSingleThreadedCleanup(bool leak); |
592 | 582 |
593 // We use thread local store to identify which ThreadData to interact with. | 583 // We use thread local store to identify which ThreadData to interact with. |
594 static base::ThreadLocalStorage::Slot tls_index_; | 584 static base::ThreadLocalStorage::Slot tls_index_; |
595 | 585 |
| 586 // List of ThreadData instances for use with worker threads. When a worker |
| 587 // thread is done (terminated), we push it onto this llist. When a new worker |
| 588 // thread is created, we first try to re-use a ThreadData instance from the |
| 589 // list, and if none are available, construct a new one. |
| 590 // This is only accessed while list_lock_ is held. |
| 591 static ThreadData* first_retired_worker_; |
| 592 |
596 // Link to the most recently created instance (starts a null terminated list). | 593 // Link to the most recently created instance (starts a null terminated list). |
597 // The list is traversed by about:profiler when it needs to snapshot data. | 594 // The list is traversed by about:profiler when it needs to snapshot data. |
598 // This is only accessed while list_lock_ is held. | 595 // This is only accessed while list_lock_ is held. |
599 static ThreadData* all_thread_data_list_head_; | 596 static ThreadData* all_thread_data_list_head_; |
600 // Set of ThreadData instances for use with worker threads. When a worker | |
601 // thread is done (terminating), we push it into this pool. When a new worker | |
602 // thread is created, we first try to re-use a ThreadData instance from the | |
603 // pool, and if none are available, construct a new one. | |
604 // This is only accessed while list_lock_ is held. | |
605 static ThreadDataPool* unregistered_thread_data_pool_; | |
606 // The next available thread number. This should only be accessed when the | 597 // The next available thread number. This should only be accessed when the |
607 // list_lock_ is held. | 598 // list_lock_ is held. |
608 static int thread_number_counter_; | 599 static int thread_number_counter_; |
609 // Incarnation sequence number, indicating how many times (during unittests) | 600 // Incarnation sequence number, indicating how many times (during unittests) |
610 // we've either transitioned out of UNINITIALIZED, or into that state. This | 601 // we've either transitioned out of UNINITIALIZED, or into that state. This |
611 // value is only accessed while the list_lock_ is held. | 602 // value is only accessed while the list_lock_ is held. |
612 static int incarnation_counter_; | 603 static int incarnation_counter_; |
613 // Protection for access to all_thread_data_list_head_, and to | 604 // Protection for access to all_thread_data_list_head_, and to |
614 // unregistered_thread_data_pool_. This lock is leaked at shutdown. | 605 // unregistered_thread_data_pool_. This lock is leaked at shutdown. |
615 // The lock is very infrequently used, so we can afford to just make a lazy | 606 // The lock is very infrequently used, so we can afford to just make a lazy |
616 // instance and be safe. | 607 // instance and be safe. |
617 static base::LazyInstance<base::Lock, | 608 static base::LazyInstance<base::Lock, |
618 base::LeakyLazyInstanceTraits<base::Lock> > list_lock_; | 609 base::LeakyLazyInstanceTraits<base::Lock> > list_lock_; |
619 | 610 |
620 // Record of what the incarnation_counter_ was when this instance was created. | 611 // Record of what the incarnation_counter_ was when this instance was created. |
621 // If the incarnation_counter_ has changed, then we avoid pushing into the | 612 // If the incarnation_counter_ has changed, then we avoid pushing into the |
622 // pool (this is only critical in tests which go through multiple | 613 // pool (this is only critical in tests which go through multiple |
623 // incarations). | 614 // incarations). |
624 int incarnation_count_for_pool_; | 615 int incarnation_count_for_pool_; |
625 | 616 |
626 // We set status_ to SHUTDOWN when we shut down the tracking service. | 617 // We set status_ to SHUTDOWN when we shut down the tracking service. |
627 static Status status_; | 618 static Status status_; |
628 | 619 |
629 // Link to next instance (null terminated list). Used to globally track all | 620 // Link to next instance (null terminated list). Used to globally track all |
630 // registered instances (corresponds to all registered threads where we keep | 621 // registered instances (corresponds to all registered threads where we keep |
631 // data). | 622 // data). |
632 ThreadData* next_; | 623 ThreadData* next_; |
633 | 624 |
| 625 // Pointer to another ThreadData instance for a Worker-Thread that has been |
| 626 // retired (its thread was terminated). This value is non-NULL only for a |
| 627 // retired ThreadData associated with a Worker-Thread. |
| 628 ThreadData* next_retired_worker_; |
| 629 |
634 // The name of the thread that is being recorded. If this thread has no | 630 // The name of the thread that is being recorded. If this thread has no |
635 // message_loop, then this is a worker thread, with a sequence number postfix. | 631 // message_loop, then this is a worker thread, with a sequence number postfix. |
636 std::string thread_name_; | 632 std::string thread_name_; |
637 | 633 |
638 // Indicate if this is a worker thread, and the ThreadData contexts should be | 634 // Indicate if this is a worker thread, and the ThreadData contexts should be |
639 // stored in the unregistered_thread_data_pool_ when not in use. | 635 // stored in the unregistered_thread_data_pool_ when not in use. |
640 // Value is zero when it is not a worker thread. Value is a positive integer | 636 // Value is zero when it is not a worker thread. Value is a positive integer |
641 // corresponding to the created thread name if it is a worker thread. | 637 // corresponding to the created thread name if it is a worker thread. |
642 size_t worker_thread_number_; | 638 int worker_thread_number_; |
643 | 639 |
644 // A map used on each thread to keep track of Births on this thread. | 640 // A map used on each thread to keep track of Births on this thread. |
645 // This map should only be accessed on the thread it was constructed on. | 641 // This map should only be accessed on the thread it was constructed on. |
646 // When a snapshot is needed, this structure can be locked in place for the | 642 // When a snapshot is needed, this structure can be locked in place for the |
647 // duration of the snapshotting activity. | 643 // duration of the snapshotting activity. |
648 BirthMap birth_map_; | 644 BirthMap birth_map_; |
649 | 645 |
650 // Similar to birth_map_, this records informations about death of tracked | 646 // Similar to birth_map_, this records informations about death of tracked |
651 // instances (i.e., when a tracked instance was destroyed on this thread). | 647 // instances (i.e., when a tracked instance was destroyed on this thread). |
652 // It is locked before changing, and hence other threads may access it by | 648 // It is locked before changing, and hence other threads may access it by |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
684 } | 680 } |
685 | 681 |
686 private: | 682 private: |
687 | 683 |
688 DISALLOW_COPY_AND_ASSIGN(AutoTracking); | 684 DISALLOW_COPY_AND_ASSIGN(AutoTracking); |
689 }; | 685 }; |
690 | 686 |
691 } // namespace tracked_objects | 687 } // namespace tracked_objects |
692 | 688 |
693 #endif // BASE_TRACKED_OBJECTS_H_ | 689 #endif // BASE_TRACKED_OBJECTS_H_ |
OLD | NEW |