initial commit

This commit is contained in:
Jörg Prante 2016-11-01 15:55:47 +01:00
commit ec58c764d2
32 changed files with 3320 additions and 0 deletions

13
.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
/data
/work
/logs
/.idea
/target
.DS_Store
*.iml
/.settings
/.classpath
/.project
/.gradle
/build
/plugins

202
LICENSE.txt Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
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.

75
build.gradle Normal file
View file

@ -0,0 +1,75 @@
plugins {
id "org.sonarqube" version "2.2"
id "org.ajoberstar.github-pages" version "1.6.0-rc.1"
id "org.xbib.gradle.plugin.jbake" version "1.1.0"
}
group = 'org.xbib'
version = '1.0.0'
printf "Host: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGroovy: %s\nGradle: %s\n" +
"Build: group: ${project.group} name: ${project.name} version: ${project.version}\n",
InetAddress.getLocalHost(),
System.getProperty("os.name"),
System.getProperty("os.arch"),
System.getProperty("os.version"),
System.getProperty("java.version"),
System.getProperty("java.vm.version"),
System.getProperty("java.vm.vendor"),
System.getProperty("java.vm.name"),
GroovySystem.getVersion(),
gradle.gradleVersion
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: 'findbugs'
apply plugin: 'pmd'
apply plugin: 'checkstyle'
apply plugin: "jacoco"
apply from: 'gradle/ext.gradle'
configurations {
wagon
}
dependencies {
testCompile "junit:junit:4.12"
testCompile "org.apache.logging.log4j:log4j-core:2.7"
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:2.7"
wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:all" << "-profile" << "compact3"
}
task javadocJar(type: Jar, dependsOn: classes) {
from javadoc
into "build/tmp"
classifier 'javadoc'
}
task sourcesJar(type: Jar, dependsOn: classes) {
from sourceSets.main.allSource
into "build/tmp"
classifier 'sources'
}
artifacts {
archives javadocJar, sourcesJar
}
if (project.hasProperty('signing.keyId')) {
signing {
sign configurations.archives
}
}
apply from: 'gradle/publish.gradle'
apply from: 'gradle/sonarqube.gradle'

View file

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<property name="groups" value="com,junit,net,org,java,javax"/>
<!-- This ensures that static imports go first. -->
<property name="option" value="top"/>
<property name="tokens" value="STATIC_IMPORT, IMPORT"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="scope" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowUndeclaredRTE" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
</module>

8
gradle/ext.gradle Normal file
View file

@ -0,0 +1,8 @@
ext {
user = 'xbib'
name = 'metrics'
description = 'A stripped-down version of Coda Hale Metrics'
scmUrl = 'https://github.com/' + user + '/' + name
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
scmDeveloperConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
}

66
gradle/publish.gradle Normal file
View file

@ -0,0 +1,66 @@
task xbibUpload(type: Upload) {
configuration = configurations.archives
uploadDescriptor = true
repositories {
if (project.hasProperty("xbibUsername")) {
mavenDeployer {
configuration = configurations.wagon
repository(url: 'scpexe://xbib.org/repository') {
authentication(userName: xbibUsername, privateKey: xbibPrivateKey)
}
}
}
}
}
task sonaTypeUpload(type: Upload) {
configuration = configurations.archives
uploadDescriptor = true
repositories {
if (project.hasProperty('ossrhUsername')) {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots') {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
groupId project.group
artifactId project.name
version project.version
name project.name
description description
packaging 'jar'
inceptionYear '2016'
url scmUrl
organization {
name 'xbib'
url 'http://xbib.org'
}
developers {
developer {
id user
name 'Jörg Prante'
email 'joergprante@gmail.com'
url 'https://github.com/jprante'
}
}
scm {
url scmUrl
connection scmConnection
developerConnection scmDeveloperConnection
}
licenses {
license {
name 'The Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
}
}
}
}
}

41
gradle/sonarqube.gradle Normal file
View file

