1 /*
2  * MemUsageWidget.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.workbench.views.environment.view;
16 
17 import com.google.gwt.dom.client.Style;
18 import com.google.gwt.i18n.client.NumberFormat;
19 import com.google.gwt.resources.client.ImageResource;
20 import com.google.gwt.user.client.ui.Composite;
21 import com.google.gwt.user.client.ui.HTMLPanel;
22 import com.google.gwt.user.client.ui.Widget;
23 import org.rstudio.core.client.ElementIds;
24 import org.rstudio.core.client.StringUtil;
25 import org.rstudio.core.client.widget.MiniPieWidget;
26 import org.rstudio.core.client.widget.ToolbarButton;
27 import org.rstudio.core.client.widget.ToolbarMenuButton;
28 import org.rstudio.core.client.widget.ToolbarPopupMenu;
29 import org.rstudio.core.client.widget.UserPrefMenuItem;
30 import org.rstudio.studio.client.RStudioGinjector;
31 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
32 import org.rstudio.studio.client.workbench.views.environment.model.MemoryUsage;
33 
34 public class MemUsageWidget extends Composite
35 {
MemUsageWidget(MemoryUsage usage, UserPrefs prefs)36    public MemUsageWidget(MemoryUsage usage, UserPrefs prefs)
37    {
38       prefs_ = prefs;
39       suspended_ = false;
40 
41       host_ = new HTMLPanel("");
42       Style style = host_.getElement().getStyle();
43       style.setProperty("display", "flex");
44       style.setProperty("flexDirection", "row");
45 
46       pieCrust_ = new HTMLPanel("");
47       pieCrust_.setHeight("13px");
48       pieCrust_.setWidth("13px");
49       style = pieCrust_.getElement().getStyle();
50       style.setMarginTop(3, Style.Unit.PX);
51       style.setMarginRight(3, Style.Unit.PX);
52       host_.add(pieCrust_);
53       ElementIds.assignElementId(pieCrust_, ElementIds.MEMORY_PIE_MINI);
54 
55       ToolbarPopupMenu memoryMenu = new ToolbarPopupMenu();
56       memoryMenu.addItem(RStudioGinjector.INSTANCE.getCommands().freeUnusedMemory().createMenuItem(false));
57       memoryMenu.addItem(RStudioGinjector.INSTANCE.getCommands().showMemoryUsageReport().createMenuItem(false));
58       memoryMenu.addSeparator();
59       memoryMenu.addItem(new UserPrefMenuItem<Boolean>(
60          prefs_.showMemoryUsage(),
61          true,
62          "Show Current Memory Usage",
63          prefs_
64       ));
65 
66       menu_ = new ToolbarMenuButton(
67          "Mem",
68          ToolbarButton.NoTitle,
69          (ImageResource) null,
70          memoryMenu);
71       ElementIds.assignElementId(menu_, ElementIds.MEMORY_DROPDOWN);
72       menu_.getElement().getStyle().setMarginTop(-3, Style.Unit.PX);
73       host_.add(menu_);
74 
75       setMemoryUsage(usage);
76 
77       prefs.showMemoryUsage().addValueChangeHandler(evt ->
78       {
79          // Clear memory usage display immediately when turned off
80          if (!evt.getValue())
81          {
82             setMemoryUsage(null);
83          }
84       });
85 
86       initWidget(host_);
87    }
88 
89    /**
90     * Sets the memory usage to be displayed in the widget.
91     *
92     * @param usage The memory usage to display
93     */
setMemoryUsage(MemoryUsage usage)94    public void setMemoryUsage(MemoryUsage usage)
95    {
96       usage_ = usage;
97 
98       if (usage == null)
99       {
100          pieCrust_.getElement().removeAllChildren();
101          pieCrust_.setVisible(false);
102          menu_.setText("Mem");
103       }
104       else
105       {
106          // If we were previously showing suspended memory data, then switch to live session view
107          if (suspended_)
108          {
109             setSuspended(false);
110          }
111 
112          menu_.setTitle(StringUtil.prettyFormatNumber(usage.getProcess().getKb()) +
113             " KiB used by R session (source: " + usage.getProcess().getProviderName() + ")");
114          menu_.setText(formatBigMemory(usage.getProcess().getKb()));
115 
116          MemoryUsagePieChart pie = new MemoryUsagePieChart(usage);
117          loadPieDisplay(pie);
118       }
119    }
120 
121    /**
122     * Load a pie chart into the memory usage widget
123     *
124     * @param pie The pie chart to load
125     */
loadPieDisplay(Widget pie)126    private void loadPieDisplay(Widget pie)
127    {
128       // For browser SVG painting reasons, it is necessary to create a wholly
129       // new SVG element and then replay it as HTML into the DOM to get it to
130       // draw correctly.
131       pieCrust_.setVisible(true);
132       pieCrust_.getElement().removeAllChildren();
133       pieCrust_.add(pie);
134       pieCrust_.getElement().setInnerHTML(pieCrust_.getElement().getInnerHTML());
135    }
136 
137    /**
138     * Formats a large memory statistic for memory display
139     *
140     * @param kb The amount of memory
141     * @return A string describing the amount of memory
142     */
formatBigMemory(int kb)143    private String formatBigMemory(int kb)
144    {
145       long mib = kb / 1024;
146       if (mib >= 1024)
147       {
148          // Memory usage is > 1GiB, format as XX.YY GiB
149          NumberFormat decimalFormat = NumberFormat.getFormat(".##");
150          return decimalFormat.format((double)mib / (double)1024) + " GiB";
151       }
152 
153       // Memory usage is in MiB
154       return mib + " MiB";
155    }
156 
157    /**
158     * Sets whether to show the memory usage as suspended
159     *
160     * @param suspended
161     */
setSuspended(boolean suspended)162    public void setSuspended(boolean suspended)
163    {
164       // Ignore if we're already in the desired state
165       if (suspended == suspended_)
166       {
167          return;
168       }
169       suspended_ = suspended;
170 
171       // If the widget isn't showing memory usage information
172       if (usage_ == null)
173       {
174          return;
175       }
176 
177       if (suspended)
178       {
179          MiniPieWidget pie = new MiniPieWidget(
180             "Memory in use: none (suspended)",
181             "Empty pie chart depicting no memory usage",
182             MEMORY_PIE_UNUSED_COLOR);
183          loadPieDisplay(pie);
184 
185          menu_.setEnabled(false);
186       }
187       else
188       {
189          menu_.setEnabled(true);
190       }
191    }
192 
193    private final UserPrefs prefs_;
194    private final HTMLPanel pieCrust_;
195    private final ToolbarMenuButton menu_;
196    private final HTMLPanel host_;
197 
198    private MemoryUsage usage_;
199    private boolean suspended_;
200 
201    public static final String MEMORY_PIE_UNUSED_COLOR = "#e4e4e4";
202 }