Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ private Map<CBTools.Type, String> getToolsMap(String toolKey, String os) {
} else if (ALL_TOOLS.equals(toolKey)) {
map.put(CBTools.Type.CBC_PILLOW_FIGHT, path + "cbc-pillowfight" + suffix);
map.put(CBTools.Type.MCTIMINGS, path + "mctimings" + suffix);
map.put(CBTools.Type.CBSTATS, path + "cbstats" + suffix);

} else {
throw new IllegalStateException("Not implemented yet");
Expand Down Expand Up @@ -155,16 +156,24 @@ public void downloadDependencies() throws Exception {

ToolSpec cbTools = downloads.get(ALL_TOOLS);
String toolsDir = toolsPath + File.separator + cbTools.getInstallationPath();
if (CBTools.getTool(CBTools.Type.CBC_PILLOW_FIGHT).getStatus() == ToolStatus.NOT_AVAILABLE
&& !isInstalled(toolsPath, downloads.get(ALL_TOOLS), CBTools.Type.CBC_PILLOW_FIGHT)) {

Log.info("Downloading CB tools. The feature will be automatically enabled when the download is complete.");
CBTools.getTool(CBTools.Type.CBC_PILLOW_FIGHT).setStatus(ToolStatus.DOWNLOADING);
CBTools.getTool(CBTools.Type.MCTIMINGS).setStatus(ToolStatus.DOWNLOADING);
CBTools.Type[] toolTypes = {CBTools.Type.CBC_PILLOW_FIGHT, CBTools.Type.MCTIMINGS, CBTools.Type.CBSTATS};

boolean shouldDownload = false;
for (CBTools.Type toolType : toolTypes) {
if (CBTools.getTool(toolType).getStatus() == ToolStatus.NOT_AVAILABLE
&& !isInstalled(toolsPath, downloads.get(ALL_TOOLS), toolType)) {
shouldDownload = true;
CBTools.getTool(toolType).setStatus(ToolStatus.DOWNLOADING);
} else {
Log.debug(toolType + " tool is already installed");
setToolActive(ToolStatus.AVAILABLE, toolsDir, cbTools);
}
}

if (shouldDownload) {
Log.info("Downloading tools. The features will be automatically enabled when the download is complete.");
downloadAndUnzip(toolsDir, cbTools);
} else {
Log.debug("CB Tools are already installed");
setToolActive(ToolStatus.AVAILABLE, toolsDir, cbTools);
}

}
Expand Down
82 changes: 82 additions & 0 deletions src/main/java/com/couchbase/intellij/tools/CBStats.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.couchbase.intellij.tools;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.couchbase.intellij.database.ActiveCluster;
import com.couchbase.intellij.workbench.Log;

import utils.OSUtil;
import utils.ProcessUtils;

public class CBStats {
private final String bucketName;
private final String scopeName;
private final String collectionName;

private final String type;

public CBStats(String bucketName, String scopeName, String collectionName, String type) {
this.bucketName = bucketName;
this.scopeName = scopeName;
this.collectionName = collectionName;
this.type = type;
}

public String executeCommand() throws IOException, InterruptedException {
StringBuilder output;

List<String> command = new ArrayList<>();
// command.add(CBTools.getTool(CBTools.Type.CBSTATS).getPath());
String osArch = OSUtil.getOSArch();

// Determine the command path based on the operating system and architecture
switch (osArch) {
case OSUtil.MACOS_ARM:
case OSUtil.MACOS_64:
command.add("/Applications/Couchbase Server.app/Contents/Resources/couchbase-core/bin/cbstats");
break;
case OSUtil.WINDOWS_ARM:
case OSUtil.WINDOWS_64:
command.add("C:\\Program Files\\Couchbase\\Server\\bin\\cbstats");
break;
case OSUtil.LINUX_ARM:
case OSUtil.LINUX_64:
command.add("/opt/couchbase/bin/cbstats");
break;
default:
throw new UnsupportedOperationException("Unsupported operating system: " + osArch);
}
command.add((ActiveCluster.getInstance().getClusterURL().replaceFirst("^couchbase://", "")) + ":"
+ (ActiveCluster.getInstance().isSSLEnabled() ? "11207" : "11210"));
command.add("-u");
command.add(ActiveCluster.getInstance().getUsername());
command.add("-p");
command.add(ActiveCluster.getInstance().getPassword());

command.add("-b");
command.add(bucketName);

if (type.equalsIgnoreCase("collection")) {
command.add("collections");
command.add(scopeName + "." + collectionName);
} else if (type.equalsIgnoreCase("scope")) {
command.add("scopes");
command.add(scopeName);
}

ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();

output = new StringBuilder(ProcessUtils.returnOutput(process));

if (process.waitFor() == 0) {
Log.info("Command executed successfully");
} else {
Log.error("Command execution failed");
}

return output.toString();
}
}
3 changes: 2 additions & 1 deletion src/main/java/com/couchbase/intellij/tools/CBTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum Type {
CB_IMPORT,
CB_EXPORT,
CBC_PILLOW_FIGHT,
MCTIMINGS
CBSTATS,
MCTIMINGS,
}
}
151 changes: 151 additions & 0 deletions src/main/java/com/couchbase/intellij/tools/dialog/CbstatsDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.couchbase.intellij.tools.dialog;

import java.awt.BorderLayout;

import javax.swing.JComponent;
import javax.swing.JPanel;

import org.jetbrains.annotations.Nullable;

import com.couchbase.intellij.tools.CBStats;
import com.couchbase.intellij.workbench.Log;
import com.intellij.openapi.ui.DialogWrapper;

import utils.TemplateUtil;

public class CbstatsDialog extends DialogWrapper {

private final String bucketName;
private final String scopeName;
private final String collectionName;
private final String type;

private static final String MEMORY_USED_BY_COLLECTION = "Memory Used By Collection";
private static final String COLLECTION_DATA_SIZE = "Collection Data Size";
private static final String NUMBER_OF_ITEMS = "Number Of Items";
private static final String COLLECTION_NAME = "Collection Name";
private static final String NUMBER_OF_DELETE_OPERATIONS = "Number Of Delete Operations";
private static final String NUMBER_OF_GET_OPERATIONS = "Number Of Get Operations";
private static final String NUMBER_OF_STORE_OPERATIONS = "Number Of Store Operations";
private static final String SCOPE_NAME = "Scope Name";

public CbstatsDialog(String bucket, String scope, String collection, String type) {
super(true);
this.bucketName = bucket;
this.scopeName = scope;
this.collectionName = collection;
this.type = type;
init();
setTitle("Stats for " + type);
setResizable(true);
}

@Nullable
@Override
protected JComponent createCenterPanel() {
JPanel dialogPanel = new JPanel(new BorderLayout());

CBStats cbStats = new CBStats(bucketName, scopeName, collectionName, type);
String output = "";
try {
output = cbStats.executeCommand();
} catch (Exception ex) {
Log.error(ex);
}

String[] lines = output.split("\n");
String[] keys = new String[lines.length];
String[] values = new String[lines.length];
String[] helpTexts = new String[lines.length]; // array for help texts

for (int i = 0; i < lines.length; i++) {
int keyStartIndex = lines[i].indexOf(':', lines[i].indexOf(':') + 1) + 1;
int valueStartIndex = lines[i].lastIndexOf(':') + 1;
keys[i] = getFriendlyKeyName(lines[i].substring(keyStartIndex, valueStartIndex - 1).trim());
values[i] = getFriendlyValue(lines[i].substring(valueStartIndex).trim(), keys[i]);
helpTexts[i] = getHelpText(keys[i]); // get help text for each key
}

JPanel keyValuePanel = TemplateUtil.createKeyValuePanelWithHelp(keys, values, helpTexts, 1);

dialogPanel.add(keyValuePanel, BorderLayout.CENTER);

return dialogPanel;
}

private String getHelpText(String key) {
switch (key) {
case MEMORY_USED_BY_COLLECTION:
return "This is the memory used by the collection. It is measured in bytes.";
case COLLECTION_DATA_SIZE:
return "This is the size of the data in the collection. It is also measured in bytes.";
case NUMBER_OF_ITEMS:
return "This represents the number of items in the collection. It doesn't have a unit as it's a count.";
case COLLECTION_NAME:
return "This is the name of the collection. It's a string and doesn't have a unit.";
case NUMBER_OF_DELETE_OPERATIONS:
return "This is the number of delete operations that have been performed on the collection. It doesn't have a unit as it's a count.";
case NUMBER_OF_GET_OPERATIONS:
return "This is the number of get operations that have been performed on the collection. It doesn't have a unit as it's a count.";
case NUMBER_OF_STORE_OPERATIONS:
return "This is the number of store operations that have been performed on the collection. It doesn't have a unit as it's a count.";
case SCOPE_NAME:
return "This is the name of the scope that contains the collection. It's a string and doesn't have a unit.";
default:
return "";
}
}

private String getFriendlyKeyName(String key) {
switch (key) {
case "collections_mem_used":
return MEMORY_USED_BY_COLLECTION;
case "data_size":
return COLLECTION_DATA_SIZE;
case "items":
return NUMBER_OF_ITEMS;
case "name":
return COLLECTION_NAME;
case "ops_delete":
return NUMBER_OF_DELETE_OPERATIONS;
case "ops_get":
return NUMBER_OF_GET_OPERATIONS;
case "ops_store":
return NUMBER_OF_STORE_OPERATIONS;
case "scope_name":
return SCOPE_NAME;
default:
return key;
}
}

private String getFriendlyValue(String value, String key) {
switch (key) {
case MEMORY_USED_BY_COLLECTION:
case COLLECTION_DATA_SIZE:
long bytes = Long.parseLong(value);
double sizeInMB = bytes / (1024.0 * 1024.0);
if (sizeInMB >= 1024) {
double sizeInGB = sizeInMB / 1024.0;
return String.format("%.2f GB", sizeInGB);
} else {
return String.format("%.2f MB", sizeInMB);
}
case NUMBER_OF_DELETE_OPERATIONS:
case NUMBER_OF_GET_OPERATIONS:
case NUMBER_OF_STORE_OPERATIONS:
case NUMBER_OF_ITEMS:
long count = Long.parseLong(value);
if (count >= 1_000_000) {
return String.format("%.2f M", count / 1_000_000.0);
} else if (count >= 1_000) {
return String.format("%.2f K", count / 1_000.0);
} else {
return value;
}
default:
return value;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import com.couchbase.intellij.tools.CBImport;
import com.couchbase.intellij.tools.CBTools;
import com.couchbase.intellij.tools.PillowFightDialog;
import com.couchbase.intellij.tools.dialog.CbstatsDialog;
import com.couchbase.intellij.tools.dialog.DDLExportDialog;
import com.couchbase.intellij.tools.dialog.ExportDialog;
import com.couchbase.intellij.tree.NewEntityCreationDialog.EntityType;
import com.couchbase.intellij.tree.docfilter.DocumentFilterDialog;
import com.couchbase.intellij.tree.node.*;
import com.couchbase.intellij.tree.overview.IndexOverviewDialog;
Expand Down Expand Up @@ -596,6 +596,21 @@ public void actionPerformed(@NotNull AnActionEvent e) {
actionGroup.add(simpleExport);
}

actionGroup.addSeparator();
AnAction viewStats = new AnAction("View Collection Statistics") {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
CbstatsDialog cbstatsDialog = new CbstatsDialog(
col.getBucket(),
col.getScope(),
col.getText(),
"collection"
);
cbstatsDialog.show();
}
};
actionGroup.add(viewStats);

showPopup(e, tree, actionGroup);
}
}
22 changes: 22 additions & 0 deletions src/main/java/utils/ProcessUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,26 @@ public static void printOutput(Process process, String message) throws IOExcepti
}
}
}

public static String returnOutput(Process process) throws IOException {
StringBuilder output = new StringBuilder();

// Process standard output
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}

// Process standard error
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}

return output.toString();
}
}
Loading