@ -0,0 +1,41 @@
tasks.withType(FindBugs) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = false
}
}
tasks.withType(Pmd) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = true
}
}
tasks.withType(Checkstyle) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = true
}
}
jacocoTestReport {
reports {
xml.enabled true
csv.enabled false
xml.destination "${buildDir}/reports/jacoco-xml"
html.destination "${buildDir}/reports/jacoco-html"
}
}
sonarqube {
properties {
property "sonar.projectName", "${project.group} ${project.name}"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.tests", "src/test/java"
property "sonar.scm.provider", "git"
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.junit.reportsPath", "build/test-results/test/"
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,6 @@
#Tue Nov 01 15:47:24 CET 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip

169
gradlew vendored Executable file
View file

@ -0,0 +1,169 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

84
gradlew.bat vendored Normal file
View file

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,62 @@
package org.xbib.metrics;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
/**
* An abstraction for how time passes. It is passed to {@link Sampler} to track timing.
*/
public abstract class Clock {
/**
* The default clock to use.
*
* @return the default {@link Clock} instance
* @see Clock.UserTimeClock
*/
public static Clock defaultClock() {
return UserTimeClock.DEFAULT;
}
/**
* Returns the current time tick.
*
* @return time tick in nanoseconds
*/
public abstract long getTick();
/**
* Returns the current time in milliseconds.
*
* @return time in milliseconds
*/
public long getTime() {
return System.currentTimeMillis();
}
/**
* A clock implementation which returns the current time in epoch nanoseconds.
*/
public static class UserTimeClock extends Clock {
@Override
public long getTick() {
return System.nanoTime();
}
static final Clock DEFAULT = new UserTimeClock();
}
/**
* A clock implementation which returns the current thread's CPU time.
*/
public static class CpuTimeClock extends Clock {
private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean();
@Override
public long getTick() {
return THREAD_MX_BEAN.getCurrentThreadCpuTime();
}
}
}

View file

@ -0,0 +1,30 @@
package org.xbib.metrics;
/**
* An interface for metric types which have counts.
*/
public interface Count {
void inc();
void inc(long n);
void inc(String index, String type, String id);
void dec();
void dec(long n);
void dec(String index, String type, String id);
/**
* Returns the current count.
*
* @return the current count
*/
long getCount();
String getIncChecksum(String index, String type);
String getDecChecksum(String index, String type);
}

View file

@ -0,0 +1,107 @@
package org.xbib.metrics;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.zip.CRC32;
/**
* An incrementing and decrementing counter metric.
*/
public class CountMetric implements Metric, Count {
private final LongAdder count;
private final Map<String, CRC32> checksumIn;
private final Map<String, CRC32> checksumOut;
public CountMetric() {
this.count = new LongAdder();
this.checksumIn = new HashMap<>();
this.checksumOut = new HashMap<>();
}
/**
* Increment the counter by one.
*/
@Override
public void inc() {
inc(1);
}
/**
* Increment the counter by {@code n}.
*
* @param n the amount by which the counter will be increased
*/
@Override
public void inc(long n) {
count.add(n);
}
@Override
public void inc(String index, String type, String id) {
CRC32 crc32 = checksumIn.get(index + "/" + type);
if (crc32 == null) {
crc32 = new CRC32();
checksumIn.put(index + "/" + type, crc32);
}
if (id != null) {
crc32.update(id.getBytes(StandardCharsets.UTF_8));
}
}
/**
* Decrement the counter by one.
*/
@Override
public void dec() {
dec(1);
}
/**
* Decrement the counter by {@code n}.
*
* @param n the amount by which the counter will be decreased
*/
@Override
public void dec(long n) {
count.add(-n);
}
@Override
public void dec(String index, String type, String id) {
CRC32 crc32 = checksumOut.get(index + "/" + type);
if (crc32 == null) {
crc32 = new CRC32();
checksumOut.put(index + "/" + type, crc32);
}
if (id != null) {
crc32.update(id.getBytes(StandardCharsets.UTF_8));
}
}
/**
* Returns the counter's current value.
*
* @return the counter's current value
*/
@Override
public long getCount() {
return count.sum();
}
@Override
public String getIncChecksum(String index, String type) {
return Long.toHexString(checksumIn.containsKey(index + "/" + type) ?
checksumIn.get(index + "/" + type).getValue() : 0L);
}
@Override
public String getDecChecksum(String index, String type) {
return Long.toHexString(checksumOut.containsKey(index + "/" + type) ?
checksumOut.get(index + "/" + type).getValue() : 0L);
}
}

View file

@ -0,0 +1,96 @@
package org.xbib.metrics;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
/**
* An exponentially-weighted moving average.
*
* @see <a href="http://www.teamquest.com/pdfs/whitepaper/ldavg1.pdf">UNIX Load Average Part 1: How It Works</a>
* @see <a href="http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf">UNIX Load Average Part 2: Not Your Average
* Average</a>
*/
public class ExpWeightedMovingAverage {
private static final double M1_ALPHA = 1 - Math.exp(-5 / 60.0);
private static final double M5_ALPHA = 1 - Math.exp(-5 / 60.0 / 5);
private static final double M15_ALPHA = 1 - Math.exp(-5 / 60.0 / 15);
private final LongAdder uncounted = new LongAdder();
private final double alpha, interval;
private volatile boolean initialized = false;
private volatile double rate = 0.0;
/**
* Create a new EWMA with a specific smoothing constant.
*
* @param alpha the smoothing constant
* @param interval the expected tick interval
* @param intervalUnit the time unit of the tick interval
*/
public ExpWeightedMovingAverage(double alpha, long interval, TimeUnit intervalUnit) {
this.interval = intervalUnit.toNanos(interval);
this.alpha = alpha;
}
/**
* Creates a new EWMA which is equivalent to the UNIX one minute load average and which expects to be ticked every
* 5 seconds.
*
* @return a one-minute EWMA
*/
public static ExpWeightedMovingAverage oneMinuteEWMA() {
return new ExpWeightedMovingAverage(M1_ALPHA, 5, TimeUnit.SECONDS);
}
/**
* Creates a new EWMA which is equivalent to the UNIX five minute load average and which expects to be ticked every
* 5 seconds.
*
* @return a five-minute EWMA
*/
public static ExpWeightedMovingAverage fiveMinuteEWMA() {
return new ExpWeightedMovingAverage(M5_ALPHA, 5, TimeUnit.SECONDS);
}
/**
* Creates a new EWMA which is equivalent to the UNIX fifteen minute load average and which expects to be ticked
* every 5 seconds.
*
* @return a fifteen-minute EWMA
*/
public static ExpWeightedMovingAverage fifteenMinuteEWMA() {
return new ExpWeightedMovingAverage(M15_ALPHA, 5, TimeUnit.SECONDS);
}
/**
* Update the moving average with a new value.
*
* @param n the new value
*/
public void update(long n) {
uncounted.add(n);
}
/**
* Mark the passage of time and decay the current rate accordingly.
*/
public void tick() {
final long count = uncounted.sumThenReset();
double instantRate = count / interval;
if (initialized) {
rate += (alpha * (instantRate - rate));
} else {
rate = instantRate;
initialized = true;
}
}
/**
* Returns the rate in the given units of time.
*
* @param rateUnit the unit of time
* @return the rate
*/
public double getRate(TimeUnit rateUnit) {
return rate * (double) rateUnit.toNanos(1);
}
}

View file

@ -0,0 +1,195 @@
package org.xbib.metrics;
import static java.lang.Math.exp;
import static java.lang.Math.min;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* An exponentially-decaying random reservoir of {@code long}s. Uses Cormode et al's
* forward-decaying priority reservoir sampling method to produce a statistically representative
* sampling reservoir, exponentially biased towards newer entries.
*
* @see <a href="http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf">
* Cormode et al. Forward Decay: A Practical Time Decay Model for Streaming Systems. ICDE '09:
* Proceedings of the 2009 IEEE International Conference on Data Engineering (2009)</a>
*/
public class ExponentiallyDecayingReservoir implements Reservoir {
private static final int DEFAULT_SIZE = 1028;
private static final double DEFAULT_ALPHA = 0.015;
private static final long RESCALE_THRESHOLD = TimeUnit.HOURS.toNanos(1);
private final ConcurrentSkipListMap<Double, org.xbib.metrics.WeightedSnapshot.WeightedSample> values;
private final ReentrantReadWriteLock lock;
private final double alpha;
private final int size;
private final AtomicLong count;
private final AtomicLong nextScaleTime;
private final Clock clock;
private volatile long startTime;
/**
* Creates a new {@link ExponentiallyDecayingReservoir} of 1028 elements, which offers a 99.9%
* confidence level with a 5% margin of error assuming a normal distribution, and an alpha
* factor of 0.015, which heavily biases the reservoir to the past 5 minutes of measurements.
*/
public ExponentiallyDecayingReservoir() {
this(DEFAULT_SIZE, DEFAULT_ALPHA);
}
/**
* Creates a new {@link ExponentiallyDecayingReservoir}.
*
* @param size the number of samples to keep in the sampling reservoir
* @param alpha the exponential decay factor; the higher this is, the more biased the reservoir
* will be towards newer values
*/
public ExponentiallyDecayingReservoir(int size, double alpha) {
this(size, alpha, Clock.defaultClock());
}
/**
* Creates a new {@link ExponentiallyDecayingReservoir}.
*
* @param size the number of samples to keep in the sampling reservoir
* @param alpha the exponential decay factor; the higher this is, the more biased the reservoir
* will be towards newer values
* @param clock the clock used to timestamp samples and track rescaling
*/
public ExponentiallyDecayingReservoir(int size, double alpha, Clock clock) {
this.values = new ConcurrentSkipListMap<>();
this.lock = new ReentrantReadWriteLock();
this.alpha = alpha;
this.size = size;
this.clock = clock;
this.count = new AtomicLong(0);
this.startTime = currentTimeInSeconds();
this.nextScaleTime = new AtomicLong(clock.getTick() + RESCALE_THRESHOLD);
}
public int size() {
return (int) min(size, count.get());
}
public void update(long value) {
update(value, currentTimeInSeconds());
}
/**
* Adds an old value with a fixed timestamp to the reservoir.
*
* @param value the value to be added
* @param timestamp the epoch timestamp of {@code value} in seconds
*/
public void update(long value, long timestamp) {
rescaleIfNeeded();
lockForRegularUsage();
try {
final double itemWeight = weight(timestamp - startTime);
final WeightedSnapshot.WeightedSample sample = new WeightedSnapshot.WeightedSample(value, itemWeight);
final double priority = itemWeight / ThreadLocalRandom.current().nextDouble();
final long newCount = count.incrementAndGet();
if (newCount <= size) {
values.put(priority, sample);
} else {
Double first = values.firstKey();
if (first < priority && values.putIfAbsent(priority, sample) == null) {
// ensure we always remove an item
while (values.remove(first) == null) {
first = values.firstKey();
}
}
}
} finally {
unlockForRegularUsage();
}
}
private void rescaleIfNeeded() {
final long now = clock.getTick();
final long next = nextScaleTime.get();
if (now >= next) {
rescale(now, next);
}
}
public Snapshot getSnapshot() {
lockForRegularUsage();
try {
return new WeightedSnapshot(values.values());
} finally {
unlockForRegularUsage();
}
}
private long currentTimeInSeconds() {
return TimeUnit.MILLISECONDS.toSeconds(clock.getTime());
}
private double weight(long t) {
return exp(alpha * t);
}
/* "A common feature of the above techniques—indeed, the key technique that
* allows us to track the decayed weights efficientlyis that they maintain
* counts and other quantities based on g(ti L), and only scale by g(t L)
* at query time. But while g(ti L)/g(tL) is guaranteed to lie between zero
* and one, the intermediate values of g(ti L) could become very large. For
* polynomial functions, these values should not grow too large, and should be
* effectively represented in practice by floating point values without loss of
* precision. For exponential functions, these values could grow quite large as
* new values of (ti L) become large, and potentially exceed the capacity of
* common floating point types. However, since the values stored by the
* algorithms are linear combinations of g values (scaled sums), they can be
* rescaled relative to a new landmark. That is, by the analysis of exponential
* decay in Section III-A, the choice of L does not affect the final result. We
* can therefore multiply each value based on L by a factor of exp(α(L L)),
* and obtain the correct value as if we had instead computed relative to a new
* landmark L (and then use this new L at query time). This can be done with
* a linear pass over whatever data structure is being used."
*/
private void rescale(long now, long next) {
if (nextScaleTime.compareAndSet(next, now + RESCALE_THRESHOLD)) {
lockForRescale();
try {
final long oldStartTime = startTime;
this.startTime = currentTimeInSeconds();
final double scalingFactor = exp(-alpha * (startTime - oldStartTime));
final ArrayList<Double> keys = new ArrayList<>(values.keySet());
for (Double key : keys) {
final WeightedSnapshot.WeightedSample sample = values.remove(key);
final WeightedSnapshot.WeightedSample newSample =
new WeightedSnapshot.WeightedSample(sample.value, sample.weight * scalingFactor);
values.put(key * scalingFactor, newSample);
}
// make sure the counter is in sync with the number of stored samples.
count.set(values.size());
} finally {
unlockForRescale();
}
}
}
private void unlockForRescale() {
lock.writeLock().unlock();
}
private void lockForRescale() {
lock.writeLock().lock();
}
private void lockForRegularUsage() {
lock.readLock().lock();
}
private void unlockForRegularUsage() {
lock.readLock().unlock();
}
}

View file

@ -0,0 +1,24 @@
package org.xbib.metrics;
/**
* A gauge metric is an instantaneous reading of a particular value. To instrument a queue's depth,
* for example:<br>
* <pre><code>
* final Queue&lt;String&gt; queue = new ConcurrentLinkedQueue&lt;String&gt;();
* final Gauge&lt;Integer&gt; queueDepth = new Gauge&lt;Integer&gt;() {
* public Integer getValue() {
* return queue.size();
* }
* };
* </code></pre>
*
* @param <T> the type of the metric's value
*/
public interface Gauge<T> extends Metric {
/**
* Returns the metric's current value.
*
* @return the metric's current value
*/
T getValue();
}

View file

@ -0,0 +1,85 @@
package org.xbib.metrics;
import java.util.concurrent.atomic.LongAdder;
/**
* A metric which calculates the distribution of a value.
*
* @see <a href="http://www.johndcook.com/standard_deviation.html">Accurately computing running
* variance</a>
*/
public class Histogram implements Metric, Sampling, Count {
private final Reservoir reservoir;
private final LongAdder count;
/**
* Creates a new {@link Histogram} with the given reservoir.
*
* @param reservoir the reservoir to create a histogram from
*/
public Histogram(Reservoir reservoir) {
this.reservoir = reservoir;
this.count = new LongAdder();
}
@Override
public void inc() {
inc(1);
}
/**
* Adds a recorded value.
*
* @param value the length of the value
*/
@Override
public void inc(long value) {
count.increment();
reservoir.update(value);
}
@Override
public void inc(String index, String type, String id) {
}
@Override
public void dec() {
dec(1);
}
@Override
public void dec(long n) {
throw new UnsupportedOperationException();
}
@Override
public void dec(String index, String type, String id) {
}
/**
* Returns the number of values recorded.
*
* @return the number of values recorded
*/
@Override
public long getCount() {
return count.sum();
}
@Override
public String getIncChecksum(String index, String type) {
return null;
}
@Override
public String getDecChecksum(String index, String type) {
return null;
}
@Override
public Snapshot getSnapshot() {
return reservoir.getSnapshot();
}
}

View file

@ -0,0 +1,134 @@
package org.xbib.metrics;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* A meter metric which measures mean throughput and one-, five-, and fifteen-minute
* exponentially-weighted moving average throughputs.
*
* @see ExpWeightedMovingAverage
*/
public class Meter implements Metered {
private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5);
private final ExpWeightedMovingAverage m1Rate = ExpWeightedMovingAverage.oneMinuteEWMA();
private final ExpWeightedMovingAverage m5Rate = ExpWeightedMovingAverage.fiveMinuteEWMA();
private final ExpWeightedMovingAverage m15Rate = ExpWeightedMovingAverage.fifteenMinuteEWMA();
private final LongAdder count = new LongAdder();
private final AtomicLong lastTick;
private final Clock clock;
private long startedAt;
private ScheduledFuture<?> future;
/**
* Creates a new {@link Meter}.
*/
public Meter() {
this(Clock.defaultClock());
}
/**
* Creates a new {@link Meter}.
*
* @param clock the clock to use for the meter ticks
*/
public Meter(Clock clock) {
this.clock = clock;
this.startedAt = this.clock.getTick();
this.lastTick = new AtomicLong(startedAt);
}
public void spawn(long intervalSeconds) {
this.future = Executors.newScheduledThreadPool(1)
.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
tickIfNecessary();
}
}, intervalSeconds, intervalSeconds, TimeUnit.SECONDS);
}
public void stop() {
future.cancel(false);
}
/**
* Mark the occurrence of an event.
*/
public void mark() {
mark(1);
}
/**
* Mark the occurrence of a given number of events.
*
* @param n the number of events
*/
public void mark(long n) {
tickIfNecessary();
count.add(n);
m1Rate.update(n);
m5Rate.update(n);
m15Rate.update(n);
}
private void tickIfNecessary() {
final long oldTick = lastTick.get();
final long newTick = clock.getTick();
final long age = newTick - oldTick;
if (age > TICK_INTERVAL) {
final long newIntervalStartTick = newTick - age % TICK_INTERVAL;
if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) {
final long requiredTicks = age / TICK_INTERVAL;
for (long i = 0; i < requiredTicks; i++) {
m1Rate.tick();
m5Rate.tick();
m15Rate.tick();
}
}
}
}
@Override
public long getCount() {
return count.sum();
}
@Override
public double getFifteenMinuteRate() {
tickIfNecessary();
return m15Rate.getRate(TimeUnit.SECONDS);
}
@Override
public double getFiveMinuteRate() {
tickIfNecessary();
return m5Rate.getRate(TimeUnit.SECONDS);
}
@Override
public double getMeanRate() {
if (getCount() == 0) {
return 0.0;
} else {
final double elapsed = clock.getTick() - startedAt;
return getCount() / elapsed * TimeUnit.SECONDS.toNanos(1);
}
}
@Override
public double getOneMinuteRate() {
tickIfNecessary();
return m1Rate.getRate(TimeUnit.SECONDS);
}
public long elapsed() {
return clock.getTick() - startedAt;
}
}

View file

@ -0,0 +1,57 @@
package org.xbib.metrics;
/**
* An object which maintains mean and exponentially-weighted rate.
*/
public interface Metered extends Metric {
/**
* Returns the number of events which have been marked.
*
* @return the number of events which have been marked
*/
long getCount();
/**
* Returns the mean rate at which events have occurred since the meter was created.
*
* @return the mean rate at which events have occurred since the meter was created
*/
double getMeanRate();
/**
* Returns the one-minute exponentially-weighted moving average rate at which events have
* occurred since the meter was created.
* <p>
* This rate has the same exponential decay factor as the one-minute load average in the {@code
* top} Unix command.
*
* @return the one-minute exponentially-weighted moving average rate at which events have
* occurred since the meter was created
*/
double getOneMinuteRate();
/**
* Returns the five-minute exponentially-weighted moving average rate at which events have
* occurred since the meter was created.
* <p>
* This rate has the same exponential decay factor as the five-minute load average in the {@code
* top} Unix command.
*
* @return the five-minute exponentially-weighted moving average rate at which events have
* occurred since the meter was created
*/
double getFiveMinuteRate();
/**
* Returns the fifteen-minute exponentially-weighted moving average rate at which events have
* occurred since the meter was created.
* <p>
* This rate has the same exponential decay factor as the fifteen-minute load average in the
* {@code top} Unix command.
*
* @return the fifteen-minute exponentially-weighted moving average rate at which events have
* occurred since the meter was created
*/
double getFifteenMinuteRate();
}

View file

@ -0,0 +1,7 @@
package org.xbib.metrics;
/**
*
*/
public interface Metric {
}

View file

@ -0,0 +1,25 @@
package org.xbib.metrics;
/**
* A filter used to determine whether or not a metric should be reported, among other things.
*/
public interface MetricFilter {
/**
* Matches all metrics, regardless of type or name.
*/
MetricFilter ALL = new MetricFilter() {
@Override
public boolean matches(MetricName name, Metric metric) {
return true;
}
};
/**
* Returns {@code true} if the metric matches the filter; {@code false} otherwise.
*
* @param name the metric's name
* @param metric the metric
* @return {@code true} if the metric matches the filter
*/
boolean matches(MetricName name, Metric metric);
}

View file

@ -0,0 +1,312 @@
package org.xbib.metrics;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* A metric name with the ability to include semantic tags.
*/
public class MetricName implements Comparable<MetricName> {
public static final String SEPARATOR = ".";
public static final Map<String, String> EMPTY_TAGS = Collections.unmodifiableMap(new HashMap<String, String>());
public static final MetricName EMPTY = new MetricName();
private final String key;
private final Map<String, String> tags;
public MetricName() {
this.key = null;
this.tags = EMPTY_TAGS;
}
public MetricName(String key) {
this.key = key;
this.tags = EMPTY_TAGS;
}
public MetricName(String key, Map<String, String> tags) {
this.key = key;
this.tags = checkTags(tags);
}
/**
* Join the specified set of metric names.
*
* @param parts Multiple metric names to join using the separator.
* @return A newly created metric name which has the name of the specified
* parts and includes all tags of all child metric names.
**/
public static MetricName join(MetricName... parts) {
final StringBuilder nameBuilder = new StringBuilder();
final Map<String, String> tags = new HashMap<String, String>();
boolean first = true;
for (MetricName part : parts) {
final String name = part.getKey();
if (name != null && !name.isEmpty()) {
if (first) {
first = false;
} else {
nameBuilder.append(SEPARATOR);
}
nameBuilder.append(name);
}
if (!part.getTags().isEmpty()) {
tags.putAll(part.getTags());
}
}
return new MetricName(nameBuilder.toString(), tags);
}
/**
* Build a new metric name using the specific path components.
*
* @param parts Path of the new metric name.
* @return A newly created metric name with the specified path.
**/
public static MetricName build(String... parts) {
if (parts == null || parts.length == 0) {
return MetricName.EMPTY;
}
if (parts.length == 1) {
return new MetricName(parts[0], EMPTY_TAGS);
}
return new MetricName(buildName(parts), EMPTY_TAGS);
}
private static String buildName(String... names) {
final StringBuilder builder = new StringBuilder();
boolean first = true;
for (String name : names) {
if (name == null || name.isEmpty()) {
continue;
}
if (first) {
first = false;
} else {
builder.append(SEPARATOR);
}
builder.append(name);
}
return builder.toString();
}
private Map<String, String> checkTags(Map<String, String> tags) {
if (tags == null || tags.isEmpty()) {
return EMPTY_TAGS;
}
return Collections.unmodifiableMap(tags);
}
public String getKey() {
return key;
}
public Map<String, String> getTags() {
return tags;
}
/**
* Build the MetricName that is this with another path appended to it.
*
* The new MetricName inherits the tags of this one.
*
* @param p The extra path element to add to the new metric.
* @return A new metric name relative to the original by the path specified
* in p.
*/
public MetricName resolve(String p) {
final String next;
if (p != null && !p.isEmpty()) {
if (key != null && !key.isEmpty()) {
next = key + SEPARATOR + p;
} else {
next = p;
}
} else {
next = this.key;
}
return new MetricName(next, tags);
}
/**
* Add tags to a metric name and return the newly created MetricName.
*
* @param add Tags to add.
* @return A newly created metric name with the specified tags associated with it.
*/
public MetricName tagged(Map<String, String> add) {
final Map<String, String> tags = new HashMap<String, String>(add);
tags.putAll(this.tags);
return new MetricName(key, tags);
}
/**
* Same as {@link #tagged(Map)}, but takes a variadic list
* of arguments.
*
* @param pairs An even list of strings acting as key-value pairs.
* @return A newly created metric name with the specified tags associated
* with it.
* @see #tagged(Map)
*/
public MetricName tagged(String... pairs) {
if (pairs == null) {
return this;
}
if (pairs.length % 2 != 0) {
throw new IllegalArgumentException("Argument count must be even");
}
final Map<String, String> add = new HashMap<String, String>();
for (int i = 0; i < pairs.length; i += 2) {
add.put(pairs[i], pairs[i + 1]);
}
return tagged(add);
}
@Override
public String toString() {
if (tags.isEmpty()) {
return key;
//return key + "{}";
}
return key + tags;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((tags == null) ? 0 : tags.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;
}
MetricName other = (MetricName) obj;
if (key == null) {
if (other.key != null) {
return false;
}
} else if (!key.equals(other.key)) {
return false;
}
return tags.equals(other.tags);
}
@Override
public int compareTo(MetricName o) {
if (o == null) {
return -1;
}
int c = compareName(key, o.getKey());
if (c != 0) {
return c;
}
return compareTags(tags, o.getTags());
}
private int compareName(String left, String right) {
if (left == null && right == null) {
return 0;
}
if (left == null) {
return 1;
}
if (right == null) {
return -1;
}
return left.compareTo(right);
}
private int compareTags(Map<String, String> left, Map<String, String> right) {
if (left == null && right == null) {
return 0;
}
if (left == null) {
return 1;
}
if (right == null) {
return -1;
}
final Iterable<String> keys = uniqueSortedKeys(left, right);
for (final String key : keys) {
final String a = left.get(key);
final String b = right.get(key);
if (a == null && b == null) {
continue;
}
if (a == null) {
return -1;
}
if (b == null) {
return 1;
}
int c = a.compareTo(b);
if (c != 0) {
return c;
}
}
return 0;
}
private Iterable<String> uniqueSortedKeys(Map<String, String> left, Map<String, String> right) {
final Set<String> set = new TreeSet<String>(left.keySet());
set.addAll(right.keySet());
return set;
}
}

View file

@ -0,0 +1,511 @@
package org.xbib.metrics;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A registry of metric instances.
*/
public class MetricRegistry implements MetricSet {
private final ConcurrentMap<MetricName, Metric> metrics;
private final List<MetricRegistryListener> listeners;
/**
* Creates a new {@link MetricRegistry}.
*/
public MetricRegistry() {
this(new ConcurrentHashMap<MetricName, Metric>());
}
/**
* Creates a {@link MetricRegistry} with a custom {@link ConcurrentMap} implementation for use
* inside the registry. Call as the super-constructor to create a {@link MetricRegistry} with
* space- or time-bounded metric lifecycles, for example.
*
* @param metricsMap metrics map
*/
protected MetricRegistry(ConcurrentMap<MetricName, Metric> metricsMap) {
this.metrics = metricsMap;
this.listeners = new CopyOnWriteArrayList<MetricRegistryListener>();
}
/**
* @param klass the class
* @param names The remaining elements of the name
* @return A metric name matching the specified components.
* @see #name(String, String...)
*/
public static MetricName name(Class<?> klass, String... names) {
return name(klass.getName(), names);
}
/**
* Shorthand method for backwards compatibility in creating metric names.
*
* Uses {@link MetricName#build(String...)} for its
* heavy lifting.
*
* @param name The first element of the name
* @param names The remaining elements of the name
* @return A metric name matching the specified components.
* @see MetricName#build(String...)
*/
public static MetricName name(String name, String... names) {
final int length;
if (names == null) {
length = 0;
} else {
length = names.length;
}
final String[] parts = new String[length + 1];
parts[0] = name;
System.arraycopy(names, 0, parts, 1, length);
return MetricName.build(parts);
}
/**
* @param name the metric name
* @param metric the metric
* @param <T> the type of the metric
* @return {@code metric}
* @see #register(MetricName, Metric)
*/
@SuppressWarnings("unchecked")
public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException {
return register(MetricName.build(name), metric);
}
/**
* Given a {@link Metric}, registers it under the given name.
*
* @param name the name of the metric
* @param metric the metric
* @param <T> the type of the metric
* @return {@code metric}
* @throws IllegalArgumentException if the name is already registered
*/
@SuppressWarnings("unchecked")
public <T extends Metric> T register(MetricName name, T metric) throws IllegalArgumentException {
if (metric instanceof MetricSet) {
registerAll(name, (MetricSet) metric);
} else {
final Metric existing = metrics.putIfAbsent(name, metric);
if (existing == null) {
onMetricAdded(name, metric);
} else {
throw new IllegalArgumentException("A metric named " + name + " already exists");
}
}
return metric;
}
/**
* Given a metric set, registers them.
*
* @param metrics a set of metrics
* @throws IllegalArgumentException if any of the names are already registered
*/
public void registerAll(MetricSet metrics) throws IllegalArgumentException {
registerAll(null, metrics);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link CountMetric}
* @see #counter(MetricName)
*/
public CountMetric counter(String name) {
return counter(MetricName.build(name));
}
/**
* Return the {@link CountMetric} registered under this name; or create and register
* a new {@link CountMetric} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link CountMetric}
*/
public CountMetric counter(MetricName name) {
return getOrAdd(name, MetricBuilder.COUNTERS);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link Histogram}
* @see #histogram(MetricName)
*/
public Histogram histogram(String name) {
return histogram(MetricName.build(name));
}
/**
* Return the {@link Histogram} registered under this name; or create and register
* a new {@link Histogram} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link Histogram}
*/
public Histogram histogram(MetricName name) {
return getOrAdd(name, MetricBuilder.HISTOGRAMS);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link Meter}
* @see #meter(MetricName)
*/
public Meter meter(String name) {
return meter(MetricName.build(name));
}
/**
* Return the {@link Meter} registered under this name; or create and register
* a new {@link Meter} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link Meter}
*/
public Meter meter(MetricName name) {
return getOrAdd(name, MetricBuilder.METERS);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link Sampler}
* @see #timer(MetricName)
*/
public Sampler timer(String name) {
return timer(MetricName.build(name));
}
/**
* Return the {@link Sampler} registered under this name; or create and register
* a new {@link Sampler} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link Sampler}
*/
public Sampler timer(MetricName name) {
return getOrAdd(name, MetricBuilder.TIMERS);
}
/**
* Removes the metric with the given name.
*
* @param name the name of the metric
* @return whether or not the metric was removed
*/
public boolean remove(MetricName name) {
final Metric metric = metrics.remove(name);
if (metric != null) {
onMetricRemoved(name, metric);
return true;
}
return false;
}
/**
* Removes all metrics which match the given filter.
*
* @param filter a filter
*/
public void removeMatching(MetricFilter filter) {
for (Map.Entry<MetricName, Metric> entry : metrics.entrySet()) {
if (filter.matches(entry.getKey(), entry.getValue())) {
remove(entry.getKey());
}
}
}
/**
* Adds a {@link MetricRegistryListener} to a collection of listeners that will be notified on
* metric creation. Listeners will be notified in the order in which they are added.
* The listener will be notified of all existing metrics when it first registers.
*
* @param listener the listener that will be notified
*/
public void addListener(MetricRegistryListener listener) {
listeners.add(listener);
for (Map.Entry<MetricName, Metric> entry : metrics.entrySet()) {
notifyListenerOfAddedMetric(listener, entry.getValue(), entry.getKey());
}
}
/**
* Removes a {@link MetricRegistryListener} from this registry's collection of listeners.
*
* @param listener the listener that will be removed
*/
public void removeListener(MetricRegistryListener listener) {
listeners.remove(listener);
}
/**
* Returns a set of the names of all the metrics in the registry.
*
* @return the names of all the metrics
*/
public SortedSet<MetricName> getNames() {
return Collections.unmodifiableSortedSet(new TreeSet<>(metrics.keySet()));
}
/**
* Returns a map of all the gauges in the registry and their names.
*
* @return all the gauges in the registry
*/
@SuppressWarnings("rawtypes")
public SortedMap<MetricName, Gauge> getGauges() {
return getGauges(MetricFilter.ALL);
}
/**
* Returns a map of all the gauges in the registry and their names which match the given filter.
*
* @param filter the metric filter to match
* @return all the gauges in the registry
*/
@SuppressWarnings("rawtypes")
public SortedMap<MetricName, Gauge> getGauges(MetricFilter filter) {
return getMetrics(Gauge.class, filter);
}
/**
* Returns a map of all the counters in the registry and their names.
*
* @return all the counters in the registry
*/
public SortedMap<MetricName, CountMetric> getCounters() {
return getCounters(MetricFilter.ALL);
}
/**
* Returns a map of all the counters in the registry and their names which match the given
* filter.
*
* @param filter the metric filter to match
* @return all the counters in the registry
*/
public SortedMap<MetricName, CountMetric> getCounters(MetricFilter filter) {
return getMetrics(CountMetric.class, filter);
}
/**
* Returns a map of all the histograms in the registry and their names.
*
* @return all the histograms in the registry
*/
public SortedMap<MetricName, Histogram> getHistograms() {
return getHistograms(MetricFilter.ALL);
}
/**
* Returns a map of all the histograms in the registry and their names which match the given
* filter.
*
* @param filter the metric filter to match
* @return all the histograms in the registry
*/
public SortedMap<MetricName, Histogram> getHistograms(MetricFilter filter) {
return getMetrics(Histogram.class, filter);
}
/**
* Returns a map of all the meters in the registry and their names.
*
* @return all the meters in the registry
*/
public SortedMap<MetricName, Meter> getMeters() {
return getMeters(MetricFilter.ALL);
}
/**
* Returns a map of all the meters in the registry and their names which match the given filter.
*
* @param filter the metric filter to match
* @return all the meters in the registry
*/
public SortedMap<MetricName, Meter> getMeters(MetricFilter filter) {
return getMetrics(Meter.class, filter);
}
/**
* Returns a map of all the timers in the registry and their names.
*
* @return all the timers in the registry
*/
public SortedMap<MetricName, Sampler> getTimers() {
return getTimers(MetricFilter.ALL);
}
/**
* Returns a map of all the timers in the registry and their names which match the given filter.
*
* @param filter the metric filter to match
* @return all the timers in the registry
*/
public SortedMap<MetricName, Sampler> getTimers(MetricFilter filter) {
return getMetrics(Sampler.class, filter);
}
@SuppressWarnings("unchecked")
private <T extends Metric> T getOrAdd(MetricName name, MetricBuilder<T> builder) {
final Metric metric = metrics.get(name);
if (builder.isInstance(metric)) {
return (T) metric;
} else if (metric == null) {
try {
return register(name, builder.newMetric());
} catch (IllegalArgumentException e) {
final Metric added = metrics.get(name);
if (builder.isInstance(added)) {
return (T) added;
}
}
}
throw new IllegalArgumentException(name + " is already used for a different type of metric");
}
@SuppressWarnings("unchecked")
private <T extends Metric> SortedMap<MetricName, T> getMetrics(Class<T> klass, MetricFilter filter) {
final TreeMap<MetricName, T> timers = new TreeMap<MetricName, T>();
for (Map.Entry<MetricName, Metric> entry : metrics.entrySet()) {
if (klass.isInstance(entry.getValue()) && filter.matches(entry.getKey(),
entry.getValue())) {
timers.put(entry.getKey(), (T) entry.getValue());
}
}
return Collections.unmodifiableSortedMap(timers);
}
private void onMetricAdded(MetricName name, Metric metric) {
for (MetricRegistryListener listener : listeners) {
notifyListenerOfAddedMetric(listener, metric, name);
}
}
private void notifyListenerOfAddedMetric(MetricRegistryListener listener, Metric metric, MetricName name) {
if (metric instanceof Gauge) {
listener.onGaugeAdded(name, (Gauge<?>) metric);
} else if (metric instanceof CountMetric) {
listener.onCounterAdded(name, (CountMetric) metric);
} else if (metric instanceof Histogram) {
listener.onHistogramAdded(name, (Histogram) metric);
} else if (metric instanceof Meter) {
listener.onMeterAdded(name, (Meter) metric);
} else if (metric instanceof Sampler) {
listener.onTimerAdded(name, (Sampler) metric);
} else {
throw new IllegalArgumentException("Unknown metric type: " + metric.getClass());
}
}
private void onMetricRemoved(MetricName name, Metric metric) {
for (MetricRegistryListener listener : listeners) {
notifyListenerOfRemovedMetric(name, metric, listener);
}
}
private void notifyListenerOfRemovedMetric(MetricName name, Metric metric, MetricRegistryListener listener) {
if (metric instanceof Gauge) {
listener.onGaugeRemoved(name);
} else if (metric instanceof CountMetric) {
listener.onCounterRemoved(name);
} else if (metric instanceof Histogram) {
listener.onHistogramRemoved(name);
} else if (metric instanceof Meter) {
listener.onMeterRemoved(name);
} else if (metric instanceof Sampler) {
listener.onTimerRemoved(name);
} else {
throw new IllegalArgumentException("Unknown metric type: " + metric.getClass());
}
}
private void registerAll(MetricName prefix, MetricSet metrics) throws IllegalArgumentException {
if (prefix == null) {
prefix = MetricName.EMPTY;
}
for (Map.Entry<MetricName, Metric> entry : metrics.getMetrics().entrySet()) {
if (entry.getValue() instanceof MetricSet) {
registerAll(MetricName.join(prefix, entry.getKey()), (MetricSet) entry.getValue());
} else {
register(MetricName.join(prefix, entry.getKey()), entry.getValue());
}
}
}
@Override
public Map<MetricName, Metric> getMetrics() {
return Collections.unmodifiableMap(metrics);
}
/**
* A quick and easy way of capturing the notion of default metrics.
*/
private interface MetricBuilder<T extends Metric> {
MetricBuilder<CountMetric> COUNTERS = new MetricBuilder<CountMetric>() {
@Override
public CountMetric newMetric() {
return new CountMetric();
}
@Override
public boolean isInstance(Metric metric) {
return CountMetric.class.isInstance(metric);
}
};
MetricBuilder<Histogram> HISTOGRAMS = new MetricBuilder<Histogram>() {
@Override
public Histogram newMetric() {
return new Histogram(new ExponentiallyDecayingReservoir());
}
@Override
public boolean isInstance(Metric metric) {
return Histogram.class.isInstance(metric);
}
};
MetricBuilder<Meter> METERS = new MetricBuilder<Meter>() {
@Override
public Meter newMetric() {
return new Meter();
}
@Override
public boolean isInstance(Metric metric) {
return Meter.class.isInstance(metric);
}
};
MetricBuilder<Sampler> TIMERS = new MetricBuilder<Sampler>() {
@Override
public Sampler newMetric() {
return new Sampler();
}
@Override
public boolean isInstance(Metric metric) {
return Sampler.class.isInstance(metric);
}
};
T newMetric();
boolean isInstance(Metric metric);
}
}

