Compare commits

...
137 Commits
Author SHA1 Message Date
Bill Oley 7694d0afe7 Update dependencies 2019-04-01 17:01:00 -04:00
Bill Oley dba494b06d Bump pom versions to 1.2.1-SNAPSHOT 2019-02-25 15:41:31 -05:00
Bill Oley 5826884c3c Tag version 1.2.0 2019-02-25 15:11:57 -05:00
Bill Oley f94c731bbc Remove unused modeule directory introduced in error 2019-02-25 15:11:12 -05:00
Bill Oley 4e042bcb94 Allow all http methods in grafana auth proxy, disable cookies in HttpPools 2019-02-22 16:17:36 -05:00
Bill Oley ab7f499134 closes #180 Allow a configurable default visibility for metrics 2019-02-21 15:33:25 -05:00
Bill Oley c098f9f3e4 re #179 Create an authentication app for grafana that uses Timely's authentication framework and grafana's auth-proxy
re #179 Create authentication proxy for grafana
2019-02-21 13:28:12 -05:00
Bill Oley 94cfcbc0c4 Allow authentication provider to return roles, check users against requiredRoles and requiredAuths for access
index on master: 674e1b0 Improve synchronization in AuthenticationService

Check validated users against requiredRoles and requiredAuths

Conflicts:
	server/conf/security.xml
	server/src/main/java/timely/auth/AuthenticationService.java

More work
2019-02-15 11:24:07 -05:00
Bill Oley 674e1b0ab5 Improve synchronization in AuthenticationService 2019-02-08 16:53:35 -05:00
Bill Oley 4eedb84547 Add synchronization to AuthenticationService to prevent simultaneous authentications of the same user by multiple threads 2019-02-07 17:17:08 -05:00
Bill Oley bef86eac37 Fix websocket proxying with a client certificate, add ability to configure client cert in python websocket client 2019-02-07 17:16:00 -05:00
Bill Oley 27bb16e403 re #178 Allow proxying of http and ws requests
More work

Correct errors
2019-02-05 11:41:35 -05:00
Bill Oley a95d2d7ea1 Bump pom versions to 1.1.1-SNAPSHOT 2019-01-16 13:55:47 -05:00
Bill Oley f751175190 Update version of collectd 2019-01-16 13:53:43 -05:00
Bill Oley 648199f330 Revert "Bump pom versions to 1.1.1-SNAPSHOT"
This reverts commit fcfc3f7bc0.
2019-01-16 13:53:26 -05:00
Bill Oley fcfc3f7bc0 Bump pom versions to 1.1.1-SNAPSHOT 2019-01-15 15:48:43 -05:00
Bill Oley 200bc39902 Tag version 1.1.0
Grafana app changes
2019-01-15 15:47:14 -05:00
Bill Oley f854c9e394 Add build profile for python client package 2019-01-15 15:08:30 -05:00
Bill Oley 73ddae1f8d Correct typo 2019-01-15 14:15:20 -05:00
Bill Oley cb7184503e Allow Websocket requests to take advantage of cached metrics 2019-01-11 16:12:26 -05:00
Christopher Tubbs 3a357b50ef Update jackson-databind dependency
(Also, remove redundant test dependency on timely-common.)
2019-01-10 16:07:25 -05:00
Bill Oley 689aee0678 Fix deadlock in tracking nonCachedMetrics across processes 2019-01-08 12:06:41 -05:00
Bill Oley 32f6b9d4c9 Include the bin/testing directory in assembly 2019-01-04 11:14:11 -05:00
Bill Oley 512f318387 Add distributed tracking of nonCachedMetrics between the timely servers and the balancers
work in progress

Add distributed tracking of noncachedMetrics
2019-01-04 11:04:24 -05:00
Bill Oley e04cfb310b Add execute to bash scripts 2019-01-03 11:40:15 -05:00
Bill Oley 0cecc3b191 Fix copy error and imports 2019-01-02 11:02:25 -05:00
Bill Oley b3d75e4c68 Fixes to synchronizing assignments between Balancer copies, reduce number of Timer threads 2018-12-27 17:14:19 -05:00
Bill Oley 004c344153 Fix to oldestTimestamp tracking in GorillaStore and additional trace logging 2018-12-21 14:52:59 -05:00
Bill Oley 2590effeee Add actions for BalancedMetricResolver leaderLatch 2018-12-19 13:58:29 -05:00
Bill Oley f49c83601a Fix merge issues 2018-12-19 10:50:18 -05:00
Bill Oley df035259c9 Auto-discovery of timely-servers, run multiple balancers, refactor configuration
Changes in progress

Changes in progress

