Java tutorial
/** * Copyright 2005-2014 Red Hat, Inc. * * Red Hat licenses this file to you 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 io.fabric8.insight.metrics.service; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import io.fabric8.api.*; import io.fabric8.api.scr.ValidatingReference; import io.fabric8.common.util.JMXUtils; import io.fabric8.groups.Group; import io.fabric8.groups.GroupListener; import io.fabric8.groups.NodeState; import io.fabric8.groups.internal.TrackingZooKeeperGroup; import io.fabric8.insight.metrics.model.*; import io.fabric8.insight.metrics.service.support.JmxUtils; import org.apache.felix.scr.annotations.*; import org.apache.karaf.jaas.boot.principal.RolePrincipal; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.security.auth.Subject; import java.io.IOException; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.*; import java.util.concurrent.*; import static io.fabric8.common.util.IOHelpers.loadFully; import static io.fabric8.insight.metrics.model.MetricsJSON.parseJson; /** * Collects all the charting metrics defined against its profiles */ @Component(immediate = true, metatype = false) @Service({ MetricsCollectorMBean.class }) public class MetricsCollector implements MetricsCollectorMBean { public static final String GRAPH_JSON = "io.fabric8.insight.metrics.json"; public static final String QUERIES = "queries"; public static final String NAME = "name"; public static final String TEMPLATE = "template"; public static final String METADATA = "metadata"; public static final String LOCK = "lock"; public static final String PERIOD = "period"; public static final String MIN_PERIOD = "minPeriod"; public static final String REQUESTS = "requests"; public static final String OBJ = "obj"; public static final String ATTRS = "attrs"; public static final String OPER = "oper"; public static final String ARGS = "args"; public static final String SIG = "sig"; public static final String DEFAULT = "default"; public static final String LOCK_GLOBAL = "global"; public static final String LOCK_HOST = "host"; private static final transient Logger LOG = LoggerFactory.getLogger(MetricsCollector.class); private ObjectName objectName; @Reference private FabricService fabricService; private ScheduledThreadPoolExecutor executor; private Map<Query, QueryState> queries = new ConcurrentHashMap<Query, QueryState>(); @Reference private MBeanServer mbeanServer; @Reference(name = "storage", referenceInterface = MetricsStorageService.class) private final ValidatingReference<MetricsStorageService> storage = new ValidatingReference<>(); private int defaultDelay = 60; private int threadPoolSize = 5; private String type = "sta"; private BundleContext bundleContext; static class QueryState { ScheduledFuture<?> future; Server server; Query query; QueryResult lastResult; boolean lastResultSent; long lastSent; Map metadata; Group<QueryNodeState> lock; public void close() { future.cancel(false); if (lock != null) { try { lock.close(); } catch (IOException e) { // Ignore } } } } @Activate private void activate(BundleContext bundleContext) throws Exception { this.bundleContext = bundleContext; this.executor = new ScheduledThreadPoolExecutor(threadPoolSize); this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); this.executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); this.executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { process(); } }, 1, defaultDelay, TimeUnit.SECONDS); JMXUtils.registerMBean(this, mbeanServer, new ObjectName("io.fabric8.insight:type=MetricsCollector")); } @Deactivate private void deactivate() throws Exception { JMXUtils.unregisterMBean(mbeanServer, new ObjectName("io.fabric8.insight:type=MetricsCollector")); executor.shutdown(); try { executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { // Ignore } executor.shutdownNow(); try { executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { // Ignore } for (QueryState q : queries.values()) { q.close(); } } private void bindStorage(MetricsStorageService storage) { this.storage.bind(storage); } private void unbindStorage(MetricsStorageService storage) { this.storage.unbind(storage); } static class QueryNodeState extends NodeState { @JsonProperty String[] services; QueryNodeState() { } QueryNodeState(String id, String container, String[] services) { super(id, container); this.services = services; } } public void setDefaultDelay(int defaultDelay) { this.defaultDelay = defaultDelay; } public void setThreadPoolSize(int threadPoolSize) { this.threadPoolSize = threadPoolSize; } public void setType(String type) { this.type = type; } @Override public String getMetrics() { Map<String, Object> meta = new HashMap<String, Object>(); for (Map.Entry<Query, QueryState> e : queries.entrySet()) { meta.put(e.getKey().getName(), e.getValue().metadata); } return MetricsJSON.toJson(meta); } public void process() { try { Container container = MetricsCollector.this.fabricService.getCurrentContainer(); if (container != null) { Set<Query> newQueries = new HashSet<Query>(); Profile[] profiles = container.getProfiles(); if (profiles != null) { for (Profile profile : profiles) { loadProfile(profile, newQueries); } } for (Query q : queries.keySet()) { if (!newQueries.remove(q)) { queries.remove(q).close(); } } Server server = new Server(container.getId()); for (Query q : newQueries) { final String queryName = q.getName(); final String containerName = container.getId(); final QueryState state = new QueryState(); state.server = server; state.query = q; if (q.getMetadata() != null) { state.metadata = parseJson(loadFully(new URL(q.getMetadata()))); } // Clustered stats ? if (q.getLock() != null) { state.lock = new TrackingZooKeeperGroup<>(bundleContext, getGroupPath(q), QueryNodeState.class); state.lock.add(new GroupListener<QueryNodeState>() { @Override public void groupEvent(Group<QueryNodeState> group, GroupEvent event) { try { state.lock.update(new QueryNodeState(queryName, containerName, state.lock.isMaster() ? new String[] { "stat" } : null)); } catch (IllegalStateException e) { // not joined ? ignore } } }); state.lock.update(new QueryNodeState(queryName, containerName, null)); state.lock.start(); } long delay = q.getPeriod() > 0 ? q.getPeriod() : defaultDelay; state.future = this.executor.scheduleAtFixedRate(new Task(state, storage), Math.round(Math.random() * 1000) + 1, delay * 1000, TimeUnit.MILLISECONDS); queries.put(q, state); } } } catch (RejectedExecutionException t) { // Ignore, the thread pool has been shut down } catch (Throwable t) { LOG.warn("Error while starting metrics", t); } } protected synchronized String getGroupPath(Query q) { if (LOCK_GLOBAL.equals(q.getLock())) { return "/fabric/registry/clusters/insight-metrics/global/" + q.getName(); } else if (LOCK_HOST.equals(q.getLock())) { String host; try { host = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { throw new IllegalStateException("Unable to retrieve host name", e); } return "/fabric/registry/clusters/insight-metrics/host-" + host + "/" + q.getName(); } else { throw new IllegalArgumentException("Unknown lock type: " + q.getLock()); } } protected void loadProfile(Profile profile, Set<Query> queries) { Map<String, byte[]> fileConfigurations = profile.getFileConfigurations(); byte[] bytes = fileConfigurations.get(GRAPH_JSON); if (bytes != null && bytes.length > 0) { try { Map object = new ObjectMapper().readValue(bytes, Map.class); for (Map q : (List<Map>) object.get(QUERIES)) { String name = (String) q.get(NAME); String template = (String) q.get(TEMPLATE); String metadata = (String) q.get(METADATA); String lock = (String) q.get(LOCK); int period = DEFAULT.equals(q.get(PERIOD)) ? defaultDelay : q.get(PERIOD) != null ? ((Number) q.get(PERIOD)).intValue() : defaultDelay; int minPeriod = DEFAULT.equals(q.get(MIN_PERIOD)) ? defaultDelay : q.get(MIN_PERIOD) != null ? ((Number) q.get(MIN_PERIOD)).intValue() : period; Set<Request> requests = new HashSet<Request>(); for (Map mb : (List<Map>) q.get(REQUESTS)) { if (mb.containsKey(ATTRS)) { String mname = (String) mb.get(NAME); String mobj = (String) mb.get(OBJ); List<String> mattrs = (List<String>) mb.get(ATTRS); requests.add(new MBeanAttrs(mname, mobj, mattrs)); } else if (mb.containsKey(OPER)) { String mname = (String) mb.get(NAME); String mobj = (String) mb.get(OBJ); String moper = (String) mb.get(OPER); List<Object> margs = (List<Object>) mb.get(ARGS); List<String> msig = (List<String>) mb.get(SIG); requests.add(new MBeanOpers(mname, mobj, moper, margs, msig)); } else { throw new IllegalArgumentException("Unknown request " + MetricsJSON.toJson(mb)); } } queries.add(new Query(name, requests, template, metadata, lock, period, minPeriod)); } } catch (Throwable t) { LOG.warn("Unable to load queries from profile " + profile.getId(), t); } } ProfileService profileService = MetricsCollector.this.fabricService.adapt(ProfileService.class); Version version = profileService.getVersion(profile.getVersion()); for (String parentId : profile.getParentIds()) { loadProfile(version.getRequiredProfile(parentId), queries); } } class Task implements Runnable { private final QueryState query; public Task(QueryState query, ValidatingReference<MetricsStorageService> storage) { this.query = query; } @Override public void run() { try { final MetricsStorageService svc = storage.get(); // Abort if required services aren't available if (mbeanServer == null || svc == null) { return; } // If there's a lock, check we are the master if (query.lock != null && !query.lock.isMaster()) { return; } Subject subject = new Subject(); subject.getPrincipals().add(new RolePrincipal("admin")); QueryResult qrs = Subject.doAsPrivileged(subject, new PrivilegedAction<QueryResult>() { @Override public QueryResult run() { try { return JmxUtils.execute(query.server, query.query, mbeanServer); } catch (Throwable e) { LOG.error("Error sending metrics", e); } return null; } }, AccessController.getContext()); if (qrs != null) { boolean forceSend = query.query.getMinPeriod() == query.query.getPeriod() || qrs.getTimestamp().getTime() - query.lastSent >= TimeUnit.SECONDS .toMillis(query.query.getMinPeriod()); if (!forceSend && query.lastResult != null) { if (qrs.getResults().equals(query.lastResult.getResults())) { query.lastResult = qrs; query.lastResultSent = false; } if (!query.lastResultSent) { renderAndSend(svc, query.lastResult); } } query.lastResult = qrs; query.lastResultSent = true; query.lastSent = qrs.getTimestamp().getTime(); renderAndSend(svc, qrs); } } catch (Throwable e) { LOG.error("Error sending metrics", e); } } private void renderAndSend(MetricsStorageService svc, QueryResult qrs) throws Exception { long timestamp = qrs.getTimestamp().getTime(); svc.store(type, timestamp, qrs); } } }