View file

@ -0,0 +1,128 @@
package org.xbib.metrics;
import java.util.EventListener;
/**
* Listeners for events from the registry. Listeners must be thread-safe.
*/
public interface MetricRegistryListener extends EventListener {
/**
* Called when a {@link Gauge} is added to the registry.
*
* @param name the gauge's name
* @param gauge the gauge
*/
void onGaugeAdded(MetricName name, Gauge<?> gauge);
/**
* Called when a {@link Gauge} is removed from the registry.
*
* @param name the gauge's name
*/
void onGaugeRemoved(MetricName name);
/**
* Called when a {@link CountMetric} is added to the registry.
*
* @param name the counter's name
* @param counter the counter
*/
void onCounterAdded(MetricName name, CountMetric counter);
/**
* Called when a {@link CountMetric} is removed from the registry.
*
* @param name the counter's name
*/
void onCounterRemoved(MetricName name);
/**
* Called when a {@link Histogram} is added to the registry.
*
* @param name the histogram's name
* @param histogram the histogram
*/
void onHistogramAdded(MetricName name, Histogram histogram);
/**
* Called when a {@link Histogram} is removed from the registry.
*
* @param name the histogram's name
*/
void onHistogramRemoved(MetricName name);
/**
* Called when a {@link Meter} is added to the registry.
*
* @param name the meter's name
* @param meter the meter
*/
void onMeterAdded(MetricName name, Meter meter);
/**
* Called when a {@link Meter} is removed from the registry.
*
* @param name the meter's name
*/
void onMeterRemoved(MetricName name);
/**
* Called when a {@link Sampler} is added to the registry.
*
* @param name the sampler's name
* @param sampler the sampler
*/
void onTimerAdded(MetricName name, Sampler sampler);
/**
* Called when a {@link Sampler} is removed from the registry.
*
* @param name the timer's name
*/
void onTimerRemoved(MetricName name);
/**
* A no-op implementation of {@link MetricRegistryListener}.
*/
abstract class Base implements MetricRegistryListener {
@Override
public void onGaugeAdded(MetricName name, Gauge<?> gauge) {
}
@Override
public void onGaugeRemoved(MetricName name) {
}
@Override
public void onCounterAdded(MetricName name, CountMetric counter) {
}
@Override
public void onCounterRemoved(MetricName name) {
}
@Override
public void onHistogramAdded(MetricName name, Histogram histogram) {
}
@Override
public void onHistogramRemoved(MetricName name) {
}
@Override
public void onMeterAdded(MetricName name, Meter meter) {
}
@Override
public void onMeterRemoved(MetricName name) {
}
@Override
public void onTimerAdded(MetricName name, Sampler sampler) {
}
@Override
public void onTimerRemoved(MetricName name) {
}
}
}