Work in progress
2018-12-18 16:01:10 -05:00
Bill Oley 3a8cbd3378 Use StampedLocks in BalancedMetricResolver, recalibrate ArrivalRate calculations, bug fixes 2018-11-26 16:30:17 -05:00
Bill Oley 39bea1da8d Synchronize access to balancer assignments, save & restore assignments on restart, correct access to native netty libraries 2018-11-20 12:13:55 -05:00
Bill Oley 1d3f8b7d3a Reformatting and fix PooledSocketFactory validate 2018-11-16 15:14:06 -05:00
Bill Oley 0fbbb86768 re #176 Create TCP/UDP/HTTP/Websocket balancer for timely 2018-11-06 13:22:19 -05:00
Bill Oley 8b807e5ac7 re #163 Create in-memory cache capability 2018-11-06 13:12:08 -05:00
Christopher Tubbs 710c789984 More POM cleanup
* More consolidation of plugin configuration to parent POM
* Fix grafana (timely-app) module config (not a jar, so skip jar plugins)
* Migrate to spotbugs from findbugs
* Remove unneeded bundling of flatbuffers-java library
* Keep exclude filter files out of the jar artifacts by moving out of src/main/resources
2018-10-18 20:30:52 -04:00
Christopher Tubbs cf0d63a4e7 Apply formatting changes to java source files 2018-10-18 18:41:23 -04:00
Christopher Tubbs bfc67db185 Update poms
* use sortpom
* remove redundancy in child poms
* add missing versions
* update versions to fix CVE issues
* update formatter and add impsort
2018-10-18 18:40:12 -04:00
femtotrader f604314640 Update README.md (#175) 2018-10-03 01:28:16 -04:00
Christopher Tubbs 25c9e93888 Update lodash dependency to 4.17.11
Update dependency due to https://nvd.nist.gov/vuln/detail/CVE-2018-3721
2018-09-27 16:23:06 -04:00
Bill Oley a323d198e9 fixes #173 Allow gaps in data to be interpolated or filled with value 2018-09-21 14:48:06 -04:00
Bill Oley 8e6cc4e0b4 closes #171 TimelyWebsocket should handle json return of a TimelyException 2018-09-14 14:35:37 -04:00
Bill Oley 3a3d98ec81 closes #172 WebSocketClient typo in constructor (uri vs url) 2018-09-14 14:34:07 -04:00
Bill Oley 7599a2ac60 closes #170 TimeDateRange python code is mis-calculating begin/end date 2018-09-14 14:33:14 -04:00
Bill Oley 3d33ba2e31 re #163 send and accept metric name in query paramater for load
balancing by metric name
2018-06-15 15:54:32 -04:00
Bill Oley bbbb453818 closes #168 - Websocket MetricScanner sometimes does not return completed=true element 2018-05-25 15:04:01 -04:00
Bill Oley 54951c05e6 re #165 - Aggregation buckets in AggregationIterator should be cleared once their values are returned 2018-03-26 10:00:50 -04:00
Bill Oley b63914e02c Remove unnecessary debug logging 2018-03-22 11:04:19 -04:00
Bill Oley 1f386db00a closes #167 - Python client and analytic code should retain metrics with duplicate timestamps 2018-03-22 10:52:23 -04:00
Bill Oley 49222ac76a Bump version to 1.0.1-SNAPSHOT 2018-03-22 10:52:17 -04:00
Bill Oley 38f2c6cdfa Release version 1.0.0 2018-03-22 10:52:03 -04:00
Bill Oley 3926e6f066 Revert "closes #167 - Python client and analytic code should retain metrics with duplicate timestamps"
This reverts commit 30800a0b5c.
2018-03-22 10:47:18 -04:00
Bill Oley 30800a0b5c closes #167 - Python client and analytic code should retain metrics with duplicate timestamps 2018-03-20 13:31:57 -04:00
Bill Oley 92b23a4c51 closes #166 : Gracefully handle tag values with delimiters and handle bad data
closes #166 : Gracefully handle tag values with delimiters and handle bad data
2018-03-15 16:13:11 -04:00
Bill Oley 8030ea59a5 TIMELY-165: Refactor key structure of metrics table for better query performance
refactor key structure for better performance

refactor key structure - fix tests

encode timestamp in colQual, fix range creator in DataSourceImpl

Add setup.py to .gitignore, add logging for metrics that fail to parse

TIMELY-165: cleanup
2018-03-08 16:57:30 -05:00
Bill Oley a4c8158003 closes #164 - DownsampleMemoryEstimator should be able to handle situation where start of scan has no samples and correctly estimate memory usage anyway 2018-02-13 13:13:47 -05:00
Bill Oley acba454ee3 Merge commit '3079060' 2018-01-26 10:39:06 -05:00
Bill Oley 30790608ca Bump version to 0.0.7-SNAPSHOT 2018-01-26 10:38:16 -05:00
Bill Oley f2f3788f99 tag version 0.0.6 2018-01-26 10:35:56 -05:00
Bill Oley 326b9dc8f1 closes #164 - Limit memory consumed by DownsampleIterator and return iteratively on time bucket boundaries
Initial work

More work

cwImproved implementation of DownsampleIterator.MemoryEstimator

closes #164 - Limit memory consumed by DownsampleIterator and return iteratively on time bucket boundaries
2018-01-24 15:23:20 -05:00
Bill Oley 3f3b605c63 re #160 - Close and invalidate CloseableHttpClient so that we can recover from bad connections 2017-11-17 10:19:11 -05:00
Bill Oley 5580a2fa66 re #160 - refactor config for WriteNSQPlugin, add socket timeouts to WriteTimelyPlugin 2017-11-09 16:08:08 -05:00
Bill Oley 1a54c3ada3 closes #161 - Add handling of a min and max percentage of a rolling average to python analytic 2017-11-03 12:20:13 -04:00
Bill Oley 2080764e0d closes #162 - Python analytic should be able to convert a counter metric into a rate 2017-11-03 09:57:38 -04:00
Bill Oley e7dade04ee closes #160 - Improve reliability and error recovery of WriteNSQPlugin and WriteTimelyPlugin 2017-10-31 14:52:49 -04:00
Bill Oley 7ed337483e fixes #159 TimelyAnalytic error when analyticConfig.groupByColumn is None 2017-10-18 12:21:28 -04:00
Bill Oley b070bc3c47 closes #158 Package python code so that it can be deployed and managed with pip 2017-10-18 11:10:41 -04:00
Bill Oley 51f9103741 closes #156: Use object pools in WriteNSQPlugin and WriteTimelyPlugin to support multithreaded use 2017-10-02 13:56:16 -04:00
Bill Oley a97fa83e2f closes #157 - Add query metrics to internal metrics 2017-09-26 12:59:59 -04:00
Drew Farris 0b5c3544db Merge pull request #154 from drewfarris/docker-update
Docker & Grafana updates for 0.0.6-SNAPSHOT
2017-08-15 16:21:15 -04:00
Dave Marion 186b60756a fixes #155 - log more information on ssl error 2017-08-14 09:18:32 -04:00
Drew Farris 133a6bf5cf Bumped grafana plugin reference to 0.0.6-SNAPSHOT 2017-08-12 12:24:51 -04:00
Drew Farris b684d7eefe Updated Docker config for 0.0.6-SNAPSHOT and grafana 4.4.3 2017-08-12 12:21:34 -04:00
Dave Marion 73f1603f1e Bump version to 0.0.6-SNAPSHOT 2017-08-11 11:24:36 -04:00
Dave Marion 9267e0c915 Change pom versions for 0.0.5 tag 2017-08-11 11:21:44 -04:00
Dave Marion 1ce49628e5 Closes #123 - Updates to Timely App and Dashboards for latest Grafana 2017-08-08 10:30:38 -04:00
Bill Oley acad3af083 fixes #149: Add funtionality to CollectDPluginParent to handle GenericJMX metrics 2017-08-01 16:39:32 -04:00
Dave Marion b4635f1a55 re #123 - update pairs() to toPairs() 2017-07-12 14:15:00 -04:00
alerman 4c1ede04df Update datasource.js for grafana 4 (#147)
fixes #123 - Update _.all to _.every as the newer version of lodash has changed the term. This fixes error seen with Timely Standalon and Grafana >4.2
2017-07-12 13:44:49 -04:00
Dave Marion 2841b0a3e9 closes #143 : set sock to null in close, fixed some warnings 2017-05-04 16:09:57 -04:00
Dave Marion 6efa13909f closes #144: Update to Netty 4.0.46.Final 2017-05-04 12:45:42 -04:00
Dave Marion 368c572c67 closes #141 - build collectd plugins only when collectd profile is activated 2017-04-14 09:34:17 -04:00
Dave Marion 554baf6af7 Updated readme to new markdown syntax 2017-04-13 12:59:50 -04:00
Dave Marion 1ef11799be fixes #138 - don't close source until all subscriptions have completed. 2017-03-24 08:52:51 -04:00
Dave Marion 16b664297d fixes #138 - synchronize the response list and exit the subscription source when the last message is received 2017-03-23 16:35:16 -04:00
Bill Oley 4277b63bc3 re #130 - Improvements to python code 2017-03-22 10:57:42 -04:00
Bill Oley 5a9ad3cb27 re #130 - Corrections to python code 2017-03-20 08:22:34 -04:00
Bill Oley f990e89ab3 re #130 - Improvements to python code 2017-03-17 17:05:19 -04:00
Bill Oley c01ac80f23 re #130 - Corrections to python code 2017-03-16 08:59:20 -04:00
Bill Oley 35e8f99d09 re #130 - Additional development on Python client and analytics 2017-03-15 18:21:43 -04:00
Bill Oley 847144d236 re #130 - Update to seaborn graphing for python client 2017-02-13 17:55:22 -05:00
Ivan Bella b3bfbc1e1f fixes #136 removed the encoding and decoding of tag names and values from the tag parser 2017-02-06 13:48:07 -05:00
Bill Oley 8d86e3894a re #130 - Try seaborn graphing to generate png file programmatically - in progress 2017-02-03 16:43:47 -05:00
Dave Marion 99b9181902 Bumped version to 0.0.5-SNAPSHOT 2017-02-02 10:48:56 -05:00
Dave Marion 3c0bf87b79 0.0.4 release 2017-02-02 10:43:02 -05:00
Bill Oley d2ba7f635e re #130 - Refactor python code in preparation to automate 2017-01-23 15:08:42 -05:00
Ivan Bella 92510fdc83 Aggregation (#135)
fixes #108 - Implemented the aggregation across time series separate after downsampling
2017-01-23 10:33:04 -05:00
Dave Marion 4eac17fbb5 re #73 - change map in accumulator to a concurrent map, it was failing to serialize with a ConcurrentModificationException from the task manager to the job manager 2017-01-20 12:13:36 -05:00
Dave Marion 328ae075f4 fixes #134 - remove log4j2 transitive dependency so that flink job logging works 2017-01-19 13:15:27 -05:00
Ivan Bella cf12295ac8 fixes 133 - added the buildnumber-maven-plugin, and put the buildNumber and timestamp into each of the jar's MANIFEST 2017-01-18 14:37:28 -05:00
Ivan Bella df180504f2 fixes #132 - Ensure we do not seek past the end key in the initial range when skipping aged off data. 2017-01-18 13:15:00 -05:00
Dave Marion 021ff832ef fixes #131 - bring back the old ageoff filter and apply to meta table, use new iterator on metrics table 2017-01-18 09:10:40 -05:00
Drew Farris 6fc9066e70 Closes #128 modify server pom for netty-transport-native-epoll dep
- remove runtime scope
2017-01-10 21:14:38 -05:00
Bill Oley f11b82b662 re #130 - Create analytics library - correct errors and add ability to display all series regardless of detected conditions 2017-01-09 17:29:15 -05:00
Bill Oley dcfd165a9b re #130 - Add sample analytic script 2017-01-06 18:32:39 -05:00
Bill Oley fd98558218 re #130 - Create analytics library and graphing capability using plot.ly 2017-01-06 18:26:02 -05:00
Dave Marion f47cd66ec2 re #128 - use no classifier for the native transport epoll dependency, use one for the linux environments though 2017-01-05 16:49:58 -05:00
Dave Marion ec4ba6e7f5 closes #129 - update Apache Flink version to 1.1.4 2017-01-05 15:14:29 -05:00
Dave Marion e698734e4b closes #126: Added udp and tcp client 2016-12-21 16:53:11 -05:00
Dave Marion 78b49fe44e closes #124 - change ageoff filter to an iterator so that it can seek smartly 2016-12-13 12:25:23 -05:00
Dave Marion f109f04162 closes #125 - use a smarter start time when zero is supplied by using the age off configuration 2016-12-08 09:59:35 -05:00
Dave Marion 4bf68fe328 closes #122: use netty native epoll 2016-12-07 15:03:05 -05:00
Dave Marion 9eb8da8e80 re #73 - changes from testing Flink summarization job
closes #121 - made metric scanner options configurable
2016-11-30 15:23:45 -05:00
Bill Oley f54705374e Add more robust begin/end time options 2016-11-29 17:16:52 -05:00
Dave Marion 5e166f0044 re #120 - add task to send ping to server at interval 2016-11-21 09:41:50 -05:00
Bill Oley 17527af6b4 re #119 - Create python websocket client 2016-11-16 16:31:50 -05:00
Dave Marion 64f7890f8b re #73 - created Flink job to summarize metrics and re-insert into Timely 2016-11-10 16:03:11 -05:00
Ivan Bella e7c058ef56 closes #116: Simplified the endOfPeriod calculation 2016-11-10 14:11:13 -05:00
Dave Marion 6415d100fc re #118 - send last message at end of subscription with complete flag set to true. Refactor client classes. 2016-11-10 13:25:22 -05:00
Dave Marion 046000c16a closes #118 - don't close connection when metric scanner is done. There might be multiple on one connection, but do inform the Subscription object that the thread is done 2016-11-10 11:25:15 -05:00
Dave Marion d0f6534296 re #118 - close web socket connection on error or when done sending subscription data 2016-11-10 11:05:19 -05:00
Dave Marion 07c04425e1 closes #117 - added endTime to client AddSubscription object 2016-11-09 17:08:28 -05:00
Dave Marion 113b6464f3 closes #116 - aligned the end timestamp to the end of the last downsample period 2016-11-09 15:38:14 -05:00
Dave Marion e2eda4c74a closes #116 - baseline query start to the start of the first downsample window based on the epoch 2016-11-09 10:54:42 -05:00
Dave Marion 68b8d6cd51 closes #115 = added viz tags to metric response on subscriptions 2016-11-08 09:48:52 -05:00
Dave Marion 458028d5df re #111 - remove unused import 2016-11-04 12:05:27 -04:00
Dave Marion 320f291785 closes #113 - fallback to log4j2 2.6.2 2016-11-04 11:27:01 -04:00
Dave Marion 95c2654357 Merge branch 'master' of https://github.com/NationalSecurityAgency/timely 2016-11-03 16:08:57 -04:00
Dave Marion 045964c01d closes #112 - added Java WebSocket client 2016-11-03 14:42:29 -04:00
Ivan Bella 2f8759e32b re #111: Added some useful trace messages 2016-11-03 13:28:28 -04:00
Dave Marion 451f001b9f closes #109 - added endTime to add subscription request 2016-11-03 11:22:03 -04:00
Ivan Bella 044f6576fd close #111: Reimplemented the counter concept for rates. 2016-11-02 17:18:33 -04:00
Dave Marion 520edee16f closes #110 - created client request objects for login and subscription API 2016-11-02 11:09:49 -04:00
Dave Marion 8034a673fc Updated haproxy parsing logic 2016-10-31 12:53:29 -04:00
Dave Marion eb9e874a57 closes #105 - updated most dependencies to latest release 2016-10-27 16:51:20 -04:00
Dave Marion 1658312512 Bumped version for next dev cycle 2016-10-27 13:44:12 -04:00
361 changed files with 35427 additions and 6621 deletions

No files matched your search

+1
View File
@@ -6,4 +6,5 @@ target/
*.iml
logs/
server/pki/
client/src/main/python/setup.py
-2
View File
@@ -1,2 +0,0 @@
This product includes Google Flatbuffers (https://google.github.io/flatbuffers/)
Copyright 2014 Google Inc. All rights reserved.
+2 -2
View File
@@ -2,7 +2,7 @@
[![Apache License][li]][ll]
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo] (http://accumulo.apache.org/) and [Grafana] (http://www.grafana.org). Documentation is located [here] (https://nationalsecurityagency.github.io/timely/).
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo](https://accumulo.apache.org/) and [Grafana](https://www.grafana.com). Documentation is located [here](https://nationalsecurityagency.github.io/timely/).
[li]: http://img.shields.io/badge/license-ASL-blue.svg
[li]: https://img.shields.io/badge/license-ASL-blue.svg
[ll]: https://www.apache.org/licenses/LICENSE-2.0
+2
View File
@@ -0,0 +1,2 @@
/bin/
/target
+20
View File
@@ -0,0 +1,20 @@
timelyHostname=
timelyTcpPort=
timelyHttpsPort=
timelyWssPort=
doLogin=
#timelyUsername=
#timelyPassword=
keyStoreFile=
keyStoreType=
keyStorePass=
trustStoreFile=
trustStoreType=
trustStorePass=
hostVerificationEnabled=false
bufferSize=10485760
metrics=
startTime=0
endTime=0
interval=1
intervalUnits=HOURS
+256
View File
@@ -0,0 +1,256 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>1.2.1-SNAPSHOT</version>
</parent>
<artifactId>timely-analytics</artifactId>
<!--
Execute "mvn clean package -Pbuild-jar"
to build a jar file out of this project!
How to use the Flink Quickstart pom:
a) Adding new dependencies:
You can add dependencies to the list below.
Please check if the maven-shade-plugin below is filtering out your dependency
and remove the exclude from there.
b) Build a jar for running on the cluster:
There are two options for creating a jar from this project
b.1) "mvn clean package" -> this will create a fat jar which contains all
dependencies necessary for running the jar created by this pom in a cluster.
The "maven-shade-plugin" excludes everything that is provided on a running Flink cluster.
b.2) "mvn clean package -Pbuild-jar" -> This will also create a fat-jar, but with much
nicer dependency exclusion handling. This approach is preferred and leads to
much cleaner jar files.
-->
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerId>jdt</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<version>0.21.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<!-- We use the maven-shade plugin to create a fat jar that contains all dependencies
except flink and it's transitive dependencies. The resulting fat-jar can be executed
on a cluster. Change the value of Program-Class if your program entry point changes. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<!-- Run shade goal on package phase -->
<execution>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactSet>
<excludes>
<!-- This list contains all dependencies of flink-dist
Everything else will be packaged into the fat-jar
-->
<exclude>org.apache.flink:flink-annotations</exclude>
<exclude>org.apache.flink:flink-shaded-hadoop1_2.11</exclude>
<exclude>org.apache.flink:flink-shaded-hadoop2</exclude>
<exclude>org.apache.flink:flink-shaded-curator-recipes</exclude>
<exclude>org.apache.flink:flink-core</exclude>
<exclude>org.apache.flink:flink-java</exclude>
<exclude>org.apache.flink:flink-scala_2.11</exclude>
<exclude>org.apache.flink:flink-runtime_2.11</exclude>
<exclude>org.apache.flink:flink-optimizer_2.11</exclude>
<exclude>org.apache.flink:flink-clients_2.11</exclude>
<exclude>org.apache.flink:flink-avro_2.11</exclude>
<exclude>org.apache.flink:flink-examples-batch_2.11</exclude>
<exclude>org.apache.flink:flink-examples-streaming_2.11</exclude>
<exclude>org.apache.flink:flink-streaming-java_2.11</exclude>
<!-- Also exclude very big transitive dependencies of Flink
WARNING: You have to remove these excludes if your code relies on other
versions of these dependencies.
-->
<exclude>org.scala-lang:scala-library</exclude>
<exclude>org.scala-lang:scala-compiler</exclude>
<exclude>org.scala-lang:scala-reflect</exclude>
<exclude>com.typesafe.akka:akka-actor_*</exclude>
<exclude>com.typesafe.akka:akka-remote_*</exclude>
<exclude>com.typesafe.akka:akka-slf4j_*</exclude>
<exclude>io.netty:netty-all</exclude>
<exclude>io.netty:netty</exclude>
<exclude>commons-fileupload:commons-fileupload</exclude>
<exclude>org.apache.avro:avro</exclude>
<exclude>commons-collections:commons-collections</exclude>
<exclude>org.codehaus.jackson:jackson-core-asl</exclude>
<exclude>org.codehaus.jackson:jackson-mapper-asl</exclude>
<exclude>com.thoughtworks.paranamer:paranamer</exclude>
<exclude>org.xerial.snappy:snappy-java</exclude>
<exclude>org.apache.commons:commons-compress</exclude>
<exclude>org.tukaani:xz</exclude>
<exclude>com.esotericsoftware.kryo:kryo</exclude>
<exclude>com.esotericsoftware.minlog:minlog</exclude>
<exclude>org.objenesis:objenesis</exclude>
<exclude>com.twitter:chill_*</exclude>
<exclude>com.twitter:chill-java</exclude>
<exclude>commons-lang:commons-lang</exclude>
<exclude>junit:junit</exclude>
<exclude>org.apache.commons:commons-lang3</exclude>
<exclude>org.slf4j:slf4j-api</exclude>
<exclude>org.slf4j:slf4j-log4j12</exclude>
<exclude>log4j:log4j</exclude>
<exclude>org.apache.commons:commons-math</exclude>
<exclude>org.apache.sling:org.apache.sling.commons.json</exclude>
<exclude>commons-logging:commons-logging</exclude>
<exclude>commons-codec:commons-codec</exclude>
<exclude>com.fasterxml.jackson.core:jackson-core</exclude>
<exclude>com.fasterxml.jackson.core:jackson-databind</exclude>
<exclude>com.fasterxml.jackson.core:jackson-annotations</exclude>
<exclude>stax:stax-api</exclude>
<exclude>com.typesafe:config</exclude>
<exclude>org.uncommons.maths:uncommons-maths</exclude>
<exclude>com.github.scopt:scopt_*</exclude>
<exclude>commons-io:commons-io</exclude>
<exclude>commons-cli:commons-cli</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>org.apache.flink:*</artifact>
<excludes>
<!-- exclude shaded google but include shaded curator -->
<exclude>org/apache/flink/shaded/com/**</exclude>
<exclude>web-docs/**</exclude>
</excludes>
</filter>
<filter>
<!-- Do not copy the signatures in the META-INF folder.
Otherwise, this might cause SecurityExceptions when using the JAR.
-->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<!-- If you want to use ./bin/flink run <quickstart jar> uncomment the following lines.
This will add a Main-Class entry to the manifest file -->
<!--
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>jar.StreamingJob</mainClass>
</transformer>
</transformers>
-->
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Profile for packaging correct JAR files -->
<id>build-jar</id>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- disable the exclusion rules -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactSet>
<excludes combine.self="override" />
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,168 @@
package timely.analytics.flink;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.flink.streaming.util.serialization.SerializationSchema;
import timely.model.Tag;
public class MetricHistogram implements Serializable, SerializationSchema<MetricHistogram> {
private static final long serialVersionUID = 1L;
private static final String SPACE = " ";
private static final String AVG = " sample=avg\n";
private static final String COUNT = " sample=count\n";
private static final String MAX = " sample=max\n";
private static final String MIN = " sample=min\n";
private static final String SUM = " sample=sum\n";
private static final String p50 = " sample=50p\n";
private static final String p75 = " sample=75p\n";
private static final String p90 = " sample=90p\n";
private static final String p99 = " sample=99p\n";
private String metric = null;
private List<Tag> tags = null;
private long timestamp = 0L;
private boolean initialized = false;
private List<Double> values = new ArrayList<>();
public void initialize(String metric, List<Tag> tags) {
this.metric = metric;
this.tags = tags;
initialized = true;
}
public boolean isInitialized() {
return initialized;
}
public String getMetric() {
return metric;
}
public List<Tag> getTags() {
return tags;
}
public long getTimestamp() {
return timestamp;
}
public void update(Double d, long timestamp) {
values.add(d);
this.timestamp = timestamp;
}
public void done() {
Collections.sort(values);
}
public Double avg() {
if (values.size() == 0) {
return 0.0D;
}
return sum() / count();
}
public Double min() {
if (values.size() == 0) {
return 0.0D;
}
return values.get(0);
}
public Double max() {
if (values.size() == 0) {
return 0.0D;
}
return values.get(values.size() - 1);
}
public Double getPercentile(int p) {
if (values.size() == 0) {
return 0.0D;
}
int position = Math.round((p * 1.0f / 100) * values.size());
position = (0 == position) ? 0 : position - 1;
return values.get(position);
}
public Double count() {
return values.size() * 1.0D;
}
public Double sum() {
double sum = 0.0D;
for (Double d : values) {
sum += d;
}
return sum;
}
@Override
public String toString() {
done();
StringBuilder buf = new StringBuilder();
buf.append("metric: ").append(metric);
buf.append(" tags: ").append(tags);
buf.append(" timestamp: ").append(timestamp);
buf.append(" count: ").append(count());
buf.append(" min: ").append(min());
buf.append(" max: ").append(max());
buf.append(" sum: ").append(sum());
buf.append(" 50p: ").append(getPercentile(50));
buf.append(" 75p: ").append(getPercentile(75));
buf.append(" 99p: ").append(getPercentile(99));
return buf.toString();
}
public byte[] serialize(MetricHistogram histo) {
StringBuilder buf = new StringBuilder();
String tags = histo.getTags().stream().map(t -> t.join()).collect(Collectors.joining(" "));
// @formatter:off
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.min())
.append(SPACE).append(tags).append(MIN);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.max())
.append(SPACE).append(tags).append(MAX);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.count())
.append(SPACE).append(tags).append(COUNT);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.sum())
.append(SPACE).append(tags).append(SUM);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.avg())
.append(SPACE).append(tags).append(AVG);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(50))
.append(SPACE).append(tags).append(p50);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(75))
.append(SPACE).append(tags).append(p75);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(90))
.append(SPACE).append(tags).append(p90);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(99))
.append(SPACE).append(tags).append(p99);
// @formatter:off
return buf.toString().getBytes();
}
}
@@ -0,0 +1,19 @@
package timely.analytics.flink;
import org.apache.flink.api.java.functions.KeySelector;
import timely.api.response.MetricResponse;
/**
* Uniquely defines a time series in a Flink stream. A unique time series is the
* metric name and all of its tags, to include visibility if it exists.
*/
public class MetricKeySelector implements KeySelector<MetricResponse, String> {
private static final long serialVersionUID = 1L;
@Override
public String getKey(MetricResponse m) throws Exception {
return m.getMetric() + m.getTags().toString();
}
}
@@ -0,0 +1,55 @@
package timely.analytics.flink;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.commons.lang3.mutable.MutableLong;
import org.apache.flink.api.common.accumulators.Accumulator;
/**
* Custom accumulator that retains a sorted map of strings and their counts
*
*/
public class SortedStringAccumulator implements Accumulator<String, ConcurrentSkipListMap<String, MutableLong>> {
private static final long serialVersionUID = 1L;
private ConcurrentSkipListMap<String, MutableLong> values = new ConcurrentSkipListMap<>();
@Override
public void add(String value) {
if (values.containsKey(value)) {
values.get(value).increment();
} else {
values.put(value, new MutableLong(1));
}
}
@Override
public ConcurrentSkipListMap<String, MutableLong> getLocalValue() {
return values;
}
@Override
public void resetLocal() {
values.clear();
}
@Override
public void merge(Accumulator<String, ConcurrentSkipListMap<String, MutableLong>> other) {
other.getLocalValue().forEach((k, v) -> {
if (values.containsKey(k)) {
values.get(k).add(v.longValue());
} else {
values.put(k, v);
}
});
}
@Override
public Accumulator<String, ConcurrentSkipListMap<String, MutableLong>> clone() {
SortedStringAccumulator a = new SortedStringAccumulator();
a.getLocalValue().putAll(this.values);
return a;
}
}
@@ -0,0 +1,217 @@
package timely.analytics.flink;
import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import org.apache.flink.api.common.accumulators.LongCounter;
import org.apache.flink.api.common.functions.StoppableFunction;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;
import org.apache.flink.streaming.api.watermark.Watermark;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.response.MetricResponse;
import timely.api.response.MetricResponses;
import timely.client.websocket.ClientHandler;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
import timely.serialize.JsonSerializer;
public class SubscriptionSource extends RichSourceFunction<MetricResponse> implements Serializable, StoppableFunction {
private static class SubscriptionThreadFactory implements ThreadFactory {
private static final String NAME = "Subscription Source";
private AtomicInteger threadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, NAME + " " + threadNum.getAndIncrement());
}
}
private static final Logger LOG = LoggerFactory.getLogger(SubscriptionSource.class);
private static final long serialVersionUID = 1L;
private transient WebSocketSubscriptionClient client = null;
private long start = 0L;
private long end = 0L;
private final String[] metrics;
private final SummarizationJobParameters jp;
private final long window;
private final AtomicInteger subscriptionsRemaining;
public SubscriptionSource(SummarizationJobParameters jp) {
this.jp = jp;
start = jp.getStartTime();
end = jp.getEndTime();
metrics = jp.getMetrics();
window = jp.getSummarizationInterval().toMilliseconds();
subscriptionsRemaining = new AtomicInteger(metrics.length);
}
@Override
public void open(Configuration parameters) throws Exception {
LOG.info("Opening summarization job.");
super.open(parameters);
client = new WebSocketSubscriptionClient(jp.getTimelyHostname(), jp.getTimelyHttpsPort(), jp.getTimelyWssPort(),
jp.isClientAuth(), jp.isDoLogin(), jp.getTimelyUsername(), jp.getTimelyPassword(), jp.getKeyStoreFile(),
jp.getKeyStoreType(), jp.getKeyStorePass(), jp.getTrustStoreFile(), jp.getTrustStoreType(),
jp.getTrustStorePass(), jp.isHostVerificationEnabled(), jp.getBufferSize());
}
@Override
public synchronized void close() throws Exception {
super.close();
if (null != client) {
client.close();
}
}
@Override
public void run(SourceContext<MetricResponse> ctx) throws Exception {
LOG.info("Running summarization job");
final SortedStringAccumulator dateTimeAccumulator = new SortedStringAccumulator();
this.getRuntimeContext().addAccumulator("source hourly count", dateTimeAccumulator);
final LongCounter sourceInputs = new LongCounter();
this.getRuntimeContext().addAccumulator("source input counter", sourceInputs);
final LongCounter inputProcessedInMainThread = new LongCounter();
this.getRuntimeContext().addAccumulator("input processed in main thread", inputProcessedInMainThread);
// Use only one thread to ensure that time is still ordered
final ExecutorService svc = Executors.newFixedThreadPool(1, new SubscriptionThreadFactory());
ClientHandler handler = new ClientHandler() {
@Override
public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(String.class, new MessageHandler.Whole<String>() {
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH");
private long lastWatermarkTime = 0L;
@Override
public void onMessage(final String message) {
LOG.info("Message received on Websocket session {}, length: {}", session.getId(),
message.length());
try {
// Deserialize in this thread
final MetricResponses responses = JsonSerializer.getObjectMapper().readValue(message,
MetricResponses.class);
// Process in the other
final Runnable r = () -> {
responses.getResponses().forEach(response -> {
if (response.isComplete()) {
LOG.info("Received last message.");
ctx.emitWatermark(new Watermark(Long.MAX_VALUE));
subscriptionsRemaining.getAndDecrement();
return;
}
LOG.trace("Received metric: {}", response);
long time = response.getTimestamp();
ctx.collectWithTimestamp(response, time);
dateTimeAccumulator.add(formatter.format(new Date(response.getTimestamp())));
sourceInputs.add(1);
// Emit a watermark every second of event
// time
if (lastWatermarkTime == 0) {
lastWatermarkTime = time;
} else if ((time - lastWatermarkTime) > window) {
lastWatermarkTime = time;
ctx.emitWatermark(new Watermark(time - 1));
}
});
};
try {
svc.execute(r);
} catch (RejectedExecutionException e) {
// Run it in this thread
r.run();
inputProcessedInMainThread.add(1);
}
} catch (IOException e) {
LOG.error("Error deserializing metric response, closing", e);
try {
close();
} catch (Exception e1) {
LOG.error("Error closing client", e1);
}
}
}
});
}
@Override
public void onClose(Session session, CloseReason reason) {
super.onClose(session, reason);
try {
close();
} catch (Exception e1) {
LOG.error("Error closing client", e1);
}
// Signal done sending data
ctx.emitWatermark(new Watermark(Long.MAX_VALUE));
}
@Override
public void onError(Session session, Throwable error) {
super.onError(session, error);
try {
close();
} catch (Exception e1) {
LOG.error("Error closing client", e1);
}
}
};
try {
client.open(handler);
for (String m : metrics) {
LOG.info("Adding subscription for {}", m);
client.addSubscription(m, null, start, end, 5000);
}
while (!client.isClosed() && subscriptionsRemaining.get() > 0) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
LOG.error("Sleep interrupted.");
}
}
} finally {
svc.shutdown();
}
}
@Override
public void cancel() {
LOG.info("Cancelling subscription source.");
try {
close();
} catch (Exception e) {
LOG.error("Error closing web socket client");
}
}
@Override
public void stop() {
LOG.info("Stopping subscription source.");
try {
close();
} catch (Exception e) {
LOG.error("Error closing web socket client");
}
}
}
@@ -0,0 +1,108 @@
package timely.analytics.flink;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.flink.api.common.accumulators.LongCounter;
import org.apache.flink.api.common.functions.FoldFunction;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.SocketClientSink;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.response.MetricResponse;
public class SummarizationJob {
private static final Logger LOG = LoggerFactory.getLogger(SummarizationJob.class);
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: timely.analytics.flink.SummarizationJob <path_to_properties_file>");
System.exit(1);
}
LOG.info("Loading properties from: {}", args[0]);
ParameterTool params = ParameterTool.fromPropertiesFile(args[0]);
final SummarizationJobParameters jp = new SummarizationJobParameters(params);
LOG.info("Properties: {}", jp);
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// @formatter:off
env.getConfig().setGlobalJobParameters(jp);
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.addSource(new SubscriptionSource(jp), "Timely Metrics Subscription")
// partition by unique metric
.keyBy(new MetricKeySelector())
.window(TumblingEventTimeWindows.of(jp.getSummarizationInterval()))
.fold(new MetricHistogram(), new FoldFunction<MetricResponse, MetricHistogram>() {
private final Logger LOG = LoggerFactory
.getLogger("timely.analytics.SummarizationJob.FoldFunction@");
private static final long serialVersionUID = 1L;
@Override
public MetricHistogram fold(MetricHistogram histo, MetricResponse metric) throws Exception {
if (!histo.isInitialized()) {
histo.initialize(metric.getMetric(), metric.getTags());
} else {
if (!histo.getMetric().equals(metric.getMetric())) {
throw new RuntimeException("Metric name does not match - partitioning problem");
}
if (!histo.getTags().equals(metric.getTags())) {
throw new RuntimeException("Tags don't not match - partitioning problem");
}
}
LOG.trace("Updating histogram for {} with value {} at {} for metric {}", histo.getMetric()
+ histo.getTags(), metric.getValue(), metric.getTimestamp(), metric.getMetric()
+ metric.getTags());
histo.update(metric.getValue(), metric.getTimestamp());
return histo;
}
})
.name("Metric Windows")
.addSink(
new SocketClientSink<MetricHistogram>(jp.getTimelyHostname(), jp.getTimelyTcpPort(),
new MetricHistogram(), 2, true) {
private final Logger LOG = LoggerFactory
.getLogger("timely.analytics.SummarizationJob.SocketClientSink");
private static final long serialVersionUID = 1L;
private final LongCounter sinkCounter = new LongCounter();
private final SortedStringAccumulator dateTimeAccumulator = new SortedStringAccumulator();
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH");
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
this.getRuntimeContext().addAccumulator("sink counter", this.sinkCounter);
this.getRuntimeContext().addAccumulator("sink hourly count", dateTimeAccumulator);
LOG.info("Sink opened");
}
@Override
public void invoke(MetricHistogram histo) throws Exception {
histo.done();
super.invoke(histo);
this.sinkCounter.add(1);
this.dateTimeAccumulator.add(formatter.format(new Date(histo.getTimestamp())));
}
@Override
public void close() throws Exception {
super.close();
LOG.info("Sink closed");
}
}).name("Timely Metric Sink");
// @formatter:on
env.execute("Metric Summarization");
// Looks like there is no way to stop Flink job if underlying web socket
// is closed.
}
}
@@ -0,0 +1,215 @@
package timely.analytics.flink;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.flink.api.common.ExecutionConfig.GlobalJobParameters;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.windowing.time.Time;
public class SummarizationJobParameters extends GlobalJobParameters implements Serializable {
private static final long serialVersionUID = 1L;
private final String timelyHostname;
private final int timelyTcpPort;
private final int timelyHttpsPort;
private final int timelyWssPort;
private final boolean clientAuth;
private final boolean doLogin;
private final String timelyUsername;
private final String timelyPassword;
private final String keyStoreFile;
private final String keyStoreType;
private final String keyStorePass;
private final String trustStoreFile;
private final String trustStoreType;
private final String trustStorePass;
private final boolean hostVerificationEnabled;
private final int bufferSize;
private final String[] metrics;
private final long startTime;
private final long endTime;
private final String interval;
private final String intervalUnits;
public SummarizationJobParameters(ParameterTool params) {
timelyHostname = params.getRequired("timelyHostname");
timelyTcpPort = params.getInt("timelyTcpPort", 4241);
timelyHttpsPort = params.getInt("timelyHttpsPort", 4242);
timelyWssPort = params.getInt("timelyWssPort", 4243);
clientAuth = params.getBoolean("clientAuth", false);
doLogin = params.getBoolean("doLogin", false);
timelyUsername = params.get("timelyUsername", null);
timelyPassword = params.get("timelyPassword", null);
keyStoreFile = params.getRequired("keyStoreFile");
keyStoreType = params.get("keyStoreType", "JKS");
keyStorePass = params.getRequired("keyStorePass");
trustStoreFile = params.getRequired("trustStoreFile");
trustStoreType = params.get("trustStoreType", "JKS");
trustStorePass = params.getRequired("trustStorePass");
hostVerificationEnabled = params.getBoolean("hostVerificationEnabled", true);
bufferSize = params.getInt("bufferSize", 10485760);
String metricNames = params.getRequired("metrics");
if (null != metricNames) {
metrics = metricNames.split(",");
} else {
metrics = null;
}
startTime = params.getLong("startTime", 0L);
endTime = params.getLong("endTime", 0L);
interval = params.getRequired("interval");
intervalUnits = params.getRequired("intervalUnits");
}
public SummarizationJobParameters(Configuration conf) {
System.out.println("Creating job parameters from configuration: " + conf);
timelyHostname = conf.getString("timelyHostname", null);
timelyTcpPort = conf.getInteger("timelyTcpPort", 4241);
timelyHttpsPort = conf.getInteger("timelyHttpsPort", 4242);
timelyWssPort = conf.getInteger("timelyWssPort", 4243);
clientAuth = conf.getBoolean("clientAuth", false);
doLogin = conf.getBoolean("doLogin", false);
timelyUsername = conf.getString("timelyUsername", null);
timelyPassword = conf.getString("timelyPassword", null);
keyStoreFile = conf.getString("keyStoreFile", null);
keyStoreType = conf.getString("keyStoreType", "JKS");
keyStorePass = conf.getString("keyStorePass", null);
trustStoreFile = conf.getString("trustStoreFile", null);
trustStoreType = conf.getString("trustStoreType", "JKS");
trustStorePass = conf.getString("trustStorePass", null);
hostVerificationEnabled = conf.getBoolean("hostVerificationEnabled", true);
bufferSize = conf.getInteger("bufferSize", 10485760);
String metricNames = conf.getString("metrics", null);
if (null != metricNames) {
metrics = metricNames.split(",");
} else {
metrics = null;
}
startTime = conf.getLong("startTime", 0L);
endTime = conf.getLong("endTime", 0L);
interval = conf.getString("interval", null);
intervalUnits = conf.getString("intervalUnits", null);
}
public Time getSummarizationInterval() {
return Time.of(Long.parseLong(interval), TimeUnit.valueOf(intervalUnits));
}
public String getTimelyHostname() {
return timelyHostname;
}
public int getTimelyTcpPort() {
return timelyTcpPort;
}
public int getTimelyHttpsPort() {
return timelyHttpsPort;
}
public int getTimelyWssPort() {
return timelyWssPort;
}
public boolean isClientAuth() {
return clientAuth;
}
public boolean isDoLogin() {
return doLogin;
}
public String getTimelyUsername() {
return timelyUsername;
}
public String getTimelyPassword() {
return timelyPassword;
}
public String getKeyStoreFile() {
return keyStoreFile;
}
public String getKeyStoreType() {
return keyStoreType;
}
public String getKeyStorePass() {
return keyStorePass;
}
public String getTrustStoreFile() {
return trustStoreFile;
}
public String getTrustStoreType() {
return trustStoreType;
}
public String getTrustStorePass() {
return trustStorePass;
}
public boolean isHostVerificationEnabled() {
return hostVerificationEnabled;
}
public int getBufferSize() {
return bufferSize;
}
public String[] getMetrics() {
return metrics;
}
public long getStartTime() {
return startTime;
}
public long getEndTime() {
return endTime;
}
public String getInterval() {
return interval;
}
public String getIntervalUnits() {
return intervalUnits;
}
@Override
public Map<String, String> toMap() {
Map<String, String> m = new HashMap<>();
m.put("timelyHostname", this.timelyHostname);
m.put("timelyHttpsPort", Integer.toString(this.timelyHttpsPort));
m.put("timelyWssPort", Integer.toString(this.timelyWssPort));
m.put("doLogin", Boolean.toString(this.doLogin));
m.put("timelyUsername", this.timelyUsername);
m.put("timelyPassword", this.timelyPassword);
m.put("keyStoreFile", this.keyStoreFile);
m.put("keyStoreType", this.keyStoreType);
m.put("keyStorePass", this.keyStorePass);
m.put("trustStoreFile", this.trustStoreFile);
m.put("trustStoreType", this.trustStoreType);
m.put("trustStorePass", this.trustStorePass);
m.put("hostVerificationEnabled", Boolean.toString(this.hostVerificationEnabled));
m.put("bufferSize", Integer.toString(this.bufferSize));
m.put("metrics", String.join(",", this.metrics));
m.put("startTime", Long.toString(this.startTime));
m.put("endTime", Long.toString(this.endTime));
m.put("interval", this.interval);
m.put("intervalUnits", this.intervalUnits);
return m;
}
@Override
public String toString() {
return toMap().toString();
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="timely.analytics.flink.MetricHistogram" />
<Method name="serialize" />
<Bug pattern="DM_DEFAULT_ENCODING" />
</Match>
<Match>
<Class name="timely.analytics.flink.SortedStringAccumulator" />
<Method name="clone" />
<Bug pattern="CN_IDIOM_NO_SUPER_CALL" />
</Match>
<Match>
<Class name="timely.analytics.flink.SummarizationJobParameters" />
<Method name="getMetrics" />
<Bug pattern="EI_EXPOSE_REP" />
</Match>
</FindBugsFilter>
@@ -0,0 +1,106 @@
package timely.analytics.flink;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import timely.model.Metric;
import timely.model.Tag;
import timely.model.parse.MetricParser;
public class MetricHistogramTest {
private MetricHistogram m = null;
private long timestamp = System.currentTimeMillis();
@Before
public void createMetric() throws Exception {
m = new MetricHistogram();
for (double d = 1.0D; d <= 100.0; d += 1.0) {
timestamp += 1000;
m.update(d, timestamp);
}
m.toString();
Assert.assertEquals(timestamp, m.getTimestamp());
}
@Test
public void testMin() throws Exception {
Assert.assertEquals(1.0D, m.min(), 0.0D);
}
@Test
public void testMax() throws Exception {
Assert.assertEquals(100.0D, m.max(), 0.0D);
}
@Test
public void testAvg() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
Assert.assertEquals((sum / 100.0D), m.avg(), 0.0D);
}
@Test
public void testCount() throws Exception {
Assert.assertEquals(100.0D, m.count(), 0.0D);
}
@Test
public void test50thPercentile() throws Exception {
Assert.assertEquals(50.0D, m.getPercentile(50), 0.0D);
}
@Test
public void test75thPercentile() throws Exception {
Assert.assertEquals(75.0D, m.getPercentile(75), 0.0D);
}
@Test
public void test90thPercentile() throws Exception {
Assert.assertEquals(90.0D, m.getPercentile(90), 0.0D);
}
@Test
public void test99thPercentile() throws Exception {
Assert.assertEquals(99.0D, m.getPercentile(99), 0.0D);
}
@Test
public void testSerialization() throws Exception {
MetricParser metricParser = new MetricParser();
Tag t1 = new Tag("tag1=value1");
Tag t2 = new Tag("tag2=value2");
Tag avg = new Tag("sample=avg");
Tag min = new Tag("sample=min");
Tag max = new Tag("sample=max");
Tag sum = new Tag("sample=sum");
Tag count = new Tag("sample=count");
Tag p50 = new Tag("sample=50p");
Tag p75 = new Tag("sample=75p");
Tag p90 = new Tag("sample=90p");
Tag p99 = new Tag("sample=99p");
List<Tag> tags = new ArrayList<>();
tags.add(t1);
tags.add(t2);
m.initialize("sys.cpu.user", tags);
byte[] bytes = m.serialize(m);
String puts = new String(bytes);
for (String put : puts.split("\n")) {
Metric metric = metricParser.parse(put);
Assert.assertEquals("sys.cpu.user_summarized", metric.getName());
metric.getTags().forEach(t -> {
Assert.assertTrue(
t.equals(t1) || t.equals(t2) || t.equals(avg) || t.equals(min) || t.equals(max) || t.equals(sum)
|| t.equals(count) || t.equals(p50) || t.equals(p75) || t.equals(p90) || t.equals(p99));
});
}
}
}
File diff suppressed because it is too large. Load diff
+11
View File
@@ -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
+21
View File
@@ -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 $*
+21
View File
@@ -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 $*
+11
View File
@@ -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
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
TCNATIVE_SUFFIX="jnilib"
else
THIS_SCRIPT=`readlink -f $0`
TCNATIVE_SUFFIX="so"
fi
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.${TCNATIVE_SUFFIX}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-transport-native-epoll*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
JVM_ARGS="-Xmx1G -Xms1G -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}"
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
+13
View File
@@ -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>
+29
View File
@@ -0,0 +1,29 @@
<?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>
<Logger name="timely" additivity="false">
<AppenderRef ref="Console" level="debug" />
</Logger>
</Loggers>
</Configuration>
+71
View File
@@ -0,0 +1,71 @@
<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:list>
<beans:bean class="timely.auth.TimelyUser">
<beans:constructor-arg name="subjectDn">
<beans:value>CN=example.com</beans:value>
</beans:constructor-arg>
<beans:constructor-arg name="auths">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</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="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
</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>
<util:list id="requiredRoles" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
<util:list id="requiredAuths" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
</beans:beans>
+128
View File
@@ -0,0 +1,128 @@
timely-balancer:
zooKeeper:
servers: localhost:2181
timeout: 30s
checkServerHealthInterval: 10000
serverFailuresBeforeDown: 6
serverSuccessesBeforeUp: 1
assignmentFile: /timely/balancer/assignments.csv
security:
session-max-age: 86400
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
serverSsl:
certificate-file:
key-file:
#key-password:
use-generated-keypair: true
trust-store-file:
use-openssl: true
#use-ciphers:
clientSsl:
keyFile:
keyType: pkcs12
#keyPassword:
trustStoreFile:
trustStoreType: jks
#trustStorePassword:
cache:
enabled: false
metric-age-off-hours:
default: 26
nonCachedMetrics:
server:
ip: 127.0.0.1
tcpPort: 4242
numTcpPools: 1
tcpBufferSize: 100
tcpClientPool:
minIdlePerKey: 0
maxIdlePerKey: 40
maxTotalPerKey: 40
maxTotal: -1
lifo: true
fairness: false
maxWaitMillis: -1
minEvictableIdleTimeMillis: 1800000
evictorShutdownTimeoutMillis: 10000
softMinEvictableIdleTimeMillis: -1
numTestsPerEvictionRun: 3
timeBetweenEvictionRunsMillis: -1
blockWhenExhausted: true
jmxEnabled: true
testOnBorrow: false
testOnReturn: false
udpPort: 4245
udpClientPool:
minIdlePerKey: 0
maxIdlePerKey: 40
maxTotalPerKey: 40
maxTotal: -1
lifo: true
fairness: false
maxWaitMillis: -1
minEvictableIdleTimeMillis: 1800000
evictorShutdownTimeoutMillis: 10000
softMinEvictableIdleTimeMillis: -1
numTestsPerEvictionRun: 3
timeBetweenEvictionRunsMillis: -1
blockWhenExhausted: true
jmxEnabled: true
testOnBorrow: false
testOnReturn: false
http:
host: localhost
ip: 127.0.0.1
port: 4243
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
httpClientPool:
minIdlePerKey: 0
maxIdlePerKey: 40
maxTotalPerKey: 40
maxTotal: -1
lifo: true
fairness: false
maxWaitMillis: -1
minEvictableIdleTimeMillis: 1800000
evictorShutdownTimeoutMillis: 10000
softMinEvictableIdleTimeMillis: -1
numTestsPerEvictionRun: 3
timeBetweenEvictionRunsMillis: -1
blockWhenExhausted: true
jmxEnabled: true
testOnBorrow: false
testOnReturn: false
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
wsClientPool:
minIdlePerKey: 0
maxIdlePerKey: 40
maxTotalPerKey: 40
maxTotal: -1
lifo: true
fairness: false
maxWaitMillis: -1
minEvictableIdleTimeMillis: 1800000
evictorShutdownTimeoutMillis: 10000
softMinEvictableIdleTimeMillis: -1
numTestsPerEvictionRun: 3
timeBetweenEvictionRunsMillis: -1
blockWhenExhausted: true
jmxEnabled: true
testOnBorrow: false
testOnReturn: false
+327
View File
@@ -0,0 +1,327 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>1.2.1-SNAPSHOT</version>
</parent>
<artifactId>timely-balancer</artifactId>
<dependencies>
<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>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</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>net.sourceforge.javacsv</groupId>
<artifactId>javacsv</artifactId>
</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.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</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-minicluster</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</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.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-client</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-server</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<scope>runtime</scope>
</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>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.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<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>
</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>
<goals>
<goal>add-source</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<sources>
<source>src/main/generated-java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<extensions>
<!-- Use os-maven-plugin to initialize the "os.detected" properties -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
</extension>
</extensions>
</build>
<profiles>
<profile>
<id>native-epoll-linux</id>
<activation>
<os>
<family>!mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${version.netty}</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
</profile>
<profile>
<id>native-epoll</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${version.netty}</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
+54
View File
@@ -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,133 @@
package timely.balancer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;
import com.google.common.util.concurrent.AtomicDouble;
public class ArrivalRate {
private AtomicLong shortTermArrivals = new AtomicLong(0);
private AtomicDouble shortTermRate = new AtomicDouble(0);
private AtomicLong shortTermLastReset = new AtomicLong(0);
private AtomicLong mediumTermArrivals = new AtomicLong(0);
private AtomicDouble mediumTermRate = new AtomicDouble(0);
private AtomicLong mediumTermLastReset = new AtomicLong(0);
private AtomicLong longTermArrivals = new AtomicLong(0);
private AtomicDouble longTermRate = new AtomicDouble(0);
private AtomicLong longTermLastReset = new AtomicLong(0);
public ArrivalRate(Timer timer) {
long now = System.currentTimeMillis();
shortTermLastReset.set(now);
mediumTermLastReset.set(now);
longTermLastReset.set(now);
// 2 minute
timer.schedule(new TimerTask() {
@Override
public void run() {
resetShort();
}
}, 120000, 120000);
// 5 minute
timer.schedule(new TimerTask() {
@Override
public void run() {
resetMedium();
}
}, 300000, 300000);
// 15 minute
timer.schedule(new TimerTask() {
@Override
public void run() {
resetLong();
}
}, 900000, 900000);
}
private void resetShort() {
long now = System.currentTimeMillis();
long lastReset = shortTermLastReset.get();
if (now == lastReset) {
shortTermRate.set(0);
} else {
shortTermRate.set(shortTermArrivals.get() / (((double) (now - lastReset)) / 1000));
}
shortTermArrivals.set(0);
shortTermLastReset.set(now);
}
private void resetMedium() {
long now = System.currentTimeMillis();
long lastReset = mediumTermLastReset.get();
if (now == lastReset) {
mediumTermRate.set(0);
} else {
mediumTermRate.set(mediumTermArrivals.get() / (((double) (now - lastReset)) / 1000));
}
mediumTermArrivals.set(0);
mediumTermLastReset.set(now);
}
private void resetLong() {
long now = System.currentTimeMillis();
long lastReset = longTermLastReset.get();
if (now == lastReset) {
longTermRate.set(0);
} else {
longTermRate.set(longTermArrivals.get() / (((double) (now - lastReset)) / 1000));
}
longTermArrivals.set(0);
longTermLastReset.set(now);
}
public void arrived() {
shortTermArrivals.incrementAndGet();
mediumTermArrivals.incrementAndGet();
longTermArrivals.incrementAndGet();
}
private double getShortRate(long now) {
double rate = shortTermRate.get();
long lastReset = shortTermLastReset.get();
if (rate > 0 || now == lastReset) {
return rate;
} else {
return shortTermArrivals.get() / (((double) (now - lastReset)) / 1000);
}
}
private double getMediumRate(long now) {
double rate = mediumTermRate.get();
long lastReset = mediumTermLastReset.get();
if (rate > 0 || now == lastReset) {
return rate;
} else {
return mediumTermArrivals.get() / (((double) (now - lastReset)) / 1000);
}
}
private double getLongRate(long now) {
double rate = longTermRate.get();
long lastReset = longTermLastReset.get();
if (rate > 0 || now == lastReset) {
return rate;
} else {
return longTermArrivals.get() / (((double) (now - lastReset)) / 1000);
}
}
public double getRate() {
long now = System.currentTimeMillis();
return 0.10 * getLongRate(now) + 0.10 * getMediumRate(now) + 0.80 * getShortRate(now);
}
}
@@ -0,0 +1,577 @@
package timely.balancer;
import static timely.Server.SERVICE_DISCOVERY_PATH;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
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;
import javax.net.ssl.SSLContext;
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.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryForever;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import timely.auth.AuthCache;
import timely.balancer.configuration.BalancerConfiguration;
import timely.balancer.configuration.ClientSsl;
import timely.balancer.configuration.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.configuration.ServerSsl;
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.WebSocketFullRequestHandler;
import timely.netty.websocket.WebSocketRequestDecoder;
import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
public class Balancer {
final public static String LEADER_LATCH_PATH = "/timely/balancer/leader";
final public static String ASSIGNMENTS_LOCK_PATH = "/timely/balancer/assignments/lock";
final public static String ASSIGNMENTS_LAST_UPDATED_PATH = "/timely/balancer/assignments/lastUpdated";
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 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<>();
private CuratorFramework curatorFramework;
protected volatile boolean shutdown = false;
private static final int DEFAULT_EVENT_LOOP_THREADS;
private String[] zkPaths = new String[] { LEADER_LATCH_PATH, ASSIGNMENTS_LAST_UPDATED_PATH, ASSIGNMENTS_LOCK_PATH,
SERVICE_DISCOVERY_PATH, NON_CACHED_METRICS, NON_CACHED_METRICS_LOCK_PATH };
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
public Balancer(BalancerConfiguration balancerConf) throws Exception {
this.balancerConfig = balancerConf;
}
protected static ConfigurableApplicationContext initializeConfiguration(String[] args) {
return new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).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 = balancerConfig.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();
}
if (curatorFramework != null) {
curatorFramework.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(BalancerConfiguration config) throws Exception {
ServerSsl sslCfg = config.getSecurity().getServerSsl();
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 {
ClientSsl ssl = config.getSecurity().getClientSsl();
return HttpClient.getSSLContext(ssl.getTrustStoreFile(), ssl.getTrustStoreType(), ssl.getTrustStorePassword(),
ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword());
}
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
for (String s : paths) {
try {
Stat stat = curatorFramework.checkExists().forPath(s);
if (stat == null) {
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
.forPath(s);
}
} catch (Exception e) {
LOG.info(e.getMessage());
}
}
}
public void run() throws Exception {
// start curator framework
RetryPolicy retryPolicy = new RetryForever(1000);
curatorFramework = CuratorFrameworkFactory.newClient(balancerConfig.getZooKeeper().getServers(), 30000, 1000,
retryPolicy);
curatorFramework.start();
ensureZkPaths(curatorFramework, zkPaths);
AuthCache.setSessionMaxAge(balancerConfig.getSecurity());
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());
List<TcpClientPool> tcpClientPools = new ArrayList<>();
for (int x = 0; x < balancerConfig.getServer().getNumTcpPools(); x++) {
tcpClientPools.add(new TcpClientPool(this.balancerConfig));
}
HealthChecker healthChecker = new HealthChecker(this.balancerConfig, tcpClientPools.get(0));
this.metricResolver = new BalancedMetricResolver(curatorFramework, this.balancerConfig, healthChecker);
final ServerBootstrap tcpServer = new ServerBootstrap();
tcpServer.group(tcpBossGroup, tcpWorkerGroup);
tcpServer.channel(channelClass);
tcpServer.handler(new LoggingHandler());
tcpServer.childHandler(setupTcpChannel(metricResolver, tcpClientPools));
tcpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
tcpServer.option(ChannelOption.SO_BACKLOG, 128);
tcpServer.option(ChannelOption.SO_KEEPALIVE, true);
final int tcpPort = balancerConfig.getServer().getTcpPort();
final String tcpIp = balancerConfig.getServer().getIp();
tcpChannelHandle = tcpServer.bind(tcpIp, tcpPort).sync().channel();
final String tcpAddress = ((InetSocketAddress) tcpChannelHandle.localAddress()).getAddress().getHostAddress();
final int httpPort = balancerConfig.getHttp().getPort();
final String httpIp = balancerConfig.getHttp().getIp();
SslContext sslCtx = createSSLContext(balancerConfig);
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(balancerConfig.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.getSecurity(), balancerConfig.getHttp(),
clientSSLContext);
httpServer.childHandler(setupHttpChannel(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 = balancerConfig.getWebsocket().getPort();
final String wsIp = balancerConfig.getWebsocket().getIp();
final ServerBootstrap wsServer = new ServerBootstrap();
wsServer.group(wsBossGroup, wsWorkerGroup);
wsServer.channel(channelClass);
wsServer.handler(new LoggingHandler());
WsClientPool wsClientPool = new WsClientPool(balancerConfig, clientSSLContext);
wsServer.childHandler(setupWSChannel(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 = balancerConfig.getServer().getUdpPort();
final String udpIp = balancerConfig.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);
BalancerConfiguration balancerConf = applicationContext.getBean(BalancerConfiguration.class);
Balancer b = new Balancer(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(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(balancerConfig.getHttp(), 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(65536));
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
ch.pipeline().addLast("queryDecoder", new timely.netty.http.HttpRequestDecoder(
balancerConfig.getSecurity(), balancerConfig.getHttp()));
ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler());
ch.pipeline().addLast("login",
new X509LoginRequestHandler(balancerConfig.getSecurity(), balancerConfig.getHttp()));
ch.pipeline().addLast("httpRelay", new HttpRelayHandler(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(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new UdpDecoder());
ch.pipeline().addLast("udpRelayHandler", new UdpRelayHandler(metricResolver, udpClientPool));
}
};
}
protected ChannelHandler setupTcpChannel(MetricResolver metricResolver, List<TcpClientPool> tcpClientPools) {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new TcpDecoder());
ch.pipeline().addLast("tcpRelayHandler", new TcpRelayHandler(metricResolver, tcpClientPools));
ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
}
};
}
protected ChannelHandler setupWSChannel(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(65536));
ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
ch.pipeline().addLast("idle-handler",
new IdleStateHandler(balancerConfig.getWebsocket().getTimeout(), 0, 0));
ch.pipeline().addLast("ws-protocol",
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true));
ch.pipeline().addLast("wsDecoder", new WebSocketRequestDecoder(balancerConfig.getSecurity()));
ch.pipeline().addLast("httpRelay", new WsRelayHandler(balancerConfig, metricResolver, wsClientPool));
ch.pipeline().addLast("error", new WSTimelyExceptionHandler());
}
};
}
}
@@ -0,0 +1,123 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import timely.configuration.Cache;
@Validated
@Component
@ConfigurationProperties(prefix = "timely-balancer")
public class BalancerConfiguration {
@Valid
@NestedConfigurationProperty
private BalancerServer server = new BalancerServer();
@Valid
@NestedConfigurationProperty
private BalancerHttp http = new BalancerHttp();
@Valid
@NestedConfigurationProperty
private BalancerWebsocket websocket = new BalancerWebsocket();
@Valid
@NestedConfigurationProperty
private ZooKeeper zooKeeper = new ZooKeeper();
@Valid
@NestedConfigurationProperty
private BalancerSecurity security = new BalancerSecurity();
@Valid
@NestedConfigurationProperty
private Cache cache = new Cache();
private boolean loginRequired = false;
private String assignmentFile;
private Double controlBandPercentage = 0.05;
private long checkServerHealthInterval = 10000;
private int serverFailuresBeforeDown = 3;
private int serverSuccessesBeforeUp = 3;
public ZooKeeper getZooKeeper() {
return zooKeeper;
}
public void setLoginRequired(boolean loginRequired) {
this.loginRequired = loginRequired;
}
public boolean isLoginRequired() {
return loginRequired;
}
public String getAssignmentFile() {
return assignmentFile;
}
public void setAssignmentFile(String assignmentFile) {
this.assignmentFile = assignmentFile;
}
public void setControlBandPercentage(Double controlBandPercentage) {
this.controlBandPercentage = controlBandPercentage;
}
public Double getControlBandPercentage() {
return controlBandPercentage;
}
public BalancerSecurity getSecurity() {
return security;
}
public BalancerServer getServer() {
return server;
}
public BalancerHttp getHttp() {
return http;
}
public BalancerWebsocket getWebsocket() {
return websocket;
}
public Cache getCache() {
return cache;
}
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;
}
}
@@ -0,0 +1,18 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import timely.configuration.Http;
public class BalancerHttp extends Http {
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfig httpClientPool = new GenericKeyedObjectPoolConfig();
public GenericKeyedObjectPoolConfig getHttpClientPool() {
return httpClientPool;
}
}
@@ -0,0 +1,17 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import timely.configuration.Security;
public class BalancerSecurity extends Security {
@Valid
@NestedConfigurationProperty
private ClientSsl clientSsl = new ClientSsl();
public ClientSsl getClientSsl() {
return clientSsl;
}
}
@@ -0,0 +1,46 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import timely.configuration.Server;
public class BalancerServer extends Server {
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfig udpClientPool = new GenericKeyedObjectPoolConfig();
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfig tcpClientPool = new GenericKeyedObjectPoolConfig();
private int numTcpPools = 1;
private int tcpBufferSize = -1;
public GenericKeyedObjectPoolConfig getUdpClientPool() {
return udpClientPool;
}
public GenericKeyedObjectPoolConfig getTcpClientPool() {
return tcpClientPool;
}
public int getNumTcpPools() {
return numTcpPools;
}
public void setNumTcpPools(int numTcpPools) {
this.numTcpPools = numTcpPools;
}
public int getTcpBufferSize() {
return tcpBufferSize;
}
public void setTcpBufferSize(int tcpBufferSize) {
this.tcpBufferSize = tcpBufferSize;
}
}
@@ -0,0 +1,18 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import timely.configuration.Websocket;
public class BalancerWebsocket extends Websocket {
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfig wsClientPool = new GenericKeyedObjectPoolConfig();
public GenericKeyedObjectPoolConfig getWsClientPool() {
return wsClientPool;
}
}
@@ -0,0 +1,77 @@
package timely.balancer.configuration;
public class ClientSsl {
private String keyFile;
private String keyType;
private String keyPassword;
private String trustStoreFile;
private String trustStoreType;
private String trustStorePassword;
private boolean useClientCert = true;
private boolean hostVerificationEnabled = true;
public String getKeyFile() {
return keyFile;
}
public void setKeyFile(String keyFile) {
this.keyFile = keyFile;
}
public String getKeyType() {
return keyType;
}
public void setKeyType(String keyType) {
this.keyType = keyType;
}
public String getKeyPassword() {
return keyPassword;
}
public void setKeyPassword(String keyPassword) {
this.keyPassword = keyPassword;
}
public String getTrustStoreFile() {
return trustStoreFile;
}
public void setTrustStoreFile(String trustStoreFile) {
this.trustStoreFile = trustStoreFile;
}
public String getTrustStoreType() {
return trustStoreType;
}
public void setTrustStoreType(String trustStoreType) {
this.trustStoreType = trustStoreType;
}
public String getTrustStorePassword() {
return trustStorePassword;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public void setUseClientCert(boolean useClientCert) {
this.useClientCert = useClientCert;
}
public boolean isUseClientCert() {
return useClientCert;
}
public void setHostVerificationEnabled(boolean hostVerificationEnabled) {
this.hostVerificationEnabled = hostVerificationEnabled;
}
public boolean isHostVerificationEnabled() {
return hostVerificationEnabled;
}
}
@@ -0,0 +1,7 @@
package timely.balancer.configuration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackageClasses = { SpringBootstrap.class })
public class SpringBootstrap {
}
@@ -0,0 +1,14 @@
package timely.balancer.configuration;
public class ZooKeeper {
private String servers;
public String getServers() {
return servers;
}
public void setServers(String servers) {
this.servers = servers;
}
}
@@ -0,0 +1,189 @@
package timely.balancer.connection;
import java.util.Timer;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.balancer.ArrivalRate;
import timely.balancer.configuration.BalancerConfiguration;
public class TimelyBalancedHost {
private static final Logger LOG = LoggerFactory.getLogger(TimelyBalancedHost.class);
static private Timer arrivalRateTimer = new Timer("ArrivalRateTimerTBH", true);
private String host;
private int tcpPort;
private int httpPort;
private int wsPort;
private int udpPort;
private boolean isUp = true;
private BalancerConfiguration balancerConfig;
private int failures = 0;
private int successes = 0;
private ArrivalRate arrivalRate = new ArrivalRate(arrivalRateTimer);
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 setBalancerConfig(BalancerConfiguration balancerConfig) {
this.balancerConfig = balancerConfig;
}
synchronized public boolean isUp() {
return isUp;
}
public void reportSuccess() {
int serverSuccessesBeforeUp = balancerConfig.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 = balancerConfig.getServerFailuresBeforeDown();
String h = host;
int p = tcpPort;
synchronized (this) {
if (LOG.isTraceEnabled()) {
LOG.trace("failure reported host:{} port:{} isUp:{} failures:{} serverFailuresBeforeUp:{}", 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 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();
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(host);
builder.append(tcpPort);
builder.append(httpPort);
builder.append(wsPort);
builder.append(udpPort);
return builder.toHashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof TimelyBalancedHost) {
TimelyBalancedHost other = (TimelyBalancedHost) o;
EqualsBuilder builder = new EqualsBuilder();
builder.append(host, other.host);
builder.append(tcpPort, other.tcpPort);
builder.append(httpPort, other.httpPort);
builder.append(wsPort, other.wsPort);
builder.append(udpPort, other.udpPort);
return builder.isEquals();
} else {
return false;
}
}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
builder.append(host);
builder.append(tcpPort);
builder.append(httpPort);
builder.append(wsPort);
builder.append(udpPort);
builder.append(isUp);
return builder.toString();
}
}
@@ -0,0 +1,48 @@
package timely.balancer.connection.http;
import javax.net.ssl.SSLContext;
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.CloseableHttpClient;
import timely.balancer.configuration.BalancerSecurity;
import timely.balancer.connection.TimelyBalancedHost;
public class HttpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, CloseableHttpClient> {
private final SSLContext sslContext;
private final BalancerSecurity security;
public HttpClientFactory(BalancerSecurity security, SSLContext sslContext) {
this.security = security;
this.sslContext = sslContext;
}
@Override
public PooledObject<CloseableHttpClient> makeObject(TimelyBalancedHost k) throws Exception {
// disable cookie management because we are sharing connections
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, null,
security.getClientSsl().isHostVerificationEnabled(), security.getClientSsl().isUseClientCert()));
}
@Override
public void destroyObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
}
@Override
public boolean validateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) {
return true;
}
@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,20 @@
package timely.balancer.connection.http;
import javax.net.ssl.SSLContext;
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.configuration.BalancerHttp;
import timely.balancer.configuration.BalancerSecurity;
import timely.balancer.connection.TimelyBalancedHost;
public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, CloseableHttpClient> {
private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class);
public HttpClientPool(BalancerSecurity balancerSecurity, BalancerHttp http, SSLContext sslContext) {
super(new HttpClientFactory(balancerSecurity, sslContext), http.getHttpClientPool());
}
}
@@ -0,0 +1,56 @@
package timely.balancer.connection.tcp;
import java.io.IOException;
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;
public class TcpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, TcpClient> {
private int bufferSize;
public TcpClientFactory(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public PooledObject<TcpClient> makeObject(TimelyBalancedHost k) throws Exception {
return new DefaultPooledObject<>(new TcpClient(k.getHost(), k.getTcpPort(), bufferSize));
}
@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) {
return true;
}
@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.configuration.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.getServer().getTcpBufferSize()), config.getServer().getTcpClientPool());
}
}
@@ -0,0 +1,53 @@
package timely.balancer.connection.udp;
import java.io.IOException;
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;
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) {
return true;
}
@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.configuration.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.getServer().getUdpClientPool());
}
}
@@ -0,0 +1,52 @@
package timely.balancer.connection.ws;
import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import timely.balancer.configuration.BalancerConfiguration;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, WebSocketSubscriptionClient> {
private final SSLContext sslContext;
private BalancerConfiguration balancerConfig;
public WsClientFactory(BalancerConfiguration balancerConfig, SSLContext sslContext) {
this.balancerConfig = balancerConfig;
this.sslContext = sslContext;
}
@Override
public PooledObject<WebSocketSubscriptionClient> makeObject(TimelyBalancedHost k) throws Exception {
int bufferSize = balancerConfig.getWebsocket().getSubscriptionBatchSize() * 500;
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslContext, k.getHost(), k.getHttpPort(),
k.getWsPort(), balancerConfig.getSecurity().getClientSsl().isUseClientCert(),
balancerConfig.isLoginRequired(), "", "",
balancerConfig.getSecurity().getClientSsl().isHostVerificationEnabled(), 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) {
return true;
}
@Override
public void activateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
}
@Override
public void passivateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
}
}
@@ -0,0 +1,15 @@
package timely.balancer.connection.ws;
import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import timely.balancer.configuration.BalancerConfiguration;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
public class WsClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, WebSocketSubscriptionClient> {
public WsClientPool(BalancerConfiguration balancerConfig, SSLContext sslContext) {
super(new WsClientFactory(balancerConfig, sslContext), balancerConfig.getWebsocket().getWsClientPool());
}
}
@@ -0,0 +1,53 @@
package timely.balancer.healthcheck;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.balancer.configuration.BalancerConfiguration;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.tcp.TcpClientPool;
public class HealthChecker {
private static final Logger LOG = LoggerFactory.getLogger(HealthChecker.class);
private List<TimelyBalancedHost> timelyHosts = new ArrayList<>();
private Timer timer;
final private TCPServerHealthCheck tcpServerHealthCheck;
private TimerTask check = new TimerTask() {
@Override
public void run() {
synchronized (timelyHosts) {
for (TimelyBalancedHost h : timelyHosts) {
try {
if (tcpServerHealthCheck.isServerHealthy(h)) {
h.reportSuccess();
} else {
h.reportFailure();
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
h.reportFailure();
}
}
}
}
};
public HealthChecker(BalancerConfiguration config, TcpClientPool tcpClientPool) {
this.tcpServerHealthCheck = new TCPServerHealthCheck(tcpClientPool);
this.timer = new Timer("HealthCheckerThread", true);
this.timer.scheduleAtFixedRate(check, 0, config.getCheckServerHealthInterval());
}
public void setTimelyHosts(List<TimelyBalancedHost> timelyHosts) {
synchronized (timelyHosts) {
this.timelyHosts.clear();
this.timelyHosts.addAll(timelyHosts);
}
}
}
@@ -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,205 @@
package timely.balancer.netty.http;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import java.io.ByteArrayOutputStream;
import java.net.URLEncoder;
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 com.google.common.collect.Multimap;
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.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
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.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.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.AuthenticatedRequest;
import timely.api.request.MetricRequest;
import timely.api.request.timeseries.HttpRequest;
import timely.api.request.timeseries.QueryRequest;
import timely.api.response.TimelyException;
import timely.auth.util.HttpHeaderUtils;
import timely.auth.util.ProxiedEntityUtils;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.http.HttpClientPool;
import timely.balancer.resolver.MetricResolver;
import timely.netty.http.TimelyHttpHandler;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpRelayHandler.class);
private final HttpClientPool httpClientPool;
private MetricResolver metricResolver;
public HttpRelayHandler(MetricResolver metricResolver, HttpClientPool httpClientPool) {
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);
}
client = httpClientPool.borrowObject(k);
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(request.headers());
if (msg instanceof AuthenticatedRequest) {
AuthenticatedRequest authenticatedRequest = (AuthenticatedRequest) msg;
TimelyAuthenticationToken token = authenticatedRequest.getToken();
if (token != null) {
if (token.getClientCert() != null) {
ProxiedEntityUtils.addProxyHeaders(headers, token.getClientCert());
}
}
}
List<Header> relayedHeaderList = new ArrayList<>();
for (Map.Entry<String, String> h : headers.entries()) {
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 {
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(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;
}
}
@@ -0,0 +1,99 @@
package timely.balancer.netty.tcp;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
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.BalancedMetricResolver;
import timely.balancer.resolver.MetricResolver;
import timely.client.tcp.TcpClient;
import timely.netty.Constants;
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 BalancedMetricResolver metricResolver;
private List<TcpClientPool> tcpClientPools;
public TcpRelayHandler(MetricResolver metricResolver, List<TcpClientPool> tcpClientPools) {
this.metricResolver = (BalancedMetricResolver) metricResolver;
this.tcpClientPools = tcpClientPools;
}
private TcpClientPool tcpClientPool() {
int x = Math.toIntExact(Thread.currentThread().getId() % tcpClientPools.size());
return tcpClientPools.get(x);
}
@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");
} finally {
if (keyClientPair != null && keyClientPair.getLeft() != null && keyClientPair.getRight() != null) {
tcpClientPool().returnObject(keyClientPair.getLeft(), keyClientPair.getRight());
}
}
} catch (IOException 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;
if (failures % 10 == 0) {
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 java.nio.charset.StandardCharsets;
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;
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,93 @@
package timely.balancer.netty.ws;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
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.auth.util.ProxiedEntityUtils;
import timely.client.websocket.ClientHandler;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class WsClientHandler extends ClientHandler {
private static final Logger LOG = LoggerFactory.getLogger(WsClientHandler.class);
private final ChannelHandlerContext ctx;
private final TimelyAuthenticationToken token;
private ScheduledFuture<?> ping;
public WsClientHandler(ChannelHandlerContext ctx, TimelyAuthenticationToken token, int pingRate) {
this.ctx = ctx;
this.token = token;
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 beforeRequest(Map<String, List<String>> headers) {
if (token.getClientCert() != null) {
Multimap<String, String> proxyRequestHeaders = HashMultimap.create();
ProxiedEntityUtils.addProxyHeaders(proxyRequestHeaders, token.getClientCert());
for (String s : proxyRequestHeaders.keySet()) {
headers.put(s, new ArrayList(proxyRequestHeaders.get(s)));
}
}
Multimap<String, String> originalRequestHeaders = token.getHttpHeaders();
for (String s : originalRequestHeaders.keySet()) {
if (!headers.containsKey(s)) {
// add pre-existing values if key does not exist in proxyRequest
headers.put(s, new ArrayList(originalRequestHeaders.get(s)));
}
}
}
@Override
public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(String.class, 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 java.io.IOException;
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;
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,207 @@
package timely.balancer.netty.ws;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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 com.google.common.collect.Multimap;
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.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.auth.util.ProxiedEntityUtils;
import timely.balancer.configuration.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;
public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(WsRelayHandler.class);
private final BalancerConfiguration balancerConfig;
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, MetricResolver metricResolver,
WsClientPool wsClientPool) {
this.balancerConfig = balancerConfig;
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;
Multimap<String, String> headers = msg.getRequestHeaders();
ProxiedEntityUtils.addProxyHeaders(headers, msg.getToken().getClientCert());
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, add.getToken(),
(balancerConfig.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");
}
}
}
File diff suppressed because it is too large. Load diff
@@ -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);
}
+2
View File
@@ -0,0 +1,2 @@
cd src/main/generated-java
flatc --java ../resources/fb/metric.fbs
+21
View File
@@ -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;
+13
View File
@@ -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>
+143 -114
View File
@@ -1,120 +1,149 @@
<?xml version="1.0"?>
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>timely-client</artifactId>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.3</version>
</parent>
<dependencies>
<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.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</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.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<build>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>1.2.1-SNAPSHOT</version>
</parent>
<artifactId>timely-client</artifactId>
<properties>
<!-- PEP 440 compliant version gets substituted into src/main/python/template/setup.py -->
<python.client.version>1.2.1.dev0</python.client.version>
</properties>
<dependencies>
<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.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-client</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</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>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>filter-setup</id>
<goals>
<goal>copy-resources</goal>
</goals>
<phase>process-resources</phase>
<configuration>
<outputDirectory>${project.basedir}/src/main/python</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/python/template</directory>
<includes>
<include>setup.py</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>python</id>
<build>
<plugins>
<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.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>formatter-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>${version.findbugs-maven}</version>
<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>
<effort>Max</effort>
<threshold>Medium</threshold>
<executable>python</executable>
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
<commandlineArgs>setup.py sdist --dist-dir ${project.build.directory}</commandlineArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${version.jacoco}</version>
<reportSets>
<reportSet>
<id>JaCoCo Unit Test Coverage</id>
<configuration>
<dataFile>${project.build.directory}/coverage/jacoco-ut.exec</dataFile>
</configuration>
<reports>
<report>report</report>
</reports>
</reportSet>
<reportSet>
<id>JaCoCo Integration Test Coverage</id>
<configuration>
<dataFile>${project.build.directory}/coverage/jacoco-it.exec</dataFile>
</configuration>
<reports>
<report>report-integration</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</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>
</reporting>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,82 @@
package timely.api;
import java.util.Map;
import java.util.Optional;
public class AddSubscription extends SubscriptionRequest {
private String operation = "add";
private String metric = null;
private Optional<Map<String, String>> tags = Optional.empty();
private Optional<Long> startTime = Optional.empty();
private Optional<Long> endTime = Optional.empty();
private Optional<Long> delayTime = Optional.empty();
public String getOperation() {
return operation;
}
public String getMetric() {
return metric;
}
public Optional<Map<String, String>> getTags() {
return tags;
}
public Optional<Long> getStartTime() {
return startTime;
}
public Optional<Long> getDelayTime() {
return delayTime;
}
public void setMetric(String metric) {
this.metric = metric;
}
public void setTags(Optional<Map<String, String>> tags) {
this.tags = tags;
}
public void setStartTime(Optional<Long> startTime) {
this.startTime = startTime;
}
public void setDelayTime(Optional<Long> delayTime) {
this.delayTime = delayTime;
}
public Optional<Long> getEndTime() {
return endTime;
}
public void setEndTime(Optional<Long> endTime) {
this.endTime = endTime;
}
@Override
public int hashCode() {
return (operation + subscriptionId + metric + tags + startTime + endTime + delayTime).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof AddSubscription) {
AddSubscription other = (AddSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId) && this.metric.equals(other.metric)
&& this.tags.equals(other.tags) && this.startTime.equals(other.startTime)
&& this.endTime.equals(other.endTime) && this.delayTime.equals(other.delayTime));
} else {
return false;
}
}
}
@@ -0,0 +1,45 @@
package timely.api;
public class BasicAuthLogin {
protected String username;
protected String password;
public String getUsername() {
return username;
}
public void setUsername(String user) {
this.username = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public int hashCode() {
return (username + password).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof BasicAuthLogin) {
BasicAuthLogin other = (BasicAuthLogin) obj;
return (this.username.equals(other.username) && this.password.equals(other.password));
} else {
return false;
}
}
}
@@ -0,0 +1,32 @@
package timely.api;
public class CloseSubscription extends SubscriptionRequest {
private String operation = "close";
public String getOperation() {
return operation;
}
@Override
public int hashCode() {
return (operation + subscriptionId).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof CloseSubscription) {
CloseSubscription other = (CloseSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId));
} else {
return false;
}
}
}
@@ -0,0 +1,32 @@
package timely.api;
public class CreateSubscription extends SubscriptionRequest {
private String operation = "create";
public String getOperation() {
return operation;
}
@Override
public int hashCode() {
return (operation + subscriptionId).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof CreateSubscription) {
CreateSubscription other = (CreateSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId));
} else {
return false;
}
}
}
@@ -0,0 +1,41 @@
package timely.api;
public class RemoveSubscription extends SubscriptionRequest {
private String operation = "remove";
private String metric = null;
public String getOperation() {
return operation;
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
@Override
public int hashCode() {
return (operation + subscriptionId + metric).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof RemoveSubscription) {
RemoveSubscription other = (RemoveSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId) && this.metric.equals(other.metric));
} else {
return false;
}
}
}
@@ -0,0 +1,15 @@
package timely.api;
public abstract class SubscriptionRequest {
protected String subscriptionId = null;
public final String getSubscriptionId() {
return subscriptionId;
}
public final void setSubscriptionId(String subscriptionId) {
this.subscriptionId = subscriptionId;
}
}
@@ -5,7 +5,6 @@ import java.util.List;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import timely.model.Metric;
import timely.model.Tag;
@@ -16,6 +15,7 @@ public class MetricResponse {
private double value;
private List<Tag> tags;
private String subscriptionId;
private boolean complete = false;
public String getMetric() {
return metric;
@@ -33,6 +33,10 @@ public class MetricResponse {
return tags;
}
public boolean isComplete() {
return complete;
}
public void setMetric(String metric) {
this.metric = metric;
}
@@ -57,6 +61,10 @@ public class MetricResponse {
this.subscriptionId = subscriptionId;
}
public void setComplete(boolean closed) {
this.complete = closed;
}
public static MetricResponse fromMetric(Metric metric, String subscriptionId) {
MetricResponse response = new MetricResponse();
response.setMetric(metric.getName());
@@ -64,6 +72,7 @@ public class MetricResponse {
response.setTags(metric.getTags());
response.setValue(metric.getValue().getMeasure());
response.setSubscriptionId(subscriptionId);
response.setComplete(false);
return response;
}
@@ -75,6 +84,7 @@ public class MetricResponse {
hash.append(this.value);
hash.append(this.tags);
hash.append(this.subscriptionId);
hash.append(this.complete);
return hash.hashCode();
}
@@ -96,6 +106,7 @@ public class MetricResponse {
equals.append(this.value, other.value);
equals.append(this.tags, other.tags);
equals.append(this.subscriptionId, other.subscriptionId);
equals.append(this.complete, other.complete);
return equals.isEquals();
}
@@ -107,6 +118,7 @@ public class MetricResponse {
tsb.append("value", this.value);
tsb.append("tags", this.tags);
tsb.append("subscriptionId", this.subscriptionId);
tsb.append("complete", this.complete);
return tsb.toString();
}
@@ -0,0 +1,29 @@
package timely.api.response;
import java.util.ArrayList;
import java.util.List;
public class MetricResponses {
private List<MetricResponse> responses = new ArrayList<>();
public synchronized List<MetricResponse> getResponses() {
return this.responses;
}
public synchronized void setResponses(List<MetricResponse> responses) {
this.responses = responses;
}
public synchronized void addResponse(MetricResponse r) {
this.responses.add(r);
}
public int size() {
return this.responses.size();
}
public synchronized void clear() {
this.responses.clear();
}
}
@@ -0,0 +1,73 @@
package timely.client.http;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.glassfish.tyrus.client.SslContextConfigurator;
public class HttpClient {
public static SSLContext getSSLContext(String trustStoreFile, String trustStoreType, String trustStorePass,
String keyStoreFile, String keyStoreType, String keyStorePass) {
SslContextConfigurator scc = new SslContextConfigurator();
scc.setTrustStoreFile(trustStoreFile);
scc.setTrustStoreType(trustStoreType);
if (null != trustStorePass) {
scc.setTrustStorePassword(trustStorePass);
}
scc.setKeyStoreFile(keyStoreFile);
scc.setKeyStoreType(keyStoreType);
if (null != keyStorePass) {
scc.setKeyStorePassword(keyStorePass);
}
return scc.createSSLContext();
}
public static CloseableHttpClient get(String trustStoreFile, String trustStoreType, String trustStorePass,
String keyStoreFile, String keyStoreType, String keyStorePass, CookieStore cookieStore,
boolean hostVerificationEnabled) {
SSLContext ssl = getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType,
keyStorePass);
return get(ssl, cookieStore, hostVerificationEnabled);
}
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) {
return get(ssl, cookieStore, hostVerificationEnabled, false);
}
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled,
boolean clientAuth) {
HostnameVerifier hostnameVerifier;
if (hostVerificationEnabled) {
hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
} else {
hostnameVerifier = new NoopHostnameVerifier();
}
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
HttpClientBuilder builder;
if (cookieStore == null) {
builder = HttpClients.custom().setSSLContext(ssl).disableCookieManagement()
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
} else {
builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore)
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
}
if (clientAuth) {
SSLConnectionSocketFactory sslCSF = new SSLConnectionSocketFactory(ssl,
new String[] { "TLSv1.1", "TLSv1.2" }, null, hostnameVerifier);
builder.setSSLSocketFactory(sslCSF);
}
return builder.build();
}
}
@@ -0,0 +1,124 @@
package timely.client.tcp;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TcpClient implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(TcpClient.class);
private final String host;
private final int port;
private Socket sock = null;
private PrintWriter out = null;
private long connectTime = 0L;
private long backoff = 2000;
private int bufferSize = -1;
private int writesSinceFlush = 0;
public TcpClient(String hostname, int port) {
this(hostname, port, -1);
}
public TcpClient(String hostname, int port, int bufferSize) {
this.host = hostname;
this.port = port;
this.bufferSize = bufferSize;
}
/**
* Opens a TCP connection to the specified host and port
*
* @throws IOException
* if an error occurs
*/
public void open() throws IOException {
if (connect() != 0) {
throw new IOException();
}
}
/**
* Write a metric to Timely
*
* @param metric
* newline terminated string representation of Timely metric
* @throws IOException
*/
public synchronized void write(String metric) throws IOException {
if (connect() != 0) {
throw new IOException();
}
out.write(metric);
writesSinceFlush++;
if (bufferSize > 0 && writesSinceFlush >= bufferSize) {
flush();
}
}
public synchronized void flush() {
if (null != out) {
out.flush();
writesSinceFlush = 0;
}
}
/**
* Closes the tcp connection to Timely
*
* @throws IOException
* if an error occurs
*/
@Override
public void close() throws IOException {
LOG.trace("Shutting down connection to Timely at {}:{}", host, port);
if (null != sock) {
try {
if (null != out) {
if (bufferSize > 0) {
flush();
}
out.close();
}
sock.close();
} catch (IOException e) {
LOG.error("Error closing connection to Timely at " + host + ":" + port + ". Error: " + e.getMessage());
} finally {
sock = null;
}
}
}
private synchronized int connect() {
if (null == sock || !sock.isConnected() || out.checkError()) {
if (System.currentTimeMillis() > (connectTime + backoff)) {
try {
connectTime = System.currentTimeMillis();
sock = new Socket(host, port);
out = new PrintWriter(sock.getOutputStream(), false);
backoff = 2000;
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.info("Will retry connection in {} ms.", backoff);
return -1;
}
} else {
LOG.warn("Not writing to Timely, waiting to reconnect");
return -1;
}
}
return 0;
}
public long getConnectTime() {
return connectTime;
}
}
@@ -0,0 +1,55 @@
package timely.client.udp;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 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 {
if (null == sock) {
this.sock = new DatagramSocket();
}
}
public void write(String metric) throws IOException {
if (null == this.sock) {
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);
}
public void flush() throws IOException {
}
public void close() throws IOException {
if (null != sock) {
sock.close();
sock = null;
}
}
}
@@ -0,0 +1,43 @@
package timely.client.websocket;
import java.util.List;
import java.util.Map;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ClientHandler extends Endpoint {
private final static Logger LOG = LoggerFactory.getLogger(ClientHandler.class);
@Override
public void onOpen(Session session, EndpointConfig config) {
LOG.info("Websocket session {} opened.", session.getId());
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String message) {
LOG.info("Message received on Websocket session {}: {}", session.getId(), message);
}
});
}
@Override
public void onClose(Session session, CloseReason reason) {
LOG.info("Websocket session {} closed.", session.getId());
}
@Override
public void onError(Session session, Throwable error) {
LOG.error("Error occurred on Websocket session" + session.getId(), error);
}
public void beforeRequest(Map<String, List<String>> headers) {
}
}
@@ -0,0 +1,68 @@
package timely.client.websocket;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.Extension;
import org.apache.http.cookie.Cookie;
public class TimelyEndpointConfig implements ClientEndpointConfig {
private String sessionCookie = null;
private final ClientHandler clientEndpoint;
private static final String FORMAT = "%s=%s;";
public TimelyEndpointConfig(ClientHandler clientEndpoint, Cookie sessionCookie) {
if (null != sessionCookie) {
this.sessionCookie = String.format(FORMAT, sessionCookie.getName(), sessionCookie.getValue());
}
this.clientEndpoint = clientEndpoint;
}
@Override
public List<Class<? extends Encoder>> getEncoders() {
return Collections.emptyList();
}
@Override
public List<Class<? extends Decoder>> getDecoders() {
return Collections.emptyList();
}
@Override
public Map<String, Object> getUserProperties() {
return Collections.emptyMap();
}
@Override
public List<String> getPreferredSubprotocols() {
return Collections.emptyList();
}
@Override
public List<Extension> getExtensions() {
return Collections.emptyList();
}
@Override
public Configurator getConfigurator() {
return new Configurator() {
@Override
public void beforeRequest(Map<String, List<String>> headers) {
super.beforeRequest(headers);
if (null != sessionCookie) {
headers.put("Cookie", Collections.singletonList(sessionCookie));
}
TimelyEndpointConfig.this.clientEndpoint.beforeRequest(headers);
}
};
}
}
@@ -0,0 +1,178 @@
package timely.client.websocket;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.websocket.DeploymentException;
import javax.websocket.Session;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.client.ClientProperties;
import org.glassfish.tyrus.client.SslEngineConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.BasicAuthLogin;
import timely.client.http.HttpClient;
import timely.serialize.JsonSerializer;
public class WebSocketClient implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(WebSocketClient.class);
private final String timelyHostname;
private final int timelyHttpsPort;
private final int timelyWssPort;
private final boolean doLogin;
private final boolean clientAuth;
private final String timelyUsername;
private final String timelyPassword;
private final boolean hostVerificationEnabled;
private final int bufferSize;
private final SSLContext ssl;
private ClientManager webSocketClient = null;
protected Session session = null;
protected volatile boolean closed = true;
protected WebSocketClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort,
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
boolean hostVerificationEnabled, int bufferSize) {
this.ssl = ssl;
this.timelyHostname = timelyHostname;
this.timelyHttpsPort = timelyHttpsPort;
this.timelyWssPort = timelyWssPort;
this.doLogin = doLogin;
this.clientAuth = clientAuth;
this.timelyUsername = timelyUsername;
this.timelyPassword = timelyPassword;
this.hostVerificationEnabled = hostVerificationEnabled;
this.bufferSize = bufferSize;
Preconditions.checkNotNull(timelyHostname, "%s must be supplied", "Timely host name");
Preconditions.checkNotNull(timelyHttpsPort, "%s must be supplied", "Timely HTTPS port");
Preconditions.checkNotNull(timelyWssPort, "%s must be supplied", "Timely WSS port");
if (doLogin && !clientAuth) {
if ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword)
|| (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword)))) {
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty");
}
}
}
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth,
boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType,
String keyStorePass, String trustStoreFile, String trustStoreType, String trustStorePass,
boolean hostVerificationEnabled, int bufferSize) {
this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType,
keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername,
timelyPassword, hostVerificationEnabled, bufferSize);
}
public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException {
Cookie sessionCookie = null;
if (doLogin) {
BasicCookieStore cookieJar = new BasicCookieStore();
try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled, clientAuth)) {
String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login";
HttpRequestBase request = null;
if (StringUtils.isEmpty(timelyUsername)) {
// HTTP GET to /login to use certificate based login
request = new HttpGet(target);
LOG.trace("Performing client certificate login");
} else {
// HTTP POST to /login to use username/password
BasicAuthLogin login = new BasicAuthLogin();
login.setUsername(timelyUsername);
login.setPassword(timelyPassword);
String payload = JsonSerializer.getObjectMapper().writeValueAsString(login);
HttpPost post = new HttpPost(target);
post.setEntity(new StringEntity(payload));
request = post;
LOG.trace("Performing BasicAuth login");
}
HttpResponse response = null;
try {
response = client.execute(request);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new HttpResponseException(response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase());
}
for (Cookie c : cookieJar.getCookies()) {
if (c.getName().equals("TSESSIONID")) {
sessionCookie = c;
break;
}
}
if (null == sessionCookie) {
throw new IllegalStateException(
"Unable to find TSESSIONID cookie header in Timely login response");
}
} finally {
HttpClientUtils.closeQuietly(response);
}
}
}
SslEngineConfigurator sslEngine = new SslEngineConfigurator(ssl);
sslEngine.setClientMode(true);
sslEngine.setHostVerificationEnabled(hostVerificationEnabled);
webSocketClient = ClientManager.createClient();
webSocketClient.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngine);
webSocketClient.getProperties().put(ClientProperties.INCOMING_BUFFER_SIZE, bufferSize);
String wssPath = "wss://" + timelyHostname + ":" + timelyWssPort + "/websocket";
session = webSocketClient.connectToServer(clientEndpoint,
new TimelyEndpointConfig(clientEndpoint, sessionCookie), new URI(wssPath));
final ByteBuffer pingData = ByteBuffer.allocate(0);
webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> {
try {
session.getBasicRemote().sendPing(pingData);
} catch (Exception e) {
LOG.error("Error sending ping", e);
}
}, 30, 60, TimeUnit.SECONDS);
closed = false;
}
public synchronized void close() throws IOException {
if (closed) {
return;
}
try {
if (null != webSocketClient) {
webSocketClient.shutdown();
}
} finally {
session = null;
webSocketClient = null;
closed = true;
}
}
public boolean isClosed() {
return closed;
}
}
@@ -0,0 +1,91 @@
package timely.client.websocket.subscription;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Future;
import javax.net.ssl.SSLContext;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.AddSubscription;
import timely.api.CloseSubscription;
import timely.api.CreateSubscription;
import timely.api.RemoveSubscription;
import timely.client.websocket.ClientHandler;
import timely.client.websocket.WebSocketClient;
import timely.serialize.JsonSerializer;
public class WebSocketSubscriptionClient extends WebSocketClient {
private static final Logger LOG = LoggerFactory.getLogger(WebSocketSubscriptionClient.class);
private final String subscriptionId;
public WebSocketSubscriptionClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort,
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
boolean hostVerificationEnabled, int bufferSize) {
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
hostVerificationEnabled, bufferSize);
subscriptionId = UUID.randomUUID().toString();
LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
}
public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort,
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile,
String keyStoreType, String keyStorePass, String trustStoreFile, String trustStoreType,
String trustStorePass, boolean hostVerificationEnabled, int bufferSize) {
super(timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
keyStoreFile, keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass,
hostVerificationEnabled, bufferSize);
subscriptionId = UUID.randomUUID().toString();
LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
}
@Override
public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException {
super.open(clientEndpoint);
CreateSubscription create = new CreateSubscription();
create.setSubscriptionId(subscriptionId);
session.getBasicRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(create));
}
@Override
public synchronized void close() throws IOException {
if (null != session) {
CloseSubscription close = new CloseSubscription();
close.setSubscriptionId(subscriptionId);
try {
session.getBasicRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(close));
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Client called close."));
} catch (Exception e) {
LOG.info("Unable to send close message to server: {}", e.getMessage());
}
}
super.close();
}
public Future<Void> addSubscription(String metric, Map<String, String> tags, long startTime, long endTime,
long delayTime) throws IOException {
AddSubscription add = new AddSubscription();
add.setSubscriptionId(subscriptionId);
add.setMetric(metric);
add.setTags(Optional.ofNullable(tags));
add.setStartTime(Optional.ofNullable(startTime));
add.setEndTime(Optional.ofNullable(endTime));
add.setDelayTime(Optional.ofNullable(delayTime));
return session.getAsyncRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(add));
}
public Future<Void> removeSubscription(String metric) throws Exception {
RemoveSubscription remove = new RemoveSubscription();
remove.setSubscriptionId(subscriptionId);
remove.setMetric(metric);
return session.getAsyncRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(remove));
}
}
+11 -4
View File
@@ -1,12 +1,17 @@
package timely.model;
import com.fasterxml.jackson.annotation.*;
import timely.model.parse.TagParser;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import timely.model.parse.TagParser;
/**
* A Metric consists of a metric name, tags, and a Value
*
@@ -17,7 +22,9 @@ import java.util.List;
@JsonRootName("metric")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({ "name" })
public class Metric {
public class Metric implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private List<Tag> tags;
@@ -0,0 +1,197 @@
package timely.model;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Logger;
/**
* A simple interface that objects can implement to return the object size.
*/
public interface ObjectSizeOf {
/**
* The (approximate) size of the object
*/
long sizeInBytes();
public static class ObjectInstance {
private Object o;
public ObjectInstance(Object _o) {
this.o = _o;
}
public Object getObject() {
return o;
}
@Override
public int hashCode() {
return System.identityHashCode(o);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ObjectInstance)) {
return false;
}
return ((ObjectInstance) obj).o == this.o;
}
}
public static class Sizer {
private static final Logger log = Logger.getLogger(Sizer.class.getCanonicalName());
public static final short OBJECT_OVERHEAD = 8;
public static final short ARRAY_OVERHEAD = 12;
public static final short REFERENCE = 4;
// The size of the basic Number constructs (and Boolean and Character)
// is 16: roundUp(8 + primitiveSize)
public static final short NUMBER_SIZE = 16;
/**
* Get the size of an object. Note that we want something relatively fast that
* gives us an order of magnitude here. The java Instrumentation agent mechanism
* is a little too costly for general use here. This will look for the
* ObjectSizeOf interface and if implemented on the object will use that.
* Otherwise it will do a simple navigation of the fields using reflection.
*
* @param o
* @return an approximation of the object size
*/
public static long getObjectSize(Object o, boolean useSizeInBytesMethod, boolean debug) {
long estSize = getObjectSize(o, new HashSet<ObjectInstance>(), new Stack<ObjectInstance>(),
useSizeInBytesMethod, debug);
return estSize;
}
public static long getObjectSize(Object o) {
long estSize = getObjectSize(o, new HashSet<ObjectInstance>(), new Stack<ObjectInstance>(), true, false);
return estSize;
}
public static long getObjectSize(Object o, Set<ObjectInstance> visited, Stack<ObjectInstance> stack,
boolean useSizeInBytesMethod, boolean debug) {
long totalSize = 0;
stack.add(new ObjectInstance(o));
while (!stack.isEmpty()) {
long size = 0;
ObjectInstance oi = stack.pop();
o = oi.getObject();
if (o != null && !visited.contains(oi)) {
visited.add(oi);
try {
if (useSizeInBytesMethod) {
try {
if (o instanceof ObjectSizeOf) {
size = ((ObjectSizeOf) o).sizeInBytes();
} else {
Method sizeInBytes = o.getClass().getMethod("sizeInBytes", (Class<?>[]) null);
size = (Long) sizeInBytes.invoke(o);
}
} catch (Throwable t) {
// ok, lets do this the hard way...
}
}
if (size == 0) {
// the hard way...
// do not include Class related objects or
// reflection objects
if ((o instanceof Class) || (o instanceof ClassLoader) || (o.getClass().getPackage() != null
&& o.getClass().getPackage().getName().startsWith("java.lang.reflect"))) {
size = 0;
} else if (o instanceof Number || o instanceof Boolean || o instanceof Character) {
size = NUMBER_SIZE;
} else {
// lets do a simple sizing
Class<?> c = o.getClass();
if (c.isArray()) {
size = ARRAY_OVERHEAD;
int length = Array.getLength(o);
if (c.getComponentType().isPrimitive()) {
size += length * getPrimitiveObjectSize(c.getComponentType());
} else {
size += length * REFERENCE;
for (int i = 0; i < length; i++) {
Object element = Array.get(o, i);
if (element != null) {
stack.add(new ObjectInstance(element));
}
}
}
} else {
size += OBJECT_OVERHEAD;
while (c != null) {
for (Field field : c.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
if (field.getType().isPrimitive()) {
size += getPrimitiveObjectSize(field.getType());
} else {
size += REFERENCE;
boolean accessible = field.isAccessible();
field.setAccessible(true);
try {
Object fieldObject = field.get(o);
if (fieldObject != null) {
stack.push(new ObjectInstance(fieldObject));
}
} catch (Exception e) {
// cannot get to field, so
// ignore it in this size
// calculation
e.printStackTrace();
}
field.setAccessible(accessible);
}
}
c = c.getSuperclass();
}
}
size = roundUp(size);
}
}
} catch (Throwable t) {
log.warning("Unable to determine object size for " + o);
}
if (debug) {
System.out.println(o.getClass().getCanonicalName() + "=" + size);
}
}
totalSize += size;
}
return totalSize;
}
public static long roundUp(long size) {
long extra = size % 8;
if (extra > 0) {
size = size + 8 - extra;
}
return size;
}
public static short getPrimitiveObjectSize(Class<?> primitiveType) {
if (primitiveType.equals(int.class) || primitiveType.equals(float.class)) {
return 4;
} else if (primitiveType.equals(boolean.class) || primitiveType.equals(byte.class)) {
return 1;
} else if (primitiveType.equals(char.class) || primitiveType.equals(short.class)) {
return 2;
} else if (primitiveType.equals(long.class) || primitiveType.equals(double.class)) {
return 8;
} else { // if (primitiveType.equals(void.class)) {
return 0;
}
}
}
}
+14 -14
View File
@@ -1,18 +1,19 @@
package timely.model;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import timely.model.parse.TagParser;
import java.io.Serializable;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import timely.model.parse.TagParser;
/**
* Tag consists of key value pair
*/
@@ -21,7 +22,6 @@ public class Tag implements Comparable<Tag>, Serializable {
private static final long serialVersionUID = 1L;
private static final Joiner equalJoiner = Joiner.on("=");
private static final TagParser tagParser = new TagParser();
private String key;
@@ -81,12 +81,12 @@ public class Tag implements Comparable<Tag>, Serializable {
* @return
*/
public String join() {
return equalJoiner.join(key, value);
return tagParser.combine(this);
}
@Override
public String toString() {
return "Tag{" + equalJoiner.join(this.getKey(), this.getValue()) + "}";
return "Tag{" + join() + "}";
}
@Override
@@ -100,12 +100,12 @@ public class Tag implements Comparable<Tag>, Serializable {
return true;
if (null == o || getClass() != o.getClass())
return false;
return this.toString().equals(o.toString());
Tag other = (Tag) o;
return new EqualsBuilder().append(this.key, other.key).append(this.value, other.value).isEquals();
}
@Override
public int compareTo(Tag other) {
return this.toString().compareTo(other.toString());
return new CompareToBuilder().append(this.key, other.key).append(this.value, other.value).toComparison();
}
}
+4 -4
View File
@@ -1,13 +1,13 @@
package timely.model;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
import java.io.Serializable;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Objects;
/**
* Value consists of a long timestamp and a double measurement
*/
@@ -0,0 +1,6 @@
package timely.model.parse;
public interface Combiner<T> {
String combine(T o);
}
@@ -1,11 +1,11 @@
package timely.model.parse;
import java.util.List;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import timely.model.Metric;
import java.util.List;
public class MetricParser implements Parser<Metric> {
private static final Splitter spaceSplitter = Splitter.on(" ").omitEmptyStrings().trimResults();
@@ -1,23 +1,61 @@
package timely.model.parse;
import com.google.common.base.Splitter;
import timely.model.Tag;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import timely.model.Tag;
/**
* Parses tag1=val1,tag2=val2,tag3=val3 into a {@code List} of {@code Tag}
* Combines {@code List} of {@code Tag} into {@code String} of
* tag1=val1,tag2=val2,tag3=val3.
*/
public class TagListParser implements Parser<List<Tag>> {
public class TagListParser implements Parser<List<Tag>>, Combiner<Collection<Tag>> {
private static final Splitter commaSplitter = Splitter.on(",").omitEmptyStrings();
private static final TagParser tagParser = new TagParser();
@Override
public List<Tag> parse(String t) {
ArrayList<Tag> tags = new ArrayList<>();
commaSplitter.splitToList(t).forEach(p -> tags.add(tagParser.parse(p)));
// only split on commas that are not preceded with a backslash
String[] parts = t.split("(?<!\\\\),");
for (String p : parts) {
if (!p.isEmpty()) {
Tag tag = tagParser.parse(p);
// unescape commas in the key and value of the tag
tag.setKey(tag.getKey().replaceAll("\\\\,", ","));
tag.setValue(tag.getValue().replaceAll("\\\\,", ","));
tags.add(tag);
}
}
return tags;
}
@Override
public String combine(Collection<Tag> tags) {
StringBuilder builder = new StringBuilder();
if (!tags.isEmpty()) {
tags.forEach(p -> builder.append(tagParser.combine(p)).append(','));
builder.setLength(builder.length() - 1);
}
return builder.toString();
}
/**
* An alternative combiner for a map of string to string instead of a list of
* tags
*
* @param tags
* @return string representation
*/
public String combine(Map<String, String> tags) {
StringBuilder builder = new StringBuilder();
if (!tags.isEmpty()) {
tags.forEach((k, v) -> builder.append(tagParser.combine(new Tag(k, v))).append(','));
builder.setLength(builder.length() - 1);
}
return builder.toString();
}
}
@@ -1,13 +1,19 @@
package timely.model.parse;
import com.google.common.base.Splitter;
import timely.model.Tag;
import java.util.List;
public class TagParser implements Parser<Tag> {
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import timely.model.Tag;
private static final Splitter equalSplitter = Splitter.on("=").trimResults();
/**
* Parses tag1=val1 into a {@code Tag} Combines {@code Tag} into {@code String}
* of tag1=val1
*/
public class TagParser implements Parser<Tag>, Combiner<Tag> {
private static final Splitter equalSplitter = Splitter.on("=").limit(2).trimResults();
private static final Joiner equalsJoiner = Joiner.on("=");
@Override
public Tag parse(String t) {
@@ -17,4 +23,10 @@ public class TagParser implements Parser<Tag> {
}
return new Tag(parts.get(0), parts.get(1));
}
@Override
public String combine(Tag t) {
return equalsJoiner.join(t.getKey(), t.getValue());
}
}
@@ -0,0 +1,22 @@
package timely.serialize;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
public class JsonSerializer {
private static ObjectMapper mapper = new ObjectMapper();
static {
mapper.registerModule(new Jdk8Module());
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
}
public static ObjectMapper getObjectMapper() {
return mapper;
}
}
+12
View File
@@ -0,0 +1,12 @@
from setuptools import setup
setup(
name='timely',
version='${python.client.version}',
packages=['timely'],
install_requires=[
'seaborn',
'plotly'
],
license='Apache License, Version 2.0'
)
@@ -0,0 +1,332 @@
import time
from datetime import timedelta
from datetime import datetime
from TimeDateRange import UTC
import pandas
import matplotlib.dates as mdates
import plotly.graph_objs as go
import plotly.offline as py
import seaborn
import matplotlib.ticker as tkr
import numpy as np
import os
utc = UTC()
def pivot(df, metric, groupByColumn=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
if groupByColumn is not None:
dataFrame["date"] = dataFrame.index.to_pydatetime()
dataFrame = dataFrame.pivot_table(index="date", columns=groupByColumn, values=metric)
return dataFrame
def resample(df, sample, how='mean', interpolate=True, fill=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
if sample is not None:
dataFrame = dataFrame.resample(sample, how=how)
if (interpolate == True):
dataFrame = dataFrame.interpolate()
if fill is not None:
dataFrame = dataFrame.fillna(value=fill)
return dataFrame
def unpivot(df, metric, groupByColumn=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
dataFrame['date'] = dataFrame.index.values
dataFrame = pandas.melt(dataFrame, id_vars=['date'], value_name=metric, var_name=groupByColumn)
dataFrame = dataFrame.set_index('date')
return dataFrame
def rolling_average(df, metric, rolling_average=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
if rolling_average is not None:
dataFrame[metric] = pandas.rolling_mean(dataFrame[metric], rolling_average)
return dataFrame
def graph(analyticConfig, df, timelyMetric, seriesConfig={}, graphConfig={}, notebook=False, type='png'):
if type == 'png':
return graphSeaborn(analyticConfig, df, timelyMetric, seriesConfig=seriesConfig, graphConfig=graphConfig, notebook=notebook)
else:
return graphPlotly(analyticConfig, df, timelyMetric, seriesConfig=seriesConfig, graphConfig=graphConfig, notebook=notebook)
def graphSeaborn(analyticConfig, df, timelyMetric, seriesConfig={}, graphConfig={}, notebook=False):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
dataFrame = ensureMinSeriesLength(dataFrame, analyticConfig.groupByColumn)
dataFrame['date'] = dataFrame.index.values
dataFrame['dateFloat'] = dataFrame['date'].apply(lambda x: datetime2graphdays(x))
dataFrame['index'] = dataFrame['dateFloat']
groupByColumn = analyticConfig.groupByColumn
if groupByColumn is not None:
# sort on date and the groupByColumn so that the series legend is sorted
dataFrame = dataFrame.sort_values(['index', analyticConfig.groupByColumn])
else:
dataFrame = dataFrame.sort_values(['index'])
#dataFrame = dataFrame.set_index('index', drop=True)
dataFrame = dataFrame.dropna()
seaborn.set(style="darkgrid", palette="Set2")
fig, ax = seaborn.plt.subplots()
colors = np.random.random((len(dataFrame), 3))
i = 0
for col in df[analyticConfig.groupByColumn].unique():
tempDataFrame = dataFrame.loc[dataFrame[analyticConfig.groupByColumn] == col]
addlColConfig = seriesConfig.get(col, seriesConfig.get('default', dict()))
markerDict = addlColConfig.get("marker", dict())
color = markerDict.get("color", "")
if color == "red":
seaborn.tsplot(tempDataFrame, time="dateFloat", unit=None, interpolate=False, color="red", marker="d",
estimator=np.nanmean, condition=groupByColumn, value=timelyMetric.metric, ax=ax)
else:
seaborn.tsplot(tempDataFrame, time="dateFloat", unit=None, interpolate=True, color=colors[i],
estimator=np.nanmean, condition=groupByColumn, value=timelyMetric.metric, ax=ax)
i = i + 1
ax.xaxis_date()
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y/%m/%d %H:%M:%S'))
ax.set_xlim(xmin=dataFrame['dateFloat'].min(), xmax=dataFrame['dateFloat'].max())
formatter = tkr.ScalarFormatter(useMathText=False)
formatter.set_scientific(False)
ax.yaxis.set_major_formatter(formatter)
graphTitle = getTitle(timelyMetric, analyticConfig)
seaborn.plt.title(graphTitle, fontsize='large')
seaborn.plt.xlabel('date/time')
seaborn.plt.ylabel(timelyMetric.metric)
legendTitle = "Legend"
if groupByColumn is not None:
legendTitle = groupByColumn
seaborn.plt.legend(loc='upper center', prop={'size':8}, ncol=10, title=legendTitle, bbox_to_anchor=(0.5,-0.4))
fig.autofmt_xdate(rotation=45)
seaborn.plt.autoscale(tight=False)
if notebook == True:
seaborn.plt.show()
return None
else:
directory = analyticConfig.output_dir
try:
os.makedirs(directory, mode=0755)
except OSError, e:
# be happy if someone already created the path
pass
baseFilename = getFilename(timelyMetric.metric, analyticConfig)
filename = directory+'/'+baseFilename
fig.savefig(filename + '.png')
return baseFilename + '.png'
def graphPlotly(analyticConfig, df, timelyMetric, seriesConfig={}, graphConfig={}, notebook=False):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
dataFrame['colFromIndex'] = dataFrame.index
if analyticConfig.groupByColumn is not None:
# sort on date and the groupByColumn so that the series legend is sorted
dataFrame = dataFrame.sort_values(['colFromIndex', analyticConfig.groupByColumn])
else:
dataFrame = dataFrame.sort_values(['colFromIndex'])
title = getTitle(timelyMetric, analyticConfig)
dataFrame['date'] = dataFrame.index.values
layoutConfig = dict(
title=title,
autosize=False,
width=1000,
height=700,
showlegend=True,
xaxis=dict(
autorange=True,
tickangle=-45
),
yaxis=dict(
autorange=True,
title=timelyMetric.metric
)
)
layoutConfig.update(graphConfig)
layout = go.Layout(layoutConfig)
data = None
groupByValues = sorted(dataFrame[analyticConfig.groupByColumn].unique())
for col in groupByValues:
tempDataFrame = dataFrame.loc[dataFrame[analyticConfig.groupByColumn] == col]
addlColConfig = seriesConfig.get(col, seriesConfig.get('default', dict()))
config = dict(
name=col,
x=tempDataFrame.index,
y=tempDataFrame[timelyMetric.metric],
hoverinfo='x+y+text',
text=col
)
config.update(addlColConfig)
s = go.Scatter(config)
if data is None:
data = [s]
else:
data.append(s)
directory = analyticConfig.output_dir
try:
os.makedirs(directory, mode=0755)
except OSError, e:
# be happy if someone already created the path
pass
baseFilename = getFilename(timelyMetric.metric, analyticConfig)
filename = directory+'/'+baseFilename
fig = go.Figure(data=data, layout=layout)
if notebook:
py.iplot(fig, filename=filename, show_link=False)
return None
else:
py.plot(fig, output_type='file', filename=filename+'.html', image_filename=filename, image=None, auto_open=False, show_link=False)
return baseFilename+'.html'
def ensureMinSeriesLength(df, groupByColumn):
dataFrame = df
if groupByColumn is not None:
dataFrame = pandas.DataFrame(df, copy=True)
dataFrame = dataFrame.dropna()
extra = pandas.DataFrame()
for c, (cond, df_c) in enumerate(dataFrame.groupby(groupByColumn, sort=False)):
if len(df_c) <= 1:
extra = extra.append(df_c)
extra.index = extra.index + pandas.DateOffset(seconds=1)
dataFrame = dataFrame.append(extra)
return dataFrame
def datetime2graphdays(dt):
return mdates.epoch2num((time.mktime(dt.timetuple()) / 86400) * mdates.SEC_PER_DAY)
def graphdays2datetime(x):
epoch = mdates.num2epoch(x)
return datetime.fromtimestamp(int(epoch), utc)
def getFilename(metric, analyticConfig):
now = datetime.now(tz=utc)
format = "%Y%m%d%H%M%S"
nowStr = time.strftime(format, now.timetuple())
fileNameBase = metric
if analyticConfig.sample is not None:
fileNameBase += "-" + analyticConfig.sample
if analyticConfig.how is not None:
fileNameBase += "-" + analyticConfig.how
return fileNameBase + '-' + nowStr
def getDirectoryPathForDate(format='%Y/%m/%d'):
now = datetime.now(tz=utc)
return time.strftime(format, now.timetuple())
def getTitle(timelyMetric, config, separator='\n'):
if config.orCondition:
condition = "OR"
else:
condition = "AND"
metricTitle = timelyMetric.metric
if timelyMetric.tags is not None and len(timelyMetric.tags) > 0:
metricTitle += ' (' + timelyMetric.tags + ')'
sampleTitle = ""
alertTitle = ""
if config.sample_period is not None:
sampleTitle = config.sample_period + " " + config.how + " samples"
if config.rolling_average_period is not None:
sampleTitle += ', ' + config.rolling_average_period + ' rolling average'
if config.min_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y < " + str(config.min_threshold) + ")"
if config.average_min_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y-avg < " + str(config.average_min_threshold) + ")"
if config.max_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y > " + str(config.max_threshold) + ")"
if config.average_max_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y-avg > " + str(config.average_max_threshold) + ")"
if config.min_threshold_percentage is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
if config.min_threshold_percentage >= 0:
descriptivePercentage = 100 + config.min_threshold_percentage
else:
descriptivePercentage = abs(config.min_threshold_percentage)
alertTitle += "(y < " + str(descriptivePercentage) + "% of " + str(
config.rolling_average_period) + " sample average)"
if config.max_threshold_percentage is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
if config.max_threshold_percentage >= 0:
descriptivePercentage = 100 + config.max_threshold_percentage
else:
descriptivePercentage = abs(config.max_threshold_percentage)
alertTitle += "(y > " + str(descriptivePercentage) + "% of " + str(
config.rolling_average_period) + " sample average)"
if config.system_name is None:
title = metricTitle
else:
title = config.system_name + ' - ' + metricTitle
if sampleTitle != "":
title += separator + sampleTitle
if alertTitle != "":
if config.min_alert_period is not None:
alertTitle = '(' + alertTitle + ') for at least ' + config.min_alert_period
if config.last_alert is not None:
alertTitle = alertTitle + ' in last ' + config.last_alert
title += separator + alertTitle
return title
@@ -0,0 +1,127 @@
import time
from datetime import datetime
from datetime import timedelta
from datetime import tzinfo
import pandas
class TimeDateError(ValueError):
def __init__(self, message, *args):
self.message = message
super(TimeDateError, self).__init__(message, *args)
ZERO = timedelta(0)
# A UTC class.
class UTC(tzinfo):
"""UTC"""
def __str__(self):
return "UTC"
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
utc = UTC()
class TimeDateRange:
beginDt = None
endDt = None
format = "%Y%m%d %H%M%S"
def getBeginMs(self):
if self.beginDt == None:
return None
return TimeDateRange.unix_time_millis(self.beginDt)
def getBeginStr(self):
if self.beginDt == None:
return None
return time.strftime(self.format, self.beginDt.timetuple())
def getEndMs(self):
if self.endDt == None:
return None
return TimeDateRange.unix_time_millis(self.endDt)
def getEndStr(self):
if self.endDt == None:
return None
return time.strftime(self.format, self.endDt.timetuple())
def __init__(self, beginStr, endStr, period, format="%Y%m%d-%H%M%S"):
now = datetime.now(tz=utc)
nowMs = TimeDateRange.unix_time_millis(now)
if beginStr != None and endStr != None and period != None:
raise TimeDateError("You can not provide a begin, end, and period")
elif (beginStr == None and endStr == None and period == None) or (period == None and beginStr == None):
raise TimeDateError("You must provide a time range")
if period == None:
# calculate begin time
beginTuple = time.strptime(beginStr, self.format)
self.beginDt = datetime(*beginTuple[0:6], tzinfo=utc)
# calculate end time
# end is either specified or now
if endStr == None or endStr == 'now':
self.endDt = now
else:
self.endDt = datetime(*time.strptime(endStr, self.format)[0:6], tzinfo=utc)
if self.endDt > now:
self.endDt = now
else:
# period must be specified
td = pandas.to_timedelta(period)
rangeInSec = td.total_seconds()
if beginStr == None:
# calculate begin time from end tme and period
# calculate end time
# end is either specified or now
if endStr == None or endStr == 'now':
self.endDt = now
else:
self.endDt = datetime(*time.strptime(endStr, self.format)[0:6], tzinfo=utc)
if self.endDt > now:
self.endDt = now
# calculate begin time
self.beginDt = self.endDt - timedelta(milliseconds=rangeInSec * 1000)
else:
# calculate end time from begin tme and period
# calculate begin time
self.beginDt = datetime(*time.strptime(beginStr, self.format)[0:6], tzinfo=utc)
# calculate end time
self.endDt = self.beginDt + timedelta(milliseconds=rangeInSec * 1000)
if self.endDt > now:
self.endDt = now
if self.beginDt > now:
raise TimeDateError("begin date/time can not be in the future")
if self.beginDt > self.endDt:
raise TimeDateError("begin date/time can not be past end date/time")
epoch = datetime.utcfromtimestamp(0).replace(tzinfo=utc)
@classmethod
def unix_time_millis(cls, dt):
return int((dt - TimeDateRange.epoch).total_seconds() * 1000.0)
@@ -0,0 +1,72 @@
import smtplib
from os.path import basename
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import TimelyMetric
import syslog
import DataOperations
syslog.openlog("TimelyNotifications")
class TimelyAlert():
def __init__(self, timelyMetric, dataFrame, message, seriesConfig, analyticConfig, notebook):
self.timelyMetric = timelyMetric
self.dataFrame = dataFrame
self.seriesConfig = seriesConfig
self.analyticConfig = analyticConfig
self.notebook = notebook
self.message = message
def email(self, send_from, send_to, subject, text, files=None, server="127.0.0.1"):
assert isinstance(send_to, list)
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach(MIMEText(text))
for f in files or []:
with open(f, "rb") as fil:
part = MIMEApplication(
fil.read(),
Name=basename(f)
)
part['Content-Disposition'] = 'attachment; filename="%s"' % basename(f)
msg.attach(part)
smtp = smtplib.SMTP(server)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
def log(self, message):
syslog.systlog(syslog.LOG_ALERT, message)
def graph(self, type="png"):
graphConfig = {}
graphConfig["title"] = DataOperations.getTitle(self.timelyMetric, self.analyticConfig)
return TimelyMetric.graph(self.analyticConfig, self.dataFrame, self.timelyMetric, seriesConfig=self.seriesConfig, graphConfig=graphConfig, notebook=self.notebook, type=type)
def getDataFrame(self):
return self.dataFrame
def getAnalyticConfig(self):
return self.analyticConfig
def getSeriesConfig(self):
return self.seriesConfig
def getTimelyMetric(self):
return self.timelyMetric
def getMessage(self):
return self.message
@@ -0,0 +1,224 @@
import TimelyMetric
from TimeDateRange import TimeDateRange
from TimeDateRange import UTC
from TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
import DataOperations
import pandas
import re
from datetime import datetime
from datetime import timedelta
from TimelyAlert import TimelyAlert
import numpy as np
utc = UTC()
def logTiming(timelyMetric, startTime, stopTime):
df = timelyMetric.getDataFrame()
print("start: " + str(startTime))
print("stop: " + str(stopTime))
print("num records: " + str(len(df)))
elapsedSec = (TimeDateRange.unix_time_millis(stopTime) - TimeDateRange.unix_time_millis(startTime)) / 1000
print("elapsed seconds: " + str(elapsedSec))
recPerSec = len(df) / elapsedSec
print("records per second: " + recPerSec)
def addCondition(orCondition, currResult, condition):
if orCondition:
currResult = currResult | condition
else:
currResult = currResult & condition
return currResult
def keepConsecutiveAlerts(dataFrame, exceptions, minimumSpan):
if exceptions.size > 0 and minimumSpan is not None:
currentFirst = None
currentLast = None
result_span_values = pandas.DataFrame()
result_span_values['bool'] = np.zeros(exceptions.shape, bool)
result_span_values = result_span_values.set_index(exceptions.index)
current_result_span_values = pandas.DataFrame()
current_result_span_values['bool'] = np.zeros(exceptions.shape, bool)
current_result_span_values = current_result_span_values.set_index(exceptions.index)
for index, row in dataFrame.iterrows():
if index in exceptions.index:
current_result_span_values.loc[index]['bool'] = True
currentLast = index
if currentFirst is None:
currentFirst = index
if int((currentLast - currentFirst).total_seconds() / 60) >= int(minimumSpan):
result_span_values['bool'] = result_span_values['bool'] | current_result_span_values['bool']
else:
current_result_span_values['bool'] = np.zeros(exceptions.shape, bool)
currentFirst = None
currentLast = None
return exceptions.loc[result_span_values['bool']]
else:
return exceptions
def convertCounterToRate(timelyMetric, analyticConfig):
# Adjust counter metric into a rate metric
df = timelyMetric.getDataFrame()
columnNames = df[analyticConfig.groupByColumn].unique()
df = DataOperations.pivot(df, timelyMetric.metric, analyticConfig.groupByColumn)
df = DataOperations.resample(df, analyticConfig.sample, how=analyticConfig.how)
for c in columnNames:
df[c] = df[c].diff(1) / (analyticConfig.sample_minutes * 60 * 1000)
df = DataOperations.unpivot(df, timelyMetric.metric, analyticConfig.groupByColumn)
df = df.dropna()
timelyMetric.setDataFrame(df)
def find_alerts(timelyMetric, analyticConfig, notebook=False):
if (analyticConfig.counter == True):
convertCounterToRate(timelyMetric, analyticConfig)
df = timelyMetric.getDataFrame()
graphDF = TimelyMetric.pivot(df, timelyMetric.metric, groupByColumn=analyticConfig.groupByColumn)
if analyticConfig.excludeColRegex is not None:
graphDF = graphDF.select(lambda x : not (re.search(analyticConfig.excludeColRegex, x)), axis=1)
if analyticConfig.includeColRegex is not None:
graphDF = graphDF.select(lambda x : re.search(analyticConfig.includeColRegex, x), axis=1)
if analyticConfig.sample is not None:
graphDF = TimelyMetric.resample(graphDF, analyticConfig.sample, how=analyticConfig.how, interpolate=analyticConfig.interpolate, fill=analyticConfig.fill)
graphDF_avg = pandas.DataFrame(graphDF, copy=True)
combined = pandas.DataFrame()
seriesConfig = {}
for i in graphDF_avg.columns:
col = str(i)
any_conditions_values = False
result_values = np.ones(graphDF[col].shape, bool)
if analyticConfig.orCondition:
result_values = np.zeros(graphDF[col].shape, bool)
any_conditions_average = False
result_average = np.ones(graphDF_avg[col].shape, bool)
if analyticConfig.orCondition:
result_average = np.zeros(graphDF_avg[col].shape, bool)
if analyticConfig.min_threshold is not None:
currCondition = graphDF[col].astype(float) < analyticConfig.min_threshold
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.max_threshold is not None:
currCondition = graphDF[col].astype(float) > analyticConfig.max_threshold
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.rolling_average_samples is not None:
graphDF_avg = TimelyMetric.rolling_average(graphDF_avg, col, rolling_average=analyticConfig.rolling_average_samples)
if analyticConfig.min_threshold_percentage is not None:
if analyticConfig.min_threshold_percentage >= 0:
multiple = 1.0 + (float(abs(analyticConfig.min_threshold_percentage)) / float(100))
else:
multiple = 1.0 - (float(abs(analyticConfig.min_threshold_percentage)) / float(100))
currCondition = graphDF[col].astype(float) < (graphDF_avg[col].astype(float) * multiple)
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.max_threshold_percentage is not None:
if analyticConfig.max_threshold_percentage >= 0:
multiple = 1.0 + (float(abs(analyticConfig.max_threshold_percentage)) / float(100))
else:
multiple = 1.0 - (float(abs(analyticConfig.max_threshold_percentage)) / float(100))
currCondition = graphDF[col].astype(float) > (graphDF_avg[col].astype(float) * multiple)
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.average_min_threshold is not None:
currCondition = graphDF_avg[col].astype(float) < analyticConfig.average_min_threshold
result_average = addCondition(analyticConfig.orCondition, result_average, currCondition)
any_conditions_average = True
if analyticConfig.average_max_threshold is not None:
currCondition = graphDF_avg[col].astype(float) > analyticConfig.average_max_threshold
result_average = addCondition(analyticConfig.orCondition, result_average, currCondition)
any_conditions_average = True
# if orCondition is AND and no exceptional conditions have been found, then result_values will be all True
if any_conditions_values == False:
result_values = np.zeros(graphDF[col].shape, bool)
exceptional_values = graphDF.loc[result_values, col]
# if orCondition is AND and no exceptional conditions have been found, then result_average will be all True
if any_conditions_average == False:
result_average = np.zeros(graphDF_avg[col].shape, bool)
exceptional_average = graphDF_avg.loc[result_average, col]
# only keep alerts that are in consecutive periods of length analyticConfig.min_alert_minutes
exceptional_values = keepConsecutiveAlerts(graphDF, exceptional_values, analyticConfig.min_alert_minutes)
exceptional_average = keepConsecutiveAlerts(graphDF_avg, exceptional_average, analyticConfig.min_alert_minutes)
# only evaluate the last analyticConfig.last_alert_minutes if set
if analyticConfig.last_alert_minutes is not None:
end = datetime.fromtimestamp(timelyMetric.timeDateRange.getEndMs() / 1000.00, DataOperations.utc)
recentEnoughBegin = end - timedelta(minutes=analyticConfig.last_alert_minutes)
exceptional_values = exceptional_values.ix[recentEnoughBegin:end]
exceptional_average = exceptional_average.ix[recentEnoughBegin:end]
anyValueExceptions = exceptional_values.size > 0
anyAverageExceptions = exceptional_average.size > 0
if (analyticConfig.display.lower() == "all") or (analyticConfig.display.lower() == "alerts" and anyValueExceptions):
combined[col] = graphDF[col]
if analyticConfig.rolling_average_samples is not None:
if (analyticConfig.display.lower() == "all"):
combined[col + '_avg'] = graphDF_avg[col]
else:
if (anyAverageExceptions):
combined[col + '_avg'] = graphDF_avg[col]
if (anyValueExceptions and (analyticConfig.min_threshold_percentage is not None or analyticConfig.max_threshold_percentage is not None)):
combined[col + '_avg'] = graphDF_avg[col]
if ((analyticConfig.display.lower() == "all") or (analyticConfig.display.lower() == "alerts" and anyValueExceptions)):
combined[col + '_warn'] = exceptional_values.dropna()
seriesConfig[col + '_warn'] = {
"mode" : "markers",
"marker" : {
"symbol" : "hash-open",
"color" : "red"
}
}
if ((analyticConfig.display.lower() == "all") or (analyticConfig.display.lower() == "alerts" and anyAverageExceptions)):
combined[col + '_avg_warn'] = exceptional_average.dropna()
seriesConfig[col + '_avg_warn'] = {
"mode" : "markers",
"marker" : {
"symbol" : "hash-open",
"color" : "red"
}
}
timelyAlert = None
if not combined.empty:
alertAnalyticConfig = TimelyAnalyticConfiguration(analyticConfig)
if alertAnalyticConfig.groupByColumn is None:
alertAnalyticConfig.groupByColumn = timelyMetric.metric + "_obs"
combined = TimelyMetric.unpivot(combined, timelyMetric.metric, groupByColumn=alertAnalyticConfig.groupByColumn)
combined = combined.sort_index()
combined['date'] = combined.index.values
combined = combined.sort_values(['date', alertAnalyticConfig.groupByColumn])
combined = combined.drop(['date'], 1)
combined = combined.dropna()
combined = DataOperations.ensureMinSeriesLength(combined, alertAnalyticConfig.groupByColumn)
message = DataOperations.getTitle(timelyMetric, analyticConfig, separator=', ')
timelyAlert = TimelyAlert(timelyMetric, combined, message, seriesConfig, alertAnalyticConfig, notebook)
return timelyAlert
@@ -0,0 +1,88 @@
import pandas
class TimelyAnalyticConfiguration():
def __init__(self, analyticConfig):
if isinstance(analyticConfig, dict):
self.groupByColumn = analyticConfig.get('groupByColumn', None)
self.includeColRegex = analyticConfig.get('includeColRegex', None)
self.excludeColRegex = analyticConfig.get('excludeColRegex', None)
self.counter = analyticConfig.get('counter', False)
self.sample_period = analyticConfig.get('sample', None)
self.how = analyticConfig.get('how', 'mean')
self.interpolate = analyticConfig.get('interpolate', True)
self.fill = analyticConfig.get('fill', None)
self.rolling_average_period = analyticConfig.get('rolling_average_period', None)
self.min_threshold = analyticConfig.get('min_threshold', None)
self.average_min_threshold = analyticConfig.get('average_min_threshold', None)
self.max_threshold = analyticConfig.get('max_threshold', None)
self.average_max_threshold = analyticConfig.get('average_max_threshold', None)
self.min_threshold_percentage = analyticConfig.get('min_threshold_percentage', None)
self.max_threshold_percentage = analyticConfig.get('max_threshold_percentage', None)
self.min_alert_period = analyticConfig.get('min_alert_period', None)
boolean = analyticConfig.get('boolean', 'or')
self.orCondition = boolean == 'or' or boolean == 'OR'
# alerts or all
self.display = analyticConfig.get('display', 'alerts')
self.send_alerts_to = analyticConfig.get('send_alerts_to', [])
self.output_dir = analyticConfig.get('output_dir', '/tmp')
self.last_alert = analyticConfig.get('last_alert', None)
self.system_name = analyticConfig.get('system_name', None)
self.sample = None
self.sample_minutes = None
if self.sample_period is not None:
td = pandas.to_timedelta(self.sample_period)
self.sample_minutes = int(td.total_seconds() / 60)
self.sample = str(self.sample_minutes) + 'min'
self.rolling_average_samples = None
self.rolling_average_minutes = None
if (self.rolling_average_period is not None) and (self.sample_minutes is not None):
td = pandas.to_timedelta(self.rolling_average_period)
self.rolling_average_minutes = int(td.total_seconds() / 60)
self.rolling_average_samples = int(self.rolling_average_minutes / self.sample_minutes)
self.min_alert_minutes = None
if self.min_alert_period is not None:
td = pandas.to_timedelta(self.min_alert_period)
self.min_alert_minutes = int(td.total_seconds() / 60)
self.last_alert_minutes = None
if self.last_alert is not None:
td = pandas.to_timedelta(self.last_alert)
self.last_alert_minutes = int(td.total_seconds() / 60)
elif isinstance(analyticConfig, TimelyAnalyticConfiguration):
self.groupByColumn = analyticConfig.groupByColumn
self.includeColRegex = analyticConfig.includeColRegex
self.excludeColRegex = analyticConfig.excludeColRegex
self.counter = analyticConfig.counter
self.sample_period = analyticConfig.sample_period
self.sample_minutes = analyticConfig.sample_minutes
self.sample = analyticConfig.sample
self.how = analyticConfig.how
self.interpolate = analyticConfig.interpolate
self.fill = analyticConfig.fill
self.rolling_average_period = analyticConfig.rolling_average_period
self.rolling_average_samples = analyticConfig.rolling_average_samples
self.rolling_average_minutes = analyticConfig.rolling_average_minutes
self.min_threshold = analyticConfig.min_threshold
self.average_min_threshold = analyticConfig.average_min_threshold
self.max_threshold = analyticConfig.max_threshold
self.average_max_threshold = analyticConfig.average_max_threshold
self.min_threshold_percentage = analyticConfig.min_threshold_percentage
self.max_threshold_percentage = analyticConfig.max_threshold_percentage
self.min_alert_period = analyticConfig.min_alert_period
self.min_alert_minutes = analyticConfig.min_alert_minutes
self.orCondition = analyticConfig.orCondition
# alerts or all
self.display = analyticConfig.display
self.send_alerts_to = analyticConfig.send_alerts_to
self.output_dir = analyticConfig.output_dir
self.last_alert = analyticConfig.last_alert
self.last_alert_minutes = analyticConfig.last_alert_minutes
self.system_name = analyticConfig.system_name
@@ -0,0 +1,231 @@
from TimeDateRange import TimeDateRange
from TimeDateRange import TimeDateError
from TimeDateRange import UTC
from DataOperations import *
import json
import pandas
import getopt
import sys
import random
from WebSocketClient import WebSocketClient
from tornado import ioloop
utc = UTC()
class TimelyWebSocketClient(WebSocketClient):
def __init__(self, hostport, metric, tags, startTime, endTime, connect_timeout=WebSocketClient.DEFAULT_CONNECT_TIMEOUT,
request_timeout=WebSocketClient.DEFAULT_REQUEST_TIMEOUT, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
self.metric = metric
self.tags = tags
self.startTime = startTime
self.endTime = endTime
self.subscriptionId = random.randint(1000000000, 9999999999)
WebSocketClient.__init__(self, hostport, '/websocket', {'metric' : metric}, connect_timeout, request_timeout,
client_cert=client_cert, client_key=client_key, ca_certs=ca_certs, validate_cert=validate_cert)
def _on_message(self, msg):
# implement in subclass
pass
def _on_connection_success(self):
print('Connected.')
create = {
"operation": "create",
"subscriptionId": self.subscriptionId,
}
self.send(create)
m1 = {
"operation": "add",
"subscriptionId": self.subscriptionId,
"metric": self.metric,
"startTime": self.startTime,
"endTime" : self.endTime
}
if self.tags is not None:
t = self.tags.strip().split(',')
tagDict = {}
for pair in t:
k = pair.split('=')[0].strip()
v = pair.split('=')[1].strip()
tagDict = dict(tagDict.items() + dict({k : v}).items())
m1["tags"] = tagDict
self.send(m1)
def _on_connection_close(self):
print('Connection closed!')
exit()
def _on_connection_error(self, exception):
print('Connection error: %s', exception)
class TimelyMetric(TimelyWebSocketClient):
client = None
endtime = None
def __init__(self, hostport, metric, tags, beginStr, endStr, period, sample,
client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
self.series = pandas.Series()
self.metric = metric
self.tags = tags
self.sample = sample
self.debug = False
self.dataFrame = None
self.data = []
self.timeDateRange = TimeDateRange(beginStr, endStr, period)
TimelyWebSocketClient.__init__(self, hostport, metric, tags, self.timeDateRange.getBeginMs(), self.timeDateRange.getEndMs(),
client_cert=client_cert, client_key=client_key, ca_certs=ca_certs, validate_cert=validate_cert)
def debugOn(self):
self.debug = True
def debugOff(self):
self.debug = False
def fetch(self):
ioloop.IOLoop().make_current()
self.connect(ioloop.IOLoop.current(instance=False))
ioloop.IOLoop.current(instance=False).start()
return self
def _on_message(self, msg):
global df
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")
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)
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
self.dataFrame = pandas.DataFrame(self.data)
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()
print("Exiting.")
def getDataFrame(self):
return self.dataFrame;
def setDataFrame(self, dataFrame):
self.dataFrame = dataFrame
def print_debug(self):
if self.debug:
print(self.dataFrame)
def graph(self, groupByColumn=None):
return graph(self.dataFrame, self, sample=self.sample, how=self.how, groupByColumn=groupByColumn)
def main():
period = None
beginStr = None
endStr = None
metric = "timely.metrics.received"
sample = None
how = None
tags = None
groupByColumn = None
try:
argv = sys.argv
options, remainder = getopt.getopt(argv[1:], 'h', ['metric=', 'tags=', 'period=', 'groupByColumn=', 'begin=', 'end=', 'sample=', 'how=', 'hostport='])
except getopt.GetoptError:
print('TimelyMetric.py ---hostport=<host:port> --metric=<metric> --tags=<tag1=val1,tag2=val2> --period=<period>, --groupByColumn=<groupByColumn>, --sample=<sample_period> --how=<mean|min|max>')
sys.exit(2)
for opt, arg in options:
if opt == '-h':
print('TimelyMetric.py --hostport=<host:port> --metric=<metric> --tags=<tag1=val1,tag2=val2> --period=<period>, --groupByColumn=<groupByColumn>, --sample=<sample_period> --how=<mean|min|max>')
sys.exit()
elif opt in ("--metric"):
metric = arg
elif opt in ("--period"):
period = arg
elif opt in ("--begin"):
beginStr = arg
elif opt in ("--end"):
endStr = arg
elif opt in ("--groupByColumn"):
groupByColumn = arg
elif opt in ("--sample"):
sample = arg
elif opt in ("--how"):
how = arg
elif opt in ("--hostport"):
hostport = arg
try:
timelyMetric = TimelyMetric(hostport, metric, tags, beginStr, endStr, period, sample).fetch()
dataFrame = timelyMetric.getDataFrame()
if dataFrame is not None:
dataFrame = resample(dataFrame, metric, sample, how)
dataFrame = pivot(dataFrame, metric, groupByColumn=groupByColumn)
graph(dataFrame, timelyMetric, sample=sample, groupByColumn=groupByColumn, graphConfig={}, notebook=False)
except TimeDateError as e:
print(e.message)
if __name__ == '__main__':
main()
@@ -0,0 +1,110 @@
from tornado import escape
from tornado import gen
from tornado import ioloop
from tornado import websocket
import json
class WebSocketClient():
"""Base for web socket clients.
"""
DEFAULT_CONNECT_TIMEOUT = 60
DEFAULT_REQUEST_TIMEOUT = 60
APPLICATION_JSON = 'application/json'
def __init__(self, hostport, path, params, connect_timeout=DEFAULT_CONNECT_TIMEOUT,
request_timeout=DEFAULT_REQUEST_TIMEOUT, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
self.connect_timeout = connect_timeout
self.request_timeout = request_timeout
self.client_cert=client_cert
self.client_key=client_key
self.ca_certs=ca_certs
self.validate_cert=validate_cert
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.
:param str url: server URL.
"""
request = websocket.httpclient.HTTPRequest(self.url, connect_timeout=self.connect_timeout, request_timeout=self.request_timeout, validate_cert=self.validate_cert,
client_cert=self.client_cert, client_key=self.client_key, ca_certs=self.ca_certs)
websocket.websocket_connect(request, io_loop=ioloop, callback=self._connect_callback)
def send(self, data):
"""Send message to the server
:param str data: message.
"""
if not self._ws_connection:
raise RuntimeError('Web socket connection is closed.')
self._ws_connection.write_message(escape.utf8(json.dumps(data)))
def close(self):
"""Close connection.
"""
if not self._ws_connection:
raise RuntimeError('Web socket connection is already closed.')
self._ws_connection.close()
self._ws_connection = None
def _connect_callback(self, future):
if future.exception() is None:
self._ws_connection = future.result()
self._on_connection_success()
self._read_messages()
else:
self._on_connection_error(future.exception())
@gen.coroutine
def _read_messages(self):
while True and not self._ws_connection is None:
msg = yield self._ws_connection.read_message()
if msg is None:
self._on_connection_close()
break
self._on_message(msg)
def _on_message(self, msg):
"""This is called when new message is available from the server.
:param str msg: server message.
"""
pass
def _on_connection_success(self):
"""This is called on successful connection ot the server.
"""
pass
def _on_connection_close(self):
"""This is called when server closed the connection.
"""
pass
def _on_connection_error(self, exception):
"""This is called in case if connection to the server could
not established.
"""
pass
Whitespace-only changes.
@@ -0,0 +1,52 @@
import timely.TimelyMetric
import timely.TimelyAnalytic
import timely.DataOperations
import os
from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
hostport = "127.0.0.1:5244"
client_cert = None
client_key = None
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None, client_cert=client_cert, client_key=client_key).fetch()
print(timelyMetric.getDataFrame())
exit()
analyticConfig = TimelyAnalyticConfiguration({
'system_name' : 'TestSystem',
'counter' : False,
'includeColRegex' : None,
'excludeColRegex' : None,
'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' : None,
'max_threshold_percentage' : None,
'boolean' : 'and',
'min_alert_period' : None,
'last_alert' : None,
'display' : 'all',
'output_dir' : '/path/to/output'
})
alert = timely.TimelyAnalytic.find_alerts(timelyMetric, analyticConfig)
if alert is not None:
# write graph to file
oldmask = os.umask(022)
file = alert.graph(type='html')
os.umask(oldmask)
text = timely.DataOperations.getTitle(timelyMetric, analyticConfig)
# send email with graph attached
alert.email("", "", text, text, [file])
# log to syslog
alert.log(text)
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
</FindBugsFilter>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Bug pattern="DM_DEFAULT_ENCODING"/>
<Method name="connect" />
<Class name="timely.client.tcp.TcpClient"/>
</Match>
</FindBugsFilter>
@@ -0,0 +1,56 @@
package timely.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import timely.serialize.JsonSerializer;
public class SerializationTest {
private ObjectMapper mapper = JsonSerializer.getObjectMapper();
@Test
public void testBasicAuth() throws Exception {
BasicAuthLogin login = new BasicAuthLogin();
login.setUsername("test");
login.setPassword("pass");
testSerialization(login);
}
@Test
public void testCreateSubscription() throws Exception {
CreateSubscription create = new CreateSubscription();
create.setSubscriptionId("1234");
testSerialization(create);
}
@Test
public void testCloseSubscription() throws Exception {
CloseSubscription close = new CloseSubscription();
close.setSubscriptionId("1234");
testSerialization(close);
}
@Test
public void testAddSubscription() throws Exception {
AddSubscription add = new AddSubscription();
add.setSubscriptionId("1234");
add.setMetric("sys.cpu.user");
testSerialization(add);
}
@Test
public void testRemoveSubscription() throws Exception {
RemoveSubscription remove = new RemoveSubscription();
remove.setSubscriptionId("1234");
remove.setMetric("sys.cpu.user");
testSerialization(remove);
}
@SuppressWarnings("unchecked")
private <T> void testSerialization(T o) throws Exception {
String ser = mapper.writeValueAsString(o);
T des = mapper.readValue(ser, (Class<T>) o.getClass());
Assert.assertEquals(o, des);
}
}
@@ -1,14 +1,14 @@
package timely.model;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
/**
*
*
@@ -0,0 +1,40 @@
package timely.model.parse;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import timely.model.Metric;
import timely.model.Tag;
public class MetricParserTest {
@Test
public void testParseWithEscapedCharacters() {
MetricParser parser = new MetricParser();
Metric m = parser.parse("put mymetric 12341234 5.0 tag1=value1,value1 tag2=value2=value2");
Assert.assertEquals("mymetric", m.getName());
Assert.assertEquals(12341234, (long) m.getValue().getTimestamp());
Assert.assertEquals(5.0, (double) m.getValue().getMeasure(), 0);
List<Tag> expected = new ArrayList<>();
expected.add(new Tag("tag1", "value1,value1"));
expected.add(new Tag("tag2", "value2=value2"));
Assert.assertEquals(expected, m.getTags());
}
@Test
public void testParseMalformatted() {
MetricParser parser = new MetricParser();
try {
// parser should throw an exception
parser.parse("put mymetric 12341234 5.0 tag1 tag2=value2");
Assert.fail();
} catch (IllegalArgumentException e) {
}
}
}
@@ -0,0 +1,51 @@
package timely.model.parse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.junit.Assert;
import org.junit.Test;
import timely.model.Tag;
public class TagListParserTest {
@Test
public void testListParse() {
String value = "tag1=value1,tag2=value2";
List<Tag> tags = new TagListParser().parse(value);
Assert.assertEquals(2, tags.size());
Assert.assertEquals(new Tag("tag1", "value1"), tags.get(0));
Assert.assertEquals(new Tag("tag2", "value2"), tags.get(1));
}
@Test
public void testListCombine() {
List<Tag> tags = new ArrayList<>();
tags.add(new Tag("tag1", "value1"));
tags.add(new Tag("tag2", "value2"));
String combined = new TagListParser().combine(tags);
Assert.assertEquals("tag1=value1,tag2=value2", combined);
}
@Test
public void testListCombineMap() {
Map<String, String> map = new TreeMap<>();
map.put("tag1", "value1");
map.put("tag2", "value2");
String combined = new TagListParser().combine(map);
Assert.assertEquals("tag1=value1,tag2=value2", combined);
}
@Test
public void testParseTagsWithCommas() {
try {
String s = "tag1=value1,tag2=3.4.3_(default\\,_Date\\,_Time)_";
new TagListParser().parse(s);
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
}
Loaded 100 of 361 files, more files were not shown because too many files have changed in this diff. Show more