All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.idea.memory.MemoryMonitorView Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 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.
 */
package com.android.tools.idea.memory;

import com.android.SdkConstants;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
import com.android.tools.idea.ddms.ClientCellRenderer;
import com.android.tools.idea.ddms.DeviceRenderer;
import com.android.tools.idea.memory.actions.*;
import com.android.tools.idea.model.AndroidModuleInfo;
import com.google.common.collect.Maps;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.JBColor;
import com.intellij.ui.SideBorder;
import com.intellij.util.ui.UIUtil;
import icons.AndroidIcons;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.ENABLE_EXPERIMENTAL_ACTIONS;

public class MemoryMonitorView
  implements AndroidDebugBridge.IDeviceChangeListener, AndroidDebugBridge.IClientChangeListener, MemorySampler.MemorySamplerListener,
             HierarchyListener, AndroidDebugBridge.IDebugBridgeChangeListener {

  /**
   * Maximum number of samples to keep in memory. We not only sample at {@code SAMPLE_FREQUENCY_MS} but we also receive
   * a sample on every GC.
   */
  public static final int SAMPLES = 2048;
  private static final Color BACKGROUND_COLOR = UIUtil.getTextFieldBackground();
  private static final int SAMPLE_FREQUENCY_MS = 500;
  @NotNull
  private final Project myProject;
  @Nullable
  private AndroidDebugBridge myBridge;
  @NotNull
  private final Map myPreferredClients;
  public boolean myIgnoreActionEvents;
  @NotNull
  private JPanel myContentPane;
  @NotNull
  private TimelineComponent myTimelineComponent;
  @NotNull
  private JPanel myToolbarPanel;
  @NotNull
  private JComboBox myDeviceCombo;
  @NotNull
  private JComboBox myClientCombo;
  private JPanel myTopPanel;
  @NotNull
  private MemorySampler myMemorySampler;
  @NotNull
  private TimelineData myData;
  @Nullable
  private String myCandidateClientName;
  @NotNull
  private MemorySamplerTask myMemorySamplerTask;

  public MemoryMonitorView(@NotNull Project project) {
    $$$setupUI$$$(); // See IDEA-67765

    myProject = project;
    myPreferredClients = Maps.newHashMap();
    myCandidateClientName = getApplicationName();

    myTimelineComponent.configureUnits("MB");
    myTimelineComponent.configureStream(0, "Allocated", new JBColor(0x78abd9, 0x78abd9));
    myTimelineComponent.configureStream(1, "Free", new JBColor(0xbaccdc, 0x51585c));
    myTimelineComponent
      .configureEvent(MemorySampler.TYPE_HPROF_REQUEST, MemorySampler.TYPE_HPROF_RESULT, 0, AndroidIcons.Ddms.ScreenCapture,
                      new JBColor(0x92ADC6, 0x718493), new JBColor(0x2B4E8C, 0xC7E5FF));
    myTimelineComponent.setBackground(BACKGROUND_COLOR);
    myTopPanel.setBackground(BACKGROUND_COLOR);

    myMemorySampler = new MemorySampler(myData, SAMPLE_FREQUENCY_MS);
    myMemorySampler.addListener(this);

    ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, getToolbarActions(), false);
    myToolbarPanel.add(toolbar.getComponent());
    myToolbarPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.RIGHT));

    myContentPane.addHierarchyListener(this);

    myMemorySamplerTask = new MemorySamplerTask(project, myMemorySampler);


    initializeDeviceCombo();
    initializeClientCombo();

    AndroidSdkUtils.activateDdmsIfNecessary(myProject);

    AndroidDebugBridge.addDeviceChangeListener(this);
    AndroidDebugBridge.addClientChangeListener(this);
    AndroidDebugBridge.addDebugBridgeChangeListener(this);
  }

  private void initializeClientCombo() {
    myClientCombo.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent actionEvent) {
        if (myIgnoreActionEvents) return;

        Client client = (Client)myClientCombo.getSelectedItem();
        myPreferredClients.put(client.getDevice().getName(), client.getClientData().getClientDescription());
        myMemorySampler.setClient(client);
      }
    });

    myClientCombo.setRenderer(new ClientCellRenderer("No Debuggable Applications"));
    Dimension size = myClientCombo.getMinimumSize();
    myClientCombo.setMinimumSize(new Dimension(250, size.height));
  }

  private void initializeDeviceCombo() {
    myDeviceCombo.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent actionEvent) {
        if (myIgnoreActionEvents) return;

        updateClientCombo();
      }
    });

    myDeviceCombo.setRenderer(new DeviceRenderer.DeviceComboBoxRenderer("No Connected Devices"));
    Dimension size = myDeviceCombo.getMinimumSize();
    myDeviceCombo.setMinimumSize(new Dimension(200, size.height));
  }

  private void $$$setupUI$$$() {
  }

  private void createUIComponents() {

    // Buffer at one and a half times the sample frequency.
    float bufferTimeInSeconds = SAMPLE_FREQUENCY_MS * 1.5f / 1000.f;
    float initialMax = 5.0f;
    float initialMarker = 2.0f;

    myData = new TimelineData(2, SAMPLES);
    myTimelineComponent = new TimelineComponent(myData, bufferTimeInSeconds, initialMax, initialMarker);
  }

  @NotNull
  public ActionGroup getToolbarActions() {
    DefaultActionGroup group = new DefaultActionGroup();

    if (Boolean.getBoolean(ENABLE_EXPERIMENTAL_ACTIONS)) {
      group.add(new RecordingAction(myMemorySampler));
      group.add(new MemorySnapshotAction(myMemorySampler));
    }
    group.add(new GcAction(myMemorySampler));
    group.add(new CloseMemoryMonitorWindow(this));

    if (Boolean.getBoolean("studio.profiling.debug")) {
      group.addSeparator();
      group.add(new ToggleDebugRender(myTimelineComponent));
    }

    return group;
  }

  @Nullable
  private String getApplicationName() {
    for (Module module : ModuleManager.getInstance(myProject).getModules()) {
      AndroidModuleInfo moduleInfo = AndroidModuleInfo.get(module);
      if (moduleInfo != null) {
        String pkg = moduleInfo.getPackage();
        if (pkg != null) {
          return pkg;
        }
      }
    }
    return null;
  }

  public JPanel getComponent() {
    return myContentPane;
  }

  public void close() {
    myMemorySampler.stop();

    ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
    ToolWindow toolWindow = toolWindowManager.getToolWindow(MemoryMonitorToolWindowFactory.ID);
    toolWindow.hide(null);
  }

  @Override
  public void bridgeChanged(AndroidDebugBridge bridge) {
    myBridge = bridge;
    updateDeviceCombo();
  }

  @Override
  public void deviceConnected(IDevice device) {
    ApplicationManager.getApplication().invokeLater(new Runnable() {
      @Override
      public void run() {
        updateDeviceCombo();
      }
    });
  }

  @Override
  public void deviceDisconnected(IDevice device) {
    ApplicationManager.getApplication().invokeLater(new Runnable() {
      @Override
      public void run() {
        updateDeviceCombo();
      }
    });
  }

  @Override
  public void deviceChanged(final IDevice device, final int changeMask) {
    ApplicationManager.getApplication().invokeLater(new Runnable() {
      @Override
      public void run() {
        if ((changeMask & IDevice.CHANGE_CLIENT_LIST) != 0) {
          updateClientCombo();
        }
      }
    });
  }

  @Override
  public void clientChanged(Client client, int changeMask) {
    if ((changeMask & Client.CHANGE_NAME) != 0) {
      ApplicationManager.getApplication().invokeLater(new Runnable() {
        @Override
        public void run() {
          updateClientCombo();
        }
      });
    }
  }

  private void updateDeviceCombo() {
    myIgnoreActionEvents = true;

    boolean update = true;
    IDevice selected = (IDevice)myDeviceCombo.getSelectedItem();
    myDeviceCombo.removeAllItems();
    if (myBridge != null) {
      for (IDevice device : myBridge.getDevices()) {
        myDeviceCombo.addItem(device);
        if (selected == device) {
          myDeviceCombo.setSelectedItem(device);
          update = false;
        }
      }
    }

    if (update) {
      updateClientCombo();
    }

    myIgnoreActionEvents = false;
  }

  private void updateClientCombo() {
    myIgnoreActionEvents = true;

    IDevice device = (IDevice)myDeviceCombo.getSelectedItem();
    Client selected = (Client)myClientCombo.getSelectedItem();
    Client toSelect = selected;
    boolean update = true;
    myClientCombo.removeAllItems();
    if (device != null) {
      // Change the currently selected client if the user has a preference.
      String preferred = getPreferredClientForDevice(device.getName());
      if (preferred != null) {
        Client preferredClient = device.getClient(preferred);
        if (preferredClient != null) {
          toSelect = preferredClient;
        }
      }

      Client[] clients = device.getClients();
      Arrays.sort(clients, new ClientCellRenderer.ClientComparator());

      for (Client client : clients) {
        myClientCombo.addItem(client);
        if (client == toSelect) {
          myClientCombo.setSelectedItem(toSelect);
          update = toSelect != selected;
        }
      }
    }

    myIgnoreActionEvents = false;

    if (update) {
      selected = (Client)myClientCombo.getSelectedItem();
      myMemorySampler.setClient(selected);
    }
  }

  @Nullable
  private String getPreferredClientForDevice(String deviceName) {
    String client = myPreferredClients.get(deviceName);
    return client == null ? myCandidateClientName : client;
  }

  @Override
  public void onStart() {
  }

  @Override
  public void onStop() {
  }

  @Override
  public void onHprofCompleted(@NotNull byte[] data, @NotNull Client client) {
    File f;
    try {
      f = FileUtil.createTempFile("ddms", "." + SdkConstants.EXT_HPROF);
      FileUtil.writeToFile(f, data);
    }
    catch (IOException e) {
      return;
    }
    final VirtualFile vf = VfsUtil.findFileByIoFile(f, true);
    if (vf == null) {
      return;
    }
    OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, vf);
    FileEditorManager.getInstance(myProject).openEditor(descriptor, true);
  }

  @Override
  public void hierarchyChanged(HierarchyEvent hierarchyEvent) {
    if ((hierarchyEvent.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
      myMemorySamplerTask.exit();
      if (myContentPane.isShowing()) {
        myMemorySampler.start();
      }
      else {
        if (myMemorySampler.isRunning()) {
          myMemorySamplerTask = new MemorySamplerTask(myProject, myMemorySampler);
          ProgressManager.getInstance().run(myMemorySamplerTask);
        }
      }
    }
  }

  private static class MemorySamplerTask extends Task.Backgroundable {

    private final MemorySampler myMemorySampler;
    private final CountDownLatch myLatch;

    public MemorySamplerTask(@Nullable Project project, MemorySampler memorySampler) {
      super(project, "Monitoring Memory ...", true);
      myMemorySampler = memorySampler;
      myLatch = new CountDownLatch(1);
    }

    public void exit() {
      myLatch.countDown();
    }

    @Override
    public void run(@NotNull ProgressIndicator indicator) {
      indicator.setIndeterminate(true);
      while (myMemorySampler.isRunning() && myLatch.getCount() > 0) {
        try {
          myLatch.await(200, TimeUnit.MILLISECONDS);
        }
        catch (InterruptedException e) {
          break;
        }

        if (indicator.isCanceled()) {
          myMemorySampler.stop();
          break;
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy