Skip to content

Commit

Permalink
Merge 'JMX footprint work' from Calle
Browse files Browse the repository at this point in the history
"
Fixes #133
Fixes #134
Refs #135

Makes CF mbean refresh code synchronized and tries to remove reductant
calls if we contend. Adds background reaping of dead objects to reduce
memory load in (test) scenarios where we manage to refresh to add, but
not cause removal (i.e. no wildcard queries).

TableMetricsObjectName serialization is fixed in the series because
without it we see loads of exceptions when refreshing the mbean set.
"

* elcallio-jmx-fixes:
  scylla-jmx: Use registration checker objects
  scylla-jmx: Introduce a registration check object
  scylla-jmx: Fix TableMetricObjectName serialization
  • Loading branch information
penberg committed Sep 7, 2020
2 parents 12ab6aa + ba3f58c commit 8d92e54
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 88 deletions.
57 changes: 31 additions & 26 deletions src/main/java/com/scylladb/jmx/metrics/APIMBean.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.scylladb.jmx.metrics;

import java.lang.reflect.Field;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -55,35 +56,39 @@ public APIMBean(String mbeanName, APIClient client) {
* @param generator
* {@link Function} to create a new MBean instance for a given
* {@link ObjectName}
*
* @return
* @throws MalformedObjectNameException
*/
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.getMBeanServerInterceptor().unregisterMBean(name);
} catch (MBeanRegistrationException | InstanceNotFoundException e) {
}
}
}

int added = 0;
for (ObjectName name : all) {
if (!registered.contains(name)) {
try {
server.getMBeanServerInterceptor().registerMBean(generator.apply(name), name);
added++;
} catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
}
}
}
return added > 0;
}
public static boolean checkRegistration(JmxMBeanServer server, Set<ObjectName> all,
EnumSet<RegistrationMode> mode, final Predicate<ObjectName> predicate,
Function<ObjectName, Object> generator) throws MalformedObjectNameException {
Set<ObjectName> registered = queryNames(server, predicate);
if (mode.contains(RegistrationMode.Remove)) {
for (ObjectName name : registered) {
if (!all.contains(name)) {
try {
server.getMBeanServerInterceptor().unregisterMBean(name);
} catch (MBeanRegistrationException | InstanceNotFoundException e) {
}
}
}
}

int added = 0;
if (mode.contains(RegistrationMode.Add)) {
for (ObjectName name : all) {
if (!registered.contains(name)) {
try {
server.getMBeanServerInterceptor().registerMBean(generator.apply(name), name);
added++;
} catch (InstanceAlreadyExistsException | MBeanRegistrationException
| NotCompliantMBeanException e) {
}
}
}
}
return added > 0;
}

/**
* Helper method to query {@link ObjectName}s from an {@link MBeanServer}
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/com/scylladb/jmx/metrics/RegistrationChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.scylladb.jmx.metrics;

import static com.scylladb.jmx.metrics.RegistrationMode.Remove;
import static com.scylladb.jmx.metrics.RegistrationMode.Wait;
import static java.util.EnumSet.allOf;
import static java.util.EnumSet.of;

import java.net.UnknownHostException;
import java.util.EnumSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.management.OperationsException;

import com.scylladb.jmx.api.APIClient;
import com.sun.jmx.mbeanserver.JmxMBeanServer;

/**
* Helper type to do optional locking for registration. Allows for
* per-bind-point locks and registration, instead of per-type or per-instance
* locks which may be misguiding, since for example one instance can be bound to
* many MBeanServers etc.
*
* Also allows for polled checks, i.e. try-lock and either wait or skip. Wait,
* because we probably should not repeat things hidden by this type too often,
* and skip because for example a periodic task checking can just skip if a
* user-initiated registration check is being done.
*
* @author calle
*
*/
@SuppressWarnings("restriction")
public abstract class RegistrationChecker {
private final Lock lock = new ReentrantLock();

public static final EnumSet<RegistrationMode> REMOVE_NO_WAIT = of(Remove);
public static final EnumSet<RegistrationMode> ADD_AND_REMOVE = allOf(RegistrationMode.class);

public final void reap(APIClient client, JmxMBeanServer server) throws OperationsException, UnknownHostException {
check(client, server, REMOVE_NO_WAIT);
}

public final void check(APIClient client, JmxMBeanServer server) throws OperationsException, UnknownHostException {
check(client, server, ADD_AND_REMOVE);
}

public final void check(APIClient client, JmxMBeanServer server, EnumSet<RegistrationMode> mode)
throws OperationsException, UnknownHostException {
if (!lock.tryLock()) {
if (mode.contains(Wait)) {
// someone is doing update.
// since this is jmx, and sloppy, we'll just
// assume that once he is done, things are
// good enough.
lock.lock();
lock.unlock();
}
return;
}
try {
doCheck(client, server, mode);
} finally {
lock.unlock();
}
}

protected abstract void doCheck(APIClient client, JmxMBeanServer server, EnumSet<RegistrationMode> mode)
throws OperationsException, UnknownHostException;
}
5 changes: 5 additions & 0 deletions src/main/java/com/scylladb/jmx/metrics/RegistrationMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.scylladb.jmx.metrics;

