mirror of
https://github.com/NationalSecurityAgency/timely.git
synced 2026-11-10 03:41:25 -10:00
Compare commits
26
Commits
1.0.0
...
TIMELY-163
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccb5643d77 | ||
|
|
4d8966dedb | ||
|
|
bb1afd6cda | ||
|
|
a1656e0f1d | ||
|
|
ddc18b57aa | ||
|
|
49a4f6573b | ||
|
|
22b3a1e918 | ||
|
|
5daf1ef6f5 | ||
|
|
436fe806aa | ||
|
|
3d80b880d9 | ||
|
|
2fd1496e9a | ||
|
|
f8cc045b3e | ||
|
|
d481c629c2 | ||
|
|
24823c7fe6 | ||
|
|
8e6cc4e0b4 | ||
|
|
3a3d98ec81 | ||
|
|
7599a2ac60 | ||
|
|
3d33ba2e31 | ||
|
|
bbbb453818 | ||
|
|
54951c05e6 | ||
|
|
b63914e02c | ||
|
|
1f386db00a | ||
|
|
49222ac76a | ||
|
|
38f2c6cdfa | ||
|
|
3926e6f066 | ||
|
|
30800a0b5c |
No files matched your search
+1
-1
@@ -23,7 +23,7 @@ under the License.
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<properties>
|
||||
<flink.version>1.1.4</flink.version>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
export TRUSTSTORE_FILE=
|
||||
export TRUSTSTORE_TYPE="jks";
|
||||
export TRUSTSTORE_PASS=
|
||||
export KEYSTORE_FILE=
|
||||
export KEYSTORE_TYPE="pkcs12";
|
||||
export KEYSTORE_PASS=
|
||||
|
||||
export NUM_QUERY_THREADS=4
|
||||
export NUM_INGEST_THREADS=4
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
BASE_DIR=${THIS_DIR}/../..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
|
||||
. ${THIS_DIR}/load-test-env.sh
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${THIS_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx1G -Xms1G"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricPutLoadTest $*"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricPutLoadTest $*
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
BASE_DIR=${THIS_DIR}/../..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
|
||||
. ${THIS_DIR}/load-test-env.sh
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${THIS_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx1G -Xms1G"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricQueryLoadTest $*"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricQueryLoadTest $*
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
|
||||
sed -i -- "s/#.*#/#$1#/g" $THIS_DIR/load-test-data.txt
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash -vx
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
TCNATIVE_SUFFIX="tcnative.jnilib"
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
TCNATIVE_SUFFIX="tcnative.so"
|
||||
fi
|
||||
|
||||
# netty tcnative file reference
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
|
||||
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
NATIVE_DIR="${THIS_DIR}/META-INF/native"
|
||||
BASE_DIR=${THIS_DIR}/..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
NUM_SERVER_THREADS=4
|
||||
|
||||
if [[ -e ${TMP_DIR} ]]; then
|
||||
rm -rf ${TMP_DIR}
|
||||
fi
|
||||
mkdir ${TMP_DIR}
|
||||
|
||||
if [[ -e ${NATIVE_DIR} ]]; then
|
||||
rm -rf ${NATIVE_DIR}
|
||||
fi
|
||||
mkdir -p ${NATIVE_DIR}
|
||||
|
||||
pushd ${BASE_DIR}/bin
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty_${TCNATIVE_SUFFIX}
|
||||
popd
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}"
|
||||
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
|
||||
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}/libnetty_${TCNATIVE_SUFFIX}"
|
||||
JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn" monitorInterval="60">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="error" includeLocation="false">
|
||||
<AppenderRef ref="Console" level="error" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn" monitorInterval="60">
|
||||
<Properties>
|
||||
<Property name="PID">????</Property>
|
||||
<Property name="LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%wEx</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<RollingRandomAccessFile name="Rolling" fileName="/srv/logs/timely/timely-balancer.log"
|
||||
filePattern="/srv/logs/timely/$${date:yyyy-MM}/timely-balancer-%d{MM-dd-yyyy}-%i.log.gz">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
<Policies>
|
||||
<SizeBasedTriggeringPolicy size="1024 MB" />
|
||||
</Policies>
|
||||
<DefaultRolloverStrategy max="10" />
|
||||
</RollingRandomAccessFile>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="trace" includeLocation="false">
|
||||
<AppenderRef ref="Console" level="warn" />
|
||||
<AppenderRef ref="Rolling" level="trace" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,49 @@
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/security
|
||||
http://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util.xsd">
|
||||
|
||||
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
|
||||
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
|
||||
<beans:property name="users">
|
||||
<util:map key-type="java.lang.String">
|
||||
<beans:entry key="example.com">
|
||||
<util:list>
|
||||
<beans:value>D</beans:value>
|
||||
<beans:value>E</beans:value>
|
||||
<beans:value>F</beans:value>
|
||||
</util:list>
|
||||
</beans:entry>
|
||||
</util:map>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
|
||||
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
|
||||
</beans:bean>
|
||||
|
||||
<user-service id="usernamePasswordService">
|
||||
<user name="test" password="test1" authorities="A,B,C" />
|
||||
</user-service>
|
||||
|
||||
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
|
||||
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
|
||||
</beans:bean>
|
||||
|
||||
<authentication-manager alias="authenticationManager">
|
||||
<!-- X509 PreAuthenticated authorization provider -->
|
||||
<authentication-provider ref="x509AuthorizationProvider"/>
|
||||
<!-- Basic authorization provider, validates provided username and password -->
|
||||
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
|
||||
</authentication-manager>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,91 @@
|
||||
timely:
|
||||
balancer:
|
||||
timelyHosts:
|
||||
-
|
||||
regex: ^[a-g].*
|
||||
host: localhost
|
||||
tcpPort: 4242
|
||||
httpPort: 4243
|
||||
wsPort: 4244
|
||||
udpPort: 4245
|
||||
-
|
||||
regex: ^[h-n].*
|
||||
host: localhost
|
||||
tcpPort: 4342
|
||||
httpPort: 4343
|
||||
wsPort: 4244
|
||||
udpPort: 4245
|
||||
-
|
||||
regex: ^[o-z].*
|
||||
host: localhost
|
||||
tcpPort: 4442
|
||||
httpPort: 4443
|
||||
wsPort: 4444
|
||||
udpPort: 4445
|
||||
tcpClientPool:
|
||||
minIdlePerKey: 0
|
||||
maxIdlePerKey: 8
|
||||
maxTotalPerKey: 8
|
||||
maxTotal: -1
|
||||
lifo: true
|
||||
fairness: false
|
||||
maxWaitMillis: -1
|
||||
minEvictableIdleTimeMillis: 1800000
|
||||
evictorShutdownTimeoutMillis: 10000
|
||||
softMinEvictableIdleTimeMillis: -1
|
||||
numTestsPerEvictionRun: 3
|
||||
timeBetweenEvictionRunsMillis: -1
|
||||
blockWhenExhausted: true
|
||||
jmxEnabled: true
|
||||
httpClientPool:
|
||||
minIdlePerKey: 0
|
||||
maxIdlePerKey: 8
|
||||
maxTotalPerKey: 8
|
||||
maxTotal: -1
|
||||
lifo: true
|
||||
fairness: false
|
||||
maxWaitMillis: -1
|
||||
minEvictableIdleTimeMillis: 1800000
|
||||
evictorShutdownTimeoutMillis: 10000
|
||||
softMinEvictableIdleTimeMillis: -1
|
||||
numTestsPerEvictionRun: 3
|
||||
timeBetweenEvictionRunsMillis: -1
|
||||
blockWhenExhausted: true
|
||||
jmxEnabled: true
|
||||
wsClientPool:
|
||||
minIdlePerKey: 0
|
||||
maxIdlePerKey: 8
|
||||
maxTotalPerKey: 8
|
||||
maxTotal: -1
|
||||
lifo: true
|
||||
fairness: false
|
||||
maxWaitMillis: -1
|
||||
minEvictableIdleTimeMillis: 1800000
|
||||
evictorShutdownTimeoutMillis: 10000
|
||||
softMinEvictableIdleTimeMillis: -1
|
||||
numTestsPerEvictionRun: 3
|
||||
timeBetweenEvictionRunsMillis: -1
|
||||
blockWhenExhausted: true
|
||||
jmxEnabled: true
|
||||
udpClientPool:
|
||||
minIdlePerKey: 0
|
||||
maxIdlePerKey: 8
|
||||
maxTotalPerKey: 8
|
||||
maxTotal: -1
|
||||
lifo: true
|
||||
fairness: false
|
||||
maxWaitMillis: -1
|
||||
minEvictableIdleTimeMillis: 1800000
|
||||
evictorShutdownTimeoutMillis: 10000
|
||||
softMinEvictableIdleTimeMillis: -1
|
||||
numTestsPerEvictionRun: 3
|
||||
timeBetweenEvictionRunsMillis: -1
|
||||
blockWhenExhausted: true
|
||||
jmxEnabled: true
|
||||
clientSsl:
|
||||
keyFile:
|
||||
keyType: pkcs12
|
||||
keyPassword:
|
||||
trustStoreFile:
|
||||
trustStoreType: jks
|
||||
trustStorePassword:
|
||||
@@ -0,0 +1,54 @@
|
||||
timely:
|
||||
metrics-table: timely.metrics
|
||||
meta-table: timely.meta
|
||||
metric-age-off-days:
|
||||
default: 7
|
||||
accumulo:
|
||||
instance-name: dummy
|
||||
zookeepers: localhost
|
||||
username: dummy
|
||||
password: dummy
|
||||
write:
|
||||
latency: 5s
|
||||
threads: 3
|
||||
bufferSize: 52428800
|
||||
scan:
|
||||
threads: 4
|
||||
max-downsample-memory: 10000000
|
||||
security:
|
||||
session-max-age: 86400
|
||||
allow-anonymous-access: true
|
||||
ssl:
|
||||
certificate-file:
|
||||
key-file:
|
||||
key-password:
|
||||
use-generated-keypair: false
|
||||
trust-store-file:
|
||||
use-openssl: true
|
||||
#use-ciphers:
|
||||
server:
|
||||
ip: 127.0.0.1
|
||||
tcp-port: 5242
|
||||
udp-port: 5245
|
||||
http:
|
||||
host: localhost
|
||||
ip: 127.0.0.1
|
||||
port: 5243
|
||||
redirect-path: /secure-me
|
||||
strict-transport-max-age: 608400
|
||||
cors:
|
||||
allow-any-origin: false
|
||||
allow-null-origin: false
|
||||
allow-credentials: true
|
||||
allowed-origins:
|
||||
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
|
||||
allowed-headers: content-type
|
||||
websocket:
|
||||
ip: 127.0.0.1
|
||||
port: 5244
|
||||
timeout: 60
|
||||
subscription-lag: 120
|
||||
scanner-batch-size: 10000
|
||||
flush-interval-seconds: 30
|
||||
scanner-read-ahead: 1
|
||||
subscription-batch-size: 5000
|
||||
@@ -0,0 +1,331 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<artifactId>timely-balancer</artifactId>
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-server</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-client</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely-common</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.flatbuffers</groupId>
|
||||
<artifactId>flatbuffers-java</artifactId>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/../server/lib/flatbuffers-java-1.4.0.jar</systemPath>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lmax</groupId>
|
||||
<artifactId>disruptor</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fi.iki.yak</groupId>
|
||||
<artifactId>compression-gorilla</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.lukehutch</groupId>
|
||||
<artifactId>fast-classpath-scanner</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
<artifactId>accumulo-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.accumulo</groupId>
|
||||
<artifactId>accumulo-minicluster</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-common</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>jdk.tools</groupId>
|
||||
<artifactId>jdk.tools</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-minicluster</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test-autoconfigure</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-configuration</groupId>
|
||||
<artifactId>commons-configuration</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<!-- Use os-maven-plugin to initialize the "os.detected" properties -->
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>${version.findbugs-maven}</version>
|
||||
<configuration>
|
||||
<effort>Max</effort>
|
||||
<threshold>Medium</threshold>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble</id>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/dist/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<tarLongFileMode>posix</tarLongFileMode>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<phase>prepare-package</phase>
|
||||
<configuration>
|
||||
<excludeArtifactIds>junit</excludeArtifactIds>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>unpack-native</id>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<phase>process-test-classes</phase>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<classifier>${os.detected.classifier}</classifier>
|
||||
<type>jar</type>
|
||||
<overWrite>false</overWrite>
|
||||
<outputDirectory>${project.build.directory}/native</outputDirectory>
|
||||
<includes>**/*.so</includes>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/main/generated-java</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>net.revelc.code</groupId>
|
||||
<artifactId>formatter-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>buildnumber-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
<id>dist</id>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>README*</include>
|
||||
<include>LICENSE*</include>
|
||||
<include>NOTICE*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>timely*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/lib</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/bin</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/conf</directory>
|
||||
<outputDirectory>conf</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/src/main/resources/webapp</directory>
|
||||
<outputDirectory>bin/webapp</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,141 @@
|
||||
package timely.balancer;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class ArrivalRate {
|
||||
|
||||
private long shortTermArrivals = 0l;
|
||||
private double shortTermRate = 0;
|
||||
private long shortTermLastReset = 0l;
|
||||
|
||||
private long mediumTermArrivals = 0l;
|
||||
private double mediumTermRate = 0;
|
||||
private long mediumTermLastReset = 0l;
|
||||
|
||||
private long longTermArrivals = 0l;
|
||||
private double longTermRate = 0;
|
||||
private long longTermLastReset = 0l;
|
||||
|
||||
private double rate = 0;
|
||||
|
||||
private boolean recalculateEveryUpdate = true;
|
||||
private long created = System.currentTimeMillis();
|
||||
|
||||
private Timer timer = new Timer("ArrivalRateTimer");
|
||||
|
||||
public ArrivalRate() {
|
||||
shortTermLastReset = mediumTermLastReset = longTermLastReset = System.currentTimeMillis();
|
||||
|
||||
// 5 minute
|
||||
timer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
resetShort();
|
||||
}
|
||||
}, 300000, 300000);
|
||||
|
||||
// 5 minute
|
||||
timer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
resetMedium();
|
||||
}
|
||||
}, 900000, 900000);
|
||||
|
||||
// 5 minute
|
||||
timer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
resetLong();
|
||||
}
|
||||
}, 3600000, 3600000);
|
||||
}
|
||||
|
||||
synchronized private void resetShort() {
|
||||
long now = System.currentTimeMillis();
|
||||
shortTermRate = shortTermArrivals / (((double) (now - shortTermLastReset)) / 1000);
|
||||
shortTermArrivals = 0;
|
||||
shortTermLastReset = now;
|
||||
}
|
||||
|
||||
synchronized private void resetMedium() {
|
||||
long now = System.currentTimeMillis();
|
||||
mediumTermRate = mediumTermArrivals / (((double) (now - mediumTermLastReset)) / 1000);
|
||||
mediumTermArrivals = 0;
|
||||
mediumTermLastReset = now;
|
||||
}
|
||||
|
||||
synchronized private void resetLong() {
|
||||
long now = System.currentTimeMillis();
|
||||
longTermRate = longTermArrivals / (((double) (now - longTermLastReset)) / 1000);
|
||||
longTermArrivals = 0;
|
||||
longTermLastReset = now;
|
||||
}
|
||||
|
||||
synchronized public void arrived() {
|
||||
shortTermArrivals++;
|
||||
mediumTermArrivals++;
|
||||
longTermArrivals++;
|
||||
if (recalculateEveryUpdate) {
|
||||
calculateRate();
|
||||
if ((System.currentTimeMillis() - created) > 600000) {
|
||||
recalculateEveryUpdate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private double getShortRate(long now) {
|
||||
if (shortTermRate > 0) {
|
||||
return shortTermRate;
|
||||
} else {
|
||||
return shortTermArrivals / (((double) (now - shortTermLastReset)) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private double getMediumRate(long now) {
|
||||
if (mediumTermRate > 0) {
|
||||
return mediumTermRate;
|
||||
} else {
|
||||
return mediumTermArrivals / (((double) (now - mediumTermLastReset)) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private double getLongRate(long now) {
|
||||
if (longTermRate > 0) {
|
||||
return longTermRate;
|
||||
} else {
|
||||
return longTermArrivals / (((double) (now - longTermLastReset)) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public void calculateRate() {
|
||||
long now = System.currentTimeMillis();
|
||||
// 1 minute
|
||||
if (now - shortTermLastReset > 60000) {
|
||||
// use live
|
||||
shortTermRate = 0;
|
||||
}
|
||||
// 3 minute
|
||||
if (now - mediumTermLastReset > 180000) {
|
||||
// use live
|
||||
mediumTermRate = 0;
|
||||
}
|
||||
// 12 minute
|
||||
if (now - longTermLastReset > 720000) {
|
||||
// use live
|
||||
longTermRate = 0;
|
||||
}
|
||||
rate = 0.50 * getLongRate(now) + 0.30 * getMediumRate(now) + 0.20 * getShortRate(now);
|
||||
}
|
||||
|
||||
synchronized public double getRate() {
|
||||
if (rate == 0) {
|
||||
calculateRate();
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
package timely.balancer;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollChannelOption;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.http.HttpContentCompressor;
|
||||
import io.netty.handler.codec.http.HttpContentDecompressor;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.OpenSslServerContext;
|
||||
import io.netty.handler.ssl.OpenSslServerSessionContext;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.NettyRuntime;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import timely.Configuration;
|
||||
import timely.SpringBootstrap;
|
||||
import timely.balancer.connection.http.HttpClientPool;
|
||||
import timely.balancer.connection.tcp.TcpClientPool;
|
||||
import timely.balancer.connection.udp.UdpClientPool;
|
||||
import timely.balancer.connection.ws.WsClientPool;
|
||||
import timely.balancer.healthcheck.HealthChecker;
|
||||
import timely.balancer.netty.http.HttpRelayHandler;
|
||||
import timely.balancer.netty.tcp.TcpRelayHandler;
|
||||
import timely.balancer.netty.udp.UdpRelayHandler;
|
||||
import timely.balancer.netty.ws.WsRelayHandler;
|
||||
import timely.balancer.resolver.BalancedMetricResolver;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
import timely.client.http.HttpClient;
|
||||
import timely.netty.http.HttpStaticFileServerHandler;
|
||||
import timely.netty.http.NonSslRedirectHandler;
|
||||
import timely.netty.http.TimelyExceptionHandler;
|
||||
import timely.netty.http.auth.X509LoginRequestHandler;
|
||||
import timely.netty.tcp.MetricsBufferDecoder;
|
||||
import timely.netty.tcp.TcpDecoder;
|
||||
import timely.netty.tcp.TcpVersionHandler;
|
||||
import timely.netty.udp.UdpDecoder;
|
||||
import timely.netty.udp.UdpPacketToByteBuf;
|
||||
import timely.netty.websocket.WebSocketHttpCookieHandler;
|
||||
import timely.netty.websocket.WebSocketRequestDecoder;
|
||||
import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Balancer {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Balancer.class);
|
||||
private static final int EPOLL_MIN_MAJOR_VERSION = 2;
|
||||
private static final int EPOLL_MIN_MINOR_VERSION = 6;
|
||||
private static final int EPOLL_MIN_PATCH_VERSION = 32;
|
||||
private static final String OS_NAME = "os.name";
|
||||
private static final String OS_VERSION = "os.version";
|
||||
private static final String WS_PATH = "/websocket";
|
||||
protected static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
static ConfigurableApplicationContext applicationContext;
|
||||
|
||||
private final Configuration config;
|
||||
private final BalancerConfiguration balancerConfig;
|
||||
private EventLoopGroup tcpWorkerGroup = null;
|
||||
private EventLoopGroup tcpBossGroup = null;
|
||||
private EventLoopGroup httpWorkerGroup = null;
|
||||
private EventLoopGroup httpBossGroup = null;
|
||||
private EventLoopGroup wsWorkerGroup = null;
|
||||
private EventLoopGroup wsBossGroup = null;
|
||||
private EventLoopGroup udpBossGroup = null;
|
||||
private EventLoopGroup udpWorkerGroup = null;
|
||||
private MetricResolver metricResolver;
|
||||
protected Channel tcpChannelHandle = null;
|
||||
protected Channel httpChannelHandle = null;
|
||||
protected Channel wsChannelHandle = null;
|
||||
protected List<Channel> udpChannelHandleList = new ArrayList<>();
|
||||
protected volatile boolean shutdown = false;
|
||||
private static final int DEFAULT_EVENT_LOOP_THREADS;
|
||||
|
||||
static {
|
||||
DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
|
||||
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
|
||||
}
|
||||
|
||||
public Balancer(Configuration conf, BalancerConfiguration balancerConf) throws Exception {
|
||||
this.config = conf;
|
||||
this.balancerConfig = balancerConf;
|
||||
}
|
||||
|
||||
protected static ConfigurableApplicationContext initializeConfiguration(String[] args) {
|
||||
return new SpringApplicationBuilder(SpringBootstrap.class).web(false).run(args);
|
||||
}
|
||||
|
||||
private void shutdownHook() {
|
||||
|
||||
final Runnable shutdownRunner = () -> {
|
||||
if (!shutdown) {
|
||||
shutdown();
|
||||
}
|
||||
};
|
||||
final Thread hook = new Thread(shutdownRunner, "shutdown-hook-thread");
|
||||
Runtime.getRuntime().addShutdownHook(hook);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
List<ChannelFuture> channelFutures = new ArrayList<>();
|
||||
|
||||
if (tcpChannelHandle != null) {
|
||||
LOG.info("Closing tcpChannelHandle");
|
||||
channelFutures.add(tcpChannelHandle.close());
|
||||
}
|
||||
|
||||
if (httpChannelHandle != null) {
|
||||
LOG.info("Closing httpChannelHandle");
|
||||
channelFutures.add(httpChannelHandle.close());
|
||||
}
|
||||
|
||||
if (wsChannelHandle != null) {
|
||||
LOG.info("Closing wsChannelHandle");
|
||||
channelFutures.add(wsChannelHandle.close());
|
||||
}
|
||||
|
||||
for (Channel c : udpChannelHandleList) {
|
||||
LOG.info("Closing udpChannelHandle");
|
||||
channelFutures.add(c.close());
|
||||
}
|
||||
|
||||
// wait for the channels to shutdown
|
||||
channelFutures.forEach(f -> {
|
||||
try {
|
||||
f.get();
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error while shutting down channel: {}", f.channel().config());
|
||||
LOG.error("{}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
Integer quietPeriod = config.getServer().getShutdownQuietPeriod();
|
||||
List<Future<?>> groupFutures = new ArrayList<>();
|
||||
if (tcpBossGroup != null) {
|
||||
LOG.info("Shutting down tcpBossGroup");
|
||||
groupFutures.add(tcpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (tcpWorkerGroup != null) {
|
||||
LOG.info("Shutting down tcpWorkerGroup");
|
||||
groupFutures.add(tcpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (httpBossGroup != null) {
|
||||
LOG.info("Shutting down httpBossGroup");
|
||||
groupFutures.add(httpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (httpWorkerGroup != null) {
|
||||
LOG.info("Shutting down httpWorkerGroup");
|
||||
groupFutures.add(httpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (wsBossGroup != null) {
|
||||
LOG.info("Shutting down wsBossGroup");
|
||||
groupFutures.add(wsBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (wsWorkerGroup != null) {
|
||||
LOG.info("Shutting down wsWorkerGroup");
|
||||
groupFutures.add(wsWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (udpBossGroup != null) {
|
||||
LOG.info("Shutting down udpBossGroup");
|
||||
groupFutures.add(udpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
if (udpWorkerGroup != null) {
|
||||
LOG.info("Shutting down udpWorkerGroup");
|
||||
groupFutures.add(udpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
groupFutures.parallelStream().forEach(f -> {
|
||||
try {
|
||||
f.get();
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error while shutting down group: {}", f.toString());
|
||||
LOG.error("{}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
LOG.info("Closing WebSocketRequestDecoder");
|
||||
WebSocketRequestDecoder.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error closing WebSocketRequestDecoder during shutdown", e);
|
||||
}
|
||||
|
||||
if (applicationContext != null) {
|
||||
LOG.info("Closing applicationContext");
|
||||
applicationContext.close();
|
||||
}
|
||||
this.shutdown = true;
|
||||
LOG.info("Server shut down.");
|
||||
}
|
||||
|
||||
private static boolean useEpoll() {
|
||||
|
||||
// Should we just return true if this is Linux and if we get an error
|
||||
// during Epoll
|
||||
// setup handle it there?
|
||||
final String os = SystemPropertyUtil.get(OS_NAME).toLowerCase().trim();
|
||||
final String[] version = SystemPropertyUtil.get(OS_VERSION).toLowerCase().trim().split("\\.");
|
||||
if (os.startsWith("linux") && version.length >= 3) {
|
||||
final int major = Integer.parseInt(version[0]);
|
||||
if (major > EPOLL_MIN_MAJOR_VERSION) {
|
||||
return true;
|
||||
} else if (major == EPOLL_MIN_MAJOR_VERSION) {
|
||||
final int minor = Integer.parseInt(version[1]);
|
||||
if (minor > EPOLL_MIN_MINOR_VERSION) {
|
||||
return true;
|
||||
} else if (minor == EPOLL_MIN_MINOR_VERSION) {
|
||||
final int patch = Integer.parseInt(version[2].substring(0, 2));
|
||||
return patch >= EPOLL_MIN_PATCH_VERSION;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected SslContext createSSLContext(Configuration config) throws Exception {
|
||||
|
||||
Configuration.Ssl sslCfg = config.getSecurity().getSsl();
|
||||
Boolean generate = sslCfg.isUseGeneratedKeypair();
|
||||
SslContextBuilder ssl;
|
||||
if (generate) {
|
||||
LOG.warn("Using generated self signed server certificate");
|
||||
Date begin = new Date();
|
||||
Date end = new Date(begin.getTime() + 86400000);
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate("localhost", begin, end);
|
||||
ssl = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey());
|
||||
} else {
|
||||
String cert = sslCfg.getCertificateFile();
|
||||
String key = sslCfg.getKeyFile();
|
||||
String keyPass = sslCfg.getKeyPassword();
|
||||
if (null == cert || null == key) {
|
||||
throw new IllegalArgumentException("Check your SSL properties, something is wrong.");
|
||||
}
|
||||
ssl = SslContextBuilder.forServer(new File(cert), new File(key), keyPass);
|
||||
}
|
||||
|
||||
ssl.ciphers(sslCfg.getUseCiphers());
|
||||
|
||||
// Can't set to REQUIRE because the CORS pre-flight requests will fail.
|
||||
ssl.clientAuth(ClientAuth.OPTIONAL);
|
||||
|
||||
Boolean useOpenSSL = sslCfg.isUseOpenssl();
|
||||
if (useOpenSSL) {
|
||||
ssl.sslProvider(SslProvider.OPENSSL);
|
||||
} else {
|
||||
ssl.sslProvider(SslProvider.JDK);
|
||||
}
|
||||
String trustStore = sslCfg.getTrustStoreFile();
|
||||
if (null != trustStore) {
|
||||
if (!trustStore.isEmpty()) {
|
||||
ssl.trustManager(new File(trustStore));
|
||||
}
|
||||
}
|
||||
return ssl.build();
|
||||
}
|
||||
|
||||
protected SSLContext createSSLClientContext(BalancerConfiguration config) throws Exception {
|
||||
|
||||
BalancerConfiguration.ClientSsl ssl = config.getClientSsl();
|
||||
return HttpClient.getSSLContext(ssl.getTrustStoreFile(), ssl.getTrustStoreType(), ssl.getTrustStorePassword(),
|
||||
ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword());
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
|
||||
final boolean useEpoll = useEpoll();
|
||||
Class<? extends ServerSocketChannel> channelClass;
|
||||
Class<? extends Channel> datagramChannelClass;
|
||||
if (useEpoll) {
|
||||
tcpWorkerGroup = new EpollEventLoopGroup();
|
||||
tcpBossGroup = new EpollEventLoopGroup();
|
||||
httpWorkerGroup = new EpollEventLoopGroup();
|
||||
httpBossGroup = new EpollEventLoopGroup();
|
||||
wsWorkerGroup = new EpollEventLoopGroup();
|
||||
wsBossGroup = new EpollEventLoopGroup();
|
||||
udpWorkerGroup = new EpollEventLoopGroup();
|
||||
udpBossGroup = new EpollEventLoopGroup();
|
||||
channelClass = EpollServerSocketChannel.class;
|
||||
datagramChannelClass = EpollDatagramChannel.class;
|
||||
} else {
|
||||
tcpWorkerGroup = new NioEventLoopGroup();
|
||||
tcpBossGroup = new NioEventLoopGroup();
|
||||
httpWorkerGroup = new NioEventLoopGroup();
|
||||
httpBossGroup = new NioEventLoopGroup();
|
||||
wsWorkerGroup = new NioEventLoopGroup();
|
||||
wsBossGroup = new NioEventLoopGroup();
|
||||
udpWorkerGroup = new NioEventLoopGroup();
|
||||
udpBossGroup = new NioEventLoopGroup();
|
||||
channelClass = NioServerSocketChannel.class;
|
||||
datagramChannelClass = NioDatagramChannel.class;
|
||||
}
|
||||
LOG.info("Using channel class {}", channelClass.getSimpleName());
|
||||
|
||||
TcpClientPool tcpClientPool = new TcpClientPool(this.balancerConfig);
|
||||
HealthChecker healthChecker = new HealthChecker(this.balancerConfig, tcpClientPool);
|
||||
this.metricResolver = new BalancedMetricResolver(this.balancerConfig, healthChecker);
|
||||
|
||||
final ServerBootstrap tcpServer = new ServerBootstrap();
|
||||
tcpServer.group(tcpBossGroup, tcpWorkerGroup);
|
||||
tcpServer.channel(channelClass);
|
||||
tcpServer.handler(new LoggingHandler());
|
||||
tcpServer.childHandler(setupTcpChannel(metricResolver, tcpClientPool));
|
||||
tcpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
tcpServer.option(ChannelOption.SO_BACKLOG, 128);
|
||||
tcpServer.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
final int tcpPort = config.getServer().getTcpPort();
|
||||
final String tcpIp = config.getServer().getIp();
|
||||
tcpChannelHandle = tcpServer.bind(tcpIp, tcpPort).sync().channel();
|
||||
final String tcpAddress = ((InetSocketAddress) tcpChannelHandle.localAddress()).getAddress().getHostAddress();
|
||||
|
||||
final int httpPort = config.getHttp().getPort();
|
||||
final String httpIp = config.getHttp().getIp();
|
||||
SslContext sslCtx = createSSLContext(config);
|
||||
if (sslCtx instanceof OpenSslServerContext) {
|
||||
OpenSslServerContext openssl = (OpenSslServerContext) sslCtx;
|
||||
String application = "Timely_" + httpPort;
|
||||
OpenSslServerSessionContext opensslCtx = openssl.sessionContext();
|
||||
opensslCtx.setSessionCacheEnabled(true);
|
||||
opensslCtx.setSessionCacheSize(128);
|
||||
opensslCtx.setSessionIdContext(application.getBytes(StandardCharsets.UTF_8));
|
||||
opensslCtx.setSessionTimeout(config.getSecurity().getSessionMaxAge());
|
||||
}
|
||||
SSLContext clientSSLContext = createSSLClientContext(balancerConfig);
|
||||
final ServerBootstrap httpServer = new ServerBootstrap();
|
||||
httpServer.group(httpBossGroup, httpWorkerGroup);
|
||||
httpServer.channel(channelClass);
|
||||
httpServer.handler(new LoggingHandler());
|
||||
HttpClientPool httpClientPool = new HttpClientPool(balancerConfig, clientSSLContext);
|
||||
httpServer.childHandler(setupHttpChannel(config, balancerConfig, sslCtx, metricResolver, httpClientPool));
|
||||
httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
httpServer.option(ChannelOption.SO_BACKLOG, 128);
|
||||
httpServer.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
httpChannelHandle = httpServer.bind(httpIp, httpPort).sync().channel();
|
||||
final String httpAddress = ((InetSocketAddress) httpChannelHandle.localAddress()).getAddress().getHostAddress();
|
||||
|
||||
final int wsPort = config.getWebsocket().getPort();
|
||||
final String wsIp = config.getWebsocket().getIp();
|
||||
final ServerBootstrap wsServer = new ServerBootstrap();
|
||||
wsServer.group(wsBossGroup, wsWorkerGroup);
|
||||
wsServer.channel(channelClass);
|
||||
wsServer.handler(new LoggingHandler());
|
||||
WsClientPool wsClientPool = new WsClientPool(config, balancerConfig, clientSSLContext);
|
||||
wsServer.childHandler(setupWSChannel(config, balancerConfig, sslCtx, metricResolver, wsClientPool));
|
||||
wsServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
wsServer.option(ChannelOption.SO_BACKLOG, 128);
|
||||
wsServer.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
/* Not sure if next three lines are necessary */
|
||||
wsServer.option(ChannelOption.SO_SNDBUF, 1048576);
|
||||
wsServer.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 838860);
|
||||
wsServer.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 620145);
|
||||
wsChannelHandle = wsServer.bind(wsIp, wsPort).sync().channel();
|
||||
final String wsAddress = ((InetSocketAddress) wsChannelHandle.localAddress()).getAddress().getHostAddress();
|
||||
|
||||
final int udpPort = config.getServer().getUdpPort();
|
||||
final String udpIp = config.getServer().getIp();
|
||||
UdpClientPool udpClientPool = new UdpClientPool(balancerConfig);
|
||||
for (int n = 1; n < DEFAULT_EVENT_LOOP_THREADS; n++) {
|
||||
final Bootstrap udpServer = new Bootstrap();
|
||||
udpServer.group(udpBossGroup);
|
||||
udpServer.channel(datagramChannelClass);
|
||||
udpServer.handler(setupUdpChannel(metricResolver, udpClientPool));
|
||||
udpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
udpServer.option(EpollChannelOption.SO_REUSEADDR, true);
|
||||
udpServer.option(EpollChannelOption.SO_REUSEPORT, true);
|
||||
udpChannelHandleList.add(udpServer.bind(udpIp, udpPort).sync().channel());
|
||||
}
|
||||
shutdownHook();
|
||||
LOG.info(
|
||||
"Server started. Listening on {}:{} for TCP traffic, {}:{} for HTTP traffic, {}:{} for WebSocket traffic, and {}:{} for UDP traffic",
|
||||
tcpAddress, tcpPort, httpAddress, httpPort, wsAddress, wsPort, wsAddress, udpPort);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
Balancer.applicationContext = Balancer.initializeConfiguration(args);
|
||||
Configuration conf = applicationContext.getBean(Configuration.class);
|
||||
BalancerConfiguration balancerConf = applicationContext.getBean(BalancerConfiguration.class);
|
||||
|
||||
Balancer b = new Balancer(conf, balancerConf);
|
||||
try {
|
||||
b.run();
|
||||
LATCH.await();
|
||||
} catch (final InterruptedException e) {
|
||||
LOG.info("Server shutting down.");
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error running server.", e);
|
||||
} finally {
|
||||
try {
|
||||
b.shutdown();
|
||||
} catch (Exception e) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ChannelHandler setupHttpChannel(Configuration config, BalancerConfiguration balancerConfig,
|
||||
SslContext sslCtx, MetricResolver metricResolver, HttpClientPool httpClientPool) {
|
||||
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
|
||||
ch.pipeline().addLast("ssl", new NonSslRedirectHandler(config, sslCtx));
|
||||
ch.pipeline().addLast("encoder", new HttpResponseEncoder());
|
||||
ch.pipeline().addLast("decoder", new HttpRequestDecoder());
|
||||
ch.pipeline().addLast("compressor", new HttpContentCompressor());
|
||||
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
|
||||
ch.pipeline().addLast("queryDecoder", new timely.netty.http.HttpRequestDecoder(config));
|
||||
ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler());
|
||||
ch.pipeline().addLast("login", new X509LoginRequestHandler(config));
|
||||
ch.pipeline()
|
||||
.addLast("httpRelay", new HttpRelayHandler(balancerConfig, metricResolver, httpClientPool));
|
||||
ch.pipeline().addLast("error", new TimelyExceptionHandler());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected ChannelHandler setupUdpChannel(MetricResolver metricResolver, UdpClientPool udpClientPool) {
|
||||
return new ChannelInitializer<DatagramChannel>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(DatagramChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("logger", new LoggingHandler());
|
||||
ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new UdpDecoder());
|
||||
ch.pipeline().addLast("udpRelayHandler", new UdpRelayHandler(metricResolver, udpClientPool));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected ChannelHandler setupTcpChannel(MetricResolver metricResolver, TcpClientPool tcpClientPool) {
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new TcpDecoder());
|
||||
ch.pipeline().addLast("tcpRelayHandler", new TcpRelayHandler(metricResolver, tcpClientPool));
|
||||
ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected ChannelHandler setupWSChannel(Configuration conf, BalancerConfiguration balancerConfig,
|
||||
SslContext sslCtx, MetricResolver metricResolver, WsClientPool wsClientPool) {
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
|
||||
ch.pipeline().addLast("httpServer", new HttpServerCodec());
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(config));
|
||||
|
||||
ch.pipeline().addLast("idle-handler", new IdleStateHandler(conf.getWebsocket().getTimeout(), 0, 0));
|
||||
ch.pipeline().addLast("ws-protocol",
|
||||
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true));
|
||||
ch.pipeline().addLast("wsDecoder", new WebSocketRequestDecoder(config));
|
||||
ch.pipeline().addLast("httpRelay",
|
||||
new WsRelayHandler(balancerConfig, config, metricResolver, wsClientPool));
|
||||
ch.pipeline().addLast("error", new WSTimelyExceptionHandler());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package timely.balancer;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "timely.balancer")
|
||||
public class BalancerConfiguration {
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig udpClientPool = new GenericKeyedObjectPoolConfig();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig tcpClientPool = new GenericKeyedObjectPoolConfig();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig httpClientPool = new GenericKeyedObjectPoolConfig();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig wsClientPool = new GenericKeyedObjectPoolConfig();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private ClientSsl clientSsl = new ClientSsl();
|
||||
|
||||
private boolean loginRequired = false;
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private List<TimelyBalancedHost> timelyHosts = new ArrayList<>();
|
||||
|
||||
private long checkServerHealthInterval = 10000;
|
||||
|
||||
private int serverFailuresBeforeDown = 3;
|
||||
|
||||
private int serverSuccessesBeforeUp = 3;
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private List<TimelyBalancedHost> wsHosts = new ArrayList<>();
|
||||
|
||||
public void setCheckServerHealthInterval(long checkServerHealthInterval) {
|
||||
this.checkServerHealthInterval = checkServerHealthInterval;
|
||||
}
|
||||
|
||||
public long getCheckServerHealthInterval() {
|
||||
return checkServerHealthInterval;
|
||||
}
|
||||
|
||||
public int getServerFailuresBeforeDown() {
|
||||
return serverFailuresBeforeDown;
|
||||
}
|
||||
|
||||
public void setServerFailuresBeforeDown(int serverFailuresBeforeDown) {
|
||||
this.serverFailuresBeforeDown = serverFailuresBeforeDown;
|
||||
}
|
||||
|
||||
public int getServerSuccessesBeforeUp() {
|
||||
return serverSuccessesBeforeUp;
|
||||
}
|
||||
|
||||
public void setServerSuccessesBeforeUp(int serverSuccessesBeforeUp) {
|
||||
this.serverSuccessesBeforeUp = serverSuccessesBeforeUp;
|
||||
}
|
||||
|
||||
public GenericKeyedObjectPoolConfig getUdpClientPool() {
|
||||
return udpClientPool;
|
||||
}
|
||||
|
||||
public GenericKeyedObjectPoolConfig getTcpClientPool() {
|
||||
return tcpClientPool;
|
||||
}
|
||||
|
||||
public GenericKeyedObjectPoolConfig getHttpClientPool() {
|
||||
return httpClientPool;
|
||||
}
|
||||
|
||||
public GenericKeyedObjectPoolConfig getWsClientPool() {
|
||||
return wsClientPool;
|
||||
}
|
||||
|
||||
public ClientSsl getClientSsl() {
|
||||
return clientSsl;
|
||||
}
|
||||
|
||||
public void setLoginRequired(boolean loginRequired) {
|
||||
this.loginRequired = loginRequired;
|
||||
}
|
||||
|
||||
public boolean isLoginRequired() {
|
||||
return loginRequired;
|
||||
}
|
||||
|
||||
public List<TimelyBalancedHost> getTimelyHosts() {
|
||||
return timelyHosts;
|
||||
}
|
||||
|
||||
public class ClientSsl {
|
||||
|
||||
private String keyFile;
|
||||
private String keyType;
|
||||
private String keyPassword;
|
||||
private String trustStoreFile;
|
||||
private String trustStoreType;
|
||||
private String trustStorePassword;
|
||||
|
||||
public String getKeyFile() {
|
||||
return keyFile;
|
||||
}
|
||||
|
||||
public BalancerConfiguration setKeyFile(String keyFile) {
|
||||
this.keyFile = keyFile;
|
||||
return BalancerConfiguration.this;
|
||||
}
|
||||
|
||||
public String getKeyType() {
|
||||
return keyType;
|
||||
}
|
||||
|
||||
public BalancerConfiguration setKeyType(String keyType) {
|
||||
this.keyType = keyType;
|
||||
return BalancerConfiguration.this;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return keyPassword;
|
||||
}
|
||||
|
||||
public BalancerConfiguration setKeyPassword(String keyPassword) {
|
||||
this.keyPassword = keyPassword;
|
||||
return BalancerConfiguration.this;
|
||||
}
|
||||
|
||||
public String getTrustStoreFile() {
|
||||
return trustStoreFile;
|
||||
}
|
||||
|
||||
public BalancerConfiguration setTrustStoreFile(String trustStoreFile) {
|
||||
this.trustStoreFile = trustStoreFile;
|
||||
return BalancerConfiguration.this;
|
||||
}
|
||||
|
||||
public String getTrustStoreType() {
|
||||
return trustStoreType;
|
||||
}
|
||||
|
||||
public BalancerConfiguration setTrustStoreType(String trustStoreType) {
|
||||
this.trustStoreType = trustStoreType;
|
||||
return BalancerConfiguration.this;
|
||||
}
|
||||
|
||||
public String getTrustStorePassword() {
|
||||
return trustStorePassword;
|
||||
}
|
||||
|
||||
public BalancerConfiguration setTrustStorePassword(String trustStorePassword) {
|
||||
this.trustStorePassword = trustStorePassword;
|
||||
return BalancerConfiguration.this;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package timely.balancer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import timely.SpringBootstrap;
|
||||
|
||||
@SpringBootApplication(scanBasePackageClasses = { SpringBootstrap.class })
|
||||
public class BalancerSpringBootstrap {
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package timely.balancer.connection;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.ArrivalRate;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
|
||||
public class TimelyBalancedHost {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TimelyBalancedHost.class);
|
||||
|
||||
private String regex;
|
||||
private String host;
|
||||
private int tcpPort;
|
||||
private int httpPort;
|
||||
private int wsPort;
|
||||
private int udpPort;
|
||||
private boolean isUp = true;
|
||||
private BalancerConfiguration config;
|
||||
private int failures = 0;
|
||||
private int successes = 0;
|
||||
private ArrivalRate arrivalRate = new ArrivalRate();
|
||||
|
||||
public TimelyBalancedHost() {
|
||||
|
||||
}
|
||||
|
||||
public TimelyBalancedHost(String host, int tcpPort, int httpPort, int wsPort, int udpPort) {
|
||||
this.host = host;
|
||||
this.tcpPort = tcpPort;
|
||||
this.httpPort = httpPort;
|
||||
this.wsPort = wsPort;
|
||||
this.udpPort = udpPort;
|
||||
}
|
||||
|
||||
public TimelyBalancedHost(String host, int basePort) {
|
||||
this.host = host;
|
||||
this.tcpPort = basePort;
|
||||
this.httpPort = basePort + 1;
|
||||
this.wsPort = basePort + 2;
|
||||
this.udpPort = basePort + 3;
|
||||
}
|
||||
|
||||
static public TimelyBalancedHost of(String host, int tcpPort, int httpPort, int wsPort, int udpPort) {
|
||||
return new TimelyBalancedHost(host, tcpPort, httpPort, wsPort, udpPort);
|
||||
}
|
||||
|
||||
static public TimelyBalancedHost of(String host, int basePort) {
|
||||
return new TimelyBalancedHost(host, basePort);
|
||||
}
|
||||
|
||||
public void setConfig(BalancerConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
synchronized public boolean isUp() {
|
||||
return isUp;
|
||||
}
|
||||
|
||||
public void reportSuccess() {
|
||||
int serverSuccessesBeforeUp = config.getServerSuccessesBeforeUp();
|
||||
String h = host;
|
||||
int p = tcpPort;
|
||||
synchronized (this) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("success reported host:{} port:{} isUp:{} successes:{} serverSuccessesBeforeUp:{}", h, p,
|
||||
isUp, (successes + 1), serverSuccessesBeforeUp);
|
||||
}
|
||||
if (!isUp) {
|
||||
if (++successes >= serverSuccessesBeforeUp) {
|
||||
isUp = true;
|
||||
successes = 0;
|
||||
LOG.info("host up host:{} port:{}", h, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportFailure() {
|
||||
int serverFailuresBeforeDown = config.getServerFailuresBeforeDown();
|
||||
String h = host;
|
||||
int p = tcpPort;
|
||||
synchronized (this) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("failure reported host:{} port:{} isUp:{} successes:{} serverSuccessesBeforeUp:{}", h, p,
|
||||
isUp, (failures + 1), serverFailuresBeforeDown);
|
||||
}
|
||||
if (isUp) {
|
||||
if (++failures >= serverFailuresBeforeDown) {
|
||||
isUp = false;
|
||||
failures = 0;
|
||||
LOG.info("host down host:{} port:{}", h, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getRegex() {
|
||||
return regex;
|
||||
}
|
||||
|
||||
public void setRegex(String regex) {
|
||||
this.regex = regex;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getUdpPort() {
|
||||
return udpPort;
|
||||
}
|
||||
|
||||
public void setUdpPort(int udpPort) {
|
||||
this.udpPort = udpPort;
|
||||
}
|
||||
|
||||
public int getTcpPort() {
|
||||
return tcpPort;
|
||||
}
|
||||
|
||||
public void setTcpPort(int tcpPort) {
|
||||
this.tcpPort = tcpPort;
|
||||
}
|
||||
|
||||
public int getHttpPort() {
|
||||
return httpPort;
|
||||
}
|
||||
|
||||
public void setHttpPort(int httpPort) {
|
||||
this.httpPort = httpPort;
|
||||
}
|
||||
|
||||
public int getWsPort() {
|
||||
return wsPort;
|
||||
}
|
||||
|
||||
public void setWsPort(int wsPort) {
|
||||
this.wsPort = wsPort;
|
||||
}
|
||||
|
||||
public void arrived() {
|
||||
arrivalRate.arrived();
|
||||
}
|
||||
|
||||
public double getArrivalRate() {
|
||||
return arrivalRate.getRate();
|
||||
}
|
||||
|
||||
public void calculateRate() {
|
||||
arrivalRate.calculateRate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package timely.balancer.connection.http;
|
||||
|
||||
import org.apache.commons.pool2.KeyedPooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import org.apache.http.impl.client.BasicCookieStore;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
public class HttpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, CloseableHttpClient> {
|
||||
|
||||
private final SSLContext sslContext;
|
||||
|
||||
public HttpClientFactory(SSLContext sslContext) {
|
||||
this.sslContext = sslContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<CloseableHttpClient> makeObject(TimelyBalancedHost k) throws Exception {
|
||||
BasicCookieStore cookieJar = new BasicCookieStore();
|
||||
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, cookieJar, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) {
|
||||
if (k.isUp()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package timely.balancer.connection.http;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
//import java.util.Map;
|
||||
//import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, CloseableHttpClient> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class);
|
||||
|
||||
public HttpClientPool(BalancerConfiguration config, SSLContext sslContext) {
|
||||
super(new HttpClientFactory(sslContext), config.getHttpClientPool());
|
||||
}
|
||||
|
||||
// private Map<TimelyBalancedHost, Integer> checkedOut = new
|
||||
// ConcurrentHashMap<>();
|
||||
|
||||
// @Override
|
||||
// public CloseableHttpClient borrowObject(TimelyBalancedHost key) throws
|
||||
// Exception {
|
||||
// CloseableHttpClient client = super.borrowObject(key);
|
||||
// Integer n;
|
||||
// synchronized (checkedOut) {
|
||||
// n = checkedOut.get(key);
|
||||
// if (n == null) {
|
||||
// n = 0;
|
||||
// }
|
||||
// n++;
|
||||
// checkedOut.put(key, n);
|
||||
// }
|
||||
// LOG.trace("Borrowing HttpClient for key " + key.getHost() + ":" +
|
||||
// key.getHttpPort() + " " + getNumActive(key)
|
||||
// + ":" + getNumIdle(key) + ":" + n);
|
||||
// return client;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void returnObject(TimelyBalancedHost key, CloseableHttpClient obj)
|
||||
// {
|
||||
// super.returnObject(key, obj);
|
||||
// Integer n;
|
||||
// synchronized (checkedOut) {
|
||||
// n = checkedOut.get(key);
|
||||
// n--;
|
||||
// checkedOut.put(key, n);
|
||||
// }
|
||||
// LOG.trace("Returning HttpClient for key " + key.getHost() + ":" +
|
||||
// key.getHttpPort() + " " + getNumActive(key)
|
||||
// + ":" + getNumIdle(key) + ":" + n);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package timely.balancer.connection.tcp;
|
||||
|
||||
import org.apache.commons.pool2.KeyedPooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.client.tcp.TcpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class TcpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, TcpClient> {
|
||||
|
||||
public TcpClientFactory() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<TcpClient> makeObject(TimelyBalancedHost k) throws Exception {
|
||||
return new DefaultPooledObject<>(new TcpClient(k.getHost(), k.getTcpPort()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyObject(TimelyBalancedHost k, PooledObject<TcpClient> o) throws Exception {
|
||||
try {
|
||||
o.getObject().flush();
|
||||
o.getObject().close();
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Error closing connection to " + k.getHost() + ":" + k.getTcpPort());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateObject(TimelyBalancedHost k, PooledObject<TcpClient> o) {
|
||||
if (k.isUp()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateObject(TimelyBalancedHost k, PooledObject<TcpClient> o) throws Exception {
|
||||
try {
|
||||
o.getObject().open();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to connect to " + k.getHost() + ":" + k.getTcpPort(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivateObject(TimelyBalancedHost k, PooledObject<TcpClient> o) throws Exception {
|
||||
try {
|
||||
o.getObject().flush();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Error flushing connection to " + k.getHost() + ":" + k.getTcpPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package timely.balancer.connection.tcp;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.client.tcp.TcpClient;
|
||||
|
||||
public class TcpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, TcpClient> {
|
||||
|
||||
public TcpClientPool(BalancerConfiguration config) {
|
||||
super(new TcpClientFactory(), config.getTcpClientPool());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package timely.balancer.connection.udp;
|
||||
|
||||
import org.apache.commons.pool2.KeyedPooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.client.udp.UdpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UdpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, UdpClient> {
|
||||
|
||||
public UdpClientFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<UdpClient> makeObject(TimelyBalancedHost k) throws Exception {
|
||||
return new DefaultPooledObject<>(new UdpClient(k.getHost(), k.getUdpPort()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyObject(TimelyBalancedHost k, PooledObject<UdpClient> o) throws Exception {
|
||||
try {
|
||||
o.getObject().flush();
|
||||
o.getObject().close();
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Error closing connection to " + k.getHost() + ":" + k.getUdpPort());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateObject(TimelyBalancedHost k, PooledObject<UdpClient> o) {
|
||||
if (k.isUp()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateObject(TimelyBalancedHost k, PooledObject<UdpClient> o) throws Exception {
|
||||
try {
|
||||
o.getObject().open();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to connect to " + k.getHost() + ":" + k.getUdpPort(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivateObject(TimelyBalancedHost k, PooledObject<UdpClient> o) throws Exception {
|
||||
try {
|
||||
o.getObject().flush();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Error flushing connection to " + k.getHost() + ":" + k.getUdpPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package timely.balancer.connection.udp;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.client.udp.UdpClient;
|
||||
|
||||
public class UdpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, UdpClient> {
|
||||
|
||||
public UdpClientPool(BalancerConfiguration config) {
|
||||
super(new UdpClientFactory(), config.getUdpClientPool());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package timely.balancer.connection.ws;
|
||||
|
||||
import org.apache.commons.pool2.KeyedPooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.BasicCookieStore;
|
||||
import timely.Configuration;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, WebSocketSubscriptionClient> {
|
||||
|
||||
private final SSLContext sslContext;
|
||||
private final boolean doLogin;
|
||||
private Configuration config;
|
||||
|
||||
public WsClientFactory(Configuration config, SSLContext sslContext, boolean doLogin) {
|
||||
this.sslContext = sslContext;
|
||||
this.doLogin = doLogin;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<WebSocketSubscriptionClient> makeObject(TimelyBalancedHost k) throws Exception {
|
||||
|
||||
int bufferSize = config.getWebsocket().getSubscriptionBatchSize() * 500;
|
||||
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslContext, k.getHost(), k.getHttpPort(),
|
||||
k.getWsPort(), doLogin, "", "", false, bufferSize);
|
||||
return new DefaultPooledObject<>(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) {
|
||||
if (k.isUp()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
|
||||
|
||||
// try {
|
||||
// o.getObject().open();
|
||||
// } catch (IOException e) {
|
||||
// throw new IOException("Unable to connect to " + k.getHost() + ":" +
|
||||
// k.getHttpPort());
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
|
||||
// try {
|
||||
// o.getObject().flush();
|
||||
// } catch (Exception e) {
|
||||
// throw new IOException("Error flushing connection to " + k.getHost() +
|
||||
// ":" + k.getHttpPort());
|
||||
// }
|
||||
// try {
|
||||
// o.getObject().flush();
|
||||
// o.getObject().close();
|
||||
// } catch (IOException e) {
|
||||
// throw new IOException("Error closing connection to " + k.getHost() +
|
||||
// ":" + k.getHttpPort());
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package timely.balancer.connection.ws;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import timely.Configuration;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
public class WsClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, WebSocketSubscriptionClient> {
|
||||
|
||||
public WsClientPool(Configuration config, BalancerConfiguration balancerConfig, SSLContext sslContext) {
|
||||
super(new WsClientFactory(config, sslContext, balancerConfig.isLoginRequired()), balancerConfig
|
||||
.getWsClientPool());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package timely.balancer.healthcheck;
|
||||
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.tcp.TcpClientPool;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class HealthChecker {
|
||||
|
||||
private List<TimelyBalancedHost> timelyHosts;
|
||||
private Timer timer;
|
||||
final private TCPServerHealthCheck tcpServerHealthCheck;
|
||||
private TimerTask check = new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (TimelyBalancedHost h : timelyHosts) {
|
||||
if (tcpServerHealthCheck.isServerHealthy(h)) {
|
||||
h.reportSuccess();
|
||||
} else {
|
||||
h.reportFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public HealthChecker(BalancerConfiguration config, TcpClientPool tcpClientPool) {
|
||||
this.timelyHosts = config.getTimelyHosts();
|
||||
this.tcpServerHealthCheck = new TCPServerHealthCheck(tcpClientPool);
|
||||
this.timer = new Timer("HealthCheckerThread", true);
|
||||
this.timer.scheduleAtFixedRate(check, 0, config.getCheckServerHealthInterval());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package timely.balancer.healthcheck;
|
||||
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
public interface ServerHealthCheck {
|
||||
|
||||
public boolean isServerHealthy(TimelyBalancedHost timelyBalancedHost);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package timely.balancer.healthcheck;
|
||||
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.tcp.TcpClientPool;
|
||||
import timely.client.tcp.TcpClient;
|
||||
|
||||
public class TCPServerHealthCheck implements ServerHealthCheck {
|
||||
|
||||
final private TcpClientPool tcpClientPool;
|
||||
|
||||
public TCPServerHealthCheck(TcpClientPool tcpClientPool) {
|
||||
this.tcpClientPool = tcpClientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServerHealthy(TimelyBalancedHost timelyBalancedHost) {
|
||||
|
||||
boolean healthy = true;
|
||||
TcpClient client = null;
|
||||
try {
|
||||
client = tcpClientPool.borrowObject(timelyBalancedHost);
|
||||
client.open();
|
||||
} catch (Exception e) {
|
||||
healthy = false;
|
||||
|
||||
} finally {
|
||||
if (client != null) {
|
||||
tcpClientPool.returnObject(timelyBalancedHost, client);
|
||||
}
|
||||
}
|
||||
return healthy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package timely.balancer.netty.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import timely.api.request.HttpGetRequest;
|
||||
import timely.api.request.HttpPostRequest;
|
||||
import timely.api.request.MetricRequest;
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
import timely.api.request.timeseries.QueryRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.http.HttpClientPool;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
import timely.netty.http.TimelyHttpHandler;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
|
||||
|
||||
public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpRelayHandler.class);
|
||||
private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}";
|
||||
private static final String LOG_PARSED_REQUEST = "Parsed request {}";
|
||||
private static final String NO_AUTHORIZATIONS = "";
|
||||
|
||||
private final BalancerConfiguration conf;
|
||||
private final HttpClientPool httpClientPool;
|
||||
private MetricResolver metricResolver;
|
||||
|
||||
public HttpRelayHandler(BalancerConfiguration config, MetricResolver metricResolver, HttpClientPool httpClientPool) {
|
||||
this.conf = config;
|
||||
this.metricResolver = metricResolver;
|
||||
this.httpClientPool = httpClientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
|
||||
|
||||
CloseableHttpClient client = null;
|
||||
TimelyBalancedHost k = null;
|
||||
CloseableHttpResponse relayedResponse = null;
|
||||
FullHttpRequest request = null;
|
||||
try {
|
||||
try {
|
||||
request = msg.getHttpRequest();
|
||||
String originalURI = request.getUri();
|
||||
originalURI = encodeURI(originalURI);
|
||||
|
||||
String metric = null;
|
||||
if (msg instanceof QueryRequest) {
|
||||
Collection<QueryRequest.SubQuery> subqueries = ((QueryRequest) msg).getQueries();
|
||||
if (subqueries != null) {
|
||||
Iterator<QueryRequest.SubQuery> itr = subqueries.iterator();
|
||||
if (itr.hasNext()) {
|
||||
metric = itr.next().getMetric();
|
||||
}
|
||||
}
|
||||
k = metricResolver.getHostPortKey(metric);
|
||||
} else if (msg instanceof MetricRequest) {
|
||||
metric = ((MetricRequest) msg).getMetric().getName();
|
||||
k = metricResolver.getHostPortKeyIngest(metric);
|
||||
} else {
|
||||
k = metricResolver.getHostPortKey(null);
|
||||
}
|
||||
k = metricResolver.getHostPortKey(metric);
|
||||
client = httpClientPool.borrowObject(k);
|
||||
|
||||
List<Map.Entry<String, String>> headerList = request.headers().entries();
|
||||
List<Header> relayedHeaderList = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, String> h : headerList) {
|
||||
if (!h.getKey().equals(CONTENT_LENGTH)) {
|
||||
relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue()));
|
||||
}
|
||||
}
|
||||
Header[] headerArray = new Header[relayedHeaderList.size()];
|
||||
relayedHeaderList.toArray(headerArray);
|
||||
|
||||
String relayURI = "https://" + k.getHost() + ":" + k.getHttpPort() + originalURI;
|
||||
if (request.getMethod().equals(HttpMethod.GET)) {
|
||||
HttpUriRequest relayedRequest = new HttpGet(relayURI);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
|
||||
} else if (request.getMethod().equals(HttpMethod.POST)) {
|
||||
HttpPost relayedRequest = new HttpPost(relayURI);
|
||||
|
||||
String content = request.content().toString(StandardCharsets.UTF_8);
|
||||
StringEntity entity = new StringEntity(content);
|
||||
relayedRequest.setEntity(entity);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
if (message == null) {
|
||||
LOG.error("", e);
|
||||
} else {
|
||||
if (message.contains("No matching tags")) {
|
||||
LOG.trace(message);
|
||||
} else {
|
||||
LOG.error(message, e);
|
||||
}
|
||||
}
|
||||
this.sendHttpError(
|
||||
ctx,
|
||||
new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), e.getMessage(), e
|
||||
.getLocalizedMessage(), e));
|
||||
return;
|
||||
} finally {
|
||||
if (request != null && request instanceof AutoCloseable) {
|
||||
ReferenceCountUtil.release(request);
|
||||
}
|
||||
}
|
||||
FullHttpResponse response = null;
|
||||
if (relayedResponse == null) {
|
||||
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(),
|
||||
"Method not allowed", "Method not allowed"));
|
||||
} else {
|
||||
// buf = JsonUtil.getObjectMapper().writeValueAsBytes(msg);
|
||||
ByteArrayOutputStream baos = new MyByteArrayOutputStream();
|
||||
StreamUtils.copy(relayedResponse.getEntity().getContent(), baos);
|
||||
|
||||
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(relayedResponse
|
||||
.getStatusLine().getStatusCode()), Unpooled.copiedBuffer(baos.toByteArray()));
|
||||
for (Header h : relayedResponse.getAllHeaders()) {
|
||||
response.headers().add(h.getName(), h.getValue());
|
||||
}
|
||||
// response.headers().set(HttpHeaders.Names.CONTENT_TYPE,
|
||||
// Constants.JSON_TYPE);
|
||||
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
|
||||
sendResponse(ctx, response);
|
||||
}
|
||||
} finally {
|
||||
if (relayedResponse != null) {
|
||||
relayedResponse.close();
|
||||
}
|
||||
if (client != null && k != null) {
|
||||
httpClientPool.returnObject(k, client);
|
||||
} else {
|
||||
LOG.error("NOT RETURNING CONNECTION! " + msg.getHttpRequest().getUri());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public class MyByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
public MyByteArrayOutputStream() {
|
||||
}
|
||||
|
||||
public MyByteArrayOutputStream(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public byte[] getBuf() {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
static public String encodeURI(String s) {
|
||||
try {
|
||||
s = s.replaceAll("\\{", URLEncoder.encode("{", "UTF-8"));
|
||||
s = s.replaceAll("\\}", URLEncoder.encode("}", "UTF-8"));
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
String s = "https://127.0.0.1:4243/api/query?start=1356998400000&end=1528398304000&m=sum:rate{false,100,0}:sys.thermal.gauge{class=aws}";
|
||||
URI uri = URI.create(encodeURI(s));
|
||||
|
||||
System.out.println(uri.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package timely.balancer.netty.tcp;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.request.MetricRequest;
|
||||
import timely.api.request.TcpRequest;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.tcp.TcpClientPool;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
import timely.client.tcp.TcpClient;
|
||||
import timely.netty.Constants;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class TcpRelayHandler extends SimpleChannelInboundHandler<TcpRequest> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TcpRelayHandler.class);
|
||||
private static final String LOG_ERR_MSG = "Error storing put metric: {}";
|
||||
private static final String ERR_MSG = "Error storing put metric: ";
|
||||
|
||||
private MetricResolver metricResolver;
|
||||
private TcpClientPool tcpClientPool;
|
||||
|
||||
public TcpRelayHandler(MetricResolver metricResolver, TcpClientPool tcpClientPool) {
|
||||
this.metricResolver = metricResolver;
|
||||
this.tcpClientPool = tcpClientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TcpRequest msg) throws Exception {
|
||||
LOG.trace("Received {}", msg);
|
||||
try {
|
||||
String line;
|
||||
Pair<TimelyBalancedHost, TcpClient> keyClientPair = null;
|
||||
try {
|
||||
if (msg instanceof MetricRequest) {
|
||||
String metricName = ((MetricRequest) msg).getMetric().getName();
|
||||
line = ((MetricRequest) msg).getLine();
|
||||
keyClientPair = getClient(metricName, true);
|
||||
} else {
|
||||
// Version request
|
||||
line = "version";
|
||||
keyClientPair = getClient(null, false);
|
||||
}
|
||||
keyClientPair.getRight().write(line + "\n");
|
||||
keyClientPair.getRight().flush();
|
||||
} finally {
|
||||
if (keyClientPair != null && keyClientPair.getLeft() != null && keyClientPair.getRight() != null) {
|
||||
tcpClientPool.returnObject(keyClientPair.getLeft(), keyClientPair.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error(LOG_ERR_MSG, msg, e);
|
||||
ChannelFuture cf = ctx.writeAndFlush(Unpooled.copiedBuffer((ERR_MSG + e.getMessage() + "\n")
|
||||
.getBytes(StandardCharsets.UTF_8)));
|
||||
if (!cf.isSuccess()) {
|
||||
LOG.error(Constants.ERR_WRITING_RESPONSE, cf.cause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<TimelyBalancedHost, TcpClient> getClient(String metric, boolean metricRequest) {
|
||||
TcpClient client = null;
|
||||
TimelyBalancedHost k = null;
|
||||
int failures = 0;
|
||||
while (client == null) {
|
||||
try {
|
||||
k = (metricRequest == true) ? metricResolver.getHostPortKeyIngest(metric) : metricResolver
|
||||
.getHostPortKey(metric);
|
||||
client = tcpClientPool.borrowObject(k);
|
||||
} catch (Exception e1) {
|
||||
failures++;
|
||||
client = null;
|
||||
LOG.error(e1.getMessage(), e1);
|
||||
try {
|
||||
Thread.sleep(failures < 10 ? 500 : 60000);
|
||||
} catch (InterruptedException e2) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair.of(k, client);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package timely.balancer.netty.udp;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.request.MetricRequest;
|
||||
import timely.api.request.UdpRequest;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.udp.UdpClientPool;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
import timely.client.udp.UdpClient;
|
||||
import timely.netty.Constants;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class UdpRelayHandler extends SimpleChannelInboundHandler<UdpRequest> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UdpRelayHandler.class);
|
||||
private static final String LOG_ERR_MSG = "Error storing put metric: {}";
|
||||
private static final String ERR_MSG = "Error storing put metric: ";
|
||||
|
||||
private MetricResolver metricResolver;
|
||||
private UdpClientPool udpClientPool;
|
||||
|
||||
public UdpRelayHandler(MetricResolver metricResolver, UdpClientPool udpClientPool) {
|
||||
this.metricResolver = metricResolver;
|
||||
this.udpClientPool = udpClientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, UdpRequest msg) throws Exception {
|
||||
LOG.trace("Received {}", msg);
|
||||
try {
|
||||
MetricRequest metricRequest = (MetricRequest) msg;
|
||||
String metricName = metricRequest.getMetric().getName();
|
||||
Pair<TimelyBalancedHost, UdpClient> keyClientPair = null;
|
||||
try {
|
||||
keyClientPair = getClient(metricName, true);
|
||||
keyClientPair.getRight().write(metricRequest.getLine() + "\n");
|
||||
keyClientPair.getRight().flush();
|
||||
} finally {
|
||||
if (keyClientPair != null && keyClientPair.getLeft() != null && keyClientPair.getRight() != null) {
|
||||
udpClientPool.returnObject(keyClientPair.getLeft(), keyClientPair.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error(LOG_ERR_MSG, msg, e);
|
||||
ChannelFuture cf = ctx.writeAndFlush(Unpooled.copiedBuffer((ERR_MSG + e.getMessage() + "\n")
|
||||
.getBytes(StandardCharsets.UTF_8)));
|
||||
if (!cf.isSuccess()) {
|
||||
LOG.error(Constants.ERR_WRITING_RESPONSE, cf.cause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<TimelyBalancedHost, UdpClient> getClient(String metric, boolean metricRequest) {
|
||||
UdpClient client = null;
|
||||
TimelyBalancedHost k = null;
|
||||
int failures = 0;
|
||||
while (client == null) {
|
||||
try {
|
||||
k = (metricRequest == true) ? metricResolver.getHostPortKeyIngest(metric) : metricResolver
|
||||
.getHostPortKey(metric);
|
||||
client = udpClientPool.borrowObject(k);
|
||||
} catch (Exception e1) {
|
||||
failures++;
|
||||
client = null;
|
||||
LOG.error(e1.getMessage(), e1);
|
||||
try {
|
||||
Thread.sleep(failures < 10 ? 500 : 60000);
|
||||
} catch (InterruptedException e2) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair.of(k, client);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package timely.balancer.netty.ws;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.util.concurrent.ScheduledFuture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.client.websocket.ClientHandler;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class WsClientHandler extends ClientHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WsClientHandler.class);
|
||||
private final ChannelHandlerContext ctx;
|
||||
private ScheduledFuture<?> ping;
|
||||
|
||||
public WsClientHandler(ChannelHandlerContext ctx, int pingRate) {
|
||||
this.ctx = ctx;
|
||||
this.ping = this.ctx.executor().scheduleAtFixedRate(() -> {
|
||||
LOG.trace("Sending ping on channel {}", ctx.channel());
|
||||
ctx.writeAndFlush(new PingWebSocketFrame());
|
||||
}, pingRate, pingRate, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
session.addMessageHandler((MessageHandler.Whole<String>) message -> {
|
||||
ctx.writeAndFlush(new TextWebSocketFrame(message));
|
||||
LOG.debug("Message received on Websocket session {}: {}", session.getId(), message);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, CloseReason reason) {
|
||||
super.onClose(session, reason);
|
||||
if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) {
|
||||
LOG.error("Abnormal close: " + reason.getReasonPhrase());
|
||||
Exception e = new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
reason.getReasonPhrase(), "");
|
||||
WsRelayHandler.sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e);
|
||||
}
|
||||
if (!ping.isCancelled()) {
|
||||
ping.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Session session, Throwable t) {
|
||||
super.onError(session, t);
|
||||
LOG.error(t.getMessage(), t);
|
||||
Exception e = new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), t.getMessage(), "");
|
||||
WsRelayHandler.sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e);
|
||||
if (!ping.isCancelled()) {
|
||||
ping.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package timely.balancer.netty.ws;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.ws.WsClientPool;
|
||||
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class WsClientHolder {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WsClientHolder.class);
|
||||
private WebSocketSubscriptionClient client = null;
|
||||
private TimelyBalancedHost host = null;
|
||||
|
||||
public WsClientHolder(TimelyBalancedHost host, WebSocketSubscriptionClient client) {
|
||||
this.host = host;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
synchronized public WebSocketSubscriptionClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
synchronized public TimelyBalancedHost getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
synchronized public void close(WsClientPool wsClientPool) {
|
||||
if (client != null && host != null) {
|
||||
try {
|
||||
client.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error closing web socket client", e);
|
||||
}
|
||||
wsClientPool.returnObject(host, client);
|
||||
client = null;
|
||||
host = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package timely.balancer.netty.ws;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.Configuration;
|
||||
import timely.api.request.subscription.AddSubscription;
|
||||
import timely.api.request.subscription.CloseSubscription;
|
||||
import timely.api.request.subscription.CreateSubscription;
|
||||
import timely.api.request.subscription.RemoveSubscription;
|
||||
import timely.api.request.subscription.SubscriptionRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.connection.ws.WsClientPool;
|
||||
import timely.balancer.resolver.MetricResolver;
|
||||
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
|
||||
import timely.netty.http.TimelyHttpHandler;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequest> implements TimelyHttpHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WsRelayHandler.class);
|
||||
|
||||
private final BalancerConfiguration balancerConfig;
|
||||
private final Configuration config;
|
||||
private final WsClientPool wsClientPool;
|
||||
private MetricResolver metricResolver;
|
||||
static private ObjectMapper mapper;
|
||||
Map<String, Map<String, WsClientHolder>> wsClients = new ConcurrentHashMap<>();
|
||||
Map<String, ChannelHandlerContext> wsSubscriptions = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
mapper = new ObjectMapper();
|
||||
mapper.registerModule(new Jdk8Module());
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
mapper.configure(SerializationFeature.WRAP_EXCEPTIONS, true);
|
||||
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
|
||||
}
|
||||
|
||||
public WsRelayHandler(BalancerConfiguration balancerConfig, Configuration config, MetricResolver metricResolver,
|
||||
WsClientPool wsClientPool) {
|
||||
this.balancerConfig = balancerConfig;
|
||||
this.config = config;
|
||||
this.metricResolver = metricResolver;
|
||||
this.wsClientPool = wsClientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, SubscriptionRequest msg) throws Exception {
|
||||
TimelyBalancedHost k;
|
||||
ChannelHandlerContext origContext = null;
|
||||
String subscriptionId;
|
||||
try {
|
||||
String metric = null;
|
||||
if (msg instanceof CreateSubscription) {
|
||||
CreateSubscription create = (CreateSubscription) msg;
|
||||
final String currentSubscriptionId = create.getSubscriptionId();
|
||||
subscriptionId = currentSubscriptionId;
|
||||
wsSubscriptions.put(subscriptionId, ctx);
|
||||
// WebSocketSubscriptionClient sends the createSubscription when
|
||||
// we call open
|
||||
// We don't know where to send it to yet anyway because that
|
||||
// depends on the metric.
|
||||
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
Map<String, WsClientHolder> metricToClientMap = wsClients.get(currentSubscriptionId);
|
||||
|
||||
if (metricToClientMap != null) {
|
||||
synchronized (metricToClientMap) {
|
||||
// close all clients for this subscriptionId
|
||||
LOG.info("Channel closed, closing subscriptions for subscriptionId:%s",
|
||||
currentSubscriptionId);
|
||||
for (Map.Entry<String, WsClientHolder> entry : metricToClientMap.entrySet()) {
|
||||
entry.getValue().close(wsClientPool);
|
||||
}
|
||||
wsClients.remove(currentSubscriptionId);
|
||||
wsSubscriptions.remove(currentSubscriptionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else if (msg instanceof AddSubscription) {
|
||||
AddSubscription add = (AddSubscription) msg;
|
||||
metric = add.getMetric();
|
||||
subscriptionId = add.getSubscriptionId();
|
||||
origContext = wsSubscriptions.get(subscriptionId);
|
||||
if (origContext == null) {
|
||||
// send error because user never sent a CreateSubscription
|
||||
// and we have nowhere to send the results
|
||||
LOG.info("ChannelHandlerContext not found for subscriptionId:%s - createSubscription not called?",
|
||||
subscriptionId);
|
||||
sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, new IllegalArgumentException(
|
||||
"Must call create first"));
|
||||
}
|
||||
Map<String, WsClientHolder> metricToClientMap = wsClients.get(subscriptionId);
|
||||
if (metricToClientMap == null) {
|
||||
metricToClientMap = new HashMap<>();
|
||||
wsClients.put(subscriptionId, metricToClientMap);
|
||||
}
|
||||
WebSocketSubscriptionClient client = null;
|
||||
synchronized (metricToClientMap) {
|
||||
WsClientHolder hostClientHolder = metricToClientMap.get(metric);
|
||||
if (hostClientHolder == null) {
|
||||
k = metricResolver.getHostPortKey(metric);
|
||||
client = wsClientPool.borrowObject(k);
|
||||
client.open(new WsClientHandler(origContext, (config.getWebsocket().getTimeout() / 2)));
|
||||
metricToClientMap.put(metric, new WsClientHolder(k, client));
|
||||
} else {
|
||||
client = hostClientHolder.getClient();
|
||||
}
|
||||
}
|
||||
Map<String, String> tags = null;
|
||||
|
||||
Long startTime = 0L;
|
||||
Long endTime = 0L;
|
||||
Long delayTime = 5000L;
|
||||
if (add.getTags().isPresent()) {
|
||||
tags = add.getTags().get();
|
||||
}
|
||||
if (add.getStartTime().isPresent()) {
|
||||
startTime = add.getStartTime().get();
|
||||
}
|
||||
if (add.getEndTime().isPresent()) {
|
||||
endTime = add.getEndTime().get();
|
||||
}
|
||||
if (add.getDelayTime().isPresent()) {
|
||||
delayTime = add.getDelayTime().get();
|
||||
}
|
||||
|
||||
client.addSubscription(add.getMetric(), tags, startTime, endTime, delayTime);
|
||||
|
||||
} else if (msg instanceof RemoveSubscription) {
|
||||
RemoveSubscription remove = (RemoveSubscription) msg;
|
||||
metric = remove.getMetric();
|
||||
subscriptionId = remove.getSubscriptionId();
|
||||
Map<String, WsClientHolder> metricToClientMap = wsClients.get(subscriptionId);
|
||||
if (metricToClientMap != null) {
|
||||
synchronized (metricToClientMap) {
|
||||
WsClientHolder hostClientHolder = metricToClientMap.get(metric);
|
||||
if (hostClientHolder != null) {
|
||||
hostClientHolder.getClient().removeSubscription(metric);
|
||||
} else {
|
||||
LOG.info("client not found for subscriptionId:%s metric:%s", subscriptionId, metric);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (msg instanceof CloseSubscription) {
|
||||
CloseSubscription close = (CloseSubscription) msg;
|
||||
subscriptionId = close.getSubscriptionId();
|
||||
Map<String, WsClientHolder> metricToClientMap = wsClients.get(subscriptionId);
|
||||
if (metricToClientMap != null) {
|
||||
synchronized (metricToClientMap) {
|
||||
// close all clients for this subscriptionId
|
||||
for (Map.Entry<String, WsClientHolder> entry : metricToClientMap.entrySet()) {
|
||||
entry.getValue().close(wsClientPool);
|
||||
}
|
||||
}
|
||||
wsClients.remove(subscriptionId);
|
||||
wsSubscriptions.remove(subscriptionId);
|
||||
}
|
||||
ctx.writeAndFlush(new CloseWebSocketFrame(1000, "Client requested close."));
|
||||
}
|
||||
} catch (ConnectException e1) {
|
||||
LOG.error(e1.getMessage(), e1);
|
||||
ChannelHandlerContext errorCtx = origContext == null ? ctx : origContext;
|
||||
sendErrorResponse(errorCtx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e1);
|
||||
} catch (Exception e2) {
|
||||
LOG.error(e2.getMessage(), e2);
|
||||
ChannelHandlerContext errorCtx = origContext == null ? ctx : origContext;
|
||||
sendErrorResponse(errorCtx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static public void sendErrorResponse(ChannelHandlerContext ctx, HttpResponseStatus status, Exception ex) {
|
||||
|
||||
try {
|
||||
TimelyException te = new TimelyException(status.code(), ex.getMessage(), ex.getMessage());
|
||||
String json = mapper.writeValueAsString(te);
|
||||
ctx.writeAndFlush(new TextWebSocketFrame(json));
|
||||
} catch (JsonProcessingException e) {
|
||||
LOG.error("Error serializing exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package timely.balancer.resolver;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.ArrivalRate;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.healthcheck.HealthChecker;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class BalancedMetricResolver implements MetricResolver {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BalancedMetricResolver.class);
|
||||
|
||||
private Map<String, TimelyBalancedHost> metricToHostMap = new HashMap<>();
|
||||
private Map<Integer, TimelyBalancedHost> serverMap = new HashMap<>();
|
||||
private Map<String, ArrivalRate> metricMap = new HashMap<>();
|
||||
private Random r = new Random();
|
||||
final private HealthChecker healthChecker;
|
||||
private Timer timer = new Timer("RebalanceTimer", true);
|
||||
private long balanceUntil = System.currentTimeMillis() + 1800000l;
|
||||
|
||||
public BalancedMetricResolver(BalancerConfiguration config, HealthChecker healthChecker) {
|
||||
int n = 0;
|
||||
synchronized (this) {
|
||||
for (TimelyBalancedHost h : config.getTimelyHosts()) {
|
||||
h.setConfig(config);
|
||||
serverMap.put(n++, h);
|
||||
}
|
||||
}
|
||||
this.healthChecker = healthChecker;
|
||||
|
||||
timer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
rebalanceAllMetrics();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}, 300000);
|
||||
|
||||
timer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (System.currentTimeMillis() < balanceUntil) {
|
||||
try {
|
||||
balance();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 600000, 120000);
|
||||
}
|
||||
|
||||
private TimelyBalancedHost getLeastUsedHost() {
|
||||
|
||||
Map<Double, TimelyBalancedHost> rateSortedHosts = new TreeMap<>();
|
||||
for (Map.Entry<Integer, TimelyBalancedHost> e : serverMap.entrySet()) {
|
||||
rateSortedHosts.put(e.getValue().getArrivalRate(), e.getValue());
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Double, TimelyBalancedHost>> itr = rateSortedHosts.entrySet().iterator();
|
||||
|
||||
TimelyBalancedHost tbh = null;
|
||||
|
||||
while (itr.hasNext() && tbh == null) {
|
||||
TimelyBalancedHost currentTBH = itr.next().getValue();
|
||||
if (currentTBH.isUp()) {
|
||||
tbh = currentTBH;
|
||||
}
|
||||
}
|
||||
return tbh;
|
||||
}
|
||||
|
||||
private TimelyBalancedHost getRandomHost(TimelyBalancedHost notThisOne) {
|
||||
|
||||
TimelyBalancedHost tbh = null;
|
||||
for (int x = 0; tbh == null && x < serverMap.size(); x++) {
|
||||
tbh = serverMap.get(Math.abs(r.nextInt() & Integer.MAX_VALUE) % serverMap.size());
|
||||
if (!tbh.isUp()) {
|
||||
tbh = null;
|
||||
} else if (notThisOne != null && tbh.equals(notThisOne)) {
|
||||
tbh = null;
|
||||
}
|
||||
}
|
||||
return tbh;
|
||||
}
|
||||
|
||||
private TimelyBalancedHost getRoundRobinHost() {
|
||||
TimelyBalancedHost tbh;
|
||||
int x = metricToHostMap.size() % serverMap.size();
|
||||
tbh = serverMap.get(x);
|
||||
if (tbh.isUp()) {
|
||||
return tbh;
|
||||
} else {
|
||||
return getRandomHost(null);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public void rebalanceAllMetrics() {
|
||||
|
||||
Map<Double, String> rateSortedMetrics = new TreeMap<>();
|
||||
for (Map.Entry<String, ArrivalRate> e : metricMap.entrySet()) {
|
||||
rateSortedMetrics.put(e.getValue().getRate(), e.getKey());
|
||||
}
|
||||
metricToHostMap.clear();
|
||||
for (String m : rateSortedMetrics.values()) {
|
||||
metricToHostMap.put(m, getRoundRobinHost());
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public void balance() {
|
||||
LOG.info("rebalancing begin");
|
||||
Map<Double, TimelyBalancedHost> ratedSortedHosts = new TreeMap<>();
|
||||
double totalArrivalRate = 0;
|
||||
for (Map.Entry<Integer, TimelyBalancedHost> e : serverMap.entrySet()) {
|
||||
ratedSortedHosts.put(e.getValue().getArrivalRate(), e.getValue());
|
||||
totalArrivalRate += e.getValue().getArrivalRate();
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Double, TimelyBalancedHost>> itr = ratedSortedHosts.entrySet().iterator();
|
||||
TimelyBalancedHost mostUsed = null;
|
||||
TimelyBalancedHost leastUsed = null;
|
||||
|
||||
while (itr.hasNext()) {
|
||||
TimelyBalancedHost currentTBH = itr.next().getValue();
|
||||
if (currentTBH.isUp()) {
|
||||
if (leastUsed == null) {
|
||||
leastUsed = currentTBH;
|
||||
mostUsed = currentTBH;
|
||||
} else {
|
||||
// should end up with the last server that is up
|
||||
mostUsed = currentTBH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double averageArrivalRate = totalArrivalRate / serverMap.size();
|
||||
double highestArrivalRate = mostUsed.getArrivalRate();
|
||||
double lowestArrivalRate = leastUsed.getArrivalRate();
|
||||
|
||||
LOG.info("rebalancing high:{} avg:{} low:{}", highestArrivalRate, averageArrivalRate, lowestArrivalRate);
|
||||
|
||||
// 5% over average
|
||||
int numReassigned = 0;
|
||||
if (highestArrivalRate > averageArrivalRate * 1.05) {
|
||||
LOG.info("rebalancing: high > 5% higher than average");
|
||||
// sort metrics by rate
|
||||
Map<Double, String> rateSortedMetrics = new TreeMap<>();
|
||||
for (Map.Entry<String, ArrivalRate> e : metricMap.entrySet()) {
|
||||
rateSortedMetrics.put(e.getValue().getRate(), e.getKey());
|
||||
}
|
||||
|
||||
double desiredDeltaHighest = (highestArrivalRate - averageArrivalRate) * 0.1;
|
||||
double desiredDeltaLowest = (averageArrivalRate - lowestArrivalRate) * 0.1;
|
||||
Iterator<Map.Entry<Double, String>> metricItr = rateSortedMetrics.entrySet().iterator();
|
||||
boolean done = false;
|
||||
LOG.info("rebalancing: desiredDeltaHighest:{} desiredDeltaLowest:{} rateSortedMetrics.size():{}",
|
||||
desiredDeltaHighest, desiredDeltaLowest, rateSortedMetrics.size());
|
||||
// advance to halfway
|
||||
for (int numMetric = 0; metricItr.hasNext() && numMetric <= rateSortedMetrics.size() / 2; numMetric++) {
|
||||
metricItr.next();
|
||||
}
|
||||
long maxToReassign = Math.round(((double) metricMap.size() / serverMap.size()) * 0.20);
|
||||
while (!done && metricItr.hasNext() && numReassigned < maxToReassign) {
|
||||
Map.Entry<Double, String> current = metricItr.next();
|
||||
Double currentRate = current.getKey();
|
||||
String currentMetric = current.getValue();
|
||||
|
||||
if (desiredDeltaHighest > 0) {
|
||||
if (metricToHostMap.get(currentMetric).equals(mostUsed)) {
|
||||
LOG.debug("rebalancing: trying to reassign metric {} from server {}:{}", currentMetric,
|
||||
mostUsed.getHost(), mostUsed.getTcpPort());
|
||||
if (desiredDeltaHighest > 0) {
|
||||
numReassigned++;
|
||||
metricToHostMap.put(currentMetric, leastUsed);
|
||||
LOG.debug("rebalancing: reassigning metric {} from server {}:{} to {}:{}", currentMetric,
|
||||
mostUsed.getHost(), mostUsed.getTcpPort(), leastUsed.getHost(),
|
||||
leastUsed.getTcpPort());
|
||||
desiredDeltaLowest -= currentRate;
|
||||
desiredDeltaHighest -= currentRate;
|
||||
} else {
|
||||
TimelyBalancedHost tbh = getRandomHost(mostUsed);
|
||||
if (tbh != null) {
|
||||
numReassigned++;
|
||||
metricToHostMap.put(currentMetric, tbh);
|
||||
LOG.info("rebalancing: reassigning metric {} from server {}:{} to {}:{}",
|
||||
currentMetric, mostUsed.getHost(), mostUsed.getTcpPort(), tbh.getHost(),
|
||||
tbh.getTcpPort());
|
||||
desiredDeltaHighest -= currentRate;
|
||||
} else {
|
||||
LOG.debug(
|
||||
"rebalancing: unable to reassign metric {} from server {}:{} - getRandomHost returned null",
|
||||
currentMetric, mostUsed.getHost(), mostUsed.getTcpPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.info("rebalancing end - reassigned {}", numReassigned);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimelyBalancedHost getHostPortKeyIngest(String metric) {
|
||||
if (StringUtils.isNotBlank(metric)) {
|
||||
ArrivalRate rate;
|
||||
rate = metricMap.get(metric);
|
||||
if (rate == null) {
|
||||
rate = new ArrivalRate();
|
||||
metricMap.put(metric, rate);
|
||||
}
|
||||
rate.arrived();
|
||||
}
|
||||
|
||||
TimelyBalancedHost tbh;
|
||||
if (StringUtils.isBlank(metric)) {
|
||||
tbh = getRandomHost(null);
|
||||
} else {
|
||||
tbh = metricToHostMap.get(metric);
|
||||
if (tbh == null) {
|
||||
tbh = getRoundRobinHost();
|
||||
metricToHostMap.put(metric, tbh);
|
||||
} else if (!tbh.isUp()) {
|
||||
TimelyBalancedHost oldTbh = tbh;
|
||||
tbh = getLeastUsedHost();
|
||||
LOG.debug("rebalancing from host that is down: reassigning metric {} from server {}:{} to {}:{}",
|
||||
metric, oldTbh.getHost(), oldTbh.getTcpPort(), tbh.getHost(), tbh.getTcpPort());
|
||||
metricToHostMap.put(metric, tbh);
|
||||
}
|
||||
}
|
||||
|
||||
// if all else fails
|
||||
if (tbh == null || !tbh.isUp()) {
|
||||
for (TimelyBalancedHost h : serverMap.values()) {
|
||||
if (h.isUp()) {
|
||||
tbh = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tbh != null && StringUtils.isNotBlank(metric)) {
|
||||
metricToHostMap.put(metric, tbh);
|
||||
}
|
||||
}
|
||||
if (tbh != null) {
|
||||
tbh.arrived();
|
||||
tbh.calculateRate();
|
||||
}
|
||||
return tbh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimelyBalancedHost getHostPortKey(String metric) {
|
||||
TimelyBalancedHost tbh = null;
|
||||
if (StringUtils.isNotBlank(metric)) {
|
||||
tbh = metricToHostMap.get(metric);
|
||||
}
|
||||
|
||||
if (tbh == null || !tbh.isUp()) {
|
||||
for (int x = 0; tbh == null && x < serverMap.size(); x++) {
|
||||
tbh = serverMap.get(Math.abs(r.nextInt() & Integer.MAX_VALUE) % serverMap.size());
|
||||
if (!tbh.isUp()) {
|
||||
tbh = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if all else fails
|
||||
if (tbh == null || !tbh.isUp()) {
|
||||
for (TimelyBalancedHost h : serverMap.values()) {
|
||||
if (h.isUp()) {
|
||||
tbh = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tbh != null && StringUtils.isNotBlank(metric)) {
|
||||
metricToHostMap.put(metric, tbh);
|
||||
}
|
||||
}
|
||||
return tbh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package timely.balancer.resolver;
|
||||
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
public interface MetricResolver {
|
||||
|
||||
public TimelyBalancedHost getHostPortKeyIngest(String metric);
|
||||
|
||||
public TimelyBalancedHost getHostPortKey(String metric);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package timely.balancer.resolver;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.balancer.BalancerConfiguration;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
import timely.balancer.healthcheck.HealthChecker;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class RegexMetricResolver implements MetricResolver {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegexMetricResolver.class);
|
||||
|
||||
private Map<Integer, TimelyBalancedHost> serverMap = new HashMap<>();
|
||||
private Random r = new Random();
|
||||
final private HealthChecker healthChecker;
|
||||
|
||||
public RegexMetricResolver(BalancerConfiguration config, HealthChecker healthChecker) {
|
||||
int n = 0;
|
||||
for (TimelyBalancedHost h : config.getTimelyHosts()) {
|
||||
h.setConfig(config);
|
||||
serverMap.put(n++, h);
|
||||
}
|
||||
this.healthChecker = healthChecker;
|
||||
}
|
||||
|
||||
public TimelyBalancedHost getHostPortKeyInternal(String metric) {
|
||||
|
||||
TimelyBalancedHost tbh = null;
|
||||
if (StringUtils.isNotBlank(metric)) {
|
||||
for (TimelyBalancedHost h : serverMap.values()) {
|
||||
if (metric.matches(h.getRegex()) && h.isUp()) {
|
||||
tbh = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tbh == null) {
|
||||
tbh = getFallbackHost(metric);
|
||||
}
|
||||
|
||||
LOG.info("routing metric: " + metric + " to " + tbh.getHost() + ":" + tbh.getTcpPort());
|
||||
return tbh;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimelyBalancedHost getHostPortKeyIngest(String metric) {
|
||||
return getHostPortKeyInternal(metric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimelyBalancedHost getHostPortKey(String metric) {
|
||||
return getHostPortKeyInternal(metric);
|
||||
}
|
||||
|
||||
private TimelyBalancedHost getFallbackHost(String metric) {
|
||||
|
||||
TimelyBalancedHost tbh;
|
||||
if (StringUtils.isBlank(metric)) {
|
||||
do {
|
||||
tbh = serverMap.get(Math.abs(r.nextInt() & Integer.MAX_VALUE) % serverMap.size());
|
||||
} while (!tbh.isUp());
|
||||
} else {
|
||||
// reverse metric so that similar metrics go to different hosts in a
|
||||
// reproducible way
|
||||
String reverseMetric = StringUtils.reverse(metric);
|
||||
int x = 0;
|
||||
do {
|
||||
// subtract a charter off of the end until we find a server that
|
||||
// is up
|
||||
int hash = reverseMetric.substring(x++).hashCode();
|
||||
tbh = serverMap.get(Math.abs(hash & Integer.MAX_VALUE) % serverMap.size());
|
||||
} while (!tbh.isUp());
|
||||
}
|
||||
|
||||
// if all else fails
|
||||
if (!tbh.isUp()) {
|
||||
for (TimelyBalancedHost h : serverMap.values()) {
|
||||
if (h.isUp()) {
|
||||
tbh = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tbh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
cd src/main/generated-java
|
||||
flatc --java ../resources/fb/metric.fbs
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace timely.api.flatbuffer;
|
||||
|
||||
file_identifier "TMFB";
|
||||
|
||||
table Tag {
|
||||
key:string;
|
||||
value:string;
|
||||
}
|
||||
|
||||
table Metric {
|
||||
name:string;
|
||||
timestamp:long;
|
||||
value:double;
|
||||
tags:[Tag];
|
||||
}
|
||||
|
||||
table Metrics {
|
||||
metrics:[Metric];
|
||||
}
|
||||
|
||||
root_type Metrics;
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FindBugsFilter>
|
||||
<Match>
|
||||
<Class name="timely.balancer.Balancer" />
|
||||
<Method name="shutdown" />
|
||||
<Bug pattern="UC_USELESS_OBJECT" />
|
||||
</Match>
|
||||
<Match>
|
||||
<Class name="timely.balancer.netty.http.HttpRelayHandler$MyByteArrayOutputStream" />
|
||||
<Method name="getBuf" />
|
||||
<Bug pattern="EI_EXPOSE_REP" />
|
||||
</Match>
|
||||
</FindBugsFilter>
|
||||
+41
-34
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<properties>
|
||||
<!-- PEP 440 compliant version gets supstituted into src/main/python/template/setup.py -->
|
||||
<python.client.version>0.0.7.dev0</python.client.version>
|
||||
<python.client.version>1.0.1.dev0</python.client.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -127,40 +127,47 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>python-dist</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>python</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>setup.py sdist - -dist-dir ${project.build.directory}</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>python-clean</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>rm</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>-rf timely.egg-info setup.py</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>python</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>python-dist</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>python</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>setup.py sdist --dist-dir ${project.build.directory}</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>python-clean</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>rm</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>-rf timely.egg-info setup.py</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
@@ -63,7 +63,7 @@ public class TcpClient implements AutoCloseable {
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
LOG.info("Shutting down connection to Timely at {}:{}", host, port);
|
||||
LOG.trace("Shutting down connection to Timely at {}:{}", host, port);
|
||||
if (null != sock) {
|
||||
try {
|
||||
if (null != out) {
|
||||
@@ -86,13 +86,13 @@ public class TcpClient implements AutoCloseable {
|
||||
sock = new Socket(host, port);
|
||||
out = new PrintWriter(sock.getOutputStream(), false);
|
||||
backoff = 2000;
|
||||
LOG.info("Connected to Timely at {}:{}", host, port);
|
||||
LOG.trace("Connected to Timely at {}:{}", host, port);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error connecting to Timely at {}:" + host + ":" + port + ". Error: " + e.getMessage());
|
||||
backoff = backoff * 2;
|
||||
sock = null;
|
||||
out = null;
|
||||
LOG.warn("Will retry connection in {} ms.", backoff);
|
||||
LOG.info("Will retry connection in {} ms.", backoff);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package timely.client.udp;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -9,13 +12,19 @@ import java.net.InetSocketAddress;
|
||||
|
||||
public class UdpClient implements AutoCloseable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UdpClient.class);
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final InetSocketAddress address;
|
||||
private final DatagramPacket packet;
|
||||
private DatagramSocket sock;
|
||||
|
||||
public UdpClient(String hostname, int port) {
|
||||
this.address = new InetSocketAddress(hostname, port);
|
||||
public UdpClient(String host, int port) {
|
||||
this.address = new InetSocketAddress(host, port);
|
||||
this.packet = new DatagramPacket("".getBytes(UTF_8), 0, 0, address.getAddress(), port);
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void open() throws IOException {
|
||||
@@ -29,6 +38,7 @@ public class UdpClient implements AutoCloseable {
|
||||
throw new IllegalStateException("Must call open first");
|
||||
}
|
||||
this.packet.setData(metric.getBytes(UTF_8));
|
||||
LOG.info("writing '" + metric + "' to " + this.host + ":" + this.port);
|
||||
this.sock.send(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package timely.model.parse;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timely.model.Tag;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -41,7 +41,7 @@ class TimeDateRange:
|
||||
def getBeginMs(self):
|
||||
if self.beginDt == None:
|
||||
return None
|
||||
return int(time.mktime(self.beginDt.timetuple()) * 1000)
|
||||
return TimeDateRange.unix_time_millis(self.beginDt)
|
||||
|
||||
def getBeginStr(self):
|
||||
if self.beginDt == None:
|
||||
@@ -51,7 +51,7 @@ class TimeDateRange:
|
||||
def getEndMs(self):
|
||||
if self.endDt == None:
|
||||
return None
|
||||
return int(time.mktime(self.endDt.timetuple()) * 1000)
|
||||
return TimeDateRange.unix_time_millis(self.endDt)
|
||||
|
||||
def getEndStr(self):
|
||||
if self.endDt == None:
|
||||
@@ -123,6 +123,6 @@ class TimeDateRange:
|
||||
|
||||
@classmethod
|
||||
def unix_time_millis(cls, dt):
|
||||
return (dt - TimeDateRange.epoch).total_seconds() * 1000.0
|
||||
return int((dt - TimeDateRange.epoch).total_seconds() * 1000.0)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class TimelyWebSocketClient(WebSocketClient):
|
||||
self.startTime = startTime
|
||||
self.endTime = endTime
|
||||
self.subscriptionId = random.randint(1000000000, 9999999999)
|
||||
WebSocketClient.__init__(self, hostport, connect_timeout, request_timeout)
|
||||
WebSocketClient.__init__(self, hostport, '/websocket', {'metric' : metric}, connect_timeout, request_timeout)
|
||||
|
||||
def _on_message(self, msg):
|
||||
# implement in subclass
|
||||
@@ -37,7 +37,6 @@ class TimelyWebSocketClient(WebSocketClient):
|
||||
"subscriptionId": self.subscriptionId,
|
||||
}
|
||||
self.send(create)
|
||||
self.send(create)
|
||||
|
||||
m1 = {
|
||||
"operation": "add",
|
||||
@@ -105,30 +104,41 @@ class TimelyMetric(TimelyWebSocketClient):
|
||||
global endtime
|
||||
|
||||
responsesObj = json.loads(msg)
|
||||
|
||||
responses = responsesObj.get("responses")
|
||||
if responses is not None:
|
||||
for obj in responses:
|
||||
complete = bool(obj.get("complete"))
|
||||
if complete:
|
||||
self._on_connection_close()
|
||||
return
|
||||
else:
|
||||
date = int(obj.get("timestamp")/1000)
|
||||
dt = pandas.datetime.utcfromtimestamp(date)
|
||||
metricName = str(obj.get("metric"))
|
||||
metricValue = obj.get("value")
|
||||
|
||||
for obj in responses:
|
||||
complete = bool(obj.get("complete"))
|
||||
if complete:
|
||||
self._on_connection_close()
|
||||
return
|
||||
else:
|
||||
date = int(obj.get("timestamp")/1000)
|
||||
dt = pandas.datetime.utcfromtimestamp(date)
|
||||
metricName = str(obj.get("metric"))
|
||||
metricValue = obj.get("value")
|
||||
newData = {}
|
||||
newData["date"] = dt
|
||||
newData[metricName] = metricValue
|
||||
|
||||
newData = {}
|
||||
newData["date"] = dt
|
||||
newData[metricName] = metricValue
|
||||
|
||||
tags = obj.get("tags")
|
||||
for d in tags:
|
||||
for k,v in d.items():
|
||||
newData[k] = v
|
||||
|
||||
self.data.append(newData)
|
||||
tags = obj.get("tags")
|
||||
for d in tags:
|
||||
for k,v in d.items():
|
||||
newData[k] = v
|
||||
|
||||
self.data.append(newData)
|
||||
else:
|
||||
print("Session ended abnormally")
|
||||
exception = responsesObj.get("TimelyException")
|
||||
if exception is not None:
|
||||
code = exception.get("code")
|
||||
message = exception.get("message")
|
||||
print("Exception")
|
||||
print("code: " + str(code))
|
||||
print("message: " + message)
|
||||
self._on_connection_close()
|
||||
return
|
||||
|
||||
def _on_connection_close(self):
|
||||
global client
|
||||
@@ -137,6 +147,8 @@ class TimelyMetric(TimelyWebSocketClient):
|
||||
if len(self.dataFrame) > 0:
|
||||
self.dataFrame['date'] = pandas.DatetimeIndex(self.dataFrame['date'])
|
||||
self.dataFrame = self.dataFrame.set_index('date')
|
||||
j = pandas.to_timedelta(self.dataFrame.groupby(self.dataFrame.index).cumcount(), unit='us').values
|
||||
self.dataFrame.index = self.dataFrame.index + j
|
||||
|
||||
self.close()
|
||||
ioloop.IOLoop.current(instance=False).stop()
|
||||
|
||||
@@ -14,12 +14,26 @@ class WebSocketClient():
|
||||
DEFAULT_REQUEST_TIMEOUT = 60
|
||||
APPLICATION_JSON = 'application/json'
|
||||
|
||||
def __init__(self, hostport, connect_timeout=DEFAULT_CONNECT_TIMEOUT,
|
||||
def __init__(self, hostport, path, params, connect_timeout=DEFAULT_CONNECT_TIMEOUT,
|
||||
request_timeout=DEFAULT_REQUEST_TIMEOUT):
|
||||
|
||||
self.connect_timeout = connect_timeout
|
||||
self.request_timeout = request_timeout
|
||||
self.url = 'wss://' + hostport + '/websocket'
|
||||
self.url = 'wss://' + hostport
|
||||
if (path is not None and len(path) > 0):
|
||||
if (path.startswith('/')):
|
||||
self.url = self.url + path
|
||||
else:
|
||||
self.url = self.url + '/' + path
|
||||
if (any(params.values())):
|
||||
first = True
|
||||
for key, value in params.iteritems():
|
||||
if (first == True):
|
||||
self.url = self.url + '?'
|
||||
first = False
|
||||
else:
|
||||
self.url = self.url + '&'
|
||||
self.url = self.url + key + '=' + value
|
||||
|
||||
def connect(self, ioloop=ioloop.IOLoop.current(instance=False)):
|
||||
"""Connect to the server.
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
import TimelyMetric
|
||||
import TimelyAnalytic
|
||||
import DataOperations
|
||||
import timely.TimelyMetric
|
||||
import timely.TimelyAnalytic
|
||||
import timely.DataOperations
|
||||
import os
|
||||
from TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
|
||||
from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
|
||||
|
||||
hostport = "127.0.0.1:4244"
|
||||
timelyMetric = TimelyMetric.TimelyMetric(hostport, '', None, None, None, 48, None).fetch()
|
||||
hostport = "127.0.0.1:5244"
|
||||
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None).fetch()
|
||||
|
||||
print(timelyMetric.getDataFrame())
|
||||
exit()
|
||||
|
||||
analyticConfig = TimelyAnalyticConfiguration({
|
||||
'system_name' : 'TestSystem',
|
||||
'counter' : False,
|
||||
'includeColRegex' : None,
|
||||
'excludeColRegex' : None,
|
||||
'groupByColumn' : 'instance',
|
||||
'sample' : '10 minutes',
|
||||
'groupByColumn' : 'host',
|
||||
'sample' : '1 minute',
|
||||
'how' : 'mean',
|
||||
'rolling_average_period' : '12 hours',
|
||||
'min_threshold' : None,
|
||||
'average_min_threshold' : None,
|
||||
'max_threshold' : None,
|
||||
'average_max_threshold' : None,
|
||||
'min_threshold_percentage' : -50,
|
||||
'max_threshold_percentage' : 50,
|
||||
'min_threshold_percentage' : None,
|
||||
'max_threshold_percentage' : None,
|
||||
'boolean' : 'and',
|
||||
'min_alert_period' : '5 minutes',
|
||||
'last_alert' : '1 hour',
|
||||
'min_alert_period' : None,
|
||||
'last_alert' : None,
|
||||
'display' : 'all',
|
||||
'output_dir' : '/path/to/output'
|
||||
})
|
||||
|
||||
alert = TimelyAnalytic.find_alerts(timelyMetric, analyticConfig)
|
||||
alert = timely.TimelyAnalytic.find_alerts(timelyMetric, analyticConfig)
|
||||
|
||||
if alert is not None:
|
||||
# write graph to file
|
||||
@@ -37,7 +40,7 @@ if alert is not None:
|
||||
file = alert.graph(type='html')
|
||||
os.umask(oldmask)
|
||||
|
||||
text = DataOperations.getTitle(timelyMetric, analyticConfig)
|
||||
text = timely.DataOperations.getTitle(timelyMetric, analyticConfig)
|
||||
|
||||
# send email with graph attached
|
||||
alert.email("", "", text, text, [file])
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>collectd-nsq-plugin</artifactId>
|
||||
<dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>collectd-plugin-parent</artifactId>
|
||||
<dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>collectd-timely-plugin</artifactId>
|
||||
<dependencies>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>timely-common</artifactId>
|
||||
<dependencies>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
+35
-21
@@ -227,33 +227,47 @@ System.register(['lodash', 'angular', '../../../app/core/utils/datemath'], funct
|
||||
key: 'performTimeSeriesQuery',
|
||||
value: function performTimeSeriesQuery(queries, start, end) {
|
||||
var msResolution = true;
|
||||
var responsePromises = [];
|
||||
|
||||
var reqBody = {
|
||||
start: start,
|
||||
queries: queries,
|
||||
msResolution: msResolution,
|
||||
globalAnnotations: true,
|
||||
showQuery: true
|
||||
};
|
||||
// execute backend datasourceRequests separately
|
||||
// for better performance and load balancing
|
||||
_.each(queries, function (query) {
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (end) {
|
||||
reqBody.end = end;
|
||||
}
|
||||
var reqBody = {
|
||||
start: start,
|
||||
queries: [query],
|
||||
msResolution: msResolution,
|
||||
globalAnnotations: true,
|
||||
showQuery: true
|
||||
};
|
||||
|
||||
var options = {
|
||||
method: 'POST',
|
||||
url: this.url + '/api/query',
|
||||
data: reqBody
|
||||
};
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (end) {
|
||||
reqBody.end = end;
|
||||
}
|
||||
|
||||
this._addCredentialOptions(options);
|
||||
var options = {
|
||||
method: 'POST',
|
||||
url: this.url + '/api/query?metric=' + query.metric,
|
||||
data: reqBody
|
||||
};
|
||||
this._addCredentialOptions(options);
|
||||
|
||||
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
|
||||
// go as POST rather than OPTIONS+POST
|
||||
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
|
||||
// go as POST rather than OPTIONS+POST
|
||||
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
responsePromises.push(this.backendSrv.datasourceRequest(options));
|
||||
}.bind(this));
|
||||
|
||||
return this.backendSrv.datasourceRequest(options);
|
||||
// wait until all promised datasourceRequests complete
|
||||
// and then return combined responses
|
||||
return Promise.all(responsePromises).then(function (queryResponses) {
|
||||
var combinedResponse = queryResponses[0];
|
||||
for (var x = 1; x < queryResponses.length; x++) {
|
||||
combinedResponse.data = combinedResponse.data.concat(queryResponses[x].data);
|
||||
}
|
||||
return combinedResponse;
|
||||
}.bind(this));
|
||||
}
|
||||
}, {
|
||||
key: '_saveTagKeys',
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"sources":["../../src/datasource/datasource.js"],"names":["_","angular","dateMath","TimelyDatasource","instanceSettings","$q","backendSrv","templateSrv","contextSrv","type","name","jsonData","httpsPort","timelyHost","wsPort","basicAuths","url","_makeHttpsUrl","withCredentials","basicAuth","supportMetrics","tagKeys","aggregatorsPromise","filterTypesPromise","options","start","convertToTSDBTime","rangeRaw","from","end","to","qs","each","targets","target","metric","push","convertTargetToQuery","bind","queries","compact","isEmpty","d","defer","resolve","data","promise","groupByTags","query","filters","length","val","tagk","tags","key","performTimeSeriesQuery","then","response","metricToTargetMapping","mapMetricsToTargets","result","map","metricData","index","_saveTagKeys","transformMetricData","datasourceRequest","method","status","message","title","eventList","aggregator","annotation","results","annotationObject","annotations","isGlobal","globalAnnotations","event","description","time","Math","floor","startTime","text","notes","when","interpolated","replace","err","reject","responseTransform","value","metrics_regex","tag_names_regex","tag_values_regex","tag_names_suggest_regex","tag_values_suggest_regex","metrics_query","match","_performSuggestQuery","tag_names_query","_performMetricKeyLookup","tag_values_query","_performMetricKeyValueLookup","tag_names_suggest_query","tag_values_suggest_query","msResolution","reqBody","showQuery","_addCredentialOptions","headers","Object","keys","aggregateTags","tag","_get","q","max","keysArray","split","trim","keysQuery","splice","join","m","limit","tagvs","r","indexOf","tagks","tagv","relativeUrl","params","isArray","sort","md","metricLabel","createMetricLabel","dps","v","k","datapoints","alias","scopedVars","clone","label","tagData","toPairs","has","hide","shouldComputeRate","rate","rateOptions","counter","isCounter","counterMax","parseInt","counterResetValue","resetValue","disableDownsampling","interval","downsampleInterval","parseFloat","downsample","downsampleAggregator","downsampleFillPolicy","copy","filter_key","filter","tag_key","metrics","interpolatedTagValue","findIndex","every","tagV","tagK","date","roundUp","parse","valueOf"],"mappings":";;;;;;;;;;;;;;;AAAOA,O;;AACAC,a;;AACKC,c;;;;;;;;;;;;;;;;;;;;;kCAECC,gB;;AAEX;AACA,kCAAYC,gBAAZ,EAA8BC,EAA9B,EAAkCC,UAAlC,EAA8CC,WAA9C,EAA2DC,UAA3D,EAAuE;AAAA;;AACrE,eAAKC,IAAL,GAAYL,iBAAiBK,IAA7B;AACA,eAAKC,IAAL,GAAYN,iBAAiBM,IAA7B;;AAEA,eAAKC,QAAL,GAAgBP,iBAAiBO,QAAjB,IAA6B,EAA7C;AACA,eAAKC,SAAL,GAAiBR,iBAAiBO,QAAjB,CAA0BC,SAA3C;AACA,eAAKC,UAAL,GAAkBT,iBAAiBO,QAAjB,CAA0BE,UAA5C;AACA,eAAKC,MAAL,GAAcV,iBAAiBO,QAAjB,CAA0BG,MAAxC;AACA,cAAI,CAACV,iBAAiBO,QAAjB,CAA0BI,UAA/B,EAA0C;AACxCX,6BAAiBO,QAAjB,CAA0BI,UAA1B,GAAuC,KAAvC;AACD;;AAED,eAAKC,GAAL,GAAW,KAAKC,aAAL,EAAX;AACA,eAAKC,eAAL,GAAuB,IAAvB;AACA,eAAKC,SAAL,GAAiB,KAAjB;;AAEA,eAAKC,cAAL,GAAsB,IAAtB;AACA,eAAKC,OAAL,GAAe,EAAf;;AAEA,eAAKhB,EAAL,GAAUA,EAAV;AACA,eAAKC,UAAL,GAAkBA,UAAlB;AACA,eAAKC,WAAL,GAAmBA,WAAnB;AACA,eAAKC,UAAL,GAAkBA,UAAlB;;AAEA,eAAKc,kBAAL,GAA0B,IAA1B;AACA,eAAKC,kBAAL,GAA0B,IAA1B;AAED;;AAED;;;;;gCACMC,O,EAAQ;AACZ,gBAAIC,QAAQ,KAAKC,iBAAL,CAAuBF,QAAQG,QAAR,CAAiBC,IAAxC,EAA8C,KAA9C,CAAZ;AACA,gBAAIC,MAAM,KAAKH,iBAAL,CAAuBF,QAAQG,QAAR,CAAiBG,EAAxC,EAA4C,IAA5C,CAAV;AACA,gBAAIC,KAAK,EAAT;;AAEA/B,cAAEgC,IAAF,CAAOR,QAAQS,OAAf,EAAwB,UAASC,MAAT,EAAiB;AACvC,kBAAI,CAACA,OAAOC,MAAZ,EAAoB;AAAE;AAAS;AAC/BJ,iBAAGK,IAAH,CAAQ,KAAKC,oBAAL,CAA0BH,MAA1B,EAAkCV,OAAlC,CAAR;AACD,aAHuB,CAGtBc,IAHsB,CAGjB,IAHiB,CAAxB;;AAKA,gBAAIC,UAAUvC,EAAEwC,OAAF,CAAUT,EAAV,CAAd;;AAEA;AACA,gBAAI/B,EAAEyC,OAAF,CAAUF,OAAV,CAAJ,EAAwB;AACtB,kBAAIG,IAAI,KAAKrC,EAAL,CAAQsC,KAAR,EAAR;AACAD,gBAAEE,OAAF,CAAU,EAAEC,MAAM,EAAR,EAAV;AACA,qBAAOH,EAAEI,OAAT;AACD;;AAED,gBAAIC,cAAc,EAAlB;AACA/C,cAAEgC,IAAF,CAAOO,OAAP,EAAgB,UAASS,KAAT,EAAgB;AAC9B,kBAAIA,MAAMC,OAAN,IAAiBD,MAAMC,OAAN,CAAcC,MAAd,GAAuB,CAA5C,EAA+C;AAC7ClD,kBAAEgC,IAAF,CAAOgB,MAAMC,OAAb,EAAsB,UAASE,GAAT,EAAc;AAClCJ,8BAAYI,IAAIC,IAAhB,IAAwB,IAAxB;AACD,iBAFD;AAGD,eAJD,MAIO;AACLpD,kBAAEgC,IAAF,CAAOgB,MAAMK,IAAb,EAAmB,UAASF,GAAT,EAAcG,GAAd,EAAmB;AACpCP,8BAAYO,GAAZ,IAAmB,IAAnB;AACD,iBAFD;AAGD;AACF,aAVD;;AAYA,mBAAO,KAAKC,sBAAL,CAA4BhB,OAA5B,EAAqCd,KAArC,EAA4CI,GAA5C,EAAiD2B,IAAjD,CAAsD,UAASC,QAAT,EAAmB;AAC9E,kBAAIC,wBAAwB,KAAKC,mBAAL,CAAyBF,SAASZ,IAAlC,EAAwCrB,OAAxC,CAA5B;AACA,kBAAIoC,SAAS5D,EAAE6D,GAAF,CAAMJ,SAASZ,IAAf,EAAqB,UAASiB,UAAT,EAAqBC,KAArB,EAA4B;AAC5DA,wBAAQL,sBAAsBK,KAAtB,CAAR;AACA,oBAAIA,UAAU,CAAC,CAAf,EAAkB;AAChBA,0BAAQ,CAAR;AACD;AACD,qBAAKC,YAAL,CAAkBF,UAAlB;;AAEA,uBAAO,KAAKG,mBAAL,CAAyBH,UAAzB,EAAqCf,WAArC,EAAkDvB,QAAQS,OAAR,CAAgB8B,KAAhB,CAAlD,EAA0EvC,OAA1E,CAAP;AACD,eARiC,CAQhCc,IARgC,CAQ3B,IAR2B,CAArB,CAAb;AASA,qBAAO,EAAEO,MAAMe,MAAR,EAAP;AACD,aAZ4D,CAY3DtB,IAZ2D,CAYtD,IAZsD,CAAtD,CAAP;AAaD;;;2CAGe;AACd,mBAAO,KAAKhC,UAAL,CAAgB4D,iBAAhB,CAAkC;AACvClD,mBAAK,KAAKA,GAAL,GAAW,UADuB;AAEvCmD,sBAAQ;AAF+B,aAAlC,EAGJX,IAHI,CAGC,oBAAY;AAClB,kBAAIC,SAASW,MAAT,KAAoB,GAAxB,EAA6B;AAC3B,uBAAO,EAAEA,QAAQ,SAAV,EAAqBC,SAAS,wBAA9B,EAAwDC,OAAO,SAA/D,EAAP;AACLine truncated
|
||||
{"version":3,"sources":["../../src/datasource/datasource.js"],"names":["_","angular","dateMath","TimelyDatasource","instanceSettings","$q","backendSrv","templateSrv","contextSrv","type","name","jsonData","httpsPort","timelyHost","wsPort","basicAuths","url","_makeHttpsUrl","withCredentials","basicAuth","supportMetrics","tagKeys","aggregatorsPromise","filterTypesPromise","options","start","convertToTSDBTime","rangeRaw","from","end","to","qs","each","targets","target","metric","push","convertTargetToQuery","bind","queries","compact","isEmpty","d","defer","resolve","data","promise","groupByTags","query","filters","length","val","tagk","tags","key","performTimeSeriesQuery","then","response","metricToTargetMapping","mapMetricsToTargets","result","map","metricData","index","_saveTagKeys","transformMetricData","datasourceRequest","method","status","message","title","eventList","aggregator","annotation","results","annotationObject","annotations","isGlobal","globalAnnotations","event","description","time","Math","floor","startTime","text","notes","when","interpolated","replace","err","reject","responseTransform","value","metrics_regex","tag_names_regex","tag_values_regex","tag_names_suggest_regex","tag_values_suggest_regex","metrics_query","match","_performSuggestQuery","tag_names_query","_performMetricKeyLookup","tag_values_query","_performMetricKeyValueLookup","tag_names_suggest_query","tag_values_suggest_query","msResolution","responsePromises","reqBody","showQuery","_addCredentialOptions","headers","Promise","all","queryResponses","combinedResponse","x","concat","Object","keys","aggregateTags","tag","_get","q","max","keysArray","split","trim","keysQuery","splice","join","m","limit","tagvs","r","indexOf","tagks","tagv","relativeUrl","params","isArray","sort","md","metricLabel","createMetricLabel","dps","v","k","datapoints","alias","scopedVars","clone","label","tagData","toPairs","has","hide","shouldComputeRate","rate","rateOptions","counter","isCounter","counterMax","parseInt","counterResetValue","resetValue","disableDownsampling","interval","downsampleInterval","parseFloat","downsample","downsampleAggregator","downsampleFillPolicy","copy","filter_key","filter","tag_key","metrics","interpolatedTagValue","findIndex","every","tagV","tagK","date","roundUp","parse","valueOf"],"mappings":";;;;;;;;;;;;;;;AAAOA,O;;AACAC,a;;AACKC,c;;;;;;;;;;;;;;;;;;;;;kCAECC,gB;;AAEX;AACA,kCAAYC,gBAAZ,EAA8BC,EAA9B,EAAkCC,UAAlC,EAA8CC,WAA9C,EAA2DC,UAA3D,EAAuE;AAAA;;AACrE,eAAKC,IAAL,GAAYL,iBAAiBK,IAA7B;AACA,eAAKC,IAAL,GAAYN,iBAAiBM,IAA7B;;AAEA,eAAKC,QAAL,GAAgBP,iBAAiBO,QAAjB,IAA6B,EAA7C;AACA,eAAKC,SAAL,GAAiBR,iBAAiBO,QAAjB,CAA0BC,SAA3C;AACA,eAAKC,UAAL,GAAkBT,iBAAiBO,QAAjB,CAA0BE,UAA5C;AACA,eAAKC,MAAL,GAAcV,iBAAiBO,QAAjB,CAA0BG,MAAxC;AACA,cAAI,CAACV,iBAAiBO,QAAjB,CAA0BI,UAA/B,EAA0C;AACxCX,6BAAiBO,QAAjB,CAA0BI,UAA1B,GAAuC,KAAvC;AACD;;AAED,eAAKC,GAAL,GAAW,KAAKC,aAAL,EAAX;AACA,eAAKC,eAAL,GAAuB,IAAvB;AACA,eAAKC,SAAL,GAAiB,KAAjB;;AAEA,eAAKC,cAAL,GAAsB,IAAtB;AACA,eAAKC,OAAL,GAAe,EAAf;;AAEA,eAAKhB,EAAL,GAAUA,EAAV;AACA,eAAKC,UAAL,GAAkBA,UAAlB;AACA,eAAKC,WAAL,GAAmBA,WAAnB;AACA,eAAKC,UAAL,GAAkBA,UAAlB;;AAEA,eAAKc,kBAAL,GAA0B,IAA1B;AACA,eAAKC,kBAAL,GAA0B,IAA1B;AAED;;AAED;;;;;gCACMC,O,EAAQ;AACZ,gBAAIC,QAAQ,KAAKC,iBAAL,CAAuBF,QAAQG,QAAR,CAAiBC,IAAxC,EAA8C,KAA9C,CAAZ;AACA,gBAAIC,MAAM,KAAKH,iBAAL,CAAuBF,QAAQG,QAAR,CAAiBG,EAAxC,EAA4C,IAA5C,CAAV;AACA,gBAAIC,KAAK,EAAT;;AAEA/B,cAAEgC,IAAF,CAAOR,QAAQS,OAAf,EAAwB,UAASC,MAAT,EAAiB;AACvC,kBAAI,CAACA,OAAOC,MAAZ,EAAoB;AAAE;AAAS;AAC/BJ,iBAAGK,IAAH,CAAQ,KAAKC,oBAAL,CAA0BH,MAA1B,EAAkCV,OAAlC,CAAR;AACD,aAHuB,CAGtBc,IAHsB,CAGjB,IAHiB,CAAxB;;AAKA,gBAAIC,UAAUvC,EAAEwC,OAAF,CAAUT,EAAV,CAAd;;AAEA;AACA,gBAAI/B,EAAEyC,OAAF,CAAUF,OAAV,CAAJ,EAAwB;AACtB,kBAAIG,IAAI,KAAKrC,EAAL,CAAQsC,KAAR,EAAR;AACAD,gBAAEE,OAAF,CAAU,EAAEC,MAAM,EAAR,EAAV;AACA,qBAAOH,EAAEI,OAAT;AACD;;AAED,gBAAIC,cAAc,EAAlB;AACA/C,cAAEgC,IAAF,CAAOO,OAAP,EAAgB,UAASS,KAAT,EAAgB;AAC9B,kBAAIA,MAAMC,OAAN,IAAiBD,MAAMC,OAAN,CAAcC,MAAd,GAAuB,CAA5C,EAA+C;AAC7ClD,kBAAEgC,IAAF,CAAOgB,MAAMC,OAAb,EAAsB,UAASE,GAAT,EAAc;AAClCJ,8BAAYI,IAAIC,IAAhB,IAAwB,IAAxB;AACD,iBAFD;AAGD,eAJD,MAIO;AACLpD,kBAAEgC,IAAF,CAAOgB,MAAMK,IAAb,EAAmB,UAASF,GAAT,EAAcG,GAAd,EAAmB;AACpCP,8BAAYO,GAAZ,IAAmB,IAAnB;AACD,iBAFD;AAGD;AACF,aAVD;;AAYA,mBAAO,KAAKC,sBAAL,CAA4BhB,OAA5B,EAAqCd,KAArC,EAA4CI,GAA5C,EAAiD2B,IAAjD,CAAsD,UAASC,QAAT,EAAmB;AAC9E,kBAAIC,wBAAwB,KAAKC,mBAAL,CAAyBF,SAASZ,IAAlC,EAAwCrB,OAAxC,CAA5B;AACA,kBAAIoC,SAAS5D,EAAE6D,GAAF,CAAMJ,SAASZ,IAAf,EAAqB,UAASiB,UAAT,EAAqBC,KAArB,EAA4B;AAC5DA,wBAAQL,sBAAsBK,KAAtB,CAAR;AACA,oBAAIA,UAAU,CAAC,CAAf,EAAkB;AAChBA,0BAAQ,CAAR;AACD;AACD,qBAAKC,YAAL,CAAkBF,UAAlB;;AAEA,uBAAO,KAAKG,mBAAL,CAAyBH,UAAzB,EAAqCf,WAArC,EAAkDvB,QAAQS,OAAR,CAAgB8B,KAAhB,CAAlD,EAA0EvC,OAA1E,CAAP;AACD,eARiC,CAQhCc,IARgC,CAQ3B,IAR2B,CAArB,CAAb;AASA,qBAAO,EAAEO,MAAMe,MAAR,EAAP;AACD,aAZ4D,CAY3DtB,IAZ2D,CAYtD,IAZsD,CAAtD,CAAP;AAaD;;;2CAGe;AACd,mBAAO,KAAKhC,UAAL,CAAgB4D,iBAAhB,CAAkC;AACvClD,mBAAK,KAAKA,GAAL,GAAW,UADuB;AAEvCmD,sBAAQ;AAF+B,aAAlC,EAGJX,IAHI,CAGC,oBAAY;AAClB,kBAAIC,SAASW,MAAT,KAAoB,GLine truncated
|
||||
@@ -28,7 +28,8 @@
|
||||
"grunt-execute": "~0.2.2",
|
||||
"grunt-mocha-test": "~0.12.7",
|
||||
"grunt-systemjs-builder": "^0.2.5",
|
||||
"load-grunt-tasks": "~3.2.0"
|
||||
"load-grunt-tasks": "~3.2.0",
|
||||
"mocha": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ag-grid": "^5.0.2",
|
||||
|
||||
@@ -183,33 +183,47 @@ export class TimelyDatasource {
|
||||
|
||||
performTimeSeriesQuery(queries, start, end){
|
||||
var msResolution = true;
|
||||
var responsePromises = [];
|
||||
|
||||
var reqBody = {
|
||||
start: start,
|
||||
queries: queries,
|
||||
msResolution: msResolution,
|
||||
globalAnnotations: true,
|
||||
showQuery: true
|
||||
};
|
||||
// execute backend datasourceRequests separately
|
||||
// for better performance and load balancing
|
||||
_.each(queries, function(query) {
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (end) {
|
||||
reqBody.end = end;
|
||||
}
|
||||
var reqBody = {
|
||||
start: start,
|
||||
queries: [query],
|
||||
msResolution: msResolution,
|
||||
globalAnnotations: true,
|
||||
showQuery: true
|
||||
};
|
||||
|
||||
var options = {
|
||||
method: 'POST',
|
||||
url: this.url + '/api/query',
|
||||
data: reqBody
|
||||
};
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (end) {
|
||||
reqBody.end = end;
|
||||
}
|
||||
|
||||
this._addCredentialOptions(options);
|
||||
var options = {
|
||||
method: 'POST',
|
||||
url: this.url + '/api/query?metric=' + query.metric,
|
||||
data: reqBody
|
||||
};
|
||||
this._addCredentialOptions(options);
|
||||
|
||||
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
|
||||
// go as POST rather than OPTIONS+POST
|
||||
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
// In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
|
||||
// go as POST rather than OPTIONS+POST
|
||||
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
responsePromises.push(this.backendSrv.datasourceRequest(options));
|
||||
}.bind(this));
|
||||
|
||||
return this.backendSrv.datasourceRequest(options);
|
||||
// wait until all promised datasourceRequests complete
|
||||
// and then return combined responses
|
||||
return Promise.all(responsePromises).then(function(queryResponses) {
|
||||
var combinedResponse = queryResponses[0];
|
||||
for (var x=1; x < queryResponses.length; x++) {
|
||||
combinedResponse.data = combinedResponse.data.concat(queryResponses[x].data);
|
||||
}
|
||||
return combinedResponse;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
_saveTagKeys(metricData) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Timely</name>
|
||||
<description>A secure and scalable time series database based on Apache Accumulo</description>
|
||||
@@ -28,6 +28,7 @@
|
||||
<module>server</module>
|
||||
<module>grafana</module>
|
||||
<module>analytics</module>
|
||||
<module>balancer</module>
|
||||
</modules>
|
||||
<properties>
|
||||
<!-- Configure the os-maven-plugin extension to expand the classifier on -->
|
||||
@@ -43,6 +44,8 @@
|
||||
<version.caffeine>2.3.3</version.caffeine>
|
||||
<version.collectd>5.5.1</version.collectd>
|
||||
<version.commons-collections4>4.1</version.commons-collections4>
|
||||
<version.commons-configuration>1.10</version.commons-configuration>
|
||||
<version.commons-io>2.6</version.commons-io>
|
||||
<version.commons-lang3>3.5</version.commons-lang3>
|
||||
<version.commons-pool2>2.4.2</version.commons-pool2>
|
||||
<version.disruptor>3.3.6</version.disruptor>
|
||||
@@ -57,11 +60,12 @@
|
||||
<version.jsoup>1.9.2</version.jsoup>
|
||||
<version.junit>4.12</version.junit>
|
||||
<version.log4j>2.6.2</version.log4j>
|
||||
<version.netty>4.0.46.Final</version.netty>
|
||||
<version.netty-tcnative>2.0.1.Final</version.netty-tcnative>
|
||||
<version.netty>4.0.56.Final</version.netty>
|
||||
<version.netty-tcnative>2.0.7.Final</version.netty-tcnative>
|
||||
<version.powermock>1.6.4</version.powermock>
|
||||
<version.slf4j>1.7.21</version.slf4j>
|
||||
<version.spring-boot>1.4.1.RELEASE</version.spring-boot>
|
||||
<version.spring-websocket>5.0.8.RELEASE</version.spring-websocket>
|
||||
<!-- plugin versions -->
|
||||
<version.eclipse-lifecycle>1.0.0</version.eclipse-lifecycle>
|
||||
<version.findbugs-maven>3.0.3</version.findbugs-maven>
|
||||
@@ -134,6 +138,11 @@
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>LATEST</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fi.iki.yak</groupId>
|
||||
<artifactId>compression-gorilla</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.lukehutch</groupId>
|
||||
<artifactId>fast-classpath-scanner</artifactId>
|
||||
@@ -161,6 +170,16 @@
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${version.commons-collections4}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-configuration</groupId>
|
||||
<artifactId>commons-configuration</artifactId>
|
||||
<version>${version.commons-configuration}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${version.commons-io}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
export TRUSTSTORE_FILE=
|
||||
export TRUSTSTORE_TYPE="jks";
|
||||
export TRUSTSTORE_PASS=
|
||||
export KEYSTORE_FILE=
|
||||
export KEYSTORE_TYPE="pkcs12";
|
||||
export KEYSTORE_PASS=
|
||||
|
||||
export NUM_QUERY_THREADS=4
|
||||
export NUM_INGEST_THREADS=4
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
BASE_DIR=${THIS_DIR}/../..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
|
||||
. ${THIS_DIR}/load-test-env.sh
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${THIS_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx1G -Xms1G"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricPutLoadTest $*"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricPutLoadTest $*
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
BASE_DIR=${THIS_DIR}/../..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
|
||||
. ${THIS_DIR}/load-test-env.sh
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${THIS_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx1G -Xms1G"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricQueryLoadTest $*"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricQueryLoadTest $*
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
|
||||
sed -i -- "s/#.*#/#$1#/g" $THIS_DIR/load-test-data.txt
|
||||
@@ -3,6 +3,10 @@ timely:
|
||||
meta-table: timely.meta
|
||||
metric-age-off-days:
|
||||
default: 7
|
||||
cache:
|
||||
enabled:
|
||||
age-off-hours:
|
||||
default: 30
|
||||
metrics-report-ignored-tags:
|
||||
accumulo:
|
||||
instance-name:
|
||||
|
||||
+13
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>timely</groupId>
|
||||
<artifactId>timely</artifactId>
|
||||
<version>0.0.7-SNAPSHOT</version>
|
||||
<version>1.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -54,6 +54,10 @@
|
||||
<artifactId>disruptor</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fi.iki.yak</groupId>
|
||||
<artifactId>compression-gorilla</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.lukehutch</groupId>
|
||||
<artifactId>fast-classpath-scanner</artifactId>
|
||||
@@ -74,6 +78,10 @@
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
@@ -174,6 +182,10 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-configuration</groupId>
|
||||
<artifactId>commons-configuration</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<extensions>
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.Valid;
|
||||
@@ -15,6 +16,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import timely.store.cache.DataStoreCache;
|
||||
import timely.validator.NotEmptyIfFieldSet;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -27,6 +29,7 @@ public class Configuration {
|
||||
private String metaTable = "timely.meta";
|
||||
private HashMap<String, Integer> metricAgeOffDays = new HashMap<>();
|
||||
private List<String> metricsReportIgnoredTags = new ArrayList<>();
|
||||
private String instance = null;
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
@@ -45,6 +48,9 @@ public class Configuration {
|
||||
private MetaCache metaCache = new MetaCache();
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private Cache cache = new Cache();
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private VisibilityCache visibilityCache = new VisibilityCache();
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
@@ -68,6 +74,14 @@ public class Configuration {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setInstance(String instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public String getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public HashMap<String, Integer> getMetricAgeOffDays() {
|
||||
return metricAgeOffDays;
|
||||
}
|
||||
@@ -109,6 +123,10 @@ public class Configuration {
|
||||
return metaCache;
|
||||
}
|
||||
|
||||
public Cache getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public VisibilityCache getVisibilityCache() {
|
||||
return visibilityCache;
|
||||
}
|
||||
@@ -652,6 +670,61 @@ public class Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
public class Cache {
|
||||
|
||||
private boolean enabled = false;
|
||||
private HashMap<String, Integer> metricAgeOffHours = new HashMap<>();
|
||||
private List<String> nonCachedMetrics = new ArrayList<>();
|
||||
private long maxUniqueTagSets = 50000;
|
||||
private long flushInterval = 5000;
|
||||
|
||||
public HashMap<String, Integer> getMetricAgeOffHours() {
|
||||
return metricAgeOffHours;
|
||||
}
|
||||
|
||||
public Configuration setMetricAgeOffHours(HashMap<String, Integer> metricAgeOffHours) {
|
||||
this.metricAgeOffHours = metricAgeOffHours;
|
||||
return Configuration.this;
|
||||
}
|
||||
|
||||
public void setDefaultAgeOffHours(int defaultAgeOffHours) {
|
||||
this.metricAgeOffHours.put(DataStoreCache.DEFAULT_AGEOFF_KEY, defaultAgeOffHours);
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Configuration setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
return Configuration.this;
|
||||
}
|
||||
|
||||
public List<String> getNonCachedMetrics() {
|
||||
return nonCachedMetrics;
|
||||
}
|
||||
|
||||
public void setNonCachedMetrics(List<String> nonCachedMetrics) {
|
||||
this.nonCachedMetrics = nonCachedMetrics;
|
||||
}
|
||||
|
||||
public long getMaxUniqueTagSets() {
|
||||
return maxUniqueTagSets;
|
||||
}
|
||||
|
||||
public void setMaxUniqueTagSets(long maxUniqueTagSets) {
|
||||
this.maxUniqueTagSets = maxUniqueTagSets;
|
||||
}
|
||||
|
||||
public long getFlushInterval() {
|
||||
return flushInterval;
|
||||
}
|
||||
|
||||
public void setFlushInterval(long flushInterval) {
|
||||
this.flushInterval = flushInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public class VisibilityCache {
|
||||
|
||||
private long expirationMinutes = 60;
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollChannelOption;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
@@ -40,6 +41,7 @@ import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.NettyRuntime;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
|
||||
import java.io.File;
|
||||
@@ -60,6 +62,7 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.auth.VisibilityCache;
|
||||
import timely.netty.http.HttpCacheRequestHandler;
|
||||
import timely.netty.http.HttpMetricPutHandler;
|
||||
import timely.netty.http.HttpStaticFileServerHandler;
|
||||
import timely.netty.http.HttpVersionRequestHandler;
|
||||
@@ -96,6 +99,7 @@ import timely.netty.websocket.timeseries.WSSuggestRequestHandler;
|
||||
import timely.store.DataStore;
|
||||
import timely.store.DataStoreFactory;
|
||||
import timely.store.MetaCacheFactory;
|
||||
import timely.store.cache.DataStoreCache;
|
||||
|
||||
public class Server {
|
||||
|
||||
@@ -122,9 +126,11 @@ public class Server {
|
||||
protected Channel tcpChannelHandle = null;
|
||||
protected Channel httpChannelHandle = null;
|
||||
protected Channel wsChannelHandle = null;
|
||||
protected Channel udpChannelHandle = null;
|
||||
protected List<Channel> udpChannelHandleList = new ArrayList<>();
|
||||
protected DataStore dataStore = null;
|
||||
protected DataStoreCache dataStoreCache = null;
|
||||
protected volatile boolean shutdown = false;
|
||||
private final int DEFAULT_EVENT_LOOP_THREADS;
|
||||
|
||||
private static boolean useEpoll() {
|
||||
|
||||
@@ -215,9 +221,9 @@ public class Server {
|
||||
channelFutures.add(wsChannelHandle.close());
|
||||
}
|
||||
|
||||
if (udpChannelHandle != null) {
|
||||
for (Channel c : udpChannelHandleList) {
|
||||
LOG.info("Closing udpChannelHandle");
|
||||
channelFutures.add(udpChannelHandle.close());
|
||||
channelFutures.add(c.close());
|
||||
}
|
||||
|
||||
// wait for the channels to shutdown
|
||||
@@ -312,8 +318,16 @@ public class Server {
|
||||
LOG.info("Server shut down.");
|
||||
}
|
||||
|
||||
public Server(Configuration conf, int eventLoopThreads) throws Exception {
|
||||
|
||||
DEFAULT_EVENT_LOOP_THREADS = eventLoopThreads;
|
||||
this.config = conf;
|
||||
}
|
||||
|
||||
public Server(Configuration conf) throws Exception {
|
||||
|
||||
DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
|
||||
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
|
||||
this.config = conf;
|
||||
}
|
||||
|
||||
@@ -322,6 +336,10 @@ public class Server {
|
||||
int nettyThreads = Math.max(1,
|
||||
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
|
||||
dataStore = DataStoreFactory.create(config, nettyThreads);
|
||||
if (config.getCache().isEnabled()) {
|
||||
dataStoreCache = new DataStoreCache(config);
|
||||
dataStore.setCache(dataStoreCache);
|
||||
}
|
||||
// Initialize the MetaCache
|
||||
MetaCacheFactory.getCache(config);
|
||||
// initialize the auth cache
|
||||
@@ -411,18 +429,21 @@ public class Server {
|
||||
|
||||
final int udpPort = config.getServer().getUdpPort();
|
||||
final String udpIp = config.getServer().getIp();
|
||||
final Bootstrap udpServer = new Bootstrap();
|
||||
udpServer.group(udpBossGroup);
|
||||
udpServer.channel(datagramChannelClass);
|
||||
udpServer.handler(setupUdpChannel());
|
||||
udpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
udpChannelHandle = udpServer.bind(udpIp, udpPort).sync().channel();
|
||||
final String udpAddress = ((InetSocketAddress) wsChannelHandle.localAddress()).getAddress().getHostAddress();
|
||||
|
||||
for (int n = 0; n < DEFAULT_EVENT_LOOP_THREADS; n++) {
|
||||
final Bootstrap udpServer = new Bootstrap();
|
||||
udpServer.group(udpBossGroup);
|
||||
udpServer.channel(datagramChannelClass);
|
||||
udpServer.handler(setupUdpChannel());
|
||||
udpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||
udpServer.option(EpollChannelOption.SO_REUSEADDR, true);
|
||||
udpServer.option(EpollChannelOption.SO_REUSEPORT, true);
|
||||
udpChannelHandleList.add(udpServer.bind(udpIp, udpPort).sync().channel());
|
||||
}
|
||||
shutdownHook();
|
||||
LOG.info(
|
||||
"Server started. Listening on {}:{} for TCP traffic, {}:{} for HTTP traffic, {}:{} for WebSocket traffic, and {}:{} for UDP traffic",
|
||||
tcpAddress, tcpPort, httpAddress, httpPort, wsAddress, wsPort, udpAddress, udpPort);
|
||||
tcpAddress, tcpPort, httpAddress, httpPort, wsAddress, wsPort, wsAddress, udpPort);
|
||||
}
|
||||
|
||||
protected SslContext createSSLContext(Configuration config) throws Exception {
|
||||
@@ -509,6 +530,7 @@ public class Server {
|
||||
ch.pipeline().addLast("search", new HttpSearchLookupRequestHandler(dataStore));
|
||||
ch.pipeline().addLast("suggest", new HttpSuggestRequestHandler(dataStore));
|
||||
ch.pipeline().addLast("version", new HttpVersionRequestHandler());
|
||||
ch.pipeline().addLast("cache", new HttpCacheRequestHandler(dataStoreCache));
|
||||
ch.pipeline().addLast("put", new HttpMetricPutHandler(dataStore));
|
||||
ch.pipeline().addLast("error", new TimelyExceptionHandler());
|
||||
}
|
||||
@@ -525,7 +547,7 @@ public class Server {
|
||||
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
|
||||
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
|
||||
ch.pipeline().addLast("putDecoder", new UdpDecoder());
|
||||
ch.pipeline().addLast(udpWorkerGroup, "putHandler", new TcpPutHandler(dataStore));
|
||||
ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -554,7 +576,8 @@ public class Server {
|
||||
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
|
||||
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(config));
|
||||
ch.pipeline().addLast("idle-handler", new IdleStateHandler(conf.getWebsocket().getTimeout(), 0, 0));
|
||||
ch.pipeline().addLast("ws-protocol", new WebSocketServerProtocolHandler(WS_PATH, null, true));
|
||||
ch.pipeline().addLast("ws-protocol",
|
||||
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true));
|
||||
ch.pipeline().addLast("wsDecoder", new WebSocketRequestDecoder(config));
|
||||
ch.pipeline().addLast("aggregators", new WSAggregatorsRequestHandler());
|
||||
ch.pipeline().addLast("metrics", new WSMetricsRequestHandler(config));
|
||||
|
||||
@@ -2,6 +2,6 @@ package timely;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackageClasses = { Configuration.class })
|
||||
@SpringBootApplication(scanBasePackageClasses = { SpringBootstrap.class })
|
||||
public class SpringBootstrap {
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.adapter.accumulo;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import org.apache.accumulo.core.client.lexicoder.LongLexicoder;
|
||||
import org.apache.accumulo.core.client.lexicoder.PairLexicoder;
|
||||
import org.apache.accumulo.core.client.lexicoder.StringLexicoder;
|
||||
@@ -17,10 +18,7 @@ import timely.model.parse.TagParser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -39,6 +37,7 @@ public class MetricAdapter {
|
||||
|
||||
public static final ColumnVisibility EMPTY_VISIBILITY = new ColumnVisibility();
|
||||
public static final String VISIBILITY_TAG = "viz";
|
||||
public static final Joiner equalsJoiner = Joiner.on("=");
|
||||
|
||||
public static long roundTimestampToLastHour(long timestamp) {
|
||||
return timestamp - (timestamp % 3600000);
|
||||
@@ -64,7 +63,24 @@ public class MetricAdapter {
|
||||
return newTags;
|
||||
}
|
||||
|
||||
private static Map<String, String> escapeDelimiters(Map<String, String> tags) {
|
||||
Map<String, String> newTags = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, String> t : tags.entrySet()) {
|
||||
// escape all commas and equals in the tag key and value since they
|
||||
// are used as a delimiter
|
||||
String k = t.getKey();
|
||||
String v = t.getValue();
|
||||
k = k.replaceAll("=", "\\=");
|
||||
k = k.replaceAll(",", "\\,");
|
||||
v = v.replaceAll("=", "\\=");
|
||||
v = v.replaceAll(",", "\\,");
|
||||
newTags.put(k, v);
|
||||
}
|
||||
return newTags;
|
||||
}
|
||||
|
||||
public static Mutation toMutation(Metric metric) {
|
||||
|
||||
final Mutation mutation = new Mutation(encodeRowKey(metric));
|
||||
List<Tag> tags = metric.getTags();
|
||||
tags = escapeDelimiters(tags);
|
||||
@@ -88,6 +104,30 @@ public class MetricAdapter {
|
||||
return mutation;
|
||||
}
|
||||
|
||||
public static Key toKey(String metric, Map<String, String> tags, long timestamp) {
|
||||
byte[] row = encodeRowKey(metric, timestamp);
|
||||
|
||||
tags = escapeDelimiters(tags);
|
||||
StringBuilder colQualSb = new StringBuilder();
|
||||
String cf = null;
|
||||
for (Map.Entry<String, String> entry : tags.entrySet()) {
|
||||
if (entry.getKey().equals(VISIBILITY_TAG))
|
||||
continue;
|
||||
|
||||
if (cf == null) {
|
||||
cf = equalsJoiner.join(entry.getKey(), entry.getValue());
|
||||
continue;
|
||||
}
|
||||
if (colQualSb.length() > 0) {
|
||||
colQualSb.append(",");
|
||||
}
|
||||
colQualSb.append(equalsJoiner.join(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
byte[] cqBytes = encodeColQual(timestamp, colQualSb.toString());
|
||||
ColumnVisibility colVis = extractVisibility(tags);
|
||||
return new Key(new Text(row), new Text(cf), new Text(cqBytes), colVis, timestamp);
|
||||
}
|
||||
|
||||
private static Value extractValue(Metric metric) {
|
||||
return new Value(encodeValue(metric.getValue().getMeasure()));
|
||||
}
|
||||
@@ -104,7 +144,7 @@ public class MetricAdapter {
|
||||
return bb.getDouble();
|
||||
}
|
||||
|
||||
private static ColumnVisibility extractVisibility(List<Tag> tags) {
|
||||
public static ColumnVisibility extractVisibility(List<Tag> tags) {
|
||||
// @formatter:off
|
||||
Optional<Tag> visTag = tags.stream()
|
||||
.filter(t -> t.getKey().equals(VISIBILITY_TAG))
|
||||
@@ -113,6 +153,14 @@ public class MetricAdapter {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static ColumnVisibility extractVisibility(Map<String, String> tags) {
|
||||
if (tags.containsKey(VISIBILITY_TAG)) {
|
||||
return new ColumnVisibility(tags.get(VISIBILITY_TAG));
|
||||
} else {
|
||||
return EMPTY_VISIBILITY;
|
||||
}
|
||||
}
|
||||
|
||||
public static Metric parse(Key k, Value v, boolean includeVizTag) {
|
||||
ComparablePair<String, Long> row = rowCoder.decode(k.getRow().getBytes());
|
||||
// @formatter:off
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
package timely.api.request;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
|
||||
public class AuthenticatedWebSocketRequest extends AuthenticatedRequest implements WebSocketRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package timely.api.request;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.annotation.WebSocket;
|
||||
|
||||
@Http(path = "/api/cache")
|
||||
@WebSocket(operation = "cache")
|
||||
public class CacheRequest extends AuthenticatedRequest implements HttpGetRequest, HttpPostRequest, WebSocketRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@Override
|
||||
public HttpGetRequest parseQueryParameters(QueryStringDecoder decoder) throws Exception {
|
||||
return new CacheRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpPostRequest parseBody(String content) throws Exception {
|
||||
return new CacheRequest();
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
package timely.api.request;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
|
||||
public interface HttpGetRequest extends Request {
|
||||
public interface HttpGetRequest extends HttpRequest {
|
||||
|
||||
public HttpGetRequest parseQueryParameters(QueryStringDecoder decoder) throws Exception;
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest);
|
||||
|
||||
public FullHttpRequest getHttpRequest();
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package timely.api.request;
|
||||
|
||||
public interface HttpPostRequest extends Request {
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
|
||||
public interface HttpPostRequest extends HttpRequest {
|
||||
|
||||
public HttpPostRequest parseBody(String content) throws Exception;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package timely.api.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.api.annotation.Http;
|
||||
@@ -23,11 +24,14 @@ import java.nio.charset.StandardCharsets;
|
||||
public class MetricRequest implements TcpRequest, HttpPostRequest, WebSocketRequest, UdpRequest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MetricRequest.class);
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@XmlElement
|
||||
@JsonUnwrapped
|
||||
private Metric metric;
|
||||
|
||||
private String line;
|
||||
|
||||
private static final MetricParser metricParser = new MetricParser();
|
||||
|
||||
public MetricRequest() {
|
||||
@@ -55,7 +59,8 @@ public class MetricRequest implements TcpRequest, HttpPostRequest, WebSocketRequ
|
||||
public void parse(String line) {
|
||||
LOG.trace("Parsing Line: {}", line);
|
||||
try {
|
||||
metric = metricParser.parse(line);
|
||||
this.metric = metricParser.parse(line);
|
||||
this.line = line;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error parsing metric: {}", line);
|
||||
throw e;
|
||||
@@ -67,6 +72,10 @@ public class MetricRequest implements TcpRequest, HttpPostRequest, WebSocketRequ
|
||||
return metric.toString();
|
||||
}
|
||||
|
||||
public String getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
@@ -84,4 +93,12 @@ public class MetricRequest implements TcpRequest, HttpPostRequest, WebSocketRequ
|
||||
public int hashCode() {
|
||||
return metric != null ? metric.hashCode() : 0;
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.annotation.Tcp;
|
||||
@@ -10,6 +11,7 @@ import timely.api.annotation.WebSocket;
|
||||
@WebSocket(operation = "version")
|
||||
public class VersionRequest implements TcpRequest, HttpGetRequest, HttpPostRequest, WebSocketRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
public static final String VERSION;
|
||||
static {
|
||||
String ver = VersionRequest.class.getPackage().getImplementationVersion();
|
||||
@@ -40,4 +42,11 @@ public class VersionRequest implements TcpRequest, HttpGetRequest, HttpPostReque
|
||||
return new VersionRequest();
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.auth;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import timely.api.BasicAuthLogin;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.request.HttpPostRequest;
|
||||
@@ -8,6 +9,8 @@ import timely.util.JsonUtil;
|
||||
@Http(path = "/login")
|
||||
public class BasicAuthLoginRequest extends BasicAuthLogin implements HttpPostRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
HttpPostRequest.super.validate();
|
||||
@@ -26,4 +29,21 @@ public class BasicAuthLoginRequest extends BasicAuthLogin implements HttpPostReq
|
||||
return JsonUtil.getObjectMapper().readValue(content, BasicAuthLoginRequest.class);
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.auth;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.request.HttpGetRequest;
|
||||
@@ -7,9 +8,18 @@ import timely.api.request.HttpGetRequest;
|
||||
@Http(path = "/login")
|
||||
public class X509LoginRequest implements HttpGetRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@Override
|
||||
public HttpGetRequest parseQueryParameters(QueryStringDecoder decoder) throws Exception {
|
||||
return new X509LoginRequest();
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.timeseries;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.annotation.WebSocket;
|
||||
@@ -13,6 +14,8 @@ import timely.api.request.WebSocketRequest;
|
||||
public class AggregatorsRequest extends AuthenticatedRequest implements HttpGetRequest, HttpPostRequest,
|
||||
WebSocketRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
@@ -28,4 +31,11 @@ public class AggregatorsRequest extends AuthenticatedRequest implements HttpGetR
|
||||
return new AggregatorsRequest();
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package timely.api.request.timeseries;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import timely.api.request.Request;
|
||||
|
||||
public interface HttpRequest extends Request {
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest);
|
||||
|
||||
public FullHttpRequest getHttpRequest();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.timeseries;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import timely.api.annotation.Http;
|
||||
import timely.api.annotation.WebSocket;
|
||||
@@ -12,6 +13,8 @@ import timely.api.request.WebSocketRequest;
|
||||
@WebSocket(operation = "metrics")
|
||||
public class MetricsRequest extends AuthenticatedRequest implements HttpGetRequest, HttpPostRequest, WebSocketRequest {
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
@Override
|
||||
public HttpGetRequest parseQueryParameters(QueryStringDecoder decoder) throws Exception {
|
||||
return new MetricsRequest();
|
||||
@@ -22,4 +25,11 @@ public class MetricsRequest extends AuthenticatedRequest implements HttpGetReque
|
||||
return new MetricsRequest();
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.timeseries;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -366,6 +367,8 @@ public class QueryRequest extends AuthenticatedRequest implements HttpGetRequest
|
||||
private boolean showSummary = false;
|
||||
private boolean showQuery = false;
|
||||
private boolean delete = false;
|
||||
@JsonIgnore
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
public boolean isGlobalAnnotations() {
|
||||
return globalAnnotations;
|
||||
@@ -613,4 +616,12 @@ public class QueryRequest extends AuthenticatedRequest implements HttpGetRequest
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.timeseries;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -33,6 +34,8 @@ public class SearchLookupRequest extends AuthenticatedRequest implements HttpGet
|
||||
@JsonProperty("tags")
|
||||
private Collection<Tag> tags = new ArrayList<>();
|
||||
|
||||
private FullHttpRequest httpRequest = null;
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
@@ -142,4 +145,11 @@ public class SearchLookupRequest extends AuthenticatedRequest implements HttpGet
|
||||
return search;
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package timely.api.request.timeseries;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -24,6 +25,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
public class SuggestRequest extends AuthenticatedRequest implements HttpGetRequest, HttpPostRequest, WebSocketRequest {
|
||||
|
||||
private static List<String> validTypes = new ArrayList<>();
|
||||
private FullHttpRequest httpRequest = null;
|
||||
static {
|
||||
validTypes.add("metrics");
|
||||
validTypes.add("tagk");
|
||||
@@ -104,4 +106,11 @@ public class SuggestRequest extends AuthenticatedRequest implements HttpGetReque
|
||||
return suggest;
|
||||
}
|
||||
|
||||
public void setHttpRequest(FullHttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public FullHttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package timely.api.response;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CacheResponse {
|
||||
|
||||
private Long oldestTimestamp;
|
||||
private Long newestTimestamp;
|
||||
private List<String> metrics;
|
||||
|
||||
public Long getOldestTimestamp() {
|
||||
return oldestTimestamp;
|
||||
}
|
||||
|
||||
public void setOldestTimestamp(Long oldestTimestamp) {
|
||||
this.oldestTimestamp = oldestTimestamp;
|
||||
}
|
||||
|
||||
public Long getNewestTimestamp() {
|
||||
return newestTimestamp;
|
||||
}
|
||||
|
||||
public void setNewestTimestamp(Long newestTimestamp) {
|
||||
this.newestTimestamp = newestTimestamp;
|
||||
}
|
||||
|
||||
public List<String> getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public void setMetrics(List<String> metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder tsb = new ToStringBuilder(this);
|
||||
tsb.append("oldestTimestamp", this.oldestTimestamp);
|
||||
tsb.append("newestTimestamp", this.newestTimestamp);
|
||||
tsb.append("metrics", this.metrics);
|
||||
return tsb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,4 +69,4 @@ public class QueryResponse {
|
||||
return tsb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package timely.netty.http;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import timely.api.request.CacheRequest;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.netty.Constants;
|
||||
import timely.store.cache.DataStoreCache;
|
||||
import timely.util.JsonUtil;
|
||||
|
||||
public class HttpCacheRequestHandler extends SimpleChannelInboundHandler<CacheRequest> implements TimelyHttpHandler {
|
||||
|
||||
private DataStoreCache cache = null;
|
||||
|
||||
public HttpCacheRequestHandler(DataStoreCache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, CacheRequest v) throws Exception {
|
||||
byte[] buf;
|
||||
try {
|
||||
buf = JsonUtil.getObjectMapper().writeValueAsBytes(cache.getCacheStatus());
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
"Error getting cache status", "Error getting cache status", e));
|
||||
return;
|
||||
}
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
||||
Unpooled.copiedBuffer(buf));
|
||||
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
|
||||
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
|
||||
sendResponse(ctx, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.HttpGetRequest;
|
||||
import timely.api.request.HttpPostRequest;
|
||||
import timely.api.request.Request;
|
||||
import timely.api.request.timeseries.HttpRequest;
|
||||
import timely.api.response.StrictTransportResponse;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.auth.AuthCache;
|
||||
@@ -79,7 +80,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
final String sessionId = getSessionId(msg, this.anonymousAccessAllowed);
|
||||
LOG.trace("SessionID: " + sessionId);
|
||||
|
||||
Request request;
|
||||
HttpRequest request;
|
||||
try {
|
||||
if (msg.getMethod().equals(HttpMethod.GET)) {
|
||||
HttpGetRequest get = AnnotationResolver.getClassForHttpGet(decoder.path());
|
||||
@@ -103,6 +104,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
||||
((AuthenticatedRequest) request).setSessionId(sessionId);
|
||||
((AuthenticatedRequest) request).addHeaders(msg.headers().entries());
|
||||
}
|
||||
request.setHttpRequest(msg.copy());
|
||||
LOG.trace(LOG_PARSED_REQUEST, request);
|
||||
request.validate();
|
||||
out.add(request);
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package timely.netty.http.auth;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
public class TimelyPreAuthenticatedAuthenticationToken extends PreAuthenticatedAuthenticationToken {
|
||||
|
||||
private HttpHeaders httpHeaders = null;
|
||||
|
||||
public TimelyPreAuthenticatedAuthenticationToken(String subjectDN, X509Certificate clientCert,
|
||||
HttpHeaders httpHeaders) {
|
||||
super(subjectDN, clientCert);
|
||||
this.httpHeaders = httpHeaders;
|
||||
}
|
||||
|
||||
public HttpHeaders getHttpHeaders() {
|
||||
return httpHeaders;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package timely.netty.http.auth;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
@@ -26,7 +27,9 @@ public class X509LoginRequestHandler extends TimelyLoginRequestHandler<X509Login
|
||||
if (null != sslHandler) {
|
||||
X509Certificate clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0];
|
||||
String subjectDN = AuthenticationService.extractDN(clientCert);
|
||||
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(subjectDN, clientCert);
|
||||
HttpHeaders httpHeaders = loginRequest.getHttpRequest().headers();
|
||||
TimelyPreAuthenticatedAuthenticationToken token = new TimelyPreAuthenticatedAuthenticationToken(subjectDN,
|
||||
clientCert, httpHeaders);
|
||||
return AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||
} else {
|
||||
throw new IllegalStateException("The expected SSL handler is not in the pipeline.");
|
||||
|
||||
@@ -141,6 +141,10 @@ public class Aggregation implements Iterable<Sample>, Serializable {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
buckets.clear();
|
||||
}
|
||||
|
||||
public int getNumBuckets() {
|
||||
return buckets.size();
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ public class AggregationIterator extends WrappingIterator {
|
||||
return new Value(bos.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
aggregation.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
@@ -14,31 +18,40 @@ import org.apache.accumulo.core.iterators.IteratorEnvironment;
|
||||
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
|
||||
import org.apache.accumulo.core.iterators.WrappingIterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import timely.adapter.accumulo.MetricAdapter;
|
||||
import timely.api.request.timeseries.QueryRequest;
|
||||
import timely.model.Metric;
|
||||
import timely.model.ObjectSizeOf;
|
||||
import timely.model.Tag;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.sample.Aggregator;
|
||||
import timely.sample.Downsample;
|
||||
import timely.sample.DownsampleFactory;
|
||||
import timely.sample.aggregators.Avg;
|
||||
|
||||
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getTimeInMillis;
|
||||
|
||||
public class DownsampleIterator extends WrappingIterator {
|
||||
|
||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DownsampleIterator.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DownsampleIterator.class);
|
||||
private static final String START = "downsample.start";
|
||||
private static final String END = "downsample.end";
|
||||
private static final String PERIOD = "downsample.period";
|
||||
private static final String MAX_DOWNSAMPLE_MEMORY = "downsample.maxDownsampleMemory";
|
||||
private static final String AGGCLASS = "downsample.aggclass";
|
||||
|
||||
private static final long DEFAULT_DOWNSAMPLE_MS = 1;
|
||||
private static final String DEFAULT_DOWNSAMPLE_AGGREGATOR = Avg.class.getSimpleName().toLowerCase();
|
||||
|
||||
private DownsampleFactory factory;
|
||||
private final Map<Set<Tag>, Downsample> value = new HashMap<>();
|
||||
private long start;
|
||||
private long end;
|
||||
private long period;
|
||||
private Key last;
|
||||
|
||||
private DownsampleMemoryEstimator memoryEstimator = null;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -70,20 +83,11 @@ public class DownsampleIterator extends WrappingIterator {
|
||||
public boolean hasTop() {
|
||||
|
||||
if (super.hasTop()) {
|
||||
Key topKey = super.getTopKey();
|
||||
Value topValue = super.getTopValue();
|
||||
Metric metric = null;
|
||||
try {
|
||||
metric = MetricAdapter.parse(topKey, topValue);
|
||||
LOG.trace("entering hasTop() for metric=" + metric.toString());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error: {} parsing metric at key: {}", e.getMessage(), topKey.toString());
|
||||
}
|
||||
while (super.hasTop()) {
|
||||
topKey = super.getTopKey();
|
||||
topValue = super.getTopValue();
|
||||
Key topKey = super.getTopKey();
|
||||
Value topValue = super.getTopValue();
|
||||
try {
|
||||
metric = MetricAdapter.parse(topKey, topValue);
|
||||
Metric metric = MetricAdapter.parse(topKey, topValue);
|
||||
long timestamp = metric.getValue().getTimestamp();
|
||||
if (memoryEstimator.shouldReturnBasedOnMemoryUsage(timestamp, value)) {
|
||||
LOG.trace("returning current values - memory usage > " + memoryEstimator.maxDownsampleMemory
|
||||
@@ -111,7 +115,6 @@ public class DownsampleIterator extends WrappingIterator {
|
||||
throw new RuntimeException("Downstream next() failed", e);
|
||||
}
|
||||
}
|
||||
LOG.trace("exiting hasTop() for metric=" + metric.toString());
|
||||
return last != null;
|
||||
} else {
|
||||
return false;
|
||||
@@ -160,4 +163,30 @@ public class DownsampleIterator extends WrappingIterator {
|
||||
ObjectInputStream ois = new ObjectInputStream(bis);
|
||||
return (Map<Set<Tag>, Downsample>) ois.readObject();
|
||||
}
|
||||
|
||||
public static long getDownsamplePeriod(QueryRequest.SubQuery query) {
|
||||
// disabling the downsampling OR setting the aggregation to none are
|
||||
// both considered to be disabling
|
||||
if (!query.getDownsample().isPresent() || query.getDownsample().get().endsWith("-none")) {
|
||||
return DEFAULT_DOWNSAMPLE_MS;
|
||||
}
|
||||
String parts[] = query.getDownsample().get().split("-");
|
||||
return getTimeInMillis(parts[0]);
|
||||
}
|
||||
|
||||
public static Class<? extends Aggregator> getDownsampleAggregator(QueryRequest.SubQuery query) {
|
||||
String aggregatorName = Aggregator.NONE;
|
||||
if (query.getDownsample().isPresent()) {
|
||||
String parts[] = query.getDownsample().get().split("-");
|
||||
aggregatorName = parts[1];
|
||||
}
|
||||
// disabling the downsampling OR setting the aggregation to none are
|
||||
// both considered to be disabling
|
||||
if (aggregatorName.equals(Aggregator.NONE)) {
|
||||
// we need a downsampling iterator, so default to max to ensure we
|
||||
// return something
|
||||
aggregatorName = DEFAULT_DOWNSAMPLE_AGGREGATOR;
|
||||
}
|
||||
return Aggregator.getAggregator(aggregatorName);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,11 @@ import timely.api.response.TimelyException;
|
||||
import timely.api.response.timeseries.QueryResponse;
|
||||
import timely.api.response.timeseries.SearchLookupResponse;
|
||||
import timely.api.response.timeseries.SuggestResponse;
|
||||
|
||||
import timely.model.Tag;
|
||||
|
||||
import timely.store.cache.DataStoreCache;
|
||||
|
||||
public interface DataStore {
|
||||
|
||||
void store(Metric metric) throws TimelyException;
|
||||
@@ -39,4 +42,7 @@ public interface DataStore {
|
||||
Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime,
|
||||
long endTime, int lag, int scannerBatchSize, int scannerReadAhead) throws TimelyException;
|
||||
|
||||
void setCache(DataStoreCache cache);
|
||||
|
||||
DataStoreCache getCache();
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.accumulo.core.client.AccumuloException;
|
||||
import org.apache.accumulo.core.client.BatchScanner;
|
||||
import org.apache.accumulo.core.client.BatchWriter;
|
||||
import org.apache.accumulo.core.client.BatchWriterConfig;
|
||||
@@ -78,10 +79,10 @@ import timely.model.Tag;
|
||||
import timely.sample.Aggregation;
|
||||
import timely.sample.Aggregator;
|
||||
import timely.sample.Sample;
|
||||
import timely.sample.aggregators.Avg;
|
||||
import timely.sample.iterators.AggregationIterator;
|
||||
import timely.sample.iterators.DownsampleIterator;
|
||||
import timely.store.iterators.RateIterator;
|
||||
import timely.store.cache.DataStoreCache;
|
||||
import timely.util.MetaKeySet;
|
||||
|
||||
public class DataStoreImpl implements DataStore {
|
||||
@@ -89,8 +90,6 @@ public class DataStoreImpl implements DataStore {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DataStoreImpl.class);
|
||||
|
||||
private static final long METRICS_PERIOD = 30000;
|
||||
private static final long DEFAULT_DOWNSAMPLE_MS = 1;
|
||||
private static final String DEFAULT_DOWNSAMPLE_AGGREGATOR = Avg.class.getSimpleName().toLowerCase();
|
||||
private static final Pattern REGEX_TEST = Pattern.compile("^\\w+$");
|
||||
|
||||
/*
|
||||
@@ -119,8 +118,8 @@ public class DataStoreImpl implements DataStore {
|
||||
private final AtomicReference<SortedMap<MetricTagK, Integer>> metaCounts = new AtomicReference<>(new TreeMap<>());
|
||||
private final String metricsTable;
|
||||
private final String metaTable;
|
||||
private final InternalMetrics internalMetrics = new InternalMetrics();
|
||||
private final Timer internalMetricsTimer = new Timer(true);
|
||||
private final InternalMetrics internalMetrics;
|
||||
private final Timer internalMetricsTimer = new Timer("InternalMatricsTimer", true);
|
||||
private final int scannerThreads;
|
||||
private final long maxDownsampleMemory;
|
||||
private final BatchWriterConfig bwConfig;
|
||||
@@ -129,7 +128,8 @@ public class DataStoreImpl implements DataStore {
|
||||
private final ThreadLocal<BatchWriter> batchWriter = new ThreadLocal<>();
|
||||
private boolean anonAccessAllowed = false;
|
||||
private final Map<String, String> ageOff;
|
||||
private final long defaultAgeOff;
|
||||
private final long defaultAgeOffMilliSec;
|
||||
private DataStoreCache cache = null;
|
||||
|
||||
public DataStoreImpl(Configuration conf, int numWriteThreads) throws TimelyException {
|
||||
|
||||
@@ -164,7 +164,7 @@ public class DataStoreImpl implements DataStore {
|
||||
}
|
||||
}
|
||||
ageOff = getAgeOff(conf);
|
||||
defaultAgeOff = this.getAgeOffForMetric(MetricAgeOffIterator.DEFAULT_AGEOFF_KEY);
|
||||
defaultAgeOffMilliSec = this.getAgeOffForMetric(MetricAgeOffIterator.DEFAULT_AGEOFF_KEY);
|
||||
|
||||
final Map<String, String> tableIdMap = connector.tableOperations().tableIdMap();
|
||||
if (!tableIdMap.containsKey(metricsTable)) {
|
||||
@@ -175,8 +175,18 @@ public class DataStoreImpl implements DataStore {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
this.removeAgeOffIterators(connector, metricsTable);
|
||||
this.applyAgeOffIterator(connector, metricsTable, true);
|
||||
try {
|
||||
this.removeAgeOffIterators(connector, metricsTable);
|
||||
this.applyAgeOffIterator(connector, metricsTable, true);
|
||||
} catch (AccumuloException e1) {
|
||||
Throwable cause = e1.getCause();
|
||||
if (cause instanceof IllegalArgumentException
|
||||
&& cause.getMessage().startsWith("iterator name conflict")) {
|
||||
LOG.info("ignoring iterator name conflict due to multiple instances starting up");
|
||||
} else {
|
||||
throw e1;
|
||||
}
|
||||
}
|
||||
|
||||
metaTable = conf.getMetaTable();
|
||||
if (!tableIdMap.containsKey(metaTable)) {
|
||||
@@ -190,21 +200,33 @@ public class DataStoreImpl implements DataStore {
|
||||
this.removeAgeOffIterators(connector, metaTable);
|
||||
this.applyAgeOffIterator(connector, metaTable, false);
|
||||
|
||||
internalMetrics = new InternalMetrics(conf);
|
||||
internalMetricsTimer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
internalMetrics.getMetricsAndReset().forEach(m -> store(m));
|
||||
internalMetrics.getMetricsAndReset().forEach(m -> store(m, false));
|
||||
}
|
||||
|
||||
}, METRICS_PERIOD, METRICS_PERIOD);
|
||||
|
||||
this.metaCache = MetaCacheFactory.getCache(conf);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception e2) {
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error creating DataStoreImpl",
|
||||
e.getMessage(), e);
|
||||
e2.getMessage(), e2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCache(DataStoreCache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStoreCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static final EnumSet<IteratorScope> AGEOFF_SCOPES = EnumSet.allOf(IteratorScope.class);
|
||||
|
||||
private void removeAgeOffIterators(Connector con, String tableName) throws Exception {
|
||||
@@ -238,7 +260,16 @@ public class DataStoreImpl implements DataStore {
|
||||
|
||||
@Override
|
||||
public void store(Metric metric) {
|
||||
store(metric, true);
|
||||
}
|
||||
|
||||
public void store(Metric metric, boolean cacheEnabled) {
|
||||
LOG.trace("Received Store Request for: {}", metric);
|
||||
|
||||
if (cache != null && cacheEnabled) {
|
||||
cache.store(metric);
|
||||
}
|
||||
|
||||
if (null == metaWriter.get()) {
|
||||
try {
|
||||
BatchWriter w = connector.createBatchWriter(metaTable, bwConfig);
|
||||
@@ -462,10 +493,12 @@ public class DataStoreImpl implements DataStore {
|
||||
@Override
|
||||
public List<QueryResponse> query(QueryRequest msg) throws TimelyException {
|
||||
List<QueryResponse> result = new ArrayList<>();
|
||||
|
||||
LOG.debug("Query request {}", msg);
|
||||
long startTs = msg.getStart();
|
||||
long endTs = msg.getEnd();
|
||||
long requestedStartTs = msg.getStart();
|
||||
long requestedEndTs = msg.getEnd();
|
||||
StringBuilder metricList = new StringBuilder();
|
||||
|
||||
try {
|
||||
long numResults = 0;
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -476,79 +509,134 @@ public class DataStoreImpl implements DataStore {
|
||||
metricList.append(",");
|
||||
}
|
||||
metricList.append(metric);
|
||||
BatchScanner scanner = connector.createBatchScanner(metricsTable, getSessionAuthorizations(msg),
|
||||
scannerThreads);
|
||||
try {
|
||||
Map<Set<Tag>, List<Aggregation>> cachedMetrics = new HashMap<>();
|
||||
long oldestTimestampFromCache = Long.MAX_VALUE;
|
||||
|
||||
if (cache != null) {
|
||||
long oldestCacheTimestamp = cache.getOldestTimestamp(query.getMetric());
|
||||
if (requestedEndTs >= oldestCacheTimestamp) {
|
||||
cachedMetrics = cache.subquery(msg, query);
|
||||
|
||||
int z = 0;
|
||||
for (Collection<Aggregation> c : cachedMetrics.values()) {
|
||||
z += c.size();
|
||||
}
|
||||
long totalRequestedTime = requestedEndTs - requestedStartTs;
|
||||
long percentServedFromCache = 0;
|
||||
if (!cachedMetrics.isEmpty() && oldestCacheTimestamp != Long.MAX_VALUE) {
|
||||
if (requestedStartTs < oldestCacheTimestamp && requestedEndTs < oldestCacheTimestamp) {
|
||||
percentServedFromCache = 0;
|
||||
} else if (requestedStartTs >= oldestCacheTimestamp
|
||||
&& requestedEndTs >= oldestCacheTimestamp) {
|
||||
percentServedFromCache = 100;
|
||||
} else {
|
||||
long timeAnsweredFromCache = requestedEndTs - oldestCacheTimestamp;
|
||||
percentServedFromCache = Math.round((double) timeAnsweredFromCache
|
||||
/ (double) totalRequestedTime * 100);
|
||||
}
|
||||
if (percentServedFromCache > 100) {
|
||||
percentServedFromCache = 100;
|
||||
}
|
||||
LOG.debug(
|
||||
"Cache query time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}",
|
||||
(System.currentTimeMillis() - now),
|
||||
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), z,
|
||||
percentServedFromCache);
|
||||
allSeries.putAll(cachedMetrics);
|
||||
}
|
||||
}
|
||||
oldestTimestampFromCache = Math.max(oldestCacheTimestamp,
|
||||
System.currentTimeMillis() - cache.getAgeOffForMetric(query.getMetric()) + 1);
|
||||
}
|
||||
|
||||
if (cachedMetrics.isEmpty() || requestedStartTs < oldestTimestampFromCache) {
|
||||
// we have already searched from oldestTimestampFromCache to
|
||||
// requestedEndTs
|
||||
long endTs = (oldestTimestampFromCache == Long.MAX_VALUE) ? requestedEndTs
|
||||
: oldestTimestampFromCache - 1;
|
||||
|
||||
// Reset the start timestamp for the query to the
|
||||
// beginning of the downsample period based on the epoch
|
||||
long downsample = getDownsamplePeriod(query);
|
||||
long downsample = DownsampleIterator.getDownsamplePeriod(query);
|
||||
LOG.trace("Downsample period {}", downsample);
|
||||
long startOfFirstPeriod = startTs - (startTs % downsample);
|
||||
long startOfFirstPeriod = requestedStartTs - (requestedStartTs % downsample);
|
||||
long endDistanceFromDownSample = endTs % downsample;
|
||||
long endOfLastPeriod = (endDistanceFromDownSample > 0 ? endTs + downsample
|
||||
- endDistanceFromDownSample : endTs);
|
||||
|
||||
List<String> tagOrder = prioritizeTags(query.getMetric(), query.getTags());
|
||||
Map<String, String> orderedTags = orderTags(tagOrder, query.getTags());
|
||||
Set<Tag> colFamValues = getColumnFamilies(metric, orderedTags);
|
||||
List<Range> ranges = getQueryRanges(metric, startOfFirstPeriod, endOfLastPeriod, colFamValues);
|
||||
scanner.setRanges(ranges);
|
||||
setQueryColumns(scanner, metric, orderedTags, colFamValues);
|
||||
LOG.debug("startOfFirstPeriod:{} endOfLastPeriod:{}", startOfFirstPeriod, endOfLastPeriod);
|
||||
|
||||
if (query.isRate()) {
|
||||
LOG.trace("Adding rate iterator");
|
||||
IteratorSetting rate = new IteratorSetting(499, RateIterator.class);
|
||||
RateIterator.setRateOptions(rate, query.getRateOptions());
|
||||
scanner.addScanIterator(rate);
|
||||
}
|
||||
if (endOfLastPeriod > startOfFirstPeriod) {
|
||||
BatchScanner scanner = null;
|
||||
try {
|
||||
scanner = connector.createBatchScanner(metricsTable, getSessionAuthorizations(msg),
|
||||
scannerThreads);
|
||||
List<String> tagOrder = prioritizeTags(query.getMetric(), query.getTags());
|
||||
Map<String, String> orderedTags = orderTags(tagOrder, query.getTags());
|
||||
Set<Tag> colFamValues = getColumnFamilies(metric, orderedTags);
|
||||
List<Range> ranges = getQueryRanges(metric, startOfFirstPeriod, endOfLastPeriod,
|
||||
colFamValues);
|
||||
scanner.setRanges(ranges);
|
||||
setQueryColumns(scanner, metric, orderedTags, colFamValues);
|
||||
|
||||
Class<? extends Aggregator> daggClass = getDownsampleAggregator(query);
|
||||
if (daggClass == null) {
|
||||
// we should always have a downsample iterator in the
|
||||
// stack.
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
"Error during query: programming error", "daggClass == null");
|
||||
} else {
|
||||
LOG.trace("Downsample Aggregator type {}", daggClass.getSimpleName());
|
||||
IteratorSetting is = new IteratorSetting(500, DownsampleIterator.class);
|
||||
DownsampleIterator.setDownsampleOptions(is, startOfFirstPeriod, endOfLastPeriod, downsample,
|
||||
maxDownsampleMemory, daggClass.getName());
|
||||
scanner.addScanIterator(is);
|
||||
}
|
||||
if (query.isRate()) {
|
||||
LOG.trace("Adding rate iterator");
|
||||
IteratorSetting rate = new IteratorSetting(499, RateIterator.class);
|
||||
RateIterator.setRateOptions(rate, query.getRateOptions());
|
||||
scanner.addScanIterator(rate);
|
||||
}
|
||||
|
||||
Class<? extends Aggregator> aggClass = getAggregator(query);
|
||||
// the aggregation iterator is optional
|
||||
if (aggClass != null) {
|
||||
LOG.trace("Aggregator type {}", aggClass.getSimpleName());
|
||||
IteratorSetting is = new IteratorSetting(501, AggregationIterator.class);
|
||||
AggregationIterator.setAggregationOptions(is, query.getTags(), aggClass.getName());
|
||||
scanner.addScanIterator(is);
|
||||
}
|
||||
Class<? extends Aggregator> daggClass = DownsampleIterator.getDownsampleAggregator(query);
|
||||
if (daggClass == null) {
|
||||
// we should always have a downsample iterator
|
||||
// in the stack.
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
"Error during query: programming error", "daggClass == null");
|
||||
} else {
|
||||
LOG.trace("Downsample Aggregator type {}", daggClass.getSimpleName());
|
||||
IteratorSetting is = new IteratorSetting(500, DownsampleIterator.class);
|
||||
DownsampleIterator.setDownsampleOptions(is, startOfFirstPeriod, endOfLastPeriod,
|
||||
downsample, maxDownsampleMemory, daggClass.getName());
|
||||
scanner.addScanIterator(is);
|
||||
}
|
||||
|
||||
// tag -> array of results by period starting at start
|
||||
for (Entry<Key, Value> encoded : scanner) {
|
||||
// we can decode the value as a Map<Set<Tag>,
|
||||
// Aggregation> even if an AggregationIterator
|
||||
// is not used because Downsample is a subclass of
|
||||
// Aggregation
|
||||
Map<Set<Tag>, Aggregation> samples = AggregationIterator.decodeValue(encoded.getValue());
|
||||
for (Entry<Set<Tag>, Aggregation> entry : samples.entrySet()) {
|
||||
numResults++;
|
||||
Set<Tag> key = new HashSet<>();
|
||||
for (Tag tag : entry.getKey()) {
|
||||
if (query.getTags().keySet().contains(tag.getKey())) {
|
||||
key.add(tag);
|
||||
Class<? extends Aggregator> aggClass = getAggregator(query);
|
||||
// the aggregation iterator is optional
|
||||
if (aggClass != null) {
|
||||
LOG.trace("Aggregator type {}", aggClass.getSimpleName());
|
||||
IteratorSetting is = new IteratorSetting(501, AggregationIterator.class);
|
||||
AggregationIterator.setAggregationOptions(is, query.getTags(), aggClass.getName());
|
||||
scanner.addScanIterator(is);
|
||||
}
|
||||
|
||||
// tag -> array of results by period starting at
|
||||
// start
|
||||
for (Entry<Key, Value> encoded : scanner) {
|
||||
// we can decode the value as a Map<Set<Tag>,
|
||||
// Aggregation> even if an AggregationIterator
|
||||
// is not used because Downsample is a subclass
|
||||
// of Aggregation
|
||||
Map<Set<Tag>, Aggregation> samples = AggregationIterator
|
||||
.decodeValue(encoded.getValue());
|
||||
for (Entry<Set<Tag>, Aggregation> entry : samples.entrySet()) {
|
||||
Set<Tag> key = new HashSet<>();
|
||||
for (Tag tag : entry.getKey()) {
|
||||
if (query.getTags().keySet().contains(tag.getKey())) {
|
||||
key.add(tag);
|
||||
}
|
||||
}
|
||||
List<Aggregation> aggregations = allSeries.getOrDefault(key, new ArrayList<>());
|
||||
aggregations.add(entry.getValue());
|
||||
allSeries.put(key, aggregations);
|
||||
}
|
||||
}
|
||||
List<Aggregation> aggregations = allSeries.getOrDefault(key, new ArrayList<>());
|
||||
aggregations.add(entry.getValue());
|
||||
allSeries.put(key, aggregations);
|
||||
LOG.trace("allSeries: {}", allSeries);
|
||||
} finally {
|
||||
if (scanner != null) {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.trace("allSeries: {}", allSeries);
|
||||
} finally {
|
||||
scanner.close();
|
||||
}
|
||||
|
||||
// TODO groupby here?
|
||||
@@ -558,8 +646,8 @@ public class DataStoreImpl implements DataStore {
|
||||
result.add(convertToQueryResponse(query, entry.getKey(), entry.getValue(), tsDivisor));
|
||||
}
|
||||
}
|
||||
LOG.debug("Query time: {} duration: {} metrics: {} results: {}", (System.currentTimeMillis() - now),
|
||||
(endTs - startTs), metricList.toString(), numResults);
|
||||
LOG.debug("Query time:{} duration:{} metrics:{} results:{}", (System.currentTimeMillis() - now),
|
||||
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), numResults);
|
||||
internalMetrics.addQueryResponse(result.size(), (System.currentTimeMillis() - now));
|
||||
return result;
|
||||
} catch (ClassNotFoundException | IOException | TableNotFoundException ex) {
|
||||
@@ -584,7 +672,6 @@ public class DataStoreImpl implements DataStore {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param query
|
||||
* @return ordered list of most specific to least specific tags in the query
|
||||
*/
|
||||
private List<String> prioritizeTags(String metric, Map<String, String> tags) {
|
||||
@@ -843,32 +930,6 @@ public class DataStoreImpl implements DataStore {
|
||||
return Aggregator.getAggregator(query.getAggregator());
|
||||
}
|
||||
|
||||
private Class<? extends Aggregator> getDownsampleAggregator(SubQuery query) {
|
||||
String aggregatorName = Aggregator.NONE;
|
||||
if (query.getDownsample().isPresent()) {
|
||||
String parts[] = query.getDownsample().get().split("-");
|
||||
aggregatorName = parts[1];
|
||||
}
|
||||
// disabling the downsampling OR setting the aggregation to none are
|
||||
// both considered to be disabling
|
||||
if (aggregatorName.equals(Aggregator.NONE)) {
|
||||
// we need a downsampling iterator, so default to max to ensure we
|
||||
// return something
|
||||
aggregatorName = DEFAULT_DOWNSAMPLE_AGGREGATOR;
|
||||
}
|
||||
return Aggregator.getAggregator(aggregatorName);
|
||||
}
|
||||
|
||||
private long getDownsamplePeriod(SubQuery query) {
|
||||
// disabling the downsampling OR setting the aggregation to none are
|
||||
// both considered to be disabling
|
||||
if (!query.getDownsample().isPresent() || query.getDownsample().get().endsWith("-none")) {
|
||||
return DEFAULT_DOWNSAMPLE_MS;
|
||||
}
|
||||
String parts[] = query.getDownsample().get().split("-");
|
||||
return getTimeInMillis(parts[0]);
|
||||
}
|
||||
|
||||
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) {
|
||||
return getSessionAuthorizations(request.getSessionId());
|
||||
}
|
||||
@@ -900,7 +961,7 @@ public class DataStoreImpl implements DataStore {
|
||||
public long getAgeOffForMetric(String metricName) {
|
||||
String age = this.ageOff.get(MetricAgeOffIterator.AGE_OFF_PREFIX + metricName);
|
||||
if (null == age) {
|
||||
return this.defaultAgeOff;
|
||||
return this.defaultAgeOffMilliSec;
|
||||
} else {
|
||||
return Long.parseLong(age);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import timely.Configuration;
|
||||
import timely.model.Metric;
|
||||
import timely.model.Tag;
|
||||
|
||||
@@ -37,14 +38,19 @@ public class InternalMetrics {
|
||||
|
||||
private List<Tag> tags = new ArrayList<Tag>();
|
||||
|
||||
public InternalMetrics() {
|
||||
public InternalMetrics(Configuration conf) {
|
||||
super();
|
||||
try {
|
||||
hostName = InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
LOG.error("Error getting hostname", e);
|
||||
}
|
||||
tags.add(new Tag(HOSTNAME_TAG, hostName));
|
||||
String instance = conf.getInstance();
|
||||
if (instance == null) {
|
||||
tags.add(new Tag(HOSTNAME_TAG, hostName));
|
||||
} else {
|
||||
tags.add(new Tag(HOSTNAME_TAG, hostName + "_" + instance));
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementMetricsReceived(long num) {
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
package timely.store.cache;
|
||||
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.apache.accumulo.core.client.IteratorSetting;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
|
||||
import org.apache.accumulo.core.security.Authorizations;
|
||||
import org.apache.commons.collections.map.LRUMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.Configuration;
|
||||
import timely.api.request.AuthenticatedRequest;
|
||||
import timely.api.request.timeseries.QueryRequest;
|
||||
import timely.api.response.CacheResponse;
|
||||
import timely.api.response.TimelyException;
|
||||
import timely.api.response.timeseries.QueryResponse;
|
||||
import timely.auth.AuthCache;
|
||||
import timely.model.Metric;
|
||||
import timely.model.Tag;
|
||||
import timely.sample.Aggregation;
|
||||
import timely.sample.Aggregator;
|
||||
import timely.sample.Sample;
|
||||
import timely.sample.iterators.AggregationIterator;
|
||||
import timely.sample.iterators.DownsampleIterator;
|
||||
import timely.store.MetricAgeOffIterator;
|
||||
import timely.store.iterators.RateIterator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
public class DataStoreCache {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DataStoreCache.class);
|
||||
public static final String DEFAULT_AGEOFF_KEY = "default";
|
||||
|
||||
private Map<String, Map<TaggedMetric, GorillaStore>> gorillaMap = new HashMap<>();
|
||||
private StampedLock gorillaMapLock = new StampedLock();
|
||||
|
||||
private Set<String> nonCachedMetrics = Collections.synchronizedSet(new HashSet<>());
|
||||
private long maxUniqueTagSets;
|
||||
private boolean anonAccessAllowed;
|
||||
private Map<String, Long> minimumAgeOff;
|
||||
private Map<String, String> minimumAgeOffForIterator;
|
||||
private int flushBatch = 0;
|
||||
private int numBatches = 5;
|
||||
|
||||
private Timer maintenanceTimer = new Timer("DataStoreCacheTimer");
|
||||
private Timer statsTimer = new Timer();
|
||||
|
||||
public DataStoreCache(Configuration conf) {
|
||||
nonCachedMetrics.addAll(conf.getCache().getNonCachedMetrics());
|
||||
maxUniqueTagSets = conf.getCache().getMaxUniqueTagSets();
|
||||
anonAccessAllowed = conf.getSecurity().isAllowAnonymousAccess();
|
||||
Map<String, Integer> cacheAgeOff = conf.getCache().getMetricAgeOffHours();
|
||||
Map<String, Integer> accumuloAgeOff = conf.getMetricAgeOffDays();
|
||||
LOG.info("cacheAgeOff:{} accumuloAgeOff:{}", cacheAgeOff, accumuloAgeOff);
|
||||
minimumAgeOff = getMinimumAgeOffs(accumuloAgeOff, cacheAgeOff);
|
||||
|
||||
for (Map.Entry<String, Long> e : minimumAgeOff.entrySet()) {
|
||||
LOG.info("minimumAgeOff key:{} value:{}", e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
minimumAgeOffForIterator = getAgeOffForIterator(minimumAgeOff);
|
||||
|
||||
maintenanceTimer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
flushCaches(flushBatch);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}, conf.getCache().getFlushInterval(), conf.getCache().getFlushInterval());
|
||||
|
||||
maintenanceTimer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ageOffGorillaStores();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}, (600 * 1000), (600 * 1000));
|
||||
|
||||
maintenanceTimer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
archiveGorillaStoreCurrentCompressors();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}, (7200 * 1000), (3600 * 100));
|
||||
|
||||
statsTimer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
pruneStats();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}, (600 * 1000), (600 * 100));
|
||||
}
|
||||
|
||||
private void pruneStats() {
|
||||
long stamp = gorillaMapLock.writeLock();
|
||||
try {
|
||||
Iterator<Map.Entry<String, Map<TaggedMetric, GorillaStore>>> metricItr = gorillaMap.entrySet().iterator();
|
||||
|
||||
while (metricItr.hasNext()) {
|
||||
Map.Entry<String, Map<TaggedMetric, GorillaStore>> entry1 = metricItr.next();
|
||||
int numberTagVariations = entry1.getValue().size();
|
||||
if (numberTagVariations > maxUniqueTagSets) {
|
||||
LOG.info("Cache of metric {} has {] tag variations. Discontinuing cache.", entry1.getKey(),
|
||||
numberTagVariations);
|
||||
metricItr.remove();
|
||||
nonCachedMetrics.add(entry1.getKey());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
private void ageOffGorillaStores() {
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
long numRemoved = 0;
|
||||
for (Map.Entry<String, Map<TaggedMetric, GorillaStore>> entry1 : gorillaMap.entrySet()) {
|
||||
long maxAge = getAgeOffForMetric(entry1.getKey());
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("ageOffGorillaStores metric:{} maxAge:{}", entry1.getKey(), maxAge);
|
||||
}
|
||||
for (GorillaStore store : entry1.getValue().values()) {
|
||||
numRemoved += store.ageOffArchivedCompressors(maxAge);
|
||||
}
|
||||
}
|
||||
if (numRemoved > 0) {
|
||||
LOG.debug("ageOffGorillaStores - Aged off {} archived Gorilla compressors", numRemoved);
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
protected void flushCaches(int flushBatch) {
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
int x = 0;
|
||||
for (Map.Entry<String, Map<TaggedMetric, GorillaStore>> entry : gorillaMap.entrySet()) {
|
||||
if (flushBatch == -1 || x % numBatches == flushBatch) {
|
||||
for (GorillaStore store : entry.getValue().values()) {
|
||||
try {
|
||||
store.flush();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
x++;
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
flushBatch++;
|
||||
if (flushBatch == numBatches) {
|
||||
flushBatch = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void archiveGorillaStoreCurrentCompressors() {
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
for (Map.Entry<String, Map<TaggedMetric, GorillaStore>> entry : gorillaMap.entrySet()) {
|
||||
for (GorillaStore store : entry.getValue().values()) {
|
||||
store.archiveCurrentCompressor();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
public long getAgeOffForMetric(String metricName) {
|
||||
if (this.minimumAgeOff.containsKey(metricName)) {
|
||||
return this.minimumAgeOff.get(metricName);
|
||||
} else {
|
||||
return this.minimumAgeOff.get(DEFAULT_AGEOFF_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultAgeOffMilliSec(long defaultAgeOffMilliSec) {
|
||||
this.minimumAgeOff.put(DEFAULT_AGEOFF_KEY, defaultAgeOffMilliSec);
|
||||
this.minimumAgeOffForIterator.put(
|
||||
MetricAgeOffIterator.AGE_OFF_PREFIX + MetricAgeOffIterator.DEFAULT_AGEOFF_KEY,
|
||||
Long.toString(defaultAgeOffMilliSec));
|
||||
}
|
||||
|
||||
private Map<String, Long> getMinimumAgeOffs(Map<String, Integer> accumuloAgeOffDays,
|
||||
Map<String, Integer> cacheAgeOffHours) {
|
||||
Map<String, Long> minimumAgeOffs = new HashMap<>();
|
||||
Set<String> keys = new HashSet<>();
|
||||
keys.addAll(accumuloAgeOffDays.keySet());
|
||||
keys.addAll(cacheAgeOffHours.keySet());
|
||||
for (String name : keys) {
|
||||
Long accumuloAgeOffValue = (accumuloAgeOffDays.containsKey(name)) ? accumuloAgeOffDays.get(name) * 86400000L
|
||||
: Long.MAX_VALUE;
|
||||
Long cacheAgeOffValue = (cacheAgeOffHours.containsKey(name)) ? cacheAgeOffHours.get(name) * 3600000L
|
||||
: Long.MAX_VALUE;
|
||||
minimumAgeOffs.put(name, Math.min(accumuloAgeOffValue, cacheAgeOffValue));
|
||||
}
|
||||
return minimumAgeOffs;
|
||||
}
|
||||
|
||||
private Map<String, String> getAgeOffForIterator(Map<String, Long> minimumAgeOff) {
|
||||
Map<String, String> ageOffOptions = new HashMap<>();
|
||||
minimumAgeOff.forEach((k, v) -> {
|
||||
String ageoff = Long.toString(v);
|
||||
LOG.trace("Adding age off for metric: {} of {} milliseconds", k, v);
|
||||
ageOffOptions.put(MetricAgeOffIterator.AGE_OFF_PREFIX + k, ageoff);
|
||||
});
|
||||
ageOffOptions
|
||||
.put(MetricAgeOffIterator.DEFAULT_AGEOFF_KEY, Long.toString(minimumAgeOff.get(DEFAULT_AGEOFF_KEY)));
|
||||
return ageOffOptions;
|
||||
}
|
||||
|
||||
public Map<TaggedMetric, GorillaStore> getGorillaStores(String metric) {
|
||||
Map<TaggedMetric, GorillaStore> returnedMap = new HashMap<>();
|
||||
Map<TaggedMetric, GorillaStore> metricMap;
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
metricMap = gorillaMap.get(metric);
|
||||
if (metricMap == null) {
|
||||
metricMap = new HashMap<>();
|
||||
long writeStamp = gorillaMapLock.tryConvertToWriteLock(stamp);
|
||||
if (writeStamp == 0) {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
stamp = gorillaMapLock.writeLock();
|
||||
} else {
|
||||
stamp = writeStamp;
|
||||
}
|
||||
gorillaMap.put(metric, metricMap);
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlock(stamp);
|
||||
}
|
||||
returnedMap.putAll(metricMap);
|
||||
return returnedMap;
|
||||
}
|
||||
|
||||
public GorillaStore getGorillaStore(String metric, TaggedMetric taggedMetric) {
|
||||
|
||||
Map<TaggedMetric, GorillaStore> metricMap;
|
||||
GorillaStore gStore;
|
||||
boolean needWrite = false;
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
metricMap = gorillaMap.get(metric);
|
||||
if (metricMap == null) {
|
||||
metricMap = new HashMap<>();
|
||||
needWrite = true;
|
||||
}
|
||||
gStore = metricMap.get(taggedMetric);
|
||||
if (gStore == null) {
|
||||
gStore = new GorillaStore();
|
||||
needWrite = true;
|
||||
}
|
||||
if (needWrite) {
|
||||
long writeStamp = gorillaMapLock.tryConvertToWriteLock(stamp);
|
||||
if (writeStamp == 0) {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
stamp = gorillaMapLock.writeLock();
|
||||
} else {
|
||||
stamp = writeStamp;
|
||||
}
|
||||
metricMap.put(taggedMetric, gStore);
|
||||
gorillaMap.put(metric, metricMap);
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlock(stamp);
|
||||
}
|
||||
return gStore;
|
||||
}
|
||||
|
||||
private boolean shouldCache(Metric metric) {
|
||||
|
||||
String metricName = metric.getName();
|
||||
if (nonCachedMetrics.contains(metricName)) {
|
||||
return false;
|
||||
}
|
||||
synchronized (nonCachedMetrics) {
|
||||
for (String r : nonCachedMetrics) {
|
||||
if (metricName.matches(r)) {
|
||||
LOG.info("Adding {} to list of non-cached metrics", metricName);
|
||||
nonCachedMetrics.add(metricName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void store(Metric metric) {
|
||||
if (shouldCache(metric)) {
|
||||
TaggedMetric taggedMetric = new TaggedMetric(metric.getName(), metric.getTags());
|
||||
GorillaStore gs = getGorillaStore(metric.getName(), taggedMetric);
|
||||
gs.addValue(metric);
|
||||
}
|
||||
}
|
||||
|
||||
public List<QueryResponse> query(QueryRequest msg) throws TimelyException {
|
||||
|
||||
List<QueryResponse> result = new ArrayList<>();
|
||||
try {
|
||||
Collection<QueryRequest.SubQuery> subQueries = msg.getQueries();
|
||||
for (QueryRequest.SubQuery query : subQueries) {
|
||||
Map<Set<Tag>, List<Aggregation>> aggregations = subquery(msg, query);
|
||||
for (Map.Entry<Set<Tag>, List<Aggregation>> entry : aggregations.entrySet()) {
|
||||
long tsDivisor = msg.isMsResolution() ? 1 : 1000;
|
||||
result.add(convertToQueryResponse(query, entry.getKey(), entry.getValue(), tsDivisor));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error during query: " + e.getMessage(), e);
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error during query: "
|
||||
+ e.getMessage(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Set<Tag>, List<Aggregation>> subquery(QueryRequest msg, QueryRequest.SubQuery query)
|
||||
throws TimelyException {
|
||||
|
||||
Map<Set<Tag>, List<Aggregation>> aggregationList = new HashMap<>();
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
SortedKeyValueIterator<org.apache.accumulo.core.data.Key, org.apache.accumulo.core.data.Value> itr = null;
|
||||
itr = setupIterator(msg, query, getSessionAuthorizations(msg), getAgeOffForMetric(query.getMetric()));
|
||||
Map<Set<Tag>, Set<Tag>> matchingTagCache = new HashMap<>();
|
||||
while (itr.hasTop()) {
|
||||
Map<Set<Tag>, Aggregation> samples = AggregationIterator.decodeValue(itr.getTopValue());
|
||||
for (Map.Entry<Set<Tag>, Aggregation> entry : samples.entrySet()) {
|
||||
Set<Tag> allMatchingTags = (Set<Tag>) matchingTagCache.get(entry.getKey());
|
||||
if (allMatchingTags == null) {
|
||||
allMatchingTags = new HashSet<>();
|
||||
for (Tag tag : entry.getKey()) {
|
||||
if (query.getTags().keySet().contains(tag.getKey())) {
|
||||
allMatchingTags.add(tag);
|
||||
}
|
||||
}
|
||||
matchingTagCache.put(entry.getKey(), allMatchingTags);
|
||||
}
|
||||
List<Aggregation> aggregations = aggregationList.getOrDefault(allMatchingTags, new ArrayList<>());
|
||||
aggregations.add(entry.getValue());
|
||||
aggregationList.put(allMatchingTags, aggregations);
|
||||
}
|
||||
itr.next();
|
||||
}
|
||||
return aggregationList;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error during query: " + e.getMessage(), e);
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error during query: "
|
||||
+ e.getMessage(), e.getMessage(), e);
|
||||
} finally {
|
||||
LOG.trace("Time for cache subquery for {} - {}ms", query.toString(), System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
|
||||
protected SortedKeyValueIterator<Key, Value> setupIterator(QueryRequest query, QueryRequest.SubQuery subQuery,
|
||||
Authorizations authorizations, long ageOffForMetric) throws TimelyException {
|
||||
|
||||
SortedKeyValueIterator<org.apache.accumulo.core.data.Key, org.apache.accumulo.core.data.Value> itr = null;
|
||||
|
||||
long downsamplePeriod = DownsampleIterator.getDownsamplePeriod(subQuery);
|
||||
long startTs = query.getStart();
|
||||
long endTs = query.getEnd();
|
||||
long ageOffTs = System.currentTimeMillis() - ageOffForMetric;
|
||||
if (startTs <= ageOffTs) {
|
||||
startTs = ageOffTs + 1;
|
||||
}
|
||||
|
||||
long startOfFirstPeriod = startTs - (startTs % downsamplePeriod);
|
||||
long endDistanceFromDownSample = endTs % downsamplePeriod;
|
||||
long endOfLastPeriod = (endDistanceFromDownSample > 0 ? endTs + downsamplePeriod - endDistanceFromDownSample
|
||||
: endTs);
|
||||
|
||||
try {
|
||||
// create DataStoreCacheIterator which is the base iterator of
|
||||
// the stack
|
||||
VisibilityFilter visFilter = new VisibilityFilter(authorizations);
|
||||
|
||||
itr = new DataStoreCacheIterator(this, visFilter, subQuery, startOfFirstPeriod, endOfLastPeriod);
|
||||
|
||||
// IteratorSetting ageOffIteratorSettings = new IteratorSetting(100,
|
||||
// "ageoff", MetricAgeOffIterator.class,
|
||||
// this.minimumAgeOffForIterator);
|
||||
// MetricAgeOffIterator ageOff = new MetricAgeOffIterator();
|
||||
// ageOff.init(itr, ageOffIteratorSettings.getOptions(), null);
|
||||
// itr = ageOff;
|
||||
|
||||
// create RateIterator if necessary
|
||||
if (subQuery.isRate()) {
|
||||
LOG.trace("Adding rate iterator");
|
||||
IteratorSetting rate = new IteratorSetting(499, RateIterator.class);
|
||||
RateIterator.setRateOptions(rate, subQuery.getRateOptions());
|
||||
RateIterator rateIterator = new RateIterator();
|
||||
rateIterator.init(itr, rate.getOptions(), null);
|
||||
itr = rateIterator;
|
||||
}
|
||||
|
||||
// create DownsampleIterator - we should always have a downsample
|
||||
// iterator
|
||||
// in the stack even if only using the default downsample settings
|
||||
Class<? extends Aggregator> daggClass = DownsampleIterator.getDownsampleAggregator(subQuery);
|
||||
if (daggClass == null) {
|
||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
||||
"Error during query: programming error", "daggClass == null");
|
||||
} else {
|
||||
LOG.trace("Downsample Aggregator type {}", daggClass.getSimpleName());
|
||||
IteratorSetting downsample = new IteratorSetting(500, DownsampleIterator.class);
|
||||
DownsampleIterator.setDownsampleOptions(downsample, startOfFirstPeriod, endOfLastPeriod,
|
||||
DownsampleIterator.getDownsamplePeriod(subQuery), -1, daggClass.getName());
|
||||
DownsampleIterator downsampleIterator = new DownsampleIterator();
|
||||
downsampleIterator.init(itr, downsample.getOptions(), null);
|
||||
itr = downsampleIterator;
|
||||
}
|
||||
|
||||
// create AggregatingIterator if necessary
|
||||
Class<? extends Aggregator> aggClass = Aggregator.getAggregator(subQuery.getAggregator());
|
||||
// the aggregation iterator is optional
|
||||
if (aggClass != null) {
|
||||
LOG.trace("Aggregator type {}", aggClass.getSimpleName());
|
||||
IteratorSetting aggregation = new IteratorSetting(501, AggregationIterator.class);
|
||||
AggregationIterator.setAggregationOptions(aggregation, subQuery.getTags(), aggClass.getName());
|
||||
AggregationIterator aggregationIterator = new AggregationIterator();
|
||||
aggregationIterator.init(itr, aggregation.getOptions(), null);
|
||||
itr = aggregationIterator;
|
||||
}
|
||||
|
||||
itr.seek(new Range(subQuery.getMetric()), null, true);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) {
|
||||
return getSessionAuthorizations(request.getSessionId());
|
||||
}
|
||||
|
||||
private Authorizations getSessionAuthorizations(String sessionId) {
|
||||
if (anonAccessAllowed) {
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
return Authorizations.EMPTY;
|
||||
} else {
|
||||
Authorizations auths = AuthCache.getAuthorizations(sessionId);
|
||||
if (null == auths) {
|
||||
auths = Authorizations.EMPTY;
|
||||
}
|
||||
return auths;
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
throw new IllegalArgumentException("session id cannot be null");
|
||||
} else {
|
||||
Authorizations auths = AuthCache.getAuthorizations(sessionId);
|
||||
if (null == auths) {
|
||||
throw new IllegalStateException("No auths found for sessionId: " + sessionId);
|
||||
}
|
||||
return auths;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private QueryResponse convertToQueryResponse(QueryRequest.SubQuery query, Set<Tag> tags,
|
||||
Collection<Aggregation> values, long tsDivisor) {
|
||||
QueryResponse response = new QueryResponse();
|
||||
response.setMetric(query.getMetric());
|
||||
for (Tag tag : tags) {
|
||||
response.putTag(tag.getKey(), tag.getValue());
|
||||
}
|
||||
QueryRequest.RateOption rateOptions = query.getRateOptions();
|
||||
Aggregation combined = Aggregation.combineAggregation(values, rateOptions);
|
||||
for (Sample entry : combined) {
|
||||
long ts = entry.timestamp / tsDivisor;
|
||||
response.putDps(Long.toString(ts), entry.value);
|
||||
}
|
||||
LOG.trace("Created query response {}", response);
|
||||
return response;
|
||||
}
|
||||
|
||||
public long getNewestTimestamp(String metric) {
|
||||
|
||||
long newest = 0;
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
Map<TaggedMetric, GorillaStore> gorillaStoreMap = gorillaMap.get(metric);
|
||||
for (Map.Entry<TaggedMetric, GorillaStore> entry : gorillaStoreMap.entrySet()) {
|
||||
if (entry.getValue().getNewestTimestamp() > newest) {
|
||||
newest = entry.getValue().getNewestTimestamp();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
|
||||
public long getOldestTimestamp(String metric) {
|
||||
long oldest = Long.MAX_VALUE;
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
Map<TaggedMetric, GorillaStore> gorillaStoreMap = gorillaMap.get(metric);
|
||||
if (gorillaStoreMap != null) {
|
||||
for (Map.Entry<TaggedMetric, GorillaStore> entry : gorillaStoreMap.entrySet()) {
|
||||
if (entry.getValue().getOldestTimestamp() < oldest) {
|
||||
oldest = entry.getValue().getOldestTimestamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
return oldest;
|
||||
}
|
||||
|
||||
public long getNewestTimestamp() {
|
||||
|
||||
long newest = 0;
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
for (String metric : gorillaMap.keySet()) {
|
||||
Long newestForMetric = getNewestTimestamp(metric);
|
||||
if (newestForMetric > newest) {
|
||||
newest = newestForMetric;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
|
||||
public long getOldestTimestamp() {
|
||||
long oldest = Long.MAX_VALUE;
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
for (String metric : gorillaMap.keySet()) {
|
||||
Long oldestForMetric = getOldestTimestamp(metric);
|
||||
if (oldestForMetric < oldest) {
|
||||
oldest = oldestForMetric;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
return oldest;
|
||||
}
|
||||
|
||||
public CacheResponse getCacheStatus() {
|
||||
CacheResponse response = new CacheResponse();
|
||||
response.setOldestTimestamp(getOldestTimestamp());
|
||||
response.setNewestTimestamp(getNewestTimestamp());
|
||||
long stamp = gorillaMapLock.readLock();
|
||||
try {
|
||||
response.setMetrics(new ArrayList<>(gorillaMap.keySet()));
|
||||
} finally {
|
||||
gorillaMapLock.unlockRead(stamp);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package timely.store.cache;
|
||||
|
||||
import org.apache.accumulo.core.data.ByteSequence;
|
||||
import org.apache.accumulo.core.data.Key;
|
||||
import org.apache.accumulo.core.data.KeyValue;
|
||||
import org.apache.accumulo.core.data.Range;
|
||||
import org.apache.accumulo.core.data.Value;
|
||||
import org.apache.accumulo.core.iterators.IteratorEnvironment;
|
||||
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import timely.adapter.accumulo.MetricAdapter;
|
||||
import timely.api.request.timeseries.QueryRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class DataStoreCacheIterator implements SortedKeyValueIterator<Key, Value> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DataStoreCacheIterator.class);
|
||||
private DataStoreCache store;
|
||||
private VisibilityFilter visibilityFilter;
|
||||
private QueryRequest.SubQuery query;
|
||||
private long startTs;
|
||||
private long endTs;
|
||||
|
||||
private Iterator<Map.Entry<TaggedMetric, GorillaStore>> storeItr = null;
|
||||
private WrappedGorillaDecompressorIterator decompressors = null;
|
||||
private KeyValue currentKeyValue = null;
|
||||
private Queue<KeyValue> kvQueue = new LinkedList<>();
|
||||
private Set<TaggedMetric> matchingTaggedMetrics = new HashSet<>();
|
||||
|
||||
public DataStoreCacheIterator(DataStoreCache store, VisibilityFilter visibilityFilter, QueryRequest.SubQuery query,
|
||||
long startTs, long endTs) {
|
||||
|
||||
this.store = store;
|
||||
this.visibilityFilter = visibilityFilter;
|
||||
this.query = query;
|
||||
this.startTs = startTs;
|
||||
this.endTs = endTs;
|
||||
Map<TaggedMetric, GorillaStore> storeMap = this.store.getGorillaStores(query.getMetric());
|
||||
this.storeItr = storeMap.entrySet().iterator();
|
||||
this.decompressors = getNextDecompressorIterable();
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
Map<Key, Value> entries = getEntries();
|
||||
for (Map.Entry<Key, Value> entry : entries.entrySet()) {
|
||||
kvQueue.add(new KeyValue(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
LOG.info(
|
||||
"Time to initialize cache iterator for {} with {} TaggedMetric/GorillaStore pairs and {} K/V entries - {}ms",
|
||||
query.toString(), storeMap.size(), entries.size(), System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
private WrappedGorillaDecompressorIterator getNextDecompressorIterable() {
|
||||
Map<String, String> requestedTags = query.getTags();
|
||||
WrappedGorillaDecompressorIterator nextPair = null;
|
||||
while (nextPair == null && storeItr.hasNext()) {
|
||||
Map.Entry<TaggedMetric, GorillaStore> entry = storeItr.next();
|
||||
TaggedMetric currentTaggedMetric = entry.getKey();
|
||||
|
||||
// keep a cache of tagged metrics that match
|
||||
boolean addCurrent = false;
|
||||
if (matchingTaggedMetrics.contains(currentTaggedMetric)) {
|
||||
addCurrent = true;
|
||||
} else {
|
||||
if (currentTaggedMetric.matches(requestedTags) && entry.getKey().isVisible(visibilityFilter)) {
|
||||
addCurrent = true;
|
||||
matchingTaggedMetrics.add(currentTaggedMetric);
|
||||
}
|
||||
}
|
||||
|
||||
if (addCurrent) {
|
||||
List<WrappedGorillaDecompressor> listDecompressors = entry.getValue().getDecompressors(startTs, endTs);
|
||||
if (listDecompressors.size() > 0) {
|
||||
nextPair = new WrappedGorillaDecompressorIterator(currentTaggedMetric, listDecompressors);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nextPair;
|
||||
}
|
||||
|
||||
private Map<Key, Value> getEntries() {
|
||||
|
||||
// sort all retrieved entries by key order, consistent with the accumulo
|
||||
// version
|
||||
Map<Key, Value> entries = new TreeMap<>();
|
||||
if (decompressors != null) {
|
||||
TaggedMetric tm = decompressors.getTaggedMetric();
|
||||
WrappedGorillaDecompressor decompressor = decompressors.getDecompressorWrapper();
|
||||
|
||||
fi.iki.yak.ts.compression.gorilla.Pair gPair = null;
|
||||
while (decompressor != null && decompressors != null) {
|
||||
gPair = decompressor.readPair();
|
||||
if (gPair == null) {
|
||||
if (decompressors.hasNext()) {
|
||||
decompressor = decompressors.next();
|
||||
} else {
|
||||
if ((decompressors = getNextDecompressorIterable()) != null) {
|
||||
tm = decompressors.getTaggedMetric();
|
||||
decompressor = decompressors.getDecompressorWrapper();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
long ts = gPair.getTimestamp();
|
||||
if (ts >= startTs && ts <= endTs) {
|
||||
entries.put(MetricAdapter.toKey(this.query.getMetric(), tm.getTags(), ts), new Value(
|
||||
MetricAdapter.encodeValue(gPair.getDoubleValue())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(SortedKeyValueIterator<Key, Value> source, Map<String, String> options, IteratorEnvironment env)
|
||||
throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTop() {
|
||||
return currentKeyValue != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void next() throws IOException {
|
||||
currentKeyValue = kvQueue.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(Range range, Collection<ByteSequence> columnFamilies, boolean inclusive) throws IOException {
|
||||
currentKeyValue = kvQueue.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getTopKey() {
|
||||
if (currentKeyValue != null) {
|
||||
return currentKeyValue.getKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getTopValue() {
|
||||
if (currentKeyValue != null) {
|
||||
return currentKeyValue.getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedKeyValueIterator<Key, Value> deepCopy(IteratorEnvironment env) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package timely.store.cache;
|
||||
|
||||
import fi.iki.yak.ts.compression.gorilla.GorillaDecompressor;
|
||||
import fi.iki.yak.ts.compression.gorilla.LongArrayInput;
|
||||
import org.apache.hadoop.fs.FSDataInputStream;
|
||||
import org.apache.hadoop.fs.FileStatus;
|
||||
import org.apache.hadoop.fs.FileSystem;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import timely.model.Metric;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
public class GorillaStore {
|
||||
|
||||
private Queue<WrappedGorillaCompressor> archivedCompressors = new LinkedList<WrappedGorillaCompressor>();
|
||||
private StampedLock archivedCompressorLock = new StampedLock();
|
||||
private StampedLock currentCompressorLock = new StampedLock();
|
||||
|
||||
transient private WrappedGorillaCompressor current = null;
|
||||
transient private List<Metric> metricCache = new ArrayList<>();
|
||||
|
||||
private long oldestTimestamp = Long.MAX_VALUE;
|
||||
private long newestTimestamp = -1;
|
||||
|
||||
public GorillaStore() {
|
||||
|
||||
}
|
||||
|
||||
public GorillaStore(FileSystem fs, String metric, timely.Configuration conf) throws IOException {
|
||||
String baseDir = "/timely/cache";
|
||||
Path directory = new Path(baseDir + "/" + metric);
|
||||
List<WrappedGorillaCompressor> compressors = readCompressors(fs, directory);
|
||||
long stamp = archivedCompressorLock.writeLock();
|
||||
try {
|
||||
archivedCompressors.addAll(compressors);
|
||||
} finally {
|
||||
archivedCompressorLock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
private WrappedGorillaCompressor getCompressor(long timestamp, long lockStamp) {
|
||||
if (current == null) {
|
||||
if (oldestTimestamp == -1) {
|
||||
oldestTimestamp = timestamp;
|
||||
}
|
||||
newestTimestamp = timestamp;
|
||||
long stamp = lockStamp == 0 ? currentCompressorLock.writeLock() : lockStamp;
|
||||
try {
|
||||
current = new WrappedGorillaCompressor(timestamp);
|
||||
} finally {
|
||||
if (lockStamp == 0) {
|
||||
currentCompressorLock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
public long ageOffArchivedCompressors(long maxAge) {
|
||||
long numRemoved = 0;
|
||||
long oldestRemainingTimestamp = Long.MAX_VALUE;
|
||||
long stamp = archivedCompressorLock.writeLock();
|
||||
try {
|
||||
if (archivedCompressors.size() > 0) {
|
||||
Iterator<WrappedGorillaCompressor> itr = archivedCompressors.iterator();
|
||||
long now = System.currentTimeMillis();
|
||||
while (itr.hasNext()) {
|
||||
WrappedGorillaCompressor c = itr.next();
|
||||
long timeSinceNewestTimestamp = now - c.getNewestTimestamp();
|
||||
if (timeSinceNewestTimestamp >= maxAge) {
|
||||
itr.remove();
|
||||
numRemoved++;
|
||||
} else {
|
||||
if (c.getOldestTimestamp() < oldestRemainingTimestamp) {
|
||||
oldestRemainingTimestamp = c.getOldestTimestamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldestRemainingTimestamp < Long.MAX_VALUE) {
|
||||
oldestTimestamp = oldestRemainingTimestamp;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
archivedCompressorLock.unlockWrite(stamp);
|
||||
}
|
||||
return numRemoved;
|
||||
}
|
||||
|
||||
protected void writeCompressor(String metric, WrappedGorillaCompressor wrappedGorillaCompressor) throws IOException {
|
||||
|
||||
try {
|
||||
Configuration configuration = new Configuration();
|
||||
FileSystem fs = FileSystem.get(new URI("hdfs://localhost:8020"), configuration);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss.SSS");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
String baseDir = "/timely/cache";
|
||||
Path directory = new Path(baseDir + "/" + metric);
|
||||
String fileName = metric + "-" + sdf.format(new Date(wrappedGorillaCompressor.getOldestTimestamp()));
|
||||
Path outputPath = new Path(directory, fileName);
|
||||
if (!fs.exists(directory)) {
|
||||
fs.mkdirs(directory);
|
||||
}
|
||||
if (fs.exists(outputPath)) {
|
||||
throw new IOException("output path exists");
|
||||
}
|
||||
OutputStream os = fs.create(outputPath);
|
||||
// write object to hdfs file
|
||||
ObjectOutputStream oos = new ObjectOutputStream(os);
|
||||
oos.writeObject(wrappedGorillaCompressor);
|
||||
oos.close();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<WrappedGorillaCompressor> readCompressors(FileSystem fs, Path directory) throws IOException {
|
||||
|
||||
List<WrappedGorillaCompressor> compressors = new ArrayList<>();
|
||||
|
||||
ObjectInputStream ois = null;
|
||||
try {
|
||||
FileStatus fileStatus[] = fs.listStatus(directory);
|
||||
for (FileStatus status : fileStatus) {
|
||||
FSDataInputStream inputStream = fs.open(status.getPath());
|
||||
ois = new ObjectInputStream(inputStream);
|
||||
WrappedGorillaCompressor copyCompressor = (WrappedGorillaCompressor) ois.readObject();
|
||||
compressors.add(copyCompressor);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IOException(e);
|
||||
} finally {
|
||||
if (ois != null) {
|
||||
ois.close();
|
||||
}
|
||||
}
|
||||
return compressors;
|
||||
}
|
||||
|
||||
public void archiveCurrentCompressor() {
|
||||
|
||||
long archiveStamp = archivedCompressorLock.writeLock();
|
||||
long currentStamp = currentCompressorLock.writeLock();
|
||||
try {
|
||||
if (current != null) {
|
||||
current.close();
|
||||
archivedCompressors.add(current);
|
||||
current = null;
|
||||
}
|
||||
} finally {
|
||||
archivedCompressorLock.unlockWrite(archiveStamp);
|
||||
currentCompressorLock.unlockWrite(currentStamp);
|
||||
}
|
||||
}
|
||||
|
||||
public List<WrappedGorillaDecompressor> getDecompressors(long begin, long end) {
|
||||
|
||||
List<WrappedGorillaDecompressor> decompressors = new ArrayList<>();
|
||||
|
||||
long stamp = archivedCompressorLock.readLock();
|
||||
try {
|
||||
for (WrappedGorillaCompressor r : archivedCompressors) {
|
||||
if (r.inRange(begin, end)) {
|
||||
GorillaDecompressor d = new GorillaDecompressor(new LongArrayInput(r.getCompressorOutput()));
|
||||
// use -1 length since this compressor is closed
|
||||
decompressors.add(new WrappedGorillaDecompressor(d, -1));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
archivedCompressorLock.unlockRead(stamp);
|
||||
}
|
||||
|
||||
// as long as begin is inRange, we should use the data in the current
|
||||
// compressor as well
|
||||
stamp = currentCompressorLock.readLock();
|
||||
try {
|
||||
if (current != null && current.inRange(begin, end)) {
|
||||
LongArrayInput decompressorByteBufferInput = new LongArrayInput(current.getCompressorOutput());
|
||||
GorillaDecompressor d = new GorillaDecompressor(decompressorByteBufferInput);
|
||||
|
||||
WrappedGorillaDecompressor wd = new WrappedGorillaDecompressor(d, current.getNumEntries());
|
||||
decompressors.add(wd);
|
||||
}
|
||||
} finally {
|
||||
currentCompressorLock.unlockRead(stamp);
|
||||
}
|
||||
return decompressors;
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
|
||||
List<Metric> tempCache = new ArrayList<>();
|
||||
synchronized (metricCache) {
|
||||
tempCache.addAll(metricCache);
|
||||
metricCache.clear();
|
||||
}
|
||||
|
||||
long stamp = currentCompressorLock.writeLock();
|
||||
try {
|
||||
WrappedGorillaCompressor c = null;
|
||||
for (Metric m : tempCache) {
|
||||
long ts = m.getValue().getTimestamp();
|
||||
double v = m.getValue().getMeasure();
|
||||
if (ts > newestTimestamp) {
|
||||
newestTimestamp = ts;
|
||||
if (c == null) {
|
||||
c = getCompressor(ts, stamp);
|
||||
}
|
||||
c.addValue(ts, v);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
currentCompressorLock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValue(Metric metric) {
|
||||
synchronized (metricCache) {
|
||||
metricCache.add(metric);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValue(long timestamp, double value) {
|
||||
|
||||
// values must be inserted in order
|
||||
if (timestamp >= newestTimestamp) {
|
||||
newestTimestamp = timestamp;
|
||||
long stamp = currentCompressorLock.writeLock();
|
||||
try {
|
||||
getCompressor(timestamp, stamp).addValue(timestamp, value);
|
||||
} finally {
|
||||
currentCompressorLock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getNewestTimestamp() {
|
||||
return newestTimestamp;
|
||||
}
|
||||
|
||||
public long getOldestTimestamp() {
|
||||
return oldestTimestamp;
|
||||
}
|
||||
|
||||
public long getNumEntries() {
|
||||
|
||||
long numEntries = 0;
|
||||
long stamp = currentCompressorLock.readLock();
|
||||
try {
|
||||
if (current != null) {
|
||||
numEntries += current.getNumEntries();
|
||||
}
|
||||
} finally {
|
||||
currentCompressorLock.unlockRead(stamp);
|
||||
}
|
||||
stamp = archivedCompressorLock.readLock();
|
||||
try {
|
||||
if (archivedCompressors.size() > 0) {
|
||||
Iterator<WrappedGorillaCompressor> itr = archivedCompressors.iterator();
|
||||
while (itr.hasNext()) {
|
||||
WrappedGorillaCompressor c = itr.next();
|
||||
numEntries += c.getNumEntries();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
archivedCompressorLock.unlockRead(stamp);
|
||||
}
|
||||
return numEntries;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user