Merge "Reduce memory usage" from Piotr

"
Introduce more memory efficient version of ObjectName and use it in JMX Server.
The original version stores the same data multiple times in different forms.
Big part of data is shared by multiple instances of ObjectName.
Original class keeps a separate copy for each instance.
The new version keeps only one copy that's shared by all instances.
"

* 'speedup_1' of https://github.com/haaawk/scylla-jmx:
  Introduce and use TableMetricObjectName
  Ensure regular ObjectName is returned to remote callers
  Use JmxMBeanServer instead of MBeanServer
This commit is contained in:
Avi Kivity 2018-05-21 11:10:22 +03:00
commit e27312df10
8 changed files with 223 additions and 42 deletions

View File

@ -20,6 +20,7 @@ import javax.management.ObjectName;
import javax.management.QueryExp;
import com.scylladb.jmx.api.APIClient;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
/**
* Base type for MBeans in scylla-jmx. Wraps auto naming and {@link APIClient}
@ -58,14 +59,14 @@ public class APIMBean implements MBeanRegistration {
* @return
* @throws MalformedObjectNameException
*/
public static boolean checkRegistration(MBeanServer server, Set<ObjectName> all,
public static boolean checkRegistration(JmxMBeanServer server, Set<ObjectName> all,
final Predicate<ObjectName> predicate, Function<ObjectName, Object> generator)
throws MalformedObjectNameException {
Set<ObjectName> registered = queryNames(server, predicate);
for (ObjectName name : registered) {
if (!all.contains(name)) {
try {
server.unregisterMBean(name);
server.getMBeanServerInterceptor().unregisterMBean(name);
} catch (MBeanRegistrationException | InstanceNotFoundException e) {
}
}
@ -75,7 +76,7 @@ public class APIMBean implements MBeanRegistration {
for (ObjectName name : all) {
if (!registered.contains(name)) {
try {
server.registerMBean(generator.apply(name), name);
server.getMBeanServerInterceptor().registerMBean(generator.apply(name), name);
added++;
} catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
}
@ -92,7 +93,7 @@ public class APIMBean implements MBeanRegistration {
* @param predicate
* @return
*/
public static Set<ObjectName> queryNames(MBeanServer server, final Predicate<ObjectName> predicate) {
public static Set<ObjectName> queryNames(JmxMBeanServer server, final Predicate<ObjectName> predicate) {
@SuppressWarnings("serial")
Set<ObjectName> registered = server.queryNames(null, new QueryExp() {
@Override
@ -108,7 +109,7 @@ public class APIMBean implements MBeanRegistration {
return registered;
}
MBeanServer server;
JmxMBeanServer server;
ObjectName name;
protected final ObjectName getBoundName() {
@ -162,7 +163,7 @@ public class APIMBean implements MBeanRegistration {
if (this.server != null) {
throw new IllegalStateException("Can only exist in a single MBeanServer");
}
this.server = server;
this.server = (JmxMBeanServer) server;
if (name == null) {
name = generateName();
}

View File

@ -16,6 +16,7 @@ import org.apache.cassandra.metrics.Metrics;
import org.apache.cassandra.metrics.MetricsRegistry;
import com.scylladb.jmx.api.APIClient;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
/**
* Base type for MBeans containing {@link Metrics}.
@ -47,7 +48,7 @@ public abstract class MetricsMBean extends APIMBean {
};
}
private void register(MetricsRegistry registry, MBeanServer server) throws MalformedObjectNameException {
private void register(MetricsRegistry registry, JmxMBeanServer server) throws MalformedObjectNameException {
// Check if we're the first/last of our type bound/removed.
boolean empty = queryNames(server, getTypePredicate()).isEmpty();
for (Metrics m : metrics) {
@ -63,7 +64,7 @@ public abstract class MetricsMBean extends APIMBean {
// Get name etc.
name = super.preRegister(server, name);
// Register all metrics in server
register(new MetricsRegistry(client, server), server);
register(new MetricsRegistry(client, (JmxMBeanServer) server), (JmxMBeanServer) server);
return name;
}
@ -77,7 +78,7 @@ public abstract class MetricsMBean extends APIMBean {
public void register(Supplier<MetricMBean> s, ObjectName... objectNames) {
for (ObjectName name : objectNames) {
try {
server.unregisterMBean(name);
server.getMBeanServerInterceptor().unregisterMBean(name);
} catch (MBeanRegistrationException | InstanceNotFoundException e) {
}
}

View File

@ -26,10 +26,14 @@ import javax.management.MBeanServer;
import javax.management.MBeanServerBuilder;
import javax.management.MBeanServerDelegate;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
public class APIBuilder extends MBeanServerBuilder {
@Override
public MBeanServer newMBeanServer(String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) {
MBeanServer nested = super.newMBeanServer(defaultDomain, outer, delegate);
// It is important to set |interceptors| to true while creating the JmxMBeanSearver.
// It is required for calls to JmxMBeanServer.getMBeanServerInterceptor() to be allowed.
JmxMBeanServer nested = (JmxMBeanServer) JmxMBeanServer.newMBeanServer(defaultDomain, outer, delegate, true);
return new APIMBeanServer(client, nested);
}
}

View File

@ -5,6 +5,7 @@ import java.net.UnknownHostException;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.management.Attribute;
import javax.management.AttributeList;
@ -33,50 +34,59 @@ import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.metrics.StreamingMetrics;
import com.scylladb.jmx.api.APIClient;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
public class APIMBeanServer implements MBeanServer {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(APIMBeanServer.class.getName());
private final APIClient client;
private final MBeanServer server;
private final JmxMBeanServer server;
public APIMBeanServer(APIClient client, MBeanServer server) {
public APIMBeanServer(APIClient client, JmxMBeanServer server) {
this.client = client;
this.server = server;
}
private static ObjectInstance prepareForRemote(final ObjectInstance i) {
return new ObjectInstance(prepareForRemote(i.getObjectName()), i.getClassName());
}
private static ObjectName prepareForRemote(final ObjectName n) {
return ObjectName.getInstance(n);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException,
InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException {
return server.createMBean(className, name);
return prepareForRemote(server.createMBean(className, name));
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName)
throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException,
NotCompliantMBeanException, InstanceNotFoundException {
return server.createMBean(className, name, loaderName);
return prepareForRemote(server.createMBean(className, name, loaderName));
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, Object[] params, String[] signature)
throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException,
NotCompliantMBeanException {
return server.createMBean(className, name, params, signature);
return prepareForRemote(server.createMBean(className, name, params, signature));
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Object[] params,
String[] signature) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException,
MBeanException, NotCompliantMBeanException, InstanceNotFoundException {
return server.createMBean(className, name, loaderName, params, signature);
return prepareForRemote(server.createMBean(className, name, loaderName, params, signature));
}
@Override
public ObjectInstance registerMBean(Object object, ObjectName name)
throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
return server.registerMBean(object, name);
return prepareForRemote(server.registerMBean(object, name));
}
@Override
@ -87,19 +97,19 @@ public class APIMBeanServer implements MBeanServer {
@Override
public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException {
checkRegistrations(name);
return server.getObjectInstance(name);
return prepareForRemote(server.getObjectInstance(name));
}
@Override
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
checkRegistrations(name);
return server.queryNames(name, query);
return server.queryNames(name, query).stream().map(n -> prepareForRemote(n)).collect(Collectors.toSet());
}
@Override
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
checkRegistrations(name);
return server.queryMBeans(name, query);
return server.queryMBeans(name, query).stream().map(i -> prepareForRemote(i)).collect(Collectors.toSet());
}
@Override

View File

@ -54,6 +54,7 @@ import org.apache.cassandra.metrics.TableMetrics;
import com.scylladb.jmx.api.APIClient;
import com.scylladb.jmx.metrics.MetricsMBean;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
public class ColumnFamilyStore extends MetricsMBean implements ColumnFamilyStoreMBean {
private static final Logger logger = Logger.getLogger(ColumnFamilyStore.class.getName());
@ -102,7 +103,7 @@ public class ColumnFamilyStore extends MetricsMBean implements ColumnFamilyStore
"org.apache.cassandra.db:type=" + type + ",keyspace=" + keyspace + ",columnfamily=" + name);
}
public static boolean checkRegistration(APIClient client, MBeanServer server) throws MalformedObjectNameException {
public static boolean checkRegistration(APIClient client, JmxMBeanServer server) throws MalformedObjectNameException {
JsonArray mbeans = client.getJsonArray("/column_family/");
Set<ObjectName> all = new HashSet<ObjectName>();
for (int i = 0; i < mbeans.size(); i++) {

View File

@ -39,6 +39,7 @@ import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import com.scylladb.jmx.api.APIClient;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
/**
* Makes integrating 3.0 metrics API with 2.0.
@ -53,9 +54,9 @@ public class MetricsRegistry {
private static final Logger logger = Logger.getLogger(MetricsRegistry.class.getName());
private final APIClient client;
private final MBeanServer mBeanServer;
private final JmxMBeanServer mBeanServer;
public MetricsRegistry(APIClient client, MBeanServer mBeanServer) {
public MetricsRegistry(APIClient client, JmxMBeanServer mBeanServer) {
this.client = client;
this.mBeanServer = mBeanServer;
}
@ -108,7 +109,7 @@ public class MetricsRegistry {
MetricMBean bean = f.get();
for (ObjectName name : objectNames) {
try {
mBeanServer.registerMBean(bean, name);
mBeanServer.getMBeanServerInterceptor().registerMBean(bean, name);
} catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
logger.log(SEVERE, "Could not register mbean", e);
}

View File

@ -33,12 +33,12 @@ import java.util.HashSet;
import java.util.Set;
import javax.json.JsonArray;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.scylladb.jmx.api.APIClient;
import com.scylladb.jmx.metrics.APIMBean;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
/**
* Metrics for streaming.
@ -65,11 +65,11 @@ public class StreamingMetrics {
return TYPE_NAME.equals(n.getKeyProperty("type"));
}
public static void unregister(APIClient client, MBeanServer server) throws MalformedObjectNameException {
public static void unregister(APIClient client, JmxMBeanServer server) throws MalformedObjectNameException {
APIMBean.checkRegistration(server, emptySet(), StreamingMetrics::isStreamingName, (n) -> null);
}
public static boolean checkRegistration(APIClient client, MBeanServer server)
public static boolean checkRegistration(APIClient client, JmxMBeanServer server)
throws MalformedObjectNameException, UnknownHostException {
Set<ObjectName> all = new HashSet<ObjectName>(globalNames);

View File

@ -19,6 +19,7 @@ package org.apache.cassandra.metrics;
import static com.scylladb.jmx.api.APIClient.getReader;
import java.util.Hashtable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@ -27,7 +28,6 @@ import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.metrics.MetricsRegistry.MetricMBean;
import com.scylladb.jmx.api.APIClient;
@ -266,7 +266,93 @@ public class TableMetrics implements Metrics {
registry.createTableCounter("RowCacheMiss", "row_cache_miss");
}
static class TableMetricNameFactory implements MetricNameFactory {
static class TableMetricObjectName extends javax.management.ObjectName {
private static final String FAKE_NAME = "a:a=a";
private final TableMetricStringNameFactory factory;
private final String metricName;
public TableMetricObjectName(TableMetricStringNameFactory factory, String metricName) throws MalformedObjectNameException {
super(FAKE_NAME);
this.factory = factory;
this.metricName = metricName;
}
@Override
public boolean isPropertyValuePattern(String property) {
return false;
}
@Override
public String getCanonicalName() {
return factory.createMetricStringName(metricName);
}
@Override
public String getDomain() {
return factory.getDomain();
}
@Override
public String getKeyProperty(String property) {
if (property == "name") {
return metricName;
}
return factory.getKeyProperty(property);
}
@Override
public Hashtable<String,String> getKeyPropertyList() {
Hashtable<String, String> res = factory.getKeyPropertyList();
res.put("name", metricName);
return res;
}
@Override
public String getKeyPropertyListString() {
return factory.getKeyPropertyListString(metricName);
}
@Override
public String getCanonicalKeyPropertyListString() {
return getKeyPropertyListString();
}
@Override
public String toString() {
return getCanonicalName();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TableMetricObjectName)) return false;
return getCanonicalName().equals(((TableMetricObjectName) o).getCanonicalName());
}
@Override
public int hashCode() {
return getCanonicalName().hashCode();
}
@Override
public boolean apply(ObjectName name) {
if (name.isDomainPattern() || name.isPropertyListPattern() || name.isPropertyValuePattern()) {
return false;
}
return getCanonicalName().equals(name.getCanonicalName());
}
}
static interface TableMetricStringNameFactory {
String createMetricStringName(String metricName);
String getDomain();
String getKeyProperty(String property);
Hashtable<String,String> getKeyPropertyList();
String getKeyPropertyListString(String metricName);
}
static class TableMetricNameFactory implements MetricNameFactory, TableMetricStringNameFactory {
private final String keyspaceName;
private final String tableName;
private final boolean isIndex;
@ -279,37 +365,114 @@ public class TableMetrics implements Metrics {
this.type = type;
}
@Override
public ObjectName createMetricName(String metricName) throws MalformedObjectNameException {
String groupName = TableMetrics.class.getPackage().getName();
private void appendKeyPropertyListString(final StringBuilder sb, final String metricName) {
String type = isIndex ? "Index" + this.type : this.type;
// Order matters here - keys have to be sorted
sb.append("keyspace=").append(keyspaceName);
sb.append(",name=").append(metricName);
sb.append(",scope=").append(tableName);
sb.append(",type=").append(type);
}
@Override
public String createMetricStringName(String metricName) {
String groupName = TableMetrics.class.getPackage().getName();
StringBuilder mbeanName = new StringBuilder();
mbeanName.append(groupName).append(":");
mbeanName.append("type=").append(type);
mbeanName.append(",keyspace=").append(keyspaceName);
mbeanName.append(",scope=").append(tableName);
mbeanName.append(",name=").append(metricName);
appendKeyPropertyListString(mbeanName, metricName);
return mbeanName.toString();
}
return new ObjectName(mbeanName.toString());
@Override
public String getDomain() {
return TableMetrics.class.getPackage().getName();
}
@Override
public String getKeyProperty(String property) {
switch (property) {
case "keyspace": return keyspaceName;
case "scope": return tableName;
case "type": return type;
default: return null;
}
}
@Override
public Hashtable<String,String> getKeyPropertyList() {
Hashtable<String, String> res = new Hashtable<>();
res.put("keyspace", keyspaceName);
res.put("scope", tableName);
res.put("type", type);
return res;
}
@Override
public String getKeyPropertyListString(String metricName) {
final StringBuilder sb = new StringBuilder();
appendKeyPropertyListString(sb, metricName);
return sb.toString();
}
@Override
public ObjectName createMetricName(String metricName) throws MalformedObjectNameException {
return new TableMetricObjectName(this, metricName);
}
}
static class AllTableMetricNameFactory implements MetricNameFactory {
static class AllTableMetricNameFactory implements MetricNameFactory, TableMetricStringNameFactory {
private final String type;
public AllTableMetricNameFactory(String type) {
this.type = type;
}
private void appendKeyPropertyListString(final StringBuilder sb, final String metricName) {
// Order matters here - keys have to be sorted
sb.append("name=").append(metricName);
sb.append(",type=" + type);
}
@Override
public ObjectName createMetricName(String metricName) throws MalformedObjectNameException {
public String createMetricStringName(String metricName) {
String groupName = TableMetrics.class.getPackage().getName();
StringBuilder mbeanName = new StringBuilder();
mbeanName.append(groupName).append(":");
mbeanName.append("type=" + type);
mbeanName.append(",name=").append(metricName);
return new ObjectName(mbeanName.toString());
appendKeyPropertyListString(mbeanName, metricName);
return mbeanName.toString();
}
@Override
public String getDomain() {
return TableMetrics.class.getPackage().getName();
}
@Override
public String getKeyProperty(String property) {
switch (property) {
case "type": return type;
default: return null;
}
}
@Override
public Hashtable<String,String> getKeyPropertyList() {
Hashtable<String, String> res = new Hashtable<>();
res.put("type", type);
return res;
}
@Override
public String getKeyPropertyListString(String metricName) {
final StringBuilder sb = new StringBuilder();
appendKeyPropertyListString(sb, metricName);
return sb.toString();
}
@Override
public ObjectName createMetricName(String metricName) throws MalformedObjectNameException {
return new TableMetricObjectName(this, metricName);
}
}