// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "athena/resource_manager/public/resource_manager.h" #include #include #include "athena/activity/public/activity.h" #include "athena/activity/public/activity_manager.h" #include "athena/activity/public/activity_manager_observer.h" #include "athena/resource_manager/memory_pressure_notifier.h" #include "athena/resource_manager/public/resource_manager_delegate.h" #include "athena/wm/public/window_list_provider.h" #include "athena/wm/public/window_list_provider_observer.h" #include "athena/wm/public/window_manager.h" #include "athena/wm/public/window_manager_observer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/time/time.h" #include "ui/aura/window.h" namespace athena { namespace { class ResourceManagerImpl : public ResourceManager, public WindowManagerObserver, public ActivityManagerObserver, public MemoryPressureObserver, public WindowListProviderObserver { public: ResourceManagerImpl(ResourceManagerDelegate* delegate); virtual ~ResourceManagerImpl(); // ResourceManager: virtual void SetMemoryPressureAndStopMonitoring( MemoryPressureObserver::MemoryPressure pressure) OVERRIDE; virtual void SetWaitTimeBetweenResourceManageCalls(int time_in_ms) OVERRIDE { wait_time_for_resource_deallocation_ = base::TimeDelta::FromMilliseconds(time_in_ms); // Reset the timeout to force the next resource call to execute immediately. next_resource_management_time_ = base::Time::Now(); } virtual void Pause(bool pause) OVERRIDE { if (pause) { if (!pause_) queued_command_ = false; ++pause_; } else { DCHECK(pause_); --pause_; if (!pause && queued_command_) { UpdateActivityOrder(); ManageResource(); } } } // ActivityManagerObserver: virtual void OnActivityStarted(Activity* activity) OVERRIDE; virtual void OnActivityEnding(Activity* activity) OVERRIDE; // WindowManagerObserver: virtual void OnOverviewModeEnter() OVERRIDE; virtual void OnOverviewModeExit() OVERRIDE; virtual void OnSplitViewModeEnter() OVERRIDE; virtual void OnSplitViewModeExit() OVERRIDE; // MemoryPressureObserver: virtual void OnMemoryPressure( MemoryPressureObserver::MemoryPressure pressure) OVERRIDE; virtual ResourceManagerDelegate* GetDelegate() OVERRIDE; // WindowListProviderObserver: virtual void OnWindowStackingChanged() OVERRIDE; virtual void OnWindowRemoved(aura::Window* removed_window, int index) OVERRIDE; private: // Manage the resources for our activities. void ManageResource(); // Check that the visibility of activities is properly set. void UpdateVisibilityStates(); // Check if activities can be unloaded to reduce memory pressure. void TryToUnloadAnActivity(); // Order our activity list to the order of activities of the stream. // TODO(skuhne): Once the ActivityManager is responsible to create this list // for us, we can remove this code here. void UpdateActivityOrder(); // Resources were released and a quiet period is needed before we release // more since it takes a while to trickle through the system. void OnResourcesReleased(); // The memory pressure has increased, previously applied measures did not show // effect and immediate action is required. void OnMemoryPressureIncreased(); // Returns true when the previous memory release was long enough ago to try // unloading another activity. bool AllowedToUnloadActivity(); // The sorted (new(front) -> old(back)) activity list. // TODO(skuhne): Once the ActivityManager is responsible to create this list // for us, we can remove this code here. std::vector activity_list_; // The resource manager delegate. scoped_ptr delegate_; // Keeping a reference to the current memory pressure. MemoryPressureObserver::MemoryPressure current_memory_pressure_; // The memory pressure notifier. scoped_ptr memory_pressure_notifier_; // A ref counter. As long as not 0, the management is on hold. int pause_; // If true, a command came in while the resource manager was paused. bool queued_command_; // Used by ManageResource() to determine an activity state change while it // changes Activity properties. bool activity_order_changed_; // True if in overview mode - activity order changes will be ignored if true // and postponed till after the overview mode is ending. bool in_overview_mode_; // True if we are in split view mode. bool in_split_view_mode_; // The last time the resource manager was called to release resources. // Avoid too aggressive resource de-allocation by enforcing a wait time of // |wait_time_for_resource_deallocation_| between executed calls. base::Time next_resource_management_time_; // The wait time between two resource managing executions. base::TimeDelta wait_time_for_resource_deallocation_; DISALLOW_COPY_AND_ASSIGN(ResourceManagerImpl); }; namespace { ResourceManagerImpl* instance = NULL; // We allow this many activities to be visible. All others must be at state of // invisible or below. const int kMaxVisibleActivities = 3; } // namespace ResourceManagerImpl::ResourceManagerImpl(ResourceManagerDelegate* delegate) : delegate_(delegate), current_memory_pressure_(MemoryPressureObserver::MEMORY_PRESSURE_UNKNOWN), memory_pressure_notifier_(new MemoryPressureNotifier(this)), pause_(false), queued_command_(false), activity_order_changed_(false), in_overview_mode_(false), in_split_view_mode_(false), next_resource_management_time_(base::Time::Now()), wait_time_for_resource_deallocation_(base::TimeDelta::FromMilliseconds( delegate_->MemoryPressureIntervalInMS())) { WindowManager::Get()->AddObserver(this); WindowManager::Get()->GetWindowListProvider()->AddObserver(this); ActivityManager::Get()->AddObserver(this); } ResourceManagerImpl::~ResourceManagerImpl() { ActivityManager::Get()->RemoveObserver(this); WindowManager::Get()->GetWindowListProvider()->RemoveObserver(this); WindowManager::Get()->RemoveObserver(this); while (!activity_list_.empty()) OnActivityEnding(activity_list_.front()); } void ResourceManagerImpl::SetMemoryPressureAndStopMonitoring( MemoryPressureObserver::MemoryPressure pressure) { memory_pressure_notifier_->StopObserving(); OnMemoryPressure(pressure); } void ResourceManagerImpl::OnActivityStarted(Activity* activity) { // As long as we have to manage the list of activities ourselves, we need to // order it here. activity_list_.push_back(activity); UpdateActivityOrder(); // Update the activity states. ManageResource(); // Remember that the activity order has changed. activity_order_changed_ = true; } void ResourceManagerImpl::OnActivityEnding(Activity* activity) { DCHECK(activity->GetWindow()); // Remove the activity from the list again. std::vector::iterator it = std::find(activity_list_.begin(), activity_list_.end(), activity); DCHECK(it != activity_list_.end()); activity_list_.erase(it); // Remember that the activity order has changed. activity_order_changed_ = true; } void ResourceManagerImpl::OnOverviewModeEnter() { in_overview_mode_ = true; } void ResourceManagerImpl::OnOverviewModeExit() { in_overview_mode_ = false; // Reorder the activities and manage the resources again since an order change // might have caused a visibility change. UpdateActivityOrder(); ManageResource(); } void ResourceManagerImpl::OnSplitViewModeEnter() { // Re-apply the memory pressure to make sure enough items are visible. in_split_view_mode_ = true; ManageResource(); } void ResourceManagerImpl::OnSplitViewModeExit() { // We don't do immediately something yet. The next ManageResource call will // come soon. in_split_view_mode_ = false; } void ResourceManagerImpl::OnWindowStackingChanged() { activity_order_changed_ = true; if (pause_) { queued_command_ = true; return; } // No need to do anything while being in overview mode. if (in_overview_mode_) return; // As long as we have to manage the list of activities ourselves, we need to // order it here. UpdateActivityOrder(); // Manage the resources of each activity. ManageResource(); } void ResourceManagerImpl::OnWindowRemoved(aura::Window* removed_window, int index) { } void ResourceManagerImpl::OnMemoryPressure( MemoryPressureObserver::MemoryPressure pressure) { if (pressure > current_memory_pressure_) OnMemoryPressureIncreased(); current_memory_pressure_ = pressure; ManageResource(); } ResourceManagerDelegate* ResourceManagerImpl::GetDelegate() { return delegate_.get(); } void ResourceManagerImpl::ManageResource() { // If there is none or only one app running we cannot do anything. if (activity_list_.size() <= 1U) return; if (pause_) { queued_command_ = true; return; } // Check that the visibility of items is properly set. Note that this might // already trigger a release of resources. If this happens, // AllowedToUnloadActivity() will return false. UpdateVisibilityStates(); // Since resource deallocation takes time, we avoid to release more resources // in short succession. Note that we come here periodically and if one call // is not triggering an unload, the next one will. if (AllowedToUnloadActivity()) TryToUnloadAnActivity(); } void ResourceManagerImpl::UpdateVisibilityStates() { // The first n activities should be treated as "visible", means they updated // in overview mode and will keep their layer resources for faster switch // times. Usually we use |kMaxVisibleActivities| items, but when the memory // pressure gets critical we only hold as many as are really visible. size_t max_activities = kMaxVisibleActivities; if (current_memory_pressure_ == MEMORY_PRESSURE_CRITICAL) max_activities = in_split_view_mode_ ? 2 : 1; // Restart and / or bail if the order of activities changes due to our calls. activity_order_changed_ = false; // Change the visibility of our activities in a pre-processing step. This is // required since it might change the order/number of activities. size_t index = 0; while (index < activity_list_.size()) { Activity* activity = activity_list_[index]; Activity::ActivityState state = activity->GetCurrentState(); // The first |kMaxVisibleActivities| entries should be visible, all others // invisible or at a lower activity state. if (index < max_activities || (state == Activity::ACTIVITY_INVISIBLE || state == Activity::ACTIVITY_VISIBLE)) { Activity::ActivityState visiblity_state = index < max_activities ? Activity::ACTIVITY_VISIBLE : Activity::ACTIVITY_INVISIBLE; // Only change the state when it changes. Note that when the memory // pressure is critical, only the primary activities (1 or 2) are made // visible. Furthermore, in relaxed mode we only want to turn visible, // never invisible. if (visiblity_state != state && (current_memory_pressure_ != MEMORY_PRESSURE_LOW || visiblity_state == Activity::ACTIVITY_VISIBLE)) { activity->SetCurrentState(visiblity_state); // If we turned an activity invisible, we are already releasing memory // and can hold off releasing more for now. if (visiblity_state == Activity::ACTIVITY_INVISIBLE) OnResourcesReleased(); } } // See which index we should handle next. if (activity_order_changed_) { activity_order_changed_ = false; index = 0; } else { ++index; } } } void ResourceManagerImpl::TryToUnloadAnActivity() { // TODO(skuhne): This algorithm needs to take all kinds of predictive analysis // and running applications into account. For this first patch we only do a // very simple "floating window" algorithm which is surely not good enough. size_t max_running_activities = 5; switch (current_memory_pressure_) { case MEMORY_PRESSURE_UNKNOWN: // If we do not know how much memory we have we assume that it must be a // high consumption. // Fallthrough. case MEMORY_PRESSURE_HIGH: max_running_activities = 5; break; case MEMORY_PRESSURE_CRITICAL: max_running_activities = 0; break; case MEMORY_PRESSURE_MODERATE: max_running_activities = 7; break; case MEMORY_PRESSURE_LOW: NOTREACHED(); return; } // Check if / which activity we want to unload. Activity* oldest_media_activity = NULL; std::vector unloadable_activities; for (std::vector::iterator it = activity_list_.begin(); it != activity_list_.end(); ++it) { Activity::ActivityState state = (*it)->GetCurrentState(); // The activity should neither be unloaded nor visible. if (state != Activity::ACTIVITY_UNLOADED && state != Activity::ACTIVITY_VISIBLE) { if ((*it)->GetMediaState() == Activity::ACTIVITY_MEDIA_STATE_NONE) { // Does not play media - so we can unload this immediately. unloadable_activities.push_back(*it); } else { oldest_media_activity = *it; } } } if (unloadable_activities.size() > max_running_activities) { OnResourcesReleased(); unloadable_activities.back()->SetCurrentState(Activity::ACTIVITY_UNLOADED); return; } else if (current_memory_pressure_ == MEMORY_PRESSURE_CRITICAL) { if (oldest_media_activity) { OnResourcesReleased(); oldest_media_activity->SetCurrentState(Activity::ACTIVITY_UNLOADED); LOG(WARNING) << "Unloading item to releave critical memory pressure"; return; } LOG(ERROR) << "[ResourceManager]: Single activity uses too much memory."; return; } if (current_memory_pressure_ != MEMORY_PRESSURE_UNKNOWN) { // Only show this warning when the memory pressure is actually known. This // will suppress warnings in e.g. unit tests. LOG(WARNING) << "[ResourceManager]: No way to release memory pressure (" << current_memory_pressure_ << "), Activities (running, allowed, unloadable)=(" << activity_list_.size() << ", " << max_running_activities << ", " << unloadable_activities.size() << ")"; } } void ResourceManagerImpl::UpdateActivityOrder() { queued_command_ = true; if (activity_list_.empty()) return; std::vector new_activity_list; const aura::Window::Windows children = WindowManager::Get()->GetWindowListProvider()->GetWindowList(); // Find the first window in the container which is part of the application. for (aura::Window::Windows::const_reverse_iterator child_iterator = children.rbegin(); child_iterator != children.rend(); ++child_iterator) { for (std::vector::iterator activity_iterator = activity_list_.begin(); activity_iterator != activity_list_.end(); ++activity_iterator) { if (*child_iterator == (*activity_iterator)->GetWindow()) { new_activity_list.push_back(*activity_iterator); activity_list_.erase(activity_iterator); break; } } } // At this point the old list should be empty and we can swap the lists. DCHECK(!activity_list_.size()); activity_list_ = new_activity_list; // Remember that the activity order has changed. activity_order_changed_ = true; } void ResourceManagerImpl::OnResourcesReleased() { // Do not release too many activities in short succession since it takes time // to release resources. As such wait the memory pressure interval before the // next call. next_resource_management_time_ = base::Time::Now() + wait_time_for_resource_deallocation_; } void ResourceManagerImpl::OnMemoryPressureIncreased() { // By setting the timer to Now, the next call will immediately be performed. next_resource_management_time_ = base::Time::Now(); } bool ResourceManagerImpl::AllowedToUnloadActivity() { return current_memory_pressure_ != MEMORY_PRESSURE_LOW && base::Time::Now() >= next_resource_management_time_; } } // namespace // static void ResourceManager::Create() { DCHECK(!instance); instance = new ResourceManagerImpl( ResourceManagerDelegate::CreateResourceManagerDelegate()); } // static ResourceManager* ResourceManager::Get() { return instance; } // static void ResourceManager::Shutdown() { DCHECK(instance); delete instance; instance = NULL; } ResourceManager::ResourceManager() {} ResourceManager::~ResourceManager() { DCHECK(instance); instance = NULL; } } // namespace athena