Compare commits

..
478 Commits
Author SHA1 Message Date
Dave Marion 577688aa54 Update release notes and other documentation for 0.0.5 release 2017-08-11 08:27:52 -04:00
Dave Marion 1f0fe4198d closes #140 - updated quick start documentation to include deploying timely-app into grafana plugins directory 2017-04-14 09:39:46 -04:00
Dave Marion 385b142f71 Merge branch 'slate' of https://github.com/NationalSecurityAgency/timely into slate 2017-04-14 09:33:43 -04:00
Dave Marion 9065dcdce2 re #141 - update documentation for collectd build profile 2017-04-14 09:33:12 -04:00
Ivan Bella 3877695949 fixed #137 moved column comment down to avoid messing up numbering 2017-03-13 09:13:36 -04:00
Dave Marion d4e504b31c Updated release notes 2017-02-02 11:02:52 -05:00
Dave Marion 32ae6ee399 Updated release notes for 0.0.4 release 2017-01-23 10:46:34 -05:00
Dave Marion 9688514a0f Documentation updates for the 0.0.4 release. 2017-01-23 10:04:15 -05:00
Dave Marion 9aa089a585 Merge tag 'v1.4' into slate-0.0.4 2017-01-19 13:37:35 -05:00
Robert Lord 8d75089525 Cut version 1.4.0 2016-11-24 11:26:10 -06:00
Robert Lord 7857d862b3 Update pull requset template 2016-11-24 11:26:10 -06:00
Robert Lord e8e2e5e144 Fix incorrect documentation in deploy.sh 2016-11-24 11:26:10 -06:00
Robert Lord 33515061b7 Add back in middleman flags to Vagrant with new flag syntax 2016-11-24 11:26:10 -06:00
Luke Hoersten 7d2ab1be7e Exec middleman server fails with invalid flags --force-polling and -l. Removed the flags to remedy. 2016-11-24 11:26:10 -06:00
Robert Lord 521969b9ee Update language list link and count in README 2016-11-24 11:26:10 -06:00
Robert Lord 62fb143490 Fix build, update middleman 2016-11-24 11:26:10 -06:00
Robert Lord 08cf3dc5c2 Fix bug where -margin wasn't properly respected even if search was off 2016-11-24 11:26:10 -06:00
Robert Lord 6831223f50 Update ruby version requirements in Travis and README 2016-11-24 11:26:10 -06:00
Robert Lord 20ed753723 Fix another bug where disabling language tabs didn't properly hide HTML 2016-11-24 11:26:10 -06:00
kissrain a8e3737200 fix -margin 2016-11-24 11:26:10 -06:00
Michael Schramm d1c3989a8d make scss variables changeable
all variables should only provide a default