View file

@ -0,0 +1,17 @@
package org.xbib.metrics;
import java.util.Map;
/**
* A set of named metrics.
*
* @see MetricRegistry#registerAll(MetricSet)
*/
public interface MetricSet extends Metric {
/**
* A map of metric names to metrics.
*
* @return the metrics
*/
Map<MetricName, Metric> getMetrics();
}

View file

@ -0,0 +1,27 @@
package org.xbib.metrics;
/**
* A statistically representative reservoir of a data stream.
*/
public interface Reservoir {
/**
* Returns the number of values recorded.
*
* @return the number of values recorded
*/
int size();
/**
* Adds a new recorded value to the reservoir.
*
* @param value a new recorded value
*/
void update(long value);
/**
* Returns a snapshot of the reservoir's values.
*
* @return a snapshot of the reservoir's values
*/
Snapshot getSnapshot();
}

View file

@ -0,0 +1,172 @@
package org.xbib.metrics;
import java.io.Closeable;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/**
* A sampler metric which aggregates timing durations and provides duration statistics, plus
* throughput statistics via {@link Meter}.
*/
public class Sampler implements Metered, Sampling {
private final Meter meter;
private final Histogram histogram;
private final Clock clock;
/**
* Creates a new {@link Sampler} using an {@link ExponentiallyDecayingReservoir} and the default
* {@link Clock}.
*/
public Sampler() {
this(new ExponentiallyDecayingReservoir());
}
/**
* Creates a new {@link Sampler} that uses the given {@link Reservoir}.
*
* @param reservoir the {@link Reservoir} implementation the sampler should use
*/
public Sampler(Reservoir reservoir) {
this(reservoir, Clock.defaultClock());
}
/**
* Creates a new {@link Sampler} that uses the given {@link Reservoir} and {@link Clock}.
*
* @param reservoir the {@link Reservoir} implementation the sampler should use
* @param clock the {@link Clock} implementation the sampler should use
*/
public Sampler(Reservoir reservoir, Clock clock) {
this.meter = new Meter(clock);
this.clock = clock;
this.histogram = new Histogram(reservoir);
}
/**
* Adds a recorded duration.
*
* @param duration the length of the duration
* @param unit the scale unit of {@code duration}
*/
public void update(long duration, TimeUnit unit) {
update(unit.toNanos(duration));
}
/**
* Records the duration of event.
*
* @param event a {@link Callable} whose {@link Callable#call()} method implements a process
* whose duration should be timed
* @param <T> the type of the value returned by {@code event}
* @return the value returned by {@code event}
* @throws Exception if {@code event} throws an {@link Exception}
*/
public <T> T time(Callable<T> event) throws Exception {
final long startTime = clock.getTick();
try {
return event.call();
} finally {
update(clock.getTick() - startTime);
}
}
/**
* Times and records the duration of an event.
*
* @param event a {@link Runnable} whose {@link Runnable#run()} method implements a process
* whose duration should be timed
*/
public void time(Runnable event) {
long startTime = this.clock.getTick();
try {
event.run();
} finally {
update(this.clock.getTick() - startTime);
}
}
/**
* Returns a new {@link Context}.
*
* @return a new {@link Context}
* @see Context
*/
public Context time() {
return new Context(this, clock);
}
@Override
public long getCount() {
return histogram.getCount();
}
@Override
public double getFifteenMinuteRate() {
return meter.getFifteenMinuteRate();
}
@Override
public double getFiveMinuteRate() {
return meter.getFiveMinuteRate();
}
@Override
public double getMeanRate() {
return meter.getMeanRate();
}
@Override
public double getOneMinuteRate() {
return meter.getOneMinuteRate();
}
@Override
public Snapshot getSnapshot() {
return histogram.getSnapshot();
}
private void update(long duration) {
if (duration >= 0) {
histogram.inc(duration);
meter.mark();
}
}
/**
* A timing context.
*
* @see Sampler#time()
*/
public static class Context implements Closeable {
private final Sampler sampler;
private final Clock clock;
private final long startTime;
private Context(Sampler sampler, Clock clock) {
this.sampler = sampler;
this.clock = clock;
this.startTime = clock.getTick();
}
/**
* Updates the sampler with the difference between current and start time. Call to this method will
* not reset the start time. Multiple calls result in multiple updates.
*
* @return the elapsed time in nanoseconds
*/
public long stop() {
final long elapsed = clock.getTick() - startTime;
sampler.update(elapsed, TimeUnit.NANOSECONDS);
return elapsed;
}
/**
* Equivalent to calling {@link #stop()}.
*/
@Override
public void close() {
stop();
}
}
}

