diff --git a/core/pom.xml b/core/pom.xml
index e95a72aab5b..a3ef70aeda2 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -37,6 +37,11 @@
cloud-engine-api
${project.version}
+
+ org.apache.cloudstack
+ cloud-engine-schema
+ ${project.version}
+
org.apache.cloudstack
cloud-framework-security
diff --git a/core/src/main/java/com/cloud/agent/api/HostStatsEntry.java b/core/src/main/java/com/cloud/agent/api/HostStatsEntry.java
index 82041a604e8..5fd1c515b6e 100644
--- a/core/src/main/java/com/cloud/agent/api/HostStatsEntry.java
+++ b/core/src/main/java/com/cloud/agent/api/HostStatsEntry.java
@@ -20,10 +20,12 @@
package com.cloud.agent.api;
import com.cloud.host.HostStats;
+import com.cloud.host.HostVO;
public class HostStatsEntry implements HostStats {
long hostId;
+ HostVO hostVo;
String entityType;
double cpuUtilization;
double networkReadKBs;
@@ -112,4 +114,16 @@ public class HostStatsEntry implements HostStats {
public void setHostId(long hostId) {
this.hostId = hostId;
}
+
+ public long getHostId() {
+ return hostId;
+ }
+
+ public HostVO getHostVo() {
+ return hostVo;
+ }
+
+ public void setHostVo(HostVO hostVo) {
+ this.hostVo = hostVo;
+ }
}
diff --git a/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java b/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java
index e6063b9ab5b..9f8280898ee 100644
--- a/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java
+++ b/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java
@@ -19,22 +19,25 @@
package com.cloud.agent.api;
+import com.cloud.vm.UserVmVO;
import com.cloud.vm.VmStats;
public class VmStatsEntry implements VmStats {
- double cpuUtilization;
- double networkReadKBs;
- double networkWriteKBs;
- double diskReadIOs;
- double diskWriteIOs;
- double diskReadKBs;
- double diskWriteKBs;
- double memoryKBs;
- double intfreememoryKBs;
- double targetmemoryKBs;
- int numCPUs;
- String entityType;
+ private long vmId;
+ private UserVmVO userVmVO;
+ private double cpuUtilization;
+ private double networkReadKBs;
+ private double networkWriteKBs;
+ private double diskReadIOs;
+ private double diskWriteIOs;
+ private double diskReadKBs;
+ private double diskWriteKBs;
+ private double memoryKBs;
+ private double intfreememoryKBs;
+ private double targetmemoryKBs;
+ private int numCPUs;
+ private String entityType;
public VmStatsEntry() {
}
@@ -50,14 +53,12 @@ public class VmStatsEntry implements VmStats {
this.entityType = entityType;
}
- public VmStatsEntry(double cpuUtilization, double networkReadKBs, double networkWriteKBs, double diskReadKBs, double diskWriteKBs, int numCPUs, String entityType) {
- this.cpuUtilization = cpuUtilization;
- this.networkReadKBs = networkReadKBs;
- this.networkWriteKBs = networkWriteKBs;
- this.diskReadKBs = diskReadKBs;
- this.diskWriteKBs = diskWriteKBs;
- this.numCPUs = numCPUs;
- this.entityType = entityType;
+ public long getVmId() {
+ return vmId;
+ }
+
+ public void setVmId(long vmId) {
+ this.vmId = vmId;
}
@Override
@@ -166,4 +167,12 @@ public class VmStatsEntry implements VmStats {
this.entityType = entityType;
}
+ public UserVmVO getUserVmVO() {
+ return userVmVO;
+ }
+
+ public void setUserVmVO(UserVmVO userVmVO) {
+ this.userVmVO = userVmVO;
+ }
+
}
diff --git a/server/pom.xml b/server/pom.xml
index e2461508932..19873835bd6 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -157,6 +157,11 @@
org.opensaml
opensaml
+
+ org.influxdb
+ influxdb-java
+ 2.8
+
diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java
index b66fa5f0600..8e2bc7e12a8 100644
--- a/server/src/main/java/com/cloud/server/StatsCollector.java
+++ b/server/src/main/java/com/cloud/server/StatsCollector.java
@@ -20,6 +20,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -46,8 +47,15 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.utils.graphite.GraphiteClient;
import org.apache.cloudstack.utils.graphite.GraphiteException;
import org.apache.cloudstack.utils.usage.UsageUtils;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
+import org.influxdb.InfluxDB;
+import org.influxdb.InfluxDBFactory;
+import org.influxdb.dto.BatchPoints;
+import org.influxdb.dto.Point;
+import org.influxdb.dto.Pong;
import org.springframework.stereotype.Component;
import com.cloud.agent.AgentManager;
@@ -121,6 +129,7 @@ import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.MacAddress;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmManager;
@@ -140,7 +149,7 @@ import com.cloud.vm.dao.VMInstanceDao;
public class StatsCollector extends ManagerBase implements ComponentMethodInterceptable, Configurable {
public static enum ExternalStatsProtocol {
- NONE("none"), GRAPHITE("graphite");
+ NONE("none"), GRAPHITE("graphite"), INFLUXDB("influxdb");
String _type;
ExternalStatsProtocol(String type) {
@@ -155,16 +164,52 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
public static final Logger s_logger = Logger.getLogger(StatsCollector.class.getName());
- static final ConfigKey vmDiskStatsInterval = new ConfigKey("Advanced", Integer.class, "vm.disk.stats.interval", "0",
+ private static final int UNDEFINED_PORT_VALUE = -1;
+
+ /**
+ * Default value for the Graphite connection port: {@value}
+ */
+ private static final int GRAPHITE_DEFAULT_PORT = 2003;
+
+ /**
+ * Default value for the InfluxDB connection port: {@value}
+ */
+ private static final int INFLUXDB_DEFAULT_PORT = 8086;
+
+ private static final String UUID_TAG = "uuid";
+
+ private static final String TOTAL_MEMORY_KBS_FIELD = "total_memory_kb";
+ private static final String FREE_MEMORY_KBS_FIELD = "free_memory_kb";
+ private static final String CPU_UTILIZATION_FIELD = "cpu_utilization";
+ private static final String CPUS_FIELD = "cpus";
+ private static final String CPU_SOCKETS_FIELD = "cpu_sockets";
+ private static final String NETWORK_READ_KBS_FIELD = "network_read_kbs";
+ private static final String NETWORK_WRITE_KBS_FIELD = "network_write_kbs";
+ private static final String MEMORY_TARGET_KBS_FIELD = "memory_target_kbs";
+ private static final String DISK_READ_IOPS_FIELD = "disk_read_iops";
+ private static final String DISK_READ_KBS_FIELD = "disk_read_kbs";
+ private static final String DISK_WRITE_IOPS_FIELD = "disk_write_iops";
+ private static final String DISK_WRITE_KBS_FIELD = "disk_write_kbs";
+
+ private static final String DEFAULT_DATABASE_NAME = "cloudstack";
+ private static final String INFLUXDB_HOST_MEASUREMENT = "host_stats";
+ private static final String INFLUXDB_VM_MEASUREMENT = "vm_stats";
+
+ private static final ConfigKey vmDiskStatsInterval = new ConfigKey("Advanced", Integer.class, "vm.disk.stats.interval", "0",
"Interval (in seconds) to report vm disk statistics. Vm disk statistics will be disabled if this is set to 0 or less than 0.", false);
- static final ConfigKey vmDiskStatsIntervalMin = new ConfigKey("Advanced", Integer.class, "vm.disk.stats.interval.min", "300",
+ private static final ConfigKey vmDiskStatsIntervalMin = new ConfigKey("Advanced", Integer.class, "vm.disk.stats.interval.min", "300",
"Minimal interval (in seconds) to report vm disk statistics. If vm.disk.stats.interval is smaller than this, use this to report vm disk statistics.", false);
- static final ConfigKey vmNetworkStatsInterval = new ConfigKey("Advanced", Integer.class, "vm.network.stats.interval", "0",
+ private static final ConfigKey vmNetworkStatsInterval = new ConfigKey("Advanced", Integer.class, "vm.network.stats.interval", "0",
"Interval (in seconds) to report vm network statistics (for Shared networks). Vm network statistics will be disabled if this is set to 0 or less than 0.", false);
- static final ConfigKey vmNetworkStatsIntervalMin = new ConfigKey("Advanced", Integer.class, "vm.network.stats.interval.min", "300",
- "Minimal Interval (in seconds) to report vm network statistics (for Shared networks). If vm.network.stats.interval is smaller than this, use this to report vm network statistics.", false);
- static final ConfigKey StatsTimeout = new ConfigKey("Advanced", Integer.class, "stats.timeout", "60000",
- "The timeout for stats call in milli seconds.", true, ConfigKey.Scope.Cluster);
+ private static final ConfigKey vmNetworkStatsIntervalMin = new ConfigKey("Advanced", Integer.class, "vm.network.stats.interval.min", "300",
+ "Minimal Interval (in seconds) to report vm network statistics (for Shared networks). If vm.network.stats.interval is smaller than this, use this to report vm network statistics.",
+ false);
+ private static final ConfigKey StatsTimeout = new ConfigKey("Advanced", Integer.class, "stats.timeout", "60000",
+ "The timeout for stats call in milli seconds.", true,
+ ConfigKey.Scope.Cluster);
+ private static final ConfigKey statsOutputUri = new ConfigKey("Advanced", String.class, "stats.output.uri", "",
+ "URI to send StatsCollector statistics to. The collector is defined on the URI scheme. Example: graphite://graphite-hostaddress:port or influxdb://influxdb-hostaddress/dbname. Note that the port is optional, if not added the default port for the respective collector (graphite or influxdb) will be used. Additionally, the database name '/dbname' is also optional; default db name is 'cloudstack'. You must create and configure the database if using influxdb.",
+ true);
private static StatsCollector s_instance = null;
@@ -236,20 +281,20 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
private ConcurrentHashMap _storageStats = new ConcurrentHashMap();
private ConcurrentHashMap _storagePoolStats = new ConcurrentHashMap();
- long hostStatsInterval = -1L;
- long hostAndVmStatsInterval = -1L;
- long storageStatsInterval = -1L;
- long volumeStatsInterval = -1L;
- long autoScaleStatsInterval = -1L;
+ private long hostStatsInterval = -1L;
+ private long hostAndVmStatsInterval = -1L;
+ private long storageStatsInterval = -1L;
+ private long volumeStatsInterval = -1L;
+ private long autoScaleStatsInterval = -1L;
- List hostIds = null;
private double _imageStoreCapacityThreshold = 0.90;
- String externalStatsPrefix = "";
+ private String externalStatsPrefix = "";
String externalStatsHost = null;
int externalStatsPort = -1;
- boolean externalStatsEnabled = false;
+ private String externalStatsScheme;
ExternalStatsProtocol externalStatsType = ExternalStatsProtocol.NONE;
+ private String databaseName = DEFAULT_DATABASE_NAME;
private ScheduledExecutorService _diskStatsUpdateExecutor;
private int _usageAggregationRange = 1440;
@@ -279,7 +324,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
return true;
}
- private void init(Map configs) {
+ protected void init(Map configs) {
_executor = Executors.newScheduledThreadPool(6, new NamedThreadFactory("StatsCollector"));
hostStatsInterval = NumbersUtil.parseLong(configs.get("host.stats.interval"), 60000L);
@@ -288,24 +333,25 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
volumeStatsInterval = NumbersUtil.parseLong(configs.get("volume.stats.interval"), 600000L);
autoScaleStatsInterval = NumbersUtil.parseLong(configs.get("autoscale.stats.interval"), 60000L);
- /* URI to send statistics to. Currently only Graphite is supported */
- String externalStatsUri = configs.get("stats.output.uri");
- if (externalStatsUri != null && !externalStatsUri.equals("")) {
+ String statsUri = statsOutputUri.value();
+ if (StringUtils.isNotBlank(statsUri)) {
try {
- URI uri = new URI(externalStatsUri);
- String scheme = uri.getScheme();
+ URI uri = new URI(statsUri);
+ externalStatsScheme = uri.getScheme();
try {
- externalStatsType = ExternalStatsProtocol.valueOf(scheme.toUpperCase());
+ externalStatsType = ExternalStatsProtocol.valueOf(externalStatsScheme.toUpperCase());
} catch (IllegalArgumentException e) {
- s_logger.info(scheme + " is not a valid protocol for external statistics. No statistics will be send.");
+ s_logger.error(externalStatsScheme + " is not a valid protocol for external statistics. No statistics will be send.");
}
- if (!StringUtils.isEmpty(uri.getHost())) {
+ if (StringUtils.isNotEmpty(uri.getHost())) {
externalStatsHost = uri.getHost();
}
- externalStatsPort = uri.getPort();
+ externalStatsPort = retrieveExternalStatsPortFromUri(uri);
+
+ databaseName = configureDatabaseName(uri);
if (!StringUtils.isEmpty(uri.getPath())) {
externalStatsPrefix = uri.getPath().substring(1);
@@ -318,9 +364,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
externalStatsPrefix = "";
}
- externalStatsEnabled = true;
} catch (URISyntaxException e) {
- s_logger.debug("Failed to parse external statistics URI: " + e.getMessage());
+ s_logger.error("Failed to parse external statistics URI: ", e);
}
}
@@ -342,7 +387,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
if (vmDiskStatsInterval.value() > 0) {
if (vmDiskStatsInterval.value() < vmDiskStatsIntervalMin.value()) {
- s_logger.debug("vm.disk.stats.interval - " + vmDiskStatsInterval.value() + " is smaller than vm.disk.stats.interval.min - " + vmDiskStatsIntervalMin.value() + ", so use vm.disk.stats.interval.min");
+ s_logger.debug("vm.disk.stats.interval - " + vmDiskStatsInterval.value() + " is smaller than vm.disk.stats.interval.min - " + vmDiskStatsIntervalMin.value()
+ + ", so use vm.disk.stats.interval.min");
_executor.scheduleAtFixedRate(new VmDiskStatsTask(), vmDiskStatsIntervalMin.value(), vmDiskStatsIntervalMin.value(), TimeUnit.SECONDS);
} else {
_executor.scheduleAtFixedRate(new VmDiskStatsTask(), vmDiskStatsInterval.value(), vmDiskStatsInterval.value(), TimeUnit.SECONDS);
@@ -353,7 +399,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
if (vmNetworkStatsInterval.value() > 0) {
if (vmNetworkStatsInterval.value() < vmNetworkStatsIntervalMin.value()) {
- s_logger.debug("vm.network.stats.interval - " + vmNetworkStatsInterval.value() + " is smaller than vm.network.stats.interval.min - " + vmNetworkStatsIntervalMin.value() + ", so use vm.network.stats.interval.min");
+ s_logger.debug("vm.network.stats.interval - " + vmNetworkStatsInterval.value() + " is smaller than vm.network.stats.interval.min - "
+ + vmNetworkStatsIntervalMin.value() + ", so use vm.network.stats.interval.min");
_executor.scheduleAtFixedRate(new VmNetworkStatsTask(), vmNetworkStatsIntervalMin.value(), vmNetworkStatsIntervalMin.value(), TimeUnit.SECONDS);
} else {
_executor.scheduleAtFixedRate(new VmNetworkStatsTask(), vmNetworkStatsInterval.value(), vmNetworkStatsInterval.value(), TimeUnit.SECONDS);
@@ -411,82 +458,111 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
}
- class HostCollector extends ManagedContextRunnable {
+ /**
+ * Configures the database name according to the URI path. For instance, if the URI is as influxdb://address:port/dbname, the database name will be 'dbname'.
+ */
+ protected String configureDatabaseName(URI uri) {
+ String dbname = StringUtils.removeStart(uri.getPath(), "/");
+ if (StringUtils.isBlank(dbname)) {
+ return DEFAULT_DATABASE_NAME;
+ } else {
+ return dbname;
+ }
+ }
+
+ /**
+ * Configures the port to be used when connecting with the stats collector service.
+ * Default values are 8086 for influx DB and 2003 for GraphiteDB.
+ * Throws URISyntaxException in case of non configured port and external StatsType
+ */
+ protected int retrieveExternalStatsPortFromUri(URI uri) throws URISyntaxException {
+ int port = uri.getPort();
+ if (externalStatsType != ExternalStatsProtocol.NONE) {
+ if (port != UNDEFINED_PORT_VALUE) {
+ return port;
+ }
+ if (externalStatsType == ExternalStatsProtocol.GRAPHITE) {
+ return GRAPHITE_DEFAULT_PORT;
+ }
+ if (externalStatsType == ExternalStatsProtocol.INFLUXDB) {
+ return INFLUXDB_DEFAULT_PORT;
+ }
+ }
+ throw new URISyntaxException(uri.toString(), String.format(
+ "Cannot define a port for the Stats Collector host %s://%s:%s or URI scheme is incorrect. The configured URI in stats.output.uri is not supported. Please configure as the following examples: graphite://graphite-hostaddress:port, or influxdb://influxdb-hostaddress:port. Note that the port is optional, if not added the default port for the respective collector (graphite or influxdb) will be used.",
+ externalStatsPrefix, externalStatsHost, externalStatsPort));
+ }
+
+ class HostCollector extends AbstractStatsCollector {
@Override
protected void runInContext() {
try {
s_logger.debug("HostStatsCollector is running...");
- SearchCriteria sc = _hostDao.createSearchCriteria();
- sc.addAnd("status", SearchCriteria.Op.EQ, Status.Up.toString());
- sc.addAnd("resourceState", SearchCriteria.Op.NIN, ResourceState.Maintenance, ResourceState.PrepareForMaintenance, ResourceState.ErrorInMaintenance);
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.Storage.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.ConsoleProxy.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.SecondaryStorage.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.LocalSecondaryStorage.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.TrafficMonitor.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.SecondaryStorageVM.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.ExternalFirewall.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.ExternalLoadBalancer.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.NetScalerControlCenter.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.L2Networking.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.BaremetalDhcp.toString());
- sc.addAnd("type", SearchCriteria.Op.NEQ, Host.Type.BaremetalPxe.toString());
- ConcurrentHashMap hostStats = new ConcurrentHashMap();
+ SearchCriteria sc = createSearchCriteriaForHostTypeRoutingStateUpAndNotInMaintenance();
+
+ Map