You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/07/16 01:09:46 UTC
[02/12] incubator-brooklyn git commit: brooklyn-qa: add org.apache
package prefix
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java
new file mode 100644
index 0000000..8a24f73
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.load;
+
+import static java.lang.String.format;
+
+import java.util.concurrent.Callable;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
+import brooklyn.entity.database.mysql.MySqlNode;
+import brooklyn.entity.database.mysql.MySqlNodeImpl;
+import brooklyn.entity.database.mysql.MySqlSshDriver;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.event.feed.function.FunctionFeed;
+import brooklyn.event.feed.function.FunctionPollConfig;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.system.ProcessTaskWrapper;
+import brooklyn.util.time.CountdownTimer;
+import brooklyn.util.time.Duration;
+
+/**
+ * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
+ */
+public class SimulatedMySqlNodeImpl extends MySqlNodeImpl {
+
+ public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
+ public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
+ public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
+
+ private FunctionFeed feed;
+
+ @Override
+ public Class<?> getDriverInterface() {
+ return SimulatedMySqlSshDriver.class;
+ }
+
+ @Override
+ protected void connectSensors() {
+ boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
+ if (simulateExternalMonitoring) {
+ setAttribute(DATASTORE_URL, String.format("mysql://%s:%s/", getAttribute(HOSTNAME), getAttribute(MYSQL_PORT)));
+
+ feed = FunctionFeed.builder()
+ .entity(this)
+ .period(Duration.FIVE_SECONDS)
+ .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_UP)
+ .callable(new Callable<Boolean>() {
+ private int counter = 0;
+ public Boolean call() {
+ setAttribute(QUERIES_PER_SECOND_FROM_MYSQL, (double)(counter++ % 100));
+ return true;
+ }})
+ .setOnFailureOrException(false))
+ .build();
+ } else {
+ super.connectSensors();
+ }
+ }
+
+ public static class SimulatedMySqlSshDriver extends MySqlSshDriver {
+
+ private int counter = 0;
+
+ public SimulatedMySqlSshDriver(SimulatedMySqlNodeImpl entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ // simulate metrics, for if using ssh polling
+ @Override
+ public String getStatusCmd() {
+ if (entity.getConfig(SIMULATE_ENTITY)) {
+ return "echo Uptime: 2427 Threads: 1 Questions: 581 Slow queries: 0 Opens: 53 Flush tables: 1 Open tables: 35 Queries per second avg: "+(counter++ % 100);
+ } else {
+ return super.getStatusCmd();
+ }
+ }
+
+ @Override
+ public void install() {
+ if (entity.getConfig(SKIP_SSH_ON_START)) {
+ // no-op
+ } else {
+ super.install();
+ }
+ }
+
+ // Not applying creation-script etc, as that requires launching msyqld (so would not scale for single-machine testing)
+ // This is a copy of super.customize, but with the mysqladmin-exec disabled
+ @Override
+ public void customize() {
+ if (!entity.getConfig(SIMULATE_ENTITY)) {
+ super.customize();
+ return;
+ } else if (entity.getConfig(SKIP_SSH_ON_START)) {
+ // no-op
+ } else {
+ copyDatabaseConfigScript();
+
+ newScript(CUSTOMIZING)
+ .updateTaskAndFailOnNonZeroResultCode()
+ .body.append(
+ "chmod 600 "+getConfigFile(),
+ getBaseDir()+"/scripts/mysql_install_db "+
+ "--basedir="+getBaseDir()+" --datadir="+getDataDir()+" "+
+ "--defaults-file="+getConfigFile())
+ .execute();
+
+ // launch, then we will configure it
+ launch();
+
+ CountdownTimer timer = Duration.seconds(20).countdownTimer();
+ boolean hasCreationScript = copyDatabaseCreationScript();
+ timer.waitForExpiryUnchecked();
+
+ // DELIBERATELY SKIPPED FOR SCALABILITY TESTING ON SINGLE MACHINE
+ DynamicTasks.queue(
+ SshEffectorTasks.ssh(
+ "cd "+getRunDir(),
+ "echo skipping exec of "+getBaseDir()+"/bin/mysqladmin --defaults-file="+getConfigFile()+" --password= password "+getPassword()
+ ).summary("setting password"));
+
+ if (hasCreationScript)
+ executeScriptFromInstalledFileAsync("creation-script.sql");
+
+ // not sure necessary to stop then subsequently launch, but seems safest
+ // (if skipping, use a flag in launch to indicate we've just launched it)
+ stop();
+ }
+ }
+
+ @Override
+ public void launch() {
+ if (!entity.getConfig(SIMULATE_ENTITY)) {
+ super.launch();
+ return;
+ }
+
+ entity.setAttribute(MySqlNode.PID_FILE, getRunDir() + "/" + AbstractSoftwareProcessSshDriver.PID_FILENAME);
+
+ if (entity.getConfig(SKIP_SSH_ON_START)) {
+ // minimal ssh, so that isRunning will subsequently work
+ newScript(MutableMap.of("usePidFile", true), LAUNCHING)
+ .body.append(
+ format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFile()))
+ .execute();
+ } else {
+ newScript(MutableMap.of("usePidFile", true), LAUNCHING)
+ .updateTaskAndFailOnNonZeroResultCode()
+ .body.append(format("echo skipping normal exec of nohup %s/bin/mysqld --defaults-file=%s --user=`whoami` > %s 2>&1 < /dev/null &", getBaseDir(), getConfigFile(), getLogFile()))
+ .body.append(format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFile()))
+ .execute();
+ }
+ }
+
+ @Override
+ public ProcessTaskWrapper<Integer> executeScriptFromInstalledFileAsync(String filenameAlreadyInstalledAtServer) {
+ return DynamicTasks.queue(
+ SshEffectorTasks.ssh(
+ "cd "+getRunDir(),
+ "echo skipping exec of "+getBaseDir()+"/bin/mysql --defaults-file="+getConfigFile()+" < "+filenameAlreadyInstalledAtServer)
+ .summary("executing datastore script "+filenameAlreadyInstalledAtServer));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java
new file mode 100644
index 0000000..8813a4f
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.load;
+
+import static java.lang.String.format;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Group;
+import brooklyn.entity.proxy.nginx.NginxControllerImpl;
+import brooklyn.entity.proxy.nginx.NginxSshDriver;
+import brooklyn.entity.proxy.nginx.UrlMapping;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.event.feed.ConfigToAttributes;
+import brooklyn.event.feed.function.FunctionFeed;
+import brooklyn.event.feed.function.FunctionPollConfig;
+import brooklyn.event.feed.http.HttpFeed;
+import brooklyn.event.feed.http.HttpPollConfig;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.policy.PolicySpec;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Networking;
+
+import com.google.common.base.Functions;
+
+/**
+ * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
+ */
+public class SimulatedNginxControllerImpl extends NginxControllerImpl {
+
+ public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
+ public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
+ public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
+
+ private HttpFeed httpFeed;
+ private FunctionFeed functionFeed;
+
+ @Override
+ public Class<?> getDriverInterface() {
+ return SimulatedNginxSshDriver.class;
+ }
+
+ @Override
+ public void connectSensors() {
+ boolean simulateEntity = getConfig(SIMULATE_ENTITY);
+ boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
+
+ if (!simulateEntity && !simulateExternalMonitoring) {
+ super.connectSensors();
+ return;
+ }
+
+ // From AbstractController.connectSensors
+ if (getUrl()==null) {
+ setAttribute(MAIN_URI, URI.create(inferUrl()));
+ setAttribute(ROOT_URL, inferUrl());
+ }
+ addServerPoolMemberTrackingPolicy();
+
+ // From NginxController.connectSensors
+ ConfigToAttributes.apply(this);
+
+ if (!simulateExternalMonitoring) {
+ // if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response
+ String uriToPoll = (simulateEntity) ? "http://localhost:8081" : getAttribute(MAIN_URI).toString();
+
+ httpFeed = HttpFeed.builder()
+ .entity(this)
+ .period(getConfig(HTTP_POLL_PERIOD))
+ .baseUri(uriToPoll)
+ .poll(new HttpPollConfig<Boolean>(SERVICE_UP)
+ .onSuccess(Functions.constant(true))
+ .onFailureOrException(Functions.constant(true)))
+ .build();
+ }
+
+ functionFeed = FunctionFeed.builder()
+ .entity(this)
+ .period(getConfig(HTTP_POLL_PERIOD))
+ .poll(new FunctionPollConfig<Boolean,Boolean>(SERVICE_UP)
+ .callable(new Callable<Boolean>() {
+ public Boolean call() {
+ return true;
+ }}))
+ .build();
+
+ // Can guarantee that parent/managementContext has been set
+ Group urlMappings = getConfig(URL_MAPPINGS);
+ if (urlMappings != null) {
+ // Listen to the targets of each url-mapping changing
+ subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() {
+ @Override public void onEvent(SensorEvent<Collection<String>> event) {
+ updateNeeded();
+ }
+ });
+
+ // Listen to url-mappings being added and removed
+ urlMappingsMemberTrackerPolicy = addPolicy(PolicySpec.create(UrlMappingsMemberTrackerPolicy.class)
+ .configure("group", urlMappings));
+ }
+ }
+
+ @Override
+ protected void disconnectSensors() {
+ super.disconnectSensors();
+ if (httpFeed != null) httpFeed.stop();
+ if (functionFeed != null) functionFeed.stop();
+ }
+
+ public static class SimulatedNginxSshDriver extends NginxSshDriver {
+ public SimulatedNginxSshDriver(SimulatedNginxControllerImpl entity, SshMachineLocation machine) {
+ super(entity, machine);
+ }
+
+ @Override
+ public void install() {
+ if (entity.getConfig(SKIP_SSH_ON_START)) {
+ // no-op
+ } else {
+ super.install();
+ }
+ }
+
+ @Override
+ public void customize() {
+ if (entity.getConfig(SKIP_SSH_ON_START)) {
+ // no-op
+ } else {
+ super.customize();
+ }
+ }
+
+ @Override
+ public void launch() {
+ if (!entity.getConfig(SIMULATE_ENTITY)) {
+ super.launch();
+ return;
+ }
+
+ Networking.checkPortsValid(MutableMap.of("httpPort", getPort()));
+
+ if (entity.getConfig(SKIP_SSH_ON_START)) {
+ // minimal ssh, so that isRunning will subsequently work
+ newScript(MutableMap.of("usePidFile", getPidFile()), LAUNCHING)
+ .body.append(
+ format("mkdir -p %s/logs", getRunDir()),
+ format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()))
+ .execute();
+ } else {
+ newScript(MutableMap.of("usePidFile", false), LAUNCHING)
+ .body.append(
+ format("cd %s", getRunDir()),
+ "echo skipping exec of requireExecutable ./sbin/nginx",
+ sudoBashCIfPrivilegedPort(getPort(), format(
+ "echo skipping exec of nohup ./sbin/nginx -p %s/ -c conf/server.conf > %s 2>&1 &", getRunDir(), getLogFileLocation())),
+ format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()),
+ format("echo $! > "+getPidFile()),
+ format("for i in {1..10}\n" +
+ "do\n" +
+ " test -f %1$s && ps -p `cat %1$s` && exit\n" +
+ " sleep 1\n" +
+ "done\n" +
+ "echo \"No explicit error launching nginx but couldn't find process by pid; continuing but may subsequently fail\"\n" +
+ "cat %2$s | tee /dev/stderr",
+ getPidFile(), getLogFileLocation()))
+ .execute();
+ }
+ }
+
+ // Use pid file, because just simulating the run of nginx
+ @Override
+ public void stop() {
+ newScript(MutableMap.of("usePidFile", getPidFile()), STOPPING).execute();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
new file mode 100644
index 0000000..273d130
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.load;
+
+import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady;
+import static brooklyn.event.basic.DependentConfiguration.formatString;
+
+import java.util.Collection;
+import java.util.List;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
+import brooklyn.enricher.HttpLatencyDetector;
+import brooklyn.entity.basic.AbstractApplication;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.database.mysql.MySqlNode;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.java.JavaEntityMethods;
+import brooklyn.entity.proxy.nginx.NginxController;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
+import brooklyn.entity.webapp.DynamicWebAppCluster;
+import brooklyn.entity.webapp.JavaWebAppService;
+import brooklyn.entity.webapp.WebAppService;
+import brooklyn.entity.webapp.WebAppServiceConstants;
+import brooklyn.entity.webapp.jboss.JBoss7Server;
+import brooklyn.launcher.BrooklynLauncher;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import brooklyn.util.CommandLineUtil;
+import brooklyn.util.collections.MutableSet;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+/**
+ * A 3-tier app where all components are just "simulated" - they don't actually run
+ * real app-servers or databases, instead just executing a "sleep" command to simulate
+ * the running process.
+ *
+ * This is useful for load testing, where we want to test the performance of Brooklyn
+ * rather than the ability to host many running app-servers.
+ *
+ * The app is based on WebClusterDatabaseExampleApp
+ *
+ * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
+ */
+public class SimulatedTheeTierApp extends AbstractApplication {
+
+ public static final ConfigKey<Boolean> SIMULATE_ENTITY = ConfigKeys.newBooleanConfigKey("simulateEntity", "", true);
+
+ public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = ConfigKeys.newBooleanConfigKey("simulateExternalMonitoring", "", true);
+
+ public static final ConfigKey<Boolean> SKIP_SSH_ON_START = ConfigKeys.newBooleanConfigKey("skipSshOnStart", "", false);
+
+ public static final String WAR_PATH = "classpath://hello-world.war";
+ public static final String DB_TABLE = "visitors";
+ public static final String DB_USERNAME = "brooklyn";
+ public static final String DB_PASSWORD = "br00k11n";
+ public static final boolean USE_HTTPS = false;
+
+ @Override
+ public void init() {
+ MySqlNode mysql = addChild(
+ EntitySpec.create(MySqlNode.class)
+ .impl(SimulatedMySqlNodeImpl.class));
+
+ ControlledDynamicWebAppCluster web = addChild(
+ EntitySpec.create(ControlledDynamicWebAppCluster.class)
+ .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class).impl(SimulatedJBoss7ServerImpl.class))
+ .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(NginxController.class).impl(SimulatedNginxControllerImpl.class))
+ .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+"))
+ .configure(JavaWebAppService.ROOT_WAR, WAR_PATH)
+ .configure(JavaEntityMethods.javaSysProp("brooklyn.example.db.url"),
+ formatString("jdbc:%s%s?user=%s\\&password=%s",
+ attributeWhenReady(mysql, MySqlNode.DATASTORE_URL), DB_TABLE, DB_USERNAME, DB_PASSWORD))
+ .configure(DynamicCluster.INITIAL_SIZE, 2)
+ .configure(WebAppService.ENABLED_PROTOCOLS, ImmutableSet.of(USE_HTTPS ? "https" : "http")) );
+
+ web.getCluster().addPolicy(AutoScalerPolicy.builder().
+ metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE).
+ metricRange(10, 100).
+ sizeRange(2, 5).
+ build());
+
+ addEnricher(Enrichers.builder()
+ .propagating(Attributes.MAIN_URI, WebAppServiceConstants.ROOT_URL,
+ DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW,
+ HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW)
+ .from(web)
+ .build());
+
+ addEnricher(Enrichers.builder()
+ .aggregating(Startable.SERVICE_UP)
+ .publishing(Startable.SERVICE_UP)
+ .fromHardcodedProducers(ImmutableList.of(web, mysql))
+ .computing(new Function<Collection<Boolean>, Boolean>() {
+ @Override public Boolean apply(Collection<Boolean> input) {
+ return input != null && input.size() == 2 && MutableSet.copyOf(input).equals(ImmutableSet.of(true));
+ }})
+ .build());
+ }
+
+ public static void main(String[] argv) {
+ List<String> args = Lists.newArrayList(argv);
+ String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+");
+ String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost");
+
+ BrooklynLauncher launcher = BrooklynLauncher.newInstance()
+ .application(EntitySpec.create(StartableApplication.class, SimulatedTheeTierApp.class)
+ .displayName("Brooklyn WebApp Cluster with Database example"))
+ .webconsolePort(port)
+ .location(location)
+ .start();
+
+ Entities.dumpInfo(launcher.getApplications());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java
new file mode 100644
index 0000000..7a3ea42
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.longevity;
+
+import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.chain;
+import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.noop;
+import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.toFile;
+import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.toLog;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.util.collections.TimeWindowedList;
+import brooklyn.util.collections.TimestampedValue;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Range;
+import com.google.common.io.Files;
+
+public class Monitor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Monitor.class);
+
+ private static final int checkPeriodMs = 1000;
+
+ private static final OptionParser parser = new OptionParser() {
+ {
+ acceptsAll(ImmutableList.of("help", "?", "h"), "show help");
+ accepts("webUrl", "Web-app url")
+ .withRequiredArg().ofType(URL.class);
+ accepts("brooklynPid", "Brooklyn pid")
+ .withRequiredArg().ofType(Integer.class);
+ accepts("logFile", "Brooklyn log file")
+ .withRequiredArg().ofType(File.class);
+ accepts("logGrep", "Grep in log file (defaults to 'SEVERE|ERROR|WARN|Exception|Error'")
+ .withRequiredArg().ofType(String.class);
+ accepts("logGrepExclusionsFile", "File of expressions to be ignored in log file")
+ .withRequiredArg().ofType(File.class);
+ accepts("webProcesses", "Name (for `ps ax | grep` of web-processes")
+ .withRequiredArg().ofType(String.class);
+ accepts("numWebProcesses", "Number of web-processes expected (e.g. 1 or 1-3)")
+ .withRequiredArg().ofType(String.class);
+ accepts("webProcessesCyclingPeriod", "The period (in seconds) for cycling through the range of numWebProcesses")
+ .withRequiredArg().ofType(Integer.class);
+ accepts("outFile", "File to write monitor status info")
+ .withRequiredArg().ofType(File.class);
+ accepts("abortOnError", "Exit the JVM on error, with exit code 1")
+ .withRequiredArg().ofType(Boolean.class);
+ }
+ };
+
+ public static void main(String[] argv) throws InterruptedException, IOException {
+ OptionSet options = parse(argv);
+
+ if (options == null || options.has("help")) {
+ parser.printHelpOn(System.out);
+ System.exit(0);
+ }
+
+ MonitorPrefs prefs = new MonitorPrefs();
+ prefs.webUrl = options.hasArgument("webUrl") ? (URL) options.valueOf("webUrl") : null;
+ prefs.brooklynPid = options.hasArgument("brooklynPid") ? (Integer) options.valueOf("brooklynPid") : -1;
+ prefs.logFile = options.hasArgument("logFile") ? (File) options.valueOf("logFile") : null;
+ prefs.logGrep = options.hasArgument("logGrep") ? (String) options.valueOf("logGrep") : "SEVERE|ERROR|WARN|Exception|Error";
+ prefs.logGrepExclusionsFile = options.hasArgument("logGrepExclusionsFile") ? (File) options.valueOf("logGrepExclusionsFile") : null;
+ prefs.webProcessesRegex = options.hasArgument("webProcesses") ? (String) options.valueOf("webProcesses") : null;
+ prefs.numWebProcesses = options.hasArgument("numWebProcesses") ? parseRange((String) options.valueOf("numWebProcesses")) : null;
+ prefs.webProcessesCyclingPeriod = options.hasArgument("webProcessesCyclingPeriod") ? (Integer) options.valueOf("webProcessesCyclingPeriod") : -1;
+ prefs.outFile = options.hasArgument("outFile") ? (File) options.valueOf("outFile") : null;
+ prefs.abortOnError = options.hasArgument("abortOnError") ? (Boolean) options.valueOf("abortOnError") : false;
+ Monitor main = new Monitor(prefs, MonitorListener.NOOP);
+ main.start();
+ }
+
+ private static Range<Integer> parseRange(String range) {
+ if (range.contains("-")) {
+ String[] parts = range.split("-");
+ return Range.closed(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
+ } else {
+ return Range.singleton(Integer.parseInt(range));
+ }
+ }
+
+ private static OptionSet parse(String...argv) {
+ try {
+ return parser.parse(argv);
+ } catch (Exception e) {
+ System.out.println("Error in parsing options: " + e.getMessage());
+ return null;
+ }
+ }
+
+ private final MonitorPrefs prefs;
+ private final StatusRecorder recorder;
+ private final MonitorListener listener;
+
+ public Monitor(MonitorPrefs prefs, MonitorListener listener) {
+ this.prefs = prefs;
+ this.listener = listener;
+ this.recorder = chain(toLog(LOG), (prefs.outFile != null ? toFile(prefs.outFile) : noop()));
+ }
+
+ private void start() throws IOException {
+ LOG.info("Monitoring: "+prefs);
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+
+ final AtomicReference<List<String>> previousLogLines = new AtomicReference<List<String>>(Collections.<String>emptyList());
+ final TimeWindowedList<Integer> numWebProcessesHistory = new TimeWindowedList<Integer>(
+ ImmutableMap.of("timePeriod", Duration.seconds(prefs.webProcessesCyclingPeriod), "minExpiredVals", 1));
+ final Set<String> logGrepExclusions = ImmutableSet.copyOf(Files.readLines(prefs.logGrepExclusionsFile, Charsets.UTF_8));
+
+ executor.scheduleAtFixedRate(new Runnable() {
+ @Override public void run() {
+ StatusRecorder.Record record = new StatusRecorder.Record();
+ StringBuilder failureMsg = new StringBuilder();
+ try {
+ if (prefs.brooklynPid > 0) {
+ boolean pidRunning = MonitorUtils.isPidRunning(prefs.brooklynPid, "java");
+ MonitorUtils.MemoryUsage memoryUsage = MonitorUtils.getMemoryUsage(prefs.brooklynPid, ".*brooklyn.*", 1000);
+ record.put("pidRunning", pidRunning);
+ record.put("totalMemoryBytes", memoryUsage.getTotalMemoryBytes());
+ record.put("totalMemoryInstances", memoryUsage.getTotalInstances());
+ record.put("instanceCounts", memoryUsage.getInstanceCounts());
+
+ if (!pidRunning) {
+ failureMsg.append("pid "+prefs.brooklynPid+" is not running"+"\n");
+ }
+ }
+ if (prefs.webUrl != null) {
+ boolean webUrlUp = MonitorUtils.isUrlUp(prefs.webUrl);
+ record.put("webUrlUp", webUrlUp);
+
+ if (!webUrlUp) {
+ failureMsg.append("web URL "+prefs.webUrl+" is not available"+"\n");
+ }
+ }
+ if (prefs.logFile != null) {
+ List<String> logLines = MonitorUtils.searchLog(prefs.logFile, prefs.logGrep, logGrepExclusions);
+ List<String> newLogLines = getAdditions(previousLogLines.get(), logLines);
+ previousLogLines.set(logLines);
+ record.put("logLines", newLogLines);
+
+ if (newLogLines.size() > 0) {
+ failureMsg.append("Log contains warnings/errors: "+newLogLines+"\n");
+ }
+ }
+ if (prefs.webProcessesRegex != null) {
+ List<Integer> pids = MonitorUtils.getRunningPids(prefs.webProcessesRegex, "--webProcesses");
+ pids.remove((Object)MonitorUtils.findOwnPid());
+
+ record.put("webPids", pids);
+ record.put("numWebPids", pids.size());
+ numWebProcessesHistory.add(pids.size());
+
+ if (prefs.numWebProcesses != null) {
+ boolean numWebPidsInRange = prefs.numWebProcesses.apply(pids.size());
+ record.put("numWebPidsInRange", numWebPidsInRange);
+
+ if (!numWebPidsInRange) {
+ failureMsg.append("num web processes out-of-range: pids="+pids+"; size="+pids.size()+"; expected="+prefs.numWebProcesses);
+ }
+
+ if (prefs.webProcessesCyclingPeriod > 0) {
+ List<TimestampedValue<Integer>> values = numWebProcessesHistory.getValues();
+ long valuesTimeRange = (values.get(values.size()-1).getTimestamp() - values.get(0).getTimestamp());
+ if (values.size() > 0 && valuesTimeRange > SECONDS.toMillis(prefs.webProcessesCyclingPeriod)) {
+ int min = -1;
+ int max = -1;
+ for (TimestampedValue<Integer> val : values) {
+ min = (min < 0) ? val.getValue() : Math.min(val.getValue(), min);
+ max = Math.max(val.getValue(), max);
+ }
+ record.put("minWebSizeInPeriod", min);
+ record.put("maxWebSizeInPeriod", max);
+
+ if (min > prefs.numWebProcesses.lowerEndpoint() || max < prefs.numWebProcesses.upperEndpoint()) {
+ failureMsg.append("num web processes not increasing/decreasing correctly: " +
+ "pids="+pids+"; size="+pids.size()+"; cyclePeriod="+prefs.webProcessesCyclingPeriod+
+ "; expectedRange="+prefs.numWebProcesses+"; min="+min+"; max="+max+"; history="+values);
+ }
+ } else {
+ int numVals = values.size();
+ long startTime = (numVals > 0) ? values.get(0).getTimestamp() : 0;
+ long endTime = (numVals > 0) ? values.get(values.size()-1).getTimestamp() : 0;
+ LOG.info("Insufficient vals in time-window to determine cycling behaviour over period ("+prefs.webProcessesCyclingPeriod+"secs): "+
+ "numVals="+numVals+"; startTime="+startTime+"; endTime="+endTime+"; periodCovered="+(endTime-startTime)/1000);
+ }
+ }
+ }
+ }
+
+ } catch (Throwable t) {
+ LOG.error("Error during periodic checks", t);
+ throw Throwables.propagate(t);
+ }
+
+ try {
+ recorder.record(record);
+ listener.onRecord(record);
+
+ if (failureMsg.length() > 0) {
+ listener.onFailure(record, failureMsg.toString());
+
+ if (prefs.abortOnError) {
+ LOG.error("Aborting on error: "+failureMsg);
+ System.exit(1);
+ }
+ }
+
+ } catch (Throwable t) {
+ LOG.warn("Error recording monitor info ("+record+")", t);
+ throw Throwables.propagate(t);
+ }
+ }
+ }, 0, checkPeriodMs, TimeUnit.MILLISECONDS);
+ }
+
+ // TODO What is the guava equivalent? Don't want Set.difference, because duplicates/ordered.
+ private static List<String> getAdditions(List<String> prev, List<String> next) {
+ List<String> result = Lists.newArrayList(next);
+ result.removeAll(prev);
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java
new file mode 100644
index 0000000..11fdd3f
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.longevity;
+
+import org.apache.brooklyn.qa.longevity.StatusRecorder.Record;
+
+public interface MonitorListener {
+
+ public static final MonitorListener NOOP = new MonitorListener() {
+ @Override public void onRecord(Record record) {
+ }
+ @Override public void onFailure(Record record, String msg) {
+ }
+ };
+
+ public void onRecord(Record record);
+
+ public void onFailure(Record record, String msg);
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java
new file mode 100644
index 0000000..4d74045
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.longevity;
+
+import java.io.File;
+import java.net.URL;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Range;
+
+public class MonitorPrefs {
+
+ public URL webUrl;
+ public int brooklynPid;
+ public File logFile;
+ public String logGrep;
+ public File logGrepExclusionsFile;
+ public String webProcessesRegex;
+ public Range<Integer> numWebProcesses;
+ public int webProcessesCyclingPeriod;
+ public File outFile;
+ public boolean abortOnError;
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("webUrl", webUrl)
+ .add("brooklynPid", brooklynPid)
+ .add("logFile", logFile)
+ .add("logGrep", logGrep)
+ .add("logGrepExclusionsFile", logGrepExclusionsFile)
+ .add("outFile", outFile)
+ .add("webProcessesRegex", webProcessesRegex)
+ .add("numWebProcesses", numWebProcesses)
+ .add("webProcessesCyclingPeriod", webProcessesCyclingPeriod)
+ .toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java
new file mode 100644
index 0000000..54acb3b
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.longevity;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.util.http.HttpTool;
+import brooklyn.util.http.HttpToolResponse;
+import brooklyn.util.stream.StreamGobbler;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+
+public class MonitorUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MonitorUtils.class);
+
+ private static volatile int ownPid = -1;
+
+ /**
+ * Confirm can read from URL.
+ *
+ * @param url
+ */
+ public static boolean isUrlUp(URL url) {
+ try {
+ HttpToolResponse result = HttpTool.httpGet(
+ HttpTool.httpClientBuilder().trustAll().build(),
+ URI.create(url.toString()),
+ ImmutableMap.<String,String>of());
+ int statuscode = result.getResponseCode();
+
+ if (statuscode != 200) {
+ LOG.info("Error reading URL {}: {}, {}", new Object[]{url, statuscode, result.getReasonPhrase()});
+ return false;
+ } else {
+ return true;
+ }
+ } catch (Exception e) {
+ LOG.info("Error reading URL {}: {}", url, e);
+ return false;
+ }
+ }
+
+ public static boolean isPidRunning(int pid) {
+ return isPidRunning(pid, null);
+ }
+
+ /**
+ * Confirm the given pid is running, and that the the process matches the given regex.
+ *
+ * @param pid
+ * @param regex
+ */
+ public static boolean isPidRunning(int pid, String regex) {
+ Process process = exec("ps -p " + pid);
+ String out = waitFor(process);
+ if (process.exitValue() > 0) {
+ String err = toString(process.getErrorStream());
+ LOG.info(String.format("pid %s not running: %s", pid, err));
+ return false;
+ }
+
+ if (regex != null) {
+ String regex2 = "^\\s*" + pid + ".*" + regex;
+ boolean found = false;
+ for (String line : out.split("\n")) {
+ if (hasAtLeastOneMatch(line, regex2)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ String txt = toString(process.getInputStream());
+ LOG.info("process did not match regular expression: "+txt);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean hasAtLeastOneMatch(String line, String regex) {
+ return Pattern.matches(".*"+regex+".*", line);
+ }
+
+ private static String toString(InputStream in){
+ try {
+ byte[] bytes = ByteStreams.toByteArray(in);
+ return new String(bytes);
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+
+ }
+
+ public static List<Integer> getRunningPids(String regex) {
+ return getRunningPids(regex, null);
+ }
+
+ /**
+ * Confirm the given pid is running, and that the the process matches the given regex.
+ *
+ * @param regex
+ * @param excludingRegex
+ */
+ public static List<Integer> getRunningPids(String regex, String excludingRegex) {
+ Process process = exec("ps ax");
+ String out = waitFor(process);
+
+ List<Integer> result = new LinkedList<Integer>();
+ for (String line : out.split("\n")) {
+ if (excludingRegex != null && hasAtLeastOneMatch(line, excludingRegex)) {
+ continue;
+ }
+ if (hasAtLeastOneMatch(line, regex)) {
+ String[] linesplit = line.trim().split("\\s+");
+ result.add(Integer.parseInt(linesplit[0]));
+ }
+ }
+ return result;
+ }
+
+ public static MemoryUsage getMemoryUsage(int pid){
+ return getMemoryUsage(pid, null,0);
+ }
+
+ /**
+ * @param pid
+ */
+ public static MemoryUsage getMemoryUsage(int pid, String clazzRegexOfInterest, int minInstancesOfInterest) {
+ Process process = exec(String.format("jmap -histo %s", pid));
+ String out = waitFor(process);
+
+ Map<String, Integer> instanceCounts = Maps.newLinkedHashMap();
+ long totalInstances=0;
+ long totalMemoryBytes=0;
+
+ for (String line : out.split("\n")) {
+ if (clazzRegexOfInterest!=null && hasAtLeastOneMatch(line, clazzRegexOfInterest)) {
+ // Format is:
+ // num #instances #bytes class name
+ // 1: 43506 8047096 example.MyClazz
+
+ String[] parts = line.trim().split("\\s+");
+ String clazz = parts[3];
+ int instanceCount = Integer.parseInt(parts[1]);
+ if (instanceCount >= minInstancesOfInterest) {
+ instanceCounts.put(clazz, instanceCount);
+ }
+ }
+ if (hasAtLeastOneMatch(line, "^Total.*")) {
+ String[] parts = line.split("\\s+");
+ totalInstances = Long.parseLong(parts[1]);
+ totalMemoryBytes = Long.parseLong(parts[2]);
+ }
+ }
+
+ return new MemoryUsage(totalInstances, totalMemoryBytes, instanceCounts);
+ }
+
+ public static class MemoryUsage {
+ final long totalInstances;
+ final long totalMemoryBytes;
+ final Map<String, Integer> instanceCounts;
+
+ MemoryUsage(long totalInstances, long totalMemoryBytes, Map<String, Integer> instanceCounts) {
+ this.totalInstances = totalInstances;
+ this.totalMemoryBytes = totalMemoryBytes;
+ this.instanceCounts = instanceCounts;
+ }
+
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("totalInstances", totalInstances)
+ .add("totalMemoryBytes", totalMemoryBytes)
+ .add("instanceCounts", instanceCounts)
+ .toString();
+ }
+
+ public long getTotalInstances() {
+ return totalInstances;
+ }
+
+ public long getTotalMemoryBytes() {
+ return totalMemoryBytes;
+ }
+
+ public Map<String, Integer> getInstanceCounts() {
+ return instanceCounts;
+ }
+ }
+
+ public static List<String> searchLog(File file, String grepOfInterest) {
+ return searchLog(file, grepOfInterest, new LinkedHashSet<String>());
+ }
+
+ /**
+ * Find lines in the given file that match given given regex.
+ *
+ * @param file
+ * @param grepOfInterest
+ */
+ public static List<String> searchLog(File file, String grepOfInterest, Set<String> grepExclusions) {
+ Process process = exec(String.format("grep -E %s %s", grepOfInterest, file.getAbsoluteFile()));
+ String out = waitFor(process);
+
+ // TODO Annoying that String.split() returns size 1 when empty string; lookup javadoc when back online...
+ if (out.length() == 0) return Collections.<String>emptyList();
+
+ List<String> result = new ArrayList<String>();
+ for (String line : out.trim().split("\n")) {
+ boolean excluded = false;
+ for (String exclusion : grepExclusions) {
+ if (!isNullOrEmpty(exclusion) && hasAtLeastOneMatch(line, exclusion)) {
+ excluded = true;
+ }
+ }
+ if (!excluded) {
+ result.add(line);
+ }
+ }
+ return result;
+ }
+
+ public static Process exec(String cmd) {
+ LOG.info("executing cmd: " + cmd);
+
+ try {
+ return Runtime.getRuntime().exec(cmd);
+ } catch (IOException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ public static class ProcessHasStderr extends IllegalStateException {
+ private static final long serialVersionUID = -937871002993888405L;
+
+ byte[] stderrBytes;
+ public ProcessHasStderr(byte[] stderrBytes) {
+ this("Process printed to stderr: " + new String(stderrBytes), stderrBytes);
+ }
+ public ProcessHasStderr(String message, byte[] stderrBytes) {
+ super(message);
+ this.stderrBytes = stderrBytes;
+ }
+ }
+
+ /**
+ * Waits for the given process to complete, consuming its stdout and returning it as a string.
+ * If there is any output on stderr an exception will be thrown.
+ */
+ public static String waitFor(Process process) {
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+ @SuppressWarnings("resource") //Closeable doesn't seem appropriate for StreamGobbler since it isn't expected to be called every time
+ StreamGobbler gobblerOut = new StreamGobbler(process.getInputStream(), bytesOut, null);
+ gobblerOut.start();
+
+ ByteArrayOutputStream bytesErr = new ByteArrayOutputStream();
+ @SuppressWarnings("resource")
+ StreamGobbler gobblerErr = new StreamGobbler(process.getErrorStream(), bytesErr, null);
+ gobblerErr.start();
+
+ try {
+ process.waitFor();
+ gobblerOut.blockUntilFinished();
+ gobblerErr.blockUntilFinished();
+
+ if (bytesErr.size() > 0) {
+ throw new ProcessHasStderr(bytesErr.toByteArray());
+ }
+
+ return new String(bytesOut.toByteArray());
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ } finally {
+ if (gobblerOut.isAlive()) gobblerOut.interrupt();
+ if (gobblerErr.isAlive()) gobblerErr.interrupt();
+ }
+ }
+
+ public static int findOwnPid() throws IOException {
+ if (ownPid >= 0) return ownPid;
+
+ String[] cmd = new String[]{"bash", "-c", "echo $PPID"};
+ Process process = Runtime.getRuntime().exec(cmd);
+ String out = MonitorUtils.waitFor(process);
+ ownPid = Integer.parseInt(out.trim());
+ return ownPid;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java
new file mode 100644
index 0000000..210e0a2
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.brooklyn.qa.longevity;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.slf4j.Logger;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+public interface StatusRecorder {
+
+ public void record(Record record) throws IOException;
+
+ public static class Factory {
+ public static final StatusRecorder NOOP = new StatusRecorder() {
+ @Override public void record(Record record) {}
+ };
+
+ public static StatusRecorder noop() {
+ return NOOP;
+ }
+ public static StatusRecorder toFile(File outFile) {
+ return new FileBasedStatusRecorder(outFile);
+ }
+ public static StatusRecorder toSysout() {
+ return new SysoutBasedStatusRecorder();
+ }
+ public static StatusRecorder toLog(Logger log) {
+ return new LogBasedStatusRecorder(log);
+ }
+ public static StatusRecorder chain(StatusRecorder...recorders) {
+ return new ChainingStatusRecorder(recorders);
+ }
+ }
+
+ public static class Record {
+ private final Map<String,Object> fields = Maps.newLinkedHashMap();
+
+ public void putAll(Map<String,?> entries) {
+ fields.putAll(entries);
+ }
+
+ public void putAll(String keyPrefix, Map<String,?> entries) {
+ for (Map.Entry<String,?> entry : entries.entrySet()) {
+ fields.put(keyPrefix+entry.getKey(), entry.getValue());
+ }
+ }
+
+ public void put(String key, Object val) {
+ fields.put(key, val);
+ }
+
+ @Override
+ public String toString() {
+ return fields.toString();
+ }
+ }
+
+ public static class FileBasedStatusRecorder implements StatusRecorder {
+ private final File outFile;
+
+ public FileBasedStatusRecorder(File outFile) {
+ this.outFile = outFile;
+ }
+
+ @Override
+ public void record(Record record) throws IOException {
+ Files.append(record.fields.toString()+"\n", outFile, Charsets.UTF_8);
+ }
+ }
+
+ public static class SysoutBasedStatusRecorder implements StatusRecorder {
+ public SysoutBasedStatusRecorder() {
+ }
+
+ @Override
+ public void record(Record record) {
+ System.out.println(record.fields);
+ }
+ }
+
+ public static class LogBasedStatusRecorder implements StatusRecorder {
+ private final Logger log;
+
+ public LogBasedStatusRecorder(Logger log) {
+ this.log = log;
+ }
+
+ @Override
+ public void record(Record record) {
+ log.info("{}", record.fields);
+ }
+ }
+
+ public static class ChainingStatusRecorder implements StatusRecorder {
+ private final StatusRecorder[] recorders;
+
+ public ChainingStatusRecorder(StatusRecorder... recorders) {
+ this.recorders = recorders;
+ }
+
+ @Override
+ public void record(Record record) throws IOException {
+ for (StatusRecorder recorder : recorders) {
+ recorder.record(record);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java b/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java
deleted file mode 100644
index 16a0195..0000000
--- a/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF 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 brooklyn.qa.load;
-
-import static org.testng.Assert.assertEquals;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.StartableApplication;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.rebind.persister.PersistMode;
-import brooklyn.entity.trait.Startable;
-import brooklyn.launcher.BrooklynLauncher;
-import brooklyn.location.Location;
-import brooklyn.management.ManagementContext;
-import brooklyn.management.ha.HighAvailabilityMode;
-import brooklyn.management.internal.LocalManagementContext;
-import brooklyn.test.PerformanceTestUtils;
-import brooklyn.util.os.Os;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-/**
- * Customers ask about the scalability of Brooklyn. These load tests investigate how many
- * concurrent apps can be deployed and managed by a single Brooklyn management node.
- *
- * The apps are "simulated" in that they don't create the underlying resources
- * (we are not checking if the test machine can run 100s of app-servers simultaneously!)
- * The install/customize/launch will instead execute ssh commands of comparable length,
- * but that just echo rather than execute the actual commands.
- *
- * "SIMULATE_EXTERNAL_MONITORING" means that we do not poll the entities directly (over ssh, http or
- * whatever). Instead we simulate the metrics being injected directly to be set on the entity (e.g.
- * having been collected from a Graphite server).
- *
- * "SKIP_SSH_ON_START" means don't do the normal install+customize+launch ssh commands. Instead, just
- * startup the entities so we can monitor their resource usage.
- */
-public class LoadTest {
-
- // TODO Could/should issue provisioning request through REST api, rather than programmatically;
- // and poll to detect completion.
-
- /*
- * Useful commands when investigating:
- * LOG_FILE=usage/qa/brooklyn-camp-tests.log
- * grep -E "OutOfMemoryError|[P|p]rovisioning time|sleeping before|CPU fraction|LoadTest using" $LOG_FILE | less
- * grep -E "OutOfMemoryError|[P|p]rovisioning time" $LOG_FILE; grep "CPU fraction" $LOG_FILE | tail -1; grep "LoadTest using" $LOG_FILE | tail -1
- * grep -E "OutOfMemoryError|LoadTest using" $LOG_FILE
- */
- private static final Logger LOG = LoggerFactory.getLogger(LoadTest.class);
-
- private File persistenceDir;
- private BrooklynLauncher launcher;
- private String webServerUrl;
- private ManagementContext managementContext;
- private ListeningExecutorService executor;
- private Future<?> cpuFuture;
-
- private Location localhost;
-
- List<Duration> provisioningTimes;
-
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() throws Exception {
- // Create management node
- persistenceDir = Files.createTempDir();
- launcher = BrooklynLauncher.newInstance()
- .persistMode(PersistMode.CLEAN)
- .highAvailabilityMode(HighAvailabilityMode.MASTER)
- .persistenceDir(persistenceDir)
- .start();
- webServerUrl = launcher.getServerDetails().getWebServerUrl();
- managementContext = launcher.getServerDetails().getManagementContext();
-
- localhost = managementContext.getLocationRegistry().resolve("localhost");
-
- provisioningTimes = Collections.synchronizedList(Lists.<Duration>newArrayList());
-
- // Create executors
- executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
-
- // Monitor utilisation (memory/CPU) while tests run
- executor.submit(new Callable<Void>() {
- public Void call() {
- try {
- while (true) {
- managementContext.getExecutionManager(); // force GC to be instantiated
- String usage = ((LocalManagementContext)managementContext).getGarbageCollector().getUsageString();
- LOG.info("LoadTest using "+usage);
- Thread.sleep(1000);
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt(); // exit gracefully
- } catch (Exception e) {
- LOG.error("Error getting usage info", e);
- }
- return null;
- }});
-
- cpuFuture = PerformanceTestUtils.sampleProcessCpuTime(Duration.ONE_SECOND, "during testProvisionAppsConcurrently");
-
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() throws Exception {
- if (cpuFuture != null) cpuFuture.cancel(true);
- if (executor != null) executor.shutdownNow();
- if (launcher != null) launcher.terminate();
- if (persistenceDir != null) Os.deleteRecursively(persistenceDir);
- }
-
- /**
- * Creates multiple apps simultaneously.
- *
- * Long-term target is 50 concurrent provisioning requests (which may be issued while there are
- * many existing applications under management). Until we reach that point, we can partition the
- * load across multiple (separate) brooklyn management nodes.
- * TODO TBD: is that 50 VMs worth, or 50 apps with 4 VMs in each?
- *
- * TODO Does not measure the cost of jclouds for creating all the VMs/containers.
- */
- @Test(groups="Acceptance")
- public void testLocalhostProvisioningAppsConcurrently() throws Exception {
- final int NUM_CONCURRENT_APPS_PROVISIONING = 20;
-
- List<ListenableFuture<StartableApplication>> futures = Lists.newArrayList();
- for (int i = 0; i < NUM_CONCURRENT_APPS_PROVISIONING; i++) {
- ListenableFuture<StartableApplication> future = executor.submit(newProvisionAppTask(managementContext,
- EntitySpec.create(StartableApplication.class, SimulatedTheeTierApp.class)
- .configure(SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING, true)
- .displayName("Simulated app "+i)));
- futures.add(future);
- }
-
- List<StartableApplication> apps = Futures.allAsList(futures).get();
-
- for (StartableApplication app : apps) {
- assertEquals(app.getAttribute(Startable.SERVICE_UP), (Boolean)true);
- }
- }
-
- /**
- * Creates many apps, to monitor resource usage etc.
- *
- * "SIMULATE_EXTERNAL_MONITORING" means that we do not poll the entities directly (over ssh, http or
- * whatever). Instead we simulate the metrics being injected directly to be set on the entity (e.g.
- * having been collected from a Graphite server).
- *
- * Long-term target is 2500 VMs under management.
- * Until we reach that point, we can partition the load across multiple (separate) brooklyn management nodes.
- */
- @Test(groups="Acceptance")
- public void testLocalhostManyApps() throws Exception {
- final int NUM_APPS = 630; // target is 2500 VMs; each blueprint has 4 (rounding up)
- final int NUM_APPS_PER_BATCH = 10;
- final int SLEEP_BETWEEN_BATCHES = 10*1000;
- final boolean SKIP_SSH_ON_START = true; // getting ssh errors otherwise!
-
- int counter = 0;
-
- for (int i = 0; i < NUM_APPS / NUM_APPS_PER_BATCH; i++) {
- List<ListenableFuture<StartableApplication>> futures = Lists.newArrayList();
- for (int j = 0; j < NUM_APPS_PER_BATCH; j++) {
- ListenableFuture<StartableApplication> future = executor.submit(newProvisionAppTask(
- managementContext,
- EntitySpec.create(StartableApplication.class, SimulatedTheeTierApp.class)
- .configure(SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING, true)
- .configure(SimulatedTheeTierApp.SKIP_SSH_ON_START, SKIP_SSH_ON_START)
- .displayName("Simulated app "+(++counter))));
- futures.add(future);
- }
-
- List<StartableApplication> apps = Futures.allAsList(futures).get();
-
- for (StartableApplication app : apps) {
- assertEquals(app.getAttribute(Startable.SERVICE_UP), (Boolean)true);
- }
-
- synchronized (provisioningTimes) {
- LOG.info("cycle="+i+"; numApps="+counter+": provisioning times: "+provisioningTimes);
- provisioningTimes.clear();
- }
-
- LOG.info("cycle="+i+"; numApps="+counter+": sleeping before next batch of apps");
- Thread.sleep(SLEEP_BETWEEN_BATCHES);
- }
- }
-
- protected <T extends StartableApplication> Callable<T> newProvisionAppTask(final ManagementContext managementContext, final EntitySpec<T> entitySpec) {
- return new Callable<T>() {
- public T call() {
- Stopwatch stopwatch = Stopwatch.createStarted();
- T app = managementContext.getEntityManager().createEntity(entitySpec);
- Entities.startManagement(app, managementContext);
- app.start(ImmutableList.of(localhost));
- Duration duration = Duration.of(stopwatch.elapsed(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
- LOG.info("Provisioning time: "+duration);
- provisioningTimes.add(duration);
-
- return app;
- }
- };
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java b/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java
deleted file mode 100644
index 2f1e854..0000000
--- a/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF 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 brooklyn.qa.longevity;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import org.testng.annotations.Test;
-
-import brooklyn.qa.longevity.MonitorUtils.ProcessHasStderr;
-import brooklyn.util.os.Os;
-import brooklyn.util.text.Strings;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.Files;
-
-public class MonitorUtilsTest {
-
- @Test(enabled=false, timeOut=1000) // Demonstrates that process.waitFor() hangs for big output streams
- public void testExecuteAndWaitFor() throws Exception {
- Process process = createDumpingProcess(false);
- process.waitFor();
- fail("Should block while waiting to consume process output");
- }
-
- @Test(enabled=false, timeOut=1000) // Demonstrates that process.waitFor() hangs for big err streams
- public void testExecuteAndWaitForErr() throws Exception {
- Process process = createDumpingProcess(true);
- process.waitFor();
- fail("Should block while waiting to consume process output");
- }
-
- @Test(timeOut=1000)
- public void testExecuteAndWaitForConsumingOutputStream() throws Exception {
- Process process = createDumpingProcess(false);
- String out = MonitorUtils.waitFor(process);
- assertTrue(out.length() > 100000, "out.size="+out.length());
- }
-
- @Test(timeOut=1000, expectedExceptions=IllegalStateException.class)
- public void testExecuteAndWaitForConsumingErrorStream() throws Exception {
- Process process = createDumpingProcess(true);
- MonitorUtils.waitFor(process);
- }
-
- private Process createDumpingProcess(boolean writeToErr) throws IOException {
- String errSuffix = writeToErr ? " >&2" : "";
- //Windows limits the length of the arguments so echo multiple times instead
- String bigstr = Strings.repeat("a", 8000);
- String bigcmd = Strings.repeat(getSilentPrefix() + "echo " + bigstr + errSuffix + Os.LINE_SEPARATOR, 15);
- File file = Os.newTempFile("test-consume", ".bat");
- file.setExecutable(true);
- Files.write(bigcmd, file, Charsets.UTF_8);
- Process process = MonitorUtils.exec(file.getAbsolutePath());
- return process;
- }
-
- @Test(groups="UNIX")
- public void testFindOwnPid() throws Exception {
- int ownpid = MonitorUtils.findOwnPid();
- assertTrue(ownpid > 0, "ownpid=$ownpid");
- assertTrue(MonitorUtils.isPidRunning(ownpid, "java"),"java is not running");
- }
-
- @Test(groups="UNIX")
- public void testIsPidRunning() throws Exception {
- int usedPid = MonitorUtils.findOwnPid();
-
- //the child process will terminate freeing it PID
- String[] cmd = new String[]{"bash", "-c", "echo $$"};
- Process process = Runtime.getRuntime().exec(cmd);
- String out = MonitorUtils.waitFor(process);
- int unusedPid = Integer.parseInt(out.trim());
-
- assertTrue(MonitorUtils.isPidRunning(usedPid));
- assertFalse(MonitorUtils.isPidRunning(unusedPid));
-
- try {
- assertFalse(MonitorUtils.isPidRunning(1234567)); // too large
- } catch (ProcessHasStderr e) {
- // expected on osx
- }
- }
-
- @Test(groups="UNIX")
- public void testGetRunningPids() throws Exception {
- int ownpid = MonitorUtils.findOwnPid();
-
- List<Integer> javapids = MonitorUtils.getRunningPids("java");
-
- assertTrue(javapids.contains(ownpid), "javapids="+javapids+"; ownpid="+ownpid);
- }
-
- @Test
- public void testIsUrlUp() throws Exception {
- assertFalse(MonitorUtils.isUrlUp(new URL("http://localhost/thispathdoesnotexist")));
- }
-
- @Test(groups="UNIX")
- public void testSearchLog() throws Exception {
- String fileContents = "line1\nline2\nline3\n";
- File file = File.createTempFile("monitorUtilsTest.testSearchLog", ".txt");
- Files.write(fileContents, file, Charsets.UTF_8);
-
- try {
- assertEquals(MonitorUtils.searchLog(file, "line1"), Arrays.asList("line1"));
- assertEquals(MonitorUtils.searchLog(file, "line1|line2"), Arrays.asList("line1", "line2"));
- assertEquals(MonitorUtils.searchLog(file, "textnotthere"), Collections.emptyList());
- assertEquals(MonitorUtils.searchLog(file, "line"), Arrays.asList("line1", "line2", "line3"));
- } finally {
- file.delete();
- }
- }
-
- @Test(groups="Integration")
- public void testMemoryUsage() throws Exception {
- int ownpid = MonitorUtils.findOwnPid();
-
- MonitorUtils.MemoryUsage memUsage = MonitorUtils.getMemoryUsage(ownpid);
- assertTrue(memUsage.totalInstances > 0, memUsage.toString());
- assertTrue(memUsage.totalMemoryBytes > 0, memUsage.toString());
- assertEquals(memUsage.getInstanceCounts(), Collections.emptyMap());
-
- MonitorUtils.MemoryUsage memUsage2 = MonitorUtils.getMemoryUsage(ownpid, MonitorUtilsTest.class.getCanonicalName(),0);
- assertEquals(memUsage2.getInstanceCounts(), ImmutableMap.of(MonitorUtilsTest.class.getCanonicalName(), 1));
-
- MonitorUtils.MemoryUsage memUsage3 = MonitorUtils.getMemoryUsage(ownpid, MonitorUtilsTest.class.getCanonicalName(), 2);
- assertEquals(memUsage3.getInstanceCounts(), Collections.emptyMap());
- }
-
- private String getSilentPrefix() {
- if (Os.isMicrosoftWindows()) {
- return "@";
- } else {
- return "";
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java b/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java
deleted file mode 100644
index 392b976..0000000
--- a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF 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 brooklyn.qa.longevity.webcluster;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.enricher.basic.AbstractEnricher;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EntityLocal;
-import brooklyn.event.AttributeSensor;
-
-import com.google.common.base.Throwables;
-import com.google.common.reflect.TypeToken;
-
-/**
- * Periodically publishes values in the range of 0 to #amplitude.
- * The value varies sinusoidally over time.
- */
-public class SinusoidalLoadGenerator extends AbstractEnricher {
-
- private static final Logger LOG = LoggerFactory.getLogger(SinusoidalLoadGenerator.class);
-
- public static final ConfigKey<AttributeSensor<Double>> TARGET = ConfigKeys.newConfigKey(new TypeToken<AttributeSensor<Double>>() {}, "target");
-
- public static final ConfigKey<Long> PUBLISH_PERIOD_MS = ConfigKeys.newLongConfigKey("publishPeriodMs");
-
- public static final ConfigKey<Long> SIN_PERIOD_MS = ConfigKeys.newLongConfigKey("sinPeriodMs");
-
- public static final ConfigKey<Double> SIN_AMPLITUDE = ConfigKeys.newDoubleConfigKey("sinAmplitude");
-
- private final ScheduledExecutorService executor;
-
- public SinusoidalLoadGenerator() {
- this.executor = Executors.newSingleThreadScheduledExecutor();
- }
-
- public SinusoidalLoadGenerator(AttributeSensor<Double> target, long publishPeriodMs, long sinPeriodMs, double sinAmplitude) {
- config().set(TARGET, target);
- config().set(PUBLISH_PERIOD_MS, publishPeriodMs);
- config().set(SIN_PERIOD_MS, sinPeriodMs);
- config().set(SIN_AMPLITUDE, sinAmplitude);
- this.executor = Executors.newSingleThreadScheduledExecutor();
- }
-
- @Override
- public void setEntity(final EntityLocal entity) {
- super.setEntity(entity);
-
- executor.scheduleAtFixedRate(new Runnable() {
- @Override public void run() {
- try {
- long time = System.currentTimeMillis();
- double val = getRequiredConfig(SIN_AMPLITUDE) * (1 + Math.sin( (1.0*time) / getRequiredConfig(SIN_PERIOD_MS) * Math.PI * 2 - Math.PI/2 )) / 2;
- entity.setAttribute(getRequiredConfig(TARGET), val);
- } catch (Throwable t) {
- LOG.warn("Error generating sinusoidal-load metric", t);
- throw Throwables.propagate(t);
- }
- }
-
- }, 0, getRequiredConfig(PUBLISH_PERIOD_MS), TimeUnit.MILLISECONDS);
- }
-
- @Override
- public void destroy() {
- executor.shutdownNow();
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java b/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
deleted file mode 100644
index a223861..0000000
--- a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF 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 brooklyn.qa.longevity.webcluster;
-
-import java.util.List;
-
-import brooklyn.config.BrooklynProperties;
-import brooklyn.enricher.Enrichers;
-import brooklyn.entity.basic.AbstractApplication;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.StartableApplication;
-import brooklyn.entity.proxy.nginx.NginxController;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
-import brooklyn.entity.webapp.jboss.JBoss7Server;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.Sensors;
-import brooklyn.launcher.BrooklynLauncher;
-import brooklyn.policy.EnricherSpec;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
-import brooklyn.util.CommandLineUtil;
-
-import com.google.common.collect.Lists;
-
-public class WebClusterApp extends AbstractApplication {
-
- static BrooklynProperties config = BrooklynProperties.Factory.newDefault();
-
- public static final String WAR_PATH = "classpath://hello-world.war";
-
- private static final long loadCyclePeriodMs = 2 * 60 * 1000L;
-
- @Override
- public void initApp() {
- final AttributeSensor<Double> sinusoidalLoad =
- Sensors.newDoubleSensor("brooklyn.qa.sinusoidalLoad", "Sinusoidal server load");
- AttributeSensor<Double> averageLoad =
- Sensors.newDoubleSensor("brooklyn.qa.averageLoad", "Average load in cluster");
-
- NginxController nginxController = addChild(EntitySpec.create(NginxController.class)
- // .configure("domain", "webclusterexample.brooklyn.local")
- .configure("port", "8000+"));
-
- EntitySpec<JBoss7Server> jbossSpec = EntitySpec.create(JBoss7Server.class)
- .configure("httpPort", "8080+")
- .configure("war", WAR_PATH)
- .enricher(EnricherSpec.create(SinusoidalLoadGenerator.class)
- .configure(SinusoidalLoadGenerator.TARGET, sinusoidalLoad)
- .configure(SinusoidalLoadGenerator.PUBLISH_PERIOD_MS, 500L)
- .configure(SinusoidalLoadGenerator.SIN_PERIOD_MS, loadCyclePeriodMs)
- .configure(SinusoidalLoadGenerator.SIN_AMPLITUDE, 1d));
-
- ControlledDynamicWebAppCluster web = addChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
- .displayName("WebApp cluster")
- .configure("controller", nginxController)
- .configure("initialSize", 1)
- .configure("memberSpec", jbossSpec));
-
- web.getCluster().addEnricher(Enrichers.builder()
- .aggregating(sinusoidalLoad)
- .publishing(averageLoad)
- .fromMembers()
- .computingAverage()
- .build());
- web.getCluster().addPolicy(AutoScalerPolicy.builder()
- .metric(averageLoad)
- .sizeRange(1, 3)
- .metricRange(0.3, 0.7)
- .build());
- }
-
- public static void main(String[] argv) {
- List<String> args = Lists.newArrayList(argv);
- String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+");
- String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost");
-
- BrooklynLauncher launcher = BrooklynLauncher.newInstance()
- .application(EntitySpec.create(StartableApplication.class, WebClusterApp.class).displayName("Brooklyn WebApp Cluster example"))
- .webconsolePort(port)
- .location(location)
- .start();
-
- Entities.dumpInfo(launcher.getApplications());
- }
-}