this would allow us to include the screen.scss and simply set the variables we want to change before that.
2016-11-24 11:26:10 -06:00
Robert Lord 5712ebe2ae Upgrade sprockets 2016-11-24 11:26:10 -06:00
Robert Lord e47cbe5e3b Update middleman-syntax 2016-11-24 11:26:10 -06:00
Robert Lord f5b3a205b2 Remove unused gem middleman-gh-pages 2016-11-24 11:26:10 -06:00
Robert Lord a2b61191c7 Add some company links to readme 2016-11-24 11:26:10 -06:00
Robert Lord adfd4080bd Add Ruby 2.2.0 to Travis testing matrix 2016-11-24 11:26:10 -06:00
Dave Marion c5da744883 Minor edits 2016-10-26 16:40:54 -04:00
Dave Marion ebc35e5c63 Updating documentiaton for 0.0.3 release 2016-10-24 15:46:33 -04:00
Dave Marion 3e49716525 for #75 - Update documentation to UDP protocol, commons collections4 on the tservers, release notes 2016-09-29 15:41:30 -04:00
Brian Loss b47668f277 Update documentation to reflect the change to Spring Boot configuration (re #75) 2016-09-29 10:23:01 -04:00
alliedsteel 483f5f8f1a Word missing on Readme.md (#592) 2016-09-15 11:19:59 -05:00
Jim Klucar eb45592f90 updated grunt info 2016-08-05 11:13:20 -04:00
Dave Marion 7ad726a551 re #56, update docs for changes to web socket API 2016-08-02 10:47:57 -04:00
klucar 87a3b43b13 fixed some links, added some words 2016-07-26 13:54:34 -04:00
klucar b13bd4c65d interleaved operation and responses 2016-07-26 13:34:54 -04:00
klucar 5a5c539688 interleaved operation and responses 2016-07-26 13:31:35 -04:00
Dave Marion 465223cdb6 Updated docs 2016-07-25 12:46:14 -04:00
Dave Marion 99e4e93603 Still need the commons-lang3 jar on the Accumulo Tablet Servers 2016-07-25 11:24:17 -04:00
Dave Marion 193c9f2c5b More doc changes, added collectd and examples 2016-07-22 14:48:04 -04:00
Klucar 8490e9cbc6 updated grafana build instructions 2016-07-22 13:01:58 -04:00
Klucar aa08e5df03 updated logo 2016-07-22 13:01:42 -04:00
Klucar 053017ba39 instructions to compile grafana app 2016-07-22 11:42:41 -04:00
Dave Marion 6f188b3473 Modified some CSS items 2016-07-21 15:22:02 -04:00
Dave Marion b27b3c3e52 Add Grafana docs 2016-07-21 14:26:17 -04:00
Dave Marion b2330262bd more doc changes 2016-07-20 14:26:16 -04:00
Dave Marion 0702fa3d47 wip 2016-07-19 15:57:48 -04:00
Dave Marion 813d8e5d28 Trying to get rid of the README 2016-07-19 11:42:48 -04:00
Dave Marion e63f41a20c wip 2016-07-15 07:38:42 -04:00
Dave Marion 9d7ca484e3 wip 2016-07-14 12:14:53 -04:00
Dave Marion bb787fc8ca wip 2016-07-14 09:09:35 -04:00
Robert Lord 6dd96e1c15 Bump CHANGELOG 2016-06-11 09:51:34 -05:00
Robert Lord a68507a00e Remove CONTRIBUTING 2016-06-11 09:50:23 -05:00
Robert Lord 8d3eb0a433 Update readme 2016-06-11 09:49:56 -05:00
Robert Lord e9384ed058 Add issue template 2016-06-10 22:05:39 -05:00
Robert Lord 31e021a01f Add PR template 2016-06-10 21:58:16 -05:00
Robert Lord ad752cd538 Update rouge languages link in README 2016-06-10 17:23:21 -05:00
Robert Lord ed6578ca73 Merge pull request #511 from Jameskmonger/patch-1
Add JavaScript examples
2016-05-24 09:33:49 -05:00
Robert Lord 1e54d1dd41 Merge pull request #464 from BaButtons/patch-1
Address #463
2016-05-24 00:09:57 -05:00
James Monger cec5d662ad Add JavaScript examples 2016-04-21 11:38:08 +01:00
Robert Lord f380e4c203 Note the need for an upgraded Ruby 2016-03-08 16:23:19 -06:00
Robert Lord 19d651a65a Add link to wiki 2016-03-01 20:46:01 -06:00
Robert Lord 13b113d1a5 Merge branch 'master' into dev 2016-03-01 20:44:01 -06:00
Robert Lord 979aaa91d5 Fixes #440, version 1.3.2 2016-03-01 20:43:54 -06:00
BaButtons b84d76cd83 Add deploy configuration with a port set option
Addressing tripit/slate#463
2016-02-25 15:23:17 -05:00
Robert Lord 6a8c406f56 Merge pull request #454 from pra85/patch-2
Minor English Tweak
2016-02-11 16:42:51 -06:00
Prayag Verma 30c270c029 Minor English Tweak
Replace `a` with `an`
2016-02-10 23:47:10 +05:30
Robert Lord 28066ed281 Fixes #440, version 1.3.2 2016-02-03 16:19:16 -06:00
Robert Lord 6e37ca292f Add changelog for 1.3.1 2016-01-31 12:42:35 -06:00
Robert Lord db48d22661 Fix broken word break styles 2016-01-31 12:41:11 -06:00
Tariq Islam 365a0b58ea Merge pull request #435 from tripit/dev
Thanks @lord and everyone contributing! Slate v1.3 ftw!
2016-01-29 16:16:13 -08:00
Robert Lord d4bd994f03 Add version notes 2016-01-27 17:07:02 -06:00
Robert Lord 1c5bd80d4b Add travis script 2016-01-27 16:50:19 -06:00
Robert Lord d6a7b5ff97 Small readme improvement 2016-01-27 16:48:16 -06:00
Robert Lord ac9cb0aef9 Remove Rake 2016-01-27 16:41:30 -06:00
Robert Lord d88d714d90 Make deploy script executable 2016-01-27 16:36:01 -06:00
Robert Lord ffa2829fe5 Add deploy script 2016-01-27 16:35:39 -06:00
Robert Lord b5c6495662 Fix travis bugs 2016-01-27 16:30:19 -06:00
Robert Lord 471c19a7c0 Remove docker 2016-01-27 15:53:28 -06:00
Robert Lord 1d108d0f40 Update Vagrant to work properly 2016-01-27 15:50:34 -06:00
Robert Lord 7f1d6da6dc Remove last inline script 2016-01-27 02:00:01 -06:00
Robert Lord a4b9208af0 Move jquery to be local 2016-01-27 01:37:16 -06:00
Robert Lord 9f83bbd158 Add comment about middleman hanging to config.rb 2016-01-27 01:18:58 -06:00
Robert Lord 5c87f10383 Fix non-wrapping code in firefox 2016-01-27 00:53:24 -06:00
Robert Lord ccd158df4f Add code block warning 2016-01-27 00:45:32 -06:00
Robert Lord 20d94974fa Add parrot link 2016-01-26 18:41:22 -06:00
Eric Caron 4f5443ce06 It should have be it. It wasn't it. It was its. 2016-01-26 15:54:36 -06:00
Eric Caron 79bb28c2f8 README copy-editing changes Applying the copy-editing changes noticed by Ira Brooker (@IOB1979) to the current master branch 2016-01-26 15:54:36 -06:00
Robert Lord 8cba352315 Add lang-specific class 2016-01-26 15:39:37 -06:00
Robert Lord 884cda666a Update readme 2016-01-26 13:10:36 -06:00
Robert Lord c0dddf7560 Improve contribution guidelines 2016-01-26 13:03:37 -06:00
Robert Lord 44cf741f01 Add new logo 2016-01-26 12:55:22 -06:00
Robert Lord 63e3e30bc5 Update readme styling again 2016-01-26 12:53:06 -06:00
Robert Lord b6084cfd0a Update readme styling 2016-01-26 12:52:42 -06:00
Robert Lord 02428eb687 Ignore .sass-cache 2016-01-26 12:52:33 -06:00
Robert Lord 7f88aafcba Upgrade to middleman 4 2016-01-26 12:52:03 -06:00
Robert Lord f4dea9008d Merge branch 'master' into dev 2016-01-26 12:07:22 -06:00
Robert Lord e1026d921c Merge pull request #428 from tinybike/master
Added Augur API to examples
2016-01-17 17:35:19 -06:00
Jack Peterson 35690907be Added Augur API to examples 2016-01-17 14:34:19 -08:00
Robert Lord 3ad0542fc7 Merge pull request #420 from netbe/fix-doc-docker
Update Boot2docker information for OSX
2016-01-08 23:46:11 -06:00
François Benaiteau 65c3dbd544 Update Boot2docker information for OSX 2016-01-08 12:31:33 +01:00
Robert Lord bc8ac808b4 Merge pull request #419 from blackxored/better-docker-support
Better docker support
2016-01-02 14:29:48 -06:00
Adrian Perez 75f61557a2 fix(docker): new middleman requires JS runtime 2016-01-02 01:51:06 +01:00
Adrian Perez f90e6b076d feat(docker): improve docker support
This improves Docker support in the following ways:

* Base image off `ruby:onbuild` to avoid unnecessary custom steps and ease
derived images.
* Adds `.dockerignore` to avoid sending large files to build.
* Adds a `docker-compose.yml` so users can simply type `docker-compose up`
to get it running.
* Explicity sets the volume definition in the `Dockerfile`.
* Volume is automatically mount by `docker-compose` (the `source` directory
relative to the project's dir).
* Workaround watcher failing to pick directory changes probably because of
vboxsf and the `listen` gem failing to register filesystem changes.
* Updates documentation.
2016-01-02 00:51:00 +01:00
Robert Lord 47cca3d8e7 Merge pull request #411 from zachpanz88/patch-1
Change cat name
2015-12-20 14:03:15 +08:00
Zach Panzarino 837a7e7873 Change cat name
After recent events it is probably better to use a different name for the cat.
2015-12-18 22:51:49 -05:00
Robert Lord b871b44b72 Merge pull request #399 from ReadmeCritic/patch-1
Update README URLs based on HTTP redirects
2015-12-08 18:55:30 -08:00
ReadmeCritic 97a4c89d0c Update README URLs based on HTTP redirects 2015-12-08 12:22:18 -08:00
Robert Lord 8e14f805dc Merge pull request #387 from jaredtking/invoiced
Added Invoiced to list of examples on README
2015-11-18 12:45:31 -06:00
Jared King 82717e043d Added Invoiced to list of examples on README 2015-11-18 11:10:10 -06:00
Robert Lord 018a6939db Merge pull request #385 from agonzalezro/docker-fix
Use base ruby image in Dockerfile & fix watcher
2015-11-17 15:17:50 -06:00
Álex González 2618c77db0 Use base ruby image in Dockerfile & fix watcher
It seems that the watcher will not work without the
--force-polling option.
2015-11-17 13:53:34 +01:00
Robert Lord c9d8d34e79 Merge pull request #384 from evantahler/master
add actionhero docs
2015-11-16 14:35:40 -06:00
Evan Tahler 40443b32fd Update README.md
add actionhero docs
2015-11-16 10:53:18 -08:00
Robert Lord 1151d00926 Merge pull request #380 from detain/master
Added Links to InterServer Slate Implementation
2015-11-13 18:13:28 -06:00
Joe Huss c76359868a Added Links to InterServer Slate Implementation
URL: https://my.interserver.net/apidoc/

Our implementation includes some minor customizations which others mind fight useful including icons for the Language tabs at the top right.

Side Note: The document contents including sample scripts , input/output parameter+descriptions, etc are generated entirely from a script i wrote that parses my SOAP API WSDL file, PHP Source (parsing phpdoc comments), and MySQL Table defintions (parsing field comments) to generate and populate all the neccesary info. At some point (faster if i have any requests for it ) ill make those scripts publicly available after i clean up the source a bit and make it more portable.
2015-11-12 01:01:14 -05:00
Robert Lord 964c25a36b Merge pull request #371 from djpowers/fix-broken-examples
Fix broken examples
2015-11-05 16:58:26 -06:00
Dave Powers 1e86f824af Fix link to BlockCypher (does not work over HTTPS) 2015-11-05 09:41:24 -05:00
Dave Powers 01fd02005a Fix link to IBM Cloudant (page has moved) 2015-11-05 09:39:07 -05:00
Dave Powers fb4c2a9ceb Remove link to ChaiOne Gameplan (broken link) 2015-11-04 14:15:23 -05:00
Robert Lord ecdce169bc Merge pull request #365 from mflaxman/patch-1
Add blockcypher to examples
2015-10-22 16:18:37 -05:00
Michael Flaxman f0a8dec91c Add blockcypher to examples 2015-10-22 12:35:29 -04:00
Robert Lord e9cfc6bf3c Merge pull request #354 from rexposadas/fix-docker-error
upgraded to wily to remove error from version trusty
2015-10-11 20:25:45 -05:00
Rex Posadas 150ff8a800 upgraded to later ubuntu wily to remove error from version trusty 2015-10-11 14:57:44 -05:00
Robert Lord 727ac193ab Merge pull request #350 from sproutbox/dev
Basic Vagrant support. Close #348
2015-10-08 22:00:48 -05:00
Marc Guyer 3c1c9c49d5 Add local git config to guest vm 2015-10-08 14:09:27 +00:00
Marc Guyer 4a3fdfaea2 Update Vagrantfile
More memory needed for `rake publish`
2015-10-06 17:21:17 -04:00
Marc Guyer ce2f2ae8de Update Vagrantfile
Need git for `rake publish`
2015-10-06 17:12:55 -04:00
Marc Guyer 4136f7b0f7 Formatting fixes for README 2015-10-06 16:48:51 -04:00
Marc Guyer dc45e382ce Merge remote-tracking branch 'upstream/dev' into dev 2015-10-06 16:43:01 -04:00
Marc Guyer 57aa27c0eb Basic support for Vagrant 2015-10-06 16:41:22 -04:00
Robert Lord 236ca52c93 Merge branch 'master' into dev 2015-10-06 12:19:59 -05:00
Robert Lord 787eb795b6 Merge pull request #344 from realityking/hostingDe
Add hosting.de API docs to the list
2015-09-30 14:38:46 -05:00
Rouven Weßling e0cdc7339e Add hosting.de API docs to the list 2015-09-30 17:56:34 +02:00
Robert Lord fd27069a2d Merge pull request #339 from axsemantics/master
make middleman pick up changes when run in docker
2015-09-28 12:32:44 -05:00
Andreas Madsack fcea9014d2 mount volume for live changes to Dockerfile 2015-09-28 17:03:48 +02:00
Robert Lord 9e42fbb04c Merge pull request #335 from brettporter/patch-1
Add SafetyCulture API to the list
2015-09-23 00:16:18 -05:00
Brett Porter 1c508a738f Add SafetyCulture API to the list 2015-09-23 15:09:13 +10:00
Robert Lord 7078f7f6e8 Merge pull request #331 from pomier/fix-dockerfile
Fix Dockerfile by installing nodejs
2015-08-27 19:57:34 -04:00
Romain Pomier 92d732f8a4 Install nodejs in Dockerfile to replace therubyracer 2015-08-27 11:09:28 +02:00
Robert Lord c90eab79f9 Merge pull request #327 from victorquinn/master
Add SocialRadar to the list of docs sites using Slate
2015-08-13 17:15:06 -04:00
Victor Quinn 977f2568d7 Fix path to iOS docs 2015-08-13 16:44:05 -04:00
Victor Quinn ed9e958df7 Add SocialRadar to the list of docs sites using Slate 2015-08-13 15:54:18 -04:00
Robert Lord 22ffeba825 Merge pull request #324 from gabrielf/patch-2
Use HTTPS for github urls.
2015-08-08 09:48:37 -07:00
Gabriel Falkenberg 5d27eaf9cf Use HTTPS for github urls. 2015-08-08 17:23:29 +02:00
Robert Lord 07c5ccefc0 Merge pull request #319 from dlackty/update-gems
Update gems
2015-08-06 07:26:01 -07:00
Richard Lee 47e18611f2 Update other dependencies 2015-08-06 18:40:17 +08:00
Richard Lee 83d655b55e Update rouge and middleman-autoprefixer 2015-08-06 16:58:57 +08:00
Robert Lord d4e94080d4 Merge pull request #318 from dlackty/remove-therubyracer-dev
Remove therubyracer in favor of system JS runtimes
2015-08-05 11:16:19 -07:00
Richard Lee befb8d6fac Remove therubyracer 2015-08-05 23:31:44 +08:00
Robert Lord b0e0192374 Merge pull request #316 from daudich/f/supporkit-doc-example
adding supporkit docs as an example
2015-08-05 08:25:12 -07:00
Dhiren Audich 87a2950ade adding supporkit docs as an example of the slate docs 2015-08-05 11:04:29 -04:00
Robert Lord eb1c0db712 Merge pull request #313 from pbechu/patch-1
Add Aircall.io to README
2015-08-04 09:50:02 -07:00
Pierre-Baptiste Béchu 5e605079eb Add Aircall.io to README 2015-08-04 02:42:21 -07:00
Robert Lord ebc8810d65 Merge pull request #309 from sibrus/dev
Improved the appearance of code blocks when printing.
2015-07-29 14:35:00 -07:00
Ivan Matyunin 5cfcbc7f7b Improved the appearance of code blocks when printing. 2015-07-29 16:50:28 -04:00
Tariq Islam adeb496a88 Merge pull request #292 from tripit/dev
Slate 1.2.1
@lord my fault, I seemed to have totally missed the emails sent!
2015-07-27 12:33:43 -07:00
Robert Lord b9d45b2185 Merge pull request #303 from rezanachmad/patch-1
Add OAuth.io to README
2015-07-16 09:18:37 -07:00
Rezan Achmad 0c37f0dfc4 Add OAuth.io to README 2015-07-16 15:09:59 +08:00
Robert Lord ad3b2769fe Update tocify to recache headers on image load, fixes #293 2015-07-06 18:54:45 -07:00
Robert Lord 2e22c87c1b Merge pull request #290 from niftylettuce/patch-1
Added new in the wild link I made
2015-07-02 07:55:16 -07:00
niftylettuce 7639ce60bc Update README.md 2015-07-02 03:04:14 -04:00
Robert Lord ae3d50277e Update RedCarpet gem 2015-06-28 12:44:12 -07:00
Robert Lord dcc563b406 Merge pull request #288 from johnf/patch-1
Fix Changelog date
2015-06-28 12:34:24 -07:00
John Ferlito f3591651f1 Fix Changelog date
Version 1.2 was released in 2015 not 2014
2015-06-27 16:33:07 +10:00
Tariq Islam 7f873ca9e0 Merge pull request #281 from tripit/dev
Slate v1.2
holy moly that's a lot of changes! 👍
2015-06-22 04:29:59 -07:00
Robert Lord 415dcf2c90 Add changes to log from v1.2 2015-06-20 01:54:06 -04:00
Robert Lord fd5cba00f0 Fix small bug with new language tabs 2015-06-20 01:53:20 -04:00
Robert Lord d14fd0f70e Update language tabs to not disturb existing query strings, fixes #57 2015-06-20 01:49:25 -04:00
Robert Lord bb24528f85 Update gems 2015-06-20 01:04:02 -04:00
Robert Lord 872550d0b7 Update h3 fonts to work better for chinese, fixes #214 2015-06-20 00:48:56 -04:00
Robert Lord a15a6ba1b1 Merge pull request #189 from realityking/fonts
Improvements for the icon font
2015-06-20 00:17:27 -04:00
Robert Lord be57777a3d Add chinese fonts, thanks to @JayMiao 2015-06-20 00:05:46 -04:00
Robert Lord 1fe3ce6396 Update lord.io link to https 2015-06-19 23:33:54 -04:00
Robert Lord 203bc79211 Merge pull request #276 from rezanachmad/master
Update README.md with link to Fidor Bank
2015-06-16 10:36:10 -04:00
Rezan Achmad 685b1554e9 Update README.md with link to Fidor Bank 2015-06-16 14:47:17 +07:00
Robert Lord 1eac62fd59 Merge pull request #273 from akilb/patch-1
Update README with link to viagogo API docs
2015-06-10 15:00:10 -07:00
Akil Burgess 48c514abc7 Update README with link to viagogo API docs 2015-06-10 22:48:22 +01:00
Robert Lord 8ab0ed7b24 Merge pull request #265 from realityking/unusedjs
Remove some unused JS.
2015-06-02 20:32:20 -05:00
Rouven Weßling a38011b4a5 Remove some unused JS. 2015-06-02 23:02:41 +02:00
Robert Lord 1f4e6b915d Merge pull request #264 from realityking/fix
Fix minor SCSS issues introduced in #262.
2015-06-01 17:15:11 -05:00
Rouven Weßling 402f931fc9 Fix minor SCSS issues introduced in ab816e432a. 2015-06-01 23:27:03 +02:00
Robert Lord 6d6c3e6070 Merge pull request #263 from realityking/js
Don't generate unnecessary JS files.
2015-05-31 19:54:38 -05:00
Robert Lord 2e8be87224 Merge pull request #262 from realityking/autoprefixer
Use Autoprefixer for vendor prefixes
2015-05-31 19:53:49 -05:00
Rouven Weßling b7e65975b0 Add WOFF2 version of the icon font 2015-06-01 00:38:54 +02:00
Rouven Weßling d27f5c4b7d Remove unused icons from the font 2015-06-01 00:38:54 +02:00
Rouven Weßling 384ffac2fd Don't generate unnecessary JS files.
Also exclude jquery.highlight from the nosearch version, as it's not needed there.
2015-06-01 00:29:57 +02:00
Rouven Weßling ab816e432a Use Autoprefixer for vendor prefixes 2015-06-01 00:11:21 +02:00
Robert Lord 99ee16e359 Merge pull request #261 from realityking/rouge
Update rouge to version 1.9.0
2015-05-31 16:14:17 -05:00
Rouven Weßling 3a53a3c029 Update rouge to version 1.9.0 2015-05-31 20:20:58 +02:00
Robert Lord 67263d8db4 Merge pull request #259 from kohenkatz/patch-1
Consistency fixes
2015-05-28 22:07:56 -05:00
MK 06c1641ad1 Consistency fixes
as discussed on commit 84b7705fd1
2015-05-28 18:41:56 -04:00
Robert Lord 234d1e197d Merge pull request #251 from Maks3w/feature/travis-cache
Feature/travis cache
2015-05-19 19:07:59 -05:00
Robert Lord 22e8e480e5 Merge pull request #253 from Maks3w/feature/rake-clobber
Add "rake clobber" for to remove generated files
2015-05-19 19:07:29 -05:00
Robert Lord 22fc8af88c Merge pull request #252 from Maks3w/hotfix/secure-rubygems
Hotfix/secure rubygems
2015-05-19 15:37:39 -05:00
Maks3w bdebff8250 Add "rake clobber" for to remove generated files 2015-05-18 16:33:31 +02:00
Maks3w d0e46a3894 [Travis-CI] Enable bundler cache 2015-05-18 10:26:22 +02:00
Maks3w d7769c653d [Travis-CI] Specify language to test is ruby 2015-05-18 10:25:21 +02:00
Maks3w 094c38223e [Travis-CI] Use docker build infraestructure 2015-05-18 10:25:17 +02:00
Maks3w fddf304087 Use HTTPS for fetch gems 2015-05-18 09:58:08 +02:00
Tariq Islam 452876af0d Merge pull request #239 from tripit/update
Bugfixes merged.
2015-05-01 00:24:21 -07:00
Robert Lord 16dca4d000 Update gems 2015-04-30 16:01:45 -05:00
Robert Lord f989143692 Fix styling bug in example index 2015-04-30 16:01:45 -05:00
Nik Samokhvalov 4fe37604b7 Update README.md 2015-04-30 16:01:44 -05:00
Rouven Weßling 5da237e7fe Don't use a selector to get the heading text for search results.
Prevents bugs with special CSS characters.

Closes #201.
2015-04-30 16:01:44 -05:00
German Attanasio Ruiz 23eb3f433e Update README.md
Adding IBM Cloudant to the example list
2015-04-30 16:01:44 -05:00
Chrisovalantis Kefalidis fd684122dd Specify version for therubyracer gem to 0.12.1 2015-04-30 16:01:19 -05:00
Chrisovalantis Kefalidis 6ea72a13fa Update rake gem to version 10.4.2 2015-04-30 16:01:19 -05:00
Chrisovalantis Kefalidis a91ac15fab Update redcarpet gem to version 3.2.2 2015-04-30 16:01:19 -05:00
Chrisovalantis Kefalidis e14878ff56 Specify version for middleman-syntax gem to 2.0.0 2015-04-30 16:01:19 -05:00
Chrisovalantis Kefalidis 8276ba8ff4 Specify version for middleman-gh-pages gem to 0.0.3 2015-04-30 16:01:19 -05:00
Chrisovalantis Kefalidis ec2415e319 Update middleman gem to version 3.3.10 2015-04-30 16:01:18 -05:00
Chrisovalantis Kefalidis f5b34ba69a Remove middleman-livereload gem 2015-04-30 16:01:18 -05:00
Chrisovalantis Kefalidis c39c82c917 Remove ruby18_source_location gem 2015-04-30 16:01:18 -05:00
Chrisovalantis Kefalidis e3f36f9057 Remove wdm gem 2015-04-30 16:01:18 -05:00
Chrisovalantis Kefalidis a7596ff0a5 Cleanup Gemfile 2015-04-30 16:01:18 -05:00
Chrisovalantis Kefalidis a9d8b9b3b1 Cleanup config.rb 2015-04-30 16:01:18 -05:00
Rouven Weßling c8e7c646dc Optimize images
Saves 26,9% on logo png and a whopping 96,6% on navbar.png
2015-04-30 16:01:18 -05:00
Rouven Weßling 0dac148f95 Update Middleman to version 3.3.9 2015-04-30 16:01:18 -05:00
Rouven Weßling f2ebab9325 Rename SCSS files, so no unnecessary stylesheets are generated. 2015-04-30 16:01:18 -05:00
Rouven Weßling 1d13e198d0 Update normalize.css to version 3.0.2 2015-04-30 16:01:18 -05:00
Rouven Weßling b550f04dae Update jQuery UI to version 1.11.3 2015-04-30 16:01:18 -05:00
Rouven Weßling b1e0ff5d94 Update lunr.js to version 0.5.7 2015-04-30 16:01:17 -05:00
Rouven Weßling 3a236aa81e Update jQuery to version 2.1.3 2015-04-30 16:01:17 -05:00
Rouven Weßling a60fd38cda Use strict mode for JavaScript 2015-04-30 16:01:17 -05:00
Rouven Weßling 6062141cce Update Rouge to Version 1.8 2015-04-30 16:01:17 -05:00
Matthias Kadenbach 506095a1ed Add git to allow rake build 2015-04-30 16:01:17 -05:00
Robert Lord 7452f6b172 Merge pull request #237 from FloorLamp/patch-1
Fix typo
2015-04-30 15:31:56 -05:00
Norton Wang 321a994cdc Fix typo 2015-04-29 18:14:36 -04:00
Robert Lord d82f9707b3 Merge pull request #232 from kpavlov/patch-1
added api example: developers.cardpay.com
2015-04-23 14:21:56 -05:00
Konstantin Pavlov 923add84f6 added api example: developers.cardpay.com 2015-04-23 21:33:40 +03:00
Robert Lord 58bcd3922d Merge pull request #231 from davidstanley01/patch-1
Update of Readme
2015-04-21 21:35:37 -05:00
David Stanley 0bb523732e Update of Readme
Just saw that freaking NASA is using this for docs!
2015-04-21 15:57:48 -07:00
Robert Lord e644e3a5d3 Merge pull request #227 from jordanwalsh23/master
added whispir.io to the list of reference sites
2015-04-21 14:46:41 -05:00
Jordan Walsh 8e0396d7d3 added whispir.io to the list of reference sites 2015-04-21 19:06:15 +10:00
Robert Lord ebfe330a2b Merge pull request #222 from intelliot/patch-1
Add Coinbase to list of examples
2015-04-15 13:28:37 -05:00
Elliot Lee f5dcc4efb8 Add Coinbase to list of examples 2015-04-14 20:24:31 -07:00
Robert Lord 2011c772a1 Merge pull request #216 from switchpayments/master
Added Switch Payments to list of examples.
2015-04-08 19:57:01 -05:00
André Tavares fd2f54b3de Added Switch Payments to list of examples. 2015-04-08 15:17:00 +01:00
Robert Lord 384f8d7b19 Merge pull request #202 from xunzhang/master
"Examples of Slate in the Wild" update in README.md
2015-03-17 13:12:49 -05:00
xunzhang 97f87c9c4a Examples of Slate in the Wild update in README.md 2015-03-17 12:27:07 +08:00
Robert Lord 13b4487a2b Merge pull request #200 from styloa/patch-1
added Crafty Clicks API to Slate in the Wild list
2015-03-12 13:29:58 -05:00
Adam Stylo e619c48a94 added Crafty Clicks API to Slate in the Wild list 2015-03-12 15:55:09 +00:00
Robert Lord b744ba51bb Add @realityking to contributors list 2015-02-24 12:21:37 -06:00
Robert Lord 44a0dafe03 Merge pull request #184 from imaginator/patch-1
Added another "use in the wild"
2015-02-24 12:19:34 -06:00
Simon Tennant dfa162fed5 Take 2 2015-02-24 15:37:39 +01:00
Robert Lord fd1937a3f7 Merge pull request #185 from mehulkar/patch-2
Easy setup with Docker
2015-02-23 16:12:38 -06:00
Mehul Kar 10179a6613 Easy setup with Docker 2015-02-23 11:05:57 -08:00
Simon Tennant 8eada057da Update README.md 2015-02-19 17:23:38 +01:00
Simon Tennant 872c00a894 Update README.md 2015-02-19 16:05:18 +01:00
Tariq Islam bb5c721450 Merge pull request #165 from tripit/dev
Update master, all done, thanks @lord
2015-01-28 05:44:25 -08:00
Robert Lord 7c110ede15 Remove redcarpet header fix, just use vanilla redcarpet IDs 2015-01-23 20:54:24 -06:00
Robert Lord eb5235ff8c Remove outline from lang selector links, add focus style 2015-01-23 20:39:40 -06:00
Robert Lord 5ad521614d Readd parent() selection for code samples 2015-01-23 20:27:55 -06:00
Tariq Islam 73d2997a24 Merge pull request #159 from tripit/dev
Update master. Thanks @lord
2015-01-20 09:51:28 -08:00
Robert Lord 5e89f01652 Merge pull request #157 from swarzech/dev
fixed the rouge gem to fixed an issue with the language tabs
2015-01-15 12:40:54 -06:00
Robert Lord 757cf8ef79 Add note about Windows to Readme 2015-01-15 12:39:39 -06:00
Stefanie Nickerson ddf0346f04 fixed the rouge gem to fixed an issue with the language tabs 2015-01-15 06:42:31 -05:00
Robert Lord 115d04f19e Merge pull request #154 from Codestar/patch-1
Update README.md
2015-01-11 22:26:21 -06:00
Codestar f3eaecc7a9 Update README.md 2015-01-11 15:09:07 +02:00
Robert Lord 04944c9e8f Merge pull request #151 from zdenekdrahos/patch-1
Add RozpisyZapasu to list of examples
2015-01-05 14:52:49 -06:00
zdenekdrahos 868ea757f1 Add RozpisyZapasu to list of examples 2014-12-27 11:47:30 +01:00
Robert Lord be28b785a1 Fix scrollIntoView undefined bug when there is no hash, fixes #114
Thanks to @kevin-buttercoin for this fix!
2014-12-04 15:42:01 -08:00
Robert Lord 902ddd7fdc Update gems again 2014-12-04 15:39:17 -08:00
Tariq Islam c665bb4e8f Merge pull request #143 from tripit/dev
Fix some bugs, clean up, and readme changes.

Thanks @lord!
2014-12-04 12:40:45 -08:00
Robert Lord 14a9aac0fc Clarify auto-update info in readme 2014-11-13 21:28:00 -06:00
Robert Lord c74bf7b50e Merge pull request #131 from dmyers/text-shadow
Changed text shadows to use mixin
2014-11-13 14:53:35 -06:00
Derek Myers 2828342821 Changed text shadows to use mixin 2014-11-12 14:14:20 -06:00
Robert Lord 482f0b9979 Update language tabs to recalc toc heights on change 2014-11-05 20:56:48 -06:00
Tariq Islam 058429a9fd Merge pull request #124 from tripit/dev
Update gems and new markup styling issue.

All done, thanks Robert!
2014-11-04 22:31:35 -08:00
Robert Lord 77cdc3fc66 Update gems and new markup styling issue 2014-11-02 13:42:41 -06:00
Robert Lord 5e649aa91a Merge pull request #120 from Dwolla/add-dwolla-to-examples
add Dwolla to list of examples
2014-10-24 13:48:04 -05:00
Gordon Zheng a01287460a add Dwolla to list of examples 2014-10-24 00:39:21 -04:00
Robert Lord 3530073f2e Merge pull request #116 from xmo-odoo/patch-1
Fix Python examples
2014-10-14 13:47:00 -05:00
xmo-odoo 39dc7095f7 Fix Python examples 2014-10-14 12:00:40 +02:00
Tariq Islam 28b58aed86 Merge pull request #115 from tripit/dev
Small Docs Updates
2014-10-09 23:59:45 -07:00
Robert Lord ae07d2f6d2 Update contribution guidelines 2014-10-10 00:51:33 -05:00
Joel Richards ef4da2a0c0 Remove orchestrate.io from examples 2014-10-10 00:51:33 -05:00
fermayo f00be01caf added Dockerfile 2014-10-10 00:51:03 -05:00
Robert Lord ed5fa1b75c Merge pull request #113 from kevin-buttercoin/master
fixed XSS vuln in searchbox
2014-10-09 19:05:30 -05:00
Kevin Adams b1256f4800 changed to use jQuery text method 2014-10-09 14:59:06 -07:00
Kevin Adams b40b3b47ed fixed XSS vuln in searchbox 2014-10-08 16:38:46 -07:00
Robert Lord 9c7ea33305 Merge pull request #110 from sobstel/master
Add another example of slate (golazon).
2014-10-05 14:31:43 -05:00
Przemek Sobstel ff7f3c6708 Add another example of slate (golazon). 2014-10-05 12:08:22 +02:00
Tariq Islam bd6865585c Merge pull request #104 from klysium/patch-1
Update README.md

Thanks for the update, and using Slate!
2014-08-21 11:32:35 -07:00
Kennedy c59addd90e Update README.md
added mozilla's recroom
2014-08-21 13:56:35 -04:00
Tariq Islam 1f438aaea6 Merge pull request #103 from tripit/dev
Slate 1.1
2014-08-13 09:08:41 -07:00
Robert Lord 8e7b03b64e Upgrade redcarpet, fix header patch, fixes #92 2014-07-27 23:21:24 -07:00
Robert Lord 82a99d5537 Increase line height for new font, add quotes around fonts 2014-07-17 09:41:10 -07:00
Robert Lord 3c36233e6b Merge pull request #95 from samdozor/dev
Changing the code font-family to prefer Menlo/Consolas over Monaco
2014-07-17 09:37:42 -07:00
Sam Dozor 54e4408d8c Changing the code-font font-family to prefer Menlo and Consolas over Monaco, and adding more back-up fonts. 2014-07-16 16:53:38 -04:00
Robert Lord 965156d54b Merge pull request #94 from moizjv/updateappium
Adding appium docs to examples
2014-07-14 22:34:58 -07:00
Moiz Virani f1d92f01dd Adding appium docs to examples 2014-07-14 14:33:06 -07:00
Robert Lord 052036b0c2 Fix incorrect header nesting in redcarpet patch
Spotted by @moizjv, thanks!
2014-07-09 22:41:31 -07:00
Tariq Islam c5381c62af Merge pull request #90 from tripit/dev
Version 1.0
2014-07-08 12:56:19 -07:00
Robert Lord 60d1dbd37d Update search results to scroll, remove initial dummy text 2014-07-03 00:11:02 -07:00
Robert Lord 512b57d799 Update CHANGELOG.md 2014-07-03 00:05:23 -07:00
Robert Lord 395502b152 Add changelog 2014-07-03 00:01:48 -07:00
Robert Lord 18cd104883 Add better search results in a sliding search results area 2014-07-02 23:43:25 -07:00
Robert Lord 51e537aca2 Simplify embossed tocify styles, in preperation for search 2014-07-02 23:38:17 -07:00
Robert Lord 1b1427c3ee Add placeholder to search box 2014-07-02 22:46:14 -07:00
Robert Lord 324da17ba3 Fix bugs with toc not highlighting, begin search overhaul 2014-07-02 22:34:58 -07:00
Robert Lord 719061a638 Update headers to never have problematic characters in ids, fixes #64, hopefully 2014-06-24 22:34:25 -07:00
Robert Lord e8a491c9ea Fix unrecognized .highlight. bug #84 2014-06-24 01:03:39 -07:00
Robert Lord 0f89c96ba0 Remove extra 'also' from README 2014-06-23 23:16:58 -07:00
Robert Lord d552a1b5cb More readme changes 2014-06-23 23:15:31 -07:00
Robert Lord 1bb564f84a Shuffle readme, add link to myself 2014-06-23 23:14:37 -07:00
Robert Lord f868ff0bcf Revert to old versions of gems, until codeblock-messing up reason is identified 2014-06-22 03:14:38 -07:00
Robert Lord d1fdd685a6 Fix default language selection bug 2014-06-22 03:10:26 -07:00
Robert Lord 0f93ffcc2b More js cleanup for #80 2014-06-22 02:41:16 -07:00
Robert Lord bdbd7a38a8 Update app javascript to pass jslint, fixes #80 2014-06-22 02:37:01 -07:00
Robert Lord b147e6a2d7 Update toc javascript to hopefully be more performant 2014-06-22 02:33:55 -07:00
Robert Lord 200a871f85 Update gems 2014-06-22 02:02:14 -07:00
Tariq Islam 0a8c73adab Merge pull request #78 from tripit/dev
Fix scrolling bugs, history bugs, and add search minimum score to display
2014-06-22 00:16:07 -07:00
Robert Lord c1359e81a7 Add note to readme about Travis-ci and Mozilla 2014-06-16 12:09:08 -07:00
Robert Lord 3e4f52dfc9 Add note to readme about sauce labs 2014-06-15 22:34:25 -07:00
Robert Lord f4f2a1abfa Re-rotate nav button image 2014-06-14 19:43:37 -07:00
Robert Lord b5419b1e9c Add background to nav button 2014-06-14 19:41:08 -07:00
Robert Lord 3a91faa8e6 Fix bug with messed up margin on some code samples on phones 2014-06-14 19:19:48 -07:00
Robert Lord 062e35a3b3 Update readme with responsive notes 2014-06-14 18:55:32 -07:00
Robert Lord 00a93ec76e Add new screenshot to README 2014-06-12 16:04:52 -07:00
Robert Lord 65496915b8 Hide mobile lang selector and search on print 2014-06-12 16:02:41 -07:00
Robert Lord 3125ad8f72 Add extra margin to nav touch targets on mobile 2014-06-12 01:48:53 -07:00
Robert Lord 1e144aaa66 Update nav button to go to very edge of screen 2014-06-12 01:39:37 -07:00
Robert Lord 1478579c59 Add language tabs to navbar on phone 2014-06-12 01:32:25 -07:00
Robert Lord d77f1e7b9f Increase size of nav button touch target area 2014-06-12 00:51:50 -07:00
Robert Lord e559ad1097 Add first draft of single column design for phones 2014-06-12 00:32:50 -07:00
Robert Lord c2c35cfa25 Add retracting sidebar for tablets 2014-06-12 00:01:52 -07:00
Robert Lord ec9a688846 Fix bad h1 top border commit 2014-06-11 23:09:04 -07:00
Robert Lord 4d4f6efeb8 Fix #75, don't set page hash to undefined 2014-06-11 22:10:31 -07:00
Robert Lord 5a69760f54 Remove top border from first h1 of page 2014-06-11 22:04:08 -07:00
Robert Lord 0aff03f8da Fixes #77, update sites without search
Sites without search now do not include search.js or lunr.js,
making the javascript for those pages much faster to load.
2014-06-11 21:58:33 -07:00
Robert Lord b363be873b Update language tabs to properly stay in section, and fix history navigation bug 2014-06-11 21:49:37 -07:00
Robert Lord 7dead4c392 Update selected tab to not darken on mousedown 2014-06-11 21:21:28 -07:00
Robert Lord d71ae97f7a Remove excess whitespace, add extra equal sign to lang.js 2014-06-11 21:15:26 -07:00
Robert Lord cfa9f48dd0 Merge pull request #68 from bootstraponline/lang_default
Lang default
2014-05-18 23:08:13 -07:00
bootstraponline 1e341563fb Fix language on load 2014-05-18 12:29:03 -04:00
Robert Lord 34c6c69d61 Merge pull request #67 from stetzer/master
Add Emerging Threats API docs link to the readme
2014-05-15 21:39:20 -04:00
Mark Stetzer a35e1341b6 Adding link to Emerging Threats API docs 2014-05-15 16:14:02 -04:00
Robert Lord 078c42a082 Merge pull request #63 from chrissrogers/dev
Fixes a variable leak and simplifies low score filtration
2014-05-09 12:29:49 -04:00
Christopher Rogers e0cee862d9 Fixes a variable leak and simplifies low score filtration
Signed-off-by: Christopher Rogers <[email protected]>
2014-05-07 19:39:16 -07:00
Robert Lord e5cf1f2589 Merge pull request #56 from bootstraponline/scroll_to_closest_header
Scroll to closest header
2014-05-05 12:16:45 -04:00
Robert Lord 6bf197da74 Merge pull request #53 from bootstraponline/dont_crash_on_invalid_language
Don't crash on invalid language
2014-05-05 11:51:40 -04:00
Tariq Islam 0eb4caa425 Merge pull request #49 from tripit/dev
Search, bug fixes, new features, and more
2014-05-03 17:22:36 -07:00
bootstraponline 2f617a2160 Scroll to closest header 2014-04-27 21:50:33 -04:00
Robert Lord 938a056fcc Merge pull request #51 from bootstraponline/search
Reposition headers based on score
2014-04-27 19:15:17 -04:00
bootstraponline f9c4195f23 Don't crash on invalid language 2014-04-27 16:32:11 -04:00
bootstraponline 2384d9af3f Reposition headers based on score
Enforce min score
2014-04-26 10:23:57 -04:00
Robert Lord adcf6c7099 Update Travis badge to SVG in README 2014-04-22 13:50:12 -04:00
Robert Lord 126ea199cc Add contributors section to README 2014-04-22 11:18:44 -04:00
Robert Lord edd22fde15 Update rake 2014-04-22 11:07:52 -04:00
Robert Lord 84b3e5e3d6 Add search frontmatter key to specify visibility of search 2014-04-21 12:18:44 -04:00
Robert Lord 0316c0c5e9 Add aside print styling, fixes #4 2014-04-21 12:10:36 -04:00
Robert Lord 259bf0f560 Remove from screen.scss that was causing errors 2014-04-21 12:05:07 -04:00
Robert Lord 6d73ffb052 Remove leftover console.logs from tocify edits 2014-04-21 11:56:00 -04:00
Robert Lord 75869a561f Prevent reload page on language change, fixes #36
- Uses onpopstate to handle users pressing back buttons
- Switches the query string and changes the language
  without reloading.
- Thanks to @bootstraponline for some of the code in this commit.
2014-04-21 11:31:32 -04:00
Robert Lord 215d773fcf Merge pull request #46 from bootstraponline/flash_fix
Fix flash on page load
2014-04-21 11:02:18 -04:00
bootstraponline 03b28598cc Fix flash as suggested by @lord 2014-04-19 22:01:56 -04:00
Robert Lord df2d2ec5e9 Add note about browsers to contributing.md 2014-04-19 21:36:15 -04:00
Robert Lord 0feb513407 Adjust search info appearance, fix Firefox bug 2014-04-19 21:36:00 -04:00
Robert Lord 7386fd2e21 Fix bug with section padding and long code blocks 2014-04-19 18:04:23 -04:00
Robert Lord fc0c995cb6 Add h4,h5,h6 as suggested by @bootstraponline, fix section padding 2014-04-19 17:58:42 -04:00
Robert Lord 06680512a8 Update gems 2014-04-19 17:18:21 -04:00
Robert Lord e3e72aec83 Add contributing.md 2014-04-17 13:46:29 -04:00
Robert Lord 26eef2f000 Merge pull request #35 from tmp-/fix_flash_on_load
Prevent flash on first load
2014-04-17 12:08:32 -04:00
Robert Lord 9bbb62877e Merge branch 'lunr' of https://github.com/chrissrogers/slate into dev 2014-04-17 11:27:03 -04:00
bootstraponline cabfe33e58 Prevent flash on first load 2014-04-16 20:37:57 -04:00
Christopher Rogers a4e1fb3c24 Adds js handler for setting search info box width equal to content width
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-16 15:05:05 -07:00
Christopher Rogers 0ebbae94fe Cleans up some css
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-16 14:11:43 -07:00
Christopher Rogers 440c799c4c Removes lunr stemmer from search index pipeline
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-16 14:03:58 -07:00
Tariq Islam 3de499063c Merge pull request #31 from tripit/dev
Update gems
2014-04-15 23:11:47 -07:00
Christopher Rogers 3c4e0d0bae Switches jQuery to load through Google CDN
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-15 13:44:56 -05:00
Christopher Rogers 0a0e84b8e8 Standardizes search styles
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-15 13:40:45 -05:00
Christopher Rogers 477146e5bf Removes .ruby-version
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-15 11:02:32 -05:00
Robert Lord 4e37eeb524 Update gems 2014-04-14 12:34:06 -04:00
Christopher Rogers 4d568401a3 Improves no results notice and removes highlight and notice on search field blur
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-11 20:36:03 -07:00
Christopher Rogers 535a3b0486 Adds 'no results found' message to search
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-11 18:01:55 -07:00
Christopher Rogers 5892700b5c Fixes toc alignment on search and adds highlight library
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-11 10:27:15 -07:00
Christopher Rogers 59ee05c606 Updates search field markup and style
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-10 17:47:01 -07:00
Christopher Rogers f2d1387971 Adds search icon
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-10 17:46:39 -07:00
Christopher Rogers 02e48810ba Adds .ruby-version
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-10 17:05:42 -07:00
Tariq Islam 0ddd142279 Merge pull request #28 from tripit/dev
Update gems
2014-04-10 02:42:19 -07:00
Christopher Rogers e669a0d4ac Adds rudimentary search support
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-09 18:12:55 -07:00
Christopher Rogers d1b98be182 Cleans up layout
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-09 18:12:05 -07:00
Christopher Rogers b171d3c48a Organizes javascript into libraries and application code
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-09 14:54:23 -07:00
Christopher Rogers adf94ffbf6 Simplifies layout language selection and js init logic
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-09 14:45:41 -07:00
Christopher Rogers 68807cd485 Fixes some leakage in the language selector
Signed-off-by: Christopher Rogers <[email protected]>
2014-04-09 14:14:24 -07:00
Robert Lord ebbd2d89b4 Update gems 2014-04-07 12:03:45 -04:00
Tariq Islam 53db9d4a64 Merge pull request #26 from tripit/dev
Update gems and add new site to README
2014-04-01 10:03:13 -07:00
Robert Lord 32e0470613 Update gems 2014-04-01 11:17:05 -04:00
Robert Lord ca23e5ccdc Add PricePlow to README 2014-04-01 11:13:54 -04:00
Tariq Islam 0c41f3c213 Merge pull request #23 from tripit/dev
Bug fixes, a new link in the README, and a new partials feature
2014-03-31 01:02:42 -07:00
Robert Lord c091fae675 Update gems 2014-03-30 22:53:27 -04:00
Robert Lord e307f1dbbb Update jQuery to work over SSL, resolves #18 2014-03-30 22:46:48 -04:00
Robert Lord c364a2329b Add link in README to Build a Quine tutorial 2014-03-30 22:44:30 -04:00
Robert Lord 88fcba6798 Add explanation of partials to _errors.md 2014-03-30 22:42:54 -04:00
Marcio Lima b90436e48e Add ability to split content in include files. 2014-03-28 12:29:31 -03:00
Tariq Islam dc7a7dce41 Merge pull request #16 from tripit/dev
Update gems and add execjs and therubyracer
2014-03-25 11:38:04 -07:00
Robert Lord 6e7c428315 Add execjs and rubyracer to Gemfile, should resolve #12 2014-03-21 21:13:49 -04:00
Robert Lord fa0115eb57 Add .cache to gitignore 2014-03-21 21:07:19 -04:00
Robert Lord 8eb3ce6237 Update gems 2014-03-21 21:06:36 -04:00
Tariq Islam bb83688fc7 Merge pull request #10 from lord/master
Update gems, switch redcarpet to gem reference now that 3.1 is released
2014-02-18 12:55:34 -08:00
Robert Lord 5b3dd91fff Update gems, switch redcarpet to gem reference now that 3.1 is released 2014-02-18 15:39:33 -05:00
Tariq Islam 3c5231fbd0 Merge pull request #8 from lord/master
Update gems - merged from Robert.
2014-02-14 12:13:19 -08:00
Robert Lord 8ea97c7f77 Update gems 2014-02-07 20:06:23 -05:00
Robert Lord 5bc89ce207 Update middleman-syntax and rouge, should fix #6 2014-01-13 15:24:17 -08:00
Robert Lord b7a4296978 Add ChaiOne to Slate examples in readme 2014-01-11 09:13:38 -08:00
Robert Lord 3321fd1616 Update README.md 2014-01-10 22:10:07 -08:00
Robert Lord 7defe86bf6 Update gems 2014-01-10 12:27:39 -08:00
Robert Lord 35598a4e16 Add styling for horizontal rules 2013-12-31 16:34:19 -08:00
Robert Lord d84692b7b9 Delete syntax.md
All of its contents are now hosted in the wiki.
2013-12-20 15:37:48 -08:00
Robert Lord 1c9d74a75b Update README.md, remove FAQ, add link to wiki 2013-12-20 15:26:52 -08:00
Robert Lord d744b8a22f Add styling for definition lists 2013-12-20 10:44:14 -08:00
Robert Lord 730ee46de9 Update middleman gem 2013-12-19 16:45:06 -08:00
Robert Lord a406646361 Update gems 2013-12-04 11:42:22 -08:00
Robert Lord c1502d0476 Add asides instructions to syntax.md 2013-11-18 11:39:47 -08:00
Robert Lord 446f9341b9 Update Gemfile.lock 2013-11-18 11:10:08 -08:00
Robert Lord 6d7ba45f2c Remove unused github-linguist dependency 2013-11-18 11:00:50 -08:00
Robert Lord 24729b9308 Update Travis icon to only check master branch 2013-11-18 10:45:01 -08:00
Robert Lord bc9b1263be Fix Travis README icon 2013-11-18 10:39:18 -08:00
Robert Lord 9331e3b9db Add travis config, add travis icon to README 2013-11-18 10:31:36 -08:00
Robert Lord 27e0db7d14 Set default task to build 2013-11-18 09:31:28 -08:00
Robert Lord 42cb37cc9e Add rake to Gemfile 2013-11-18 09:31:07 -08:00
Robert Lord 5b306d8134 Merge pull request #2 from tripit/dev
Update github-linguist, middleman, add Gemnasium status badge
2013-11-18 09:08:24 -08:00
Robert Lord b39deb560f Update github-linguist, middleman, add Gemnasium status badge 2013-11-18 09:00:12 -08:00
Robert Lord 198484ef2b Add hack to prevent tocify from animating on page load 2013-10-31 13:44:02 -07:00
Robert Lord d3ab3cc4b7 Add asides for nice-looking notes and warnings
You can now use <aside> with the classes

- warning
- success
- notice

to add little colored notes to your API docs! Examples are in
source/index.md.

Also, note that if you have a custom variables.scss, you'll have
to add

+$aside-notice-bg: #8fbcd4;
+$aside-warning-bg: #c97a7e;
+$aside-success-bg: #6ac174;

to it.
2013-10-31 12:26:07 -07:00
Robert Lord ca40fed573 Add note to README about variables.scss 2013-10-31 11:41:25 -07:00
Robert Lord bdd05b3c0c Fix bug where weird white block could appear at bottom of page.
This bug occured if the final code sample extended beyond the
explanation at the bottom of the page.

To fix it, I simply made the background color of body a soft
blue.
2013-10-31 10:56:54 -07:00
Robert Lord 7c6594c595 Update README with more info about logo size 2013-10-31 10:54:54 -07:00
Robert Lord c404bc02df Add default language selecting with localStorage
Now, Slate will use localStorage to track the last selected
language, so if the user visits again with no language in the URL,
they will be redirected to the last language they used.

This update also fixes a bug with how the layout inserts the
language names into the javascript. Previously, they would be,
(for some inane reason) arrays within arrays. It is now just
an array. Whoever wrote that previous code was clearly a fool.
2013-10-29 10:40:07 -07:00
Robert Lord 5acbe01c3d Fix capitalization error in README 2013-10-25 11:41:13 -07:00
Robert Lord e2b70aff42 Change print stylesheet, fix slight bug with naming
Also changed README wording slightly.
2013-10-25 11:39:53 -07:00
Robert Lord 834119ecd8 Add print stylesheet 2013-10-25 11:33:19 -07:00
Robert Lord 26d47b06a0 Fix bug where syntax.css would fail to compile, causing errors 2013-10-21 15:17:35 -07:00
Robert Lord dc33dfe0a7 Add table of contents footer links feature
Now you can add links to the bottom of your table of contents!
Documentation has also been changed to reflect this new
feature.

Please note that if you have a custom variables.scss, you'll have
to merge in this line:

    $nav-footer-border-color: #666;
2013-10-21 14:09:38 -07:00
Robert Lord 81b9110cad Add Github hosting feature to README 2013-10-20 21:14:19 -07:00
Robert Lord 052ac6db37 Update README, made it much more simple and clear
* Reworded a bunch of sections
* Added "features" section with bullets for the various features
  of Slate
* Added screenshot of Slate
2013-10-16 13:26:42 -07:00
Robert Lord f10e324f5f Fix incorrect instructions in syntax help file 2013-10-16 13:01:47 -07:00
Robert Lord 6a22d7c11d Update SCSS, fix bug where code annotations had wrong font. 2013-10-16 12:45:00 -07:00
Robert Lord 35a5d35512 Hide TripIt docs link from README, since they're not ready yet. 2013-10-14 15:35:26 -07:00
Robert Lord 04c0abbcfa Refactor SCSS for better organization, readability, and customizability. 2013-10-11 15:55:46 -07:00
Robert Lord e5658f498a Update copyright year 2013-10-11 11:55:42 -07:00
Robert Lord 2e9e2b65f8 Add special thanks to readme 2013-10-11 11:30:08 -07:00
Robert Lord 91ff8a738a Add copyright information to source files 2013-10-11 11:28:04 -07:00
Robert Lord 20f303f3d1 Remove unused commented-out settings from config.rb 2013-10-11 10:59:06 -07:00
Robert Lord a250c6dbc2 Add :no_intra_emphasis to redcarpet settings 2013-10-10 10:16:32 -07:00
Robert Lord 489fb16b94 Update README.md and default index.md
Seriously upgraded the README documentation.
ALso, added a tiny fix to the Kittn example in index.md.
2013-10-09 15:18:14 -07:00
Robert Lord d72f08d936 Update README.md
Changed a tiny wording thing.
2013-10-08 09:37:51 -07:00
Robert Lord e7a0552edc Update README.md
Fixed some more formatting problems, and reworded a few sections.
2013-10-07 14:47:09 -07:00
Robert Lord 916412b51b Update syntax.md
Fixed another problem where the table example would show up as an actual table instead of a markdown example.
2013-10-07 12:49:12 -07:00
Robert Lord 6155c542a3 Update syntax.md
Fixed a tiny problem
2013-10-07 12:48:15 -07:00
Robert Lord 8aa611f77a Adjust styling of border below h1 tag to be more consistent 2013-10-07 12:30:32 -07:00
Robert Lord 84b7705fd1 Add Kittn example file 2013-10-07 12:29:37 -07:00
Robert Lord 2d75b7a04d Add syntax documentation 2013-10-07 12:29:19 -07:00
Robert Lord dac9cff8fc Update README.md
Added some more helpful information.
2013-10-07 12:28:33 -07:00
Robert Lord b15573155e Added sample code to index.md 2013-10-03 18:02:49 -07:00
Robert Lord e641bd7d4a Add generic logo, remove TripIt logos. 2013-10-03 18:02:18 -07:00
Robert Lord d562c8f71e Neeeevermind Apache License 2013-10-03 17:57:35 -07:00
Robert Lord 0a78bb259f Add MIT License 2013-10-03 17:30:50 -07:00
Robert Lord a4e05ed506 Update README.md
Just made some edits, added the Slate name, and fixed the code rendering issue.
2013-10-03 17:24:37 -07:00
Robert Lord 82b8ca4616 A new README 2013-10-03 16:54:40 -07:00
Robert Lord 63429f1e9a Added relative links that are needed for hosting a page in a non-root directory. 2013-10-03 14:58:18 -07:00
Robert Lord 1030376e37 More style fixes and changes 2013-10-01 13:32:30 -07:00
Robert Lord f7e6117e85 Various style updates and bugfixes 2013-09-27 13:42:57 -07:00
Robert Lord 38ad55851a Change language declaration
Now, if no languages are specified, the language selection bar simply doesn't appear.

It has also been renamed from "languages" to "language_tabs".

Additionally, now language names default to the markdown name. They can still be overridden by the languagename:Display Name syntax.
2013-09-27 13:37:36 -07:00
Robert Lord 7ae21ae972 Fix padding bug with blockquotes in the right area. 2013-09-25 10:15:59 -07:00
Robert Lord fd80b240bd Make every other table row have a different color 2013-09-24 14:54:42 -07:00
Robert Lord 28f442e529 Add automatic hash generation with redcarpet dev, and change tocify scrollHistory to be compatible with the ids 2013-09-24 12:25:12 -07:00
Robert Lord 7ba798c51d Overflow fixes and toc horizontal scrollbar fix 2013-09-24 10:35:33 -07:00
Robert Lord 5cc9188e3c Fix tocify to reset cache on browser window resize 2013-09-24 09:47:40 -07:00
Robert Lord 9e84ebf28c Add margin to bottom of tables 2013-09-24 09:39:19 -07:00
Robert Lord 541998da39 Change code buttons to refresh the page with the language name after the ? in the url
This commit has two benefits:

- Now, copying the URL will actually copy the current language as well. You don't need to think to add a language

- If a language moves objects on the page down, the tocify header location cache will not be invalid, since it is generated on page load. Therefore, if code moves some elements down, since the page is reloaded, the cache will also be regenerated, to reflect the real locations of the headers.
2013-09-23 12:06:39 -07:00
Robert Lord bc79a655e5 More changes 2013-09-23 10:38:13 -07:00
Robert Lord 6c077ba873 Add stylesheets, javascript, and html for documentation generation 2013-09-17 15:44:08 -07:00
Robert Lord 7edf6c803d Add build/ directory to .gitignore 2013-09-13 15:32:37 -07:00
Robert Lord a1e673e599 Initial commit, with default middleman code 2013-09-13 15:24:31 -07:00
Robert Lord f656a9aa6d Add .DS_STORE to .gitignore 2013-09-13 15:19:33 -07:00
Robert Lord 9a707a0172 Initial commit 2013-09-13 15:15:24 -07:00
188 changed files with 17614 additions and 13179 deletions

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
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.
+5
View File
@@ -0,0 +1,5 @@
!!!!! 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.
+23 -4
View File
@@ -1,6 +1,25 @@
.classpath
.project
.settings/
target/
*.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/
.idea/
*.iml
+10
View File
@@ -0,0 +1,10 @@
sudo: false
language: ruby
rvm:
- 2.2.5
- 2.3.1
cache: bundler
script: bundle exec middleman build
+88
View File
@@ -0,0 +1,88 @@
# 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
-5
View File
@@ -1,5 +0,0 @@
Dave Marion ([email protected])
Eric Newton ([email protected])
Jim Klucar ([email protected])
Charlie Williams ([email protected])
Drew Farris ([email protected])
+9
View File
@@ -0,0 +1,9 @@
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
View File
@@ -0,0 +1,122 @@
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
+10 -200
View File
@@ -1,203 +1,13 @@
Copyright 2008-2013 Concur Technologies, Inc.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
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
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.
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.
+75 -145
View File
@@ -1,180 +1,110 @@
# Timely
---
<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>
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo] (http://accumulo.apache.org/) and [Grafana] (http://www.grafana.org).
<p align="center">Slate helps you create beautiful, intelligent, responsive API documentation.</p>
# Design
---
<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>
Being big users of Apache Accumulo, when we required a time series database application we looked to use software that was compatible with it. Specifically, we started with [OpenTSDB] (http://www.opentsdb.net) and Eric Newton's [Accumulo-OpenTSDB] (https://github.com/ericnewton/accumulo-opentsdb) project to bridge them together and Grafana for visualization. We ultimately ran into some issues that we could not work around. After much discussion we decided to replace OpenTSDB with something more native to Apache Accumulo. The implementation of Timely started with the following constraints and assumptions:
<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>
1. We need to show progress quickly.
2. We started with Grafana, so we didn't need our own user interface.
3. We already had experience with OpenTSDB, so we started with its API.
4. Space is cheap, ingest and query speed are more important.
5. Metrics will be kept short term.
6. The number of tags used for each metric will be small \( < 10 \).
7. We can create a better API and Grafana plugin at a later time.
Features
------------
## Building
* **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.
Timely uses Maven for compiling source, running tests, and packaging distributions. Below is a set of useful Maven lifecyles that we use:
Command | Description
--------|------------
mvn compile | Compiles and formats the source
mvn test | Runs unit tests
mvn package | Runs findbugs and creates a distribution
mvn verify | Runs integration tests
mvn verify site | Creates the site
The [CollectD] (http://collectd.org/) plugins require that CollectD is installed locally as the `/usr/share/collectd/java/collectd-api.jar` file is a dependency.
* **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.
## Deployment
* **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.
The Timely server requires a Java 8 runtime. Timely utilizes iterators for Apache Accumulo, so your Accumulo instance will need to be run with Java 8 also.
* **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.
Create a distribution and untar it somewhere. Modify the `conf/timely.properties` file appropriately. Then, copy the `timely-server` and `commons-lang3` jar files to your Accumulo tservers. Next, launch Timely by running the `bin/timely-server.sh` script.
* **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.
If you just want to kick the tires without having to install and setup Apache Hadoop and Apache Accumulo, then you can start Timely with the `bin/timely-standalone.sh` script. This will start an Accumulo MiniCluster in the background, but be aware that the standalone instance will not save your metric data across restarts.
* **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.
## API
* **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.
The current Timely API is compatible with a subset of the OpenTSDB API. We currently support the OpenTSDB TCP put API and we have partially implemented the following HTTP operations that the Grafana OpenTSDB datasource uses:
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).
* /api/aggregators
* /api/query
* /api/search/lookup
* /api/suggest
Getting Started with Slate
------------------------------
We also provide the following additional endpoints:
### Prerequisites
* /login
You're going to need:
This endpoint should be used for non-anonymous access. It supports a POST request with username and password, or a GET request with SSL mutual authentication.
- **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.
* /api/metrics
### Getting Set Up
This endpoint reports the metric names and tags that have been pushed to Timely. Eviction of this information is configurable, so it may reflect metrics that have been reported in the past but are no longer being actively reported. This could be useful in creating graphs in Grafana (so you won't have to go digging in the `meta` table in Accumulo).
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:
## Storage Format
```shell
# either run this to run locally
bundle install
bundle exec middleman server
Metrics sent to Timely are stored in two Accumulo tables, `meta` and `metrics`. The `meta` table stores information about the metric names and tags. It's format is:
Row | ColumnFamily | ColumnQualifier | Value
----|:-------------|:----------------|:-----
m:metric | | |
t:metric | tagKey | |
v:metric | tagKey | tagValue |
The `metrics` table stores the datapoints using the following format:
Row | ColumnFamily | ColumnQualifier | Value
----|:-------------|:----------------|:-----
metric\timestamp | tagKey=tagValue | tagKey=tagValue,tagKey=tagValue,... | metricValue
As an example, if you sent the following metric to the put api
```
put sys.cpu.user 1447879348291 2.0 rack=r001 host=r001n01 instance=0
# OR run this to run with vagrant
vagrant up
```
it would get stored in the following manner in the `meta` table:
You can now see the docs at http://localhost:4567. Whoa! That was fast!
Row | ColumnFamily | ColumnQualifier | Value
----|:-------------|:----------------|:-----
m:sys.cpu.user | | |
t:sys.cpu.user | host | |
t:sys.cpu.user | instance | |
t:sys.cpu.user | rack | |
v:sys.cpu.user | host | r001n01 |
v:sys.cpu.user | instance | 0 |
v:sys.cpu.user | rack | r001 |
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).
and in the following manner in the `metrics` table
If you'd prefer to use Docker, instructions are available [in the wiki](https://github.com/lord/slate/wiki/Docker).
Row | ColumnFamily | ColumnQualifier | Value
----|:-------------|:----------------|:-----
sys.cpu.user\1447879348291 | host=r001n01 | instance=0,rack=r001 | 2.0
sys.cpu.user\1447879348291 | instance=0 | host=r001n01,rack=r001 | 2.0
sys.cpu.user\1447879348291 | rack=r001 | host=r001n01,instance=0 | 2.0
Companies Using Slate
---------------------------------
## Configuration
* [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/)
The `NUM_SERVER_THREADS` variable in the `timely-server.sh` script controls how many threads are used in the Netty event group for TCP and HTTP operations. The TCP and HTTP groups use a different event group, so if you set the value to 8, then you will have 8 threads for TCP operations and 8 threads for HTTP operations. The properties file in the `conf` directory supports the following properties:
You can view more in [the list on the wiki](https://github.com/lord/slate/wiki/Slate-in-the-Wild).
Property | Description | Default Value
:--------|:------------|:-------------
timely.ip | The ip address where the Timely server is running |
timely.port.put | The port that will be used for processing put requests |
timely.port.query | The port that will be used for processing query requests |
timely.instance\_name | The name of your Accumulo instance |
timely.zookeepers | The list of Zookeepers for your Accumulo instance |
timely.username | The username that Timely will use to connect to Accumulo |
timely.password | The password of the Accumulo user |
timely.table | The name of the metrics table | timely.metrics
timely.meta | The name of the meta table | timely.meta
timely.write.latency | The Accumulo BatchWriter latency | 5s
timely.write.threads | The Accumulo BatchWriter number of threads | [default](https://github.com/apache/accumulo/blob/master/core/src/main/java/org/apache/accumulo/core/client/BatchWriterConfig.java#L49)
timely.write.buffer.size | The Accumulo BatchWriter buffer size | [default](https://github.com/apache/accumulo/blob/master/core/src/main/java/org/apache/accumulo/core/client/BatchWriterConfig.java#L40)
timely.metric.age.off.days | The number of days to keep metrics | 7
timely.cors.allow.any.origin | Allow any origin in cross origin requests (true/false) | false
timely.cors.allow.null.origin | Allow null origins in cross origin requests (true/false) | false
timely.cors.allowed.origins | List of allowed origins in cross origin requests (can be null or comma separated list)
timely.cors.allowed.methods | List of allowed methods for cross origin requests | <ul><li>DELETE</li><li>GET</li><li>HEAD</li><li>OPTIONS</li><li>PUT</li><li>POST</li></ul>
timely.cors.allowed.headers | Comma separated list of allowed HTTP headers for cross origin requests | content-type
timely.cors.allow.credentials | Allow credentials to be passed in cross origin requests (true/false) | true
timely.scanner.threads | Number of BatchScanner threads to be used in a query | 4
timely.metrics.report.tags.ignored | Comma separated list of tags which will not be shown in the /api/metrics response |
timely.meta.cache.expiration.minutes | Number of minutes after which unaccessed meta information will be purged from the meta cache | 60
timely.meta.cache.initial.capacity | Initial capacity of the meta cache | 2000
timely.meta.cache.max.capacity | Maximum capacity of the meta cache | 10000
timely.ssl.certificate.file | Public certificate to use for the Timely server (x509 pem format) |
timely.ssl.key.file | Private key to use for the Timely server (in pkcs8 format) |
timely.ssl.key.pass | Password to the private key |
timely.ssl.use.generated.keypair | Use a generated certificate/key pair - useful for testing | false
timely.ssl.trust.store.file | Certificate trust store (a concatenated list of trusted CA x509 pem certificates) |
timely.ssl.use.openssl | Use OpenSSL (vs JDK SSL) | true
timely.ssl.use.ciphers | List of allowed SSL ciphers | see Configuration.java
timely.session.max.age | Setting for max age of session cookie (in seconds) | 86400
timely.http.address | Address for the Timely server, used for the session cookie domain |
grafana.http.address | Address for the Grafana server |
timely.allow.anonymous.access | Allow anonymous access | false
timely.visibility.cache.expiration.minutes | Column Visibility Cache Expiration (minutes) | 60
timely.visibility.cache.initial.capacity | Column Visibility Cache Initial Capacity | 2000
timely.visibility.cache.max.capacity | Column Visibility Cache Max Capacity | 10000
Need Help? Found a bug?
--------------------
## Security
[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.
Timely allows users to optionally label their data using Accumulo column visibility expressions. To enable this feature, users should put the expressions in a tag named `viz`. Timely will [flatten] (http://accumulo.apache.org/1.7/apidocs/org/apache/accumulo/core/security/ColumnVisibility.html#flatten%28%29) this expression and store it in the column visibility of the data point in the metrics table. Column visibilities are *not* stored in the meta table, so anyone can see metric names, tag names, and tag values.
Contributors
--------------------
Timely provides HTTPS access to the query endpoints. It is possible to allow anonymous access to these endpoints which will allow anyone to see unlabeled data. Timely uses Spring Security to configure user authentication and user role information. Users must call the `/login` endpoint for authentication and Timely will respond by setting a HTTP cookie with a session id. The response to a successful login will be a temporary redirect (HTTP 307) to the configured address for the Grafana server. Grafana itself must be configured to use https for this to work properly. For more information see the [Quick Start] (quick-start/QUICK_START.md) documentation.
Slate was built by [Robert Lord](https://lord.io) while interning at [TripIt](https://www.tripit.com/).
## Tuning
Thanks to the following people who have submitted major pull requests:
1. Each thread in the Timely server that is used for processing TCP put operations has its own BatchWriter. Each BatchWriter honors the `timely.write.latency` and `timely.write.threads` configuration property, but the buffer size for each BatchWriter is `timely.write.buffer.size` divided by the number of threads. For example, if you have 8 threads processing put operations and the following settings, then you will have 8 BatchWriters each using 2 threads with a 30s latency and a maximum buffer size of 128M:
- [@chrissrogers](https://github.com/chrissrogers)
- [@bootstraponline](https://github.com/bootstraponline)
- [@realityking](https://github.com/realityking)
- [@cvkef](https://github.com/cvkef)
```
timely.write.latency=30s
timely.write.threads=2
timely.write.buffer.size=1G
```
2. The `timely.scanner.threads` property is used for BatchScanners on a per query basis. If you set this to 32 and have 8 threads processing HTTP operations, then you might have 256 threads concurrently querying your tablet servers. Be sure to set your ulimits appropriately.
3. The Timely server contains an object called the meta cache, which is a cache of the keys that would be inserted into the meta table if they did not already exist. This cache is used to reduce the insert load of duplicate keys into the meta table and to serve up data to the `/api/metrics` endpoint. The meta cache is an object that supports eviction based on last access time and can be tuned with the `timely.meta.cache.*` properties.
## NOTES
1. Not shown in the examples is the encoding of the row and value. You can apply the Timely formatter to your metrics table using the Accumulo shell command:
```
config -t timely.metrics -s table.formatter=timely.util.TimelyMetricsFormatter
```
2. You can create split points for your metrics table based on the 'm' column family in the meta table.
3. You can lower the `table.scan.max.memory` property on your metrics table in an attempt to get data back faster from the tablet servers.
4. If you don't mind losing some metric data in the event of an Accumulo tablet server death, you can set the `table.walog.enabled` property to false and the `table.durability` property to none on your metrics table. This should speed up ingest a little.
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/)
Vendored
+39
View File
@@ -0,0 +1,39 @@
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
-19
View File
@@ -1,19 +0,0 @@
# Write NSQ plugin for CollectD
To build, run ```mvn clean package```. Place the resulting jar file somewhere on the local filesystem and add the following to the
collectd configuration file
```
LoadPlugin java
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:<path_to_jar>/collectd-nsq-plugin-0.0.1-SNAPSHOT.jar"
JVMArg "-Xms1024m"
JVMArg "-Xmx1024m"
LoadPlugin "timely.collectd.plugin.WriteNSQPlugin"
<Plugin "timely.collectd.plugin.WriteNSQPlugin">
Host "<NSQHost[,NSQHost]>"
Port "<NSQHttpPort>"
</Plugin>
</Plugin>
```
-129
View File
@@ -1,129 +0,0 @@
<?xml version="1.0"?>
<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>0.0.1</version>
</parent>
<artifactId>collectd-nsq-plugin</artifactId>
<dependencies>
<dependency>
<groupId>collectd</groupId>
<artifactId>collectd-api</artifactId>
<version>5.5.1</version>
<scope>system</scope>
<systemPath>${collectd-api-jar}</systemPath>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>collectd-plugin-parent</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<effort>Max</effort>
<threshold>Medium</threshold>
<xmlOutput>true</xmlOutput>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<artifactSet>
<includes>
</includes>
</artifactSet>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<id>shade</id>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>0.5.2</version>
<configuration>
<configFile>${project.parent.basedir}/eclipse/Eclipse-Formatter.xml</configFile>
<lineEnding>LF</lineEnding>
<overrideConfigCompilerVersion>true</overrideConfigCompilerVersion>
</configuration>
<executions>
<execution>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<goals>
<goal>format</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
@@ -1,149 +0,0 @@
package timely.collectd.plugin;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Random;
import org.apache.http.HttpStatus;
import org.apache.http.client.entity.EntityBuilder;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.collectd.api.Collectd;
import org.collectd.api.CollectdConfigInterface;
import org.collectd.api.CollectdShutdownInterface;
import org.collectd.api.CollectdWriteInterface;
import org.collectd.api.OConfigItem;
import org.collectd.api.ValueList;
/**
*
* CollectD plugin that writes metrics collected by the StatsD plugin to NSQ.
* NSQ can be configured to write statsd metrics to CollectD. If you do this,
* set the statsd prefix in NSQ to 'nsq'.
*
*/
public class WriteNSQPlugin extends CollectDPluginParent implements CollectdConfigInterface, CollectdShutdownInterface,
CollectdWriteInterface {
private String host = null;
private int port = 0;
private String topic = "metrics#ephemeral";
private String endpoint = null;
private ByteArrayOutputStream baos = null;
private DataOutputStream out = null;
private CloseableHttpClient client = HttpClients.createDefault();
public WriteNSQPlugin() {
Collectd.registerConfig(WriteNSQPlugin.class.getName(), this);
Collectd.registerShutdown(WriteNSQPlugin.class.getName(), this);
Collectd.registerWrite(WriteNSQPlugin.class.getName(), this);
}
public synchronized int write(ValueList vl) {
baos = new ByteArrayOutputStream();
out = new DataOutputStream(baos);
super.process(vl);
try {
out.flush();
HttpPost post = new HttpPost(endpoint);
EntityBuilder request = EntityBuilder.create();
request.setBinary(baos.toByteArray());
post.setEntity(request.build());
CloseableHttpResponse response = client.execute(post);
try {
int code = response.getStatusLine().getStatusCode();
String msg = response.getStatusLine().getReasonPhrase();
// Consume the entire response so the connection will be reused
EntityUtils.consume(response.getEntity());
if (code != HttpStatus.SC_OK) {
Collectd.logInfo("Code: " + code + ", msg: " + msg);
}
} finally {
response.close();
}
} catch (IOException e) {
Collectd.logError("Error sending metrics to NSQ: " + e.getMessage());
} finally {
if (null != out) {
try {
out.close();
out = null;
} catch (IOException e) {
}
}
}
return 0;
}
@Override
public synchronized void write(String metric) {
try {
out.writeBytes(metric);
} catch (IOException e) {
Collectd.logError("Error writing metric: " + e.getMessage());
try {
out.close(); // Should cause IOException in write.
} catch (IOException e1) {
}
}
}
public void flush() {
}
public synchronized int shutdown() {
Collectd.logInfo("Shutting down connection to NSQ at " + host + ":" + port);
try {
client.close();
} catch (IOException e) {
Collectd.logError("Error closing HttpClient: " + e.getMessage());
}
return 0;
}
public synchronized int config(OConfigItem config) {
super.config(config);
for (OConfigItem child : config.getChildren()) {
switch (child.getKey()) {
case "host":
case "hostname":
case "Host":
case "HostName":
String hostString = child.getValues().get(0).getString();
String[] hosts = hostString.split(",");
if (hosts.length == 1) {
host = hosts[0];
} else {
host = hosts[new Random().nextInt(hosts.length)];
}
break;
case "Port":
case "port":
port = Integer.parseInt(child.getValues().get(0).getString());
break;
case "topic":
case "Topic":
topic = child.getValues().get(0).getString();
break;
default:
}
}
this.endpoint = "http://" + host + ":" + port + "/mpub?topic=" + topic;
if (null != host) {
return 0;
}
if (host == null) {
Collectd.logError("NSQ host must be configured");
}
if (port == 0) {
Collectd.logError("NSQ port must be configured");
}
return -1;
}
}
-14
View File
@@ -1,14 +0,0 @@
# CollectD Plugin
The CollectD plugin formats metrics acquired by the other plugins and sends them to Timely. The plugin contains code for parsing and extracting information from metrics reported by some of the plugins. One special case is the metrics received by the [StatsD] (https://github.com/etsy/statsd) plugin. It supports processing of [NSQ] (http://nsq.io/) metrics by looking for StatsD metrics with the prefix 'nsq'.
It also supports parsing of the StatsD metrics coming from the Hadoop Metrics2 framework (a metric with 4 components separated by periods). In this case you will want to set the skip hostname property to true and send the metrics from Hadoop to a local CollectD instance that will add the hostname and other tags.
The plugin allows for other unspecified formats to be sent as-is, except in the following case: if the StatsD metric contains a period, everything up to the period will be used in an instance tag.
For example:
"foo:1|c" will end up as the metric 'statsd.foo'
"bar.foo:1|c" will end up as the metric 'statsd.foo' with an 'instance=bar' tag.
-125
View File
@@ -1,125 +0,0 @@
<?xml version="1.0"?>
<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>0.0.1</version>
</parent>
<artifactId>collectd-plugin-parent</artifactId>
<dependencies>
<dependency>
<groupId>collectd</groupId>
<artifactId>collectd-api</artifactId>
<version>5.5.1</version>
<scope>system</scope>
<systemPath>${collectd-api-jar}</systemPath>
</dependency>
<dependency>
<groupId>net.jcip</groupId>
<artifactId>jcip-annotations</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-easymock-release-full</artifactId>
<version>1.6.4</version>
<classifier>full</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<effort>Max</effort>
<threshold>Medium</threshold>
<xmlOutput>true</xmlOutput>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<excludedGroups>timely.test.IntegrationTest</excludedGroups>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>0.5.2</version>
<configuration>
<configFile>${project.parent.basedir}/eclipse/Eclipse-Formatter.xml</configFile>
<lineEnding>LF</lineEnding>
<overrideConfigCompilerVersion>true</overrideConfigCompilerVersion>
</configuration>
<executions>
<execution>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<goals>
<goal>format</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
@@ -1,246 +0,0 @@
package timely.collectd.plugin;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.collectd.api.Collectd;
import org.collectd.api.DataSource;
import org.collectd.api.OConfigItem;
import org.collectd.api.ValueList;
public abstract class CollectDPluginParent {
private static final String PUT = "put {0} {1} {2}{3}\n";
private static final Pattern HADOOP_STATSD_PATTERN = Pattern
.compile("([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)");
private static final String INSTANCE = " instance=";
private static final String SAMPLE = " sample=";
private static final String CODE = " code=";
private static final String PERIOD = ".";
private static final String SAMPLE_TYPE = " sampleType=";
private static final String COUNTER = "COUNTER";
private static final String GAUGE = "GAUGE";
private static final String DERIVE = "DERIVE";
private static final String ABSOLUTE = "ABSOLUTE";
private static final Pattern NSQ_PATTERN1 = Pattern.compile("([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)");
private static final Pattern NSQ_PATTERN2 = Pattern.compile("([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)");
private static final Pattern NSQ_PATTERN3 = Pattern
.compile("([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_#]+)\\.([\\w-_]+)");
private static final Pattern ETHSTAT_PATTERN = Pattern.compile("([\\w-_]+)_queue_([\\w-_]+)_([\\w-_]+)");
private static final String STATSD_PREFIX = "statsd";
private static final String NSQ_PREFIX = STATSD_PREFIX + ".nsq.";
private static final Pattern HAPROXY_PATTERN = Pattern.compile("([\\w-_]+)\\.([\\w-_]+)\\.([\\w-_]+)");
private final SMARTCodeMapping smart = new SMARTCodeMapping();
protected Set<String> addlTags = new HashSet<>();
public int config(OConfigItem config) {
for (OConfigItem child : config.getChildren()) {
switch (child.getKey()) {
case "Tags":
case "tags":
String[] additionalTags = child.getValues().get(0).getString().split(",");
for (String t : additionalTags) {
addlTags.add(" " + t.trim());
}
default:
}
}
return 0;
}
public abstract void write(String metric);
public void process(ValueList vl) {
debugValueList(vl);
StringBuilder metric = new StringBuilder();
Long timestamp = vl.getTime();
StringBuilder tags = new StringBuilder();
String host = vl.getHost();
int idx = host.indexOf(PERIOD);
if (-1 != idx) {
tags.append(" host=").append(vl.getHost().substring(0, idx));
}
int nIdx = host.indexOf('n');
if (-1 != nIdx) {
tags.append(" rack=").append(host.substring(0, nIdx));
}
for (String tag : addlTags) {
tags.append(tag);
}
if (vl.getPlugin().equals(STATSD_PREFIX)) {
Matcher m = HADOOP_STATSD_PATTERN.matcher(vl.getTypeInstance());
Matcher n1 = NSQ_PATTERN1.matcher(vl.getTypeInstance());
Matcher n2 = NSQ_PATTERN2.matcher(vl.getTypeInstance());
Matcher n3 = NSQ_PATTERN3.matcher(vl.getTypeInstance());
String instance = null;
if (m.matches() && !vl.getTypeInstance().startsWith("nsq")) {
// Here we are processing the statsd metrics coming from the
// Hadoop Metrics2 StatsDSink without the host name.
// The format of metric is:
// serviceName.contextName.recordName.metricName. The recordName
// is typically duplicative and is dropped here. The serviceName
// is used as the instance.
metric.append(STATSD_PREFIX).append(PERIOD).append(m.group(2)).append(PERIOD).append(m.group(4));
instance = m.group(1);
} else if (n1.matches() && vl.getTypeInstance().startsWith("nsq")) {
metric.append(NSQ_PREFIX).append(n1.group(2)).append(PERIOD).append(n1.group(3));
} else if (n2.matches() && vl.getTypeInstance().startsWith("nsq")) {
metric.append(NSQ_PREFIX).append(n2.group(2)).append(PERIOD).append(n2.group(4));
instance = n2.group(3);
} else if (n3.matches() && vl.getTypeInstance().startsWith("nsq")) {
metric.append(NSQ_PREFIX).append(n3.group(4)).append(PERIOD).append(n3.group(6));
instance = n3.group(5);
} else {
// Handle StatsD metrics of unknown formats. If there is a
// period in the metric name, use everything up to that as
// the instance.
int period = vl.getTypeInstance().indexOf('.');
if (-1 == period) {
metric.append(STATSD_PREFIX).append(PERIOD).append(vl.getTypeInstance());
} else {
instance = vl.getTypeInstance().substring(0, period);
metric.append(STATSD_PREFIX).append(PERIOD).append(vl.getTypeInstance().substring(period + 1));
}
}
timestamp = vl.getTime();
if (null != instance) {
tags.append(INSTANCE).append(instance);
}
} else if (vl.getPlugin().equals("ethstat")) {
metric.append("sys.ethstat.");
if (vl.getTypeInstance().contains("queue")) {
Matcher m = ETHSTAT_PATTERN.matcher(vl.getTypeInstance());
if (m.matches()) {
metric.append(m.group(1)).append("_").append(m.group(3));
tags.append(" queue=").append(m.group(2));
} else {
metric.append(vl.getTypeInstance());
}
} else {
metric.append(vl.getTypeInstance());
}
tags.append(INSTANCE).append(vl.getPluginInstance());
} else if (vl.getPlugin().equals("hddtemp")) {
metric.append("sys.hddtemp.").append(vl.getType());
tags.append(INSTANCE).append(vl.getTypeInstance());
} else if (vl.getPlugin().equals("smart")) {
int code = -1;
String name = null;
if (vl.getTypeInstance().startsWith("attribute-")) {
int hyphen = vl.getTypeInstance().indexOf('-');
code = Integer.parseInt(vl.getTypeInstance().substring(hyphen + 1));
name = smart.get(code);
}
if (code == -1) {
if (notEmpty(vl.getTypeInstance())) {
metric.append("sys.smart.").append(vl.getTypeInstance());
} else {
metric.append("sys.smart.").append(vl.getType());
}
} else {
metric.append("sys.smart.").append(name);
tags.append(CODE).append(code);
}
tags.append(INSTANCE).append(vl.getPluginInstance());
} else if (vl.getPlugin().equals("sensors")) {
String instance = "";
if (vl.getTypeInstance().startsWith("temp")) {
instance = vl.getTypeInstance().substring(4);
}
metric.append("sys.sensors.").append(vl.getType()).append(PERIOD).append(vl.getPluginInstance());
tags.append(INSTANCE).append(instance);
} else if (vl.getPlugin().equals("haproxy")) {
Matcher m = HAPROXY_PATTERN.matcher(vl.getTypeInstance());
if (m.matches()) {
metric.append("sys.haproxy.").append(m.group(3));
tags.append(" proxy=").append(m.group(2));
tags.append(" server=").append(m.group(1));
} else {
metric.append("sys.haproxy.").append(vl.getTypeInstance());
}
} else if (vl.getPlugin().equals("ipmi")) {
metric.append("sys.ipmi.").append(vl.getType());
tags.append(INSTANCE).append(vl.getTypeInstance().replaceAll(" ", "_"));
} else if (notEmpty(vl.getTypeInstance()) && notEmpty(vl.getType()) && notEmpty(vl.getPluginInstance())
&& notEmpty(vl.getPlugin())) {
metric.append("sys.").append(vl.getPlugin()).append(PERIOD).append(vl.getType()).append(PERIOD)
.append(vl.getTypeInstance());
tags.append(INSTANCE).append(vl.getPluginInstance());
} else if (notEmpty(vl.getTypeInstance()) && notEmpty(vl.getType()) && notEmpty(vl.getPlugin())) {
metric.append("sys.").append(vl.getPlugin()).append(PERIOD).append(vl.getType()).append(PERIOD)
.append(vl.getTypeInstance());
} else if (notEmpty(vl.getType()) && notEmpty(vl.getPluginInstance()) && notEmpty(vl.getPlugin())) {
metric.append("sys.").append(vl.getPlugin()).append(PERIOD).append(vl.getType());
tags.append(INSTANCE).append(vl.getPluginInstance());
} else if (notEmpty(vl.getType()) && notEmpty(vl.getPlugin())) {
metric.append("sys.").append(vl.getPlugin()).append(PERIOD).append(vl.getType());
} else {
Collectd.logWarning("Unhandled metric: " + vl.toString());
return;
}
final String metricName = metric.toString().replaceAll(" ", "_");
for (int i = 0; i < vl.getValues().size(); i++) {
StringBuilder tagsWithSample = new StringBuilder(tags.toString());
String sampleName = vl.getDataSet().getDataSources().get(i).getName();
int type = vl.getDataSet().getDataSources().get(i).getType();
String sampleType = convertType(type);
if (null != sampleName) {
tagsWithSample.append(SAMPLE).append(sampleName);
}
if (null != sampleType) {
tagsWithSample.append(SAMPLE_TYPE).append(sampleType);
}
Double value = vl.getValues().get(i).doubleValue();
String datapoint = MessageFormat.format(PUT, metricName, timestamp.toString(), value.toString(),
tagsWithSample.toString());
Collectd.logDebug("Writing: " + datapoint);
write(datapoint);
}
}
private void debugValueList(ValueList vl) {
Collectd.logDebug("Input: " + vl.toString());
Collectd.logDebug("Plugin: " + vl.getPlugin());
Collectd.logDebug("PluginInstance: " + vl.getPluginInstance());
Collectd.logDebug("Type: " + vl.getType());
Collectd.logDebug("TypeInstance: " + vl.getTypeInstance());
for (int i = 0; i < vl.getValues().size(); i++) {
Number value = vl.getValues().get(i);
DataSource ds = vl.getDataSet().getDataSources().get(i);
Collectd.logDebug(convertType(ds.getType()) + " " + ds.getName() + " = " + value);
}
}
private String convertType(int type) {
String result = null;
switch (type) {
case 0:
result = COUNTER;
break;
case 1:
result = GAUGE;
break;
case 2:
result = DERIVE;
break;
case 3:
result = ABSOLUTE;
break;
default:
result = GAUGE;
break;
}
return result;
}
private boolean notEmpty(String arg) {
return (null != arg) && !(arg.equals(""));
}
}
@@ -1,169 +0,0 @@
package timely.collectd.plugin;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class SMARTCodeMapping extends HashMap<Integer, String> {
private static final long serialVersionUID = 1L;
private static final Map<Integer, String> smart;
static {
Map<Integer, String> map = new HashMap<>();
/**
* mapping from https://en.wikipedia.org/wiki/S.M.A.R.T.
*/
map.put(1, "Read_Error_Rate");
map.put(2, "Throughput_Performance");
map.put(3, "Spin_Up_Time");
map.put(4, "Start_Stop_Count");
map.put(5, "Reallocated_Sectors_Count");
map.put(6, "Read_Channel_Margin");
map.put(7, "Seek_Error_Rate");
map.put(8, "Seek_Time_Performance");
map.put(9, "Power_On_Hours");
map.put(10, "Spin_Retry_Count");
map.put(11, "Calibration_Retry_Count");
map.put(12, "Power_Cycle_Count");
map.put(13, "Soft_Read_Error_Rate");
map.put(22, "Current_Helium_Level");
map.put(170, "Available_Reserved_Space");
map.put(171, "SSD_Program_Fail_Count");
map.put(172, "SSD_Erase_Fail_Count");
map.put(173, "SSD_Wear_Leveling_Count");
map.put(174, "Unexpected_power_loss_count");
map.put(175, "Power_Loss_Protection_Failure");
map.put(176, "Erase_Fail_Count");
map.put(177, "Wear_Range_Delta");
map.put(179, "Used_Reserved_Block_Count_Total");
map.put(180, "Unused_Reserved_Block_Count_Total");
map.put(181, "Program_Fail_Count_Total");
map.put(182, "Erase_Fail_Count");
map.put(183, "SATA_Downshift_Error_Count");
map.put(184, "End_to_End_error");
map.put(185, "Head_Stability");
map.put(186, "Induced_Op_Vibration_Detection");
map.put(187, "Reported_Uncorrectable_Errors");
map.put(188, "Command_Timeout");
map.put(189, "High_Fly_Writes");
map.put(190, "Airflow_Temperature_Celsius");
map.put(190, "Temperature_Difference_from_100");
map.put(191, "G_sense_Error_Rate");
map.put(192, "Unsafe_Shutdown_Count");
map.put(193, "Load_Unload_Cycle_Count");
map.put(194, "Temperature_Celsius");
map.put(195, "Hardware_ECC_Recovered");
map.put(196, "Reallocation_Event_Count");
map.put(197, "Current_Pending_Sector_Count");
map.put(198, "Uncorrectable_Sector_Count");
map.put(199, "UltraDMA_CRC_Error_Count");
// map.put(200,"Multi_Zone_Error_Rate_[41]");
map.put(200, "Write_Error_Rate");
map.put(201, "Soft_Read_Error_Rate");
map.put(202, "Data_Address_Mark_errors");
map.put(203, "Run_Out_Cancel");
map.put(204, "Soft_ECC_Correction");
map.put(205, "Thermal_Asperity_Rate");
map.put(206, "Flying_Height");
map.put(207, "Spin_High_Current");
map.put(208, "Spin_Buzz");
map.put(209, "Offline_Seek_Performance");
map.put(210, "Vibration_During_Write");
map.put(211, "Vibration_During_Write");
map.put(212, "Shock_During_Write");
map.put(220, "Disk_Shift");
map.put(221, "G_Sense_Error_Rate");
map.put(222, "Loaded_Hours");
map.put(223, "Load_Unload_Retry_Count");
map.put(224, "Load_Friction");
map.put(225, "Load_Unload_Cycle_Count");
map.put(226, "Load_In_time");
map.put(227, "Torque_Amplification_Count");
map.put(228, "Power_Off_Retract_Cycle");
// map.put(230,"GMR_Head_Amplitude");
map.put(230, "Drive_Life_Protection_Status");
map.put(231, "Temperature");
// map.put(231,"SSD_Life_Left");
map.put(232, "Endurance_Remaining");
// map.put(232,"Available_Reserved_Space");
map.put(233, "Power_On_Hours");
// map.put(233,"Media_Wearout_Indicator");
map.put(234, "Average_erase_count");
map.put(235, "Good_Block_Count");
map.put(240, "Head_Flying_Hours");
// map.put(240,"Transfer_Error_Rate_(Fujitsu)");
map.put(241, "Total_LBAs_Written");
map.put(242, "Total_LBAs_Read");
map.put(243, "Total_LBAs_Written_Expanded");
map.put(244, "Total_LBAs_Read_Expanded");
map.put(249, "NAND_Writes_1GiB");
map.put(250, "Read_Error_Retry_Rate");
map.put(251, "Minimum_Spares_Remaining");
map.put(252, "Newly_Added_Bad_Flash_Block");
map.put(254, "Free_Fall_Protection");
smart = Collections.unmodifiableMap(map);
}
protected SMARTCodeMapping() {
}
public int size() {
return smart.size();
}
public boolean isEmpty() {
return smart.isEmpty();
}
public boolean containsKey(Object key) {
return smart.containsKey(key);
}
public boolean containsValue(Object value) {
return smart.containsValue(value);
}
public String get(Object key) {
return smart.get(key);
}
public String put(Integer key, String value) {
return smart.put(key, value);
}
public String remove(Object key) {
return smart.remove(key);
}
public void putAll(Map<? extends Integer, ? extends String> m) {
smart.putAll(m);
}
public void clear() {
smart.clear();
}
public Set<Integer> keySet() {
return smart.keySet();
}
public Collection<String> values() {
return smart.values();
}
public Set<java.util.Map.Entry<Integer, String>> entrySet() {
return smart.entrySet();
}
public boolean equals(Object o) {
return smart.equals(o);
}
public int hashCode() {
return smart.hashCode();
}
}
@@ -1,314 +0,0 @@
package timely.collectd.plugin;
import static org.junit.Assert.*;
import java.util.Collections;
import net.jcip.annotations.NotThreadSafe;
import org.collectd.api.Collectd;
import org.collectd.api.DataSet;
import org.collectd.api.DataSource;
import org.collectd.api.ValueList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Collectd.class })
@NotThreadSafe
public class PluginParentTest {
public class TestPlugin extends CollectDPluginParent {
public TestPlugin() {
super.addlTags.add(" addl1=foo");
}
@Override
public void write(String metric) {
result = metric;
}
}
private static final String HOST = "r01n01.test";
private static final Long TIME = 1456156976840L;
public String result = null;
@Before
public void setup() throws Exception {
PowerMock.suppress(PowerMock.everythingDeclaredIn(Collectd.class));
}
@Test
public void testStatsDHadoopFormat() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("statsd");
vl.setTime(TIME);
vl.setType("derive");
vl.setTypeInstance("DataNode.dfs.datanode.BlocksRead");
vl.setValues(Collections.singletonList((Number) 1.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put statsd.dfs.BlocksRead 1456156976840 1.0 host=r01n01 rack=r01 addl1=foo instance=DataNode sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testStatsDUnknownFormat() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("statsd");
vl.setTime(TIME);
vl.setType("derive");
vl.setTypeInstance("baz");
vl.setValues(Collections.singletonList((Number) 1.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals("put statsd.baz 1456156976840 1.0 host=r01n01 rack=r01 addl1=foo sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testStatsDUnknownFormat2() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("statsd");
vl.setTime(TIME);
vl.setType("derive");
vl.setTypeInstance("bar.baz");
vl.setValues(Collections.singletonList((Number) 1.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put statsd.baz 1456156976840 1.0 host=r01n01 rack=r01 addl1=foo instance=bar sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testHddTemp() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("hddtemp");
vl.setTime(TIME);
vl.setType("temperature");
vl.setTypeInstance("sda");
vl.setValues(Collections.singletonList((Number) 35.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.hddtemp.temperature 1456156976840 35.0 host=r01n01 rack=r01 addl1=foo instance=sda sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testSmart1() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("smart");
vl.setPluginInstance("sda");
vl.setTime(TIME);
vl.setType("smart_badsectors");
vl.setValues(Collections.singletonList((Number) 0.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.smart.smart_badsectors 1456156976840 0.0 host=r01n01 rack=r01 addl1=foo instance=sda sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testSmart2() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("smart");
vl.setPluginInstance("sda");
vl.setTime(TIME);
vl.setType("smart_attribute");
vl.setTypeInstance("raw-read-error-rate");
vl.setValues(Collections.singletonList((Number) 0.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.smart.raw-read-error-rate 1456156976840 0.0 host=r01n01 rack=r01 addl1=foo instance=sda sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testSmartCode() throws Exception {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("smart");
vl.setPluginInstance("sda");
vl.setTime(TIME);
vl.setType("smart_attribute");
vl.setTypeInstance("attribute-242");
vl.setValues(Collections.singletonList((Number) 0.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.smart.Total_LBAs_Read 1456156976840 0.0 host=r01n01 rack=r01 addl1=foo code=242 instance=sda sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testSensors() {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("sensors");
vl.setPluginInstance("coretemp-isa-0000");
vl.setTime(TIME);
vl.setType("temperature");
vl.setTypeInstance("temp1");
vl.setValues(Collections.singletonList((Number) 35.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.sensors.temperature.coretemp-isa-0000 1456156976840 35.0 host=r01n01 rack=r01 addl1=foo instance=1 sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testHAProxy1() {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("haproxy");
vl.setTime(TIME);
vl.setType("gauge");
vl.setTypeInstance("run_queue");
vl.setValues(Collections.singletonList((Number) 0.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.haproxy.run_queue 1456156976840 0.0 host=r01n01 rack=r01 addl1=foo sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testHAProxy2() {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("haproxy");
vl.setTime(TIME);
vl.setType("gauge");
vl.setTypeInstance("server1.proxy1.queue_current");
vl.setValues(Collections.singletonList((Number) 0.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 0.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.haproxy.queue_current 1456156976840 0.0 host=r01n01 rack=r01 addl1=foo proxy=proxy1 server=server1 sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testEthStatNoQueue() {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("ethstat");
vl.setPluginInstance("eth0");
vl.setTime(TIME);
vl.setType("derive");
vl.setTypeInstance("tx_comp_queue_full");
vl.setValues(Collections.singletonList((Number) 6.0D));
DataSet ds = new DataSet("DERIVE");
ds.addDataSource(new DataSource("value", 1, 6.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.ethstat.tx_comp_queue_full 1456156976840 6.0 host=r01n01 rack=r01 addl1=foo instance=eth0 sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testEthStatWithQueue() {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("ethstat");
vl.setPluginInstance("eth0");
vl.setTime(TIME);
vl.setType("derive");
vl.setTypeInstance("rx_queue_1_bytes");
vl.setValues(Collections.singletonList((Number) 6.0D));
DataSet ds = new DataSet("DERIVE");
ds.addDataSource(new DataSource("value", 1, 6.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.ethstat.rx_bytes 1456156976840 6.0 host=r01n01 rack=r01 addl1=foo queue=1 instance=eth0 sample=value sampleType=GAUGE\n",
result);
}
@Test
public void testIpmi() {
ValueList vl = new ValueList();
vl.setHost(HOST);
vl.setPlugin("ipmi");
vl.setTime(TIME);
vl.setType("temperature");
vl.setTypeInstance("LAN NIC Temp system_board (3.2)");
vl.setValues(Collections.singletonList((Number) 6.0D));
DataSet ds = new DataSet("GAUGE");
ds.addDataSource(new DataSource("value", 1, 6.0D, 100.0D));
vl.setDataSet(ds);
TestPlugin test = new TestPlugin();
test.process(vl);
assertEquals(
"put sys.ipmi.temperature 1456156976840 6.0 host=r01n01 rack=r01 addl1=foo instance=LAN_NIC_Temp_system_board_(3.2) sample=value sampleType=GAUGE\n",
result);
}
}
-19
View File
@@ -1,19 +0,0 @@
# Write Timely plugin for CollectD
To build, run ```mvn clean package```. Place the resulting jar file somewhere on the local filesystem and add the following to the
collectd configuration file
```
LoadPlugin java
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:<path_to_jar>/collectd-timely-plugin-0.0.1-SNAPSHOT.jar"
JVMArg "-Xms1024m"
JVMArg "-Xmx1024m"
LoadPlugin "timely.collectd.plugin.WriteTimelyPlugin"
<Plugin "timely.collectd.plugin.WriteTimelyPlugin">
Host "<TimelyHost>"
Port "<TimelyPutPort>"
</Plugin>
</Plugin>
```
-157
View File
@@ -1,157 +0,0 @@
<?xml version="1.0"?>
<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>0.0.1</version>
</parent>
<artifactId>collectd-timely-plugin</artifactId>
<dependencies>
<dependency>
<groupId>collectd</groupId>
<artifactId>collectd-api</artifactId>
<version>5.5.1</version>
<scope>system</scope>
<systemPath>${collectd-api-jar}</systemPath>
</dependency>
<dependency>
<groupId>net.jcip</groupId>
<artifactId>jcip-annotations</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-easymock-release-full</artifactId>
<version>1.6.4</version>
<classifier>full</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>collectd-plugin-parent</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<effort>Max</effort>
<threshold>Medium</threshold>
<xmlOutput>true</xmlOutput>
<excludeFilterFile>src/main/resources/findbugs-excludes.xml</excludeFilterFile>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<artifactSet>
<includes>
</includes>
</artifactSet>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<id>shade</id>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<excludedGroups>timely.test.IntegrationTest</excludedGroups>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>0.5.2</version>
<configuration>
<configFile>${project.parent.basedir}/eclipse/Eclipse-Formatter.xml</configFile>
<lineEnding>LF</lineEnding>
<overrideConfigCompilerVersion>true</overrideConfigCompilerVersion>
</configuration>
<executions>
<execution>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<goals>
<goal>format</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
@@ -1,129 +0,0 @@
package timely.collectd.plugin;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import org.collectd.api.Collectd;
import org.collectd.api.CollectdConfigInterface;
import org.collectd.api.CollectdShutdownInterface;
import org.collectd.api.CollectdWriteInterface;
import org.collectd.api.OConfigItem;
import org.collectd.api.ValueList;
/**
*
* CollectD plugin that writes metrics collected by the StatsD plugin to Timely.
*
*/
public class WriteTimelyPlugin extends CollectDPluginParent implements CollectdConfigInterface,
CollectdShutdownInterface, CollectdWriteInterface {
private String host = null;
private int port = 0;
private Socket sock = null;
private PrintWriter out = null;
private long connectTime = 0L;
private long backoff = 2000;
public WriteTimelyPlugin() {
Collectd.registerConfig(WriteTimelyPlugin.class.getName(), this);
Collectd.registerShutdown(WriteTimelyPlugin.class.getName(), this);
Collectd.registerWrite(WriteTimelyPlugin.class.getName(), this);
}
private synchronized int connect() {
if (null == sock || !sock.isConnected() || out.checkError()) {
if (System.currentTimeMillis() > (connectTime + backoff)) {
try {
connectTime = System.currentTimeMillis();
sock = new Socket(host, port);
out = new PrintWriter(sock.getOutputStream(), false);
backoff = 2000;
Collectd.logInfo("Connected to Timely at " + host + ":" + port);
} catch (IOException e) {
Collectd.logError("Error connecting to Timely at " + host + ":" + port + ". Error: "
+ e.getMessage());
backoff = backoff * 2;
sock = null;
out = null;
Collectd.logWarning("Will retry connection in " + backoff + " ms.");
return -1;
}
} else {
Collectd.logWarning("Not writing to Timely, waiting to reconnect");
return 0;
}
}
return 0;
}
public synchronized int write(ValueList vl) {
int c = connect();
if (c != 0) {
return c;
}
super.process(vl);
return 0;
}
@Override
public synchronized void write(String metric) {
out.write(metric);
}
public synchronized void flush() {
if (null != out) {
out.flush();
}
}
public synchronized int shutdown() {
Collectd.logInfo("Shutting down connection to Timely at " + host + ":" + port);
if (null != sock) {
try {
if (null != out) {
out.close();
}
sock.close();
} catch (IOException e) {
Collectd.logError("Error closing connection to Timely at " + host + ":" + port + ". Error: "
+ e.getMessage());
return -1;
}
}
return 0;
}
public synchronized int config(OConfigItem config) {
super.config(config);
for (OConfigItem child : config.getChildren()) {
switch (child.getKey()) {
case "host":
case "hostname":
case "Host":
case "HostName":
host = child.getValues().get(0).getString();
break;
case "Port":
case "port":
port = Integer.parseInt(child.getValues().get(0).getString());
break;
default:
}
}
if (null != host) {
return 0;
}
if (host == null) {
Collectd.logError("Timely host must be configured");
}
if (port == 0) {
Collectd.logError("Timely port must be configured");
}
return -1;
}
}
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Or>
<Bug pattern="DM_DEFAULT_ENCODING"/>
<Method name="connect" />
<Class name="timely.collectd.plugin.WriteTimelyPlugin"/>
</Or>
</FindBugsFilter>
@@ -1,202 +0,0 @@
package timely.collectd.plugin;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import net.jcip.annotations.NotThreadSafe;
import org.collectd.api.Collectd;
import org.collectd.api.DataSet;
import org.collectd.api.DataSource;
import org.collectd.api.OConfigItem;
import org.collectd.api.ValueList;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Collectd.class })
@NotThreadSafe
public class WriteTimelyPluginTest {
private WriteTimelyPlugin plugin = null;
private FakeServer server = null;
@Before
public void setup() throws Exception {
PowerMock.suppress(PowerMock.everythingDeclaredIn(Collectd.class));
server = new FakeServer();
}
private ValueList createMetric() {
ValueList val = new ValueList();
val.setHost("r001n01.localdomain");
val.setTime(System.currentTimeMillis());
val.setPlugin("hddtemp");
val.setPluginInstance(null);
val.setType("temperature");
val.setTypeInstance("sda");
val.setValues(Collections.singletonList((Number) new Double(2.0)));
DataSet ds = new DataSet("test");
ds.addDataSource(new DataSource("test", DataSource.TYPE_COUNTER, 0, 100));
val.setDataSet(ds);
return val;
}
private void setupPlugin() throws Exception {
OConfigItem host = new OConfigItem("Host");
host.addValue(server.getHost());
OConfigItem port = new OConfigItem("Port");
port.addValue(Integer.toString(server.getPort()));
OConfigItem config = new OConfigItem("");
config.addChild(host);
config.addChild(port);
plugin = new WriteTimelyPlugin();
Assert.assertEquals(0, plugin.config(config));
// Seed with one metric to create the connection
Assert.assertEquals(0, plugin.write(createMetric()));
plugin.flush();
Thread.sleep(100);
Assert.assertTrue(server.messageReceived());
}
@Test
public void testWrite() throws Exception {
Thread t = new Thread(server);
t.start();
setupPlugin();
while (!server.ready()) {
Thread.sleep(1000);
}
Assert.assertEquals(0, plugin.write(createMetric()));
plugin.flush();
Thread.sleep(100);
Assert.assertTrue(server.messageReceived());
plugin.shutdown();
server.shutdown();
t.join();
}
@Test
public void testWriteAfterServerRestart() throws Exception {
Thread t = new Thread(server);
t.start();
setupPlugin();
while (!server.ready()) {
Thread.sleep(1000);
}
Assert.assertEquals(0, plugin.write(createMetric()));
plugin.flush();
Thread.sleep(100);
Assert.assertTrue(server.messageReceived());
server.shutdown();
t.join();
Thread.sleep(2000);
server.create();
Thread t2 = new Thread(server);
t2.start();
while (!server.ready()) {
Thread.sleep(1000);
// Keep sending metrics to plugin to force reconnect
int result = plugin.write(createMetric());
System.out.println("Wrote to client, result: " + result);
Assert.assertEquals(0, result);
plugin.flush();
}
Assert.assertEquals(0, plugin.write(createMetric()));
plugin.flush();
Thread.sleep(1000);
Assert.assertTrue(server.messageReceived());
plugin.shutdown();
server.shutdown();
t2.join();
}
private static class FakeServer implements Runnable {
private ServerSocket server = null;
private BufferedReader reader = null;
private String host = null;
private int port = 0;
private Socket s = null;
private boolean received = false;
public FakeServer() throws Exception {
create();
}
public void create() throws Exception {
server = new ServerSocket(port);
server.setReceiveBufferSize(8192);
host = server.getInetAddress().getHostAddress();
port = server.getLocalPort();
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public void shutdown() throws Exception {
System.out.println("Closing server.");
s.close();
s = null;
reader.close();
reader = null;
server.close();
server = null;
}
public boolean ready() throws Exception {
return (s != null && s.isConnected());
}
public boolean messageReceived() {
try {
return Boolean.valueOf(this.received);
} finally {
this.received = false;
}
}
@Override
public void run() {
try {
s = server.accept();
System.out.println("Client connected.");
reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
while (server != null && !server.isClosed() && s != null && s.isConnected()) {
String line;
try {
line = reader.readLine();
} catch (Exception e) {
// Might have been closed
System.out.println("Closed by Exception: " + e.getMessage());
return;
}
if (null != line) {
this.received = true;
}
System.out.println(line);
}
} catch (IOException e) {
Assert.fail(e.getMessage());
}
System.out.println("Exiting run method.");
}
}
}
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<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="TRACE">
<AppenderRef ref="CONSOLE"/>
</Root>
</Loggers>
</Configuration>
+46
View File
@@ -0,0 +1,46 @@
# Markdown
set :markdown_engine, :redcarpet
set :markdown,
fenced_code_blocks: true,
smartypants: true,
disable_indented_code_blocks: true,
prettify: true,
tables: true,
with_toc_data: true,
no_intra_emphasis: true
# Assets
set :css_dir, 'stylesheets'
set :js_dir, 'javascripts'
set :images_dir, 'images'
set :fonts_dir, 'fonts'
# Activate the syntax highlighter
activate :syntax
activate :sprockets
activate :autoprefixer do |config|
config.browsers = ['last 2 version', 'Firefox ESR']
config.cascade = false
config.inline = true
end
# Github pages require relative links
activate :relative_assets
set :relative_links, true
# Build Configuration
configure :build do
# If you're having trouble with Middleman hanging, commenting
# out the following two lines has been known to help
activate :minify_css
activate :minify_javascript
# activate :relative_assets
# activate :asset_hash
# activate :gzip
end
# Deploy Configuration
# If you want Middleman to listen on a different port, you can set that below
set :port, 4567
Executable
+203
View File
@@ -0,0 +1,203 @@
#!/usr/bin/env bash
set -o errexit #abort if any command fails
me=$(basename "$0")
help_message="\
Usage: $me [-c FILE] [<options>]
Deploy generated files to a git branch.
Options:
-h, --help Show this help information.
-v, --verbose Increase verbosity. Useful for debugging.
-e, --allow-empty Allow deployment of an empty directory.
-m, --message MESSAGE Specify the message used when committing on the
deploy branch.
-n, --no-hash Don't append the source commit's hash to the deploy
commit's message.
"
bundle exec middleman build --clean
parse_args() {
# Set args from a local environment file.
if [ -e ".env" ]; then
source .env
fi
# Parse arg flags
# If something is exposed as an environment variable, set/overwrite it
# here. Otherwise, set/overwrite the internal variable instead.
while : ; do
if [[ $1 = "-h" || $1 = "--help" ]]; then
echo "$help_message"
return 0
elif [[ $1 = "-v" || $1 = "--verbose" ]]; then
verbose=true
shift
elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then
allow_empty=true
shift
elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then
commit_message=$2
shift 2
elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then
GIT_DEPLOY_APPEND_HASH=false
shift
else
break
fi
done
# Set internal option vars from the environment and arg flags. All internal
# vars should be declared here, with sane defaults if applicable.
# Source directory & target branch.
deploy_directory=build
deploy_branch=gh-pages
#if no user identity is already set in the current git environment, use this:
default_username=${GIT_DEPLOY_USERNAME:-deploy.sh}
default_email=${GIT_DEPLOY_EMAIL:-}
#repository to deploy to. must be readable and writable.
repo=origin
#append commit hash to the end of message by default
append_hash=${GIT_DEPLOY_APPEND_HASH:-true}
}
main() {
parse_args "$@"
enable_expanded_output
if ! git diff --exit-code --quiet --cached; then
echo Aborting due to uncommitted changes in the index >&2
return 1
fi
commit_title=`git log -n 1 --format="%s" HEAD`
commit_hash=` git log -n 1 --format="%H" HEAD`
#default commit message uses last title if a custom one is not supplied
if [[ -z $commit_message ]]; then
commit_message="publish: $commit_title"
fi
#append hash to commit message unless no hash flag was found
if [ $append_hash = true ]; then
commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash"
fi
previous_branch=`git rev-parse --abbrev-ref HEAD`
if [ ! -d "$deploy_directory" ]; then
echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2
return 1
fi
# must use short form of flag in ls for compatibility with OS X and BSD
if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then
echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2
return 1
fi
if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then
# deploy_branch exists in $repo; make sure we have the latest version
disable_expanded_output
git fetch --force $repo $deploy_branch:$deploy_branch
enable_expanded_output
fi
# check if deploy_branch exists locally
if git show-ref --verify --quiet "refs/heads/$deploy_branch"
then incremental_deploy
else initial_deploy
fi
restore_head
}
initial_deploy() {
git --work-tree "$deploy_directory" checkout --orphan $deploy_branch
git --work-tree "$deploy_directory" add --all
commit+push
}
incremental_deploy() {
#make deploy_branch the current branch
git symbolic-ref HEAD refs/heads/$deploy_branch
#put the previously committed contents of deploy_branch into the index
git --work-tree "$deploy_directory" reset --mixed --quiet
git --work-tree "$deploy_directory" add --all
set +o errexit
diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$?
set -o errexit
case $diff in
0) echo No changes to files in $deploy_directory. Skipping commit.;;
1) commit+push;;
*)
echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to master, use: git symbolic-ref HEAD refs/heads/master && git reset --mixed >&2
return $diff
;;
esac
}
commit+push() {
set_user_id
git --work-tree "$deploy_directory" commit -m "$commit_message"
disable_expanded_output
#--quiet is important here to avoid outputting the repo URL, which may contain a secret token
git push --quiet $repo $deploy_branch
enable_expanded_output
}
#echo expanded commands as they are executed (for debugging)
enable_expanded_output() {
if [ $verbose ]; then
set -o xtrace
set +o verbose
fi
}
#this is used to avoid outputting the repo URL, which may contain a secret token
disable_expanded_output() {
if [ $verbose ]; then
set +o xtrace
set -o verbose
fi
}
set_user_id() {
if [[ -z `git config user.name` ]]; then
git config user.name "$default_username"
fi
if [[ -z `git config user.email` ]]; then
git config user.email "$default_email"
fi
}
restore_head() {
if [[ $previous_branch = "HEAD" ]]; then
#we weren't on any branch before, so just set HEAD back to the commit it was on
git update-ref --no-deref HEAD $commit_hash $deploy_branch
else
git symbolic-ref HEAD refs/heads/$previous_branch
fi
git reset --mixed
}
filter() {
sed -e "s|$repo|\$repo|g"
}
sanitize() {
"$@" 2> >(filter 1>&2) | filter
}
[[ $1 = --source-only ]] || main "$@"
-295
View File
@@ -1,295 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Timely" version="12">
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
</profile>
</profiles>
+148
View File
@@ -0,0 +1,148 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M438.857 73.143q119.429 0 220.286 58.857t159.714 159.714 58.857 220.286-58.857 220.286-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857zM512 785.714v-108.571q0-8-5.143-13.429t-12.571-5.429h-109.714q-7.429 0-13.143 5.714t-5.714 13.143v108.571q0 7.429 5.714 13.143t13.143 5.714h109.714q7.429 0 12.571-5.429t5.143-13.429zM510.857 589.143l10.286-354.857q0-6.857-5.714-10.286-5.714-4.571-13.714-4.571h-125.714q-8 0-13.714 4.571-5.714 3.429-5.714 10.286l9.714 354.857q0 5.714 5.714 10t13.714 4.286h105.714q8 0 13.429-4.286t6-10z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"exclamation-circle"
],
"defaultCode": 61546,
"grid": 14
},
"attrs": [],
"properties": {
"id": 100,
"order": 4,
"prevSize": 28,
"code": 58880,
"name": "exclamation-sign",
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 0
},
{
"icon": {
"paths": [
"M585.143 786.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-54.857v-292.571q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h54.857v182.857h-54.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h256q8 0 13.143-5.143t5.143-13.143zM512 274.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-109.714q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h109.714q8 0 13.143-5.143t5.143-13.143zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"info-circle"
],
"defaultCode": 61530,
"grid": 14
},
"attrs": [],
"properties": {
"id": 85,
"order": 3,
"name": "info-sign",
"prevSize": 28,
"code": 58882
},
"setIdx": 0,
"iconIdx": 2
},
{
"icon": {
"paths": [
"M733.714 419.429q0-16-10.286-26.286l-52-51.429q-10.857-10.857-25.714-10.857t-25.714 10.857l-233.143 232.571-129.143-129.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-52 51.429q-10.286 10.286-10.286 26.286 0 15.429 10.286 25.714l206.857 206.857q10.857 10.857 25.714 10.857 15.429 0 26.286-10.857l310.286-310.286q10.286-10.286 10.286-25.714zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
],
"attrs": [],
"isMulticolor": false,
"tags": [
"check-circle"
],
"defaultCode": 61528,
"grid": 14
},
"attrs": [],
"properties": {
"id": 83,
"order": 9,
"prevSize": 28,
"code": 58886,
"name": "ok-sign"
},
"setIdx": 0,
"iconIdx": 6
},
{
"icon": {
"paths": [
"M658.286 475.429q0-105.714-75.143-180.857t-180.857-75.143-180.857 75.143-75.143 180.857 75.143 180.857 180.857 75.143 180.857-75.143 75.143-180.857zM950.857 950.857q0 29.714-21.714 51.429t-51.429 21.714q-30.857 0-51.429-21.714l-196-195.429q-102.286 70.857-228 70.857-81.714 0-156.286-31.714t-128.571-85.714-85.714-128.571-31.714-156.286 31.714-156.286 85.714-128.571 128.571-85.714 156.286-31.714 156.286 31.714 128.571 85.714 85.714 128.571 31.714 156.286q0 125.714-70.857 228l196 196q21.143 21.143 21.143 51.429z"
],
"width": 951,
"attrs": [],
"isMulticolor": false,
"tags": [
"search"
],
"defaultCode": 61442,
"grid": 14
},
"attrs": [],
"properties": {
"id": 2,
"order": 1,
"prevSize": 28,
"code": 58887,
"name": "icon-search"
},
"setIdx": 0,
"iconIdx": 7
}
],
"height": 1024,
"metadata": {
"name": "slate",
"license": "SIL OFL 1.1"
},
"preferences": {
"showGlyphs": true,
"showQuickUse": true,
"showQuickUse2": true,
"showSVGs": true,
"fontPref": {
"prefix": "icon-",
"metadata": {
"fontFamily": "slate",
"majorVersion": 1,
"minorVersion": 0,
"description": "Based on FontAwesome",
"license": "SIL OFL 1.1"
},
"metrics": {
"emSize": 1024,
"baseline": 6.25,
"whitespace": 50
},
"resetPoint": 58880,
"showSelector": false,
"selector": "class",
"classSelector": ".icon",
"showMetrics": false,
"showMetadata": true,
"showVersion": true,
"ie7": false
},
"imagePref": {
"prefix": "icon-",
"png": true,
"useClassSelector": true,
"color": 4473924,
"bgColor": 16777215
},
"historySize": 100,
"showCodes": true,
"gridSize": 16,
"showLiga": false
}
}
-241
View File
@@ -1,241 +0,0 @@
<?xml version="1.0"?>
<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>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<modules>
<module>server</module>
<module>collectd-timely-plugin</module>
<module>collectd-nsq-plugin</module>
<module>collectd-plugin-parent</module>
</modules>
<properties>
<!-- Configure the os-maven-plugin extension to expand the classifier on -->
<!-- Fedora-"like" systems. -->
<os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<collectd-api-jar>/usr/share/collectd/java/collectd-api.jar</collectd-api-jar>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<version.accumulo>1.7.0</version.accumulo>
<version.caffeine>2.2.0</version.caffeine>
<version.commons-configuration>1.10</version.commons-configuration>
<version.commons-lang3>3.1</version.commons-lang3>
<version.hadoop>2.6.2</version.hadoop>
<version.jackson>2.6.1</version.jackson>
<version.javassist>3.20.0-GA</version.javassist>
<version.junit>4.11</version.junit>
<version.log4j>2.5</version.log4j>
<version.netty>4.0.36.Final</version.netty>
<version.security>4.0.4.RELEASE</version.security>
<version.slf4j>1.7.12</version.slf4j>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${version.jackson}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${version.jackson}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${version.jackson}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${version.jackson}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${version.caffeine}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>${version.commons-configuration}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${version.netty}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${version.log4j}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${version.log4j}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${version.log4j}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.accumulo</groupId>
<artifactId>accumulo-core</artifactId>
<version>${version.accumulo}</version>
</dependency>
<dependency>
<groupId>org.apache.accumulo</groupId>
<artifactId>accumulo-minicluster</artifactId>
<version>${version.accumulo}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${version.commons-lang3}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${version.hadoop}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
</exclusion>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-util</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${version.hadoop}</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-util</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minicluster</artifactId>
<version>${version.hadoop}</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
</exclusion>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-util</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${version.javassist}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${version.security}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${version.security}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.3</version>
<configuration>
<effort>Max</effort>
<threshold>Medium</threshold>
<xmlOutput>true</xmlOutput>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
-152
View File
@@ -1,152 +0,0 @@
{
"id": 4,
"title": "Standalone Test",
"originalTitle": "Standalone Test",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "250px",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "Timely Standalone",
"editable": true,
"error": false,
"fill": 1,
"grid": {
"threshold1": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2": null,
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"id": 1,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 12,
"stack": false,
"steppedLine": false,
"targets": [
{
"aggregator": "sum",
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"downsampleInterval": "",
"errors": {},
"metric": "timely.metrics.received",
"refId": "A"
},
{
"aggregator": "sum",
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"errors": {},
"metric": "timely.keys.meta.inserted",
"refId": "B"
},
{
"aggregator": "sum",
"downsampleAggregator": "avg",
"downsampleFillPolicy": "none",
"errors": {},
"metric": "timely.keys.metric.inserted",
"refId": "C"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Timely Status",
"tooltip": {
"msResolution": true,
"shared": true,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"show": true
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
}
],
"title": "Row"
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": "1m",
"schemaVersion": 12,
"version": 3,
"links": []
}
-28
View File
@@ -1,28 +0,0 @@
#!/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}/..
CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely.properties"
LIB_DIR="${BASE_DIR}/lib"
NUM_SERVER_THREADS=4
HOST="127.0.0.1"
PORT=54321
CP=""
SEP=""
for j in ${LIB_DIR}/*.jar; do
CP="${CP}${SEP}${j}"
SEP=":"
done
JVM_ARGS="-Xmx128m -Xms128m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
echo "$JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT} --fast"
exec $JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT} --fast
-28
View File
@@ -1,28 +0,0 @@
#!/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}/..
CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely.properties"
LIB_DIR="${BASE_DIR}/lib"
NUM_SERVER_THREADS=4
HOST="127.0.0.1"
PORT=54321
CP=""
SEP=""
for j in ${LIB_DIR}/*.jar; do
CP="${CP}${SEP}${j}"
SEP=":"
done
JVM_ARGS="-Xmx128m -Xms128m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
echo "$JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT}"
exec $JAVA_HOME/bin/java -classpath ${CP} ${JVM_ARGS} timely.util.InsertTestData ${HOST} ${PORT}
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="60">
<Appenders>
<RollingRandomAccessFile name="Rolling" fileName="logs/timely-server.log"
filePattern="logs/$${date:yyyy-MM}/timely-server-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="1024 MB" />
</Policies>
<DefaultRolloverStrategy max="10" />
</RollingRandomAccessFile>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace" includeLocation="false">
<AppenderRef ref="Console" level="warn" />
<AppenderRef ref="Rolling" level="trace" />
</Root>
</Loggers>
</Configuration>
-37
View File
@@ -1,37 +0,0 @@
#!/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%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/..
TMP_DIR="${BASE_DIR}/tmp"
CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely.properties"
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}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-tcnative.so
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}/libnetty-tcnative.so"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.Server "${CONF_FILE}""
$JAVA_HOME/bin/java ${JVM_ARGS} timely.Server "${CONF_FILE}"
-38
View File
@@ -1,38 +0,0 @@
#!/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%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/..
TMP_DIR="${BASE_DIR}/tmp"
CONF_DIR="${BASE_DIR}/conf"
CONF_FILE="${CONF_DIR}/timely-standalone.properties"
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}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-tcnative.so
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*"
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${THIS_DIR}/log4j2.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}/libnetty-tcnative.so"
#JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,address=54323,server=y,suspend=y"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.StandaloneServer "${CONF_FILE}" "${TMP_DIR}""
$JAVA_HOME/bin/java ${JVM_ARGS} timely.StandaloneServer "${CONF_FILE}" "${TMP_DIR}"
-49
View File
@@ -1,49 +0,0 @@
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
</beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:map key-type="java.lang.String">
<beans:entry key="example.com">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:entry>
</util:map>
</beans:property>
</beans:bean>
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
</beans:bean>
<user-service id="usernamePasswordService">
<user name="test" password="test1" authorities="A,B,C" />
</user-service>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<!-- X509 PreAuthenticated authorization provider -->
<authentication-provider ref="x509AuthorizationProvider"/>
<!-- Basic authorization provider, validates provided username and password -->
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
</authentication-manager>
</beans:beans>
-35
View File
@@ -1,35 +0,0 @@
timely.ip=127.0.0.1
timely.port.put=54321
timely.port.query=54322
timely.instance_name=TimelyStandalone
timely.zookeepers=127.0.0.1:9804
timely.username=root
timely.password=secret
timely.table=timely.metrics
timely.meta=timely.meta
timely.write.latency=5s
timely.write.threads=3
timely.write.buffer.size=52428800
timely.metric.age.off.days=7
timely.cors.allow.any.origin=true
timely.cors.allow.null.origin=false
timely.cors.allowed.origins=
timely.cors.allowed.methods=DELETE,GET,HEAD,OPTIONS,PUT,POST
timely.cors.allowed.headers=content-type
timely.cors.allow.credentials=true
timely.scanner.threads=4
timely.metrics.report.tags.ignored=
timely.meta.cache.expiration.minutes=60
timely.meta.cache.initial.capacity=2000
timely.meta.cache.max.capacity=10000
#timely.ssl.certificate.file=
#timely.ssl.key.file=
#timely.ssl.key.pass=
timely.ssl.use.generated.keypair=true
#timely.ssl.trust.store.file=
timely.ssl.use.openssl=true
#timely.ssl.use.ciphers=
timely.session.max.age=86400
timely.http.address=localhost
grafana.http.address=http://localhost:3000/login
timely.allow.anonymous.access=true
-35
View File
@@ -1,35 +0,0 @@
timely.ip=
timely.port.put=
timely.port.query=
timely.instance_name=
timely.zookeepers=
timely.username=
timely.password=
timely.table=timely.metrics
timely.meta=timely.meta
timely.write.latency=5s
timely.write.threads=3
timely.write.buffer.size=52428800
timely.metric.age.off.days=7
timely.cors.allow.any.origin=false
timely.cors.allow.null.origin=false
timely.cors.allowed.origins=
timely.cors.allowed.methods=DELETE,GET,HEAD,OPTIONS,PUT,POST
timely.cors.allowed.headers=content-type
timely.cors.allow.credentials=true
timely.scanner.threads=4
timely.metrics.report.tags.ignored=
timely.meta.cache.expiration.minutes=60
timely.meta.cache.initial.capacity=2000
timely.meta.cache.max.capacity=10000
timely.ssl.certificate.file=
timely.ssl.key.file=
timely.ssl.key.pass=
timely.ssl.use.generated.keypair=false
timely.ssl.trust.store.file=
timely.ssl.use.openssl=true
timely.ssl.use.ciphers=
timely.session.max.age=86400
timely.http.address=
grafana.http.address=
timely.allow.anonymous.access=false
-468
View File
@@ -1,468 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>timely-server</artifactId>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>0.0.1</version>
</parent>
<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>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>1.1.33.Fork15</version>
<classifier>${os.detected.classifier}</classifier>
<!-- Eclipse doesn't like this, but it works on the cmd line -->
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.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-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minicluster</artifactId>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<build>
<extensions>
<!-- Use os-maven-plugin to initialize the "os.detected" properties -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptors>
<descriptor>src/main/dist/assembly.xml</descriptor>
</descriptors>
<tarLongFileMode>posix</tarLongFileMode>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-dependencies</id>
<goals>
<goal>copy-dependencies</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<excludeArtifactIds>junit</excludeArtifactIds>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
<execution>
<id>unpack-native</id>
<goals>
<goal>unpack</goal>
</goals>
<phase>process-test-classes</phase>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId>
<version>1.1.33.Fork15</version>
<classifier>${os.detected.classifier}</classifier>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/native</outputDirectory>
<includes>**/*.so</includes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.19</version>
<configuration>
<argLine>${failsafeArgLine} -Djava.library.path=${project.build.dir}/native/META-INF/native/libnetty-tcnative.so -Dzookeeper.sasl.client=false</argLine>
<includes>
<include>**/*.java</include>
</includes>
<groups>timely.test.IntegrationTest</groups>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<effort>Max</effort>
<threshold>Medium</threshold>
<xmlOutput>true</xmlOutput>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<argLine>${sureFireArgLine} -Djava.library.path=${project.build.dir}/native/META-INF/native/libnetty-tcnative.so -Dzookeeper.sasl.client=false</argLine>
<excludedGroups>timely.test.IntegrationTest</excludedGroups>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.6.201602180812</version>
<executions>
<execution>
<id>jacoco-prepare-ut</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<phase>process-test-classes</phase>
<configuration>
<destFile>${project.build.directory}/coverage/jacoco-ut.exec</destFile>
<propertyName>sureFireArgLine</propertyName>
</configuration>
</execution>
<execution>
<id>jacoco-prepare-it</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
<phase>pre-integration-test</phase>
<configuration>
<destFile>${project.build.directory}/coverage/jacoco-it.exec</destFile>
<propertyName>failsafeArgLine</propertyName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>0.5.2</version>
<configuration>
<configFile>${project.parent.basedir}/eclipse/Eclipse-Formatter.xml</configFile>
<lineEnding>LF</lineEnding>
<overrideConfigCompilerVersion>true</overrideConfigCompilerVersion>
</configuration>
<executions>
<execution>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.revelc.code</groupId>
<artifactId>
formatter-maven-plugin
</artifactId>
<versionRange>
[0.5.2,)
</versionRange>
<goals>
<goal>format</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-dependency-plugin
</artifactId>
<versionRange>
[2.10,)
</versionRange>
<goals>
<goal>
copy-dependencies
</goal>
<goal>unpack</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore/>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>rpm</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>rpm-maven-plugin</artifactId>
<version>2.1.4</version>
<executions>
<execution>
<id>generate-rpm</id>
<goals>
<goal>rpm</goal>
</goals>
<phase>package</phase>
<configuration>
<name>timely-server</name>
<group>Applications/System</group>
<prefix>/opt/timely</prefix>
<prefixes>
<prefix>INSTALL_DIR</prefix>
</prefixes>
<mappings>
<mapping>
<directory>/opt/timely/bin</directory>
<filemode>600</filemode>
<sources>
<source>
<location>${project.basedir}/bin</location>
<excludes>
<excludes>*.sh</excludes>
</excludes>
</source>
</sources>
</mapping>
<mapping>
<directory>/opt/timely/bin</directory>
<filemode>700</filemode>
<sources>
<source>
<location>${project.basedir}/bin</location>
<includes>
<include>*.sh</include>
</includes>
</source>
</sources>
</mapping>
<mapping>
<directory>/opt/timely/conf</directory>
<filemode>600</filemode>
<sources>
<source>
<location>${project.basedir}/conf</location>
<includes>
<include>*</include>
</includes>
</source>
</sources>
</mapping>
<mapping>
<directory>/opt/timely/lib</directory>
<filemode>600</filemode>
<sources>
<source>
<location>${project.basedir}/target/lib</location>
</source>
<source>
<location>${project.basedir}/target/${project.artifactId}-${project.version}.jar</location>
</source>
</sources>
</mapping>
</mappings>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.3</version>
<configuration>
<effort>Max</effort>
<threshold>Medium</threshold>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.6</version>
<configuration>
<targetJdk>1.8</targetJdk>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.6.201602180812</version>
<reportSets>
<reportSet>
<id>JaCoCo Unit Test Coverage</id>
<configuration>
<dataFile>${project.build.directory}/coverage/jacoco-ut.exec</dataFile>
</configuration>
<reports>
<report>report</report>
</reports>
</reportSet>
<reportSet>
<id>JaCoCo Integration Test Coverage</id>
<configuration>
<dataFile>${project.build.directory}/coverage/jacoco-it.exec</dataFile>
</configuration>
<reports>
<report>report-integration</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
</project>
-47
View File
@@ -1,47 +0,0 @@
<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>
</fileSets>
</assembly>
@@ -1,180 +0,0 @@
package timely;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.accumulo.core.client.BatchWriterConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class Configuration {
public static final String IP = "timely.ip";
public static final String PUT_PORT = "timely.port.put";
public static final String QUERY_PORT = "timely.port.query";
public static final String ZOOKEEPERS = "timely.zookeepers";
public static final String INSTANCE_NAME = "timely.instance_name";
public static final String USERNAME = "timely.username";
public static final String PASSWORD = "timely.password";
public static final String METRICS_TABLE = "timely.table";
private static final String METRICS_TABLE_DEFAULT = "timely.metrics";
public static final String META_TABLE = "timely.meta";
private static final String META_TABLE_DEFAULT = "timely.meta";
public static final String METRICS_AGEOFF_DAYS = "timely.metric.age.off.days";
private static final String METRICS_AGEOFF_DAYS_DEFAULT = "7";
public static final String MAX_LATENCY = "timely.write.latency";
private static final String MAX_LATENCY_DEFAULT = "5s";
public static final String WRITE_THREADS = "timely.write.threads";
public static final String WRITE_BUFFER_SIZE = "timely.write.buffer.size";
public static final String SCANNER_THREADS = "timely.scanner.threads";
public static final String CORS_ALLOW_ANY_ORIGIN = "timely.cors.allow.any.origin";
private static final String CORS_ALLOW_ANY_ORIGIN_DEFAULT = "false";
public static final String CORS_ALLOW_NULL_ORIGIN = "timely.cors.allow.null.origin";
private static final String CORS_ALLOW_NULL_ORIGIN_DEFAULT = "false";
public static final String CORS_ALLOWED_ORIGINS = "timely.cors.allowed.origins";
private static final String CORS_ALLOWED_ORIGINS_DEFAULT = "";
public static final String CORS_ALLOWED_METHODS = "timely.cors.allowed.methods";
private static final String CORS_ALLOWED_METHODS_DEFAULT = "DELETE,GET,HEAD,OPTIONS,PUT,POST";
public static final String CORS_ALLOWED_HEADERS = "timely.cors.allowed.headers";
private static final String CORS_ALLOWED_HEADERS_DEFAULT = "content-type";
public static final String CORS_ALLOW_CREDENTIALS = "timely.cors.allow.credentials";
private static final String CORS_ALLOW_CREDENTIALS_DEFAULT = "true";
public static final String METRICS_IGNORED_TAGS = "timely.metrics.report.tags.ignored";
public static final String META_CACHE_EXPIRATION = "timely.meta.cache.expiration.minutes";
public static final Long META_CACHE_EXPIRATION_DEFAULT = 60L;
public static final String META_CACHE_INITIAL_CAPACITY = "timely.meta.cache.initial.capacity";
public static final Integer META_CACHE_INITIAL_CAPACITY_DEFAULT = 2000;
public static final String META_CACHE_MAX_CAPACITY = "timely.meta.cache.max.capacity";
public static final Integer META_CACHE_MAX_CAPACITY_DEFAULT = 10000;
public static final String VISIBILITY_CACHE_EXPIRATION = "timely.visibility.cache.expiration.minutes";
public static final Long VISIBILITY_EXPIRATION_DEFAULT = 60L;
public static final String VISIBILITY_CACHE_INITIAL_CAPACITY = "timely.visibility.cache.initial.capacity";
public static final Integer VISIBILITY_CACHE_INITIAL_CAPACITY_DEFAULT = 2000;
public static final String VISIBILITY_CACHE_MAX_CAPACITY = "timely.visibility.cache.max.capacity";
public static final Integer VISIBILITY_CACHE_MAX_CAPACITY_DEFAULT = 10000;
/** Security properties */
public static final String SSL_CERTIFICATE_FILE = "timely.ssl.certificate.file";
public static final String SSL_PRIVATE_KEY_FILE = "timely.ssl.key.file";
public static final String SSL_PRIVATE_KEY_PASS = "timely.ssl.key.pass";
public static final String SSL_USE_GENERATED_KEYPAIR = "timely.ssl.use.generated.keypair";
private static final String SSL_USE_GENERATED_KEYPAIR_DEFAULT = "false";
public static final String SSL_TRUST_STORE_FILE = "timely.ssl.trust.store.file";
public static final String SSL_USE_OPENSSL = "timely.ssl.use.openssl";
private static final String SSL_USE_OPENSSL_DEFAULT = "true";
public static final String SSL_USE_CIPHERS = "timely.ssl.use.ciphers";
private static final String SSL_USE_CIPHERS_DEFAULT = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:"
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:"
+ "TLS_RSA_WITH_AES_128_CBC_SHA:SSL_RSA_WITH_3DES_EDE_CBC_SHA";
public static final String SESSION_MAX_AGE = "timely.session.max.age";
public static final long SESSION_MAX_AGE_DEFAULT = 86400;
public static final String TIMELY_HTTP_ADDRESS = "timely.http.address";
public static final String GRAFANA_HTTP_ADDRESS = "grafana.http.address";
public static final String ALLOW_ANONYMOUS_ACCESS = "timely.allow.anonymous.access";
private static final String ALLOW_ANONYMOUS_ACCESS_DEFAULT = "false";
private static final List<String> REQUIRED_PROPERTIES = new ArrayList<>();
static {
REQUIRED_PROPERTIES.add(IP);
REQUIRED_PROPERTIES.add(PUT_PORT);
REQUIRED_PROPERTIES.add(QUERY_PORT);
REQUIRED_PROPERTIES.add(ZOOKEEPERS);
REQUIRED_PROPERTIES.add(INSTANCE_NAME);
REQUIRED_PROPERTIES.add(USERNAME);
REQUIRED_PROPERTIES.add(PASSWORD);
REQUIRED_PROPERTIES.add(TIMELY_HTTP_ADDRESS);
REQUIRED_PROPERTIES.add(GRAFANA_HTTP_ADDRESS);
};
private final Properties props = new Properties();
public Configuration(Path path) throws IOException {
org.apache.hadoop.conf.Configuration hadoopConf = new org.apache.hadoop.conf.Configuration();
try (FileSystem fs = FileSystem.get(hadoopConf)) {
try (InputStream inputStream = fs.open(path)) {
init(inputStream);
}
}
}
public Configuration(File conf) throws IOException {
try (InputStream inputStream = new FileInputStream(conf)) {
init(inputStream);
}
}
private void init(InputStream configStream) throws IOException {
// Add defaults
props.setProperty(METRICS_TABLE, METRICS_TABLE_DEFAULT);
props.setProperty(META_TABLE, META_TABLE_DEFAULT);
props.setProperty(MAX_LATENCY, MAX_LATENCY_DEFAULT);
props.setProperty(METRICS_AGEOFF_DAYS, METRICS_AGEOFF_DAYS_DEFAULT);
final BatchWriterConfig defaults = new BatchWriterConfig();
props.setProperty(WRITE_BUFFER_SIZE, "" + defaults.getMaxMemory());
props.setProperty(WRITE_THREADS, "" + defaults.getMaxWriteThreads());
props.setProperty(SCANNER_THREADS, "" + 4);
props.setProperty(CORS_ALLOW_ANY_ORIGIN, CORS_ALLOW_ANY_ORIGIN_DEFAULT);
props.setProperty(CORS_ALLOW_NULL_ORIGIN, CORS_ALLOW_NULL_ORIGIN_DEFAULT);
props.setProperty(CORS_ALLOWED_ORIGINS, CORS_ALLOWED_ORIGINS_DEFAULT);
props.setProperty(CORS_ALLOWED_METHODS, CORS_ALLOWED_METHODS_DEFAULT);
props.setProperty(CORS_ALLOWED_HEADERS, CORS_ALLOWED_HEADERS_DEFAULT);
props.setProperty(CORS_ALLOW_CREDENTIALS, CORS_ALLOW_CREDENTIALS_DEFAULT);
props.setProperty(META_CACHE_EXPIRATION, "" + META_CACHE_EXPIRATION_DEFAULT);
props.setProperty(META_CACHE_INITIAL_CAPACITY, "" + META_CACHE_INITIAL_CAPACITY_DEFAULT);
props.setProperty(META_CACHE_MAX_CAPACITY, "" + META_CACHE_MAX_CAPACITY_DEFAULT);
props.setProperty(SSL_USE_GENERATED_KEYPAIR, SSL_USE_GENERATED_KEYPAIR_DEFAULT);
props.setProperty(SSL_USE_OPENSSL, SSL_USE_OPENSSL_DEFAULT);
props.setProperty(SSL_USE_CIPHERS, SSL_USE_CIPHERS_DEFAULT);
props.setProperty(ALLOW_ANONYMOUS_ACCESS, ALLOW_ANONYMOUS_ACCESS_DEFAULT);
props.setProperty(SESSION_MAX_AGE, SESSION_MAX_AGE_DEFAULT + "");
props.setProperty(VISIBILITY_CACHE_EXPIRATION, VISIBILITY_EXPIRATION_DEFAULT + "");
props.setProperty(VISIBILITY_CACHE_INITIAL_CAPACITY, VISIBILITY_CACHE_INITIAL_CAPACITY_DEFAULT + "");
props.setProperty(VISIBILITY_CACHE_MAX_CAPACITY, VISIBILITY_CACHE_MAX_CAPACITY_DEFAULT + "");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(configStream, StandardCharsets.UTF_8))) {
props.load(reader);
validate();
}
}
protected void validate() {
REQUIRED_PROPERTIES.forEach(p -> {
if (!props.containsKey(p) || StringUtils.isEmpty(props.getProperty(p))) {
throw new IllegalArgumentException("Required property " + p + " must be specified.");
}
});
if (!getBoolean(Configuration.SSL_USE_GENERATED_KEYPAIR)) {
if (StringUtils.isEmpty(get(Configuration.SSL_CERTIFICATE_FILE))) {
throw new IllegalArgumentException(Configuration.SSL_CERTIFICATE_FILE + "must be specified.");
}
if (StringUtils.isEmpty(get(Configuration.SSL_PRIVATE_KEY_FILE))) {
throw new IllegalArgumentException(Configuration.SSL_PRIVATE_KEY_FILE + "must be specified.");
}
}
}
public String get(String property) {
return props.getProperty(property);
}
public boolean getBoolean(String property) {
return Boolean.parseBoolean(get(property));
}
}
-376
View File
@@ -1,376 +0,0 @@
package timely;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
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.HttpMethod;
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.cors.CorsConfig;
import io.netty.handler.codec.http.cors.CorsHandler;
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.util.internal.SystemPropertyUtil;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.query.response.TimelyException;
import timely.auth.AuthCache;
import timely.auth.VisibilityCache;
import timely.netty.http.HttpAggregatorsRequestHandler;
import timely.netty.http.HttpMetricsRequestHandler;
import timely.netty.http.HttpQueryDecoder;
import timely.netty.http.HttpQueryRequestHandler;
import timely.netty.http.HttpSearchLookupRequestHandler;
import timely.netty.http.HttpSuggestRequestHandler;
import timely.netty.http.TimelyExceptionHandler;
import timely.netty.http.login.BasicAuthLoginRequestHandler;
import timely.netty.http.login.X509LoginRequestHandler;
import timely.netty.tcp.TcpPutDecoder;
import timely.netty.tcp.TcpPutHandler;
import timely.store.DataStore;
import timely.store.DataStoreFactory;
import timely.store.MetaCacheFactory;
public class Server {
private static final Logger LOG = LoggerFactory.getLogger(Server.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";
protected static final CountDownLatch LATCH = new CountDownLatch(1);
private Configuration config = null;
private EventLoopGroup tcpWorkerGroup = null;
private EventLoopGroup tcpBossGroup = null;
private EventLoopGroup httpWorkerGroup = null;
private EventLoopGroup httpBossGroup = null;
protected Channel putChannelHandle = null;
protected Channel queryChannelHandle = null;
protected DataStore dataStore = null;
protected volatile boolean shutdown = false;
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));
if (patch >= EPOLL_MIN_PATCH_VERSION) {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
private static String usage() {
return "Server <configFile>";
}
public static void fatal(String msg, Throwable t) {
LOG.error(msg, t);
LATCH.countDown();
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(usage());
}
final File conf = new File(args[0]);
if (!conf.canRead()) {
throw new RuntimeException("Configuration file does not exist or cannot be read");
}
Server s = new Server(conf);
try {
LATCH.await();
} catch (final InterruptedException e) {
LOG.info("Server shutting down.");
} finally {
s.shutdown();
}
}
private void shutdownHook() {
final Runnable shutdownRunner = new Runnable() {
@Override
public void run() {
if (!shutdown) {
shutdown();
}
}
};
final Thread hook = new Thread(shutdownRunner, "shutdown-hook-thread");
Runtime.getRuntime().addShutdownHook(hook);
}
public void shutdown() {
try {
putChannelHandle.close().sync();
} catch (final Exception e) {
LOG.error("Error shutting down tcp channel", e);
}
try {
queryChannelHandle.close().sync();
} catch (final Exception e) {
LOG.error("Error shutting down http channel", e);
}
try {
tcpBossGroup.shutdownGracefully().sync();
} catch (final Exception e) {
LOG.error("Error closing Netty TCP boss thread group", e);
}
try {
tcpWorkerGroup.shutdownGracefully().sync();
} catch (final Exception e) {
LOG.error("Error closing Netty TCP worker thread group", e);
}
try {
httpBossGroup.shutdownGracefully().sync();
} catch (final Exception e) {
LOG.error("Error closing Netty HTTP boss thread group", e);
}
try {
httpWorkerGroup.shutdownGracefully().sync();
} catch (final Exception e) {
LOG.error("Error closing Netty HTTP worker thread group", e);
}
try {
dataStore.flush();
} catch (TimelyException e) {
LOG.error("Error flushing to server during shutdown", e);
}
MetaCacheFactory.close();
this.shutdown = true;
LOG.info("Server shut down.");
}
public Server(File conf) throws Exception {
config = new Configuration(conf);
int nettyThreads = Math.max(1,
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
dataStore = DataStoreFactory.create(config, nettyThreads);
// Initialize the MetaCache
MetaCacheFactory.getCache(config);
// initialize the auth cache
AuthCache.setSessionMaxAge(config);
// Initialize the VisibilityCache
VisibilityCache.init(config);
final boolean useEpoll = useEpoll();
Class<? extends ServerSocketChannel> channelClass = null;
if (useEpoll) {
tcpWorkerGroup = new EpollEventLoopGroup();
tcpBossGroup = new EpollEventLoopGroup();
httpWorkerGroup = new EpollEventLoopGroup();
httpBossGroup = new EpollEventLoopGroup();
channelClass = EpollServerSocketChannel.class;
} else {
tcpWorkerGroup = new NioEventLoopGroup();
tcpBossGroup = new NioEventLoopGroup();
httpWorkerGroup = new NioEventLoopGroup();
httpBossGroup = new NioEventLoopGroup();
channelClass = NioServerSocketChannel.class;
}
LOG.info("Using channel class {}", channelClass.getSimpleName());
final ServerBootstrap putServer = new ServerBootstrap();
putServer.group(tcpBossGroup, tcpWorkerGroup);
putServer.channel(channelClass);
putServer.handler(new LoggingHandler());
putServer.childHandler(setupTcpChannel());
putServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
putServer.option(ChannelOption.SO_BACKLOG, 128);
putServer.option(ChannelOption.SO_KEEPALIVE, true);
final int putPort = Integer.parseInt(config.get(Configuration.PUT_PORT));
putChannelHandle = putServer.bind(putPort).sync().channel();
final String putAddress = ((InetSocketAddress) putChannelHandle.localAddress()).getAddress().getHostAddress();
final int queryPort = Integer.parseInt(config.get(Configuration.QUERY_PORT));
SslContext sslCtx = createSSLContext(config);
if (sslCtx instanceof OpenSslServerContext) {
OpenSslServerContext openssl = (OpenSslServerContext) sslCtx;
String application = "Timely_" + queryPort;
OpenSslServerSessionContext opensslCtx = openssl.sessionContext();
opensslCtx.setSessionCacheEnabled(true);
opensslCtx.setSessionCacheSize(128);
opensslCtx.setSessionIdContext(application.getBytes(StandardCharsets.UTF_8));
opensslCtx.setSessionTimeout(Integer.parseInt(config.get(Configuration.SESSION_MAX_AGE)));
}
final ServerBootstrap queryServer = new ServerBootstrap();
queryServer.group(httpBossGroup, httpWorkerGroup);
queryServer.channel(channelClass);
queryServer.handler(new LoggingHandler());
queryServer.childHandler(setupHttpChannel(config, sslCtx));
queryServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
queryServer.option(ChannelOption.SO_BACKLOG, 128);
queryServer.option(ChannelOption.SO_KEEPALIVE, true);
queryChannelHandle = queryServer.bind(queryPort).sync().channel();
final String queryAddress = ((InetSocketAddress) queryChannelHandle.localAddress()).getAddress()
.getHostAddress();
shutdownHook();
LOG.info("Server started. Listening on {}:{} for TCP traffic and {}:{} for HTTP traffic", putAddress, putPort,
queryAddress, queryPort);
}
protected SslContext createSSLContext(Configuration config) throws Exception {
Boolean generate = config.getBoolean(Configuration.SSL_USE_GENERATED_KEYPAIR);
SslContextBuilder ssl = null;
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 = config.get(Configuration.SSL_CERTIFICATE_FILE);
String key = config.get(Configuration.SSL_PRIVATE_KEY_FILE);
String keyPass = config.get(Configuration.SSL_PRIVATE_KEY_PASS);
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);
}
String ciphers = config.get(Configuration.SSL_USE_CIPHERS);
ssl.ciphers(Arrays.asList(ciphers.split(":")));
// Can't set to REQUIRE because the CORS pre-flight requests will fail.
ssl.clientAuth(ClientAuth.OPTIONAL);
Boolean useOpenSSL = config.getBoolean(Configuration.SSL_USE_OPENSSL);
if (useOpenSSL) {
ssl.sslProvider(SslProvider.OPENSSL);
} else {
ssl.sslProvider(SslProvider.JDK);
}
String trustStore = config.get(Configuration.SSL_TRUST_STORE_FILE);
if (null != trustStore) {
ssl.trustManager(new File(trustStore));
}
return ssl.build();
}
protected ChannelHandler setupHttpChannel(Configuration config, SslContext sslCtx) {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
ch.pipeline().addLast("decoder", new HttpRequestDecoder());
ch.pipeline().addLast("encoder", new HttpResponseEncoder());
ch.pipeline().addLast("deflater", new HttpContentCompressor());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192));
final CorsConfig.Builder ccb;
if (config.getBoolean(Configuration.CORS_ALLOW_ANY_ORIGIN)) {
ccb = new CorsConfig.Builder();
} else {
String origins = config.get(Configuration.CORS_ALLOWED_ORIGINS);
ccb = new CorsConfig.Builder(origins.split(","));
}
if (config.getBoolean(Configuration.CORS_ALLOW_NULL_ORIGIN)) {
ccb.allowNullOrigin();
}
if (config.getBoolean(Configuration.CORS_ALLOW_CREDENTIALS)) {
ccb.allowCredentials();
}
if (!StringUtils.isEmpty(config.get(Configuration.CORS_ALLOWED_METHODS))) {
String[] methods = config.get(Configuration.CORS_ALLOWED_METHODS).split(",");
HttpMethod[] m = new HttpMethod[methods.length];
for (int i = 0; i < methods.length; i++) {
m[i] = HttpMethod.valueOf(methods[i]);
}
ccb.allowedRequestMethods(m);
}
if (!StringUtils.isEmpty(config.get(Configuration.CORS_ALLOWED_HEADERS))) {
ccb.allowedRequestHeaders(config.get(Configuration.CORS_ALLOWED_HEADERS).split(","));
}
CorsConfig cors = ccb.build();
LOG.trace("Cors configuration: {}", cors);
ch.pipeline().addLast("cors", new CorsHandler(cors));
ch.pipeline().addLast("queryDecoder", new HttpQueryDecoder(config));
ch.pipeline().addLast("login", new X509LoginRequestHandler(config));
ch.pipeline().addLast("doLogin", new BasicAuthLoginRequestHandler(config));
ch.pipeline().addLast("aggregators", new HttpAggregatorsRequestHandler());
ch.pipeline().addLast("metrics", new HttpMetricsRequestHandler(config));
ch.pipeline().addLast("query", new HttpQueryRequestHandler(dataStore));
ch.pipeline().addLast("search", new HttpSearchLookupRequestHandler(dataStore));
ch.pipeline().addLast("suggest", new HttpSuggestRequestHandler(dataStore));
ch.pipeline().addLast("error", new TimelyExceptionHandler());
}
};
}
protected ChannelHandler setupTcpChannel() {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new TcpPutDecoder());
ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore));
}
};
}
}
@@ -1,95 +0,0 @@
package timely;
import java.io.File;
import java.io.IOException;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.admin.SecurityOperations;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.minicluster.MemoryUnit;
import org.apache.accumulo.minicluster.MiniAccumuloCluster;
import org.apache.accumulo.minicluster.MiniAccumuloConfig;
import org.apache.accumulo.minicluster.ServerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StandaloneServer extends Server {
private static final Logger LOG = LoggerFactory.getLogger(StandaloneServer.class);
private static MiniAccumuloCluster mac = null;
public StandaloneServer(File conf) throws Exception {
super(conf);
}
@Override
public void shutdown() {
try {
mac.stop();
LOG.info("MiniAccumuloCluster shutdown.");
} catch (IOException | InterruptedException e) {
System.err.println("Error stopping MiniAccumuloCluster");
e.printStackTrace();
}
super.shutdown();
}
private static String usage() {
return "StandaloneServer <configFile> <directory>";
}
public static void main(String[] args) {
if (args.length != 2) {
System.err.println(usage());
}
final File conf = new File(args[0]);
if (!conf.canRead()) {
throw new RuntimeException("Configuration file does not exist or cannot be read");
}
File tmp = new File(args[1]);
if (!tmp.canWrite()) {
System.err.println("Unable to write to directory: " + tmp);
System.exit(1);
}
File accumuloDir = new File(tmp, "accumulo");
MiniAccumuloConfig macConfig = new MiniAccumuloConfig(accumuloDir, "secret");
macConfig.setInstanceName("TimelyStandalone");
macConfig.setZooKeeperPort(9804);
macConfig.setNumTservers(1);
macConfig.setMemory(ServerType.TABLET_SERVER, 1, MemoryUnit.GIGABYTE);
try {
mac = new MiniAccumuloCluster(macConfig);
mac.start();
mac.getInstanceName();
} catch (IOException | InterruptedException e) {
System.err.println("Error starting MiniAccumuloCluster: " + e.getMessage());
System.exit(1);
}
try {
Connector conn = mac.getConnector("root", "secret");
SecurityOperations sops = conn.securityOperations();
Authorizations rootAuths = new Authorizations("A", "B", "C", "D", "E", "F", "G", "H", "I");
sops.changeUserAuthorizations("root", rootAuths);
} catch (AccumuloException | AccumuloSecurityException e) {
System.err.println("Error configuring root user");
System.exit(1);
}
try {
new StandaloneServer(conf);
} catch (Exception e) {
System.err.println("Error starting server");
e.printStackTrace();
System.exit(1);
}
try {
LATCH.await();
} catch (InterruptedException e) {
LOG.info("Server shutting down.");
}
}
}
@@ -1,48 +0,0 @@
package timely.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class AuthenticatedRequest implements Request {
private String sessionId = null;
private Map<String, String> requestHeaders = new HashMap<>();
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public void addHeaders(List<Entry<String, String>> headers) {
headers.forEach(l -> requestHeaders.put(l.getKey(), l.getValue()));
}
public Map<String, String> getRequestHeaders() {
return Collections.unmodifiableMap(requestHeaders);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("{");
buf.append("Session ID: ").append(sessionId);
buf.append(", Request Headers: ").append(requestHeaders.toString());
buf.append("}");
return buf.toString();
}
@Override
public void validate() {
Request.super.validate();
if (null == sessionId) {
throw new IllegalArgumentException("SessionID is null, must log in first");
}
}
}
@@ -1,11 +0,0 @@
package timely.api;
public interface Request {
/**
* Validates the request, throws IllegalArgumentException if not valid.
*/
default void validate() {
}
}
@@ -1,126 +0,0 @@
package timely.api.model;
import java.util.ArrayList;
import java.util.List;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
public class Meta implements Comparable<Meta> {
public static final String METRIC_PREFIX = "m:";
public static final String TAG_PREFIX = "t:";
public static final String VALUE_PREFIX = "v:";
private String metric;
private String tagKey;
private String tagValue;
public Meta(String metric, String tagKey, String tagValue) {
super();
this.metric = metric;
this.tagKey = tagKey;
this.tagValue = tagValue;
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public String getTagKey() {
return tagKey;
}
public void setTagKey(String tagKey) {
this.tagKey = tagKey;
}
public String getTagValue() {
return tagValue;
}
public void setTagValue(String tagValue) {
this.tagValue = tagValue;
}
public List<Key> toKeys() {
List<Key> keys = new ArrayList<>();
keys.add(new Key(METRIC_PREFIX + metric));
keys.add(new Key(TAG_PREFIX + metric, tagKey));
keys.add(new Key(VALUE_PREFIX + metric, tagKey, tagValue));
return keys;
}
public static Meta parse(Key k, Value v) {
if (k.getColumnQualifier().getLength() > 0) {
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), k.getColumnFamily().toString(), k
.getColumnQualifier().toString());
} else if (k.getColumnFamily().getLength() > 0) {
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), k.getColumnFamily().toString(),
null);
} else {
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), null, null);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((metric == null) ? 0 : metric.hashCode());
result = prime * result + ((tagKey == null) ? 0 : tagKey.hashCode());
result = prime * result + ((tagValue == null) ? 0 : tagValue.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Meta other = (Meta) obj;
if (metric == null) {
if (other.metric != null)
return false;
} else if (!metric.equals(other.metric))
return false;
if (tagKey == null) {
if (other.tagKey != null)
return false;
} else if (!tagKey.equals(other.tagKey))
return false;
if (tagValue == null) {
if (other.tagValue != null)
return false;
} else if (!tagValue.equals(other.tagValue))
return false;
return true;
}
@Override
public String toString() {
return "MetaKey [metric=" + metric + ", tagKey=" + tagKey + ", tagValue=" + tagValue + "]";
}
@Override
public int compareTo(Meta o) {
int result = metric.compareTo(o.metric);
if (result != 0) {
return result;
}
result = tagKey.compareTo(o.tagKey);
if (result != 0) {
return result;
}
return tagValue.compareTo(o.tagValue);
}
}
@@ -1,176 +0,0 @@
package timely.api.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.accumulo.core.client.lexicoder.DoubleLexicoder;
import org.apache.accumulo.core.client.lexicoder.LongLexicoder;
import org.apache.accumulo.core.client.lexicoder.PairLexicoder;
import org.apache.accumulo.core.client.lexicoder.StringLexicoder;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.accumulo.core.util.ComparablePair;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import timely.api.Request;
public class Metric implements Request {
private static final PairLexicoder<String, Long> rowCoder = new PairLexicoder<>(new StringLexicoder(),
new LongLexicoder());
private static final DoubleLexicoder valueCoder = new DoubleLexicoder();
public static final ColumnVisibility EMPTY_VISIBILITY = new ColumnVisibility();
private String metric;
private long timestamp;
private double value;
private List<Tag> tags;
private ColumnVisibility visibility = EMPTY_VISIBILITY;
public Metric() {
}
public Metric(String metric, long timestamp, double value, List<Tag> tags) {
super();
this.metric = metric;
this.timestamp = timestamp;
this.value = value;
this.tags = tags;
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
public List<Tag> getTags() {
return tags;
}
public void setTags(List<Tag> tags) {
this.tags = tags;
}
public void addTag(Tag tag) {
if (null == this.tags) {
tags = new ArrayList<Tag>();
}
this.tags.add(tag);
}
public ColumnVisibility getVisibility() {
return visibility;
}
public void setVisibility(ColumnVisibility visibility) {
this.visibility = visibility;
}
public Mutation toMutation() {
final byte[] row = rowCoder.encode(new ComparablePair<String, Long>(this.metric, this.timestamp));
final Value value = new Value(valueCoder.encode(this.value));
final Mutation m = new Mutation(row);
Collections.sort(tags);
for (final Tag entry : tags) {
final String cf = entry.getKey() + "=" + entry.getValue();
final StringBuffer otherTags = new StringBuffer();
String sep = "";
for (final Tag inner : this.tags) {
if (inner != entry) {
otherTags.append(sep).append(inner.getKey()).append("=").append(inner.getValue());
sep = ",";
}
}
m.put(cf, otherTags, visibility, this.timestamp, value);
}
return m;
}
public static Metric parse(Key k, Value v) {
ComparablePair<String, Long> row = rowCoder.decode(k.getRow().getBytes());
Double value = valueCoder.decode(v.get());
Metric m = new Metric();
m.setMetric(row.getFirst());
m.setTimestamp(row.getSecond());
Tag tag = new Tag(k.getColumnFamily().toString());
m.addTag(tag);
String qualifier = k.getColumnQualifier().toString();
if (!qualifier.isEmpty()) {
for (String otherTag : qualifier.split(",")) {
m.addTag(new Tag(otherTag));
}
}
m.setValue(value);
return m;
}
public static byte[] encodeRowKey(String metric, Long timestamp) {
return rowCoder.encode(new ComparablePair<String, Long>(metric, timestamp));
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[metric = ").append(metric);
buf.append(", timestamp = ").append(timestamp);
tags.forEach(t -> buf.append(", ").append(t.getKey()).append(" = ").append(t.getValue()));
buf.append(", value = ").append(value).append("]");
return buf.toString();
}
@Override
public int hashCode() {
HashCodeBuilder hc = new HashCodeBuilder(17, 37);
hc.append(metric);
hc.append(timestamp);
hc.append(value);
hc.append(tags);
return hc.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof Metric)) {
return false;
}
Metric other = (Metric) 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);
return equals.isEquals();
}
}
@@ -1,96 +0,0 @@
package timely.api.model;
import java.io.Serializable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
public class Tag implements Comparable<Tag>, Serializable {
private static final long serialVersionUID = 1L;
private String key;
private String value;
public Tag() {
super();
}
public Tag(String tagValue) {
super();
String[] tv = tagValue.split("=");
if (tv.length != 2) {
throw new IllegalArgumentException("Invalid tag format: " + tagValue);
}
this.key = tv[0];
this.value = tv[1];
}
public Tag(String key, String value) {
super();
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(key);
hcb.append(value);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof Tag) {
Tag other = (Tag) obj;
EqualsBuilder eq = new EqualsBuilder();
eq.append(this.key, other.key);
eq.append(this.value, other.value);
return eq.isEquals();
} else {
return false;
}
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("key", key);
tsb.append("value", value);
return tsb.toString();
}
@Override
public int compareTo(Tag o) {
int result = key.compareTo(o.key);
if (result != 0) {
return result;
}
return value.compareTo(o.value);
}
}
@@ -1,12 +0,0 @@
package timely.api.query.request;
import timely.api.AuthenticatedRequest;
public class AggregatorsRequest extends AuthenticatedRequest {
@Override
public String toString() {
return "";
}
}
@@ -1,39 +0,0 @@
package timely.api.query.request;
import timely.api.Request;
public class BasicAuthLoginRequest implements Request {
private String username;
private 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 void validate() {
Request.super.validate();
if (username == null || password == null) {
throw new IllegalArgumentException("Username and password are required.");
}
}
@Override
public String toString() {
return "Username: " + username;
}
}
@@ -1,7 +0,0 @@
package timely.api.query.request;
import timely.api.AuthenticatedRequest;
public class MetricsRequest extends AuthenticatedRequest {
}
@@ -1,474 +0,0 @@
package timely.api.query.request;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import timely.api.AuthenticatedRequest;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class QueryRequest extends AuthenticatedRequest {
public static class RateOption {
private boolean counter = false;
private long counterMax = Long.MAX_VALUE;
private long resetValue = 0;
public RateOption() {
}
public RateOption(String options) {
String[] parts = options.split(",");
if (parts.length == 1) {
if (!parts[0].equals("")) {
this.counter = Boolean.valueOf(parts[0]);
}
}
if (parts.length == 2) {
if (!parts[1].equals("")) {
this.counterMax = Long.parseLong(parts[1]);
}
}
if (parts.length == 3) {
if (!parts[2].equals("")) {
this.resetValue = Long.parseLong(parts[2]);
}
}
}
public boolean isCounter() {
return counter;
}
public void setCounter(boolean counter) {
this.counter = counter;
}
public long getCounterMax() {
return counterMax;
}
public void setCounterMax(long counterMax) {
this.counterMax = counterMax;
}
public long getResetValue() {
return resetValue;
}
public void setResetValue(long resetValue) {
this.resetValue = resetValue;
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("counter", counter);
tsb.append("counterMax", counterMax);
tsb.append("resetValue", resetValue);
return tsb.toString();
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(counter);
hcb.append(counterMax);
hcb.append(resetValue);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return false;
}
if (obj instanceof RateOption) {
RateOption other = (RateOption) obj;
EqualsBuilder eq = new EqualsBuilder();
eq.append(this.counter, other.counter);
eq.append(this.counterMax, other.counterMax);
eq.append(this.resetValue, other.resetValue);
return eq.isEquals();
} else {
return false;
}
}
}
public static class Filter {
private String type;
private String tagk;
private String filter;
private boolean groupBy = false;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTagk() {
return tagk;
}
public void setTagk(String tagk) {
this.tagk = tagk;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
public boolean isGroupBy() {
return groupBy;
}
public void setGroupBy(boolean groupBy) {
this.groupBy = groupBy;
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("tagk", tagk);
tsb.append("type", type);
tsb.append("filter", filter);
tsb.append("groupBy", groupBy);
return tsb.toString();
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(filter);
hcb.append(tagk);
hcb.append(filter);
hcb.append(groupBy);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return false;
}
if (obj instanceof Filter) {
Filter other = (Filter) obj;
EqualsBuilder eq = new EqualsBuilder();
eq.append(this.tagk, other.tagk);
eq.append(this.type, other.type);
eq.append(this.filter, other.filter);
eq.append(this.groupBy, other.groupBy);
return eq.isEquals();
} else {
return false;
}
}
}
public static class SubQuery {
private String aggregator;
private String metric;
private boolean rate = false;
private RateOption rateOptions = null;
private Optional<String> downsample = Optional.empty();
private Map<String, String> tags = new HashMap<>();
private Collection<Filter> filters = new ArrayList<>();
private Collection<String> tsuids = new ArrayList<>();
public String getAggregator() {
return aggregator;
}
public void setAggregator(String aggregator) {
this.aggregator = aggregator;
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public boolean isRate() {
return rate;
}
public void setRate(boolean rate) {
this.rate = rate;
}
public RateOption getRateOptions() {
return rateOptions;
}
public void setRateOptions(RateOption rateOptions) {
this.rateOptions = rateOptions;
}
public Optional<String> getDownsample() {
return downsample;
}
public void setDownsample(Optional<String> downsample) {
this.downsample = downsample;
}
public Map<String, String> getTags() {
return tags;
}
public void setTags(Map<String, String> tags) {
this.tags = tags;
}
public void addTag(String key, String value) {
this.tags.put(key, value);
}
public Collection<Filter> getFilters() {
return filters;
}
public void setFilters(Collection<Filter> filters) {
this.filters = filters;
}
public void addFilter(Filter f) {
this.filters.add(f);
}
public Collection<String> getTsuids() {
return tsuids;
}
public void setTsuids(Collection<String> tsuids) {
this.tsuids = tsuids;
}
@JsonIgnore
public boolean isTsuidQuery() {
return (this.tsuids.size() > 0);
}
public void addTsuid(String tsuid) {
this.tsuids.add(tsuid);
}
@JsonIgnore
public boolean isMetricQuery() {
return (null != this.metric);
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("aggregator", aggregator);
tsb.append("metric", metric);
tsb.append("rate", tags);
tsb.append("rateOptions", rateOptions);
tsb.append("downsample", downsample);
tsb.append("tags", tags);
tsb.append("filters", filters);
tsb.append("tsuids", tsuids);
return tsb.toString();
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(aggregator);
hcb.append(downsample);
hcb.append(filters);
hcb.append(metric);
hcb.append(rate);
hcb.append(rateOptions);
hcb.append(tags);
hcb.append(tsuids);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return false;
}
if (obj instanceof SubQuery) {
SubQuery other = (SubQuery) obj;
EqualsBuilder eq = new EqualsBuilder();
eq.append(this.aggregator, other.aggregator);
eq.append(this.downsample, other.downsample);
eq.append(this.filters, other.filters);
eq.append(this.metric, other.metric);
eq.append(this.rate, other.rate);
eq.append(this.rateOptions, other.rateOptions);
eq.append(this.tags, other.tags);
eq.append(this.tsuids, other.tsuids);
return eq.isEquals();
} else {
return false;
}
}
}
private long start = 0;
private long end = System.currentTimeMillis();
private Collection<SubQuery> queries = new ArrayList<>();
private boolean noAnnotations = false;
private boolean globalAnnotations = false;
private boolean msResolution = false;
private boolean showSummary = false;
private boolean showQuery = false;
private boolean delete = false;
public boolean isGlobalAnnotations() {
return globalAnnotations;
}
public void setGlobalAnnotations(boolean globalAnnotations) {
this.globalAnnotations = globalAnnotations;
}
public boolean isShowQuery() {
return showQuery;
}
public void setShowQuery(boolean showQuery) {
this.showQuery = showQuery;
}
public boolean isMsResolution() {
return msResolution;
}
public void setMsResolution(boolean msResolution) {
this.msResolution = msResolution;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
public Collection<SubQuery> getQueries() {
return queries;
}
public void setQueries(Collection<SubQuery> queries) {
this.queries = queries;
}
public void addQuery(SubQuery query) {
this.queries.add(query);
}
@Override
public void validate() {
super.validate();
if (queries.size() == 0) {
throw new IllegalArgumentException("No query specified.");
}
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("start", start);
tsb.append("end", end);
tsb.append("queries", queries);
tsb.append("noAnnotations", noAnnotations);
tsb.append("globalAnnotations", globalAnnotations);
tsb.append("msResolution", msResolution);
tsb.append("showSummary", showSummary);
tsb.append("showQuery", showQuery);
tsb.append("delete", delete);
return tsb.toString();
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(start);
hcb.append(end);
hcb.append(queries);
hcb.append(noAnnotations);
hcb.append(globalAnnotations);
hcb.append(msResolution);
hcb.append(showSummary);
hcb.append(showQuery);
hcb.append(delete);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return false;
}
if (obj instanceof QueryRequest) {
QueryRequest other = (QueryRequest) obj;
EqualsBuilder eq = new EqualsBuilder();
eq.append(this.start, other.start);
eq.append(this.end, other.end);
eq.append(this.queries, other.queries);
eq.append(this.noAnnotations, other.noAnnotations);
eq.append(this.globalAnnotations, other.globalAnnotations);
eq.append(this.msResolution, other.msResolution);
eq.append(this.showSummary, other.showSummary);
eq.append(this.showQuery, other.showQuery);
eq.append(this.delete, other.delete);
return eq.isEquals();
} else {
return false;
}
}
}
@@ -1,90 +0,0 @@
package timely.api.query.request;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import timely.api.AuthenticatedRequest;
import timely.api.model.Tag;
import com.fasterxml.jackson.annotation.JsonProperty;
public class SearchLookupRequest extends AuthenticatedRequest {
@JsonProperty("metric")
private String query;
@JsonProperty("limit")
private int limit = 25;
@JsonProperty("tags")
private Collection<Tag> tags = new ArrayList<>();
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public Collection<Tag> getTags() {
return tags;
}
public void setTags(Collection<Tag> tags) {
this.tags = tags;
}
public void addTag(Tag tag) {
this.tags.add(tag);
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("query", query);
tsb.append("limit", limit);
tsb.append("tags", tags);
return tsb.toString();
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(query);
hcb.append(limit);
hcb.append(tags);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return false;
}
if (obj instanceof SearchLookupRequest) {
SearchLookupRequest other = (SearchLookupRequest) obj;
EqualsBuilder eq = new EqualsBuilder();
eq.append(this.query, other.query);
eq.append(this.limit, other.limit);
eq.append(this.tags, other.tags);
return eq.isEquals();
} else {
return false;
}
}
}
@@ -1,67 +0,0 @@
package timely.api.query.request;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.builder.ToStringBuilder;
import timely.api.AuthenticatedRequest;
import com.fasterxml.jackson.annotation.JsonProperty;
public class SuggestRequest extends AuthenticatedRequest {
private static List<String> validTypes = new ArrayList<>();
static {
validTypes.add("metrics");
validTypes.add("tagk");
validTypes.add("tagv");
}
private String type;
@JsonProperty("q")
private Optional<String> query = Optional.empty();
private int max = 25;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Optional<String> getQuery() {
return query;
}
public void setQuery(Optional<String> query) {
this.query = query;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public void validate() {
super.validate();
if (!validTypes.contains(this.type)) {
throw new IllegalArgumentException("Type is not valid");
}
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("query", query);
tsb.append("type", type);
tsb.append("max", max);
return tsb.toString();
}
}
@@ -1,7 +0,0 @@
package timely.api.query.request;
import timely.api.Request;
public class X509LoginRequest implements Request {
}
@@ -1,40 +0,0 @@
package timely.api.query.response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(using = AggregatorsResponse.class)
public class AggregatorsResponse extends JsonSerializer<AggregatorsResponse> {
private List<String> aggregators = new ArrayList<>();
public List<String> getAggregators() {
return aggregators;
}
public void setAggregators(List<String> aggregators) {
this.aggregators = aggregators;
}
public void addAggregator(String agg) {
this.aggregators.add(agg);
}
@Override
public void serialize(AggregatorsResponse value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartArray();
for (String a : value.getAggregators()) {
gen.writeString(a);
}
gen.writeEndArray();
}
}
@@ -1,154 +0,0 @@
package timely.api.query.response;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang3.StringUtils;
import timely.Configuration;
import timely.api.model.Meta;
import timely.store.MetaCache;
import timely.store.MetaCacheFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class MetricsResponse {
private static final String DOCTYPE = "<!DOCTYPE html>\n";
private static final String META = "<meta charset=\"UTF-8\">\n";
private static final String HTML_START = "<html>\n";
private static final String HTML_END = "</html>\n";
private static final String HEAD_START = "<head>\n";
private static final String HEAD_END = "</head>\n";
private static final String TITLE = "<title>Timely Metric Information</title>\n";
private static final String BODY_START = "<body>\n";
private static final String BODY_END = "</body>\n";
private static final String HEADER_START = "<header>\n";
private static final String HEADER_END = "</header>\n";
private static final String HEADER_CONTENT = "<h2>Timely Metric Information</h2>\n<p>This page represents the metrics that Timely has in its internal cache, which may not be the entire set of available metrics depending on the configuration. The following tags are not shown here but are available for query: ";
private static final String TABLE_START = "<table>\n";
private static final String TABLE_END = "</table>\n";
private static final String TR_START = "<tr>\n";
private static final String TR_END = "</tr>\n";
private static final String TH_START = "<th>";
private static final String TH_END = "</th>\n";
private static final String TD_START = "<td>";
private static final String TD_END = "</td>\n";
private Set<String> ignoredTags = Collections.emptySet();
private Configuration conf = null;
public MetricsResponse() {
}
public MetricsResponse(Configuration conf) {
String tagsToIgnore = conf.get(Configuration.METRICS_IGNORED_TAGS);
if (!StringUtils.isEmpty(tagsToIgnore)) {
this.ignoredTags = new HashSet<>();
for (String tag : tagsToIgnore.split(",")) {
ignoredTags.add(tag);
}
}
this.conf = conf;
}
public StringBuilder generateHtml() {
final MetaCache cache = MetaCacheFactory.getCache(conf);
TreeSet<Meta> tree = new TreeSet<>();
cache.forEach(m -> tree.add(m));
final StringBuilder b = new StringBuilder();
b.append(DOCTYPE);
b.append(HTML_START);
b.append(HEAD_START);
b.append(META);
b.append(TITLE);
b.append(HEAD_END);
b.append(HEADER_START);
b.append(HEADER_CONTENT).append(ignoredTags.toString()).append("</p>\n");
b.append(HEADER_END);
b.append(BODY_START);
b.append(TABLE_START);
b.append(TR_START);
b.append(TH_START).append("Metric").append(TH_END);
b.append(TH_START).append("Available Tags").append(TH_END);
b.append(TR_END);
String prevMetric = null;
StringBuilder tags = new StringBuilder();
for (Meta m : tree) {
if (prevMetric != null && !m.getMetric().equals(prevMetric)) {
b.append(TR_START);
b.append(TD_START).append(prevMetric).append(TD_END);
b.append(TD_START).append(tags.toString()).append(TD_END);
b.append(TR_END);
prevMetric = m.getMetric();
tags.delete(0, tags.length());
}
if (prevMetric == null) {
prevMetric = m.getMetric();
}
if (!(this.ignoredTags.contains(m.getTagKey()))) {
tags.append(m.getTagKey()).append("=").append(m.getTagValue()).append(" ");
}
}
b.append(TR_START);
b.append(TD_START).append(prevMetric).append(TD_END);
b.append(TD_START).append(tags.toString()).append(TD_END);
b.append(TR_END);
tags = null;
prevMetric = null;
b.append(TABLE_END);
b.append(BODY_END);
b.append(HTML_END);
return b;
}
public String generateJson(final ObjectMapper mapper) throws JsonProcessingException {
// map non-ignored metrics to their list of tags
final MetaCache cache = MetaCacheFactory.getCache(conf);
Map<String, List<JsonNode>> metricTagMap = new HashMap<>();
cache.forEach(m -> {
if (!metricTagMap.containsKey(m.getMetric())) {
metricTagMap.put(m.getMetric(), new ArrayList<>());
}
if (!this.ignoredTags.contains(m.getTagKey())) {
metricTagMap.get(m.getMetric()).add(createTag(m, mapper));
}
});
ObjectNode metricsNode = mapper.createObjectNode();
ArrayNode metricsArray = mapper.createArrayNode();
metricTagMap.forEach((metric, tags) -> {
metricsArray.add(createMetric(metric, tags, mapper));
});
metricsNode.put("metrics", metricsArray);
return mapper.writeValueAsString(metricsNode);
}
private JsonNode createMetric(String metric, List<JsonNode> tags, ObjectMapper mapper) {
ObjectNode metricNode = mapper.createObjectNode();
metricNode.put("metric", metric);
ArrayNode tagsArray = mapper.createArrayNode();
tags.forEach(tagsArray::add);
metricNode.put("tags", tagsArray);
return metricNode;
}
private JsonNode createTag(Meta meta, ObjectMapper mapper) {
ObjectNode tagNode = mapper.createObjectNode();
tagNode.put("key", meta.getTagKey());
tagNode.put("value", meta.getTagValue());
return tagNode;
}
}
@@ -1,72 +0,0 @@
package timely.api.query.response;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.lang3.builder.ToStringBuilder;
public class QueryResponse {
private String metric;
private Map<String, String> tags = new HashMap<>();
private List<String> aggregatedTags = new ArrayList<>();
private Map<String, Object> dps = new TreeMap<>();
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public Map<String, String> getTags() {
return tags;
}
public void setTags(Map<String, String> tags) {
this.tags = tags;
}
public void putTag(String key, String value) {
this.tags.put(key, value);
}
public List<String> getAggregatedTags() {
return aggregatedTags;
}
public void setAggregatedTags(List<String> aggregatedTags) {
this.aggregatedTags = aggregatedTags;
}
public void addAggregatedTag(String tag) {
this.aggregatedTags.add(tag);
}
public Map<String, Object> getDps() {
return dps;
}
public void setDps(Map<String, Object> dps) {
this.dps = dps;
}
public void putDps(String key, Object value) {
this.dps.put(key, value);
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("metric", this.metric);
tsb.append("tags", this.tags);
tsb.append("aggregatedTags", this.aggregatedTags);
tsb.append("dps", this.dps);
return tsb.toString();
}
}
@@ -1,186 +0,0 @@
package timely.api.query.response;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class SearchLookupResponse {
public static class Result {
private Map<String, String> tags = new HashMap<>();
private String metric;
private String tsuid;
public Map<String, String> getTags() {
return tags;
}
public void setTags(Map<String, String> tags) {
this.tags = tags;
}
public void putTag(String key, String value) {
this.tags.put(key, value);
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public String getTsuid() {
return tsuid;
}
public void setTsuid(String tsuid) {
this.tsuid = tsuid;
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof Result) {
Result other = (Result) obj;
EqualsBuilder builder = new EqualsBuilder();
builder.append(this.metric, other.metric);
builder.append(this.tsuid, other.tsuid);
builder.append(this.tags, other.tags);
return builder.isEquals();
} else {
return false;
}
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(this.metric);
hcb.append(this.tsuid);
hcb.append(this.tags);
return hcb.toHashCode();
}
}
private String type;
private String metric;
private Map<String, String> tags = new HashMap<>();
private int limit;
private int time;
private int totalResults;
private List<Result> results = new ArrayList<>();
private int startIndex = 0; // Does not change, here for serialization
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
public Map<String, String> getTags() {
return tags;
}
public void setTags(Map<String, String> tags) {
this.tags = tags;
}
public void putTag(String key, String value) {
this.tags.put(key, value);
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public int getTotalResults() {
return totalResults;
}
public void setTotalResults(int totalResults) {
this.totalResults = totalResults;
}
public List<Result> getResults() {
return results;
}
public void setResults(List<Result> results) {
this.results = results;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof SearchLookupResponse) {
SearchLookupResponse other = (SearchLookupResponse) obj;
EqualsBuilder builder = new EqualsBuilder();
builder.append(this.type, other.type);
builder.append(this.metric, other.metric);
builder.append(this.time, other.time);
builder.append(this.totalResults, other.totalResults);
builder.append(this.tags, other.tags);
builder.append(this.results, other.results);
builder.append(this.startIndex, other.startIndex);
builder.append(this.limit, other.limit);
return builder.isEquals();
} else {
return false;
}
}
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(this.type);
hcb.append(this.metric);
hcb.append(this.time);
hcb.append(this.totalResults);
hcb.append(this.tags);
hcb.append(this.results);
hcb.append(this.startIndex);
hcb.append(this.limit);
return hcb.toHashCode();
}
}
@@ -1,40 +0,0 @@
package timely.api.query.response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(using = SuggestResponse.class)
public class SuggestResponse extends JsonSerializer<SuggestResponse> {
private List<String> suggestions = new ArrayList<>();
public List<String> getSuggestions() {
return suggestions;
}
public void setSuggestions(List<String> suggestions) {
this.suggestions = suggestions;
}
public void addSuggestion(String suggestion) {
this.suggestions.add(suggestion);
}
@Override
public void serialize(SuggestResponse value, JsonGenerator gen, SerializerProvider serializers) throws IOException,
JsonProcessingException {
gen.writeStartArray();
for (String s : value.getSuggestions()) {
gen.writeString(s);
}
gen.writeEndArray();
}
}
@@ -1,46 +0,0 @@
package timely.api.query.response;
import java.util.HashMap;
import java.util.Map;
public class TimelyException extends Exception {
private static final long serialVersionUID = 1L;
private int code = 500;
private String details = null;
private Map<String, String> responseHeaders = new HashMap<>();
public TimelyException(int code, String message, String details) {
this(code, message, details, null);
}
public TimelyException(int code, String message, String details, Throwable error) {
super(message, error);
this.code = code;
this.details = details;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public void addResponseHeader(String name, String value) {
this.responseHeaders.put(name, value);
}
public Map<String, String> getResponseHeaders() {
return this.responseHeaders;
}
}
@@ -1,61 +0,0 @@
package timely.auth;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import timely.Configuration;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class AuthCache {
private static Cache<String, Authentication> CACHE = null;
private static Long sessionMaxAge = -1L;
/**
* For tests only
*/
public static void resetSessionMaxAge() {
sessionMaxAge = -1L;
}
public static void setSessionMaxAge(Configuration config) {
if (-1 != sessionMaxAge) {
throw new IllegalStateException("Cache session max age already configured.");
}
sessionMaxAge = Long.parseLong(config.get(Configuration.SESSION_MAX_AGE));
}
public static Cache<String, Authentication> getCache() {
if (-1 == sessionMaxAge) {
throw new IllegalStateException("Cache session max age not configured.");
}
if (null == CACHE) {
CACHE = Caffeine.newBuilder().expireAfterAccess(sessionMaxAge, TimeUnit.SECONDS).build();
}
return CACHE;
}
public static Authorizations getAuthorizations(String sessionId) {
if (!StringUtils.isEmpty(sessionId)) {
Collection<? extends GrantedAuthority> authorities = CACHE.asMap().get(sessionId).getAuthorities();
String[] auths = new String[authorities.size()];
final AtomicInteger i = new AtomicInteger(0);
authorities.forEach(a -> {
auths[i.getAndIncrement()] = a.getAuthority();
});
return new Authorizations(auths);
} else {
throw new IllegalArgumentException("session id cannot be null");
}
}
}
@@ -1,37 +0,0 @@
package timely.auth;
import java.security.cert.X509Certificate;
import java.util.ServiceConfigurationError;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
public class AuthenticationService {
private static ApplicationContext springContext = null;
private static AuthenticationManager authManager = null;
private static SubjectDnX509PrincipalExtractor x509 = null;
static {
try {
springContext = new ClassPathXmlApplicationContext("security.xml");
authManager = (AuthenticationManager) springContext.getBean("authenticationManager");
x509 = (SubjectDnX509PrincipalExtractor) springContext.getBean("x509PrincipalExtractor");
} catch (BeansException e) {
throw new ServiceConfigurationError("Error setting up Authentication objects: " + e.getMessage(),
e.getRootCause());
}
}
public static AuthenticationManager getAuthenticationManager() {
return authManager;
}
public static String extractDN(X509Certificate clientCert) throws Exception {
return (String) x509.extractPrincipal(clientCert);
}
}
@@ -1,86 +0,0 @@
package timely.auth;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class UserDetailsService
implements
org.springframework.security.core.userdetails.AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
private HashMap<String, List<String>> users;
public Map<String, List<String>> getUsers() {
return users;
}
public void setUsers(LinkedHashMap<String, List<String>> users) {
this.users = new HashMap<>(users);
}
@Override
public UserDetails loadUserDetails(final PreAuthenticatedAuthenticationToken token)
throws UsernameNotFoundException {
// Determine if the user is allowed to access the system, if not throw
// UsernameNotFoundException
String username = token.getName().toString();
if (!users.containsKey(username)) {
throw new UsernameNotFoundException(username + " not configured.");
}
// If allowed, populate the user details object with the authorities for
// the user.
return new UserDetails() {
private static final long serialVersionUID = 1L;
@Override
public Collection<SimpleGrantedAuthority> getAuthorities() {
final Collection<SimpleGrantedAuthority> auths = new ArrayList<>();
users.get(username).forEach(a -> {
auths.add(new SimpleGrantedAuthority(a));
});
return auths;
}
@Override
public String getPassword() {
return "";
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
};
}
}
@@ -1,43 +0,0 @@
package timely.auth;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.accumulo.core.security.ColumnVisibility;
import timely.Configuration;
import java.util.concurrent.TimeUnit;
public class VisibilityCache {
private static volatile Cache<String, ColumnVisibility> CACHE = null;
/** Used for testing */
public static synchronized void init() {
if (CACHE == null) {
long defaultExpiration = Configuration.VISIBILITY_EXPIRATION_DEFAULT;
int initialCapacity = Configuration.VISIBILITY_CACHE_INITIAL_CAPACITY_DEFAULT;
long maxCapacity = Configuration.VISIBILITY_CACHE_MAX_CAPACITY_DEFAULT;
CACHE = Caffeine.newBuilder().expireAfterAccess(defaultExpiration, TimeUnit.MINUTES)
.initialCapacity(initialCapacity).maximumSize(maxCapacity).build();
}
}
public static synchronized void init(Configuration config) {
if (CACHE == null) {
long expireMinutes = Long.parseLong(config.get(Configuration.VISIBILITY_CACHE_EXPIRATION));
int initialCapacity = Integer.parseInt(config.get(Configuration.VISIBILITY_CACHE_INITIAL_CAPACITY));
long maxCapacity = Long.parseLong(config.get(Configuration.VISIBILITY_CACHE_MAX_CAPACITY));
CACHE = Caffeine.newBuilder().expireAfterAccess(expireMinutes, TimeUnit.MINUTES)
.initialCapacity(initialCapacity).maximumSize(maxCapacity).build();
}
}
public static ColumnVisibility getColumnVisibility(String visibilityString) {
return CACHE.get(visibilityString, key -> generateNormalizedVisibility(key));
}
private static final ColumnVisibility generateNormalizedVisibility(String visibilityString) {
// it would be nice if Accumulo would make it easier to do this.
return new ColumnVisibility(new ColumnVisibility(visibilityString).flatten());
}
}
@@ -1,10 +0,0 @@
package timely.netty;
public class Constants {
public static final String ERR_WRITING_RESPONSE = "Error writing response to pipeline: {}";
public static final String HTML_TYPE = "text/html";
public static final String JSON_TYPE = "application/json";
public static final String LOG_RETURNING_RESPONSE = "Returning response {}";
public static final String COOKIE_NAME = "TSESSIONID";
}
@@ -1,47 +0,0 @@
package timely.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import timely.api.query.request.AggregatorsRequest;
import timely.api.query.response.AggregatorsResponse;
import timely.netty.Constants;
import timely.sample.aggregators.Avg;
import timely.sample.aggregators.Count;
import timely.sample.aggregators.Dev;
import timely.sample.aggregators.Max;
import timely.sample.aggregators.Min;
import timely.sample.aggregators.Sum;
import timely.util.JsonUtil;
public class HttpAggregatorsRequestHandler extends SimpleChannelInboundHandler<AggregatorsRequest> implements
TimelyHttpHandler {
private static final AggregatorsResponse RESPONSE = new AggregatorsResponse();
static {
RESPONSE.addAggregator(Avg.class.getSimpleName().toLowerCase());
RESPONSE.addAggregator(Dev.class.getSimpleName().toLowerCase());
RESPONSE.addAggregator(Max.class.getSimpleName().toLowerCase());
RESPONSE.addAggregator(Min.class.getSimpleName().toLowerCase());
RESPONSE.addAggregator(Sum.class.getSimpleName().toLowerCase());
RESPONSE.addAggregator(Count.class.getSimpleName().toLowerCase());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, AggregatorsRequest msg) throws Exception {
byte[] buf = JsonUtil.getObjectMapper().writeValueAsBytes(RESPONSE);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(buf));
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
}
@@ -1,71 +0,0 @@
package timely.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import timely.Configuration;
import timely.api.query.request.MetricsRequest;
import timely.api.query.response.MetricsResponse;
import timely.netty.Constants;
import timely.util.JsonUtil;
public class HttpMetricsRequestHandler extends SimpleChannelInboundHandler<MetricsRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpMetricsRequestHandler.class);
private Configuration conf = null;
public HttpMetricsRequestHandler(Configuration conf) {
this.conf = conf;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, MetricsRequest msg) throws Exception {
MetricsResponse r = new MetricsResponse(conf);
String acceptValue = msg.getRequestHeaders().get(Names.ACCEPT);
MediaType negotiatedType = MediaType.TEXT_HTML;
if (null != acceptValue) {
List<MediaType> requestedTypes = MediaType.parseMediaTypes(acceptValue);
MediaType.sortBySpecificityAndQuality(requestedTypes);
LOG.trace("Acceptable response types: {}", MediaType.toString(requestedTypes));
for (MediaType t : requestedTypes) {
if (t.includes(MediaType.TEXT_HTML)) {
negotiatedType = MediaType.TEXT_HTML;
LOG.trace("{} allows HTML", t.toString());
break;
}
if (t.includes(MediaType.APPLICATION_JSON)) {
negotiatedType = MediaType.APPLICATION_JSON;
LOG.trace("{} allows JSON", t.toString());
break;
}
}
}
byte[] buf = null;
Object responseType = Constants.HTML_TYPE;
if (negotiatedType.equals(MediaType.APPLICATION_JSON)) {
buf = r.generateJson(JsonUtil.getObjectMapper()).getBytes(StandardCharsets.UTF_8);
responseType = Constants.JSON_TYPE;
} else {
buf = r.generateHtml().toString().getBytes(StandardCharsets.UTF_8);
}
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(buf));
response.headers().set(Names.CONTENT_TYPE, responseType);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
}
@@ -1,327 +0,0 @@
package timely.netty.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.Configuration;
import timely.api.AuthenticatedRequest;
import timely.api.Request;
import timely.api.model.Tag;
import timely.api.query.request.AggregatorsRequest;
import timely.api.query.request.BasicAuthLoginRequest;
import timely.api.query.request.MetricsRequest;
import timely.api.query.request.QueryRequest;
import timely.api.query.request.QueryRequest.Filter;
import timely.api.query.request.QueryRequest.RateOption;
import timely.api.query.request.QueryRequest.SubQuery;
import timely.api.query.request.SearchLookupRequest;
import timely.api.query.request.SuggestRequest;
import timely.api.query.request.X509LoginRequest;
import timely.netty.Constants;
import timely.api.query.response.TimelyException;
import timely.auth.AuthCache;
import timely.util.JsonUtil;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
public class HttpQueryDecoder extends MessageToMessageDecoder<FullHttpRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpQueryDecoder.class);
private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}";
private static final String LOG_PARSED_REQUEST = "Parsed request {}";
private static final String API_AGG = "/api/aggregators";
private static final String API_QUERY = "/api/query";
private static final String API_SEARCH_LOOKUP = "/api/search/lookup";
private static final String API_SUGGEST = "/api/suggest";
private static final String API_METRICS = "/api/metrics";
private static final String API_LOGIN = "/login";
private static final String NO_AUTHORIZATIONS = "";
private boolean anonymousAccessAllowed = false;
public HttpQueryDecoder(Configuration config) {
this.anonymousAccessAllowed = config.getBoolean(Configuration.ALLOW_ANONYMOUS_ACCESS);
}
@Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
LOG.trace(LOG_RECEIVED_REQUEST, msg);
final StringBuilder buf = new StringBuilder();
msg.headers().getAll(Names.COOKIE).forEach(h -> {
ServerCookieDecoder.STRICT.decode(h).forEach(c -> {
if (c.name().equals(Constants.COOKIE_NAME)) {
if (buf.length() == 0) {
buf.append(c.value());
}
}
});
});
String sId = buf.toString();
if (sId.length() == 0 && this.anonymousAccessAllowed) {
sId = NO_AUTHORIZATIONS;
} else if (sId.length() == 0) {
sId = null;
}
final String sessionId = sId;
LOG.trace("SessionID: " + sessionId);
if (msg.getMethod().equals(HttpMethod.GET)) {
final Collection<Request> r = parseURI(msg.getUri());
r.forEach(req -> {
if (req instanceof AuthenticatedRequest && sessionId != null) {
((AuthenticatedRequest) req).setSessionId(sessionId);
((AuthenticatedRequest) req).addHeaders(msg.headers().entries());
}
LOG.trace(LOG_PARSED_REQUEST, req);
req.validate();
out.add(req);
});
} else if (msg.getMethod().equals(HttpMethod.POST)) {
final Collection<Request> r = parsePOST(msg.getUri(), msg.content().toString(StandardCharsets.UTF_8));
r.forEach(req -> {
if (req instanceof AuthenticatedRequest && sessionId != null) {
((AuthenticatedRequest) req).setSessionId(sessionId);
((AuthenticatedRequest) req).addHeaders(msg.headers().entries());
}
LOG.trace(LOG_PARSED_REQUEST, req);
req.validate();
out.add(req);
});
} else {
TimelyException e = new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(),
"unhandled method type", "");
e.addResponseHeader(Names.ALLOW, HttpMethod.GET.name() + "," + HttpMethod.POST.name());
LOG.warn("Unhandled HTTP request type {}", msg.getMethod());
throw e;
}
for (Object r : out) {
try {
enforceAccess((Request) r);
} catch (Exception e) {
out.clear();
throw e;
}
}
}
private void enforceAccess(Request r) throws Exception {
if (!this.anonymousAccessAllowed && (r instanceof AuthenticatedRequest)) {
AuthenticatedRequest ar = (AuthenticatedRequest) r;
if (StringUtils.isEmpty(ar.getSessionId())) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Anonymous access is disabled, log in first");
}
if (!AuthCache.getCache().asMap().containsKey(ar.getSessionId())) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Unknown session id was submitted, log in again");
}
}
}
public Collection<Request> parsePOST(String uri, String content) throws JsonParseException, JsonMappingException,
IOException, TimelyException {
LOG.trace("Recevied POST content: {}", content);
final List<Request> requests = new ArrayList<>();
final QueryStringDecoder decoder = new QueryStringDecoder(uri);
if (decoder.path().equals(API_AGG)) {
// no Content
requests.add(new AggregatorsRequest());
} else if (decoder.path().equals(API_QUERY)) {
requests.add(JsonUtil.getObjectMapper().readValue(content, QueryRequest.class));
} else if (decoder.path().equals(API_SEARCH_LOOKUP)) {
requests.add(JsonUtil.getObjectMapper().readValue(content, SearchLookupRequest.class));
} else if (decoder.path().equals(API_SUGGEST)) {
requests.add(JsonUtil.getObjectMapper().readValue(content, SuggestRequest.class));
} else if (decoder.path().equals(API_LOGIN)) {
requests.add(JsonUtil.getObjectMapper().readValue(content, BasicAuthLoginRequest.class));
} else {
throw new TimelyException(HttpResponseStatus.NOT_FOUND.code(), "Unhandled query uri: " + decoder.path(), "");
}
LOG.trace("Parsed POST request {}", requests);
return requests;
}
public Collection<Request> parseURI(String uri) throws TimelyException {
final List<Request> requests = new ArrayList<>();
final QueryStringDecoder decoder = new QueryStringDecoder(uri);
if (decoder.path().equals(API_AGG)) {
requests.add(new AggregatorsRequest());
} else if (decoder.path().equals(API_QUERY)) {
final QueryRequest query = new QueryRequest();
query.setStart(Long.parseLong(decoder.parameters().get("start").get(0)));
if (decoder.parameters().containsKey("end")) {
query.setEnd(Long.parseLong(decoder.parameters().get("end").get(0)));
}
if (decoder.parameters().containsKey("m")) {
decoder.parameters()
.get("m")
.forEach(m -> {
final SubQuery sub = new SubQuery();
final String[] mParts = m.split(":");
if (mParts.length < 2) {
throw new IllegalArgumentException("Too few parameters for metric query");
}
if (mParts.length > 5) {
throw new IllegalArgumentException("Too many parameters for metric query");
}
// Aggregator is required, it's in the first section
sub.setAggregator(mParts[0]);
// Parse the rates from the 2nd through
// next to last sections
for (int i = 1; i < mParts.length - 1; i++) {
if (mParts[i].startsWith("rate")) {
sub.setRate(true);
final RateOption options = new RateOption();
if (mParts[i].equals("rate")) {
sub.setRateOptions(options);
} else {
final String rate = mParts[i].substring(5, mParts[i].length() - 1);
final String[] rateOptions = rate.split(",");
for (int x = 0; x < rateOptions.length; x++) {
switch (x) {
case 0:
options.setCounter(rateOptions[x].endsWith("counter"));
break;
case 1:
options.setCounterMax(Long.parseLong(rateOptions[x]));
break;
case 2:
options.setResetValue(Long.parseLong(rateOptions[x]));
break;
default:
}
}
sub.setRateOptions(options);
}
} else {
// downsample
sub.setDownsample(Optional.of(mParts[i]));
}
}
// Parse metric name and tags from last
// section
final String metricAndTags = mParts[mParts.length - 1];
final int idx = metricAndTags.indexOf('{');
if (-1 == idx) {
// metric only
sub.setMetric(metricAndTags);
} else {
sub.setMetric(metricAndTags.substring(0, idx));
// Only supporting one set of {} as we
// are not supporting filters right now.
if (!metricAndTags.endsWith("}")) {
throw new IllegalArgumentException("Tag section does not end with '}'");
}
final String[] tags = metricAndTags.substring(idx).split("}");
if (tags.length > 0) {
// Process the first set of tags, which
// are groupBy
final String groupingTags = tags[0];
final String[] gTags = groupingTags.substring(1, groupingTags.length()).split(
",");
for (final String tag : gTags) {
final String[] tParts = tag.split("=");
final Filter f = new Filter();
f.setGroupBy(true);
f.setTagk(tParts[0]);
f.setFilter(tParts[1]);
sub.addFilter(f);
}
if (tags.length > 1) {
// Process the first set of tags,
// which are groupBy
final String nonGroupingTags = tags[1];
final String[] ngTags = nonGroupingTags.substring(1,
nonGroupingTags.length()).split(",");
for (final String tag : ngTags) {
final String[] tParts = tag.split("=");
sub.addTag(tParts[0], tParts[1]);
}
}
}
}
query.addQuery(sub);
});
}
if (decoder.parameters().containsKey("tsuid")) {
decoder.parameters().get("tsuid").forEach(ts -> {
final SubQuery sub = new SubQuery();
final int colon = ts.indexOf(':');
if (-1 != colon) {
sub.setAggregator(ts.substring(0, colon));
}
for (final String tsuid : ts.substring(colon + 1).split(",")) {
sub.addTsuid(tsuid);
}
query.addQuery(sub);
});
}
requests.add(query);
} else if (decoder.path().equals(API_SEARCH_LOOKUP)) {
final SearchLookupRequest search = new SearchLookupRequest();
if (!decoder.parameters().containsKey("m")) {
throw new IllegalArgumentException("m parameter is required for lookup");
}
final String m = decoder.parameters().get("m").get(0);
final int tagIdx = m.indexOf("{");
if (-1 == tagIdx) {
search.setQuery(m);
} else {
search.setQuery(m.substring(0, tagIdx));
final String[] tags = m.substring(tagIdx + 1, m.length() - 1).split(",");
for (final String tag : tags) {
final String[] tParts = tag.split("=");
final Tag t = new Tag(tParts[0], tParts[1]);
search.addTag(t);
}
}
if (decoder.parameters().containsKey("limit")) {
search.setLimit(Integer.parseInt(decoder.parameters().get("limit").get(0)));
}
requests.add(search);
} else if (decoder.path().equals(API_SUGGEST)) {
final SuggestRequest suggest = new SuggestRequest();
suggest.setType(decoder.parameters().get("type").get(0));
if (decoder.parameters().containsKey("q")) {
suggest.setQuery(Optional.of(decoder.parameters().get("q").get(0)));
}
if (decoder.parameters().containsKey("max")) {
suggest.setMax(Integer.parseInt(decoder.parameters().get("max").get(0)));
}
requests.add(suggest);
} else if (decoder.path().equals(API_METRICS)) {
requests.add(new MetricsRequest());
} else if (decoder.path().equals(API_LOGIN)) {
requests.add(new X509LoginRequest());
} else {
throw new TimelyException(HttpResponseStatus.NOT_FOUND.code(), "Unhandled query uri: " + decoder.path(), "");
}
LOG.trace("Parsed GET request {}", requests);
return requests;
}
}
@@ -1,51 +0,0 @@
package timely.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.query.request.QueryRequest;
import timely.api.query.response.TimelyException;
import timely.netty.Constants;
import timely.store.DataStore;
import timely.util.JsonUtil;
public class HttpQueryRequestHandler extends SimpleChannelInboundHandler<QueryRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpQueryRequestHandler.class);
private final DataStore dataStore;
public HttpQueryRequestHandler(DataStore dataStore) {
this.dataStore = dataStore;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, QueryRequest msg) throws Exception {
byte[] buf = null;
try {
buf = JsonUtil.getObjectMapper().writeValueAsBytes(dataStore.query(msg));
} catch (TimelyException e) {
if (e.getMessage().contains("No matching tags")) {
LOG.trace(e.getMessage());
} else {
LOG.error(e.getMessage(), e);
}
this.sendHttpError(ctx, e);
return;
}
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(buf));
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
}
@@ -1,48 +0,0 @@
package timely.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.query.request.SearchLookupRequest;
import timely.api.query.response.TimelyException;
import timely.netty.Constants;
import timely.store.DataStore;
import timely.util.JsonUtil;
public class HttpSearchLookupRequestHandler extends SimpleChannelInboundHandler<SearchLookupRequest> implements
TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpSearchLookupRequestHandler.class);
private DataStore dataStore;
public HttpSearchLookupRequestHandler(DataStore dataStore) {
this.dataStore = dataStore;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, SearchLookupRequest msg) throws Exception {
byte[] buf = null;
try {
buf = JsonUtil.getObjectMapper().writeValueAsBytes(dataStore.lookup(msg));
} catch (TimelyException e) {
LOG.error(e.getMessage(), e);
this.sendHttpError(ctx, e);
return;
}
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(buf));
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
}
@@ -1,47 +0,0 @@
package timely.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.query.request.SuggestRequest;
import timely.api.query.response.TimelyException;
import timely.netty.Constants;
import timely.store.DataStore;
import timely.util.JsonUtil;
public class HttpSuggestRequestHandler extends SimpleChannelInboundHandler<SuggestRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpSuggestRequestHandler.class);
private final DataStore dataStore;
public HttpSuggestRequestHandler(DataStore dataStore) {
this.dataStore = dataStore;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, SuggestRequest msg) throws Exception {
byte[] buf = null;
try {
buf = JsonUtil.getObjectMapper().writeValueAsBytes(dataStore.suggest(msg));
} catch (TimelyException e) {
LOG.error(e.getMessage(), e);
this.sendHttpError(ctx, e);
return;
}
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(buf));
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
}
@@ -1,28 +0,0 @@
package timely.netty.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpResponseStatus;
import timely.api.query.response.TimelyException;
public class TimelyExceptionHandler extends SimpleChannelInboundHandler<TimelyException> implements TimelyHttpHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TimelyException msg) throws Exception {
this.sendHttpError(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof TimelyException) {
this.sendHttpError(ctx, (TimelyException) cause);
} else if (null != cause.getCause() && cause.getCause() instanceof TimelyException) {
this.sendHttpError(ctx, (TimelyException) cause.getCause());
} else {
TimelyException e = new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
cause.getMessage(), "");
this.sendHttpError(ctx, e);
}
}
}
@@ -1,46 +0,0 @@
package timely.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.query.response.TimelyException;
import timely.netty.Constants;
import timely.util.JsonUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
public interface TimelyHttpHandler {
Logger LOG = LoggerFactory.getLogger(TimelyHttpHandler.class);
default void sendHttpError(ChannelHandlerContext ctx, TimelyException e) throws JsonProcessingException {
LOG.error("Error in pipeline, response code: " + e.getCode(), e);
byte[] buf = JsonUtil.getObjectMapper().writeValueAsBytes(
"ResponseCode: " + e.getCode() + " Message: " + e.getMessage());
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(e
.getCode()), Unpooled.copiedBuffer(buf));
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
e.getResponseHeaders().entrySet().forEach(entry -> response.headers().set(entry.getKey(), entry.getValue()));
// Send the error response
sendResponse(ctx, response);
}
default void sendResponse(ChannelHandlerContext ctx, Object msg) {
ChannelFuture f = ctx.writeAndFlush(msg);
LOG.trace(Constants.LOG_RETURNING_RESPONSE, msg);
if (!f.isSuccess()) {
LOG.error(Constants.ERR_WRITING_RESPONSE, f.cause());
}
}
}
@@ -1,28 +0,0 @@
package timely.netty.http.login;
import io.netty.channel.ChannelHandlerContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import timely.Configuration;
import timely.api.query.request.BasicAuthLoginRequest;
import timely.auth.AuthenticationService;
public class BasicAuthLoginRequestHandler extends TimelyLoginRequestHandler<BasicAuthLoginRequest> {
public BasicAuthLoginRequestHandler(Configuration conf) {
super(conf);
}
@Override
protected Authentication authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg)
throws AuthenticationException {
// Perform the login process using username/password
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(msg.getUsername(),
msg.getPassword());
return AuthenticationService.getAuthenticationManager().authenticate(token);
}
}
@@ -1,75 +0,0 @@
package timely.netty.http.login;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import timely.Configuration;
import timely.auth.AuthCache;
import timely.netty.Constants;
import timely.netty.http.TimelyHttpHandler;
public abstract class TimelyLoginRequestHandler<T> extends SimpleChannelInboundHandler<T> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(TimelyLoginRequestHandler.class);
private long maxAge = Configuration.SESSION_MAX_AGE_DEFAULT;
private String domain = null;
private String redirect = null;
public TimelyLoginRequestHandler(Configuration conf) {
String ma = conf.get(Configuration.SESSION_MAX_AGE);
if (null != ma) {
maxAge = Long.parseLong(ma);
}
domain = conf.get(Configuration.TIMELY_HTTP_ADDRESS);
redirect = conf.get(Configuration.GRAFANA_HTTP_ADDRESS);
}
@Override
protected final void channelRead0(ChannelHandlerContext ctx, T loginRequest) throws Exception {
try {
LOG.trace("Authenticating {}", loginRequest);
Authentication auth = authenticate(ctx, loginRequest);
LOG.trace("Authenticated {}", auth);
String sessionId = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
AuthCache.getCache().put(sessionId, auth);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.TEMPORARY_REDIRECT);
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(Names.LOCATION, redirect);
DefaultCookie cookie = new DefaultCookie(Constants.COOKIE_NAME, sessionId);
cookie.setDomain(domain);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(true);
cookie.setSecure(true);
response.headers().set(Names.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
sendResponse(ctx, response);
} catch (Exception e) {
LOG.error("Login failure", e);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.UNAUTHORIZED);
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
}
protected abstract Authentication authenticate(ChannelHandlerContext ctx, T loginRequest) throws Exception;
}
@@ -1,36 +0,0 @@
package timely.netty.http.login;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.ssl.SslHandler;
import java.security.cert.X509Certificate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import timely.Configuration;
import timely.api.query.request.X509LoginRequest;
import timely.auth.AuthenticationService;
public class X509LoginRequestHandler extends TimelyLoginRequestHandler<X509LoginRequest> {
public X509LoginRequestHandler(Configuration conf) {
super(conf);
}
@Override
protected Authentication authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest) throws Exception {
// If we are operating in 2 way SSL, then get the subjectDN from the
// client certificate and perform the login process.
SslHandler sslHandler = (SslHandler) ctx.channel().pipeline().get("ssl");
if (null != sslHandler) {
X509Certificate clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0];
String subjectDN = AuthenticationService.extractDN(clientCert);
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(subjectDN, clientCert);
return AuthenticationService.getAuthenticationManager().authenticate(token);
} else {
throw new IllegalStateException("The expected SSL handler is not in the pipeline.");
}
}
}
@@ -1,69 +0,0 @@
package timely.netty.tcp;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import timely.api.model.Metric;
import timely.api.model.Tag;
import timely.auth.VisibilityCache;
public class TcpPutDecoder extends ByteToMessageDecoder {
private static final Logger LOG = LoggerFactory.getLogger(TcpPutDecoder.class);
private static final String VISIBILITY_TAG = "viz=";
private static final int VISIBILITY_TAG_LENGTH = VISIBILITY_TAG.length();
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// put specification
//
// put <metricName> <timestamp> <value> <tagK=tagV> <tagK=tagV> ...
ByteBuf buf = in.readBytes(in.readableBytes());
if (buf == Unpooled.EMPTY_BUFFER) {
return;
}
String input = new String(buf.array(), UTF_8);
LOG.trace("Received input {}", input);
try {
String[] parts = input.split(" ");
if (parts.length == 1 && parts[0].toLowerCase().startsWith("version")) {
final ByteBuf response = ctx.alloc().buffer();
response.writeBytes("0.0.1\n".getBytes(UTF_8));
ctx.writeAndFlush(response);
return;
}
Metric put = new Metric();
put.setMetric(parts[1]);
long ts = Long.parseLong(parts[2]);
if (ts < 9999999999L) {
ts *= 1000;
}
put.setTimestamp(ts);
put.setValue(Double.valueOf(parts[3]));
String part;
for (int i = 4; i < parts.length; i++) {
part = parts[i];
if (part.startsWith(VISIBILITY_TAG) && part.length() > VISIBILITY_TAG_LENGTH) {
put.setVisibility(VisibilityCache.getColumnVisibility(part.substring(VISIBILITY_TAG_LENGTH)));
} else if (!part.isEmpty()) {
put.addTag(new Tag(parts[i]));
}
}
out.add(put);
LOG.trace("Converted {} to {}", input, put);
} catch (NumberFormatException e) {
LOG.error("Error converting input '{}' to put metric", input, e);
}
}
}
@@ -1,43 +0,0 @@
package timely.netty.tcp;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.model.Metric;
import timely.netty.Constants;
import timely.store.DataStore;
public class TcpPutHandler extends SimpleChannelInboundHandler<Metric> {
private static final Logger LOG = LoggerFactory.getLogger(TcpPutHandler.class);
private static final String LOG_ERR_MSG = "Error storing put metric: {}";
private static final String ERR_MSG = "Error storing put metric: ";
private DataStore store;
public TcpPutHandler(DataStore store) {
this.store = store;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Metric msg) throws Exception {
LOG.trace("Received PutMetric {}", msg);
try {
store.store(msg);
} 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());
}
}
}
}
@@ -1,56 +0,0 @@
package timely.sample;
import timely.sample.aggregators.Avg;
import timely.sample.aggregators.Count;
import timely.sample.aggregators.Dev;
import timely.sample.aggregators.Max;
import timely.sample.aggregators.Min;
import timely.sample.aggregators.Sum;
public interface Aggregator {
/**
* Given the current value, and the current count, incorporate the new value
* into current and return the result.
*
* @param current
* last returned value from aggregate, or zero
* @param count
* the number of items aggregated previously into current
* @param update
* a new value to incorporate
* @return the combination of update and current
*/
double aggregate(double current, int count, double update);
/**
* Compute the final value given the aggregated amount and the number of
* items in the aggregate.
*
* @param current
* the last value returned from aggregate
* @param count
* the number of values in the aggregate
* @return the combination of the current value given the count
*/
double last(double current, int count);
static Class<? extends Aggregator> getAggregator(String aggregatorName) {
switch (aggregatorName) {
case "sum":
return Sum.class;
case "max":
return Max.class;
case "dev":
return Dev.class;
case "min":
return Min.class;
case "avg":
return Avg.class;
case "count":
return Count.class;
default:
return Avg.class;
}
}
}
@@ -1,194 +0,0 @@
package timely.sample;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import timely.api.query.request.QueryRequest.RateOption;
public class Downsample implements Iterable<Sample>, Serializable {
private static Logger LOG = LoggerFactory.getLogger(Downsample.class);
private static final long serialVersionUID = 1L;
private transient Aggregator aggregator;
private final String aggregatorClassname;
private final long start;
private final long period;
private final double[] values;
private final int[] counts;
public Downsample(long start, long end, long period, Aggregator agg) {
Preconditions.checkArgument(start < end, "Start must be < end");
Preconditions.checkArgument(period >= 1, "period cannot be < 1");
Preconditions.checkNotNull(agg, "Aggregator object cannot be null");
this.start = start;
this.period = period;
this.aggregator = agg;
this.aggregatorClassname = agg.getClass().getName();
long buckets = (end - start) / period + 1;
if (buckets > Integer.MAX_VALUE) {
throw new RuntimeException("Cannot downsample to " + buckets + " items");
}
values = new double[(int) buckets];
counts = new int[(int) buckets];
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
try {
aggregator = (Aggregator) getClass().getClassLoader().loadClass(aggregatorClassname).newInstance();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public void add(long ts, double value) {
if (ts < start) {
throw new IllegalArgumentException("timestamp is out of range");
}
int bucket = (int) ((ts - start) / period);
if (bucket > values.length) {
throw new IllegalArgumentException("timestamp is out of range");
}
values[bucket] = aggregator.aggregate(values[bucket], counts[bucket], value);
counts[bucket]++;
}
public void merge(Downsample other) {
for (int i = 0; i < other.counts.length; i++) {
if (other.counts[i] > 0) {
counts[i] += other.counts[i];
values[i] = aggregator.aggregate(values[i], counts[i], other.values[i]);
}
}
}
@Override
public Iterator<Sample> iterator() {
return new Iterator<Sample>() {
int next = 0;
Sample sample = new Sample();
@Override
public boolean hasNext() {
while (next < values.length && counts[next] == 0) {
next++;
}
return next < values.length;
}
@Override
public Sample next() {
double value = aggregator.last(values[next], counts[next]);
sample.set(start + next * period, value);
next++;
return sample;
}
};
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("aggregator", this.aggregatorClassname);
tsb.append("counts", this.counts);
tsb.append("period", this.period);
tsb.append("start", this.start);
tsb.append("values", this.values);
return tsb.toString();
}
public static Downsample combine(Collection<Downsample> samples, RateOption rateOption) {
List<Downsample> sampleList = new ArrayList<>(samples.size());
if (samples.isEmpty()) {
throw new IllegalArgumentException("Empty samples to combine");
}
for (Downsample ds : samples) {
if (rateOption != null && rateOption.isCounter()) {
ds = convertCountToRate(ds, rateOption.getResetValue(), rateOption.getCounterMax());
}
ds = interpolateMissingValues(ds);
sampleList.add(ds);
}
Iterator<Downsample> iter = sampleList.iterator();
Downsample result = iter.next();
while (iter.hasNext()) {
result.merge(iter.next());
}
return result;
}
private static Downsample interpolateMissingValues(Downsample ds) {
Downsample result = new Downsample(ds.start, ds.start + ds.counts.length * ds.period, ds.period, ds.aggregator);
int lastPos = -1;
for (int i = 0; i < ds.counts.length; i++) {
if (ds.counts[i] != 0) {
result.values[i] = ds.values[i];
result.counts[i] = ds.counts[i];
if (lastPos >= 0) {
double diff = ds.values[i] - ds.values[lastPos];
for (int j = lastPos + 1; j < i; j++) {
result.values[j] = result.values[j - 1] + diff / (i - lastPos);
result.counts[j] = 1;
}
}
lastPos = i;
}
}
LOG.debug("interpolate: {}", result);
return result;
}
private static Downsample convertCountToRate(Downsample ds, long resetValue, long counterMax) {
Downsample result = new Downsample(ds.start + ds.period, ds.start + ds.counts.length * ds.period, ds.period,
ds.aggregator);
int lastPos = 0;
for (int i = 1; i < ds.counts.length; i++) {
if (ds.counts[i] != 0) {
if (((Long) counterMax).equals(((Double) ds.values[i]).longValue())) {
result.values[i - 1] = resetValue;
result.counts[i - 1] = 1;
} else {
double diff = ds.values[i] - ds.values[lastPos];
if (diff < 0) {
// wrap around formula from
// https://collectd.org/wiki/index.php/Data_source says
// to do
// the following:
// double wrapValue = (counterMax - ds.values[lastPos] +
// ds.values[i]) / (ds.period);
// When counterMax is not defined it defaults to
// Long.MAX_VALUE which leaves wrapValue
// very close to counterMax. This renders the resulting
// graph useless as it blows up the
// scale, we are going to set the value to -1. If we
// have a constantly decreasing counter,
// which in theory should not happen, then all of the
// values will be -1.
double wrapValue = -1.0;
result.values[i - 1] = wrapValue;
result.counts[i - 1] = 1;
} else {
result.values[i - 1] = diff;
result.counts[i - 1] = 1;
}
}
lastPos = i;
}
}
return result;
}
}
@@ -1,28 +0,0 @@
package timely.sample;
import io.netty.handler.codec.http.HttpResponseStatus;
import timely.api.query.response.TimelyException;
public class DownsampleFactory {
private final long start;
private final long end;
private final long period;
private final Class<? extends Aggregator> aggClass;
public DownsampleFactory(long start, long end, long period, Class<? extends Aggregator> aggClass) {
this.start = start;
this.end = end;
this.period = period;
this.aggClass = aggClass;
}
public Downsample create() throws TimelyException {
try {
return new Downsample(start, end, period, aggClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
"Error creating aggregator class: " + aggClass, e.getMessage(), e);
}
}
}
@@ -1,25 +0,0 @@
package timely.sample;
import org.apache.commons.lang3.builder.ToStringBuilder;
public class Sample {
public long timestamp;
public double value;
public Sample() {
}
public void set(long timestamp, double value) {
this.timestamp = timestamp;
this.value = value;
}
@Override
public String toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("timestamp", this.timestamp);
tsb.append("value", this.value);
return tsb.toString();
}
}
@@ -1,17 +0,0 @@
package timely.sample.aggregators;
import timely.sample.Aggregator;
public class Avg implements Aggregator {
@Override
public double aggregate(double current, int count, double update) {
return current + update;
}
@Override
public double last(double current, int count) {
return current / count;
}
}
@@ -1,17 +0,0 @@
package timely.sample.aggregators;
import timely.sample.Aggregator;
public class Count implements Aggregator {
@Override
public double aggregate(double current, int count, double update) {
return 0.;
}
@Override
public double last(double current, int count) {
return count;
}
}
@@ -1,20 +0,0 @@
package timely.sample.aggregators;
import timely.sample.Aggregator;
public class Dev implements Aggregator {
@Override
public double aggregate(double current, int count, double update) {
return current + (update * update);
}
@Override
public double last(double current, int count) {
if (count == 1) {
return Math.sqrt(current);
}
return Math.sqrt(current / (count - 1));
}
}
@@ -1,19 +0,0 @@
package timely.sample.aggregators;
import timely.sample.Aggregator;
public class Max implements Aggregator {
@Override
public double aggregate(double current, int count, double update) {
if (count == 0) {
return update;
}
return Math.max(current, update);
}
@Override
public double last(double current, int count) {
return current;
}
}
@@ -1,20 +0,0 @@
package timely.sample.aggregators;
import timely.sample.Aggregator;
public class Min implements Aggregator {
@Override
public double aggregate(double current, int count, double update) {
if (count == 0) {
return update;
}
return Math.min(current, update);
}
@Override
public double last(double current, int count) {
return current;
}
}
@@ -1,17 +0,0 @@
package timely.sample.aggregators;
import timely.sample.Aggregator;
public class Sum implements Aggregator {
@Override
public double aggregate(double current, int count, double update) {
return current + update;
}
@Override
public double last(double current, int count) {
return current;
}
}
@@ -1,123 +0,0 @@
package timely.sample.iterators;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorEnvironment;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iterators.WrappingIterator;
import org.apache.commons.lang3.StringUtils;
import timely.api.model.Metric;
import timely.api.model.Tag;
import timely.api.query.response.TimelyException;
import timely.sample.Aggregator;
import timely.sample.Downsample;
import timely.sample.DownsampleFactory;
public class DownsampleIterator extends WrappingIterator {
private static final String START = "downsample.start";
private static final String END = "downsample.end";
private static final String PERIOD = "downsample.period";
private static final String QUERYTAGS = "downsample.querytags";
private static final String AGGCLASS = "downsample.aggclass";
private DownsampleFactory factory;
private final Map<Set<Tag>, Downsample> value = new HashMap<>();
private Key last;
private final Set<String> queryTags = new HashSet<>();
@SuppressWarnings("unchecked")
@Override
public void init(SortedKeyValueIterator<Key, Value> source, Map<String, String> options, IteratorEnvironment env)
throws IOException {
super.init(source, options, env);
long start = Long.parseLong(options.get(START));
long end = Long.parseLong(options.get(END));
long period = Long.parseLong(options.get(PERIOD));
String aggClassname = options.get(AGGCLASS);
queryTags.addAll(Arrays.asList(options.get(QUERYTAGS).split(",")));
Class<?> aggClass;
try {
aggClass = this.getClass().getClassLoader().loadClass(aggClassname);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
factory = new DownsampleFactory(start, end, period, (Class<? extends Aggregator>) aggClass);
}
@Override
public boolean hasTop() {
while (super.hasTop()) {
last = super.getTopKey();
Metric metric = Metric.parse(super.getTopKey(), super.getTopValue());
Set<Tag> tags = new HashSet<Tag>(metric.getTags());
Downsample sample = value.get(tags);
if (sample == null) {
try {
value.put(tags, sample = factory.create());
} catch (TimelyException e) {
throw new RuntimeException(e);
}
}
sample.add(metric.getTimestamp(), metric.getValue());
try {
super.next();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return last != null;
}
@Override
public Key getTopKey() {
return last;
}
@Override
public Value getTopValue() {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos)) {
out.writeObject(value);
out.flush();
return new Value(bos.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void next() throws IOException {
last = null;
}
public static void setDownsampleOptions(IteratorSetting is, long start, long end, long period,
Collection<String> queryTags, String classname) {
is.addOption(START, "" + start);
is.addOption(END, "" + end);
is.addOption(PERIOD, "" + period);
is.addOption(QUERYTAGS, StringUtils.join(queryTags, ","));
is.addOption(AGGCLASS, classname);
}
@SuppressWarnings("unchecked")
public static Map<Set<Tag>, Downsample> decodeValue(Value value) throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(value.get());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Map<Set<Tag>, Downsample>) ois.readObject();
}
}
@@ -1,26 +0,0 @@
package timely.store;
import java.util.List;
import timely.api.model.Metric;
import timely.api.query.request.QueryRequest;
import timely.api.query.request.SearchLookupRequest;
import timely.api.query.request.SuggestRequest;
import timely.api.query.response.QueryResponse;
import timely.api.query.response.SearchLookupResponse;
import timely.api.query.response.SuggestResponse;
import timely.api.query.response.TimelyException;
public interface DataStore {
void store(Metric metric) throws TimelyException;
SuggestResponse suggest(SuggestRequest query) throws TimelyException;
SearchLookupResponse lookup(SearchLookupRequest msg) throws TimelyException;
List<QueryResponse> query(QueryRequest msg) throws TimelyException;
void flush() throws TimelyException;
}
@@ -1,12 +0,0 @@
package timely.store;
import timely.Configuration;
import timely.api.query.response.TimelyException;
public class DataStoreFactory {
public static DataStore create(Configuration conf, int numWriteThreads) throws TimelyException {
return new DataStoreImpl(conf, numWriteThreads);
}
}
@@ -1,707 +0,0 @@
package timely.store;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getMemoryInBytes;
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getTimeInMillis;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import org.apache.accumulo.core.client.BatchScanner;
import org.apache.accumulo.core.client.BatchWriter;
import org.apache.accumulo.core.client.BatchWriterConfig;
import org.apache.accumulo.core.client.ClientConfiguration;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.Instance;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.MutationsRejectedException;
import org.apache.accumulo.core.client.NamespaceExistsException;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.TableExistsException;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.client.ZooKeeperInstance;
import org.apache.accumulo.core.client.security.tokens.PasswordToken;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iterators.user.AgeOffFilter;
import org.apache.accumulo.core.iterators.user.RegExFilter;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.util.Pair;
import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.io.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.Configuration;
import timely.Server;
import timely.api.AuthenticatedRequest;
import timely.api.model.Meta;
import timely.api.model.Metric;
import timely.api.model.Tag;
import timely.api.query.request.QueryRequest;
import timely.api.query.request.QueryRequest.RateOption;
import timely.api.query.request.QueryRequest.SubQuery;
import timely.api.query.request.SearchLookupRequest;
import timely.api.query.request.SuggestRequest;
import timely.api.query.response.QueryResponse;
import timely.api.query.response.SearchLookupResponse;
import timely.api.query.response.SearchLookupResponse.Result;
import timely.api.query.response.SuggestResponse;
import timely.api.query.response.TimelyException;
import timely.auth.AuthCache;
import timely.sample.Aggregator;
import timely.sample.Downsample;
import timely.sample.Sample;
import timely.sample.iterators.DownsampleIterator;
import timely.util.MetaKeySet;
public class DataStoreImpl implements DataStore {
private static final Logger LOG = LoggerFactory.getLogger(DataStoreImpl.class);
private static final long METRICS_PERIOD = 30000;
private static final long DEFAULT_DOWNSAMPLE_MS = 60000;
/*
* Pair doesn't implement Comparable
*/
private static class MetricTagK extends Pair<String, String> implements Comparable<MetricTagK> {
public MetricTagK(String f, String s) {
super(f, s);
}
@Override
public int compareTo(MetricTagK o) {
int result = getFirst().compareTo(o.getFirst());
if (result != 0) {
return result;
}
return getSecond().compareTo(o.getSecond());
}
}
Configuration conf;
private final Connector connector;
private MetaCache metaCache = null;
private final AtomicLong lastCountTime = new AtomicLong(System.currentTimeMillis());
private final AtomicReference<SortedMap<MetricTagK, Integer>> metaCounts = new AtomicReference<>(new TreeMap<>());
private final String metricsTable;
private final String metaTable;
private final InternalMetrics internalMetrics = new InternalMetrics();
private final Timer internalMetricsTimer = new Timer(true);
private final int scannerThreads;
private final BatchWriterConfig bwConfig;
private final List<BatchWriter> writers = new ArrayList<>();
private final ThreadLocal<BatchWriter> metaWriter = new ThreadLocal<>();
private final ThreadLocal<BatchWriter> batchWriter = new ThreadLocal<>();
private boolean anonAccessAllowed = false;
public DataStoreImpl(Configuration conf, int numWriteThreads) throws TimelyException {
try {
final BaseConfiguration apacheConf = new BaseConfiguration();
apacheConf.setProperty("instance.name", conf.get(Configuration.INSTANCE_NAME));
apacheConf.setProperty("instance.zookeeper.host", conf.get(Configuration.ZOOKEEPERS));
final ClientConfiguration aconf = new ClientConfiguration(Collections.singletonList(apacheConf));
final Instance instance = new ZooKeeperInstance(aconf);
final byte[] passwd = conf.get(Configuration.PASSWORD).getBytes(UTF_8);
connector = instance.getConnector(conf.get(Configuration.USERNAME), new PasswordToken(passwd));
bwConfig = new BatchWriterConfig();
bwConfig.setMaxLatency(getTimeInMillis(conf.get(Configuration.MAX_LATENCY)), TimeUnit.MILLISECONDS);
bwConfig.setMaxMemory(getMemoryInBytes(conf.get(Configuration.WRITE_BUFFER_SIZE)) / numWriteThreads);
bwConfig.setMaxWriteThreads(Integer.parseInt(conf.get(Configuration.WRITE_THREADS)));
scannerThreads = Integer.parseInt(conf.get(Configuration.SCANNER_THREADS));
anonAccessAllowed = conf.getBoolean(Configuration.ALLOW_ANONYMOUS_ACCESS);
String ageoff = Long.toString(Integer.parseInt(conf.get(Configuration.METRICS_AGEOFF_DAYS)) * 86400000L);
Map<String, String> ageOffOptions = new HashMap<>();
ageOffOptions.put("ttl", ageoff);
IteratorSetting ageOffIteratorSettings = new IteratorSetting(100, "ageoff", AgeOffFilter.class,
ageOffOptions);
EnumSet<IteratorScope> ageOffIteratorScope = EnumSet.allOf(IteratorScope.class);
metricsTable = conf.get(Configuration.METRICS_TABLE);
if (metricsTable.contains(".")) {
final String[] parts = metricsTable.split("\\.", 2);
final String namespace = parts[0];
if (!connector.namespaceOperations().exists(namespace)) {
try {
LOG.info("Creating namespace " + namespace);
connector.namespaceOperations().create(namespace);
} catch (final NamespaceExistsException ex) {
// don't care
}
}
}
final Map<String, String> tableIdMap = connector.tableOperations().tableIdMap();
if (!tableIdMap.containsKey(metricsTable)) {
try {
LOG.info("Creating table " + metricsTable);
connector.tableOperations().create(metricsTable);
connector.tableOperations().attachIterator(metricsTable, ageOffIteratorSettings,
ageOffIteratorScope);
} catch (final TableExistsException ex) {
// don't care
}
} else {
for (IteratorScope scope : IteratorScope.values()) {
if (connector.tableOperations().getIteratorSetting(metricsTable, "ageoff", scope) == null) {
connector.tableOperations().attachIterator(metricsTable, ageOffIteratorSettings,
EnumSet.of(scope));
}
}
}
metaTable = conf.get(Configuration.META_TABLE);
if (!tableIdMap.containsKey(metaTable)) {
try {
LOG.info("Creating table " + metaTable);
connector.tableOperations().create(metaTable);
connector.tableOperations().attachIterator(metaTable, ageOffIteratorSettings, ageOffIteratorScope);
} catch (final TableExistsException ex) {
// don't care
}
} else {
for (IteratorScope scope : IteratorScope.values()) {
if (connector.tableOperations().getIteratorSetting(metaTable, "ageoff", scope) == null) {
connector.tableOperations()
.attachIterator(metaTable, ageOffIteratorSettings, EnumSet.of(scope));
}
}
}
internalMetricsTimer.schedule(new TimerTask() {
@Override
public void run() {
internalMetrics.getMetricsAndReset().forEach(m -> store(m));
}
}, METRICS_PERIOD, METRICS_PERIOD);
this.metaCache = MetaCacheFactory.getCache(conf);
} catch (Exception e) {
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error creating DataStoreImpl",
e.getMessage(), e);
}
}
@Override
public void store(Metric metric) {
if (null == metaWriter.get()) {
try {
BatchWriter w = connector.createBatchWriter(metaTable, bwConfig);
metaWriter.set(w);
writers.add(w);
} catch (TableNotFoundException e) {
LOG.error("Error creating meta batch writer", e);
return;
}
}
if (null == batchWriter.get()) {
try {
BatchWriter w = connector.createBatchWriter(metricsTable, bwConfig);
batchWriter.set(w);
writers.add(w);
} catch (TableNotFoundException e) {
LOG.error("Error creating metric batch writer", e);
return;
}
}
internalMetrics.incrementMetricsReceived(1);
List<Meta> toCache = new ArrayList<>(metric.getTags().size());
for (final Tag tag : metric.getTags()) {
Meta key = new Meta(metric.getMetric(), tag.getKey(), tag.getValue());
if (!metaCache.contains(key)) {
toCache.add(key);
}
}
if (!toCache.isEmpty()) {
final Set<Mutation> muts = new TreeSet<>(new Comparator<Mutation>() {
@Override
public int compare(Mutation o1, Mutation o2) {
if (o1.equals(o2)) {
return 0;
} else {
if (o1.hashCode() < o2.hashCode()) {
return -1;
} else {
return 1;
}
}
}
});
MetaKeySet mks = new MetaKeySet();
toCache.forEach(m -> mks.addAll(m.toKeys()));
internalMetrics.incrementMetaKeysInserted(mks.size());
muts.addAll(mks.toMutations());
try {
metaWriter.get().addMutations(muts);
} catch (MutationsRejectedException e) {
LOG.error("Unable to write to meta table", e);
try {
try {
final BatchWriter w = metaWriter.get();
metaWriter.remove();
writers.remove(w);
w.close();
} catch (MutationsRejectedException e1) {
LOG.error("Error closing meta writer", e1);
}
final BatchWriter w = connector.createBatchWriter(metaTable, bwConfig);
metaWriter.set(w);
writers.add(w);
} catch (TableNotFoundException e1) {
Server.fatal("Unexpected error recreating meta batch writer, shutting down Timely server", e1);
}
}
metaCache.addAll(toCache);
}
try {
batchWriter.get().addMutation(metric.toMutation());
internalMetrics.incrementMetricKeysInserted(metric.getTags().size());
} catch (MutationsRejectedException e) {
LOG.error("Unable to write to metrics table", e);
try {
try {
final BatchWriter w = batchWriter.get();
batchWriter.remove();
writers.remove(w);
w.close();
} catch (MutationsRejectedException e1) {
LOG.error("Error closing metric writer", e1);
}
final BatchWriter w = connector.createBatchWriter(metricsTable, bwConfig);
batchWriter.set(w);
writers.add(w);
} catch (TableNotFoundException e1) {
Server.fatal("Unexpected error recreating metrics batch writer, shutting down Timely server", e1);
}
}
}
private void updateMetricCounts() {
long now = System.currentTimeMillis();
if (now - lastCountTime.get() > 5 * 60 * 1000) {
this.lastCountTime.set(now);
SortedMap<MetricTagK, Integer> update = new TreeMap<>();
for (Meta meta : this.metaCache) {
MetricTagK key = new MetricTagK(meta.getMetric(), meta.getTagKey());
Integer count = update.getOrDefault(key, Integer.valueOf(0));
update.put(key, Integer.valueOf(count.intValue() + 1));
}
this.metaCounts.set(update);
}
}
@Override
public SuggestResponse suggest(SuggestRequest request) throws TimelyException {
SuggestResponse result = new SuggestResponse();
try {
if (request.getType().equals("metrics")) {
Range range;
if (request.getQuery().isPresent()) {
Text start = new Text(Meta.METRIC_PREFIX + request.getQuery().get());
Text endRow = new Text(start);
endRow.append(new byte[] { (byte) 0xff }, 0, 1);
range = new Range(start, endRow);
} else {
// kind of a hack, maybe someone wants a metric with >100
// 0xff bytes?
Text start = new Text(Meta.METRIC_PREFIX);
byte last = (byte) 0xff;
byte[] lastBytes = new byte[100];
Arrays.fill(lastBytes, last);
Text end = new Text(Meta.METRIC_PREFIX);
end.append(lastBytes, 0, lastBytes.length);
range = new Range(start, end);
}
Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
scanner.setRange(range);
List<String> metrics = new ArrayList<>();
for (Entry<Key, Value> metric : scanner) {
metrics.add(metric.getKey().getRow().toString().substring(Meta.METRIC_PREFIX.length()));
if (metrics.size() >= request.getMax()) {
break;
}
}
result.setSuggestions(metrics);
}
} catch (Exception ex) {
LOG.error("Error during suggest: " + ex.getMessage(), ex);
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error during suggest: "
+ ex.getMessage(), ex.getMessage(), ex);
}
return result;
}
@Override
public void flush() {
internalMetricsTimer.cancel();
writers.forEach(w -> {
try {
w.close();
} catch (final Exception ex) {
LOG.warn("Error shutting down batchwriter", ex);
}
});
}
@Override
public SearchLookupResponse lookup(SearchLookupRequest msg) throws TimelyException {
long startMillis = System.currentTimeMillis();
SearchLookupResponse result = new SearchLookupResponse();
result.setType("LOOKUP");
result.setMetric(msg.getQuery());
Map<String, String> tags = new TreeMap<>();
for (Tag tag : msg.getTags()) {
tags.put(tag.getKey(), tag.getValue());
}
result.setTags(tags);
result.setLimit(msg.getLimit());
try {
List<Result> resultField = new ArrayList<>();
Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
Key start = new Key(Meta.VALUE_PREFIX + msg.getQuery());
Key end = start.followingKey(PartialKey.ROW);
Range range = new Range(start, end);
scanner.setRange(range);
// TODO compute columns to fetch
int total = 0;
for (Entry<Key, Value> entry : scanner) {
Meta metaEntry = Meta.parse(entry.getKey(), entry.getValue());
if (matches(metaEntry.getTagKey(), metaEntry.getTagValue(), tags)) {
if (resultField.size() < msg.getLimit()) {
Result r = new Result();
r.putTag(metaEntry.getTagKey(), metaEntry.getTagValue());
resultField.add(r);
}
total++;
}
}
result.setResults(resultField);
result.setTotalResults(total);
result.setTime((int) (System.currentTimeMillis() - startMillis));
} catch (Exception ex) {
LOG.error("Error during lookup: " + ex.getMessage(), ex);
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error during lookup: "
+ ex.getMessage(), ex.getMessage(), ex);
}
return result;
}
private boolean matches(String tagk, String tagv, Map<String, String> tags) {
// first, match keys...
for (Entry<String, String> entry : tags.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
if (("*".equals(k) || tagk.equals(k)) && ("*".equals(v) || tagv.equals(v))) {
return true;
}
}
return false;
}
@Override
public List<QueryResponse> query(QueryRequest msg) throws TimelyException {
List<QueryResponse> result = new ArrayList<>();
long startTs = msg.getStart();
long endTs = msg.getEnd();
try {
long now = System.currentTimeMillis();
for (SubQuery query : msg.getQueries()) {
Map<Set<Tag>, List<Downsample>> allSeries = new HashMap<>();
String metric = query.getMetric();
BatchScanner scanner = connector.createBatchScanner(metricsTable, getSessionAuthorizations(msg),
scannerThreads);
try {
setQueryRange(scanner, metric, startTs, endTs);
setQueryColumns(scanner, metric, query.getTags());
long downsample = getDownsamplePeriod(query);
LOG.trace("Downsample period {}", downsample);
Class<? extends Aggregator> aggClass = getAggregator(query);
LOG.trace("Aggregator type {}", aggClass.getSimpleName());
IteratorSetting is = new IteratorSetting(500, DownsampleIterator.class);
DownsampleIterator.setDownsampleOptions(is, startTs, endTs, downsample, prioritizeTags(query),
aggClass.getName());
scanner.addScanIterator(is);
// tag -> array of results by period starting at start
for (Entry<Key, Value> encoded : scanner) {
Map<Set<Tag>, Downsample> samples = DownsampleIterator.decodeValue(encoded.getValue());
for (Entry<Set<Tag>, Downsample> entry : samples.entrySet()) {
Set<Tag> key = new HashSet<>();
for (Tag tag : entry.getKey()) {
if (query.getTags().keySet().contains(tag.getKey())) {
key.add(tag);
}
}
List<Downsample> downsamples = allSeries.getOrDefault(key, new ArrayList<>());
downsamples.add(entry.getValue());
allSeries.put(key, downsamples);
}
}
LOG.trace("allSeries: {}", allSeries);
} finally {
scanner.close();
}
// TODO groupby here?
long tsDivisor = msg.isMsResolution() ? 1 : 1000;
for (Entry<Set<Tag>, List<Downsample>> entry : allSeries.entrySet()) {
result.add(convertToQueryResponse(query, entry.getKey(), entry.getValue(), tsDivisor));
}
}
LOG.debug("Query time: {}", (System.currentTimeMillis() - now));
return result;
} catch (ClassNotFoundException | IOException | TableNotFoundException ex) {
LOG.error("Error during query: " + ex.getMessage(), ex);
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error during query: "
+ ex.getMessage(), ex.getMessage(), ex);
}
}
private List<String> prioritizeTags(SubQuery query) {
// trivial cases
Map<String, String> tags = query.getTags();
if (tags.isEmpty()) {
return Collections.emptyList();
}
if (tags.size() == 1) {
return Collections.singletonList(tags.keySet().iterator().next());
}
// favor tags with fewer values
Map<String, Integer> priority = new HashMap<>();
String metric = query.getMetric();
// Count matching tags
updateMetricCounts();
for (Entry<String, String> entry : tags.entrySet()) {
String tagk = entry.getKey();
String tagv = entry.getValue();
int wildcard = tagv.lastIndexOf('*');
if (wildcard < 0) {
// no wildcard: can only match one tag
priority.put(tagk, 1);
continue;
}
String prefix = tagv.substring(0, wildcard);
int count = 0;
MetricTagK start = new MetricTagK(metric, prefix);
for (Entry<MetricTagK, Integer> metricCount : metaCounts.get().tailMap(start).entrySet()) {
Pair<String, String> metricTagk = metricCount.getKey();
if (!metricTagk.getFirst().equals(metric) || !metricTagk.getSecond().startsWith(prefix)) {
break;
}
}
priority.put(tagk, count);
}
List<String> result = new ArrayList<>(tags.keySet());
Collections.sort(result, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// greater count lowers priority
return priority.get(o1).intValue() - priority.get(o2).intValue();
}
});
LOG.trace("Tag priority {}", result);
return result;
}
private QueryResponse convertToQueryResponse(SubQuery query, Set<Tag> tags, Collection<Downsample> values,
long tsDivisor) {
QueryResponse response = new QueryResponse();
response.setMetric(query.getMetric());
for (Tag tag : tags) {
response.putTag(tag.getKey(), tag.getValue());
}
RateOption rateOptions = query.getRateOptions();
Downsample combined = Downsample.combine(values, rateOptions);
for (Sample entry : combined) {
long ts = entry.timestamp / tsDivisor;
response.putDps(Long.toString(ts), entry.value);
}
LOG.trace("Created query response {}", response);
return response;
}
private void setQueryColumns(BatchScanner scanner, String metric, Map<String, String> tags)
throws TableNotFoundException, TimelyException {
LOG.trace("Looking for requested tags: {}", tags);
Scanner meta = connector.createScanner(metaTable, Authorizations.EMPTY);
Text start = new Text(Meta.VALUE_PREFIX + metric);
Text end = new Text(Meta.VALUE_PREFIX + metric + "\\x0000");
end.append(new byte[] { (byte) 0xff }, 0, 1);
meta.setRange(new Range(start, end));
// Only look for the meta entries that match our tags, if any
boolean onlyFirstRow = false;
Entry<String, String> first = null;
Iterator<Entry<String, String>> tagIter = tags.entrySet().iterator();
if (tagIter.hasNext()) {
first = tagIter.next();
if (first.getValue().endsWith("*")) {
meta.fetchColumnFamily(new Text(first.getKey()));
} else {
meta.fetchColumn(new Text(first.getKey()), new Text(first.getValue()));
}
} else {
// only grab the first meta entry for this metric
onlyFirstRow = true;
}
final boolean ONLY_RETURN_FIRST_ROW = onlyFirstRow;
Iterator<Entry<Key, Value>> iter = meta.iterator();
Iterator<Pair<String, String>> knownKeyValues = new Iterator<Pair<String, String>>() {
boolean returnedFirstRecord = false;
@Override
public boolean hasNext() {
if (ONLY_RETURN_FIRST_ROW) {
if (this.returnedFirstRecord) {
return false;
} else {
return iter.hasNext();
}
} else {
return iter.hasNext();
}
}
@Override
public Pair<String, String> next() {
Entry<Key, Value> entry = iter.next();
this.returnedFirstRecord = true;
LOG.trace("Returning tag {}={}", entry.getKey().getColumnFamily().toString(), entry.getKey()
.getColumnQualifier().toString());
return new Pair<>(entry.getKey().getColumnFamily().toString(), entry.getKey().getColumnQualifier()
.toString());
}
};
Set<Tag> concrete = expandTagValues(first, knownKeyValues);
if (concrete.size() == 0) {
throw new TimelyException(HttpResponseStatus.BAD_REQUEST.code(), "No matching tags", "No tags were found "
+ " that matched the submitted tags. Please fix and retry");
}
LOG.trace("Found matching tags: {}", concrete);
for (Tag tag : concrete) {
Text colf = new Text(tag.getKey() + "=" + tag.getValue());
scanner.fetchColumnFamily(colf);
LOG.trace("Fetching column family: {}", colf);
}
// Add the regular expression to filter the other tags
int priority = 100;
while (tagIter.hasNext()) {
Entry<String, String> tag = tagIter.next();
LOG.trace("Additional tag {}", tag);
StringBuffer pattern = new StringBuffer();
pattern.append("(^|,)").append(Pattern.quote(tag.getKey())).append("=");
String value = tag.getValue();
if (tag.getValue().endsWith("*")) {
value = value.substring(0, value.length() - 1);
pattern.append(Pattern.quote(value));
pattern.append("[^,]*");
} else {
pattern.append(Pattern.quote(value));
pattern.append("(,|$)");
}
IteratorSetting setting = new IteratorSetting(priority++, tag.getKey() + " tag filter", RegExFilter.class);
LOG.trace("Using {} additional filter on tags", pattern);
RegExFilter.setRegexs(setting, null, null, pattern.toString(), null, false, true);
scanner.addScanIterator(setting);
}
}
private Set<Tag> expandTagValues(Entry<String, String> firstTag, Iterator<Pair<String, String>> knownKeyValues) {
Set<Tag> result = new HashSet<>();
while (knownKeyValues.hasNext()) {
Pair<String, String> knownKeyValue = knownKeyValues.next();
if (firstTag == null) {
LOG.trace("Adding tag {}={}", knownKeyValue.getFirst(), knownKeyValue.getSecond());
result.add(new Tag(knownKeyValue.getFirst(), knownKeyValue.getSecond()));
} else {
LOG.trace("Testing requested tag {}={}", firstTag.getKey(), firstTag.getValue());
if (firstTag.getKey().equals(knownKeyValue.getFirst())) {
if (firstTag.getValue().endsWith("*")) {
String prefix = firstTag.getValue().substring(0, firstTag.getValue().length() - 1);
if (knownKeyValue.getSecond().startsWith(prefix)) {
LOG.trace("Adding tag {}={}", knownKeyValue.getFirst(), knownKeyValue.getSecond());
result.add(new Tag(knownKeyValue.getFirst(), knownKeyValue.getSecond()));
}
} else {
LOG.trace("Adding tag {}={}", knownKeyValue.getFirst(), knownKeyValue.getSecond());
result.add(new Tag(knownKeyValue.getFirst(), knownKeyValue.getSecond()));
}
}
}
}
return result;
}
private void setQueryRange(BatchScanner scanner, String metric, long start, long end) {
final byte[] start_row = Metric.encodeRowKey(metric, start);
LOG.trace("Start key for metric {} and time {} is {}", metric, start, start_row);
final byte[] end_row = Metric.encodeRowKey(metric, end);
LOG.trace("End key for metric {} and time {} is {}", metric, end, end_row);
Range range = new Range(new Text(start_row), new Text(end_row));
LOG.trace("Set query range to {}", range);
scanner.setRanges(Collections.singletonList(range));
}
private Class<? extends Aggregator> getAggregator(SubQuery query) {
String aggregatorName = "avg";
if (query.getDownsample().isPresent()) {
String parts[] = query.getDownsample().get().split("-");
aggregatorName = parts[1];
}
return Aggregator.getAggregator(aggregatorName);
}
private long getDownsamplePeriod(SubQuery query) {
if (!query.getDownsample().isPresent()) {
return DEFAULT_DOWNSAMPLE_MS;
}
String parts[] = query.getDownsample().get().split("-");
return getTimeInMillis(parts[0]);
}
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) {
String sessionId = request.getSessionId();
if (!StringUtils.isEmpty(sessionId)) {
return AuthCache.getAuthorizations(sessionId);
} else if (!anonAccessAllowed) {
throw new RuntimeException("Anonymous user attempting to query");
} else {
return Authorizations.EMPTY;
}
}
}
@@ -1,65 +0,0 @@
package timely.store;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.model.Metric;
import timely.api.model.Tag;
import com.google.common.util.concurrent.AtomicDouble;
public class InternalMetrics {
private static final Logger LOG = LoggerFactory.getLogger(InternalMetrics.class);
private static final String METRICS_RECEIVED_METRIC = "timely.metrics.received";
private static final String META_KEYS_METRIC = "timely.keys.meta.inserted";
private static final String METRIC_KEYS_METRIC = "timely.keys.metric.inserted";
private static final String HOSTNAME_TAG = "host";
private String hostName = "localhost";
private AtomicDouble numMetricsReceived = new AtomicDouble(0);
private AtomicDouble numMetaKeysInserted = new AtomicDouble(0);
private AtomicDouble numMetricKeysInserted = new AtomicDouble(0);
private List<Tag> tags = new ArrayList<Tag>();
public InternalMetrics() {
super();
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
LOG.error("Error getting hostname", e);
}
tags.add(new Tag(HOSTNAME_TAG, hostName));
}
public void incrementMetricsReceived(long num) {
numMetricsReceived.addAndGet(num);
}
public void incrementMetaKeysInserted(long num) {
numMetaKeysInserted.addAndGet(num);
}
public void incrementMetricKeysInserted(long num) {
numMetricKeysInserted.addAndGet(num);
}
public List<Metric> getMetricsAndReset() {
List<Metric> metrics = new ArrayList<Metric>();
long ts = System.currentTimeMillis();
metrics.add(new Metric(METRICS_RECEIVED_METRIC, ts, numMetricsReceived.get(), tags));
numMetricsReceived.set(0);
metrics.add(new Metric(META_KEYS_METRIC, ts, numMetaKeysInserted.get(), tags));
numMetaKeysInserted.set(0);
metrics.add(new Metric(METRIC_KEYS_METRIC, ts, numMetricKeysInserted.get(), tags));
numMetricKeysInserted.set(0);
return metrics;
}
}
@@ -1,25 +0,0 @@
package timely.store;
import java.util.Collection;
import java.util.Iterator;
import timely.Configuration;
import timely.api.model.Meta;
public interface MetaCache extends Iterable<Meta> {
void init(Configuration config);
void add(Meta meta);
boolean contains(Meta meta);
void addAll(Collection<Meta> c);
Iterator<Meta> iterator();
void close();
boolean isClosed();
}
@@ -1,35 +0,0 @@
package timely.store;
import timely.Configuration;
public class MetaCacheFactory {
private static MetaCache cache = null;
public static MetaCache getCache() {
if (null != cache) {
return cache;
} else {
throw new RuntimeException("MetaCache not initialized.");
}
}
public static final synchronized MetaCache getCache(Configuration conf) {
if (null == cache || cache.isClosed()) {
if (null == conf) {
throw new RuntimeException("Configuration cannot be null");
}
cache = new MetaCacheImpl();
cache.init(conf);
}
return cache;
}
public static final synchronized void close() {
if (null != cache) {
cache.close();
}
cache = null;
}
}
@@ -1,58 +0,0 @@
package timely.store;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import timely.Configuration;
import timely.api.model.Meta;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class MetaCacheImpl implements MetaCache {
private static final Object DUMMY = new Object();
private volatile boolean closed = false;
private Cache<Meta, Object> cache = null;
@Override
public void init(Configuration config) {
long defaultExpiration = Long.parseLong(config.get(Configuration.META_CACHE_EXPIRATION));
int initialCapacity = Integer.parseInt(config.get(Configuration.META_CACHE_INITIAL_CAPACITY));
long maxCapacity = Long.parseLong(config.get(Configuration.META_CACHE_MAX_CAPACITY));
cache = Caffeine.newBuilder().expireAfterAccess(defaultExpiration, TimeUnit.MINUTES)
.initialCapacity(initialCapacity).maximumSize(maxCapacity).build();
}
@Override
public void add(Meta meta) {
cache.put(meta, DUMMY);
}
@Override
public boolean contains(Meta meta) {
return cache.asMap().containsKey(meta);
}
@Override
public void addAll(Collection<Meta> c) {
c.forEach(m -> cache.put(m, DUMMY));
}
@Override
public Iterator<Meta> iterator() {
return cache.asMap().keySet().iterator();
}
@Override
public void close() {
this.closed = true;
}
@Override
public boolean isClosed() {
return closed;
}
}
@@ -1,110 +0,0 @@
package timely.util;
import java.io.PrintStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.netty.tcp.TcpPutDecoder;
public class InsertTestData {
private static final Logger LOG = LoggerFactory.getLogger(InsertTestData.class);
private static final List<String> METRICS = new ArrayList<>();
static {
METRICS.add("sys.cpu0.user");
METRICS.add("sys.cpu0.idle");
METRICS.add("sys.cpu0.system");
METRICS.add("sys.cpu0.wait");
METRICS.add("sys.memory.free");
METRICS.add("sys.memory.used");
METRICS.add("sys.eth0.rx-errors");
METRICS.add("sys.eth0.rx-dropped");
METRICS.add("sys.eth0.rx-packets");
METRICS.add("sys.eth0.tx-errors");
METRICS.add("sys.eth0.tx-dropped");
METRICS.add("sys.eth0.tx-packets");
METRICS.add("sys.swap.free");
METRICS.add("sys.swap.used");
}
private static final List<String> HOSTS = new ArrayList<>();
static {
HOSTS.add("n01");
HOSTS.add("n02");
}
private static final List<String> RACKS = new ArrayList<>();
static {
RACKS.add("r01");
RACKS.add("r02");
}
private static final Map<String, String> VISIBILITIES = new HashMap<>();
static {
VISIBILITIES.put("sys.eth0.rx-errors", "A");
VISIBILITIES.put("sys.eth0.rx-dropped", "B");
VISIBILITIES.put("sys.eth0.rx-packets", "C");
VISIBILITIES.put("sys.eth0.tx-errors", "D");
VISIBILITIES.put("sys.eth0.tx-dropped", "E");
VISIBILITIES.put("sys.eth0.tx-packets", "F");
}
private static final String FMT = "put {0} {1,number,#} {2,number} host={4}{3} rack={4}";
private static String usage() {
return "InsertTestData <host> <port> [--fast]";
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.err.println(usage());
System.exit(1);
}
String hostname = args[0];
String port = args[1];
boolean goFast = false;
if (args.length == 3) {
if (args[2].equalsIgnoreCase("--fast")) {
goFast = true;
}
}
try (Socket sock = new Socket(hostname, Integer.parseInt(port));
PrintStream writer = new PrintStream(sock.getOutputStream(), true, StandardCharsets.UTF_8.name());) {
while (true) {
long time = System.currentTimeMillis();
METRICS.forEach(m -> {
final String viz = VISIBILITIES.get(m);
RACKS.forEach(rack -> {
HOSTS.forEach(host -> {
String put = MessageFormat.format(FMT, m, time,
ThreadLocalRandom.current().nextDouble(0.0D, 100.0D), host, rack);
if (viz != null)
put += " viz=" + viz;
writer.println(put);
LOG.info(put);
});
});
});
writer.flush();
if (!goFast) {
Thread.sleep(10 * 1000);
} else {
Thread.sleep(500);
}
}
}
}
}
@@ -1,24 +0,0 @@
package timely.util;
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 JsonUtil {
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(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
}
public static ObjectMapper getObjectMapper() {
return mapper;
}
}
Loaded 100 of 188 files, more files were not shown because too many files have changed in this diff. Show more