View file

@ -0,0 +1,13 @@
package org.xbib.metrics;
/**
* An object which samples values.
*/
public interface Sampling {
/**
* Returns a snapshot of the values.
*
* @return a snapshot of the values
*/
Snapshot getSnapshot();
}

View file

@ -0,0 +1,121 @@
package org.xbib.metrics;
import java.io.OutputStream;
/**
* A statistical snapshot of a {@link Snapshot}.
*/
public abstract class Snapshot {
/**
* Returns the value at the given quantile.
*
* @param quantile a given quantile, in {@code [0..1]}
* @return the value in the distribution at {@code quantile}
*/
public abstract double getValue(double quantile);
/**
* Returns the entire set of values in the snapshot.
*
* @return the entire set of values
*/
public abstract long[] getValues();
/**
* Returns the number of values in the snapshot.
*
* @return the number of values
*/
public abstract int size();
/**
* Returns the median value in the distribution.
*
* @return the median value
*/
public double getMedian() {
return getValue(0.5);
}
/**
* Returns the value at the 75th percentile in the distribution.
*
* @return the value at the 75th percentile
*/
public double get75thPercentile() {
return getValue(0.75);
}
/**
* Returns the value at the 95th percentile in the distribution.
*
* @return the value at the 95th percentile
*/
public double get95thPercentile() {
return getValue(0.95);
}
/**
* Returns the value at the 98th percentile in the distribution.
*
* @return the value at the 98th percentile
*/
public double get98thPercentile() {
return getValue(0.98);
}
/**
* Returns the value at the 99th percentile in the distribution.
*
* @return the value at the 99th percentile
*/
public double get99thPercentile() {
return getValue(0.99);
}
/**
* Returns the value at the 99.9th percentile in the distribution.
*
* @return the value at the 99.9th percentile
*/
public double get999thPercentile() {
return getValue(0.999);
}
/**
* Returns the highest value in the snapshot.
*
* @return the highest value
*/
public abstract long getMax();
/**
* Returns the arithmetic mean of the values in the snapshot.
*
* @return the arithmetic mean
*/
public abstract double getMean();
/**
* Returns the lowest value in the snapshot.
*
* @return the lowest value
*/
public abstract long getMin();
/**
* Returns the standard deviation of the values in the snapshot.
*
* @return the standard value
*/
public abstract double getStdDev();
/**
* Writes the values of the snapshot to the given stream.
*
* @param output an output stream
*/
public abstract void dump(OutputStream output);
}

View file

@ -0,0 +1,206 @@
package org.xbib.metrics;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
/**
* A statistical snapshot of a {@link WeightedSnapshot}.
*/
public class WeightedSnapshot extends Snapshot {
private final long[] values;
private final double[] normWeights;
private final double[] quantiles;
/**
* Create a new {@link Snapshot} with the given values.
*
* @param values an unordered set of values in the reservoir
*/
public WeightedSnapshot(Collection<WeightedSample> values) {
final WeightedSample[] copy = values.toArray(new WeightedSample[]{});
Arrays.sort(copy, new Comparator<WeightedSample>() {
@Override
public int compare(WeightedSample o1, WeightedSample o2) {
if (o1.value > o2.value) {
return 1;
}
if (o1.value < o2.value) {
return -1;
}
return 0;
}
});
this.values = new long[copy.length];
this.normWeights = new double[copy.length];
this.quantiles = new double[copy.length];
double sumWeight = 0;
for (WeightedSample sample : copy) {
sumWeight += sample.weight;
}
for (int i = 0; i < copy.length; i++) {
this.values[i] = copy[i].value;
this.normWeights[i] = copy[i].weight / sumWeight;
}
for (int i = 1; i < copy.length; i++) {
this.quantiles[i] = this.quantiles[i - 1] + this.normWeights[i - 1];
}
}
/**
* Returns the value at the given quantile.
*
* @param quantile a given quantile, in {@code [0..1]}
* @return the value in the distribution at {@code quantile}
*/
@Override
public double getValue(double quantile) {
if (quantile < 0.0 || quantile > 1.0 || Double.isNaN(quantile)) {
throw new IllegalArgumentException(quantile + " is not in [0..1]");
}
if (values.length == 0) {
return 0.0;
}
int posx = Arrays.binarySearch(quantiles, quantile);
if (posx < 0) {
posx = ((-posx) - 1) - 1;
}
if (posx < 1) {
return values[0];
}
if (posx >= values.length) {
return values[values.length - 1];
}
return values[posx];
}
/**
* Returns the number of values in the snapshot.
*
* @return the number of values
*/
@Override
public int size() {
return values.length;
}
/**
* Returns the entire set of values in the snapshot.
*
* @return the entire set of values
*/
@Override
public long[] getValues() {
return Arrays.copyOf(values, values.length);
}
/**
* Returns the highest value in the snapshot.
*
* @return the highest value
*/
@Override
public long getMax() {
if (values.length == 0) {
return 0;
}
return values[values.length - 1];
}
/**
* Returns the lowest value in the snapshot.
*
* @return the lowest value
*/
@Override
public long getMin() {
if (values.length == 0) {
return 0;
}
return values[0];
}
/**
* Returns the weighted arithmetic mean of the values in the snapshot.
*
* @return the weighted arithmetic mean
*/
@Override
public double getMean() {
if (values.length == 0) {
return 0;
}
double sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i] * normWeights[i];
}
return sum;
}
/**
* Returns the weighted standard deviation of the values in the snapshot.
*
* @return the weighted standard deviation value
*/
@Override
public double getStdDev() {
// two-pass algorithm for variance, avoids numeric overflow
if (values.length <= 1) {
return 0;
}
final double mean = getMean();
double variance = 0;
for (int i = 0; i < values.length; i++) {
final double diff = values[i] - mean;
variance += normWeights[i] * diff * diff;
}
return Math.sqrt(variance);
}
/**
* Writes the values of the snapshot to the given stream.
*
* @param output an output stream
*/
@Override
public void dump(OutputStream output) {
try (PrintWriter out = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8))) {
for (long value : values) {
out.printf("%d%n", value);
}
}
}
/**
* A single sample item with value and its weights for {@link WeightedSnapshot}.
*/
public static class WeightedSample {
public final long value;
public final double weight;
public WeightedSample(long value, double weight) {
this.value = value;
this.weight = weight;
}
}
}

View file

@ -0,0 +1,4 @@
/**
* Classes for metrics.
*/
package org.xbib.metrics;