普通文本  |  492行  |  16.75 KB

// 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 <algorithm>
#include <vector>

#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*> activity_list_;

  // The resource manager delegate.
  scoped_ptr<ResourceManagerDelegate> delegate_;

  // Keeping a reference to the current memory pressure.
  MemoryPressureObserver::MemoryPressure current_memory_pressure_;

  // The memory pressure notifier.
  scoped_ptr<MemoryPressureNotifier> 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<Activity*>::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<Activity*> unloadable_activities;
  for (std::vector<Activity*>::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<Activity*> 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<Activity*>::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