mirror of
https://github.com/NationalSecurityAgency/timely.git
synced 2026-11-10 03:41:25 -10:00
Compare commits
368
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
603160f546 | ||
|
|
729dca23ba | ||
|
|
104d20ad1b | ||
|
|
f0f005f8fa | ||
|
|
8593218b89 | ||
|
|
cbfe6a439f | ||
|
|
455616d568 | ||
|
|
9d3172cc75 | ||
|
|
7aa7b0da2a | ||
|
|
6c61861d83 | ||
|
|
91882ba363 | ||
|
|
ed61b5f305 | ||
|
|
a083c2d632 | ||
|
|
2891794174 | ||
|
|
c99b2471cf | ||
|
|
bb479a58d7 | ||
|
|
ff37d40801 | ||
|
|
a7d980975e | ||
|
|
30f5a0c1b2 | ||
|
|
2f9e6ca2d7 | ||
|
|
954528a460 | ||
|
|
0f7b514b30 | ||
|
|
a6ca46e1c1 | ||
|
|
487d1d4a5c | ||
|
|
e3c26f815c | ||
|
|
bbc1946204 | ||
|
|
991412f99b | ||
|
|
5428e93cb5 | ||
|
|
c18aae5a89 | ||
|
|
6972c46a1a | ||
|
|
4d7bee7566 | ||
|
|
4b11b3d50f | ||
|
|
84b9856ec7 | ||
|
|
c9548a9b9a | ||
|
|
aec7bd4c38 | ||
|
|
e2b8b643f2 | ||
|
|
c19e1d8b2f | ||
|
|
cb73e8e75c | ||
|
|
4c508c1a7d | ||
|
|
c6c2c0d41e | ||
|
|
a24cef78b3 | ||
|
|
08f99f6540 | ||
|
|
be11da7d15 | ||
|
|
4bc5753328 | ||
|
|
2ccb89a06d | ||
|
|
dfc3840ad4 | ||
|
|
6235034d8b | ||
|
|
d8de6ac06f | ||
|
|
f381946abe | ||
|
|
48bbe6a561 | ||
|
|
0f6110ea59 | ||
|
|
461b5e7e78 | ||
|
|
4ec7d94c28 | ||
|
|
77e910ae06 | ||
|
|
a4998af887 | ||
|
|
2858549ede | ||
|
|
57f0e1408f | ||
|
|
151adc025a | ||
|
|
5abd9d7200 | ||
|
|
fa2e211c72 | ||
|
|
a21438976e | ||
|
|
ba37047f3a | ||
|
|
9305470c69 | ||
|
|
200233542e | ||
|
|
3cd78dc624 | ||
|
|
9027cac6e3 | ||
|
|
3663b4e6d5 | ||
|
|
64973d0674 | ||
|
|
5512306f1e | ||
|
|
d35ae08188 | ||
|
|
e448657afb | ||
|
|
1dcd893711 | ||
|
|
409f5375d1 | ||
|
|
c5df8139ca | ||
|
|
dcd23d4fc8 | ||
|
|
cc7326fc19 | ||
|
|
0d2b6e61c8 | ||
|
|
b7cd8765c5 | ||
|
|
a9d27d5b1b | ||
|
|
7d67eb218d | ||
|
|
5fb7e3dd2b | ||
|
|
5a89861cec | ||
|
|
a3bc9a01ee | ||
|
|
dba494b06d | ||
|
|
5826884c3c | ||
|
|
f94c731bbc | ||
|
|
4e042bcb94 | ||
|
|
ab7f499134 | ||
|
|
c098f9f3e4 | ||
|
|
94cfcbc0c4 | ||
|
|
674e1b0ab5 | ||
|
|
4eedb84547 | ||
|
|
bef86eac37 | ||
|
|
27bb16e403 | ||
|
|
e2bbe4e212 | ||
|
|
a95d2d7ea1 | ||
|
|
f751175190 | ||
|
|
648199f330 | ||
|
|
fcfc3f7bc0 | ||
|
|
200bc39902 | ||
|
|
f854c9e394 | ||
|
|
73ddae1f8d | ||
|
|
cb7184503e | ||
|
|
3a357b50ef | ||
|
|
689aee0678 | ||
|
|
32f6b9d4c9 | ||
|
|
512f318387 | ||
|
|
e04cfb310b | ||
|
|
0cecc3b191 | ||
|
|
b3d75e4c68 | ||
|
|
004c344153 | ||
|
|
2590effeee | ||
|
|
f49c83601a | ||
|
|
df035259c9 | ||
|
|
3a8cbd3378 | ||
|
|
39bea1da8d | ||
|
|
1d3f8b7d3a | ||
|
|
0fbbb86768 | ||
|
|
8b807e5ac7 | ||
|
|
710c789984 | ||
|
|
cf0d63a4e7 | ||
|
|
bfc67db185 | ||
|
|
f604314640 | ||
|
|
25c9e93888 | ||
|
|
a323d198e9 | ||
|
|
8e6cc4e0b4 | ||
|
|
3a3d98ec81 | ||
|
|
7599a2ac60 | ||
|
|
3d33ba2e31 | ||
|
|
bbbb453818 | ||
|
|
54951c05e6 | ||
|
|
b63914e02c | ||
|
|
1f386db00a | ||
|
|
49222ac76a | ||
|
|
38f2c6cdfa | ||
|
|
3926e6f066 | ||
|
|
30800a0b5c | ||
|
|
92b23a4c51 | ||
|
|
8030ea59a5 | ||
|
|
a4c8158003 | ||
|
|
acba454ee3 | ||
|
|
30790608ca | ||
|
|
f2f3788f99 | ||
|
|
326b9dc8f1 | ||
|
|
3f3b605c63 | ||
|
|
5580a2fa66 | ||
|
|
1a54c3ada3 | ||
|
|
2080764e0d | ||
|
|
e7dade04ee | ||
|
|
7ed337483e | ||
|
|
b070bc3c47 | ||
|
|
51f9103741 | ||
|
|
a97fa83e2f | ||
|
|
0b5c3544db | ||
|
|
186b60756a | ||
|
|
133a6bf5cf | ||
|
|
b684d7eefe | ||
|
|
73f1603f1e | ||
|
|
9267e0c915 | ||
|
|
1ce49628e5 | ||
|
|
acad3af083 | ||
|
|
b4635f1a55 | ||
|
|
4c1ede04df | ||
|
|
2841b0a3e9 | ||
|
|
6efa13909f | ||
|
|
368c572c67 | ||
|
|
554baf6af7 | ||
|
|
1ef11799be | ||
|
|
16b664297d | ||
|
|
4277b63bc3 | ||
|
|
5a9ad3cb27 | ||
|
|
f990e89ab3 | ||
|
|
c01ac80f23 | ||
|
|
35e8f99d09 | ||
|
|
847144d236 | ||
|
|
b3bfbc1e1f | ||
|
|
8d86e3894a | ||
|
|
99b9181902 | ||
|
|
3c0bf87b79 | ||
|
|
d2ba7f635e | ||
|
|
92510fdc83 | ||
|
|
4eac17fbb5 | ||
|
|
328ae075f4 | ||
|
|
cf12295ac8 | ||
|
|
df180504f2 | ||
|
|
021ff832ef | ||
|
|
6fc9066e70 | ||
|
|
f11b82b662 | ||
|
|
dcfd165a9b | ||
|
|
fd98558218 | ||
|
|
f47cd66ec2 | ||
|
|
ec4ba6e7f5 | ||
|
|
e698734e4b | ||
|
|
78b49fe44e | ||
|
|
f109f04162 | ||
|
|
4bf68fe328 | ||
|
|
9eb8da8e80 | ||
|
|
f54705374e | ||
|
|
5e166f0044 | ||
|
|
17527af6b4 | ||
|
|
64f7890f8b | ||
|
|
e7c058ef56 | ||
|
|
6415d100fc | ||
|
|
046000c16a | ||
|
|
d0f6534296 | ||
|
|
07c04425e1 | ||
|
|
113b6464f3 | ||
|
|
e2eda4c74a | ||
|
|
68b8d6cd51 | ||
|
|
458028d5df | ||
|
|
320f291785 | ||
|
|
95c2654357 | ||
|
|
045964c01d | ||
|
|
2f8759e32b | ||
|
|
451f001b9f | ||
|
|
044f6576fd | ||
|
|
520edee16f | ||
|
|
8034a673fc | ||
|
|
eb9e874a57 | ||
|
|
1658312512 | ||
|
|
2fdfed7554 | ||
|
|
e90340be09 | ||
|
|
9770dce1cd | ||
|
|
8f98199a37 | ||
|
|
425616d536 | ||
|
|
158ee4fd55 | ||
|
|
89e8fd7ebd | ||
|
|
cb5794491a | ||
|
|
8b4f0d5764 | ||
|
|
c90b86c4c9 | ||
|
|
f104d02dc6 | ||
|
|
ac85ad1aa7 | ||
|
|
21f3c20a5f | ||
|
|
b43d877035 | ||
|
|
8f46ee052c | ||
|
|
6cda9c4a60 | ||
|
|
cfc3e491d6 | ||
|
|
23668b294f | ||
|
|
beceb678ea | ||
|
|
4257e28233 | ||
|
|
fb007c170e | ||
|
|
cabeb2c0d7 | ||
|
|
e4a2b011a4 | ||
|
|
8f6dfd1644 | ||
|
|
33fb3dc857 | ||
|
|
186edeb0f2 | ||
|
|
a034223376 | ||
|
|
9c75c75aa5 | ||
|
|
c8646a8d59 | ||
|
|
b28cb4451b | ||
|
|
a4b9a1034c | ||
|
|
12e64316eb | ||
|
|
a2389435d4 | ||
|
|
6a0455b1cd | ||
|
|
7abc9e68e4 | ||
|
|
e45dcb0450 | ||
|
|
a8ce05000e | ||
|
|
747d3e7ad1 | ||
|
|
fcf0ec0b87 | ||
|
|
976c6da07c | ||
|
|
ed96a0a8e8 | ||
|
|
d5c3bbbcc5 | ||
|
|
ec1ac9af73 | ||
|
|
2a2d11d01b | ||
|
|
14eba0fb93 | ||
|
|
418afe8f87 | ||
|
|
2b39ec3ab5 | ||
|
|
2ff895b147 | ||
|
|
16a6223c48 | ||
|
|
e928ac3746 | ||
|
|
8f5d54ebd4 | ||
|
|
123f63972e | ||
|
|
dd30d54ed9 | ||
|
|
d37bf1c824 | ||
|
|
6cd4a8bd67 | ||
|
|
1fa71e0c6a | ||
|
|
c5efeb5be9 | ||
|
|
2c8fa5da44 | ||
|
|
9cbc94eb58 | ||
|
|
4ce4482764 | ||
|
|
49a8d970fe | ||
|
|
1be6f24676 | ||
|
|
82b94f1a71 | ||
|
|
019c5789ce | ||
|
|
4336defc14 | ||
|
|
712a2100bb | ||
|
|
bc46f54604 | ||
|
|
1b713e7874 | ||
|
|
3f5dfd2219 | ||
|
|
ea707fda42 | ||
|
|
b245be476a | ||
|
|
9619530814 | ||
|
|
adf617e59f | ||
|
|
3f49855840 | ||
|
|
053d15c559 | ||
|
|
b3a479636b | ||
|
|
8539986faf | ||
|
|
cbdf28c510 | ||
|
|
670cc7769d | ||
|
|
22a9be3ba2 | ||
|
|
2836b03366 | ||
|
|
64bde2189e | ||
|
|
4d41c8e19a | ||
|
|
08a095e349 | ||
|
|
fc0f2b8356 | ||
|
|
e9c0f63478 | ||
|
|
9c01d00686 | ||
|
|
dd8d379292 | ||
|
|
1acd5ce5cb | ||
|
|
aa3ac901b6 | ||
|
|
2c6270f23e | ||
|
|
f6728b4e8d | ||
|
|
fb0590d778 | ||
|
|
f94ec78c66 | ||
|
|
3fddbb4c6f | ||
|
|
e841ac90b8 | ||
|
|
203a97d7db | ||
|
|
4d19538c77 | ||
|
|
e83356cb74 | ||
|
|
5f1beb6045 | ||
|
|
12ee77386b | ||
|
|
accbb2af9a | ||
|
|
041cee1a80 | ||
|
|
f8efdbf240 | ||
|
|
ca4155be6f | ||
|
|
b3c7b143e0 | ||
|
|
42d31efff8 | ||
|
|
56e90ef693 | ||
|
|
6345efa996 | ||
|
|
701f841cfe | ||
|
|
4b6de88e25 | ||
|
|
de9b0df49e | ||
|
|
b8418e4e19 | ||
|
|
d5cec191e2 | ||
|
|
13feefde31 | ||
|
|
86c9d8835d | ||
|
|
b8da5152e0 | ||
|
|
e73564c578 | ||
|
|
f42696ddfc | ||
|
|
7e3a85050d | ||
|
|
b022c5539e | ||
|
|
e4bdea4ee9 | ||
|
|
60325e1821 | ||
|
|
69ca6a8691 | ||
|
|
b18126ccd1 | ||
|
|
8d41f6305d | ||
|
|
ad223941e2 | ||
|
|
99b3635e76 | ||
|
|
f912458faa | ||
|
|
097cbf485b | ||
|
|
f85f77814a | ||
|
|
e7d38a07ff | ||
|
|
66a64241d9 | ||
|
|
c78a27bc71 | ||
|
|
b6cdadaaee | ||
|
|
a6b88f18ab | ||
|
|
b101208828 | ||
|
|
368a33b545 | ||
|
|
7850c49054 | ||
|
|
d97dd7971e | ||
|
|
6f61ffcf33 | ||
|
|
d6c9dc1a27 | ||
|
|
2dbd0b61b7 | ||
|
|
c7e1567e5a | ||
|
|
e661cbebc1 | ||
|
|
4e4f1911af | ||
|
|
45975462bd | ||
|
|
9851638c32 |
No files matched your search
@@ -1,9 +0,0 @@
|
||||
If this is a question or feature request, make sure to:
|
||||
|
||||
- [ ] The title starts with `Question:` or `Feature:`.
|
||||
|
||||
If this is an bug report, not a question, make sure to:
|
||||
|
||||
- [ ] I'm not running Windows (which is unsupported), or if I am, I can confirm this issue appears on another platform, or Vagrant.
|
||||
- [ ] This issue appears in the latest `dev` branch.
|
||||
- [ ] I've included my browser and Ruby version in this issue.
|
||||
@@ -1,5 +0,0 @@
|
||||
!!!!! STOP AND READ !!!!!
|
||||
|
||||
If the dropdown above says "base fork: lord/master", you are submitting your change to ALL USERS OF SLATE, not just your company. This is probably not what you want. Click "base fork" to change it to the right place.
|
||||
|
||||
If you're actually trying to submit a change to upstream Slate, please submit to our dev branch, PRs sent to the master branch are generally rejected.
|
||||
+8
-23
@@ -1,25 +1,10 @@
|
||||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
coverage
|
||||
InstalledFiles
|
||||
lib/bundler/man
|
||||
pkg
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
*.DS_STORE
|
||||
build/
|
||||
.cache
|
||||
.vagrant
|
||||
.sass-cache
|
||||
|
||||
# YARD artifacts
|
||||
.yardoc
|
||||
_yardoc
|
||||
doc/
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
target/
|
||||
.idea/
|
||||
*.iml
|
||||
logs/
|
||||
server/pki/
|
||||
client/src/main/python/setup.py
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
sudo: false
|
||||
|
||||
language: ruby
|
||||
|
||||
rvm:
|
||||
- 2.2.5
|
||||
- 2.3.1
|
||||
|
||||
cache: bundler
|
||||
script: bundle exec middleman build
|
||||
@@ -1,88 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## Version 1.4.0
|
||||
|
||||
*November 24, 2016*
|
||||
|
||||
- Upgrade Middleman and Rouge gems, should hopefully solve a number of bugs
|
||||
- Update some links in README
|
||||
- Fix broken Vagrant startup script
|
||||
- Fix some problems with deploy.sh help message
|
||||
- Fix bug with language tabs not hiding properly if no error
|
||||
- Add `!default` to SASS variables
|
||||
- Fix bug with logo margin
|
||||
- Bump tested Ruby versions in .travis.yml
|
||||
|
||||
## Version 1.3.3
|
||||
|
||||
*June 11, 2016*
|
||||
|
||||
Documentation and example changes.
|
||||
|
||||
## Version 1.3.2
|
||||
|
||||
*February 3, 2016*
|
||||
|
||||
A small bugfix for slightly incorrect background colors on code samples in some cases.
|
||||
|
||||
## Version 1.3.1
|
||||
|
||||
*January 31, 2016*
|
||||
|
||||
A small bugfix for incorrect whitespace in code blocks.
|
||||
|
||||
## Version 1.3
|
||||
|
||||
*January 27, 2016*
|
||||
|
||||
We've upgraded Middleman and a number of other dependencies, which should fix quite a few bugs.
|
||||
|
||||
Instead of `rake build` and `rake deploy`, you should now run `bundle exec middleman build --clean` to build your server, and `./deploy.sh` to deploy it to Github Pages.
|
||||
|
||||
## Version 1.2
|
||||
|
||||
*June 20, 2015*
|
||||
|
||||
**Fixes:**
|
||||
|
||||
- Remove crash on invalid languages
|
||||
- Update Tocify to scroll to the highlighted header in the Table of Contents
|
||||
- Fix variable leak and update search algorithms
|
||||
- Update Python examples to be valid Python
|
||||
- Update gems
|
||||
- More misc. bugfixes of Javascript errors
|
||||
- Add Dockerfile
|
||||
- Remove unused gems
|
||||
- Optimize images, fonts, and generated asset files
|
||||
- Add chinese font support
|
||||
- Remove RedCarpet header ID patch
|
||||
- Update language tabs to not disturb existing query strings
|
||||
|
||||
## Version 1.1
|
||||
|
||||
*July 27, 2014*
|
||||
|
||||
**Fixes:**
|
||||
|
||||
- Finally, a fix for the redcarpet upgrade bug
|
||||
|
||||
## Version 1.0
|
||||
|
||||
*July 2, 2014*
|
||||
|
||||
[View Issues](https://github.com/tripit/slate/issues?milestone=1&state=closed)
|
||||
|
||||
**Features:**
|
||||
|
||||
- Responsive designs for phones and tablets
|
||||
- Started tagging versions
|
||||
|
||||
**Fixes:**
|
||||
|
||||
- Fixed 'unrecognized expression' error
|
||||
- Fixed #undefined hash bug
|
||||
- Fixed bug where the current language tab would be unselected
|
||||
- Fixed bug where tocify wouldn't highlight the current section while searching
|
||||
- Fixed bug where ids of header tags would have special characters that caused problems
|
||||
- Updated layout so that pages with disabled search wouldn't load search.js
|
||||
- Cleaned up Javascript
|
||||
@@ -0,0 +1,6 @@
|
||||
Dave Marion ([email protected])
|
||||
Eric Newton ([email protected])
|
||||
Jim Klucar ([email protected])
|
||||
Charlie Williams ([email protected])
|
||||
Drew Farris ([email protected])
|
||||
Brian Loss ([email protected])
|
||||
@@ -1,9 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Middleman
|
||||
gem 'middleman', '~>4.1.0'
|
||||
gem 'middleman-syntax', '~> 3.0.0'
|
||||
gem 'middleman-autoprefixer', '~> 2.7.0'
|
||||
gem "middleman-sprockets", "~> 4.0.0"
|
||||
gem 'rouge', '~> 2.0.5'
|
||||
gem 'redcarpet', '~> 3.3.2'
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (4.2.7.1)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.4.0)
|
||||
autoprefixer-rails (6.4.1)
|
||||
execjs
|
||||
backports (3.6.8)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.10.0)
|
||||
compass-import-once (1.0.5)
|
||||
sass (>= 3.2, < 3.5)
|
||||
concurrent-ruby (1.0.2)
|
||||
contracts (0.13.0)
|
||||
dotenv (2.1.1)
|
||||
erubis (2.7.0)
|
||||
execjs (2.7.0)
|
||||
fast_blank (1.0.0)
|
||||
fastimage (2.0.0)
|
||||
addressable (~> 2)
|
||||
ffi (1.9.14)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
hamster (3.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
hashie (3.4.4)
|
||||
i18n (0.7.0)
|
||||
json (1.8.3)
|
||||
kramdown (1.12.0)
|
||||
listen (3.0.8)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
memoist (0.15.0)
|
||||
middleman (4.1.10)
|
||||
coffee-script (~> 2.2)
|
||||
compass-import-once (= 1.0.5)
|
||||
haml (>= 4.0.5)
|
||||
kramdown (~> 1.2)
|
||||
middleman-cli (= 4.1.10)
|
||||
middleman-core (= 4.1.10)
|
||||
sass (>= 3.4.0, < 4.0)
|
||||
middleman-autoprefixer (2.7.0)
|
||||
autoprefixer-rails (>= 6.3.1, < 7.0.0)
|
||||
middleman-core (>= 3.3.3)
|
||||
middleman-cli (4.1.10)
|
||||
thor (>= 0.17.0, < 2.0)
|
||||
middleman-core (4.1.10)
|
||||
activesupport (~> 4.2)
|
||||
addressable (~> 2.3)
|
||||
backports (~> 3.6)
|
||||
bundler (~> 1.1)
|
||||
contracts (~> 0.13.0)
|
||||
dotenv
|
||||
erubis
|
||||
execjs (~> 2.0)
|
||||
fast_blank
|
||||
fastimage (~> 2.0)
|
||||
hamster (~> 3.0)
|
||||
hashie (~> 3.4)
|
||||
i18n (~> 0.7.0)
|
||||
listen (~> 3.0.0)
|
||||
memoist (~> 0.14)
|
||||
padrino-helpers (~> 0.13.0)
|
||||
parallel
|
||||
rack (>= 1.4.5, < 2.0)
|
||||
sass (>= 3.4)
|
||||
servolux
|
||||
tilt (~> 1.4.1)
|
||||
uglifier (~> 3.0)
|
||||
middleman-sprockets (4.0.0)
|
||||
middleman-core (~> 4.0)
|
||||
sprockets (>= 3.0)
|
||||
middleman-syntax (3.0.0)
|
||||
middleman-core (>= 3.2)
|
||||
rouge (~> 2.0)
|
||||
minitest (5.9.0)
|
||||
padrino-helpers (0.13.3.1)
|
||||
i18n (~> 0.6, >= 0.6.7)
|
||||
padrino-support (= 0.13.3.1)
|
||||
tilt (>= 1.4.1, < 3)
|
||||
padrino-support (0.13.3.1)
|
||||
activesupport (>= 3.1)
|
||||
parallel (1.9.0)
|
||||
rack (1.6.4)
|
||||
rb-fsevent (0.9.7)
|
||||
rb-inotify (0.9.7)
|
||||
ffi (>= 0.5.0)
|
||||
redcarpet (3.3.4)
|
||||
rouge (2.0.5)
|
||||
sass (3.4.22)
|
||||
servolux (0.12.0)
|
||||
sprockets (3.7.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (1.4.1)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (3.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
middleman (~> 4.1.0)
|
||||
middleman-autoprefixer (~> 2.7.0)
|
||||
middleman-sprockets (~> 4.0.0)
|
||||
middleman-syntax (~> 3.0.0)
|
||||
redcarpet (~> 3.3.2)
|
||||
rouge (~> 2.0.5)
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.5
|
||||
@@ -1,13 +1,203 @@
|
||||
Copyright 2008-2013 Concur Technologies, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
@@ -1,110 +1,8 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/lord/img/master/logo-slate.png" alt="Slate: API Documentation Generator" width="226">
|
||||
<br>
|
||||
<a href="https://travis-ci.org/lord/slate"><img src="https://travis-ci.org/lord/slate.svg?branch=master" alt="Build Status"></a>
|
||||
</p>
|
||||

|
||||
|
||||
<p align="center">Slate helps you create beautiful, intelligent, responsive API documentation.</p>
|
||||
[![Apache License][li]][ll]
|
||||
|
||||
<p align="center"><img src="https://dl.dropboxusercontent.com/u/95847291/github%20images/slate/slate_screenshot_new.png" width=700 alt="Screenshot of Example Documentation created with Slate"></p>
|
||||
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/).
|
||||
|
||||
<p align="center"><em>The example above was created with Slate. Check it out at <a href="https://lord.github.io/slate">lord.github.io/slate</a>.</em></p>
|
||||
|
||||
Features
|
||||
------------
|
||||
|
||||
* **Clean, intuitive design** — With Slate, the description of your API is on the left side of your documentation, and all the code examples are on the right side. Inspired by [Stripe's](https://stripe.com/docs/api) and [Paypal's](https://developer.paypal.com/webapps/developer/docs/api/) API docs. Slate is responsive, so it looks great on tablets, phones, and even in print.
|
||||
|
||||
* **Everything on a single page** — Gone are the days when your users had to search through a million pages to find what they wanted. Slate puts the entire documentation on a single page. We haven't sacrificed linkability, though. As you scroll, your browser's hash will update to the nearest header, so linking to a particular point in the documentation is still natural and easy.
|
||||
|
||||
* **Slate is just Markdown** — When you write docs with Slate, you're just writing Markdown, which makes it simple to edit and understand. Everything is written in Markdown — even the code samples are just Markdown code blocks.
|
||||
|
||||
* **Write code samples in multiple languages** — If your API has bindings in multiple programming languages, you can easily put in tabs to switch between them. In your document, you'll distinguish different languages by specifying the language name at the top of each code block, just like with Github Flavored Markdown.
|
||||
|
||||
* **Out-of-the-box syntax highlighting** for [over 100 languages](https://github.com/jneen/rouge/wiki/List-of-supported-languages-and-lexers), no configuration required.
|
||||
|
||||
* **Automatic, smoothly scrolling table of contents** on the far left of the page. As you scroll, it displays your current position in the document. It's fast, too. We're using Slate at TripIt to build documentation for our new API, where our table of contents has over 180 entries. We've made sure that the performance remains excellent, even for larger documents.
|
||||
|
||||
* **Let your users update your documentation for you** — By default, your Slate-generated documentation is hosted in a public Github repository. Not only does this mean you get free hosting for your docs with Github Pages, but it also makes it simple for other developers to make pull requests to your docs if they find typos or other problems. Of course, if you don't want to use GitHub, you're also welcome to host your docs elsewhere.
|
||||
|
||||
Getting started with Slate is super easy! Simply fork this repository and follow the instructions below. Or, if you'd like to check out what Slate is capable of, take a look at the [sample docs](http://lord.github.io/slate).
|
||||
|
||||
Getting Started with Slate
|
||||
------------------------------
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You're going to need:
|
||||
|
||||
- **Linux or OS X** — Windows may work, but is unsupported.
|
||||
- **Ruby, version 2.2.5 or newer**
|
||||
- **Bundler** — If Ruby is already installed, but the `bundle` command doesn't work, just run `gem install bundler` in a terminal.
|
||||
|
||||
### Getting Set Up
|
||||
|
||||
1. Fork this repository on Github.
|
||||
2. Clone *your forked repository* (not our original one) to your hard drive with `git clone https://github.com/YOURUSERNAME/slate.git`
|
||||
3. `cd slate`
|
||||
4. Initialize and start Slate. You can either do this locally, or with Vagrant:
|
||||
|
||||
```shell
|
||||
# either run this to run locally
|
||||
bundle install
|
||||
bundle exec middleman server
|
||||
|
||||
# OR run this to run with vagrant
|
||||
vagrant up
|
||||
```
|
||||
|
||||
You can now see the docs at http://localhost:4567. Whoa! That was fast!
|
||||
|
||||
Now that Slate is all set up on your machine, you'll probably want to learn more about [editing Slate markdown](https://github.com/lord/slate/wiki/Markdown-Syntax), or [how to publish your docs](https://github.com/lord/slate/wiki/Deploying-Slate).
|
||||
|
||||
If you'd prefer to use Docker, instructions are available [in the wiki](https://github.com/lord/slate/wiki/Docker).
|
||||
|
||||
Companies Using Slate
|
||||
---------------------------------
|
||||
|
||||
* [NASA](https://api.nasa.gov)
|
||||
* [IBM](https://docs.cloudant.com/api.html)
|
||||
* [Sony](http://developers.cimediacloud.com)
|
||||
* [Mozilla](http://mozilla.github.io/localForage/)
|
||||
* [Best Buy](https://bestbuyapis.github.io/api-documentation/)
|
||||
* [Travis-CI](https://docs.travis-ci.com/api/)
|
||||
* [Greenhouse](https://developers.greenhouse.io/harvest.html)
|
||||
* [Woocommerce](http://woothemes.github.io/woocommerce-rest-api-docs/)
|
||||
* [Appium](http://appium.io/slate/en/master)
|
||||
* [Dwolla](https://docs.dwolla.com/)
|
||||
* [Clearbit](https://clearbit.com/docs)
|
||||
* [Coinbase](https://developers.coinbase.com/api)
|
||||
* [Parrot Drones](http://developer.parrot.com/docs/bebop/)
|
||||
* [Fidor Bank](http://docs.fidor.de/)
|
||||
|
||||
You can view more in [the list on the wiki](https://github.com/lord/slate/wiki/Slate-in-the-Wild).
|
||||
|
||||
Need Help? Found a bug?
|
||||
--------------------
|
||||
|
||||
[Submit an issue](https://github.com/lord/slate/issues) to the Slate Github if you need any help. And, of course, feel free to submit pull requests with bug fixes or changes.
|
||||
|
||||
Contributors
|
||||
--------------------
|
||||
|
||||
Slate was built by [Robert Lord](https://lord.io) while interning at [TripIt](https://www.tripit.com/).
|
||||
|
||||
Thanks to the following people who have submitted major pull requests:
|
||||
|
||||
- [@chrissrogers](https://github.com/chrissrogers)
|
||||
- [@bootstraponline](https://github.com/bootstraponline)
|
||||
- [@realityking](https://github.com/realityking)
|
||||
- [@cvkef](https://github.com/cvkef)
|
||||
|
||||
Also, thanks to [Sauce Labs](http://saucelabs.com) for helping sponsor the project.
|
||||
|
||||
Special Thanks
|
||||
--------------------
|
||||
- [Middleman](https://github.com/middleman/middleman)
|
||||
- [jquery.tocify.js](https://github.com/gfranko/jquery.tocify.js)
|
||||
- [middleman-syntax](https://github.com/middleman/middleman-syntax)
|
||||
- [middleman-gh-pages](https://github.com/edgecase/middleman-gh-pages)
|
||||
- [Font Awesome](http://fortawesome.github.io/Font-Awesome/)
|
||||
[li]: https://img.shields.io/badge/license-ASL-blue.svg
|
||||
[ll]: https://www.apache.org/licenses/LICENSE-2.0
|
||||
Vendored
-39
@@ -1,39 +0,0 @@
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.network :forwarded_port, guest: 4567, host: 4567
|
||||
|
||||
config.vm.provision "bootstrap",
|
||||
type: "shell",
|
||||
inline: <<-SHELL
|
||||
sudo apt-get update
|
||||
sudo apt-get install -yq ruby2.0 ruby2.0-dev pkg-config build-essential nodejs git libxml2-dev libxslt-dev
|
||||
sudo apt-get autoremove -yq
|
||||
gem2.0 install --no-ri --no-rdoc bundler
|
||||
SHELL
|
||||
|
||||
# add the local user git config to the vm
|
||||
config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig"
|
||||
|
||||
config.vm.provision "install",
|
||||
type: "shell",
|
||||
privileged: false,
|
||||
inline: <<-SHELL
|
||||
echo "=============================================="
|
||||
echo "Installing app dependencies"
|
||||
cd /vagrant
|
||||
bundle config build.nokogiri --use-system-libraries
|
||||
bundle install
|
||||
SHELL
|
||||
|
||||
config.vm.provision "run",
|
||||
type: "shell",
|
||||
privileged: false,
|
||||
run: "always",
|
||||
inline: <<-SHELL
|
||||
echo "=============================================="
|
||||
echo "Starting up middleman at http://localhost:4567"
|
||||
echo "If it does not come up, check the ~/middleman.log file for any error messages"
|
||||
cd /vagrant
|
||||
bundle exec middleman server --watcher-force-polling --watcher_latency=1 &> ~/middleman.log &
|
||||
SHELL
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
/bin/
|
||||
/target
|
||||
@@ -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
|
||||
@@ -0,0 +1,257 @@
|
||||
<?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.3.3-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>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<compilerId>jdt</compilerId>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.tycho</groupId>
|
||||
<artifactId>tycho-compiler-jdt</artifactId>
|
||||
<version>1.4.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();
|
||||
}
|
||||
}
|
||||
@@ -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
Executable
+11
@@ -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
|
||||
Executable
+21
@@ -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 $*
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
BASE_DIR=${THIS_DIR}/../..
|
||||
TMP_DIR="${BASE_DIR}/tmp"
|
||||
CONF_DIR="${BASE_DIR}/conf"
|
||||
LIB_DIR="${BASE_DIR}/lib"
|
||||
|
||||
. ${THIS_DIR}/load-test-env.sh
|
||||
|
||||
export CLASSPATH="${CONF_DIR}:${THIS_DIR}:${LIB_DIR}/*"
|
||||
JVM_ARGS="-Xmx1G -Xms1G"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricQueryLoadTest $*"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.testing.MetricQueryLoadTest $*
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
|
||||
else
|
||||
THIS_SCRIPT=`readlink -f $0`
|
||||
fi
|
||||
|
||||
THIS_DIR="${THIS_SCRIPT%/*}"
|
||||
|
||||
sed -i -- "s/#.*#/#$1#/g" $THIS_DIR/load-test-data.txt
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/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-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
|
||||
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.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} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
|
||||
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} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
|
||||
|
||||
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer"
|
||||
$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn" monitorInterval="60">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="error" includeLocation="false">
|
||||
<AppenderRef ref="Console" level="error" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,134 @@
|
||||
timely-balancer:
|
||||
zooKeeper:
|
||||
servers: localhost:2181
|
||||
timeout: 30s
|
||||
checkServerHealthInterval: 10000
|
||||
serverFailuresBeforeDown: 6
|
||||
serverSuccessesBeforeUp: 1
|
||||
assignmentFile: /timely/balancer/assignments.csv
|
||||
security:
|
||||
session-max-age: 86400
|
||||
cache-expiration-minutes: 60
|
||||
cache-refresh-minutes: 5
|
||||
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:
|
||||
jwtCheckKeyStore:
|
||||
jwtCheckKeyType:
|
||||
jwtCheckKeyPassword:
|
||||
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
|
||||
incomingBufferSize: 50000000
|
||||
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
|
||||
@@ -0,0 +1,291 @@
|
||||
<?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.3.3-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-boringssl-static</artifactId>
|
||||
<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-boringssl-static</artifactId>
|
||||
<version>${version.netty-tcnative}</version>
|
||||
<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>
|
||||
</project>
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
<id>dist</id>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>README*</include>
|
||||
<include>LICENSE*</include>
|
||||
<include>NOTICE*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>timely*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/lib</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/bin</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/conf</directory>
|
||||
<outputDirectory>conf</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>${project.basedir}/src/main/resources/webapp</directory>
|
||||
<outputDirectory>bin/webapp</outputDirectory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,121 @@
|
||||
package timely.balancer;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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(ScheduledExecutorService arrivalRateExecutor) {
|
||||
long now = System.currentTimeMillis();
|
||||
shortTermLastReset.set(now);
|
||||
mediumTermLastReset.set(now);
|
||||
longTermLastReset.set(now);
|
||||
|
||||
// 2 minute
|
||||
arrivalRateExecutor.scheduleAtFixedRate(() -> {
|
||||
resetShort();
|
||||
}, 2, 2, TimeUnit.MINUTES);
|
||||
|
||||
// 5 minute
|
||||
arrivalRateExecutor.scheduleAtFixedRate(() -> {
|
||||
resetMedium();
|
||||
}, 5, 5, TimeUnit.MINUTES);
|
||||
|
||||
// 15 minute
|
||||
arrivalRateExecutor.scheduleAtFixedRate(() -> {
|
||||
resetLong();
|
||||
}, 15, 15, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
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,600 @@
|
||||
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.WriteBufferWaterMark;
|
||||
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.accumulo.core.conf.AccumuloConfiguration;
|
||||
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.auth.JWTTokenHandler;
|
||||
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 initializeContext(String[] args) {
|
||||
SpringApplicationBuilder builder = new SpringApplicationBuilder(SpringBootstrap.class);
|
||||
builder.web(WebApplicationType.NONE);
|
||||
builder.registerShutdownHook(false);
|
||||
return builder.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());
|
||||
}
|
||||
|
||||
int udpChannel = 1;
|
||||
for (Channel c : udpChannelHandleList) {
|
||||
LOG.info("Closing udpChannelHandle #" + udpChannel++);
|
||||
channelFutures.add(c.close());
|
||||
}
|
||||
|
||||
// wait for the channels to shutdown
|
||||
channelFutures.forEach(f -> {
|
||||
try {
|
||||
f.get();
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Channel:" + f.channel().config() + " -> " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
|
||||
int 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("Group:" + f.toString() + " -> " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
LOG.info("Closing WebSocketRequestDecoder");
|
||||
WebSocketRequestDecoder.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error closing WebSocketRequestDecoder during shutdown", e);
|
||||
}
|
||||
|
||||
if (this.metricResolver != null) {
|
||||
try {
|
||||
LOG.info("Closing metricResolver");
|
||||
this.metricResolver.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (curatorFramework != null) {
|
||||
try {
|
||||
LOG.info("Closing curatorFramework");
|
||||
curatorFramework.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationContext != null) {
|
||||
LOG.info("Closing applicationContext");
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
this.shutdown = true;
|
||||
LOG.info("Server shut down.");
|
||||
}
|
||||
|
||||
private static boolean useEpoll() {
|
||||
|
||||
// Should we just return true if this is Linux and if we get an error
|
||||
// during Epoll
|
||||
// setup handle it there?
|
||||
final String os = SystemPropertyUtil.get(OS_NAME).toLowerCase().trim();
|
||||
final String[] version = SystemPropertyUtil.get(OS_VERSION).toLowerCase().trim().split("\\.");
|
||||
if (os.startsWith("linux") && version.length >= 3) {
|
||||
final int major = Integer.parseInt(version[0]);
|
||||
if (major > EPOLL_MIN_MAJOR_VERSION) {
|
||||
return true;
|
||||
} else if (major == EPOLL_MIN_MAJOR_VERSION) {
|
||||
final int minor = Integer.parseInt(version[1]);
|
||||
if (minor > EPOLL_MIN_MINOR_VERSION) {
|
||||
return true;
|
||||
} else if (minor == EPOLL_MIN_MINOR_VERSION) {
|
||||
final int patch = Integer.parseInt(version[2].substring(0, 2));
|
||||
return patch >= EPOLL_MIN_PATCH_VERSION;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected SslContext createSSLContext(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);
|
||||
int timeout = Long.valueOf(AccumuloConfiguration.getTimeInMillis(balancerConfig.getZooKeeper().getTimeout()))
|
||||
.intValue();
|
||||
curatorFramework = CuratorFrameworkFactory.newClient(balancerConfig.getZooKeeper().getServers(), timeout, 10000,
|
||||
retryPolicy);
|
||||
curatorFramework.start();
|
||||
ensureZkPaths(curatorFramework, zkPaths);
|
||||
|
||||
AuthCache.configure(balancerConfig.getSecurity());
|
||||
JWTTokenHandler.init(balancerConfig.getSecurity(), null);
|
||||
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 two lines are necessary */
|
||||
wsServer.option(ChannelOption.SO_SNDBUF, 1048576);
|
||||
wsServer.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(620145, 838860));
|
||||
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.initializeContext(args);
|
||||
BalancerConfiguration balancerConf = Balancer.applicationContext.getBean(BalancerConfiguration.class);
|
||||
|
||||
Balancer balancer = new Balancer(balancerConf);
|
||||
try {
|
||||
balancer.run();
|
||||
LATCH.await();
|
||||
} catch (final InterruptedException e) {
|
||||
LOG.info("Server shutting down.");
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error running server.", e);
|
||||
} finally {
|
||||
try {
|
||||
balancer.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(4096, 32768, 8192, true, 128));
|
||||
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().setIgnoreSslHandshakeErrors(
|
||||
balancerConfig.getSecurity().getServerSsl().isUseGeneratedKeypair()));
|
||||
ch.pipeline().addLast("login",
|
||||
new X509LoginRequestHandler(balancerConfig.getSecurity(), balancerConfig.getHttp()));
|
||||
ch.pipeline().addLast("httpRelay", new HttpRelayHandler(metricResolver, httpClientPool));
|
||||
ch.pipeline().addLast("error", new TimelyExceptionHandler().setIgnoreSslHandshakeErrors(
|
||||
balancerConfig.getSecurity().getServerSsl().isUseGeneratedKeypair()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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, false, 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,19 @@
|
||||
package timely.balancer.configuration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import timely.configuration.Http;
|
||||
|
||||
public class BalancerHttp extends Http {
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig<CloseableHttpClient> httpClientPool = new GenericKeyedObjectPoolConfig<>();
|
||||
|
||||
public GenericKeyedObjectPoolConfig<CloseableHttpClient> 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,48 @@
|
||||
package timely.balancer.configuration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import timely.client.tcp.TcpClient;
|
||||
import timely.client.udp.UdpClient;
|
||||
import timely.configuration.Server;
|
||||
|
||||
public class BalancerServer extends Server {
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig<UdpClient> udpClientPool = new GenericKeyedObjectPoolConfig<>();
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig<TcpClient> tcpClientPool = new GenericKeyedObjectPoolConfig<>();
|
||||
|
||||
private int numTcpPools = 1;
|
||||
|
||||
private int tcpBufferSize = -1;
|
||||
|
||||
public GenericKeyedObjectPoolConfig<UdpClient> getUdpClientPool() {
|
||||
return udpClientPool;
|
||||
}
|
||||
|
||||
public GenericKeyedObjectPoolConfig<TcpClient> 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,29 @@
|
||||
package timely.balancer.configuration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
|
||||
import timely.configuration.Websocket;
|
||||
|
||||
public class BalancerWebsocket extends Websocket {
|
||||
|
||||
@Valid
|
||||
@NestedConfigurationProperty
|
||||
private GenericKeyedObjectPoolConfig<WebSocketSubscriptionClient> wsClientPool = new GenericKeyedObjectPoolConfig<>();
|
||||
|
||||
public GenericKeyedObjectPoolConfig<WebSocketSubscriptionClient> getWsClientPool() {
|
||||
return wsClientPool;
|
||||
}
|
||||
|
||||
public int incomingBufferSize = 50000000;
|
||||
|
||||
public int getIncomingBufferSize() {
|
||||
return incomingBufferSize;
|
||||
}
|
||||
|
||||
public void setIncomingBufferSize(int incomingBufferSize) {
|
||||
this.incomingBufferSize = incomingBufferSize;
|
||||
}
|
||||
}
|
||||
@@ -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,27 @@
|
||||
package timely.balancer.configuration;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
public class ZooKeeper {
|
||||
|
||||
private String servers;
|
||||
|
||||
@NotBlank
|
||||
private String timeout = "120s";
|
||||
|
||||
public String getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public void setServers(String servers) {
|
||||
this.servers = servers;
|
||||
}
|
||||
|
||||
public String getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(String timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package timely.balancer.connection;
|
||||
|
||||
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);
|
||||
|
||||
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 = null;
|
||||
|
||||
private TimelyBalancedHost() {
|
||||
|
||||
}
|
||||
|
||||
private TimelyBalancedHost(String host, int tcpPort, int httpPort, int wsPort, int udpPort,
|
||||
ArrivalRate arrivalRate) {
|
||||
this.host = host;
|
||||
this.tcpPort = tcpPort;
|
||||
this.httpPort = httpPort;
|
||||
this.wsPort = wsPort;
|
||||
this.udpPort = udpPort;
|
||||
this.arrivalRate = arrivalRate;
|
||||
}
|
||||
|
||||
static public TimelyBalancedHost of(String host, int tcpPort, int httpPort, int wsPort, int udpPort,
|
||||
ArrivalRate arrivalRate) {
|
||||
return new TimelyBalancedHost(host, tcpPort, httpPort, wsPort, udpPort, arrivalRate);
|
||||
}
|
||||
|
||||
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() {
|
||||
if (this.arrivalRate != null) {
|
||||
this.arrivalRate.arrived();
|
||||
}
|
||||
}
|
||||
|
||||
public double getArrivalRate() {
|
||||
if (this.arrivalRate == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.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,16 @@
|
||||
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 timely.balancer.configuration.BalancerHttp;
|
||||
import timely.balancer.configuration.BalancerSecurity;
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, CloseableHttpClient> {
|
||||
|
||||
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().getIncomingBufferSize();
|
||||
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,61 @@
|
||||
package timely.balancer.healthcheck;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||
final private TCPServerHealthCheck tcpServerHealthCheck;
|
||||
|
||||
public HealthChecker(BalancerConfiguration config, TcpClientPool tcpClientPool) {
|
||||
this.tcpServerHealthCheck = new TCPServerHealthCheck(tcpClientPool);
|
||||
this.executorService.scheduleAtFixedRate(() -> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, config.getCheckServerHealthInterval(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void setTimelyHosts(List<TimelyBalancedHost> timelyHosts) {
|
||||
synchronized (timelyHosts) {
|
||||
this.timelyHosts.clear();
|
||||
this.timelyHosts.addAll(timelyHosts);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
this.executorService.shutdown();
|
||||
try {
|
||||
this.executorService.awaitTermination(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
} finally {
|
||||
if (!this.executorService.isTerminated()) {
|
||||
this.executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.HttpHeaderNames.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.uri();
|
||||
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.toString())) {
|
||||
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.method().equals(HttpMethod.GET)) {
|
||||
HttpUriRequest relayedRequest = new HttpGet(relayURI);
|
||||
if (headerArray.length > 0) {
|
||||
relayedRequest.setHeaders(headerArray);
|
||||
}
|
||||
relayedResponse = client.execute(relayedRequest);
|
||||
|
||||
} else if (request.method().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) {
|
||||
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().uri());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.debug("Channel closed, closing subscriptions for subscriptionId:{}",
|
||||
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:{} - 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:{} metric:{}", 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,12 @@
|
||||
package timely.balancer.resolver;
|
||||
|
||||
import timely.balancer.connection.TimelyBalancedHost;
|
||||
|
||||
public interface MetricResolver {
|
||||
|
||||
public TimelyBalancedHost getHostPortKeyIngest(String metric);
|
||||
|
||||
public TimelyBalancedHost getHostPortKey(String metric);
|
||||
|
||||
public void close() throws Exception;
|
||||
}
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
cd src/main/generated-java
|
||||
flatc --java ../resources/fb/metric.fbs
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace timely.api.flatbuffer;
|
||||
|
||||
file_identifier "TMFB";
|
||||
|
||||
table Tag {
|
||||
key:string;
|
||||
value:string;
|
||||
}
|
||||
|
||||
table Metric {
|
||||
name:string;
|
||||
timestamp:long;
|
||||
value:double;
|
||||
tags:[Tag];
|
||||
}
|
||||
|
||||
table Metrics {
|
||||
metrics:[Metric];
|
||||
}
|
||||
|
||||
root_type Metrics;
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FindBugsFilter>
|
||||
<Match>
|
||||
<Class name="timely.balancer.Balancer" />
|
||||
<Method name="shutdown" />
|
||||
<Bug pattern="UC_USELESS_OBJECT" />
|
||||
</Match>
|
||||
<Match>
|
||||
<Class name="timely.balancer.netty.http.HttpRelayHandler$MyByteArrayOutputStream" />
|
||||
<Method name="getBuf" />
|
||||
<Bug pattern="EI_EXPOSE_REP" />
|
||||
</Match>
|
||||
</FindBugsFilter>
|
||||
@@ -0,0 +1 @@
|
||||
/target/
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
<?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.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>timely-client</artifactId>
|
||||
<properties>
|
||||
<!-- PEP 440 compliant version gets substituted into src/main/python/template/setup.py -->
|
||||
<!-- use .dev0 instead of -SNAPSHOT -->
|
||||
<python.client.version>1.3.3.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>exec-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>python-dist</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>python</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>setup.py sdist --dist-dir ${project.build.directory}</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>python-clean</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<executable>rm</executable>
|
||||
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
|
||||
<commandlineArgs>-rf timely.egg-info setup.py</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package timely.api.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
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;
|
||||
|
||||
@JsonPropertyOrder({ "metric", "timestamp", "value", "tags", "subscriptionId", "complete" })
|
||||
public class MetricResponse {
|
||||
|
||||
private String metric;
|
||||
private long timestamp;
|
||||
private double value;
|
||||
private List<Tag> tags;
|
||||
private String subscriptionId;
|
||||
private boolean complete = false;
|
||||
|
||||
public String getMetric() {
|
||||
return metric;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public List<Tag> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return complete;
|
||||
}
|
||||
|
||||
public void setMetric(String metric) {
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public void setValue(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setTags(List<Tag> tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public String getSubscriptionId() {
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
public void setSubscriptionId(String subscriptionId) {
|
||||
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());
|
||||
response.setTimestamp(metric.getValue().getTimestamp());
|
||||
response.setTags(metric.getTags());
|
||||
response.setValue(metric.getValue().getMeasure());
|
||||
response.setSubscriptionId(subscriptionId);
|
||||
response.setComplete(false);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
HashCodeBuilder hash = new HashCodeBuilder();
|
||||
hash.append(this.metric);
|
||||
hash.append(this.timestamp);
|
||||
hash.append(this.value);
|
||||
hash.append(this.tags);
|
||||
hash.append(this.subscriptionId);
|
||||
hash.append(this.complete);
|
||||
return hash.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (null == obj) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof MetricResponse)) {
|
||||
return false;
|
||||
}
|
||||
MetricResponse other = (MetricResponse) obj;
|
||||
EqualsBuilder equals = new EqualsBuilder();
|
||||
equals.append(this.metric, other.metric);
|
||||
equals.append(this.timestamp, other.timestamp);
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder tsb = new ToStringBuilder(this);
|
||||
tsb.append("metric", this.metric);
|
||||
tsb.append("timestamp", this.timestamp);
|
||||
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,149 @@
|
||||
package timely.client.tcp;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
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 BufferedWriter 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
|
||||
*/
|
||||
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() throws IOException {
|
||||
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()) {
|
||||
if (System.currentTimeMillis() > (connectTime + backoff)) {
|
||||
OutputStreamWriter osw = null;
|
||||
try {
|
||||
connectTime = System.currentTimeMillis();
|
||||
sock = new Socket(host, port);
|
||||
osw = new OutputStreamWriter(sock.getOutputStream());
|
||||
out = new BufferedWriter(osw);
|
||||
backoff = 2000;
|
||||
LOG.trace("Connected to Timely at {}:{}", host, port);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error connecting to Timely at {}:" + host + ":" + port + ". Error: " + e.getMessage());
|
||||
if (sock != null) {
|
||||
try {
|
||||
sock.close();
|
||||
} catch (IOException e1) {
|
||||
LOG.error(e1.getMessage());
|
||||
} finally {
|
||||
sock = null;
|
||||
}
|
||||
}
|
||||
if (osw != null) {
|
||||
try {
|
||||
osw.close();
|
||||
} catch (IOException e1) {
|
||||
LOG.error(e1.getMessage());
|
||||
}
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e1) {
|
||||
LOG.error(e1.getMessage());
|
||||
} finally {
|
||||
out = null;
|
||||
}
|
||||
}
|
||||
backoff = backoff * 2;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package timely.model;
|
||||
|
||||
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
|
||||
*
|
||||
* Json has been flattened to reduce overhead. Example:
|
||||
* {"name":"m1","timestamp":1,"measure":1.0,"tags":[{"k1":"v1"}]}
|
||||
*
|
||||
*/
|
||||
@JsonRootName("metric")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "name" })
|
||||
public class Metric implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String name;
|
||||
private List<Tag> tags;
|
||||
private Value value;
|
||||
|
||||
public Metric() {
|
||||
tags = new ArrayList<>();
|
||||
value = new Value();
|
||||
}
|
||||
|
||||
public Metric(Metric other) {
|
||||
this();
|
||||
this.name = other.getName();
|
||||
this.tags.addAll(other.getTags());
|
||||
this.value = new Value(other.getValue());
|
||||
}
|
||||
|
||||
public Metric(String name, long timestamp, double measure) {
|
||||
this();
|
||||
this.name = name;
|
||||
this.value.setTimestamp(timestamp);
|
||||
this.value.setMeasure(measure);
|
||||
}
|
||||
|
||||
public Metric(String name, long timestamp, double measure, List<Tag> tags) {
|
||||
this(name, timestamp, measure);
|
||||
this.setTags(tags);
|
||||
}
|
||||
|
||||
public Metric(String name, List<Tag> tags) {
|
||||
this();
|
||||
this.name = name;
|
||||
this.setTags(tags);
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@JsonGetter("name")
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonGetter("tags")
|
||||
public List<Tag> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void setTags(List<Tag> tags) {
|
||||
this.tags.addAll(tags);
|
||||
}
|
||||
|
||||
public void addTag(Tag tag) {
|
||||
tags.add(new Tag(tag));
|
||||
}
|
||||
|
||||
@JsonUnwrapped
|
||||
public Value getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Value value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Collections.sort(tags);
|
||||
return "Metric{" + "name='" + name + '\'' + ", tags=" + tags + ", value=" + value + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
Metric metric = (Metric) o;
|
||||
|
||||
if (name != null ? !name.equals(metric.name) : metric.name != null)
|
||||
return false;
|
||||
|
||||
if (tags != null) {
|
||||
if (tags.size() != metric.tags.size()) {
|
||||
return false;
|
||||
} else if (!tags.containsAll(metric.tags)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return value != null ? value.equals(metric.value) : metric.value == null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = name != null ? name.hashCode() : 0;
|
||||
result = 31 * result + (tags != null ? tags.hashCode() : 0);
|
||||
result = 31 * result + (value != null ? value.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Builder for Metric. Note: cannot re-use a builder after build is
|
||||
* called.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
Metric metric;
|
||||
|
||||
private Builder() {
|
||||
metric = new Metric();
|
||||
}
|
||||
|
||||
private static TagParser parser = new TagParser();
|
||||
|
||||
public Builder tag(Tag tag) {
|
||||
metric.addTag(tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tags(List<Tag> tags) {
|
||||
metric.setTags(tags);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tag(String key, String value) {
|
||||
metric.addTag(new Tag(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tag(String rawTag) {
|
||||
metric.addTag(parser.parse(rawTag));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder name(String name) {
|
||||
metric.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder value(Value value) {
|
||||
metric.setValue(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder value(long timestamp, double measure) {
|
||||
metric.setValue(new Value(timestamp, measure));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Metric build() {
|
||||
return this.metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package timely.model;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
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.
|
||||
*
|
||||
* @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) {
|
||||
if (o instanceof ObjectSizeOf) {
|
||||
size = ((ObjectSizeOf) o).sizeInBytes();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package timely.model;
|
||||
|
||||
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
|
||||
*/
|
||||
@JsonRootName("tag")
|
||||
public class Tag implements Comparable<Tag>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final TagParser tagParser = new TagParser();
|
||||
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
public Tag() {
|
||||
}
|
||||
|
||||
public Tag(Tag other) {
|
||||
this.setKey(other.getKey());
|
||||
this.setValue(other.getValue());
|
||||
}
|
||||
|
||||
public Tag(String tag) {
|
||||
this(tagParser.parse(tag));
|
||||
}
|
||||
|
||||
public Tag(String key, String value) {
|
||||
Preconditions.checkNotNull(key);
|
||||
Preconditions.checkNotNull(value);
|
||||
this.setKey(key);
|
||||
this.setValue(value);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, String> get() {
|
||||
return Stream.of(this).collect(Collectors.toMap(Tag::getKey, Tag::getValue));
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void set(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins key and value into key=value
|
||||
*/
|
||||
public String join() {
|
||||
return tagParser.combine(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tag{" + join() + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (null == o || getClass() != o.getClass())
|
||||
return false;
|
||||
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 new CompareToBuilder().append(this.key, other.key).append(this.value, other.value).toComparison();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package timely.model;
|
||||
|
||||
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
|
||||
*/
|
||||
public class Value implements Comparator<Value>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long timestamp;
|
||||
private Double measure;
|
||||
|
||||
public Value() {
|
||||
}
|
||||
|
||||
public Value(Value other) {
|
||||
this.setTimestamp(other.getTimestamp());
|
||||
this.setMeasure(other.getMeasure());
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public Value(@JsonProperty("timestamp") Long timestamp, @JsonProperty("measure") Double measure) {
|
||||
this.setTimestamp(timestamp);
|
||||
this.setMeasure(measure);
|
||||
}
|
||||
|
||||
@JsonProperty("timestamp")
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(final Long timestamp) {
|
||||
this.setTimestamp(timestamp, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void setTimestamp(final Long timestamp, TimeUnit timeUnit) {
|
||||
this.timestamp = timeUnit.toMillis(timestamp);
|
||||
}
|
||||
|
||||
@JsonProperty("measure")
|
||||
public Double getMeasure() {
|
||||
return measure;
|
||||
}
|
||||
|
||||
public void setMeasure(final Double measure) {
|
||||
this.measure = measure;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Compares timestamps for sorting.
|
||||
*
|
||||
* Note: this comparator imposes orderings that are inconsistent with equals
|
||||
*
|
||||
*/
|
||||
public int compare(Value v1, Value v2) {
|
||||
if (v1.getTimestamp().equals(v2.getTimestamp()))
|
||||
return 0;
|
||||
return v1.getTimestamp() > v2.getTimestamp() ? 1 : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (null == o || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
if (getTimestamp() != null ? !getTimestamp().equals(((Value) o).getTimestamp())
|
||||
: ((Value) o).getTimestamp() != null)
|
||||
return false;
|
||||
return !(getMeasure() != null ? !getMeasure().equals(((Value) o).getMeasure())
|
||||
: ((Value) o).getMeasure() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(timestamp, measure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Value{" + "timestamp=" + timestamp + ", measure=" + measure + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package timely.model.parse;
|
||||
|
||||
public interface Combiner<T> {
|
||||
|
||||
String combine(T o);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package timely.model.parse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import timely.model.Metric;
|
||||
|
||||
public class MetricParser implements Parser<Metric> {
|
||||
|
||||
private static final Splitter spaceSplitter = Splitter.on(" ").omitEmptyStrings().trimResults();
|
||||
|
||||
@Override
|
||||
public Metric parse(String line) {
|
||||
|
||||
// put specification
|
||||
//
|
||||
// put <metricName> <timestamp> <value> <tagK=tagV> <tagK=tagV> ...
|
||||
|
||||
List<String> parts = spaceSplitter.splitToList(line);
|
||||
Preconditions.checkPositionIndex(3, parts.size());
|
||||
|
||||
Metric.Builder builder = Metric.newBuilder();
|
||||
// index 0 is put
|
||||
builder.name(parts.get(1)); // metric name
|
||||
builder.value(Long.parseLong(parts.get(2)), Double.parseDouble(parts.get(3)));
|
||||
parts.stream().skip(4).forEach(builder::tag);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package timely.model.parse;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Parser<T> {
|
||||
|
||||
T parse(String t);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package timely.model.parse;
|
||||
|
||||
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>>, Combiner<Collection<Tag>> {
|
||||
|
||||
private static final TagParser tagParser = new TagParser();
|
||||
|
||||
@Override
|
||||
public List<Tag> parse(String t) {
|
||||
ArrayList<Tag> tags = new ArrayList<>();
|
||||
// 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
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package timely.model.parse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import timely.model.Tag;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
List<String> parts = equalSplitter.splitToList(t);
|
||||
if (parts.size() != 2) {
|
||||
throw new IllegalArgumentException("Invalid tag format: " + t);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Loaded 100 of 561 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user