public enum RegistrationMode {
Wait, Add, Remove,
}
39 changes: 28 additions & 11 deletions src/main/java/com/scylladb/jmx/utils/APIMBeanServer.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.scylladb.jmx.utils;

import static java.util.Arrays.asList;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.MINUTES;

import java.io.ObjectInputStream;
import java.net.UnknownHostException;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -34,19 +39,34 @@
import org.apache.cassandra.metrics.StreamingMetrics;

import com.scylladb.jmx.api.APIClient;
import com.scylladb.jmx.metrics.RegistrationChecker;
import com.sun.jmx.mbeanserver.JmxMBeanServer;

@SuppressWarnings("restriction")
public class APIMBeanServer implements MBeanServer {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(APIMBeanServer.class.getName());
private static final ScheduledExecutorService executor = newScheduledThreadPool(1);

private final RegistrationChecker columnFamilyStoreChecker = ColumnFamilyStore.createRegistrationChecker();
private final RegistrationChecker streamingMetricsChecker = StreamingMetrics.createRegistrationChecker();

private final APIClient client;
private final JmxMBeanServer server;

public APIMBeanServer(APIClient client, JmxMBeanServer server) {
this.client = client;
this.server = server;

executor.scheduleWithFixedDelay(() -> {
for (RegistrationChecker c : asList(columnFamilyStoreChecker, streamingMetricsChecker)) {
try {
c.reap(client, server);
} catch (OperationsException | UnknownHostException e) {
// TODO: log?
}
}
}, 1, 5, MINUTES);
}

private static ObjectInstance prepareForRemote(final ObjectInstance i) {
Expand All @@ -65,7 +85,7 @@ private static ObjectName prepareForRemote(final ObjectName n) {
throw new IllegalArgumentException(n.toString());
}
}

@Override
public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException,
InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException {
Expand Down Expand Up @@ -286,25 +306,22 @@ public ClassLoaderRepository getClassLoaderRepository() {
}

static final Pattern tables = Pattern.compile("^\\*?((Index)?ColumnFamil(ies|y)|(Index)?(Table(s)?)?)$");

private boolean checkRegistrations(ObjectName name) {
private void checkRegistrations(ObjectName name) {
if (name != null && server.isRegistered(name)) {
return false;
return;
}

boolean result = false;


try {
String type = name != null ? name.getKeyProperty("type") : null;
if (type == null || tables.matcher(type).matches()) {
result |= ColumnFamilyStore.checkRegistration(client, server);
columnFamilyStoreChecker.check(client, server);
}
if (type == null || StreamingMetrics.TYPE_NAME.equals(type)) {
result |= StreamingMetrics.checkRegistration(client, server);
streamingMetricsChecker.check(client, server);
}
} catch (MalformedObjectNameException | UnknownHostException e) {
} catch (OperationsException | UnknownHostException e) {
// TODO: log
}
return result;
}
}
29 changes: 20 additions & 9 deletions src/main/java/org/apache/cassandra/db/ColumnFamilyStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import java.io.StringReader;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -50,6 +51,7 @@
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
Expand All @@ -65,6 +67,8 @@

import com.scylladb.jmx.api.APIClient;
import com.scylladb.jmx.metrics.MetricsMBean;
import com.scylladb.jmx.metrics.RegistrationChecker;
import com.scylladb.jmx.metrics.RegistrationMode;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
import com.google.common.base.Throwables;

Expand Down Expand Up @@ -182,15 +186,22 @@ private static ObjectName getName(String type, String keyspace, String name) thr
"org.apache.cassandra.db:type=" + type + ",keyspace=" + keyspace + ",columnfamily=" + name);
}

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++) {
JsonObject mbean = mbeans.getJsonObject(i);
all.add(getName(mbean.getString("type"), mbean.getString("ks"), mbean.getString("cf")));
}
return checkRegistration(server, all, n -> TYPE_NAMES.contains(n.getKeyProperty("type")), n -> new ColumnFamilyStore(client, n));
}
public static RegistrationChecker createRegistrationChecker() {
return new RegistrationChecker() {
@Override
protected void doCheck(APIClient client, JmxMBeanServer server, EnumSet<RegistrationMode> mode)
throws OperationsException {
JsonArray mbeans = client.getJsonArray("/column_family/");
Set<ObjectName> all = new HashSet<ObjectName>();
for (int i = 0; i < mbeans.size(); i++) {
JsonObject mbean = mbeans.getJsonObject(i);
all.add(getName(mbean.getString("type"), mbean.getString("ks"), mbean.getString("cf")));
}
checkRegistration(server, all, mode,
n -> TYPE_NAMES.contains(n.getKeyProperty("type")), n -> new ColumnFamilyStore(client, n));
}
};
}

/**
* @return the name of the column family
Expand Down
Loading

0 comments on commit 8d92e54

Please sign in to comment.